From 21f0c30f1ad1e4b21379094c5b46752384a26280 Mon Sep 17 00:00:00 2001 From: Mark Doffman Date: Tue, 20 May 2008 17:45:59 +0100 Subject: [PATCH] 2008-05-16 Mark Doffman * test/accessible_cache.py * test/testClient.py Extend the accessible_cache to return D-Bus proxy objects for the remote accessible objects. --- tests/AccessibleTreeCache.py | 154 ------------------------------- tests/accessible_cache.py | 213 +++++++++++++++++++++++++++++++++++++++++++ tests/testClient.py | 13 ++- 3 files changed, 221 insertions(+), 159 deletions(-) delete mode 100644 tests/AccessibleTreeCache.py create mode 100644 tests/accessible_cache.py diff --git a/tests/AccessibleTreeCache.py b/tests/AccessibleTreeCache.py deleted file mode 100644 index f762101..0000000 --- a/tests/AccessibleTreeCache.py +++ /dev/null @@ -1,154 +0,0 @@ -import dbus - -class AccessibleObjectDoesNotExist(Exception): - def __init__(self, path): - self.path = path - - def __str__(self): - return "Object %s does not exist" % (self.path) - -class AccessibleTreeCache(): - """ - Caches a collection of Accessible Objects. - """ - - _TREE_INTERFACE = 'org.freedesktop.atspi.Tree' - _UPDATE_SIGNAL = 'updateTree' - - def __init__(self, connection, busName, objectStorePath): - """ - Creates a cache for accessible objects. - - All accessible objects are created and accessed through this cache. - - Parameters: - 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._busName = busName - self._accessibleStore = dbus.Interface(storeObject, self._TREE_INTERFACE) - self._objects = {} - 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, objectPath): - """ - Gets the accessible object for the given object path. - """ - if objectPath in self._objects: - return self._objects[objectPath] - else: - raise AccessibleObjectDoesNotExist(objectPath) - - def _updateObjects(self, objects): - """ - Updates the object cache from an - array of wire format Accessible objects. - """ - for object in objects: - (path, - parent, - children, - interfaces, - name, - role, - description) = object - if path in self._objects: - self._objects[path].update(path, - parent, - children, - interfaces, - name, - role, - description) - else: - acc = AccessibleObjectProxy(self, - self._busName, - path, - parent, - children, - interfaces, - name, - role, - description) - self._objects[path] = acc; - - def _removeObjects(self, paths): - for path in paths: - #Probably want to set object as invalid first - del(self._objects[path]) - - def _updateHandler(self, updates): - objects, paths = updates - self._removeObjects(paths) - self._updateObjects(objects) - -class AccessibleObjectProxy(): - """ - A D-Bus proxy for an element that implements one or more of the AT-SPI - Accessibility interfaces. - """ - - def __init__(self, cache, bus, - path, parent, - children, interfaces, - name, role, description): - """ - Create an accessible object. - - Parameters: - - cache - The accessible cache that this object is owned by. - bus - Bus name where proxied object can be located. - path - The D-Bus object path of object proxied by this object. - parent - The parent accessible or '/' if the root object. - children - List of child accessible objects. - interfaces - List of interfaces supported by this object. - name - Name of the accessible. - role - The accessibles role. - description - Description of the accessible - """ - self._cache = cache - self.bus = bus - self.update(path, parent, children, interfaces, name, role, description) - - def update(self, path, parent, - children, interfaces, - name, role, description): - """ - update an accessible object. - - Parameters: - - path - The D-Bus object path of object proxied by this object. - parent - The parent accessible or '/' if the root object. - children - List of child accessible objects. - interfaces - List of interfaces supported by this object. - name - Name of the accessible. - role - The accessibles role. - description - Description of the accessible - """ - self.path = path - self.parent = parent - self.children = children - self.interfaces = interfaces - self.name = name - self.role = role - self.description = description - - def getChildren(self): - return [self._cache.getAccessible(path) for path in self.children] diff --git a/tests/accessible_cache.py b/tests/accessible_cache.py new file mode 100644 index 0000000..ebcf9fe --- /dev/null +++ b/tests/accessible_cache.py @@ -0,0 +1,213 @@ +#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_ACCESSIBLE_TREE = 'org.freedesktop.atspi.Tree' + +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_ACCESSIBLE_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 diff --git a/tests/testClient.py b/tests/testClient.py index e9c1ea0..ebf53c8 100644 --- a/tests/testClient.py +++ b/tests/testClient.py @@ -5,7 +5,7 @@ from dbus.mainloop.glib import DBusGMainLoop from xml.dom import minidom -from AccessibleTreeCache import AccessibleTreeCache, AccessibleObjectProxy +from accessible_cache import AccessibleCache DBusGMainLoop(set_as_default=True) @@ -13,7 +13,10 @@ def createNode(accessible, parentRef, parentElement): e = minidom.Element("accessible") e.attributes["reference"] = accessible.path - e.attributes["parent"] = accessible.parent + try: + e.attributes["parent"] = accessible.parent.path + except: + pass e.attributes["name"] = accessible.name e.attributes["role"] = str(int(accessible.role)) e.attributes["description"] = accessible.description @@ -23,8 +26,8 @@ def createNode(accessible, parentRef, parentElement): itf.attributes["name"] = i e.appendChild(itf) - for c in accessible.getChildren(): - createNode(c, accessible.path, e) + for i in range(0, accessible.numChildren): + createNode(accessible.getChild(i), accessible.path, e) parentElement.appendChild(e) @@ -34,7 +37,7 @@ def main(argv): loop = gobject.MainLoop() - cache = AccessibleTreeCache(bus, 'test.atspi.tree', '/org/freedesktop/atspi/tree') + cache = AccessibleCache(bus, 'test.atspi.tree', '/org/freedesktop/atspi/tree') root = cache.getRootAccessible() doc = minidom.Document() -- 2.7.4