Package lib :: Module scripting
[hide private]
[frames] | no frames]

Source Code for Module lib.scripting

   1  # -*- coding: utf-8 -*- 
   2   
   3  # Copyright (C) 2011 Chris Dekter 
   4  # 
   5  # This program is free software: you can redistribute it and/or modify 
   6  # it under the terms of the GNU General Public License as published by 
   7  # the Free Software Foundation, either version 3 of the License, or 
   8  # (at your option) any later version. 
   9  # 
  10  # This program is distributed in the hope that it will be useful, 
  11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  # GNU General Public License for more details. 
  14  # 
  15  # You should have received a copy of the GNU General Public License 
  16  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  17   
  18  import subprocess, threading, time, re 
  19  import common#, model, iomediator 
  20  #if common.USING_QT: 
  21  #    from PyQt4.QtGui import QClipboard, QApplication 
  22  #else: 
  23  #    from gi.repository import Gtk, Gdk 
  24   
25 -class Keyboard:
26 """ 27 Provides access to the keyboard for event generation. 28 """ 29
30 - def __init__(self, mediator):
31 self.mediator = mediator
32
33 - def send_keys(self, keyString):
34 """ 35 Send a sequence of keys via keyboard events 36 37 Usage: C{keyboard.send_keys(keyString)} 38 39 @param keyString: string of keys (including special keys) to send 40 """ 41 self.mediator.interface.begin_send() 42 self.mediator.send_string(keyString.decode("utf-8")) 43 self.mediator.interface.finish_send()
44
45 - def send_key(self, key, repeat=1):
46 """ 47 Send a keyboard event 48 49 Usage: C{keyboard.send_key(key, repeat=1)} 50 51 @param key: they key to be sent (e.g. "s" or "<enter>") 52 @param repeat: number of times to repeat the key event 53 """ 54 for x in xrange(repeat): 55 self.mediator.send_key(key.decode("utf-8")) 56 self.mediator.flush()
57
58 - def press_key(self, key):
59 """ 60 Send a key down event 61 62 Usage: C{keyboard.press_key(key)} 63 64 The key will be treated as down until a matching release_key() is sent. 65 @param key: they key to be pressed (e.g. "s" or "<enter>") 66 """ 67 self.mediator.press_key(key.decode("utf-8"))
68
69 - def release_key(self, key):
70 """ 71 Send a key up event 72 73 Usage: C{keyboard.release_key(key)} 74 75 If the specified key was not made down using press_key(), the event will be 76 ignored. 77 @param key: they key to be released (e.g. "s" or "<enter>") 78 """ 79 self.mediator.release_key(key.decode("utf-8"))
80
81 - def fake_keypress(self, key, repeat=1):
82 """ 83 Fake a keypress 84 85 Usage: C{keyboard.fake_keypress(key, repeat=1)} 86 87 Uses XTest to 'fake' a keypress. This is useful to send keypresses to some 88 applications which won't respond to keyboard.send_key() 89 90 @param key: they key to be sent (e.g. "s" or "<enter>") 91 @param repeat: number of times to repeat the key event 92 """ 93 for x in xrange(repeat): 94 self.mediator.fake_keypress(key.decode("utf-8"))
95
96 - def wait_for_keypress(self, key, modifiers=[], timeOut=10.0):
97 """ 98 Wait for a keypress or key combination 99 100 Usage: C{keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0)} 101 102 Note: this function cannot be used to wait for modifier keys on their own 103 104 @param key: they key to wait for 105 @param modifiers: list of modifiers that should be pressed with the key 106 @param timeOut: maximum time, in seconds, to wait for the keypress to occur 107 """ 108 w = iomediator.Waiter(key, modifiers, None, timeOut) 109 w.wait()
110 111
112 -class Mouse:
113 """ 114 Provides access to send mouse clicks 115 """
116 - def __init__(self, mediator):
117 self.mediator = mediator
118
119 - def click_relative(self, x, y, button):
120 """ 121 Send a mouse click relative to the active window 122 123 Usage: C{mouse.click_relative(x, y, button)} 124 125 @param x: x-coordinate in pixels, relative to upper left corner of window 126 @param y: y-coordinate in pixels, relative to upper left corner of window 127 @param button: mouse button to simulate (left=1, middle=2, right=3) 128 """ 129 self.mediator.send_mouse_click(x, y, button, True)
130
131 - def click_relative_self(self, x, y, button):
132 """ 133 Send a mouse click relative to the current mouse position 134 135 Usage: C{mouse.click_relative_self(x, y, button)} 136 137 @param x: x-offset in pixels, relative to current mouse position 138 @param y: y-offset in pixels, relative to current mouse position 139 @param button: mouse button to simulate (left=1, middle=2, right=3) 140 """ 141 self.mediator.send_mouse_click_relative(x, y, button)
142
143 - def click_absolute(self, x, y, button):
144 """ 145 Send a mouse click relative to the screen (absolute) 146 147 Usage: C{mouse.click_absolute(x, y, button)} 148 149 @param x: x-coordinate in pixels, relative to upper left corner of window 150 @param y: y-coordinate in pixels, relative to upper left corner of window 151 @param button: mouse button to simulate (left=1, middle=2, right=3) 152 """ 153 self.mediator.send_mouse_click(x, y, button, False)
154
155 - def wait_for_click(self, button, timeOut=10.0):
156 """ 157 Wait for a mouse click 158 159 Usage: C{mouse.wait_for_click(self, button, timeOut=10.0)} 160 161 @param button: they mouse button click to wait for as a button number, 1-9 162 @param timeOut: maximum time, in seconds, to wait for the keypress to occur 163 """ 164 button = int(button) 165 w = iomediator.Waiter(None, None, button, timeOut) 166 w.wait()
167 168
169 -class Store(dict):
170 """ 171 Allows persistent storage of values between invocations of the script. 172 """ 173
174 - def set_value(self, key, value):
175 """ 176 Store a value 177 178 Usage: C{store.set_value(key, value)} 179 """ 180 self[key] = value
181
182 - def get_value(self, key):
183 """ 184 Get a value 185 186 Usage: C{store.get_value(key)} 187 """ 188 return self[key]
189
190 - def remove_value(self, key):
191 """ 192 Remove a value 193 194 Usage: C{store.remove_value(key)} 195 """ 196 del self[key]
197
198 - def set_global_value(self, key, value):
199 """ 200 Store a global value 201 202 Usage: C{store.set_global_value(key, value)} 203 204 The value stored with this method will be available to all scripts. 205 """ 206 Store.GLOBALS[key] = value
207
208 - def get_global_value(self, key):
209 """ 210 Get a global value 211 212 Usage: C{store.get_global_value(key)} 213 """ 214 return self.GLOBALS[key]
215
216 - def remove_global_value(self, key):
217 """ 218 Remove a global value 219 220 Usage: C{store.remove_global_value(key)} 221 """ 222 del self.GLOBALS[key]
223 224
225 -class QtDialog:
226 """ 227 Provides a simple interface for the display of some basic dialogs to collect information from the user. 228 229 This version uses KDialog to integrate well with KDE. To pass additional arguments to KDialog that are 230 not specifically handled, use keyword arguments. For example, to pass the --geometry argument to KDialog 231 to specify the desired size of the dialog, pass C{geometry="700x400"} as one of the parameters. All 232 keyword arguments must be given as strings. 233 234 A note on exit codes: an exit code of 0 indicates that the user clicked OK. 235 """ 236
237 - def _run_kdialog(self, title, args, kwargs):
238 for k, v in kwargs.iteritems(): 239 args.append("--" + k) 240 args.append(v) 241 242 p = subprocess.Popen(["kdialog", "--title", title] + args, stdout=subprocess.PIPE) 243 retCode = p.wait() 244 output = p.stdout.read()[:-1] # Drop trailing newline 245 246 return (retCode, output)
247
248 - def info_dialog(self, title="Information", message="", **kwargs):
249 """ 250 Show an information dialog 251 252 Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} 253 254 @param title: window title for the dialog 255 @param message: message displayed in the dialog 256 @return: a tuple containing the exit code and user input 257 @rtype: C{tuple(int, str)} 258 """ 259 return self._run_kdialog(title, ["--msgbox", message], kwargs)
260
261 - def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs):
262 """ 263 Show an input dialog 264 265 Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} 266 267 @param title: window title for the dialog 268 @param message: message displayed above the input box 269 @param default: default value for the input box 270 @return: a tuple containing the exit code and user input 271 @rtype: C{tuple(int, str)} 272 """ 273 return self._run_kdialog(title, ["--inputbox", message, default], kwargs)
274
275 - def password_dialog(self, title="Enter password", message="Enter password", **kwargs):
276 """ 277 Show a password input dialog 278 279 Usage: C{dialog.password_dialog(title="Enter password", message="Enter password", **kwargs)} 280 281 @param title: window title for the dialog 282 @param message: message displayed above the password input box 283 @return: a tuple containing the exit code and user input 284 @rtype: C{tuple(int, str)} 285 """ 286 return self._run_kdialog(title, ["--password", message], kwargs)
287
288 - def combo_menu(self, options, title="Choose an option", message="Choose an option", **kwargs):
289 """ 290 Show a combobox menu 291 292 Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option", **kwargs)} 293 294 @param options: list of options (strings) for the dialog 295 @param title: window title for the dialog 296 @param message: message displayed above the combobox 297 @return: a tuple containing the exit code and user choice 298 @rtype: C{tuple(int, str)} 299 """ 300 return self._run_kdialog(title, ["--combobox", message] + options, kwargs)
301
302 - def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs):
303 """ 304 Show a single-selection list menu 305 306 Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} 307 308 @param options: list of options (strings) for the dialog 309 @param title: window title for the dialog 310 @param message: message displayed above the list 311 @param default: default value to be selected 312 @return: a tuple containing the exit code and user choice 313 @rtype: C{tuple(int, str)} 314 """ 315 316 choices = [] 317 optionNum = 0 318 for option in options: 319 choices.append(str(optionNum)) 320 choices.append(option) 321 if option == default: 322 choices.append("on") 323 else: 324 choices.append("off") 325 optionNum += 1 326 327 retCode, result = self._run_kdialog(title, ["--radiolist", message] + choices, kwargs) 328 choice = options[int(result)] 329 330 return retCode, choice
331
332 - def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs):
333 """ 334 Show a multiple-selection list menu 335 336 Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} 337 338 @param options: list of options (strings) for the dialog 339 @param title: window title for the dialog 340 @param message: message displayed above the list 341 @param defaults: list of default values to be selected 342 @return: a tuple containing the exit code and user choice 343 @rtype: C{tuple(int, str)} 344 """ 345 346 choices = [] 347 optionNum = 0 348 for option in options: 349 choices.append(str(optionNum)) 350 choices.append(option) 351 if option in defaults: 352 choices.append("on") 353 else: 354 choices.append("off") 355 optionNum += 1 356 357 retCode, output = self._run_kdialog(title, ["--separate-output", "--checklist", message] + choices, kwargs) 358 results = output.split() 359 360 choices = [] 361 for index in results: 362 choices.append(options[int(index)]) 363 364 return retCode, choices
365
366 - def open_file(self, title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs):
367 """ 368 Show an Open File dialog 369 370 Usage: C{dialog.open_file(title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} 371 372 @param title: window title for the dialog 373 @param initialDir: starting directory for the file dialog 374 @param fileTypes: file type filter expression 375 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 376 @return: a tuple containing the exit code and file path 377 @rtype: C{tuple(int, str)} 378 """ 379 if rememberAs is not None: 380 return self._run_kdialog(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs], kwargs) 381 else: 382 return self._run_kdialog(title, ["--getopenfilename", initialDir, fileTypes], kwargs)
383
384 - def save_file(self, title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs):
385 """ 386 Show a Save As dialog 387 388 Usage: C{dialog.save_file(title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} 389 390 @param title: window title for the dialog 391 @param initialDir: starting directory for the file dialog 392 @param fileTypes: file type filter expression 393 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 394 @return: a tuple containing the exit code and file path 395 @rtype: C{tuple(int, str)} 396 """ 397 if rememberAs is not None: 398 return self._run_kdialog(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs], kwargs) 399 else: 400 return self._run_kdialog(title, ["--getsavefilename", initialDir, fileTypes], kwargs)
401
402 - def choose_directory(self, title="Select Directory", initialDir="~", rememberAs=None, **kwargs):
403 """ 404 Show a Directory Chooser dialog 405 406 Usage: C{dialog.choose_directory(title="Select Directory", initialDir="~", rememberAs=None, **kwargs)} 407 408 @param title: window title for the dialog 409 @param initialDir: starting directory for the directory chooser dialog 410 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 411 @return: a tuple containing the exit code and chosen path 412 @rtype: C{tuple(int, str)} 413 """ 414 if rememberAs is not None: 415 return self._run_kdialog(title, ["--getexistingdirectory", initialDir, ":" + rememberAs], kwargs) 416 else: 417 return self._run_kdialog(title, ["--getexistingdirectory", initialDir], kwargs)
418
419 - def choose_colour(self, title="Select Colour", **kwargs):
420 """ 421 Show a Colour Chooser dialog 422 423 Usage: C{dialog.choose_colour(title="Select Colour")} 424 425 @param title: window title for the dialog 426 @return: a tuple containing the exit code and colour 427 @rtype: C{tuple(int, str)} 428 """ 429 return self._run_kdialog(title, ["--getcolor"], kwargs)
430
431 - def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs):
432 """ 433 Show a calendar dialog 434 435 Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} 436 437 Note: the format and date parameters are not currently used 438 439 @param title: window title for the dialog 440 @param format: format of date to be returned 441 @param date: initial date as YYYY-MM-DD, otherwise today 442 @return: a tuple containing the exit code and date 443 @rtype: C{tuple(int, str)} 444 """ 445 return self._run_kdialog(title, ["--calendar"], kwargs)
446 447
448 -class System:
449 """ 450 Simplified access to some system commands. 451 """ 452
453 - def exec_command(self, command, getOutput=True):
454 """ 455 Execute a shell command 456 457 Usage: C{system.exec_command(command, getOutput=True)} 458 459 Set getOutput to False if the command does not exit and return immediately. Otherwise 460 AutoKey will not respond to any hotkeys/abbreviations etc until the process started 461 by the command exits. 462 463 @param command: command to be executed (including any arguments) - e.g. "ls -l" 464 @param getOutput: whether to capture the (stdout) output of the command 465 @raise subprocess.CalledProcessError: if the command returns a non-zero exit code 466 """ 467 if getOutput: 468 p = subprocess.Popen(command, shell=True, bufsize=-1, stdout=subprocess.PIPE) 469 retCode = p.wait() 470 output = p.stdout.read()[:-1] 471 if retCode != 0: 472 raise subprocess.CalledProcessError(retCode, output) 473 else: 474 return output 475 else: 476 subprocess.Popen(command, shell=True, bufsize=-1)
477
478 - def create_file(self, fileName, contents=""):
479 """ 480 Create a file with contents 481 482 Usage: C{system.create_file(fileName, contents="")} 483 484 @param fileName: full path to the file to be created 485 @param contents: contents to insert into the file 486 """ 487 f = open(fileName, "w") 488 f.write(contents) 489 f.close()
490 491
492 -class GtkDialog:
493 """ 494 Provides a simple interface for the display of some basic dialogs to collect information from the user. 495 496 This version uses Zenity to integrate well with GNOME. To pass additional arguments to Zenity that are 497 not specifically handled, use keyword arguments. For example, to pass the --timeout argument to Zenity 498 pass C{timeout="15"} as one of the parameters. All keyword arguments must be given as strings. 499 500 A note on exit codes: an exit code of 0 indicates that the user clicked OK. 501 """ 502
503 - def _run_zenity(self, title, args, kwargs):
504 for k, v in kwargs.iteritems(): 505 args.append("--" + k) 506 args.append(v) 507 508 p = subprocess.Popen(["zenity", "--title", title] + args, stdout=subprocess.PIPE) 509 retCode = p.wait() 510 output = p.stdout.read()[:-1] # Drop trailing newline 511 512 return (retCode, output)
513
514 - def info_dialog(self, title="Information", message="", **kwargs):
515 """ 516 Show an information dialog 517 518 Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} 519 520 @param title: window title for the dialog 521 @param message: message displayed in the dialog 522 @return: a tuple containing the exit code and user input 523 @rtype: C{tuple(int, str)} 524 """ 525 return self._run_zenity(title, ["--info", "--text", message], kwargs)
526
527 - def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs):
528 """ 529 Show an input dialog 530 531 Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} 532 533 @param title: window title for the dialog 534 @param message: message displayed above the input box 535 @param default: default value for the input box 536 @return: a tuple containing the exit code and user input 537 @rtype: C{tuple(int, str)} 538 """ 539 return self._run_zenity(title, ["--entry", "--text", message, "--entry-text", default], kwargs)
540
541 - def password_dialog(self, title="Enter password", message="Enter password", **kwargs):
542 """ 543 Show a password input dialog 544 545 Usage: C{dialog.password_dialog(title="Enter password", message="Enter password")} 546 547 @param title: window title for the dialog 548 @param message: message displayed above the password input box 549 @return: a tuple containing the exit code and user input 550 @rtype: C{tuple(int, str)} 551 """ 552 return self._run_zenity(title, ["--entry", "--text", message, "--hide-text"], kwargs) 553 554 #def combo_menu(self, options, title="Choose an option", message="Choose an option"): 555 """ 556 Show a combobox menu - not supported by zenity 557 558 Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option")} 559 560 @param options: list of options (strings) for the dialog 561 @param title: window title for the dialog 562 @param message: message displayed above the combobox 563 """
564 #return self._run_zenity(title, ["--combobox", message] + options) 565
566 - def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs):
567 """ 568 Show a single-selection list menu 569 570 Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} 571 572 @param options: list of options (strings) for the dialog 573 @param title: window title for the dialog 574 @param message: message displayed above the list 575 @param default: default value to be selected 576 @return: a tuple containing the exit code and user choice 577 @rtype: C{tuple(int, str)} 578 """ 579 580 choices = [] 581 #optionNum = 0 582 for option in options: 583 if option == default: 584 choices.append("TRUE") 585 else: 586 choices.append("FALSE") 587 588 #choices.append(str(optionNum)) 589 choices.append(option) 590 #optionNum += 1 591 592 return self._run_zenity(title, ["--list", "--radiolist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs)
593 594 #return retCode, choice 595
596 - def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs):
597 """ 598 Show a multiple-selection list menu 599 600 Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} 601 602 @param options: list of options (strings) for the dialog 603 @param title: window title for the dialog 604 @param message: message displayed above the list 605 @param defaults: list of default values to be selected 606 @return: a tuple containing the exit code and user choice 607 @rtype: C{tuple(int, str)} 608 """ 609 610 choices = [] 611 #optionNum = 0 612 for option in options: 613 if option in defaults: 614 choices.append("TRUE") 615 else: 616 choices.append("FALSE") 617 618 #choices.append(str(optionNum)) 619 choices.append(option) 620 #optionNum += 1 621 622 retCode, output = self._run_zenity(title, ["--list", "--checklist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs) 623 results = output.split('|') 624 625 #choices = [] 626 #for choice in results: 627 # choices.append(choice) 628 629 return retCode, results
630
631 - def open_file(self, title="Open File", **kwargs):
632 """ 633 Show an Open File dialog 634 635 Usage: C{dialog.open_file(title="Open File", **kwargs)} 636 637 @param title: window title for the dialog 638 @return: a tuple containing the exit code and file path 639 @rtype: C{tuple(int, str)} 640 """ 641 #if rememberAs is not None: 642 # return self._run_zenity(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs]) 643 #else: 644 return self._run_zenity(title, ["--file-selection"], kwargs)
645
646 - def save_file(self, title="Save As", **kwargs):
647 """ 648 Show a Save As dialog 649 650 Usage: C{dialog.save_file(title="Save As", **kwargs)} 651 652 @param title: window title for the dialog 653 @return: a tuple containing the exit code and file path 654 @rtype: C{tuple(int, str)} 655 """ 656 #if rememberAs is not None: 657 # return self._run_zenity(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs]) 658 #else: 659 return self._run_zenity(title, ["--file-selection", "--save"], kwargs)
660
661 - def choose_directory(self, title="Select Directory", initialDir="~", **kwargs):
662 """ 663 Show a Directory Chooser dialog 664 665 Usage: C{dialog.choose_directory(title="Select Directory", **kwargs)} 666 667 @param title: window title for the dialog 668 @return: a tuple containing the exit code and path 669 @rtype: C{tuple(int, str)} 670 """ 671 #if rememberAs is not None: 672 # return self._run_zenity(title, ["--getexistingdirectory", initialDir, ":" + rememberAs]) 673 #else: 674 return self._run_zenity(title, ["--file-selection", "--directory"], kwargs) 675 676 #def choose_colour(self, title="Select Colour"): 677 """ 678 Show a Colour Chooser dialog - not supported by zenity 679 680 Usage: C{dialog.choose_colour(title="Select Colour")} 681 682 @param title: window title for the dialog 683 """
684 #return self._run_zenity(title, ["--getcolor"]) 685
686 - def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs):
687 """ 688 Show a calendar dialog 689 690 Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} 691 692 @param title: window title for the dialog 693 @param format: format of date to be returned 694 @param date: initial date as YYYY-MM-DD, otherwise today 695 @return: a tuple containing the exit code and date 696 @rtype: C{tuple(int, str)} 697 """ 698 if re.match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", date): 699 year = date[0:4] 700 month = date[5:7] 701 day = date[8:10] 702 date_args = ["--year=" + year, "--month=" + month, "--day=" + day] 703 else: 704 date_args = [] 705 return self._run_zenity(title, ["--calendar", "--date-format=" + format] + date_args, kwargs)
706 707
708 -class QtClipboard:
709 """ 710 Read/write access to the X selection and clipboard - QT version 711 """ 712
713 - def __init__(self, app):
714 self.clipBoard = QApplication.clipboard() 715 self.app = app
716
717 - def fill_selection(self, contents):
718 """ 719 Copy text into the X selection 720 721 Usage: C{clipboard.fill_selection(contents)} 722 723 @param contents: string to be placed in the selection 724 """ 725 self.__execAsync(self.__fillSelection, contents)
726
727 - def __fillSelection(self, string):
728 self.clipBoard.setText(string, QClipboard.Selection) 729 self.sem.release()
730
731 - def get_selection(self):
732 """ 733 Read text from the X selection 734 735 Usage: C{clipboard.get_selection()} 736 737 @return: text contents of the mouse selection 738 @rtype: C{str} 739 """ 740 self.__execAsync(self.__getSelection) 741 return unicode(self.text)
742
743 - def __getSelection(self):
744 self.text = self.clipBoard.text(QClipboard.Selection) 745 self.sem.release()
746
747 - def fill_clipboard(self, contents):
748 """ 749 Copy text into the clipboard 750 751 Usage: C{clipboard.fill_clipboard(contents)} 752 753 @param contents: string to be placed in the selection 754 """ 755 self.__execAsync(self.__fillClipboard, contents)
756
757 - def __fillClipboard(self, string):
758 self.clipBoard.setText(string, QClipboard.Clipboard) 759 self.sem.release()
760
761 - def get_clipboard(self):
762 """ 763 Read text from the clipboard 764 765 Usage: C{clipboard.get_clipboard()} 766 767 @return: text contents of the clipboard 768 @rtype: C{str} 769 """ 770 self.__execAsync(self.__getClipboard) 771 return unicode(self.text)
772
773 - def __getClipboard(self):
774 self.text = self.clipBoard.text(QClipboard.Clipboard) 775 self.sem.release()
776
777 - def __execAsync(self, callback, *args):
778 self.sem = threading.Semaphore(0) 779 self.app.exec_in_main(callback, *args) 780 self.sem.acquire()
781 782
783 -class GtkClipboard:
784 """ 785 Read/write access to the X selection and clipboard - GTK version 786 """ 787
788 - def __init__(self, app):
789 self.clipBoard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 790 self.selection = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) 791 self.app = app
792
793 - def fill_selection(self, contents):
794 """ 795 Copy text into the X selection 796 797 Usage: C{clipboard.fill_selection(contents)} 798 799 @param contents: string to be placed in the selection 800 """ 801 #self.__execAsync(self.__fillSelection, contents) 802 self.__fillSelection(contents)
803
804 - def __fillSelection(self, string):
805 Gdk.threads_enter() 806 self.selection.set_text(string.encode("utf-8")) 807 Gdk.threads_leave()
808 #self.sem.release() 809
810 - def get_selection(self):
811 """ 812 Read text from the X selection 813 814 Usage: C{clipboard.get_selection()} 815 816 @return: text contents of the mouse selection 817 @rtype: C{str} 818 @raise Exception: if no text was found in the selection 819 """ 820 self.__execAsync(self.selection.request_text, self.__receive) 821 if self.text is not None: 822 return self.text.decode("utf-8") 823 else: 824 raise Exception("No text found in X selection")
825
826 - def __receive(self, cb, text, data=None):
827 self.text = text 828 self.sem.release()
829
830 - def fill_clipboard(self, contents):
831 """ 832 Copy text into the clipboard 833 834 Usage: C{clipboard.fill_clipboard(contents)} 835 836 @param contents: string to be placed in the selection 837 """ 838 self.__fillClipboard(contents)
839
840 - def __fillClipboard(self, string):
841 Gdk.threads_enter() 842 self.clipBoard.set_text(string.encode("utf-8")) 843 Gdk.threads_leave()
844 #self.sem.release() 845
846 - def get_clipboard(self):
847 """ 848 Read text from the clipboard 849 850 Usage: C{clipboard.get_clipboard()} 851 852 @return: text contents of the clipboard 853 @rtype: C{str} 854 @raise Exception: if no text was found on the clipboard 855 """ 856 self.__execAsync(self.clipBoard.request_text, self.__receive) 857 if self.text is not None: 858 return self.text.decode("utf-8") 859 else: 860 raise Exception("No text found on clipboard")
861
862 - def __execAsync(self, callback, *args):
863 self.sem = threading.Semaphore(0) 864 Gdk.threads_enter() 865 callback(*args) 866 Gdk.threads_leave() 867 self.sem.acquire()
868 869
870 -class Window:
871 """ 872 Basic window management using wmctrl 873 874 Note: in all cases where a window title is required (with the exception of wait_for_focus()), 875 two special values of window title are permitted: 876 877 :ACTIVE: - select the currently active window 878 :SELECT: - select the desired window by clicking on it 879 """ 880
881 - def __init__(self, mediator):
882 self.mediator = mediator
883
884 - def wait_for_focus(self, title, timeOut=5):
885 """ 886 Wait for window with the given title to have focus 887 888 Usage: C{window.wait_for_focus(title, timeOut=5)} 889 890 If the window becomes active, returns True. Otherwise, returns False if 891 the window has not become active by the time the timeout has elapsed. 892 893 @param title: title to match against (as a regular expression) 894 @param timeOut: period (seconds) to wait before giving up 895 @rtype: boolean 896 """ 897 regex = re.compile(title) 898 waited = 0 899 while waited <= timeOut: 900 if regex.match(self.mediator.interface.get_window_title()): 901 return True 902 903 if timeOut == 0: 904 break # zero length timeout, if not matched go straight to end 905 906 time.sleep(0.3) 907 waited += 0.3 908 909 return False
910
911 - def wait_for_exist(self, title, timeOut=5):
912 """ 913 Wait for window with the given title to be created 914 915 Usage: C{window.wait_for_exist(title, timeOut=5)} 916 917 If the window is in existence, returns True. Otherwise, returns False if 918 the window has not been created by the time the timeout has elapsed. 919 920 @param title: title to match against (as a regular expression) 921 @param timeOut: period (seconds) to wait before giving up 922 @rtype: boolean 923 """ 924 regex = re.compile(title) 925 waited = 0 926 while waited <= timeOut: 927 retCode, output = self._run_wmctrl(["-l"]) 928 for line in output.split('\n'): 929 if regex.match(line[14:].split(' ', 1)[-1]): 930 return True 931 932 if timeOut == 0: 933 break # zero length timeout, if not matched go straight to end 934 935 time.sleep(0.3) 936 waited += 0.3 937 938 return False
939
940 - def activate(self, title, switchDesktop=False, matchClass=False):
941 """ 942 Activate the specified window, giving it input focus 943 944 Usage: C{window.activate(title, switchDesktop=False, matchClass=False)} 945 946 If switchDesktop is False (default), the window will be moved to the current desktop 947 and activated. Otherwise, switch to the window's current desktop and activate it there. 948 949 @param title: window title to match against (as case-insensitive substring match) 950 @param switchDesktop: whether or not to switch to the window's current desktop 951 @param matchClass: if True, match on the window class instead of the title 952 """ 953 if switchDesktop: 954 args = ["-a", title] 955 else: 956 args = ["-R", title] 957 if matchClass: 958 args += ["-x"] 959 self._run_wmctrl(args)
960
961 - def close(self, title, matchClass=False):
962 """ 963 Close the specified window gracefully 964 965 Usage: C{window.close(title, matchClass=False)} 966 967 @param title: window title to match against (as case-insensitive substring match) 968 @param matchClass: if True, match on the window class instead of the title 969 """ 970 if matchClass: 971 self._run_wmctrl(["-c", title, "-x"]) 972 else: 973 self._run_wmctrl(["-c", title])
974
975 - def resize_move(self, title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False):
976 """ 977 Resize and/or move the specified window 978 979 Usage: C{window.close(title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)} 980 981 Leaving and of the position/dimension values as the default (-1) will cause that 982 value to be left unmodified. 983 984 @param title: window title to match against (as case-insensitive substring match) 985 @param xOrigin: new x origin of the window (upper left corner) 986 @param yOrigin: new y origin of the window (upper left corner) 987 @param width: new width of the window 988 @param height: new height of the window 989 @param matchClass: if True, match on the window class instead of the title 990 """ 991 mvArgs = ["0", str(xOrigin), str(yOrigin), str(width), str(height)] 992 if matchClass: 993 xArgs = ["-x"] 994 else: 995 xArgs = [] 996 self._run_wmctrl(["-r", title, "-e", ','.join(mvArgs)] + xArgs)
997 998
999 - def move_to_desktop(self, title, deskNum, matchClass=False):
1000 """ 1001 Move the specified window to the given desktop 1002 1003 Usage: C{window.move_to_desktop(title, deskNum, matchClass=False)} 1004 1005 @param title: window title to match against (as case-insensitive substring match) 1006 @param deskNum: desktop to move the window to (note: zero based) 1007 @param matchClass: if True, match on the window class instead of the title 1008 """ 1009 if matchClass: 1010 xArgs = ["-x"] 1011 else: 1012 xArgs = [] 1013 self._run_wmctrl(["-r", title, "-t", str(deskNum)] + xArgs)
1014 1015
1016 - def switch_desktop(self, deskNum):
1017 """ 1018 Switch to the specified desktop 1019 1020 Usage: C{window.switch_desktop(deskNum)} 1021 1022 @param deskNum: desktop to switch to (note: zero based) 1023 """ 1024 self._run_wmctrl(["-s", str(deskNum)])
1025
1026 - def set_property(self, title, action, prop, matchClass=False):
1027 """ 1028 Set a property on the given window using the specified action 1029 1030 Usage: C{window.set_property(title, action, prop, matchClass=False)} 1031 1032 Allowable actions: C{add, remove, toggle} 1033 Allowable properties: C{modal, sticky, maximized_vert, maximized_horz, shaded, skip_taskbar, 1034 skip_pager, hidden, fullscreen, above} 1035 1036 @param title: window title to match against (as case-insensitive substring match) 1037 @param action: one of the actions listed above 1038 @param prop: one of the properties listed above 1039 @param matchClass: if True, match on the window class instead of the title 1040 """ 1041 if matchClass: 1042 xArgs = ["-x"] 1043 else: 1044 xArgs = [] 1045 self._run_wmctrl(["-r", title, "-b" + action + ',' + prop] + xArgs)
1046
1047 - def get_active_geometry(self):
1048 """ 1049 Get the geometry of the currently active window 1050 1051 Usage: C{window.get_active_geometry()} 1052 1053 @return: a 4-tuple containing the x-origin, y-origin, width and height of the window (in pixels) 1054 @rtype: C{tuple(int, int, int, int)} 1055 """ 1056 active = self.mediator.interface.get_window_title() 1057 result, output = self._run_wmctrl(["-l", "-G"]) 1058 matchingLine = None 1059 for line in output.split('\n'): 1060 if active in line[34:].split(' ', 1)[-1]: 1061 matchingLine = line 1062 1063 if matchingLine is not None: 1064 output = matchingLine.split()[2:6] 1065 return map(int, output) 1066 else: 1067 return None
1068
1069 - def get_active_title(self):
1070 """ 1071 Get the visible title of the currently active window 1072 1073 Usage: C{window.get_active_title()} 1074 1075 @return: the visible title of the currentle active window 1076 @rtype: C{str} 1077 """ 1078 return self.mediator.interface.get_window_title()
1079
1080 - def get_active_class(self):
1081 """ 1082 Get the class of the currently active window 1083 1084 Usage: C{window.get_active_class()} 1085 1086 @return: the class of the currentle active window 1087 @rtype: C{str} 1088 """ 1089 return self.mediator.interface.get_window_class()
1090
1091 - def _run_wmctrl(self, args):
1092 p = subprocess.Popen(["wmctrl"] + args, stdout=subprocess.PIPE) 1093 retCode = p.wait() 1094 output = p.stdout.read()[:-1] # Drop trailing newline 1095 1096 return (retCode, output)
1097 1098
1099 -class Engine:
1100 """ 1101 Provides access to the internals of AutoKey. 1102 1103 Note that any configuration changes made using this API while the configuration window 1104 is open will not appear until it is closed and re-opened. 1105 """ 1106
1107 - def __init__(self, configManager, runner):
1108 self.configManager = configManager 1109 self.runner = runner 1110 self.monitor = configManager.app.monitor 1111 self.__returnValue = ''
1112
1113 - def get_folder(self, title):
1114 """ 1115 Retrieve a folder by its title 1116 1117 Usage: C{engine.get_folder(title)} 1118 1119 Note that if more than one folder has the same title, only the first match will be 1120 returned. 1121 """ 1122 for folder in self.configManager.allFolders: 1123 if folder.title == title: 1124 return folder 1125 return None
1126
1127 - def create_phrase(self, folder, description, contents):
1128 """ 1129 Create a text phrase 1130 1131 Usage: C{engine.create_phrase(folder, description, contents)} 1132 1133 A new phrase with no abbreviation or hotkey is created in the specified folder 1134 1135 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1136 @param description: description for the phrase 1137 @param contents: the expansion text 1138 """ 1139 self.monitor.suspend() 1140 p = model.Phrase(description, contents) 1141 folder.add_item(p) 1142 p.persist() 1143 self.monitor.unsuspend() 1144 self.configManager.config_altered(False)
1145
1146 - def create_abbreviation(self, folder, description, abbr, contents):
1147 """ 1148 Create a text abbreviation 1149 1150 Usage: C{engine.create_abbreviation(folder, description, abbr, contents)} 1151 1152 When the given abbreviation is typed, it will be replaced with the given 1153 text. 1154 1155 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1156 @param description: description for the phrase 1157 @param abbr: the abbreviation that will trigger the expansion 1158 @param contents: the expansion text 1159 @raise Exception: if the specified abbreviation is not unique 1160 """ 1161 if not self.configManager.check_abbreviation_unique(abbr, None, None): 1162 raise Exception("The specified abbreviation is already in use") 1163 1164 self.monitor.suspend() 1165 p = model.Phrase(description, contents) 1166 p.modes.append(model.TriggerMode.ABBREVIATION) 1167 p.abbreviations = [abbr] 1168 folder.add_item(p) 1169 p.persist() 1170 self.monitor.unsuspend() 1171 self.configManager.config_altered(False)
1172
1173 - def create_hotkey(self, folder, description, modifiers, key, contents):
1174 """ 1175 Create a text hotkey 1176 1177 Usage: C{engine.create_hotkey(folder, description, modifiers, key, contents)} 1178 1179 When the given hotkey is pressed, it will be replaced with the given 1180 text. Modifiers must be given as a list of strings, with the following 1181 values permitted: 1182 1183 <ctrl> 1184 <alt> 1185 <super> 1186 <hyper> 1187 <shift> 1188 1189 The key must be an unshifted character (i.e. lowercase) 1190 1191 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1192 @param description: description for the phrase 1193 @param modifiers: modifiers to use with the hotkey (as a list) 1194 @param key: the hotkey 1195 @param contents: the expansion text 1196 @raise Exception: if the specified hotkey is not unique 1197 """ 1198 modifiers.sort() 1199 if not self.configManager.check_hotkey_unique(modifiers, key, None, None): 1200 raise Exception("The specified hotkey and modifier combination is already in use") 1201 1202 self.monitor.suspend() 1203 p = model.Phrase(description, contents) 1204 p.modes.append(model.TriggerMode.HOTKEY) 1205 p.set_hotkey(modifiers, key) 1206 folder.add_item(p) 1207 p.persist() 1208 self.monitor.unsuspend() 1209 self.configManager.config_altered(False)
1210
1211 - def run_script(self, description):
1212 """ 1213 Run an existing script using its description to look it up 1214 1215 Usage: C{engine.run_script(description)} 1216 1217 @param description: description of the script to run 1218 @raise Exception: if the specified script does not exist 1219 """ 1220 targetScript = None 1221 for item in self.configManager.allItems: 1222 if item.description == description and isinstance(item, model.Script): 1223 targetScript = item 1224 1225 if targetScript is not None: 1226 self.runner.run_subscript(targetScript) 1227 else: 1228 raise Exception("No script with description '%s' found" % description)
1229
1230 - def run_script_from_macro(self, args):
1231 """ 1232 Used internally by AutoKey for phrase macros 1233 """ 1234 self.__macroArgs = args["args"].split(',') 1235 1236 try: 1237 self.run_script(args["name"]) 1238 except Exception, e: 1239 self.set_return_value("{ERROR: %s}" % str(e))
1240
1241 - def get_macro_arguments(self):
1242 """ 1243 Get the arguments supplied to the current script via its macro 1244 1245 Usage: C{engine.get_macro_arguments()} 1246 1247 @return: the arguments 1248 @rtype: C{list(str())} 1249 """ 1250 return self.__macroArgs
1251
1252 - def set_return_value(self, val):
1253 """ 1254 Store a return value to be used by a phrase macro 1255 1256 Usage: C{engine.set_return_value(val)} 1257 1258 @param val: value to be stored 1259 """ 1260 self.__returnValue = val
1261
1262 - def _get_return_value(self):
1263 """ 1264 Used internally by AutoKey for phrase macros 1265 """ 1266 ret = self.__returnValue 1267 self.__returnValue = '' 1268 return ret
1269