From 7d5c37625c4232a61c25c6adf8731a5f9ec3ee32 Mon Sep 17 00:00:00 2001 From: Mark Doffman Date: Wed, 30 Apr 2008 15:23:13 +0100 Subject: [PATCH] 2008-04-30 Mark Doffman * tools/python/* Add some code to instrument the Tree interface. --- tools/python/AccessibleTree.py | 57 +++++ tools/python/AccessibleTreeCache.py | 147 +++++++++++ tools/python/desktop.xml | 469 ++++++++++++++++++++++++++++++++++++ tools/python/makeTree.py | 79 ++++++ tools/python/testClient.py | 41 ++++ tools/python/testServer.py | 55 +++++ 6 files changed, 848 insertions(+) create mode 100644 tools/python/AccessibleTree.py create mode 100644 tools/python/AccessibleTreeCache.py create mode 100644 tools/python/desktop.xml create mode 100644 tools/python/makeTree.py create mode 100644 tools/python/testClient.py create mode 100644 tools/python/testServer.py diff --git a/tools/python/AccessibleTree.py b/tools/python/AccessibleTree.py new file mode 100644 index 0000000..29ca3c4 --- /dev/null +++ b/tools/python/AccessibleTree.py @@ -0,0 +1,57 @@ +import dbus +import dbus.service + +TREE_UPDATE_ACCESSIBLE = 0 +TREE_REMOVE_ACCESSIBLE = 1 + +class AccessibleTree(dbus.service.Object): + """ + The Accessible tree provides the interface, + for accessing all the accessible objects + available on a particular application. + """ + + def __init__(self, bus, path): + """ + Parameters: + + bus - The D-Bus bus object to use + path - The object path this interface should use + """ + dbus.service.Object.__init__(self, bus, path) + self._toSend = {} + self._objects = {} + self._root = '/' + + @dbus.service.method(dbus_interface='org.freedesktop.atspi.Tree', + out_signature='o') + def getRoot(self): + return self._root + + @dbus.service.method(dbus_interface='org.freedesktop.atspi.Tree', + out_signature='a(qooaoassus)') + def getTree(self): + wireObjects = [] + for object in self._objects.values(): + wireObjects.append((TREE_UPDATE_ACCESSIBLE,) + object) + return wireObjects + + @dbus.service.signal(dbus_interface='org.freedesktop.atspi.Tree', + signature='a(qooaoassus)') + def updateTree(self, values): + #There are some locking issues here. + #Need to make sure that updates are not missed. + oldSend = self._toSend.values() + self._toSend = {} + return oldSend + + def updateObject(self, path, object): + self._objects[path] = object + self._toSend[path] = (TREE_UPDATE_ACCESSIBLE,) + object + + def removeObject(self, path): + self._toSend[path] = (TREE_REMOVE_ACCESSIBLE,) + self._objects[path] + del(self._objects[path]) + + def setRoot(self, root): + self._root = root diff --git a/tools/python/AccessibleTreeCache.py b/tools/python/AccessibleTreeCache.py new file mode 100644 index 0000000..b91ca3c --- /dev/null +++ b/tools/python/AccessibleTreeCache.py @@ -0,0 +1,147 @@ +import dbus + +TREE_UPDATE_ACCESSIBLE = 0 +TREE_REMOVE_ACCESSIBLE = 1 + +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' + + 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()) + + 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: + (flag, + path, + parent, + children, + interfaces, + name, + role, + description) = object + if flag == TREE_REMOVE_ACCESSIBLE: + #TODO need to set object as invalid + del(self._objects[path]) + else: + 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; + +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/tools/python/desktop.xml b/tools/python/desktop.xml new file mode 100644 index 0000000..54b4f96 --- /dev/null +++ b/tools/python/desktop.xml @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/python/makeTree.py b/tools/python/makeTree.py new file mode 100644 index 0000000..41395c2 --- /dev/null +++ b/tools/python/makeTree.py @@ -0,0 +1,79 @@ +import sys +import pyatspi +import uuid + +from xml.dom import minidom + +INTERFACES = [ +"Accessible", +"Desktop", +"Image", +"StreamableContent", +"Action", +"Document", +"Table", +"Application", +"EditableText", +"MatchRule", +"Text", +"Collection", +"Hyperlink", +"Value", +"Component", +"Hypertext", +"Selection", +] + +def getChild(accessible, i): + try: + child = accessible.getChildAtIndex(i) + except LookupError: + child = None + return child + +def createNode(accessible, parentRef, parentElement): + e = minidom.Element("accessible") + reference = '/' + str(uuid.uuid4()).replace('-', '') + + e.attributes["reference"] = reference + e.attributes["parent"] = parentRef + e.attributes["name"] = accessible.name + e.attributes["role"] = str(accessible.getRole()) + e.attributes["description"] = accessible.description + + for i in INTERFACES: + query = getattr(accessible, "query" + i) + try: + query() + itf = minidom.Element("interface") + itf.attributes["name"] = i + e.appendChild(itf) + except NotImplementedError: + pass + except LookupError: + pass + + try: + count = accessible.childCount + except LookupError: + count = 0 + + for i in range(count): + child = getChild(accessible, i) + if child is not None: + createNode(child, reference, e) + + parentElement.appendChild(e) + +def main(argv): + filename = argv[1] + doc = minidom.Document() + desk = pyatspi.Registry.getDesktop(0) + createNode(desk, '/', doc) + + file = open(filename, 'w') + file.write(doc.toprettyxml()) + file.close() + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tools/python/testClient.py b/tools/python/testClient.py new file mode 100644 index 0000000..5bc37c4 --- /dev/null +++ b/tools/python/testClient.py @@ -0,0 +1,41 @@ +import sys +import dbus + +from xml.dom import minidom + +from AccessibleTreeCache import AccessibleTreeCache, AccessibleObjectProxy + +def createNode(accessible, parentRef, parentElement): + e = minidom.Element("accessible") + + e.attributes["reference"] = accessible.path + e.attributes["parent"] = accessible.parent + e.attributes["name"] = accessible.name + e.attributes["role"] = str(int(accessible.role)) + e.attributes["description"] = accessible.description + + for i in accessible.interfaces: + itf = minidom.Element("interface") + itf.attributes["name"] = i + e.appendChild(itf) + + for c in accessible.getChildren(): + createNode(c, accessible.path, e) + + parentElement.appendChild(e) + +def main(argv): + filename = argv[1] + bus = dbus.SessionBus() + cache = AccessibleTreeCache(bus, 'test.atspi.tree', '/org/freedesktop/atspi/tree') + root = cache.getRootAccessible() + + doc = minidom.Document() + createNode(root, '/', doc) + + file = open(filename, 'w') + file.write(doc.toprettyxml()) + file.close() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/python/testServer.py b/tools/python/testServer.py new file mode 100644 index 0000000..0fe315a --- /dev/null +++ b/tools/python/testServer.py @@ -0,0 +1,55 @@ +import sys +from dbus.mainloop.glib import DBusGMainLoop + +import gobject +import dbus + +from xml.dom import minidom + +from AccessibleTree import AccessibleTree + +DBusGMainLoop(set_as_default=True) + +def getChildrenByName(node, name): + return [child for child in node.childNodes if child.nodeType == child.ELEMENT_NODE and child.nodeName == name] + +def registerObject(tree, parent, node): + reference = node.getAttribute('reference').encode('ASCII') + name = node.getAttribute('name').encode('ASCII') + role = node.getAttribute('role').encode('ASCII') + description = node.getAttribute('description').encode('ASCII') + + interfacen = getChildrenByName(node, 'interface') + interfaces = [itf.getAttribute('name').encode('ASCII') for itf in interfacen] + + childrenn = getChildrenByName(node, 'accessible') + children = [cld.getAttribute('reference').encode('ASCII') for cld in childrenn] + + object = (reference, parent, children, interfaces, name, int(role), description) + tree.updateObject(reference, object) + + for child in childrenn: + registerObject(tree, reference, child) + +def main(argv): + filename = argv[1] + bus = dbus.SessionBus() + + loop = gobject.MainLoop() + + bus.request_name('test.atspi.tree', 0) + + tree = AccessibleTree(bus, '/org/freedesktop/atspi/tree') + doc = minidom.parse(filename) + node = doc.firstChild + tree.setRoot(node.getAttribute('reference').encode('ASCII')) + + registerObject(tree, '/', node) + + try: + loop.run() + except KeyboardInterrupt: + loop.quit() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) -- 2.7.4