+++ /dev/null
-#Copyright (C) 2008 Codethink Ltd
-
-#his program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 2 of the License, or
-#(at your option) any later version.
-
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-#GNU General Public License for more details.
-#You should have received a copy of the GNU General Public License
-#along with this program; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-import dbus
-
-from weakref import ref
-from dbus.proxies import Interface, ProxyObject
-
-ATSPI_ACCESSIBLE = 'org.freedesktop.atspi.Accessible'
-ATSPI_ACTION = 'org.freedesktop.atspi.Action'
-ATSPI_APPLICATION = 'org.freedesktop.atspi.Application'
-ATSPI_COMPONENT = 'org.freedesktop.atspi.Component'
-ATSPI_DEVICE_EVENT_CONTROLLER = 'org.freedesktop.atspi.DeviceEventController'
-ATSPI_DEVICE_EVENT_LISTENER = 'org.freedesktop.atspi.DeviceEventListener'
-ATSPI_DOCUMENT = 'org.freedesktop.atspi.Document'
-ATSPI_EDITABLE_TEXT = 'org.freedesktop.atspi.EditableText'
-ATSPI_HYPERLINK = 'org.freedesktop.atspi.Hyperlink'
-ATSPI_HYPERTEXT = 'org.freedesktop.atspi.Hypertext'
-ATSPI_IMAGE = 'org.freedesktop.atspi.Image'
-ATSPI_LOGIN_HELPER = 'org.freedesktop.atspi.LoginHelper'
-ATSPI_REGISTRY = 'org.freedesktop.atspi.Registry'
-ATSPI_SELECTION = 'org.freedesktop.atspi.Selection'
-ATSPI_SELECTOR = 'org.freedesktop.atspi.Selector'
-ATSPI_STREAMABLE_CONTENT = 'org.freedesktop.atspi.Content'
-ATSPI_TABLE = 'org.freedesktop.atspi.Table'
-ATSPI_TEXT = 'org.freedesktop.atspi.Text'
-ATSPI_TREE = 'org.freedesktop.atspi.Tree'
-ATSPI_VALUE = 'org.freedesktop.atspi.Value'
-
-DBUS_INTROSPECTABLE = 'org.freedesktop.DBus.Introspectable'
-
-class AccessibleObjectDoesNotExist(Exception):
- pass
-
-class AccessibleInterfaceNotSupported(Exception):
- pass
-
-class _CacheData(object):
- __slots__ = [
- 'parent',
- 'interfaces',
- 'children',
- 'role',
- 'name',
- 'description',
- 'proxy'
- ]
-
- def __init__(self, data):
- self.update(data)
- self.proxy = None
-
- def update(self, data):
- #Don't cache the path here, used as lookup in cache object dict.
- (path,
- self.parent,
- self.children,
- self.interfaces,
- self.name,
- self.role,
- self.description) = data
-
-class AccessibleCache(object):
- """
- Caches data for a collection of accessible object proxies.
-
- The cache only holds a weak reference to any D-Bus proxies,
- they are created on demand.
- """
-
- _UPDATE_SIGNAL = 'updateTree'
-
- def __init__(self, connection, busName, objectStorePath):
- """
- Creates a cache for accessible object data.
-
- All accessible object proxies are created and accessed through this cache.
-
- connection - DBus connection.
- busName - DBus bus name where accessible tree resides.
- objectStorePath - Path where the accessible tree can be accessed.
- """
- storeObject = connection.get_object(busName, objectStorePath)
-
- self._connection = connection
- self._busName = busName
- self._accessibleStore = dbus.Interface(storeObject, ATSPI_TREE)
- self._objects = {}
- #TODO are we caching the root accessible or not?
- #Do we need a roundtrip to access this?
- #Does the root accessible ever change?
- #Probably not as the root accessible is the 'Application'
- self._root = self._accessibleStore.getRoot()
-
- self._updateObjects(self._accessibleStore.getTree())
-
- #Connect to update signal
- self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
- self._updateHandler)
-
- def getRootAccessible(self):
- """
- Gets the accessible object at the root of the tree.
- """
- return self.getAccessible(self._root)
-
- def getAccessible(self, path):
- """
- Gets a D-Bus proxy object for the given object path.
-
- path - The D-Bus path of the remote object.
- """
- if path in self._objects:
- cachedata = self._objects[path]
- proxy = None
- if cachedata.proxy is not None:
- proxy = cachedata.proxy()
- if proxy is None:
- proxy = AccessibleObjectProxy(self,
- cachedata,
- self._connection,
- self._busName,
- path)
- cachedata.proxy = ref(proxy)
- return proxy
- else:
- raise AccessibleObjectDoesNotExist, "D-Bus reference not found in cache"
-
- def _updateObjects(self, objects):
- """
- Updates the object cache from an
- array of accessible object cache data.
- """
- for data in objects:
- #First element is the object path.
- path = data[0]
- if path in self._objects:
- cachedata = self._objects[path]
- cachedata.update(data)
- else:
- self._objects[path] = _CacheData(data)
-
- def _removeObjects(self, paths):
- """
- Removes the object data from the cache.
- """
- for path in paths:
- del(self._objects[path])
-
- def _updateHandler(self, updates):
- objects, paths = updates
- self._removeObjects(paths)
- self._updateObjects(objects)
-
-class AccessibleObjectProxy(ProxyObject):
- """
- A D-Bus proxy for a remote object that implements one or more of the AT-SPI
- Accessibility interfaces.
- """
-
- def __init__(self, cache, data, connection, busName, path):
- """
- Create an accessible object.
-
- cache - The accessible cache that this object is owned by.
- connection - The D-Bus connection
- busName - The location on the bus where the remote object is located.
- path - The object path of the remote object.
- """
- ProxyObject.__init__(self, connection, busName, path, introspect=False)
-
- self._cache = cache
- self._path = path
-
- @property
- def _data(self):
- try:
- data = self._cache._objects[self._path]
- except KeyError:
- raise AccessibleObjectDoesNotExist(
- 'Cache data cannot be found for path %s' % (self._path,))
- return data
-
- @property
- def path(self):
- return self._path
-
- @property
- def parent(self):
- return self._cache.getAccessible(self._data.parent)
-
- @property
- def numChildren(self):
- return len(self._data.children)
-
- def getChild(self, index):
- return self._cache.getAccessible(self._data.children[index])
-
- @property
- def interfaces(self):
- return self._data.interfaces
-
- def getInterface(self, interface):
- if interface in self._data.interfaces:
- return Interface(self, interface)
- else:
- raise AccessibleInterfaceNotSupported(
- "%s not supported by accessible object at path %s"
- % (interface, self.path))
-
- @property
- def name(self):
- return self._data.name
-
- @property
- def role(self):
- return self._data.role
-
- @property
- def description(self):
- return self._data.description
+++ /dev/null
-2008-01-28 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py (_AccessibleMixin.getApplication): Added a ref()
- which is required when holding a reference to a _narrow()ed
- instance (bug #512702).
-
-2008-01-23 Eitan Isaacson <eitan@ascender.com>
-
- * registry.py (Registry.pumpQueuedEvents): Added this method for
- pumping queued events (bug #509412). Thanks Steve Lee!
-
-2009-01-12 Willie Walker <william.walker@sun.com>
-
- * accessible.py: fix for bug 503091 - getApplication doesn't
- fallback properly.
-
-2007-12-13 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py (_getAndCache): Cleaned up: Fixed indent width and
- added epydoc string.
-
-2007-11-15 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py: Added new caching (bug #495077). Put in a mixin
- for remote objects that need to be unreffed at deletion time
- (bug #446277).
-
-2007-11-01 Eitan Isaacson <eitan@ascender.com>
-
- * event.py (Event.__init__): Ref() the host_application attribute
- if it is available (bug #492469).
- * accessible.py (_makeExceptionHandler._inner): Revert patch from
- bug #490202. This was the wrong fix. Objects that are returned
- from methods are already reffed.
-
-2007-10-30 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py (_AccessibleMixin.__del__): Catch TypeError
- exception when _ACCESSIBLE_CACHE[hash(self)] is None (bug #491805).
-
-2007-10-25 Eitan Isaacson <eitan@ascender.com>
-
- * event.py (Event.__init__): Catch AttributeError when ref()ing
- event.source (bug #490205).
- * accessible.py (_makeExceptionHandler._inner): If return value is
- a CORBA object, ref() it (bug #490202).
-
-2007-10-22 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py (_RelationMixin.getTarget): Add a ref() to the
- retrieved target (bug #489273).
-
-2007-10-12 Eitan Isaacson <eitan@ascender.com>
-
- * accessible.py: Narrow queried interfaces (bug #478124). Narrow
- objects returned from Relation.getTarget() to
- Accessibility.Accessible (bug #478129).
-
-2007-10-06 Eitan Isaacson <eitan@ascender.com>
-
- * utils.py (StateSet.add): Fixed type (bug #484201).
-
-2007-09-15 Eitan Isaacson <eitan@ascender.com>
-
- * registry.py: Fix to allow notification of events with
- annotations of details to the event type (bug #467366). Make
- keyboard event consume behavior conform with the IDL (bug
- #472301). Freeze break approved.
- * event.py: Limit splitting of event types (bug #467366). Freeze
- break approved.
-
-2007-08-24 Eitan Isaacson <eitan@ascender.com>
-
- * event.py: Make EventType a string descendent, it is now IDL
- compatable. Bug #467366.
-
-2007-07-25 Eitan Isaacson <eitan@ascender.com>
-
- * utils.py: Fixed typo in call for _findAllDescendants (bug #454090).
-
-2007-06-20 Peter Parente <parente@cs.unc.edu>
-
- * utils.py: Fix for bug #449356, typo in _findDescendantBreadth method
- (Thanks Jon)
-
-2007-05-31 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py: Fix for bug #439531, unable to define instance
- variables in accessible
-
-2007-05-25 Peter Parente <parente@cs.unc.edu>
-
- * utils.py: Partial fix for bug #437944, state set object fails when
- used remotely. See bug about what it not fixed.
-
-2007-05-16 Peter Parente <parente@cs.unc.edu>
-
- * registry.py: Fixed bug #438623, state change events missing
-
-2007-05-10 Peter Parente <parente@cs.unc.edu>
-
- * utils.py: Fixed bug #437528, StateSet equals
-
-2007-05-10 Peter Parente <parente@cs.unc.edu>
-
- * registry.py: Fixed bug #436982 again (Patch from Eitan)
-
-2007-05-09 Peter Parente <parente@cs.unc.edu>
-
- * registry.py: Corrected patch for bug #436982 so it works with
- Python 2.4, not just 2.5
-
-2007-05-09 Peter Parente <parente@cs.unc.edu>
-
- * registry.py: Fixed bug #436982 (Patch from Eitan), crash on exit
- * utils.py: Fixed bug #437153, bad StateSet add/remove impl.
-
-2007-05-08 Peter Parente <parente@cs.unc.edu>
-
- * constants.py:
- * accessible.py:
- * utils.py:
- Fixed bug #436949, Need util to list all supported interfaces
-
-2007-05-08 Peter Parente <parente@cs.unc.edu>
-
- Patch from Eitan Isaacson
- * accessible.py:
- Fixed bug #436930, Syntax error when raising IndexError
-
-2007-05-08 Peter Parente <parente@cs.unc.edu>
-
- Patch from Eitan Isaacson
- * event.py:
- Fixed bug #435947, Fix reference count error
-
-2007-05-04 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py:
- Fixed bug #435895, Fix __getitem__
-
-2007-05-04 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py:
- Fixed bug #435832, Catch CORBA errors for ORBit.Methods
-
-2007-05-04 Peter Parente <parente@cs.unc.edu>
-
- * setup.py:
- * Makefile.am:
- Fixed bug #435809, Add setup.py and Makefile.am
-
-2007-05-03 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py:
- Fixed bug #435504, Add ignore flag to _mixClass
-
-2007-05-03 Peter Parente <parente@cs.unc.edu>
-
- Patch from Eitan Isaacson
- * registry.py:
- Fixed bug #435284, Wrong params to deregister keystrokes
-
-2007-05-03 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py:
- Fixed bug #435177, Not saving narrow'ed interface
-
-2007-05-03 Peter Parente <parente@cs.unc.edu>
-
- * registry.py:
- Fixed bug #435282, Wrong instance variable name
-
-2007-04-30 Peter Parente <parente@cs.unc.edu>
-
- * accessible.py:
- Fixed bug #434686, wrong exception caught
-
-2007-04-27 Li Yuan <li.yuan@sun.com>
-
- Patch from Peter.
- * registry.py:
- Bug #433627. Adds default handling, fixes docs.
-
-2007-04-27 Li Yuan <li.yuan@sun.com>
-
- Patch from Peter.
- * constants.py:
- Bug #431057. Adds messing event.
-
-2007-04-18 Li Yuan <li.yuan@sun.com>
-
- * __init__.py:
- * accessible.py:
- * constants.py:
- * registry.py:
- * utils.py:
- Some updates from Peter Parente.
-
-2007-04-18 Li Yuan <li.yuan@sun.com>
-
- * pyatspi/__init__.py:
- * pyatspi/accessible.py:
- * pyatspi/constants.py:
- * pyatspi/event.py:
- * pyatspi/registry.py:
- * pyatspi/utils.py:
- Bug #430938. Add some files to create the uniform Python
- wrapper for at-spi.
+++ /dev/null
-pyatspi_PYTHON = \
- __init__.py \
- accessible.py \
- constants.py \
- event.py \
- registry.py \
- utils.py
-
-pyatspidir=$(pyexecdir)/pyatspi
+++ /dev/null
-'''
-Wraps the Gnome Assistive Technology Service Provider Interface for use in
-Python. Imports the bonobo and ORBit modules. Initializes the ORBit ORB.
-Activates the bonobo Accessibility Registry. Loads the Accessibility typelib
-and imports the classes implementing the AT-SPI interfaces.
-
-@var Registry: Reference to the AT-SPI registry daemon intialized on successful
- import
-@type Registry: registry.Registry
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-
-REGISTRY_IID = "OAFIID:Accessibility_Registry:1.0"
-TYPELIB_NAME = "Accessibility"
-
-# import ORBit and bonobo first (required)
-import ORBit, bonobo
-# initialize the ORB
-orb = ORBit.CORBA.ORB_init()
-# get a reference to the gnome Accessibility registry
-reg = bonobo.activation.activate_from_id(REGISTRY_IID, 0, 0)
-if reg is None:
- raise RuntimeError('could not activate:', REGISTRY_IID)
-# generate Python code for the Accessibility module from the IDL
-ORBit.load_typelib(TYPELIB_NAME)
-
-# import our registry module
-import registry
-# wrap the raw registry object in our convenience singleton
-Registry = registry.Registry(reg)
-# overwrite the registry class in the module, so all other imports get our
-# singleton
-registry.Registry = Registry
-# now throw the module away immediately
-del registry
-
-# pull the cache level functions into this namespace, but nothing else
-from accessible import setCacheLevel, getCacheLevel, clearCache, printCache
-
-# pull constants and utilities directly into this namespace; rest of code
-# never has to be touched externally
-from constants import *
-from utils import *
-
-# throw away extra references
-del reg
-del orb
+++ /dev/null
-'''
-Creates functions at import time that are mixed into the
-Accessibility.Accessible base class to make it more Pythonic.
-
-Based on public domain code originally posted at
-U{http://wwwx.cs.unc.edu/~parente/cgi-bin/RuntimeClassMixins}.
-
-@var _ACCESSIBLE_CACHE: Pairs hash values for accessible objects to
- L{_PropertyCache} bags. We do not store actual accessibles in the dictionary
- because that would +1 their ref counts and cause __del__ to never be called
- which is the method we rely on to properly invalidate cache entries.
-@type _ACCESSIBLE_CACHE: dictionary
-@var _CACHE_LEVEL: Current level of caching enabled. Checked dynamically by
- L{_AccessibleMixin}
-@type _CACHE_LEVEL: integer
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-import new
-import types
-import ORBit
-import Accessibility
-import constants
-import utils
-import registry
-import weakref
-
-_ACCESSIBLE_CACHE = weakref.WeakValueDictionary()
-_ACCESSIBLE_USER_DATA = weakref.WeakValueDictionary()
-_CACHE_LEVEL = None
-
-class _PropertyCache:
- pass
-
-class _UserData:
- value = None
-
-def getCacheLevel():
- '''
- Gets the current level of caching.
-
- @return: None indicating no caching is in effect.
- L{constants.CACHE_INTERFACES} indicating all interface query results are
- cached. L{constants.CACHE_PROPERTIES} indicating all basic accessible
- properties are cached.
- @rtype: integer
- '''
- return _CACHE_LEVEL
-
-def setCacheLevel(val):
- '''
- Sets the desired level of caching for all accessible objects created after
- this function is invoked. Immediately clears the current accessible cache.
-
- @param val: None indicating no caching is in effect.
- L{constants.CACHE_INTERFACES} indicating all interface query results are
- cached. L{constants.CACHE_PROPERTIES} indicating all basic accessible
- properties are cached plus all interfaces.
- @type val: integer
- '''
- global _CACHE_LEVEL
- if _CACHE_LEVEL != val:
- # empty our accessible cache
- _ACCESSIBLE_CACHE.clear()
- # need to register/unregister for listeners depending on caching level
- if val == constants.CACHE_PROPERTIES:
- r = registry.Registry()
- r.registerEventListener(_updateCache, *constants.CACHE_EVENTS)
- else:
- r = registry.Registry()
- r.deregisterEventListener(_updateCache, *constants.CACHE_EVENTS)
- _CACHE_LEVEL = val
-
-def clearCache():
- '''Forces a clear of the entire cache.'''
- _ACCESSIBLE_CACHE.clear()
-
-def printCache(template='%s'):
- '''
- Prints the contents of the cache.
-
- @param template: Format string to use when printing
- @type template: string
- '''
- print template % _ACCESSIBLE_CACHE
-
-def _updateCache(event):
- '''
- Invalidates an entry in the cache when the hash value of a source of an event
- matches an entry in the cache.
-
- @param event: One of the L{constants.CACHE_EVENTS} event types
- @type event: L{event.Event}
- '''
- try:
- del _ACCESSIBLE_CACHE[hash(event.source)]
- except KeyError:
- return
-
-def _getAndCache(acc, value_name, get_method):
- '''
- If property caching is enabled, use the cached proprty, or get the
- property and cache it. If property caching is disabled, simply get the
- property.
-
- @param value_name: The name of the value, like 'role' or 'description'.
- @type value_name: string
- @param get_method: Method used to get the property, should not have any
- arguments.
- @type get_method: callable
- @return: Value of property we are retrieving.
- @rtype: object
- '''
- if _CACHE_LEVEL != constants.CACHE_PROPERTIES:
- return get_method()
-
- cache = _ACCESSIBLE_CACHE
- h = hash(acc)
-
- try:
- pc = acc._property_cache
- except AttributeError:
- try:
- pc = cache[h]
- except KeyError:
- # no cached info for this accessible yet
- pc = _PropertyCache()
- cache[h] = pc
- acc._property_cache = pc
-
- try:
- value = getattr(pc, value_name)
- except AttributeError:
- # no cached property of this type
- value = get_method()
- setattr(pc, value_name, value)
-
- return value
-
-
-def _makeQuery(interface):
- '''
- Builds a function querying to a specific interface and returns it.
-
- @param interface: Class representing an AT-SPI interface
- @type interface: class
- @return: Function querying to the given interface
- @rtype: function
- '''
- def _inner(self):
- '''
- Queries an object for another interface.
-
- @return: An object with the desired interface
- @rtype: object
- @raise NotImplementedError: When the desired interface is not supported
- '''
- iid = utils.getInterfaceIID(interface)
- try:
- i = self._icache[iid]
- except KeyError:
- # interface not cached
- caching = True
- except AttributeError:
- # determine if we're caching
- caching = _CACHE_LEVEL is not None
- if caching:
- # initialize the cache
- self._icache = {}
- else:
- # check if our cached result was an interface, or an indicator that the
- # interface is not supported
- if i is None:
- raise NotImplementedError
- else:
- return i
-
- try:
- # do the query remotely
- i = self.queryInterface(iid)
- if i is not None:
- i = i._narrow(interface)
- except Exception, e:
- raise LookupError(e)
- if i is None:
- # cache that the interface is not supported
- if caching:
- self._icache[iid] = None
- raise NotImplementedError
-
- if caching:
- # cache the narrow'ed result, but only if we're caching for this object
- self._icache[iid] = i
- return i
-
- return _inner
-
-def _makeExceptionHandler(func):
- '''
- Builds a function calling the one it wraps in try/except statements catching
- CORBA exceptions.
-
- @return: Function calling the method being wrapped
- @rtype: function
- '''
- def _inner(self, *args, **kwargs):
- try:
- # try calling the original func
- return func(self, *args, **kwargs)
- except ORBit.CORBA.NO_IMPLEMENT, e:
- # raise Python exception
- raise NotImplementedError(e)
- except ORBit.CORBA.Exception, e:
- # raise Python exception
- raise LookupError(e)
- return _inner
-
-def _mixInterfaces(cls, interfaces):
- '''
- Add methods for querying to interfaces other than the base accessible to
- the given class.
-
- @param cls: Class to mix interface methods into
- @type cls: class
- @param interfaces: Classes representing AT-SPI interfaces
- @type interfaces: list of class
- '''
- # create functions in this module for all interfaces listed in constants
- for interface in interfaces:
- # build name of converter from the name of the interface
- name = 'query%s' % utils.getInterfaceName(interface)
- # build a function that queries to the given interface
- func = _makeQuery(interface)
- # build a new method that is a clone of the original function
- method = new.function(func.func_code, func.func_globals, name,
- func.func_defaults, func.func_closure)
- # add the method to the given class
- setattr(cls, name, method)
-
-def _mixExceptions(cls):
- '''
- Wraps all methods and properties in a class with handlers for CORBA
- exceptions.
-
- @param cls: Class to mix interface methods into
- @type cls: class
- '''
- # get a method type as a reference from a known method
- method_type = Accessibility.Accessible.getRole.__class__
- # loop over all names in the new class
- for name in cls.__dict__.keys():
- obj = cls.__dict__[name]
- # check if we're on a protected or private method
- if name.startswith('_'):
- continue
- # check if we're on a method
- elif isinstance(obj, method_type):
- # wrap the function in an exception handler
- method = _makeExceptionHandler(obj)
- # add the wrapped function to the class
- setattr(cls, name, method)
- # check if we're on a property
- elif isinstance(obj, property):
- # wrap the getters and setters
- if obj.fget:
- func = getattr(cls, obj.fget.__name__)
- getter = _makeExceptionHandler(func)
- else:
- getter = None
- if obj.fset:
- func = getattr(cls, obj.fset.__name__)
- setter = _makeExceptionHandler(func)
- else:
- setter = None
- setattr(cls, name, property(getter, setter))
-
-def _mixClass(cls, new_cls, ignore=[]):
- '''
- Adds the methods in new_cls to cls. After mixing, all instances of cls will
- have the new methods. If there is a method name clash, the method already in
- cls will be prefixed with '_mix_' before the new method of the same name is
- mixed in.
-
- @note: _ is not the prefix because if you wind up with __ in front of a
- variable, it becomes private and mangled when an instance is created.
- Difficult to invoke from the mixin class.
-
- @param cls: Existing class to mix features into
- @type cls: class
- @param new_cls: Class containing features to add
- @type new_cls: class
- @param ignore: Ignore these methods from the mixin
- @type ignore: iterable
- '''
- # loop over all names in the new class
- for name, func in new_cls.__dict__.items():
- if name in ignore:
- continue
- if isinstance(func, types.FunctionType):
- # build a new function that is a clone of the one from new_cls
- method = new.function(func.func_code, func.func_globals, name,
- func.func_defaults, func.func_closure)
- try:
- # check if a method of the same name already exists in the target
- old_method = getattr(cls, name)
- except AttributeError:
- pass
- else:
- # rename the old method so we can still call it if need be
- setattr(cls, '_mix_'+name, old_method)
- # add the clone to cls
- setattr(cls, name, method)
- elif isinstance(func, staticmethod):
- try:
- # check if a method of the same name already exists in the target
- old_method = getattr(cls, name)
- except AttributeError:
- pass
- else:
- # rename the old method so we can still call it if need be
- setattr(cls, '_mix_'+name, old_method)
- setattr(cls, name, func)
- elif isinstance(func, property):
- try:
- # check if a method of the same name already exists in the target
- old_prop = getattr(cls, name)
- except AttributeError:
- pass
- else:
- # IMPORTANT: We save the old property before overwriting it, even
- # though we never end up calling the old prop from our mixin class.
- # If we don't save the old one, we seem to introduce a Python ref count
- # problem where the property get/set methods disappear before we can
- # use them at a later time. This is a minor waste of memory because
- # a property is a class object and we only overwrite a few of them.
- setattr(cls, '_mix_'+name, old_prop)
- setattr(cls, name, func)
-
-class _AccessibleMixin(object):
- '''
- Defines methods to be added to the Accessibility.Accessible class. The
- features defined here will be added to the Accessible class at run time so
- that all instances of Accessible have them (i.e. there is no need to
- explicitly wrap an Accessible in this class or derive a new class from it.)
-
- @cvar SLOTTED_CLASSES: Mapping from raw Accessibility class to a new class
- having the slots defined by L{SLOTS}
- @type SLOTTED_CLASSES: dictionary
- @cvar SLOTS: All slots to create
- @type SLOTS: tuple
- '''
- SLOTTED_CLASSES = {}
- SLOTS = ('_icache', '_property_cache', '_user_data')
-
- def __new__(cls):
- '''
- Creates a new class mimicking the one requested, but with extra named
- defined in __slots__. The _cache attribute is used internally for interface
- caching. The user_data field may be populated with whatever data structure
- a client wishes to use. Neither is set to a default value by default.
-
- Note that we can't simply mix __slots__ into this class because __slots__
- has an effect only at class creation time.
-
- We also do not completely obliterate __slots__ to allow __dict__ to be
- instantiated as normal as reducing the initialization and memory overhead
- of the millions of accessible objects that are created is a good thing for
- many clients.
-
- @param cls: Accessibility object class
- @type cls: class
- @return: Instance of the new class
- @rtype: object
- '''
- try:
- # check if we've already created a new version of the class
- new_cls = _AccessibleMixin.SLOTTED_CLASSES[cls]
- except KeyError:
- # create the new class if not
- new_cls = type(cls.__name__, (cls,),
- {'__module__' : cls.__module__,
- '__slots__' : _AccessibleMixin.SLOTS})
- _AccessibleMixin.SLOTTED_CLASSES[cls] = new_cls
- obj = cls._mix___new__(new_cls)
- return obj
-
- def __del__(self):
- '''
- Decrements the reference count on the accessible object when there are no
- Python references to this object. This provides automatic reference
- counting for AT-SPI objects. Also removes this object from the cache if
- we're caching properties.
- '''
- try:
- self.unref()
- except Exception:
- pass
-
- def __iter__(self):
- '''
- Iterator that yields one accessible child per iteration. If an exception is
- encountered, None is yielded instead.
-
- @return: A child accessible
- @rtype: Accessibility.Accessible
- '''
- for i in xrange(self.childCount):
- try:
- yield self.getChildAtIndex(i)
- except LookupError:
- yield None
-
- def __str__(self):
- '''
- Gets a human readable representation of the accessible.
-
- @return: Role and name information for the accessible
- @rtype: string
- '''
- try:
- return '[%s | %s]' % (self.getRoleName(), self.name)
- except Exception:
- return '[DEAD]'
-
- def __nonzero__(self):
- '''
- @return: True, always
- @rtype: boolean
- '''
- return True
-
- def __getitem__(self, index):
- '''
- Thin wrapper around getChildAtIndex.
-
- @param index: Index of desired child
- @type index: integer
- @return: Accessible child
- @rtype: Accessibility.Accessible
- '''
- n = self.childCount
- if index >= n:
- raise IndexError
- elif index < -n:
- raise IndexError
- elif index < 0:
- index += n
- return self.getChildAtIndex(index)
-
- def __len__(self):
- '''
- Thin wrapper around childCount.
-
- @return: Number of child accessibles
- @rtype: integer
- '''
- return self.childCount
-
- def _get_user_data(self):
- '''
- Get user_data from global dictionay fo this accessible.
-
- @return: Any data the user assigned, or None.
- @rtype: object
- '''
- global _ACCESSIBLE_USER_DATA
- h = hash(self)
-
- try:
- ud = self._user_data
- except AttributeError:
- try:
- ud = _ACCESSIBLE_USER_DATA[h]
- except KeyError:
- # no cached info for this object yet
- ud = _UserData()
- _ACCESSIBLE_USER_DATA[h] = ud
-
- self._user_data = ud
- return ud.value
-
- def _set_user_data(self, value):
- '''
- Set arbitrary data to user_data.
-
- @param value: Value to set in user_data
- @type value: object
- '''
- global _ACCESSIBLE_USER_DATA
- h = hash(self)
-
- try:
- ud = self._user_data
- except AttributeError:
- try:
- ud = _ACCESSIBLE_USER_DATA[h]
- except KeyError:
- # no cached info for this object yet
- ud = _UserData()
- _ACCESSIBLE_USER_DATA[h] = ud
-
- self._user_data = ud
- ud.value = value
-
- user_data = property(_get_user_data, _set_user_data)
-
- def _get_name(self):
- '''
- Gets the name of the accessible from the cache if it is available,
- otherwise, fetches it remotely.
-
- @return: Name of the accessible
- @rtype: string
- '''
- return _getAndCache(self, 'name', self._get_name)
-
- name = property(_get_name, Accessibility.Accessible._set_name)
-
- def _get_parent(self):
- '''
- Gets the parent of the accessible from the cache if it is available,
- otherwise, fetches it remotely.
-
- @return: Parent of the accessible
- @rtype: Accessibility.Accessible
- '''
- return _getAndCache(self, 'parent', self._get_parent)
-
- parent = property(_get_parent)
-
- def getRoleName(self):
- '''
- Gets the unlocalized role name of the accessible from the cache if it is
- available, otherwise, fetches it remotely.
-
- @return: Role name of the accessible
- @rtype: string
- '''
- return _getAndCache(self, 'rolename', self._mix_getRoleName)
-
- def getRole(self):
- '''
- Gets the role of the accessible from the cache if it is
- available, otherwise, fetches it remotely.
-
- @return: Role of the accessible
- @rtype: Accessibility.Role
- '''
- return _getAndCache(self, 'role', self._mix_getRole)
-
- def _get_description(self):
- '''
- Gets the description of the accessible from the cache if it is available,
- otherwise, fetches it remotely.
-
- @return: Description of the accessible
- @rtype: string
- '''
- return _getAndCache(self, 'description', self._get_description)
-
- description = property(_get_description,
- Accessibility.Accessible._set_description)
-
- def getIndexInParent(self):
- '''
- Gets the index of this accessible in its parent. Uses the implementation of
- this method provided by the Accessibility.Accessible object, but checks the
- bound of the value to ensure it is not outside the range of childCount
- reported by this accessible's parent.
-
- @return: Index of this accessible in its parent
- @rtype: integer
- '''
- i = self._mix_getIndexInParent()
- try:
- # correct for out-of-bounds index reporting
- return min(self.parent.childCount-1, i)
- except AttributeError:
- # return sentinel if there is no parent
- return -1
-
- def getApplication(self):
- '''
- Gets the most-parent accessible (the application) of this
- accessible. Tries using the getApplication method introduced in
- AT-SPI 1.7.0 first before resorting to traversing parent links.
-
- @warning: Cycles involving more than the previously traversed accessible
- are not detected by this code.
- @return: Application object
- @rtype: Accessibility.Application
- '''
- try:
- app = self._mix_getApplication()
- except AttributeError:
- app = None
-
- # Some toolkits (e.g., Java) do not support getApplication yet and
- # will return None as a result.
- #
- if app:
- return app
-
- # If we didn't find anything, traverse up the tree, making sure to
- # attempt to turn the thing we found into an Application object.
- #
- curr = self
- try:
- while curr.parent is not None and (not curr.parent == curr):
- curr = curr.parent
- curr.ref()
- return curr._narrow(Accessibility.Application)
- except:
- return None
-
-class _RelationMixin(object):
- '''
- Defines methods to be added to the Relation class. At this time it only
- overrides L{_RelationMixin.getTarget} which by the IDL's standard is
- supposed to return CORBA.Objects but we expect LAccessibility.Accessible
- objects (see http://bugzilla.gnome.org/show_bug.cgi?id=435833).
- This seems to be a problem especially with the Java implementation of CORBA.
- '''
- def getTarget(self, index):
- '''
- Overrides the regular getTarget to return Accessibility.Accessible
- objects.
-
- @return: The 'nth' target of this Relation.
- @rtype: Accessibility.Accessible
- '''
- target = self._mix_getTarget(index)
- target.ref()
- return target._narrow(Accessibility.Accessible)
-
-class _UnrefMixin(object):
- '''
- This mixin addresses the issue we have with unreferencing non-primitives.
- '''
- def __del__(self):
- '''
- Unrefence the instance when Python GCs it. Why do we need this twice?
- '''
- try:
- self.unref()
- except Exception:
- pass
-
-# 1. mix the exception handlers into all queryable interfaces
-map(_mixExceptions, constants.ALL_INTERFACES)
-# 2. mix the exception handlers into other Accessibility objects
-map(_mixExceptions, [Accessibility.StateSet])
-# 3. mix the new functions
-_mixClass(Accessibility.Accessible, _AccessibleMixin,
- ['_get_name', '_get_description', '_get_parent'])
-# 4. mix queryInterface convenience methods
-_mixInterfaces(Accessibility.Accessible, constants.ALL_INTERFACES)
-# 5. mix Relation class
-_mixClass(Accessibility.Relation, _RelationMixin)
-# 6. mix in neccessary unrefs
-map(lambda cls: _mixClass(cls, _UnrefMixin),
- (Accessibility.StateSet,Accessibility.Relation))
+++ /dev/null
-'''
-Defines constants used throughout this wrapper.
-
-@author: Peter Parente
-@author: Pete Brunet
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-import ORBit
-import Accessibility
-import utils
-
-# pull dictionary of Accessibility module namespace into local var, temporarily
-acc_dict = vars(Accessibility)
-
-# run through all objects in the Accessibility module
-ALL_INTERFACES = []
-# list of classes that are not queriable interfaces
-not_interfaces = ['RoleSet', 'StateSet', 'DeviceEventListener', 'LoginHelper',
- 'ContentStream', 'DeviceEventController', 'Registry'
- 'DeviceEventListener', 'EventListener', 'Relation',
- 'CommandListener', 'Selector']
-for obj in acc_dict.values():
- try:
- # see if the object has a typecode
- kind = obj.__typecode__.kind
- except AttributeError:
- continue
- # compare the typecode to the one for CORBA objects, and to our list of
- # classes that are not queriable interfaces
- if (kind == ORBit.CORBA.tk_objref and
- utils.getInterfaceName(obj) not in not_interfaces):
- # this is an interface class
- ALL_INTERFACES.append(obj)
-# get rid of our temporary vars
-del not_interfaces, obj, kind
-
-# constants used in the Component interface to get screen coordinates
-DESKTOP_COORDS = 0
-WINDOW_COORDS = 1
-
-# constants used to synthesize mouse events
-MOUSE_B1P = 'b1p'
-MOUSE_B1R = 'b1r'
-MOUSE_B1C = 'b1c'
-MOUSE_B1D = 'b1d'
-MOUSE_B2P = 'b2p'
-MOUSE_B2R = 'b2r'
-MOUSE_B2C = 'b2c'
-MOUSE_B2D = 'b2d'
-MOUSE_B3P = 'b3p'
-MOUSE_B3R = 'b3r'
-MOUSE_B3C = 'b3c'
-MOUSE_B3D = 'b3d'
-MOUSE_ABS = 'abs'
-MOUSE_REL = 'rel'
-
-# defines levels of caching where if x > y, x caches all of y plus more
-CACHE_INTERFACES = 0
-CACHE_PROPERTIES = 1
-
-# events that clear cached properties
-CACHE_EVENTS = ['object:property-change:accessible-name',
- 'object:property-change:accessible-description',
- 'object:property-change:accessible-role',
- 'object:property-change:accessible-parent']
-
-# dictionary used to correct the bug of not being able to register for all the
-# subevents given only an AT-SPI event class (i.e. first part of the event
-# name) keys are event names having subevents and values are the subevents
-# under the key event; handlers *can* be registered for events not in this tree
-EVENT_TREE = {
- 'terminal':
- ['terminal:line-changed',
- 'terminal:columncount-changed',
- 'terminal:linecount-changed',
- 'terminal:application-changed',
- 'terminal:charwidth-changed'
- ],
- 'document':
- ['document:load-complete',
- 'document:reload',
- 'document:load-stopped',
- 'document:content-changed',
- 'document:attributes-changed'
- ],
- 'object':
- ['object:property-change',
- 'object:bounds-changed',
- 'object:link-selected',
- 'object:state-changed',
- 'object:children-changed',
- 'object:visible-data-changed',
- 'object:selection-changed',
- 'object:model-changed',
- 'object:active-descendant-changed',
- 'object:row-inserted',
- 'object:row-reordered',
- 'object:row-deleted',
- 'object:column-inserted',
- 'object:column-reordered',
- 'object:column-deleted',
- 'object:text-bounds-changed',
- 'object:text-selection-changed',
- 'object:text-changed',
- 'object:text-attributes-changed',
- 'object:text-caret-moved',
- 'object:attributes-changed'],
- 'object:text-changed' :
- ['object:text-changed:insert',
- 'object:text-changed:delete'],
- 'object:property-change' :
- ['object:property-change:accessible-parent',
- 'object:property-change:accessible-name',
- 'object:property-change:accessible-description',
- 'object:property-change:accessible-value',
- 'object:property-change:accessible-role',
- 'object:property-change:accessible-table-caption',
- 'object:property-change:accessible-table-column-description',
- 'object:property-change:accessible-table-column-header',
- 'object:property-change:accessible-table-row-description',
- 'object:property-change:accessible-table-row-header',
- 'object:property-change:accessible-table-summary'],
- 'object:children-changed' :
- ['object:children-changed:add',
- 'object:children-changed:remove'],
- 'object:state-changed' :
- ['object:state-changed:'],
- 'mouse' :
- ['mouse:abs',
- 'mouse:rel',
- 'mouse:button'],
- 'mouse:button' :
- ['mouse:button:1p',
- 'mouse:button:1r',
- 'mouse:button:2p',
- 'mouse:button:2r',
- 'mouse:button:3p',
- 'mouse:button:3r'],
- 'window' :
- ['window:minimize',
- 'window:maximize',
- 'window:restore',
- 'window:close',
- 'window:create',
- 'window:reparent',
- 'window:desktop-create',
- 'window:desktop-destroy',
- 'window:activate',
- 'window:deactivate',
- 'window:raise',
- 'window:lower',
- 'window:move',
- 'window:resize',
- 'window:shade',
- 'window:unshade',
- 'window:restyle'],
- 'focus' :
- ['focus:']
-}
-
-# pull ROLE_*, STATE_*, TEXT_*, MODIFIER_*, LOCALE_*, and RELATION_*, etc.
-# constants into the local namespace for convenient access
-# grab all the variable names and their values from the Accessibility module
-
-# get the dictionary for the local namespace
-loc_dict = locals()
-# these are the prefixes for the variable names we want to pull out of the
-# Accessibility module
-prefixes = ['ROLE_', 'STATE_', 'TEXT_', 'MODIFIER_', 'LOCALE_', 'RELATION_',
- 'KEY_', 'MATCH_', 'SORT_', 'LAYER_']
-# for each variable name in the Accessibility namespace, check if it starts
-# with at least one of the prefixes above; if it does, add a 2-tuple of
-# variable name and value to the values list
-values = ((name, value) for name, value in acc_dict.items()
- if len([p for p in prefixes if name.startswith(p)]))
-# create a new dictionary from the list of tuples and then update the local
-# namespace dictionary with that dictionary
-loc_dict.update(dict(values))
-
-# build a dictionary mapping state values to names based on the prefix of the
-# constant name imported from Accessibility
-STATE_VALUE_TO_NAME = dict(((value, name[6:].lower().replace('_', ' '))
- for name, value
- in acc_dict.items()
- if name.startswith('STATE_')))
-
-# build a dictionary mapping relation values to names based on the prefix of
-# the constant name imported from Accessibility
-RELATION_VALUE_TO_NAME = dict(((value, name[9:].lower().replace('_', ' '))
- for name, value
- in acc_dict.items()
- if name.startswith('RELATION_')))
-
-# throw away any temporary variables so they don't hang around in this module
-del acc_dict, loc_dict, prefixes, values
+++ /dev/null
-'''
-Wrapper classes for AT-SPI events and device events.
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-import constants
-
-class DeviceEvent(object):
- '''
- Wraps an AT-SPI device event with a more Pythonic interface. Primarily adds
- a consume attribute which can be used to cease propagation of a device event.
-
- @ivar consume: Should this event be consumed and not allowed to pass on to
- observers further down the dispatch chain in this process or possibly
- system wide?
- @type consume: boolean
- @ivar type: Kind of event, KEY_PRESSED_EVENT or KEY_RELEASED_EVENT
- @type type: Accessibility.EventType
- @ivar id: Serial identifier for this key event
- @type id: integer
- @ivar hw_code: Hardware scan code for the key
- @type hw_code: integer
- @ivar modifiers: Modifiers held at the time of the key event
- @type modifiers: integer
- @ivar timestamp: Time at which the event occurred relative to some platform
- dependent starting point (e.g. XWindows start time)
- @type timestamp: integer
- @ivar event_string: String describing the key pressed (e.g. keysym)
- @type event_string: string
- @ivar is_text: Is the event representative of text to be inserted (True), or
- of a control key (False)?
- @type is_text: boolean
- '''
- def __init__(self, event):
- '''
- Attaches event data to this object.
-
- @param event: Event object
- @type event: Accessibility.DeviceEvent
- '''
- self.consume = False
- self.type = event.type
- self.id = event.id
- self.hw_code = event.hw_code
- self.modifiers = event.modifiers
- self.timestamp = event.timestamp
- self.event_string = event.event_string
- self.is_text = event.is_text
-
- def __str__(self):
- '''
- Builds a human readable representation of the event.
-
- @return: Event description
- @rtype: string
- '''
- if self.type == constants.KEY_PRESSED_EVENT:
- kind = 'pressed'
- elif self.type == constants.KEY_RELEASED_EVENT:
- kind = 'released'
- return '''\
-%s
-\thw_code: %d
-\tevent_string: %s
-\tmodifiers: %d
-\tid: %d
-\ttimestamp: %d
-\tis_text: %s''' % (kind, self.hw_code, self.event_string, self.modifiers,
- self.id, self.timestamp, self.is_text)
-
-class Event(object):
- '''
- Wraps an AT-SPI event with a more Pythonic interface managing exceptions,
- the differences in any_data across versions, and the reference counting of
- accessibles provided with the event.
-
- @note: All unmarked attributes of this class should be considered public
- readable and writable as the class is acting as a record object.
-
- @ivar consume: Should this event be consumed and not allowed to pass on to
- observers further down the dispatch chain in this process?
- @type consume: boolean
- @ivar type: The type of the AT-SPI event
- @type type: L{EventType}
- @ivar detail1: First AT-SPI event parameter
- @type detail1: integer
- @ivar detail2: Second AT-SPI event parameter
- @type detail2: integer
- @ivar any_data: Extra AT-SPI data payload
- @type any_data: object
- @ivar host_application: Application owning the event source
- @type host_application: Accessibility.Application
- @ivar source_name: Name of the event source at the time of event dispatch
- @type source_name: string
- @ivar source_role: Role of the event source at the time of event dispatch
- @type source_role: Accessibility.Role
- @ivar source: Source of the event
- @type source: Accessibility.Accessible
- '''
- def __init__(self, event):
- '''
- Extracts information from the provided event. If the event is a "normal"
- event, pulls the detail1, detail2, any_data, and source values out of the
- given object and stores it in this object. If the event is a device event,
- key ID is stored in detail1, scan code is stored in detail2, key name,
- key modifiers (e.g. ALT, CTRL, etc.), is text flag, and timestamp are
- stored as a 4-tuple in any_data, and source is None (since key events are
- global).
-
- @param event: Event from an AT-SPI callback
- @type event: Accessibility.Event or Accessibility.DeviceEvent
- '''
- # always start out assuming no consume
- self.consume = False
- self.type = EventType(event.type)
- self.detail1 = event.detail1
- self.detail2 = event.detail2
- # store the event source and increase the reference count since event
- # sources are borrowed references; the AccessibleMixin automatically
- # decrements it later
- try:
- event.source.ref()
- except AttributeError:
- pass
- self.source = event.source
-
- # process any_data in a at-spi version independent manner
- details = event.any_data.value()
- try:
- # see if we have a "new" any_data object which is an EventDetails struct
- self.any_data = details.any_data.value()
- except Exception:
- # any kind of error means we have an "old" any_data object and None of
- # the extra data so set them to None
- self.any_data = details
- self.host_application = None
- self.source_name = None
- self.source_role = None
- else:
- # the rest of the data should be here, so retrieve it
- self.host_application = details.host_application
- self.source_name = details.source_name
- self.source_role = details.source_role
- try:
- # if we received an accessible, be sure to increment the ref count
- self.any_data.ref()
- except AttributeError:
- pass
- try:
- # if we received a host application, be sure to increment the ref count
- self.host_application.ref()
- except AttributeError:
- pass
-
- def __str__(self):
- '''
- Builds a human readable representation of the event including event type,
- parameters, and source info.
-
- @return: Event description
- @rtype: string
- '''
- return '%s(%s, %s, %s)\n\tsource: %s\n\thost_application: %s' % \
- (self.type, self.detail1, self.detail2, self.any_data,
- self.source, self.host_application)
-
-class EventType(str):
- '''
- Wraps the AT-SPI event type string so its components can be accessed
- individually as klass (can't use the keyword class), major, minor, and detail
- (klass:major:minor:detail).
-
- @note: All attributes of an instance of this class should be considered
- public readable as it is acting a a struct.
- @ivar klass: Most general event type identifier (object, window, mouse, etc.)
- @type klass: string
- @ivar major: Second level event type description
- @type major: string
- @ivar minor: Third level event type description
- @type minor: string
- @ivar detail: Lowest level event type description
- @type detail: string
- @ivar name: Full, unparsed event name as received from AT-SPI
- @type name: string
- @cvar format: Names of the event string components
- @type format: 4-tuple of string
- '''
- format = ('klass', 'major', 'minor', 'detail')
-
- def __init__(self, name):
- '''
- Parses the full AT-SPI event name into its components
- (klass:major:minor:detail). If the provided event name is an integer
- instead of a string, then the event is really a device event.
-
- @param name: Full AT-SPI event name
- @type name: string
- @raise AttributeError: When the given event name is not a valid string
- '''
- # get rid of any leading and trailing ':' separators
- self.value = name.strip(':')
- self.name = self.value # Backward compatability
- self.klass = None
- self.major = None
- self.minor = None
- self.detail = None
-
- # split type according to delimiters
- split = self.value.split(':', 3)
- # loop over all the components
- for i in xrange(len(split)):
- # store values of attributes in this object
- setattr(self, self.format[i], split[i])
-
+++ /dev/null
-'''
-Registry that hides some of the details of registering for AT-SPI events and
-starting and stopping the main program loop.
-
-@todo: PP: when to destroy device listener?
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-import signal
-import time
-import weakref
-import Queue
-import traceback
-import ORBit
-import bonobo
-import gobject
-import Accessibility
-import Accessibility__POA
-import utils
-import constants
-import event
-
-class _Observer(object):
- '''
- Parent class for all event observers. Dispatches all received events to the
- L{Registry} that created this L{_Observer}. Provides basic reference counting
- functionality needed by L{Registry} to determine when an L{_Observer} can be
- released for garbage collection.
-
- The reference counting provided by this class is independent of the reference
- counting used by CORBA. Keeping the counts separate makes it easier for the
- L{Registry} to detect when an L{_Observer} can be freed in the
- L{Registry._unregisterObserver} method.
-
- @ivar registry: Reference to the L{Registry} that created this L{_Observer}
- @type registry: weakref.proxy to L{Registry}
- @ivar ref_count: Reference count on this L{_Observer}
- @type ref_count: integer
- '''
- def __init__(self, registry):
- '''
- Stores a reference to the creating L{Registry}. Intializes the reference
- count on this object to zero.
-
- @param registry: The L{Registry} that created this observer
- @type registry: weakref.proxy to L{Registry}
- '''
- self.registry = weakref.proxy(registry)
- self.ref_count = 0
-
- def clientRef(self):
- '''
- Increments the Python reference count on this L{_Observer} by one. This
- method is called when a new client is registered in L{Registry} to receive
- notification of an event type monitored by this L{_Observer}.
- '''
- self.ref_count += 1
-
- def clientUnref(self):
- '''
- Decrements the pyatspi reference count on this L{_Observer} by one. This
- method is called when a client is unregistered in L{Registry} to stop
- receiving notifications of an event type monitored by this L{_Observer}.
- '''
- self.ref_count -= 1
-
- def getClientRefCount(self):
- '''
- @return: Current Python reference count on this L{_Observer}
- @rtype: integer
- '''
- return self.ref_count
-
- def ref(self):
- '''Required by CORBA. Does nothing.'''
- pass
-
- def unref(self):
- '''Required by CORBA. Does nothing.'''
- pass
-
-class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
- '''
- Observes keyboard press and release events.
-
- @ivar registry: The L{Registry} that created this observer
- @type registry: L{Registry}
- @ivar key_set: Set of keys to monitor
- @type key_set: list of integer
- @ivar mask: Watch for key events while these modifiers are held
- @type mask: integer
- @ivar kind: Kind of events to monitor
- @type kind: integer
- @ivar mode: Keyboard event mode
- @type mode: Accessibility.EventListenerMode
- '''
- def __init__(self, registry, synchronous, preemptive, global_):
- '''
- Creates a mode object that defines when key events will be received from
- the system. Stores all other information for later registration.
-
- @param registry: The L{Registry} that created this observer
- @type registry: L{Registry}
- @param synchronous: Handle the key event synchronously?
- @type synchronous: boolean
- @param preemptive: Allow event to be consumed?
- @type preemptive: boolean
- @param global_: Watch for events on inaccessible applications too?
- @type global_: boolean
- '''
- _Observer.__init__(self, registry)
- self.mode = Accessibility.EventListenerMode()
- self.mode.preemptive = preemptive
- self.mode.synchronous = synchronous
- self.mode._global = global_
-
- def register(self, dc, key_set, mask, kind):
- '''
- Starts keyboard event monitoring.
-
- @param dc: Reference to a device controller
- @type dc: Accessibility.DeviceEventController
- @param key_set: Set of keys to monitor
- @type key_set: list of integer
- @param mask: Integer modifier mask or an iterable over multiple masks to
- unapply all at once
- @type mask: integer, iterable, or None
- @param kind: Kind of events to monitor
- @type kind: integer
- '''
- try:
- # check if the mask is iterable
- iter(mask)
- except TypeError:
- # register a single integer if not
- dc.registerKeystrokeListener(self._this(), key_set, mask, kind,
- self.mode)
- else:
- for m in mask:
- dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
-
- def unregister(self, dc, key_set, mask, kind):
- '''
- Stops keyboard event monitoring.
-
- @param dc: Reference to a device controller
- @type dc: Accessibility.DeviceEventController
- @param key_set: Set of keys to monitor
- @type key_set: list of integer
- @param mask: Integer modifier mask or an iterable over multiple masks to
- unapply all at once
- @type mask: integer, iterable, or None
- @param kind: Kind of events to monitor
- @type kind: integer
- '''
- try:
- # check if the mask is iterable
- iter(mask)
- except TypeError:
- # unregister a single integer if not
- dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
- else:
- for m in mask:
- dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
-
- def queryInterface(self, repo_id):
- '''
- Reports that this class only implements the AT-SPI DeviceEventListener
- interface. Required by AT-SPI.
-
- @param repo_id: Request for an interface
- @type repo_id: string
- @return: The underlying CORBA object for the device event listener
- @rtype: Accessibility.EventListener
- '''
- if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
- return self._this()
- else:
- return None
-
- def notifyEvent(self, ev):
- '''
- Notifies the L{Registry} that an event has occurred. Wraps the raw event
- object in our L{Event} class to support automatic ref and unref calls. An
- observer can return True to indicate this event should not be allowed to pass
- to other AT-SPI observers or the underlying application.
-
- @param ev: Keyboard event
- @type ev: Accessibility.DeviceEvent
- @return: Should the event be consumed (True) or allowed to pass on to other
- AT-SPI observers (False)?
- @rtype: boolean
- '''
- # wrap the device event
- ev = event.DeviceEvent(ev)
- return self.registry.handleDeviceEvent(ev, self)
-
-class _EventObserver(_Observer, Accessibility__POA.EventListener):
- '''
- Observes all non-keyboard AT-SPI events. Can be reused across event types.
- '''
- def register(self, reg, name):
- '''
- Starts monitoring for the given event.
-
- @param name: Name of the event to start monitoring
- @type name: string
- @param reg: Reference to the raw registry object
- @type reg: Accessibility.Registry
- '''
- reg.registerGlobalEventListener(self._this(), name)
-
- def unregister(self, reg, name):
- '''
- Stops monitoring for the given event.
-
- @param name: Name of the event to stop monitoring
- @type name: string
- @param reg: Reference to the raw registry object
- @type reg: Accessibility.Registry
- '''
- reg.deregisterGlobalEventListener(self._this(), name)
-
- def queryInterface(self, repo_id):
- '''
- Reports that this class only implements the AT-SPI DeviceEventListener
- interface. Required by AT-SPI.
-
- @param repo_id: Request for an interface
- @type repo_id: string
- @return: The underlying CORBA object for the device event listener
- @rtype: Accessibility.EventListener
- '''
- if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
- return self._this()
- else:
- return None
-
- def notifyEvent(self, ev):
- '''
- Notifies the L{Registry} that an event has occurred. Wraps the raw event
- object in our L{Event} class to support automatic ref and unref calls.
- Aborts on any exception indicating the event could not be wrapped.
-
- @param ev: AT-SPI event signal (anything but keyboard)
- @type ev: Accessibility.Event
- '''
- # wrap raw event so ref counts are correct before queueing
- ev = event.Event(ev)
- self.registry.handleEvent(ev)
-
-class Registry(object):
- '''
- Wraps the Accessibility.Registry to provide more Pythonic registration for
- events.
-
- This object should be treated as a singleton, but such treatment is not
- enforced. You can construct another instance of this object and give it a
- reference to the Accessibility.Registry singleton. Doing so is harmless and
- has no point.
-
- @ivar async: Should event dispatch to local listeners be decoupled from event
- receiving from the registry?
- @type async: boolean
- @ivar reg: Reference to the real, wrapped registry object
- @type reg: Accessibility.Registry
- @ivar dev: Reference to the device controller
- @type dev: Accessibility.DeviceEventController
- @ivar queue: Queue of events awaiting local dispatch
- @type queue: Queue.Queue
- @ivar clients: Map of event names to client listeners
- @type clients: dictionary
- @ivar observers: Map of event names to AT-SPI L{_Observer} objects
- @type observers: dictionary
- '''
- def __init__(self, reg):
- '''
- Stores a reference to the AT-SPI registry. Gets and stores a reference
- to the DeviceEventController.
-
- @param reg: Reference to the AT-SPI registry daemon
- @type reg: Accessibility.Registry
- '''
- self.async = None
- self.reg = reg
- self.dev = self.reg.getDeviceEventController()
- self.queue = Queue.Queue()
- self.clients = {}
- self.observers = {}
-
- def __call__(self):
- '''
- @return: This instance of the registry
- @rtype: L{Registry}
- '''
- return self
-
- def start(self, async=False, gil=True):
- '''
- Enter the main loop to start receiving and dispatching events.
-
- @param async: Should event dispatch be asynchronous (decoupled) from
- event receiving from the AT-SPI registry?
- @type async: boolean
- @param gil: Add an idle callback which releases the Python GIL for a few
- milliseconds to allow other threads to run? Necessary if other threads
- will be used in this process.
- @type gil: boolean
- '''
- self.async = async
-
- if gil:
- def releaseGIL():
- try:
- time.sleep(1e-5)
- except KeyboardInterrupt, e:
- # store the exception for later
- releaseGIL.keyboard_exception = e
- self.stop()
- return True
- # make room for an exception if one occurs during the
- releaseGIL.keyboard_exception = None
- i = gobject.idle_add(releaseGIL)
-
- # enter the main loop
- try:
- bonobo.main()
- finally:
- # clear all observers
- for name, ob in self.observers.items():
- ob.unregister(self.reg, name)
- if gil:
- gobject.source_remove(i)
- if releaseGIL.keyboard_exception is not None:
- # raise an keyboard exception we may have gotten earlier
- raise releaseGIL.keyboard_exception
-
- def stop(self, *args):
- '''Quits the main loop.'''
- try:
- bonobo.main_quit()
- except RuntimeError:
- # ignore errors when quitting (probably already quitting)
- pass
- self.flushEvents()
-
- def getDesktopCount(self):
- '''
- Gets the number of available desktops.
-
- @return: Number of desktops
- @rtype: integer
- @raise LookupError: When the count cannot be retrieved
- '''
- try:
- return self.reg.getDesktopCount()
- except Exception:
- raise LookupError
-
- def getDesktop(self, i):
- '''
- Gets a reference to the i-th desktop.
-
- @param i: Which desktop to get
- @type i: integer
- @return: Desktop reference
- @rtype: Accessibility.Desktop
- @raise LookupError: When the i-th desktop cannot be retrieved
- '''
- try:
- return self.reg.getDesktop(i)
- except Exception, e:
- raise LookupError(e)
-
- def registerEventListener(self, client, *names):
- '''
- Registers a new client callback for the given event names. Supports
- registration for all subevents if only partial event name is specified.
- Do not include a trailing colon.
-
- For example, 'object' will register for all object events,
- 'object:property-change' will register for all property change events,
- and 'object:property-change:accessible-parent' will register only for the
- parent property change event.
-
- Registered clients will not be automatically removed when the client dies.
- To ensure the client is properly garbage collected, call
- L{deregisterEventListener}.
-
- @param client: Callable to be invoked when the event occurs
- @type client: callable
- @param names: List of full or partial event names
- @type names: list of string
- '''
- for name in names:
- # store the callback for each specific event name
- self._registerClients(client, name)
-
- def deregisterEventListener(self, client, *names):
- '''
- Unregisters an existing client callback for the given event names. Supports
- unregistration for all subevents if only partial event name is specified.
- Do not include a trailing colon.
-
- This method must be called to ensure a client registered by
- L{registerEventListener} is properly garbage collected.
-
- @param client: Client callback to remove
- @type client: callable
- @param names: List of full or partial event names
- @type names: list of string
- @return: Were event names specified for which the given client was not
- registered?
- @rtype: boolean
- '''
- missed = False
- for name in names:
- # remove the callback for each specific event name
- missed |= self._unregisterClients(client, name)
- return missed
-
- def registerKeystrokeListener(self, client, key_set=[], mask=0,
- kind=(constants.KEY_PRESSED_EVENT,
- constants.KEY_RELEASED_EVENT),
- synchronous=True, preemptive=True,
- global_=False):
- '''
- Registers a listener for key stroke events.
-
- @param client: Callable to be invoked when the event occurs
- @type client: callable
- @param key_set: Set of hardware key codes to stop monitoring. Leave empty
- to indicate all keys.
- @type key_set: list of integer
- @param mask: When the mask is None, the codes in the key_set will be
- monitored only when no modifier is held. When the mask is an
- integer, keys in the key_set will be monitored only when the modifiers in
- the mask are held. When the mask is an iterable over more than one
- integer, keys in the key_set will be monitored when any of the modifier
- combinations in the set are held.
- @type mask: integer, iterable, None
- @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
- KEY_RELEASED_EVENT.
- @type kind: list
- @param synchronous: Should the callback notification be synchronous, giving
- the client the chance to consume the event?
- @type synchronous: boolean
- @param preemptive: Should the callback be allowed to preempt / consume the
- event?
- @type preemptive: boolean
- @param global_: Should callback occur even if an application not supporting
- AT-SPI is in the foreground? (requires xevie)
- @type global_: boolean
- '''
- try:
- # see if we already have an observer for this client
- ob = self.clients[client]
- except KeyError:
- # create a new device observer for this client
- ob = _DeviceObserver(self, synchronous, preemptive, global_)
- # store the observer to client mapping, and the inverse
- self.clients[ob] = client
- self.clients[client] = ob
- if mask is None:
- # None means all modifier combinations
- mask = utils.allModifiers()
- # register for new keystrokes on the observer
- ob.register(self.dev, key_set, mask, kind)
-
- def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
- kind=(constants.KEY_PRESSED_EVENT,
- constants.KEY_RELEASED_EVENT)):
- '''
- Deregisters a listener for key stroke events.
-
- @param client: Callable to be invoked when the event occurs
- @type client: callable
- @param key_set: Set of hardware key codes to stop monitoring. Leave empty
- to indicate all keys.
- @type key_set: list of integer
- @param mask: When the mask is None, the codes in the key_set will be
- monitored only when no modifier is held. When the mask is an
- integer, keys in the key_set will be monitored only when the modifiers in
- the mask are held. When the mask is an iterable over more than one
- integer, keys in the key_set will be monitored when any of the modifier
- combinations in the set are held.
- @type mask: integer, iterable, None
- @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
- KEY_RELEASED_EVENT.
- @type kind: list
- @raise KeyError: When the client isn't already registered for events
- '''
- # see if we already have an observer for this client
- ob = self.clients[client]
- if mask is None:
- # None means all modifier combinations
- mask = utils.allModifiers()
- # register for new keystrokes on the observer
- ob.unregister(self.dev, key_set, mask, kind)
-
- def generateKeyboardEvent(self, keycode, keysym, kind):
- '''
- Generates a keyboard event. One of the keycode or the keysym parameters
- should be specified and the other should be None. The kind parameter is
- required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
- KEY_SYM, or KEY_STRING.
-
- @param keycode: Hardware keycode or None
- @type keycode: integer
- @param keysym: Symbolic key string or None
- @type keysym: string
- @param kind: Kind of event to synthesize
- @type kind: integer
- '''
- if keysym is None:
- self.dev.generateKeyboardEvent(keycode, '', kind)
- else:
- self.dev.generateKeyboardEvent(None, keysym, kind)
-
- def generateMouseEvent(self, x, y, name):
- '''
- Generates a mouse event at the given absolute x and y coordinate. The kind
- of event generated is specified by the name. For example, MOUSE_B1P
- (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
- double-click).
-
- @param x: Horizontal coordinate, usually left-hand oriented
- @type x: integer
- @param y: Vertical coordinate, usually left-hand oriented
- @type y: integer
- @param name: Name of the event to generate
- @type name: string
- '''
- self.dev.generateMouseEvent(x, y, name)
-
- def handleDeviceEvent(self, event, ob):
- '''
- Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
- in the order they were registered for the given AT-SPI event. If any
- client returns True, callbacks cease for the event for clients of this registry
- instance. Clients of other registry instances and clients in other processes may
- be affected depending on the values of synchronous and preemptive used when invoking
- L{registerKeystrokeListener}.
-
- @note: Asynchronous dispatch of device events is not supported.
-
- @param event: AT-SPI device event
- @type event: L{event.DeviceEvent}
- @param ob: Observer that received the event
- @type ob: L{_DeviceObserver}
-
- @return: Should the event be consumed (True) or allowed to pass on to other
- AT-SPI observers (False)?
- @rtype: boolean
- '''
- try:
- # try to get the client registered for this event type
- client = self.clients[ob]
- except KeyError:
- # client may have unregistered recently, ignore event
- return False
- # make the call to the client
- try:
- return client(event) or event.consume
- except Exception:
- # print the exception, but don't let it stop notification
- traceback.print_exc()
-
- def handleEvent(self, event):
- '''
- Handles an AT-SPI event by either queuing it for later dispatch when the
- L{Registry.async} flag is set, or dispatching it immediately.
-
- @param event: AT-SPI event
- @type event: L{event.Event}
- '''
- if self.async:
- # queue for now
- self.queue.put_nowait(event)
- else:
- # dispatch immediately
- self._dispatchEvent(event)
-
- def _dispatchEvent(self, event):
- '''
- Dispatches L{event.Event}s to registered clients. Clients are called in
- the order they were registered for the given AT-SPI event. If any client
- returns True, callbacks cease for the event for clients of this registry
- instance. Clients of other registry instances and clients in other processes
- are unaffected.
-
- @param event: AT-SPI event
- @type event: L{event.Event}
- '''
- et = event.type
- try:
- # try to get the client registered for this event type
- clients = self.clients[et.name]
- except KeyError:
- try:
- # we may not have registered for the complete subtree of events
- # if our tree does not list all of a certain type (e.g.
- # object:state-changed:*); try again with klass and major only
- if et.detail is not None:
- # Strip the 'detail' field.
- clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
- elif et.minor is not None:
- # The event could possibly be object:state-changed:*.
- clients = self.clients['%s:%s' % (et.klass, et.major)]
- except KeyError:
- # client may have unregistered recently, ignore event
- return
- # make the call to each client
- consume = False
- for client in clients:
- try:
- consume = client(event) or False
- except Exception:
- # print the exception, but don't let it stop notification
- traceback.print_exc()
- if consume or event.consume:
- # don't allow further processing if a client returns True
- break
-
- def flushEvents(self):
- '''
- Flushes the event queue by destroying it and recreating it.
- '''
- self.queue = Queue.Queue()
-
- def pumpQueuedEvents(self, num=-1):
- '''
- Provides asynch processing of events in the queue by executeing them with
- _dispatchEvent() (as is done immediately when synch processing).
- This method would normally be called from a main loop or idle function.
-
- @param num: Number of events to pump. If number is negative it pumps
- the entire queue. Default is -1.
- @type num: integer
- @return: True if queue is not empty after events were pumped.
- @rtype: boolean
- '''
- if num < 0:
- # Dequeue as many events as currently in the queue.
- num = self.queue.qsize()
- for i in xrange(num):
- try:
- # get next waiting event
- event = self.queue.get_nowait()
- except Queue.Empty:
- break
- self._dispatchEvent(event)
-
- return not self.queue.empty()
-
- def _registerClients(self, client, name):
- '''
- Internal method that recursively associates a client with AT-SPI event
- names. Allows a client to incompletely specify an event name in order to
- register for subevents without specifying their full names manually.
-
- @param client: Client callback to receive event notifications
- @type client: callable
- @param name: Partial or full event name
- @type name: string
- '''
- try:
- # look for an event name in our event tree dictionary
- events = constants.EVENT_TREE[name]
- except KeyError:
- # if the event name doesn't exist, it's a leaf event meaning there are
- # no subtypes for that event
- # add this client to the list of clients already in the dictionary
- # using the event name as the key; if there are no clients yet for this
- # event, insert an empty list into the dictionary before appending
- # the client
- et = event.EventType(name)
- clients = self.clients.setdefault(et.name, [])
- try:
- # if this succeeds, this client is already registered for the given
- # event type, so ignore the request
- clients.index(client)
- except ValueError:
- # else register the client
- clients.append(client)
- self._registerObserver(name)
- else:
- # if the event name does exist in the tree, there are subevents for
- # this event; loop through them calling this method again to get to
- # the leaf events
- for e in events:
- self._registerClients(client, e)
-
- def _unregisterClients(self, client, name):
- '''
- Internal method that recursively unassociates a client with AT-SPI event
- names. Allows a client to incompletely specify an event name in order to
- unregister for subevents without specifying their full names manually.
-
- @param client: Client callback to receive event notifications
- @type client: callable
- @param name: Partial or full event name
- @type name: string
- '''
- missed = False
- try:
- # look for an event name in our event tree dictionary
- events = constants.EVENT_TREE[name]
- except KeyError:
- try:
- # if the event name doesn't exist, it's a leaf event meaning there are
- # no subtypes for that event
- # get the list of registered clients and try to remove the one provided
- et = event.EventType(name)
- clients = self.clients[et.name]
- clients.remove(client)
- self._unregisterObserver(name)
- except (ValueError, KeyError):
- # ignore any exceptions indicating the client is not registered
- missed = True
- return missed
- # if the event name does exist in the tree, there are subevents for this
- # event; loop through them calling this method again to get to the leaf
- # events
- for e in events:
- missed |= self._unregisterClients(client, e)
- return missed
-
- def _registerObserver(self, name):
- '''
- Creates a new L{_Observer} to watch for events of the given type or
- returns the existing observer if one is already registered. One
- L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
- any event name not found in the tree.
-
- @param name: Raw name of the event to observe
- @type name: string
- @return: L{_Observer} object that is monitoring the event
- @rtype: L{_Observer}
- '''
- et = event.EventType(name)
- try:
- # see if an observer already exists for this event
- ob = self.observers[et.name]
- except KeyError:
- # build a new observer if one does not exist
- ob = _EventObserver(self)
- # we have to register for the raw name because it may be different from
- # the parsed name determined by EventType (e.g. trailing ':' might be
- # missing)
- ob.register(self.reg, name)
- self.observers[et.name] = ob
- # increase our client ref count so we know someone new is watching for the
- # event
- ob.clientRef()
- return ob
-
- def _unregisterObserver(self, name):
- '''
- Destroys an existing L{_Observer} for the given event type only if no
- clients are registered for the events it is monitoring.
-
- @param name: Name of the event to observe
- @type name: string
- @raise KeyError: When an observer for the given event is not regist
- '''
- et = event.EventType(name)
- # see if an observer already exists for this event
- ob = self.observers[et.name]
- ob.clientUnref()
- if ob.getClientRefCount() == 0:
- ob.unregister(self.reg, name)
- del self.observers[et.name]
+++ /dev/null
-'''
-pyatspi: Python wrapper for AT-SPI
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-from distutils.core import setup
-
-doclines = __doc__.split('\n')
-setup(name='pyatspi',
- version='0.1.0',
- author='Peter Parente',
- author_email='parente@cs.unc.edu',
- url='http://live.gnome.org/GAP/PythonATSPI',
- license='http://www.opensource.org/licenses/lgpl-license.php',
- description = doclines[1],
- package_dir = {"pyatspi" : ""},
- packages = ['pyatspi'],
- )
+++ /dev/null
-'''
-Utility functions for AT-SPI for querying interfaces, searching the hierarchy,
-converting constants to strings, and so forth.
-
-@author: Peter Parente
-@organization: IBM Corporation
-@copyright: Copyright (c) 2005, 2007 IBM Corporation
-@license: LGPL
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Library General Public
-License as published by the Free Software Foundation; either
-version 2 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Library General Public License for more details.
-
-You should have received a copy of the GNU Library General Public
-License along with this library; if not, write to the
-Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.
-
-Portions of this code originally licensed and copyright (c) 2005, 2007
-IBM Corporation under the BSD license, available at
-U{http://www.opensource.org/licenses/bsd-license.php}
-'''
-import ORBit
-import Accessibility__POA
-
-def getInterfaceIID(obj):
- '''
- Gets the ID of an interface class or object in string format for use in
- queryInterface.
-
- @param obj: Class representing an AT-SPI interface or instance
- @type obj: object
- @return: IID for the interface
- @rtype: string
- @raise AttributeError: When the parameter does not provide typecode info
- '''
- return obj.__typecode__.repo_id
-
-def getInterfaceName(obj):
- '''
- Gets the human readable name of an interface class or object in string
- format.
-
- @param obj: Class representing an AT-SPI interface or instance
- @type obj: class
- @return: Name of the interface
- @rtype: string
- @raise AttributeError: When the parameter does not provide typecode info
- '''
- return obj.__typecode__.name
-
-# we're importing here to avoid cyclic importants; constants relies on the
-# two functions above
-import constants
-
-def listInterfaces(obj):
- '''
- Gets a list of the names of all interfaces supported by this object. The
- names are the short-hand interface names like "Accessible" and "Component",
- not the full interface identifiers.
-
- @param obj: Arbitrary object to query for all accessibility related
- interfaces. Must provide a queryInterface method.
- @type obj: object
- @return: Set of supported interface names
- @rtype: set
- @raise AttributeError: If the object provide does not implement
- queryInterface
- '''
- names = set()
- for ic in constants.ALL_INTERFACES:
- io = obj.queryInterface(getInterfaceIID(ic))
- if io is None:
- continue
- names.add(getInterfaceName(ic))
- return names
-
-def stringToConst(prefix, suffix):
- '''
- Maps a string name to an AT-SPI constant. The rules for the mapping are as
- follows:
- - The prefix is captalized and has an _ appended to it.
- - All spaces in the suffix are mapped to the _ character.
- - All alpha characters in the suffix are mapped to their uppercase.
-
- The resulting name is used with getattr to look up a constant with that name
- in the L{constants} module. If such a constant does not exist, the string
- suffix is returned instead.
-
- This method allows strings to be used to refer to roles, relations, etc.
- without direct access to the constants. It also supports the future expansion
- of roles, relations, etc. by allowing arbitrary strings which may or may not
- map to the current standard set of roles, relations, etc., but may still
- match some non-standard role, relation, etc. being reported by an
- application.
-
- @param prefix: Prefix of the constant name such as role, relation, state,
- text, modifier, key
- @type prefix: string
- @param suffix: Name of the role, relation, etc. to use to lookup the constant
- @type suffix: string
- @return: The matching constant value
- @rtype: object
- '''
- name = prefix.upper()+'_'+suffix.upper().replace(' ', '_')
- return getattr(constants, name, suffix)
-
-def stateToString(value):
- '''
- Converts a state value to a string based on the name of the state constant in
- the L{constants} module that has the given value.
-
- @param value: An AT-SPI state
- @type value: Accessibility.StateType
- @return: Human readable, untranslated name of the state
- @rtype: string
- '''
- return constants.STATE_VALUE_TO_NAME.get(value)
-
-def relationToString(value):
- '''
- Converts a relation value to a string based on the name of the state constant
- in the L{constants} module that has the given value.
-
- @param value: An AT-SPI relation
- @type value: Accessibility.RelationType
- @return: Human readable, untranslated name of the relation
- @rtype: string
- '''
- return constants.RELATION_VALUE_TO_NAME.get(value)
-
-def allModifiers():
- '''
- Generates all possible keyboard modifiers for use with
- L{registry.Registry.registerKeystrokeListener}.
- '''
- mask = 0
- while mask <= (1 << constants.MODIFIER_NUMLOCK):
- yield mask
- mask += 1
-
-def findDescendant(acc, pred, breadth_first=False):
- '''
- Searches for a descendant node satisfying the given predicate starting at
- this node. The search is performed in depth-first order by default or
- in breadth first order if breadth_first is True. For example,
-
- my_win = findDescendant(lambda x: x.name == 'My Window')
-
- will search all descendants of x until one is located with the name 'My
- Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
- L{_findDescendantBreadth} to start the recursive search.
-
- @param acc: Root accessible of the search
- @type acc: Accessibility.Accessible
- @param pred: Search predicate returning True if accessible matches the
- search criteria or False otherwise
- @type pred: callable
- @param breadth_first: Search breadth first (True) or depth first (False)?
- @type breadth_first: boolean
- @return: Accessible matching the criteria or None if not found
- @rtype: Accessibility.Accessible or None
- '''
- if breadth_first:
- return _findDescendantBreadth(acc, pred)
-
- for child in acc:
- try:
- ret = _findDescendantDepth(acc, pred)
- except Exception:
- ret = None
- if ret is not None: return ret
-
-def _findDescendantBreadth(acc, pred):
- '''
- Internal function for locating one descendant. Called by L{findDescendant} to
- start the search.
-
- @param acc: Root accessible of the search
- @type acc: Accessibility.Accessible
- @param pred: Search predicate returning True if accessible matches the
- search criteria or False otherwise
- @type pred: callable
- @return: Matching node or None to keep searching
- @rtype: Accessibility.Accessible or None
- '''
- for child in acc:
- try:
- if pred(child): return child
- except Exception:
- pass
- for child in acc:
- try:
- ret = _findDescendantBreadth(child, pred)
- except Exception:
- ret = None
- if ret is not None: return ret
-
-def _findDescendantDepth(acc, pred):
- '''
- Internal function for locating one descendant. Called by L{findDescendant} to
- start the search.
-
- @param acc: Root accessible of the search
- @type acc: Accessibility.Accessible
- @param pred: Search predicate returning True if accessible matches the
- search criteria or False otherwise
- @type pred: callable
- @return: Matching node or None to keep searching
- @rtype: Accessibility.Accessible or None
- '''
- try:
- if pred(acc): return acc
- except Exception:
- pass
- for child in acc:
- try:
- ret = _findDescendantDepth(child, pred)
- except Exception:
- ret = None
- if ret is not None: return ret
-
-def findAllDescendants(acc, pred):
- '''
- Searches for all descendant nodes satisfying the given predicate starting at
- this node. Does an in-order traversal. For example,
-
- pred = lambda x: x.getRole() == pyatspi.ROLE_PUSH_BUTTON
- buttons = pyatspi.findAllDescendants(node, pred)
-
- will locate all push button descendants of node.
-
- @param acc: Root accessible of the search
- @type acc: Accessibility.Accessible
- @param pred: Search predicate returning True if accessible matches the
- search criteria or False otherwise
- @type pred: callable
- @return: All nodes matching the search criteria
- @rtype: list
- '''
- matches = []
- _findAllDescendants(acc, pred, matches)
- return matches
-
-def _findAllDescendants(acc, pred, matches):
- '''
- Internal method for collecting all descendants. Reuses the same matches
- list so a new one does not need to be built on each recursive step.
- '''
- for child in acc:
- try:
- if pred(child): matches.append(child)
- except Exception:
- pass
- _findAllDescendants(child, pred, matches)
-
-def findAncestor(acc, pred):
- '''
- Searches for an ancestor satisfying the given predicate. Note that the
- AT-SPI hierarchy is not always doubly linked. Node A may consider node B its
- child, but B is not guaranteed to have node A as its parent (i.e. its parent
- may be set to None). This means some searches may never make it all the way
- up the hierarchy to the desktop level.
-
- @param acc: Starting accessible object
- @type acc: Accessibility.Accessible
- @param pred: Search predicate returning True if accessible matches the
- search criteria or False otherwise
- @type pred: callable
- @return: Node matching the criteria or None if not found
- @rtype: Accessibility.Accessible
- '''
- if acc is None:
- # guard against bad start condition
- return None
- while 1:
- if acc.parent is None:
- # stop if there is no parent and we haven't returned yet
- return None
- try:
- if pred(acc.parent): return acc.parent
- except Exception:
- pass
- # move to the parent
- acc = acc.parent
-
-def getPath(acc):
- '''
- Gets the path from the application ancestor to the given accessible in
- terms of its child index at each level.
-
- @param acc: Target accessible
- @type acc: Accessibility.Accessible
- @return: Path to the target
- @rtype: list of integer
- @raise LookupError: When the application accessible cannot be reached
- '''
- path = []
- while 1:
- if acc.parent is None:
- path.reverse()
- return path
- try:
- path.append(acc.getIndexInParent())
- except Exception:
- raise LookupError
- acc = acc.parent
-
-class _StateSetImpl(Accessibility__POA.StateSet):
- '''
- Implementation of the StateSet interface. Clients should not use this class
- directly, but rather the L{StateSet} proxy class.
-
- @param states: Set of states
- @type states: set
- '''
- def __init__(self):
- '''Initializes the state set.'''
- self.states = set()
-
- def contains(self, state):
- '''
- Checks if this StateSet contains the given state.
-
- @param state: State to check
- @type state: Accessibility.StateType
- @return: True if the set contains the given state
- @rtype: boolean
- '''
- return state in self.states
-
- def add(self, state):
- '''
- Adds a state to this set.
-
- @param state: State to add
- @type state: Accessibility.StateType
- '''
- self.states.add(state)
-
- def remove(self, state):
- '''
- Removes a state from this set.
-
- @param state: State to remove
- @type state: Accessibility.StateType
- '''
- self.states.remove(state)
-
- def equals(self, state_set):
- '''
- Checks if this StateSet contains exactly the same members as the given
- StateSet.
-
- @param state_set: Another set
- @type state_set: Accessibility.StateSet
- @return: Are the sets equivalent in terms of their contents?
- @rtype: boolean
- '''
- # don't check private members, object might be from another process
- # or implementation
- return set(state_set.getStates()) == self.states
-
- def compare(self, state_set):
- '''
- Computes the symmetric differences of this L{StateSet} and the given
- L{StateSet}.
-
- @note: This method is not currently implemented because of difficulties
- with reference counting. This method needs to return a new
- Accessibility.StateSet object, but the Python implementation for that
- object needs to be kept alive. The problem is who will keep that
- server implementation alive? As soon as it goes out of scope, it's
- GC'ed. This object cannot keep it alive either as it may go out of
- scope before the new object is ready to be finalized. With a global
- cache of objects, we don't know when to invalidate.
-
- @param state_set: Another set
- @type state_set: Accessibility.StateSet
- @return: Elements in only one of the two sets
- @rtype: Accessibility.StateSet
- '''
- raise ORBit.CORBA.NO_IMPLEMENT
-
- # don't check private members, object might be from another process
- # or implementation
- #states = set(state_set.getStates())
- #diff = self.states.symmetric_difference(states)
- #new_ss = _StateSetImpl()
- #map(new_ss._this().add, diff)
- #return new_ss._this()
-
- def isEmpty(self):
- '''
- Checks if this L{StateSet} is empty.
-
- @return: Is it empty?
- @rtype: boolean
- '''
- return len(self.states) == 0
-
- def getStates(self):
- '''
- Gets the sequence of all states in this set.
-
- @return: List of states
- @rtype: list
- '''
- return list(self.states)
-
-class StateSet(object):
- '''
- Python proxy for the L{_StateSetImpl} class. Use this to safely instantiate
- new StateSet objects in Python.
-
- @param impl: State set implementation
- @type impl: L{_StateSetImpl}
- '''
- def __init__(self, *states):
- '''
- Initializes the state set with the given states.
-
- @param states: States to add immediately
- @type states: list
- '''
- self.impl = _StateSetImpl()
- map(self.impl._this().add, states)
-
- def contains(self, state):
- '''
- Checks if this StateSet contains the given state.
-
- @param state: State to check
- @type state: Accessibility.StateType
- @return: True if the set contains the given state
- @rtype: boolean
- '''
- return self.impl._this().contains(state)
-
- def add(self, *states):
- '''
- Adds states to this set.
-
- @param states: State(s) to add
- @type states: Accessibility.StateType
- '''
- map(self.impl._this().add, states)
-
- def remove(self, state):
- '''
- Removes states from this set.
-
- @param states: State(s) to remove
- @type states: Accessibility.StateType
- '''
- map(self.impl._this().remove, state)
-
- def equals(self, state_set):
- '''
- Checks if this StateSet contains exactly the same members as the given
- StateSet.
-
- @param state_set: Another set
- @type state_set: Accessibility.StateSet
- @return: Are the sets equivalent in terms of their contents?
- @rtype: boolean
- '''
- if isinstance(state_set, self.__class__):
- # convenience if we're given a proxy
- state_set = state_set.raw()
- return self.impl._this().equals(state_set)
-
- def compare(self, state_set):
- '''
- Finds the symmetric difference between this state set andthe one provided,
- and returns it as a new StateSet.
-
- @note: This does not use L{_StateSetImpl.compare} which cannot be
- implemented at this time
- @param state_set: Set to compare against
- @type state_set: Accessibility.StateSet
- @return: Proxy for the new set
- @rtype: L{StateSet}
- '''
- if isinstance(state_set, self.__class__):
- # shortcut if it's another one of our proxies
- state_set = state_set.raw()
- a = set(self.impl._this().getStates())
- b = set(state_set.getStates())
- diff = a.symmetric_difference(b)
- return StateSet(*diff)
-
- def isEmpty(self):
- '''
- Checks if this StateSet is empty.
-
- @return: Is it empty?
- @rtype: boolean
- '''
- return self.impl._this().isEmpty()
-
- def getStates(self):
- '''
- Gets the sequence of all states in this set.
-
- @return: List of states
- @rtype: list
- '''
- return self.impl._this().getStates()
-
- def raw(self):
- '''
- Gets the Accessibility.StateSet object proxied for use in a remote
- call.
-
- @return: State set
- @rtype: Accessibility.StateSet
- '''
- return self.impl._this()