lib.scripting

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
     301
     331
     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
     593
     594          #return retCode, choice
     595
     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