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