1 #Copyright (C) 2008 Codethink Ltd
2 #copyright: Copyright (c) 2005, 2007 IBM Corporation
4 #This library is free software; you can redistribute it and/or
5 #modify it under the terms of the GNU Lesser General Public
6 #License version 2 as published by the Free Software Foundation.
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 Lesser 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.
16 #Portions of this code originally licensed and copyright (c) 2005, 2007
17 #IBM Corporation under the BSD license, available at
18 #U{http://www.opensource.org/licenses/bsd-license.php}
20 #authors: Peter Parente, Mark Doffman
24 import gobject as _gobject
26 from desktop import Desktop as _Desktop
28 from event import EventType as _EventType
29 from event import event_type_to_signal_reciever as _event_type_to_signal_reciever
31 from applicationcache import TestApplicationCache, ApplicationCache
33 from dbus.mainloop.glib import DBusGMainLoop as _DBusGMainLoop
35 from Queue import Queue
36 from deviceevent import *
37 from deviceevent import _TestDeviceEventController
39 _DBusGMainLoop(set_as_default=True)
41 #------------------------------------------------------------------------------
43 class _Registry(object):
45 Wraps the Accessibility.Registry to provide more Pythonic registration for
48 This object should be treated as a singleton, but such treatment is not
49 enforced. You can construct another instance of this object and give it a
50 reference to the Accessibility.Registry singleton. Doing so is harmless and
53 @ivar async: Should event dispatch to local listeners be decoupled from event
54 receiving from the registry?
56 @ivar reg: Reference to the real, wrapped registry object
57 @type reg: Accessibility.Registry
58 @ivar dev: Reference to the device controller
59 @type dev: Accessibility.DeviceEventController
60 @ivar queue: Queue of events awaiting local dispatch
61 @type queue: Queue.Queue
62 @ivar clients: Map of event names to client listeners
63 @type clients: dictionary
64 @ivar observers: Map of event names to AT-SPI L{_Observer} objects
65 @type observers: dictionary
70 Stores a reference to the AT-SPI registry. Gets and stores a reference
71 to the DeviceEventController.
73 @param reg: Reference to the AT-SPI registry daemon
74 @type reg: Accessibility.Registry
76 self._bus = _dbus.SessionBus()
79 if "ATSPI_TEST_APP_NAME" in _os.environ.keys():
80 app_name = _os.environ["ATSPI_TEST_APP_NAME"]
83 self._app_cache = TestApplicationCache(self, self._bus, app_name)
84 self.dev = _TestDeviceEventController()
86 self._app_cache = ApplicationCache(self, self._bus)
87 self.dev = DeviceEventController(self._bus)
89 self._event_listeners = {}
91 # All of this special casing is for the 'faked'
92 # events caused by cache updates.
94 self._name_type = _EventType("object:property-change:name")
95 self._name_listeners = {}
96 self._description_type = _EventType("object:property-change:description")
97 self._description_listeners = {}
98 self._parent_type = _EventType("object:property-change:parent")
99 self._parent_listeners = {}
100 self._children_changed_type = _EventType("object:children-changed")
101 self._children_changed_listeners = {}
104 self.deviceClients = {}
108 @return: This instance of the registry
116 This is the accessible application cache through which
117 all accessible objects are accessed.
119 return self._app_cache
121 def start(self, async=False, gil=True):
123 Enter the main loop to start receiving and dispatching events.
125 @param async: Should event dispatch be asynchronous (decoupled) from
126 event receiving from the AT-SPI registry?
128 @param gil: Add an idle callback which releases the Python GIL for a few
129 milliseconds to allow other threads to run? Necessary if other threads
130 will be used in this process.
131 Note - No Longer used.
134 self._loop = _gobject.MainLoop()
137 except KeyboardInterrupt:
140 def stop(self, *args):
141 """Quits the main loop."""
145 def getDesktopCount(self):
147 Gets the number of available desktops.
149 @return: Number of desktops
154 def getDesktop(self, i):
156 Gets a reference to the i-th desktop.
158 @param i: Which desktop to get
160 @return: Desktop reference
161 @rtype: Accessibility.Desktop
163 return _Desktop(self._app_cache)
165 # -------------------------------------------------------------------------------
167 def _callClients(self, register, event):
168 for client in register.keys():
171 def _notifyNameChange(self, event):
172 self._callClients(self._name_listeners, event)
174 def _notifyDescriptionChange(self, event):
175 self._callClients(self._description_listeners, event)
177 def _notifyParentChange(self, event):
178 self._callClients(self._parent_listeners, event)
180 def _notifyChildrenChange(self, event):
181 self._callClients(self._children_changed_listeners, event)
183 def _registerFake(self, type, register, client, *names):
185 Registers a client from a register of clients
186 for 'Fake' events emitted by the cache.
189 registered = register[client]
192 register[client] = registered
195 new_type = _EventType(name)
196 if new_type.is_subtype(type):
197 registered.append(new_type.name)
200 del(register[client])
202 def _deregisterFake(self, type, register, client, *names):
204 Deregisters a client from a register of clients
205 for 'Fake' events emitted by the cache.
208 registered = register[client]
213 remove_type = _EventType(name)
216 for i in range(0, len(copy)):
218 registered_type = _EventType(type_name)
220 if remove_type.is_subtype(registered_type):
224 del(register[client])
226 # -------------------------------------------------------------------------------
228 def registerEventListener(self, client, *names):
230 Registers a new client callback for the given event names. Supports
231 registration for all subevents if only partial event name is specified.
232 Do not include a trailing colon.
234 For example, 'object' will register for all object events,
235 'object:property-change' will register for all property change events,
236 and 'object:property-change:accessible-parent' will register only for the
237 parent property change event.
239 Registered clients will not be automatically removed when the client dies.
240 To ensure the client is properly garbage collected, call
241 L{deregisterEventListener}.
243 @param client: Callable to be invoked when the event occurs
244 @type client: callable
245 @param names: List of full or partial event names
246 @type names: list of string
249 registered = self._event_listeners[client]
252 self._event_listeners[client] = registered
255 new_type = _EventType(name)
256 registered.append((new_type.name,
257 _event_type_to_signal_reciever(self._bus, self._app_cache, client, new_type)))
259 self._registerFake(self._name_type, self._name_listeners, client, *names)
260 self._registerFake(self._description_type, self._description_listeners, client, *names)
261 self._registerFake(self._parent_type, self._parent_listeners, client, *names)
262 self._registerFake(self._children_changed_type, self._children_changed_listeners, client, *names)
264 def deregisterEventListener(self, client, *names):
266 Unregisters an existing client callback for the given event names. Supports
267 unregistration for all subevents if only partial event name is specified.
268 Do not include a trailing colon.
270 This method must be called to ensure a client registered by
271 L{registerEventListener} is properly garbage collected.
273 @param client: Client callback to remove
274 @type client: callable
275 @param names: List of full or partial event names
276 @type names: list of string
277 @return: Were event names specified for which the given client was not
282 registered = self._event_listeners[client]
284 # Presumably if were trying to deregister a client with
285 # no names then the return type is always true.
291 remove_type = _EventType(name)
293 for i in range (0, len(copy)):
294 type_name, signal_match = copy[i]
295 registered_type = _EventType(type_name)
297 if remove_type.is_subtype(registered_type):
298 signal_match.remove()
304 del(self._event_listeners[client])
306 #TODO Do these account for missing also?
307 self._deregisterFake(self._name_type, self._name_listeners, client, *names)
308 self._deregisterFake(self._description_type, self._description_listeners, client, *names)
309 self._deregisterFake(self._parent_type, self._parent_listeners, client, *names)
310 self._deregisterFake(self._children_changed_type, self._children_changed_listeners, client, *names)
314 # -------------------------------------------------------------------------------
316 def registerKeystrokeListener(self,
320 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT),
325 Registers a listener for key stroke events.
327 @param client: Callable to be invoked when the event occurs
328 @type client: callable
329 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
330 to indicate all keys.
331 @type key_set: list of integer
332 @param mask: When the mask is None, the codes in the key_set will be
333 monitored only when no modifier is held. When the mask is an
334 integer, keys in the key_set will be monitored only when the modifiers in
335 the mask are held. When the mask is an iterable over more than one
336 integer, keys in the key_set will be monitored when any of the modifier
337 combinations in the set are held.
338 @type mask: integer, iterable, None
339 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
342 @param synchronous: Should the callback notification be synchronous, giving
343 the client the chance to consume the event?
344 @type synchronous: boolean
345 @param preemptive: Should the callback be allowed to preempt / consume the
347 @type preemptive: boolean
348 @param global_: Should callback occur even if an application not supporting
349 AT-SPI is in the foreground? (requires xevie)
350 @type global_: boolean
353 # see if we already have an observer for this client
354 ob = self.deviceClients[client]
356 # create a new device observer for this client
357 ob = KeyboardDeviceEventListener(self, synchronous, preemptive, global_)
358 # store the observer to client mapping, and the inverse
359 self.deviceClients[ob] = client
360 self.deviceClients[client] = ob
362 # None means all modifier combinations
363 mask = utils.allModifiers()
364 # register for new keystrokes on the observer
365 ob.register(self.dev, key_set, mask, kind)
367 def deregisterKeystrokeListener(self,
371 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT)):
373 Deregisters a listener for key stroke events.
375 @param client: Callable to be invoked when the event occurs
376 @type client: callable
377 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
378 to indicate all keys.
379 @type key_set: list of integer
380 @param mask: When the mask is None, the codes in the key_set will be
381 monitored only when no modifier is held. When the mask is an
382 integer, keys in the key_set will be monitored only when the modifiers in
383 the mask are held. When the mask is an iterable over more than one
384 integer, keys in the key_set will be monitored when any of the modifier
385 combinations in the set are held.
386 @type mask: integer, iterable, None
387 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
390 @raise KeyError: When the client isn't already registered for events
392 # see if we already have an observer for this client
393 ob = self.deviceClients[client]
395 # None means all modifier combinations
396 mask = utils.allModifiers()
397 # register for new keystrokes on the observer
398 ob.unregister(self.dev, key_set, mask, kind)
400 def handleDeviceEvent(self, event, ob):
402 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
403 in the order they were registered for the given AT-SPI event. If any
404 client returns True, callbacks cease for the event for clients of this registry
405 instance. Clients of other registry instances and clients in other processes may
406 be affected depending on the values of synchronous and preemptive used when invoking
407 L{registerKeystrokeListener}.
409 @note: Asynchronous dispatch of device events is not supported.
411 @param event: AT-SPI device event
412 @type event: L{event.DeviceEvent}
413 @param ob: Observer that received the event
414 @type ob: L{KeyboardDeviceEventListener}
416 @return: Should the event be consumed (True) or allowed to pass on to other
417 AT-SPI observers (False)?
421 # try to get the client registered for this event type
422 client = self.deviceClients[ob]
424 # client may have unregistered recently, ignore event
426 # make the call to the client
428 return client(event) or event.consume
430 # print the exception, but don't let it stop notification
431 traceback.print_exc()
433 # -------------------------------------------------------------------------------
435 def pumpQueuedEvents (self):
437 No Longer needed all application events are asyncronous.
441 def flushEvents (self):
443 No Longer needed all application events are asyncronous.
447 # -------------------------------------------------------------------------------
449 def generateKeyboardEvent(self, keycode, keysym, kind):
451 Generates a keyboard event. One of the keycode or the keysym parameters
452 should be specified and the other should be None. The kind parameter is
453 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
454 KEY_SYM, or KEY_STRING.
456 @param keycode: Hardware keycode or None
457 @type keycode: integer
458 @param keysym: Symbolic key string or None
460 @param kind: Kind of event to synthesize
464 self.dev.generateKeyboardEvent(keycode, '', kind)
466 self.dev.generateKeyboardEvent(None, keysym, kind)
468 def generateMouseEvent(self, x, y, name):
470 Generates a mouse event at the given absolute x and y coordinate. The kind
471 of event generated is specified by the name. For example, MOUSE_B1P
472 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
475 @param x: Horizontal coordinate, usually left-hand oriented
477 @param y: Vertical coordinate, usually left-hand oriented
479 @param name: Name of the event to generate
482 self.dev.generateMouseEvent(_dbus.Int32(x), _dbus.Int32(y), name)