1 #Copyright (C) 2008 Codethink Ltd
3 #his program is free software; you can redistribute it and/or modify
4 #it under the terms of the GNU General Public License as published by
5 #the Free Software Foundation; either version 2 of the License, or
6 #(at your option) any later version.
8 #This program is distributed in the hope that it will be useful,
9 #but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 #GNU General Public License for more details.
12 #You should have received a copy of the GNU General Public License
13 #along with this program; if not, write to the Free Software
14 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 from weakref import ref
19 from dbus.proxies import Interface, ProxyObject
21 ATSPI_ACCESSIBLE = 'org.freedesktop.atspi.Accessible'
22 ATSPI_ACTION = 'org.freedesktop.atspi.Action'
23 ATSPI_APPLICATION = 'org.freedesktop.atspi.Application'
24 ATSPI_COMPONENT = 'org.freedesktop.atspi.Component'
25 ATSPI_DEVICE_EVENT_CONTROLLER = 'org.freedesktop.atspi.DeviceEventController'
26 ATSPI_DEVICE_EVENT_LISTENER = 'org.freedesktop.atspi.DeviceEventListener'
27 ATSPI_DOCUMENT = 'org.freedesktop.atspi.Document'
28 ATSPI_EDITABLE_TEXT = 'org.freedesktop.atspi.EditableText'
29 ATSPI_HYPERLINK = 'org.freedesktop.atspi.Hyperlink'
30 ATSPI_HYPERTEXT = 'org.freedesktop.atspi.Hypertext'
31 ATSPI_IMAGE = 'org.freedesktop.atspi.Image'
32 ATSPI_LOGIN_HELPER = 'org.freedesktop.atspi.LoginHelper'
33 ATSPI_REGISTRY = 'org.freedesktop.atspi.Registry'
34 ATSPI_SELECTION = 'org.freedesktop.atspi.Selection'
35 ATSPI_SELECTOR = 'org.freedesktop.atspi.Selector'
36 ATSPI_STREAMABLE_CONTENT = 'org.freedesktop.atspi.Content'
37 ATSPI_TABLE = 'org.freedesktop.atspi.Table'
38 ATSPI_TEXT = 'org.freedesktop.atspi.Text'
39 ATSPI_TREE = 'org.freedesktop.atspi.Tree'
40 ATSPI_VALUE = 'org.freedesktop.atspi.Value'
42 DBUS_INTROSPECTABLE = 'org.freedesktop.DBus.Introspectable'
44 class AccessibleObjectDoesNotExist(Exception):
47 class AccessibleInterfaceNotSupported(Exception):
50 class _CacheData(object):
61 def __init__(self, data):
65 def update(self, data):
66 #Don't cache the path here, used as lookup in cache object dict.
73 self.description) = data
75 class AccessibleCache(object):
77 Caches data for a collection of accessible object proxies.
79 The cache only holds a weak reference to any D-Bus proxies,
80 they are created on demand.
83 _UPDATE_SIGNAL = 'updateTree'
85 def __init__(self, connection, busName, objectStorePath):
87 Creates a cache for accessible object data.
89 All accessible object proxies are created and accessed through this cache.
91 connection - DBus connection.
92 busName - DBus bus name where accessible tree resides.
93 objectStorePath - Path where the accessible tree can be accessed.
95 storeObject = connection.get_object(busName, objectStorePath)
97 self._connection = connection
98 self._busName = busName
99 self._accessibleStore = dbus.Interface(storeObject, ATSPI_TREE)
101 #TODO are we caching the root accessible or not?
102 #Do we need a roundtrip to access this?
103 #Does the root accessible ever change?
104 #Probably not as the root accessible is the 'Application'
105 self._root = self._accessibleStore.getRoot()
107 self._updateObjects(self._accessibleStore.getTree())
109 #Connect to update signal
110 self._signalMatch = self._accessibleStore.connect_to_signal(self._UPDATE_SIGNAL,
113 def getRootAccessible(self):
115 Gets the accessible object at the root of the tree.
117 return self.getAccessible(self._root)
119 def getAccessible(self, path):
121 Gets a D-Bus proxy object for the given object path.
123 path - The D-Bus path of the remote object.
125 if path in self._objects:
126 cachedata = self._objects[path]
128 if cachedata.proxy is not None:
129 proxy = cachedata.proxy()
131 proxy = AccessibleObjectProxy(self,
136 cachedata.proxy = ref(proxy)
139 raise AccessibleObjectDoesNotExist, "D-Bus reference not found in cache"
141 def _updateObjects(self, objects):
143 Updates the object cache from an
144 array of accessible object cache data.
147 #First element is the object path.
149 if path in self._objects:
150 cachedata = self._objects[path]
151 cachedata.update(data)
153 self._objects[path] = _CacheData(data)
155 def _removeObjects(self, paths):
157 Removes the object data from the cache.
160 del(self._objects[path])
162 def _updateHandler(self, updates):
163 objects, paths = updates
164 self._removeObjects(paths)
165 self._updateObjects(objects)
167 class AccessibleObjectProxy(ProxyObject):
169 A D-Bus proxy for a remote object that implements one or more of the AT-SPI
170 Accessibility interfaces.
173 def __init__(self, cache, data, connection, busName, path):
175 Create an accessible object.
177 cache - The accessible cache that this object is owned by.
178 connection - The D-Bus connection
179 busName - The location on the bus where the remote object is located.
180 path - The object path of the remote object.
182 ProxyObject.__init__(self, connection, busName, path, introspect=False)
190 data = self._cache._objects[self._path]
192 raise AccessibleObjectDoesNotExist(
193 'Cache data cannot be found for path %s' % (self._path,))
202 return self._cache.getAccessible(self._data.parent)
205 def numChildren(self):
206 return len(self._data.children)
208 def getChild(self, index):
209 return self._cache.getAccessible(self._data.children[index])
212 def interfaces(self):
213 return self._data.interfaces
215 def getInterface(self, interface):
216 if interface in self._data.interfaces:
217 return Interface(self, interface)
219 raise AccessibleInterfaceNotSupported(
220 "%s not supported by accessible object at path %s"
221 % (interface, self.path))
225 return self._data.name
229 return self._data.role
232 def description(self):
233 return self._data.description