""" Dogtail's procedural UI All the classes here are intended to be single-instance, except for Action. """ __author__ = 'Zack Cerza ' ############################################################################## # # # WARNING: Here There Be Dragons (TM) # # # # If you don't understand how to use this API, you almost certainly don't # # want to read the code first. We make use of some very non-intuitive # # features of Python in order to make the API very simplistic. Therefore, # # you should probably only read this code if you're already familiar with # # some of Python's advanced features. You have been warned. ;) # # # ############################################################################## import tree import predicate from config import config from utils import Lock import rawinput #FocusError = "FocusError: %s not found" class FocusError(Exception): pass import errors def focusFailed(pred): errors.warn('The requested widget could not be focused: %s' % pred.debugName) ENOARGS = "At least one argument is needed" class FocusBase: """ The base for every class in the module. Does nothing special, really. """ node = None def __getattr__ (self, name): # Fold all the Node's AT-SPI properties into the Focus object. try: return getattr(self.node, name) except AttributeError: raise AttributeError, name def __setattr__ (self, name, value): # Fold all the Node's AT-SPI properties into the Focus object. if name == 'node': self.__class__.__dict__[name] = value else: try: setattr(self.node, name, value) except AttributeError: raise AttributeError, name class FocusApplication (FocusBase): """ Keeps track of which application is currently focused. """ desktop = tree.root def __call__ (self, name): """ Search for an application that matches and refocus on the given name. """ try: pred = predicate.IsAnApplicationNamed(name) app = self.desktop.findChild(pred, recursive = False, retry = False) except tree.SearchError, desc: if config.fatalErrors: raise FocusError, name else: focusFailed(pred) return False if app: FocusApplication.node = app FocusDialog.node = None FocusWindow.node = None FocusWidget.node = None return True class FocusDesktop (FocusBase): """ This isn't used yet, and may never be used. """ pass class FocusWindow (FocusBase): """ Keeps track of which window is currently focused. """ def __call__ (self, name): """ Search for a dialog that matches the given name and refocus on it. """ result = None pred = predicate.IsAWindowNamed(name) try: result = FocusApplication.node.findChild(pred, requireResult=False, recursive = False) except AttributeError: pass if result: FocusWindow.node = result FocusDialog.node = None FocusWidget.node = None else: if config.fatalErrors: raise FocusError, pred.debugName else: focusFailed(pred) return False return True class FocusDialog (FocusBase): """ Keeps track of which dialog is currently focused. """ def __call__ (self, name): """ Search for a dialog that matches the given name and refocus on it. """ result = None pred = predicate.IsADialogNamed(name) try: result = FocusApplication.node.findChild(pred, requireResult=False, recursive = False) except AttributeError: pass if result: FocusDialog.node = result FocusWidget.node = None else: if config.fatalErrors: raise FocusError, pred.debugName else: focusFailed(pred) return False return True class FocusWidget (FocusBase): """ Keeps track of which widget is currently focused. """ def findByPredicate(self, pred): result = None try: result = FocusWidget.node.findChild(pred, requireResult = False, retry = False) except AttributeError: pass if result: FocusWidget.node = result else: try: result = FocusDialog.node.findChild(pred, requireResult = False, retry = False) except AttributeError: pass if result: FocusWidget.node = result else: try: result = FocusWindow.node.findChild(pred, requireResult = False, retry = False) except AttributeError: pass if result: FocusWidget.node = result else: try: result = FocusApplication.node.findChild(pred, requireResult = False, retry = False) if result: FocusWidget.node = result except AttributeError: if config.fatalErrors: raise FocusError, name else: focusFailed(pred) return False if result == None: FocusWidget.node = result if config.fatalErrors: raise FocusError, pred.debugName else: focusFailed(pred) return False return True def __call__ (self, name = '', roleName = '', description = ''): """ If name, roleName or description are specified, search for a widget that matches and refocus on it. """ if not name and not roleName and not description: raise TypeError, ENOARGS # search for a widget. pred = predicate.GenericPredicate(name = name, roleName = roleName, description = description) return self.findByPredicate(pred) class Focus (FocusBase): """ The container class for the focused application, dialog and widget. """ def __getattr__ (self, name): raise AttributeError, name def __setattr__(self, name, value): if name in ('application', 'dialog', 'widget', 'window'): self.__dict__[name] = value else: raise AttributeError, name desktop = tree.root application = FocusApplication() app = application # shortcut :) dialog = FocusDialog() window = FocusWindow() frame = window widget = FocusWidget() def button (self, name): """ A shortcut to self.widget.findByPredicate(predicate.IsAButtonNamed(name)) """ return self.widget.findByPredicate(predicate.IsAButtonNamed(name)) def icon (self, name): """ A shortcut to self.widget(name, roleName = 'icon') """ return self.widget(name = name, roleName = 'icon') def menu (self, name): """ A shortcut to self.widget.findByPredicate(predicate.IsAMenuNamed(name)) """ return self.widget.findByPredicate(predicate.IsAMenuNamed(name)) def menuItem (self, name): """ A shortcut to self.widget.findByPredicate(predicate.IsAMenuItemNamed(name)) """ return self.widget.findByPredicate(predicate.IsAMenuItemNamed(name)) def table (self, name = ''): """ A shortcut to self.widget(name, roleName 'table') """ return self.widget(name = name, roleName = 'table') def tableCell (self, name = ''): """ A shortcut to self.widget(name, roleName 'table cell') """ return self.widget(name = name, roleName = 'table cell') def text (self, name = ''): """ A shortcut to self.widget.findByPredicate(IsATextEntryNamed(name)) """ return self.widget.findByPredicate(predicate.IsATextEntryNamed(name)) class Action (FocusWidget): """ Aids in executing AT-SPI actions, refocusing the widget if necessary. """ def __init__ (self, action): """ action is a string with the same name as the AT-SPI action you wish to execute using this class. """ self.action = action def __call__ (self, name = '', roleName = '', description = '', delay = config.actionDelay): """ If name, roleName or description are specified, first search for a widget that matches and refocus on it. Then execute the action. """ if name or roleName or description: FocusWidget.__call__(self, name = name, roleName = roleName, description = description) self.node.doActionNamed(self.action) def __getattr__ (self, attr): return getattr(FocusWidget.node, attr) def __setattr__ (self, attr, value): if attr == 'action': self.__dict__[attr] = value else: setattr(FocusWidget, attr, value) def button (self, name): """ A shortcut to self(name, roleName = 'push button') """ self.__call__(name = name, roleName = 'push button') def menu (self, name): """ A shortcut to self(name, roleName = 'menu') """ self.__call__(name = name, roleName = 'menu') def menuItem (self, name): """ A shortcut to self(name, roleName = 'menu item') """ self.__call__(name = name, roleName = 'menu item') def table (self, name = ''): """ A shortcut to self(name, roleName 'table') """ self.__call__(name = name, roleName = 'table') def tableCell (self, name = ''): """ A shortcut to self(name, roleName 'table cell') """ self.__call__(name = name, roleName = 'table cell') def text (self, name = ''): """ A shortcut to self(name, roleName = 'text') """ self.__call__(name = name, roleName = 'text') class Click (Action): """ A special case of Action, Click will eventually handle raw mouse events. """ primary = 1 middle = 2 secondary = 3 def __init__ (self): Action.__init__(self, 'click') def __call__ (self, name = '', roleName = '', description = '', raw = True, button = primary, delay = config.actionDelay): """ By default, execute a raw mouse event. If raw is False or if button evaluates to False, just pass the rest of the arguments to Action. """ if name or roleName or description: FocusWidget.__call__(self, name = name, roleName = roleName, description = description) if raw and button: # We're doing a raw mouse click Click.node.click(button) else: Action.__call__(self, name = name, roleName = roleName, description = description, delay = delay) class Select (Action): """ Aids in selecting and deselecting widgets, i.e. page tabs """ select = 'select' deselect = 'deselect' def __init__(self, action): """ action must be 'select' or 'deselect'. """ if action not in (self.select, self.deselect): raise ValueError, action Action.__init__(self, action) def __call__ (self, name = '', roleName = '', description = '', delay = config.actionDelay): """ If name, roleName or description are specified, first search for a widget that matches and refocus on it. Then execute the action. """ if name or roleName or description: FocusWidget.__call__(self, name = name, roleName = roleName, description = description) func = getattr(self.node, self.action) func() def type(text): if focus.widget.node: focus.widget.node.typeText(text) else: rawinput.typeText(text) def keyCombo(combo): if focus.widget.node: focus.widget.node.keyCombo(combo) else: rawinput.keyCombo(combo) def run(application, arguments = '', appName = ''): from utils import run as utilsRun pid = utilsRun(application + ' ' + arguments, appName = appName) focus.application(application) return pid # tell sniff not to use auto-refresh while script using this module is running sniff_lock = Lock(lockname='sniff_refresh.lock',randomize=False) try: sniff_lock.lock() except OSError: pass # lock was already present from other script instance or leftover from killed instance # lock should unlock automatically on script exit. focus = Focus() click = Click() activate = Action('activate') openItem = Action('open') menu = Action('menu') select = Select(Select.select) deselect = Select(Select.deselect)