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._appcache = TestApplicationCache(self, self._bus, app_name)
84 self.dev = _TestDeviceEventController()
86 self._appcache = 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 = {}
108 @return: This instance of the registry
113 def start(self, async=False, gil=True):
115 Enter the main loop to start receiving and dispatching events.
117 @param async: Should event dispatch be asynchronous (decoupled) from
118 event receiving from the AT-SPI registry?
120 @param gil: Add an idle callback which releases the Python GIL for a few
121 milliseconds to allow other threads to run? Necessary if other threads
122 will be used in this process.
123 Note - No Longer used.
126 self._loop = _gobject.MainLoop()
129 except KeyboardInterrupt:
132 def stop(self, *args):
133 """Quits the main loop."""
137 def getDesktopCount(self):
139 Gets the number of available desktops.
141 @return: Number of desktops
146 def getDesktop(self, i):
148 Gets a reference to the i-th desktop.
150 @param i: Which desktop to get
152 @return: Desktop reference
153 @rtype: Accessibility.Desktop
155 return _Desktop(self._appcache)
157 def _callClients(self, register, event):
158 for client in register.keys():
161 def _notifyNameChange(self, event):
162 self._callClients(self._name_listeners, event)
164 def _notifyDescriptionChange(self, event):
165 self._callClients(self._description_listeners, event)
167 def _notifyParentChange(self, event):
168 self._callClients(self._parent_listeners, event)
170 def _notifyChildrenChange(self, event):
171 self._callClients(self._children_changed_listeners, event)
173 def _registerFake(self, type, register, client, *names):
175 Registers a client from a register of clients
176 for 'Fake' events emitted by the cache.
179 registered = register[client]
182 register[client] = registered
185 new_type = _EventType(name)
186 if new_type.is_subtype(type):
187 registered.append(new_type.name)
189 def _deregisterFake(self, type, register, client, *names):
191 Deregisters a client from a register of clients
192 for 'Fake' events emitted by the cache.
195 registered = register[client]
200 remove_type = _EventType(name)
202 for i in range(0, len(registered) - 1):
203 type_name = registered[i]
204 registered_type = _EventType(type_name)
206 if remove_type.is_subtype(registered_type):
210 del(register[client])
212 def registerEventListener(self, client, *names):
214 Registers a new client callback for the given event names. Supports
215 registration for all subevents if only partial event name is specified.
216 Do not include a trailing colon.
218 For example, 'object' will register for all object events,
219 'object:property-change' will register for all property change events,
220 and 'object:property-change:accessible-parent' will register only for the
221 parent property change event.
223 Registered clients will not be automatically removed when the client dies.
224 To ensure the client is properly garbage collected, call
225 L{deregisterEventListener}.
227 @param client: Callable to be invoked when the event occurs
228 @type client: callable
229 @param names: List of full or partial event names
230 @type names: list of string
233 registered = self._event_listeners[client]
236 self._event_listeners[client] = registered
239 new_type = _EventType(name)
240 registered.append((new_type.name,
241 _event_type_to_signal_reciever(self._bus, self._appcache, client, new_type)))
243 self._registerFake(self._name_type, self._name_listeners, client, *names)
244 self._registerFake(self._description_type, self._description_listeners, client, *names)
245 self._registerFake(self._parent_type, self._parent_listeners, client, *names)
246 self._registerFake(self._children_changed_type, self._children_changed_listeners, client, *names)
248 def deregisterEventListener(self, client, *names):
250 Unregisters an existing client callback for the given event names. Supports
251 unregistration for all subevents if only partial event name is specified.
252 Do not include a trailing colon.
254 This method must be called to ensure a client registered by
255 L{registerEventListener} is properly garbage collected.
257 @param client: Client callback to remove
258 @type client: callable
259 @param names: List of full or partial event names
260 @type names: list of string
261 @return: Were event names specified for which the given client was not
266 registered = self._event_listeners[client]
268 # Presumably if were trying to deregister a client with
269 # no names then the return type is always true.
275 remove_type = _EventType(name)
277 for i in range(0, len(registered) - 1):
278 (type_name, signal_match) = registered[i]
279 registered_type = _EventType(type_name)
281 if remove_type.is_subtype(registered_type):
282 signal_match.remove()
288 del(self._event_listeners[client])
290 #TODO Do these account for missing also?
291 self._deregisterFake(self._name_type, self._name_listeners, client, *names)
292 self._deregisterFake(self._description_type, self._description_listeners, client, *names)
293 self._deregisterFake(self._parent_type, self._parent_listeners, client, *names)
294 self._deregisterFake(self._children_changed_type, self._children_changed_listeners, client, *names)
298 def registerKeystrokeListener(self,
302 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT),
307 Registers a listener for key stroke events.
309 @param client: Callable to be invoked when the event occurs
310 @type client: callable
311 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
312 to indicate all keys.
313 @type key_set: list of integer
314 @param mask: When the mask is None, the codes in the key_set will be
315 monitored only when no modifier is held. When the mask is an
316 integer, keys in the key_set will be monitored only when the modifiers in
317 the mask are held. When the mask is an iterable over more than one
318 integer, keys in the key_set will be monitored when any of the modifier
319 combinations in the set are held.
320 @type mask: integer, iterable, None
321 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
324 @param synchronous: Should the callback notification be synchronous, giving
325 the client the chance to consume the event?
326 @type synchronous: boolean
327 @param preemptive: Should the callback be allowed to preempt / consume the
329 @type preemptive: boolean
330 @param global_: Should callback occur even if an application not supporting
331 AT-SPI is in the foreground? (requires xevie)
332 @type global_: boolean
335 # see if we already have an observer for this client
336 ob = self.clients[client]
338 # create a new device observer for this client
339 ob = KeyboardDeviceEventListener(self, synchronous, preemptive, global_)
340 # store the observer to client mapping, and the inverse
341 self.clients[ob] = client
342 self.clients[client] = ob
344 # None means all modifier combinations
345 mask = utils.allModifiers()
346 # register for new keystrokes on the observer
347 ob.register(self.dev, key_set, mask, kind)
349 def deregisterKeystrokeListener(self,
353 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT)):
355 Deregisters a listener for key stroke events.
357 @param client: Callable to be invoked when the event occurs
358 @type client: callable
359 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
360 to indicate all keys.
361 @type key_set: list of integer
362 @param mask: When the mask is None, the codes in the key_set will be
363 monitored only when no modifier is held. When the mask is an
364 integer, keys in the key_set will be monitored only when the modifiers in
365 the mask are held. When the mask is an iterable over more than one
366 integer, keys in the key_set will be monitored when any of the modifier
367 combinations in the set are held.
368 @type mask: integer, iterable, None
369 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
372 @raise KeyError: When the client isn't already registered for events
374 # see if we already have an observer for this client
375 ob = self.clients[client]
377 # None means all modifier combinations
378 mask = utils.allModifiers()
379 # register for new keystrokes on the observer
380 ob.unregister(self.dev, key_set, mask, kind)
382 def generateKeyboardEvent(self, keycode, keysym, kind):
384 Generates a keyboard event. One of the keycode or the keysym parameters
385 should be specified and the other should be None. The kind parameter is
386 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
387 KEY_SYM, or KEY_STRING.
389 @param keycode: Hardware keycode or None
390 @type keycode: integer
391 @param keysym: Symbolic key string or None
393 @param kind: Kind of event to synthesize
397 self.dev.generateKeyboardEvent(keycode, '', kind)
399 self.dev.generateKeyboardEvent(None, keysym, kind)
401 def generateMouseEvent(self, x, y, name):
403 Generates a mouse event at the given absolute x and y coordinate. The kind
404 of event generated is specified by the name. For example, MOUSE_B1P
405 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
408 @param x: Horizontal coordinate, usually left-hand oriented
410 @param y: Vertical coordinate, usually left-hand oriented
412 @param name: Name of the event to generate
415 self.dev.generateMouseEvent(x, y, name)
417 def handleDeviceEvent(self, event, ob):
419 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
420 in the order they were registered for the given AT-SPI event. If any
421 client returns True, callbacks cease for the event for clients of this registry
422 instance. Clients of other registry instances and clients in other processes may
423 be affected depending on the values of synchronous and preemptive used when invoking
424 L{registerKeystrokeListener}.
426 @note: Asynchronous dispatch of device events is not supported.
428 @param event: AT-SPI device event
429 @type event: L{event.DeviceEvent}
430 @param ob: Observer that received the event
431 @type ob: L{KeyboardDeviceEventListener}
433 @return: Should the event be consumed (True) or allowed to pass on to other
434 AT-SPI observers (False)?
438 # try to get the client registered for this event type
439 client = self.clients[ob]
441 # client may have unregistered recently, ignore event
443 # make the call to the client
445 return client(event) or event.consume
447 # print the exception, but don't let it stop notification
448 traceback.print_exc()
450 def handleEvent(self, event):
452 Handles an AT-SPI event by either queuing it for later dispatch when the
453 L{Registry.async} flag is set, or dispatching it immediately.
455 @param event: AT-SPI event
456 @type event: L{event.Event}
460 self.queue.put_nowait(event)
462 # dispatch immediately
463 self._dispatchEvent(event)
465 def _dispatchEvent(self, event):
467 Dispatches L{event.Event}s to registered clients. Clients are called in
468 the order they were registered for the given AT-SPI event. If any client
469 returns True, callbacks cease for the event for clients of this registry
470 instance. Clients of other registry instances and clients in other processes
473 @param event: AT-SPI event
474 @type event: L{event.Event}
478 # try to get the client registered for this event type
479 clients = self.clients[et.name]
482 # we may not have registered for the complete subtree of events
483 # if our tree does not list all of a certain type (e.g.
484 # object:state-changed:*); try again with klass and major only
485 if et.detail is not None:
486 # Strip the 'detail' field.
487 clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
488 elif et.minor is not None:
489 # The event could possibly be object:state-changed:*.
490 clients = self.clients['%s:%s' % (et.klass, et.major)]
492 # client may have unregistered recently, ignore event
494 # make the call to each client
496 for client in clients:
498 consume = client(event) or False
500 # print the exception, but don't let it stop notification
501 traceback.print_exc()
502 if consume or event.consume:
503 # don't allow further processing if a client returns True
506 def flushEvents(self):
508 Flushes the event queue by destroying it and recreating it.
512 def pumpQueuedEvents(self, num=-1):
514 Provides asynch processing of events in the queue by executeing them with
515 _dispatchEvent() (as is done immediately when synch processing).
516 This method would normally be called from a main loop or idle function.
518 @param num: Number of events to pump. If number is negative it pumps
519 the entire queue. Default is -1.
521 @return: True if queue is not empty after events were pumped.
525 # Dequeue as many events as currently in the queue.
526 num = self.queue.qsize()
527 for i in xrange(num):
529 # get next waiting event
530 event = self.queue.get_nowait()
533 self._dispatchEvent(event)
535 return not self.queue.empty()