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

Source Code for Module lib.scripting

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