2 Registry that hides some of the details of registering for AT-SPI events and
3 starting and stopping the main program loop.
5 @todo: PP: when to destroy device listener?
8 @organization: IBM Corporation
9 @copyright: Copyright (c) 2005, 2007 IBM Corporation
12 This library is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Library General Public
14 License as published by the Free Software Foundation; either
15 version 2 of the License, or (at your option) any later version.
17 This library is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 Library General Public License for more details.
22 You should have received a copy of the GNU Library General Public
23 License along with this library; if not, write to the
24 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 Boston, MA 02111-1307, USA.
27 Portions of this code originally licensed and copyright (c) 2005, 2007
28 IBM Corporation under the BSD license, available at
29 U{http://www.opensource.org/licenses/bsd-license.php}
40 import Accessibility__POA
45 class _Observer(object):
47 Parent class for all event observers. Dispatches all received events to the
48 L{Registry} that created this L{_Observer}. Provides basic reference counting
49 functionality needed by L{Registry} to determine when an L{_Observer} can be
50 released for garbage collection.
52 The reference counting provided by this class is independent of the reference
53 counting used by CORBA. Keeping the counts separate makes it easier for the
54 L{Registry} to detect when an L{_Observer} can be freed in the
55 L{Registry._unregisterObserver} method.
57 @ivar registry: Reference to the L{Registry} that created this L{_Observer}
58 @type registry: weakref.proxy to L{Registry}
59 @ivar ref_count: Reference count on this L{_Observer}
60 @type ref_count: integer
62 def __init__(self, registry):
64 Stores a reference to the creating L{Registry}. Intializes the reference
65 count on this object to zero.
67 @param registry: The L{Registry} that created this observer
68 @type registry: weakref.proxy to L{Registry}
70 self.registry = weakref.proxy(registry)
75 Increments the Python reference count on this L{_Observer} by one. This
76 method is called when a new client is registered in L{Registry} to receive
77 notification of an event type monitored by this L{_Observer}.
81 def clientUnref(self):
83 Decrements the pyatspi reference count on this L{_Observer} by one. This
84 method is called when a client is unregistered in L{Registry} to stop
85 receiving notifications of an event type monitored by this L{_Observer}.
89 def getClientRefCount(self):
91 @return: Current Python reference count on this L{_Observer}
97 '''Required by CORBA. Does nothing.'''
101 '''Required by CORBA. Does nothing.'''
104 class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
106 Observes keyboard press and release events.
108 @ivar registry: The L{Registry} that created this observer
109 @type registry: L{Registry}
110 @ivar key_set: Set of keys to monitor
111 @type key_set: list of integer
112 @ivar mask: Watch for key events while these modifiers are held
114 @ivar kind: Kind of events to monitor
116 @ivar mode: Keyboard event mode
117 @type mode: Accessibility.EventListenerMode
119 def __init__(self, registry, synchronous, preemptive, global_):
121 Creates a mode object that defines when key events will be received from
122 the system. Stores all other information for later registration.
124 @param registry: The L{Registry} that created this observer
125 @type registry: L{Registry}
126 @param synchronous: Handle the key event synchronously?
127 @type synchronous: boolean
128 @param preemptive: Allow event to be consumed?
129 @type preemptive: boolean
130 @param global_: Watch for events on inaccessible applications too?
131 @type global_: boolean
133 _Observer.__init__(self, registry)
134 self.mode = Accessibility.EventListenerMode()
135 self.mode.preemptive = preemptive
136 self.mode.synchronous = synchronous
137 self.mode._global = global_
139 def register(self, dc, key_set, mask, kind):
141 Starts keyboard event monitoring.
143 @param dc: Reference to a device controller
144 @type dc: Accessibility.DeviceEventController
145 @param key_set: Set of keys to monitor
146 @type key_set: list of integer
147 @param mask: Integer modifier mask or an iterable over multiple masks to
149 @type mask: integer, iterable, or None
150 @param kind: Kind of events to monitor
154 # check if the mask is iterable
157 # register a single integer if not
158 dc.registerKeystrokeListener(self._this(), key_set, mask, kind,
162 dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
164 def unregister(self, dc, key_set, mask, kind):
166 Stops keyboard event monitoring.
168 @param dc: Reference to a device controller
169 @type dc: Accessibility.DeviceEventController
170 @param key_set: Set of keys to monitor
171 @type key_set: list of integer
172 @param mask: Integer modifier mask or an iterable over multiple masks to
174 @type mask: integer, iterable, or None
175 @param kind: Kind of events to monitor
179 # check if the mask is iterable
182 # unregister a single integer if not
183 dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
186 dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
188 def queryInterface(self, repo_id):
190 Reports that this class only implements the AT-SPI DeviceEventListener
191 interface. Required by AT-SPI.
193 @param repo_id: Request for an interface
194 @type repo_id: string
195 @return: The underlying CORBA object for the device event listener
196 @rtype: Accessibility.EventListener
198 if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
203 def notifyEvent(self, ev):
205 Notifies the L{Registry} that an event has occurred. Wraps the raw event
206 object in our L{Event} class to support automatic ref and unref calls. An
207 observer can set the L{Event} consume flag to True to indicate this event
208 should not be allowed to pass to other AT-SPI observers or the underlying
211 @param ev: Keyboard event
212 @type ev: Accessibility.DeviceEvent
213 @return: Should the event be consumed (True) or allowed to pass on to other
214 AT-SPI observers (False)?
217 # wrap the device event
218 ev = event.DeviceEvent(ev)
219 self.registry.handleDeviceEvent(ev, self)
222 class _EventObserver(_Observer, Accessibility__POA.EventListener):
224 Observes all non-keyboard AT-SPI events. Can be reused across event types.
226 def register(self, reg, name):
228 Starts monitoring for the given event.
230 @param name: Name of the event to start monitoring
232 @param reg: Reference to the raw registry object
233 @type reg: Accessibility.Registry
235 reg.registerGlobalEventListener(self._this(), name)
237 def unregister(self, reg, name):
239 Stops monitoring for the given event.
241 @param name: Name of the event to stop monitoring
243 @param reg: Reference to the raw registry object
244 @type reg: Accessibility.Registry
246 reg.deregisterGlobalEventListener(self._this(), name)
248 def queryInterface(self, repo_id):
250 Reports that this class only implements the AT-SPI DeviceEventListener
251 interface. Required by AT-SPI.
253 @param repo_id: Request for an interface
254 @type repo_id: string
255 @return: The underlying CORBA object for the device event listener
256 @rtype: Accessibility.EventListener
258 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
263 def notifyEvent(self, ev):
265 Notifies the L{Registry} that an event has occurred. Wraps the raw event
266 object in our L{Event} class to support automatic ref and unref calls.
267 Aborts on any exception indicating the event could not be wrapped.
269 @param ev: AT-SPI event signal (anything but keyboard)
270 @type ev: Accessibility.Event
272 # wrap raw event so ref counts are correct before queueing
274 self.registry.handleEvent(ev)
276 class Registry(object):
278 Wraps the Accessibility.Registry to provide more Pythonic registration for
281 This object should be treated as a singleton, but such treatment is not
282 enforced. You can construct another instance of this object and give it a
283 reference to the Accessibility.Registry singleton. Doing so is harmless and
286 @ivar async: Should event dispatch to local listeners be decoupled from event
287 receiving from the registry?
289 @ivar reg: Reference to the real, wrapped registry object
290 @type reg: Accessibility.Registry
291 @ivar dev: Reference to the device controller
292 @type dev: Accessibility.DeviceEventController
293 @ivar queue: Queue of events awaiting local dispatch
294 @type queue: Queue.Queue
295 @ivar clients: Map of event names to client listeners
296 @type clients: dictionary
297 @ivar observers: Map of event names to AT-SPI L{_Observer} objects
298 @type observers: dictionary
300 def __init__(self, reg):
302 Stores a reference to the AT-SPI registry. Gets and stores a reference
303 to the DeviceEventController.
305 @param reg: Reference to the AT-SPI registry daemon
306 @type reg: Accessibility.Registry
310 self.dev = self.reg.getDeviceEventController()
311 self.queue = Queue.Queue()
317 @return: This instance of the registry
322 def start(self, async=False, gil=True):
324 Enter the main loop to start receiving and dispatching events.
326 @param async: Should event dispatch be asynchronous (decoupled) from
327 event receiving from the AT-SPI registry?
329 @param gil: Add an idle callback which releases the Python GIL for a few
330 milliseconds to allow other threads to run? Necessary if other threads
331 will be used in this process.
340 except KeyboardInterrupt, e:
341 # store the exception for later
342 releaseGIL.keyboard_exception = e
345 # make room for an exception if one occurs during the
346 releaseGIL.keyboard_exception = None
347 i = gobject.idle_add(releaseGIL)
349 # enter the main loop
352 except KeyboardInterrupt, e:
353 # re-raise the keyboard interrupt
356 # clear all observers
357 for name, ob in self.observers.items():
358 ob.unregister(self.reg, name)
360 gobject.source_remove(i)
361 if releaseGIL.keyboard_exception is not None:
362 # re-raise the keyboard interrupt we got during the GIL release
363 raise releaseGIL.keyboard_exception
365 def stop(self, *args):
366 '''Quits the main loop.'''
370 # ignore errors when quitting (probably already quitting)
373 def getDesktopCount(self):
375 Gets the number of available desktops.
377 @return: Number of desktops
379 @raise LookupError: When the count cannot be retrieved
382 return self.reg.getDesktopCount()
386 def getDesktop(self, i):
388 Gets a reference to the i-th desktop.
390 @param i: Which desktop to get
392 @return: Desktop reference
393 @rtype: Accessibility.Desktop
394 @raise LookupError: When the i-th desktop cannot be retrieved
397 return self.reg.getDesktop(i)
401 def registerEventListener(self, client, *names):
403 Registers a new client callback for the given event names. Supports
404 registration for all subevents if only partial event name is specified.
405 Do not include a trailing colon.
407 For example, 'object' will register for all object events,
408 'object:property-change' will register for all property change events,
409 and 'object:property-change:accessible-parent' will register only for the
410 parent property change event.
412 Registered clients will not be automatically removed when the client dies.
413 To ensure the client is properly garbage collected, call
414 L{deregisterEventListener}.
416 @param client: Callable to be invoked when the event occurs
417 @type client: callable
418 @param names: List of full or partial event names
419 @type names: list of string
422 # store the callback for each specific event name
423 self._registerClients(client, name)
425 def deregisterEventListener(self, client, *names):
427 Unregisters an existing client callback for the given event names. Supports
428 unregistration for all subevents if only partial event name is specified.
429 Do not include a trailing colon.
431 This method must be called to ensure a client registered by
432 L{registerEventListener} is properly garbage collected.
434 @param client: Client callback to remove
435 @type client: callable
436 @param names: List of full or partial event names
437 @type names: list of string
438 @return: Were event names specified for which the given client was not
444 # remove the callback for each specific event name
445 missed |= self._unregisterClients(client, name)
448 def registerKeystrokeListener(self, client, key_set=[], mask=0,
449 kind=(constants.KEY_PRESSED_EVENT,
450 constants.KEY_RELEASED_EVENT),
451 synchronous=True, preemptive=True,
454 Registers a listener for key stroke events.
456 @param client: Callable to be invoked when the event occurs
457 @type client: callable
458 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
459 to indicate all keys.
460 @type key_set: list of integer
461 @param mask: When the mask is None, the codes in the key_set will be
462 monitored only when no modifier is held. When the mask is an
463 integer, keys in the key_set will be monitored only when the modifiers in
464 the mask are held. When the mask is an iterable over more than one
465 integer, keys in the key_set will be monitored when any of the modifier
466 combinations in the set are held.
467 @type mask: integer, iterable, None
468 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
471 @param synchronous: Should the callback notification be synchronous, giving
472 the client the chance to consume the event?
473 @type synchronous: boolean
474 @param preemptive: Should the callback be allowed to preempt / consume the
476 @type preemptive: boolean
477 @param global_: Should callback occur even if an application not supporting
478 AT-SPI is in the foreground? (requires xevie)
479 @type global_: boolean
482 # see if we already have an observer for this client
483 ob = self.clients[client]
485 # create a new device observer for this client
486 ob = _DeviceObserver(self, synchronous, preemptive, global_)
487 # store the observer to client mapping, and the inverse
488 self.clients[ob] = client
489 self.clients[client] = ob
491 # None means all modifier combinations
492 mask = utils.allModifiers()
493 # register for new keystrokes on the observer
494 ob.register(self.dev, key_set, mask, kind)
496 def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
497 kind=(constants.KEY_PRESSED_EVENT,
498 constants.KEY_RELEASED_EVENT)):
500 Deregisters a listener for key stroke events.
502 @param client: Callable to be invoked when the event occurs
503 @type client: callable
504 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
505 to indicate all keys.
506 @type key_set: list of integer
507 @param mask: When the mask is None, the codes in the key_set will be
508 monitored only when no modifier is held. When the mask is an
509 integer, keys in the key_set will be monitored only when the modifiers in
510 the mask are held. When the mask is an iterable over more than one
511 integer, keys in the key_set will be monitored when any of the modifier
512 combinations in the set are held.
513 @type mask: integer, iterable, None
514 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
517 @raise KeyError: When the client isn't already registered for events
519 # see if we already have an observer for this client
520 ob = self.clients[client]
522 # None means all modifier combinations
523 mask = utils.allModifiers()
524 # register for new keystrokes on the observer
525 ob.unregister(self.dev, key_set, mask, kind)
527 def generateKeyboardEvent(self, keycode, keysym, kind):
529 Generates a keyboard event. One of the keycode or the keysym parameters
530 should be specified and the other should be None. The kind parameter is
531 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
532 KEY_SYM, or KEY_STRING.
534 @param keycode: Hardware keycode or None
535 @type keycode: integer
536 @param keysym: Symbolic key string or None
538 @param kind: Kind of event to synthesize
542 self.dev.generateKeyboardEvent(keycode, '', kind)
544 self.dev.generateKeyboardEvent(None, keysym, kind)
546 def generateMouseEvent(self, x, y, name):
548 Generates a mouse event at the given absolute x and y coordinate. The kind
549 of event generated is specified by the name. For example, MOUSE_B1P
550 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
553 @param x: Horizontal coordinate, usually left-hand oriented
555 @param y: Vertical coordinate, usually left-hand oriented
557 @param name: Name of the event to generate
560 self.dev.generateMouseEvent(x, y, name)
562 def handleDeviceEvent(self, event, ob):
564 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
565 in the order they were registered for the given AT-SPI event. If any
566 client sets the L{event.DeviceEvent.consume} flag to True, callbacks cease
567 for the event for clients of this registry instance. Clients of other
568 registry instances and clients in other processes may be affected
569 depending on the values of synchronous and preemptive used when invoking
570 L{registerKeystrokeListener}.
572 @note: Asynchronous dispatch of device events is not supported.
574 @param event: AT-SPI device event
575 @type event: L{event.DeviceEvent}
576 @param ob: Observer that received the event
577 @type ob: L{_DeviceObserver}
580 # try to get the client registered for this event type
581 client = self.clients[ob]
583 # client may have unregistered recently, ignore event
585 # make the call to the client
589 # print the exception, but don't let it stop notification
590 traceback.print_exc()
592 def handleEvent(self, event):
594 Handles an AT-SPI event by either queuing it for later dispatch when the
595 L{Registry.async} flag is set, or dispatching it immediately.
597 @param event: AT-SPI event
598 @type event: L{event.Event}
602 self.queue.put_nowait(event)
604 # dispatch immediately
605 self._dispatchEvent(event)
607 def _dispatchEvent(self, event):
609 Dispatches L{event.Event}s to registered clients. Clients are called in
610 the order they were registered for the given AT-SPI event. If any client
611 sets the L{Event} consume flag to True, callbacks cease for the event for
612 clients of this registry instance. Clients of other registry instances and
613 clients in other processes are unaffected.
615 @param event: AT-SPI event
616 @type event: L{event.Event}
619 # try to get the client registered for this event type
620 clients = self.clients[event.type.name]
622 # client may have unregistered recently, ignore event
624 # make the call to each client
625 for client in clients:
629 # print the exception, but don't let it stop notification
630 traceback.print_exc()
632 # don't allow further processing if the consume flag is set
635 def _registerClients(self, client, name):
637 Internal method that recursively associates a client with AT-SPI event
638 names. Allows a client to incompletely specify an event name in order to
639 register for subevents without specifying their full names manually.
641 @param client: Client callback to receive event notifications
642 @type client: callable
643 @param name: Partial or full event name
647 # look for an event name in our event tree dictionary
648 events = constants.EVENT_TREE[name]
650 # if the event name doesn't exist, it's a leaf event meaning there are
651 # no subtypes for that event
652 # add this client to the list of clients already in the dictionary
653 # using the event name as the key; if there are no clients yet for this
654 # event, insert an empty list into the dictionary before appending
656 et = event.EventType(name)
657 clients = self.clients.setdefault(et.name, [])
659 # if this succeeds, this client is already registered for the given
660 # event type, so ignore the request
661 clients.index(client)
663 # else register the client
664 clients.append(client)
665 self._registerObserver(name)
667 # if the event name does exist in the tree, there are subevents for
668 # this event; loop through them calling this method again to get to
671 self._registerClients(client, e)
673 def _unregisterClients(self, client, name):
675 Internal method that recursively unassociates a client with AT-SPI event
676 names. Allows a client to incompletely specify an event name in order to
677 unregister for subevents without specifying their full names manually.
679 @param client: Client callback to receive event notifications
680 @type client: callable
681 @param name: Partial or full event name
686 # look for an event name in our event tree dictionary
687 events = constants.EVENT_TREE[name]
690 # if the event name doesn't exist, it's a leaf event meaning there are
691 # no subtypes for that event
692 # get the list of registered clients and try to remove the one provided
693 et = event.EventType(name)
694 clients = self.clients[et.name]
695 clients.remove(client)
696 self._unregisterObserver(name)
697 except (ValueError, KeyError):
698 # ignore any exceptions indicating the client is not registered
701 # if the event name does exist in the tree, there are subevents for this
702 # event; loop through them calling this method again to get to the leaf
705 missed |= self._unregisterClients(client, e)
708 def _registerObserver(self, name):
710 Creates a new L{_Observer} to watch for events of the given type or
711 returns the existing observer if one is already registered. One
712 L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
713 any event name not found in the tree.
715 @param name: Raw name of the event to observe
717 @return: L{_Observer} object that is monitoring the event
720 et = event.EventType(name)
722 # see if an observer already exists for this event
723 ob = self.observers[et.name]
725 # build a new observer if one does not exist
726 ob = _EventObserver(self)
727 # we have to register for the raw name because it may be different from
728 # the parsed name determined by EventType (e.g. trailing ':' might be
730 ob.register(self.reg, name)
731 self.observers[et.name] = ob
732 # increase our client ref count so we know someone new is watching for the
737 def _unregisterObserver(self, name):
739 Destroys an existing L{_Observer} for the given event type only if no
740 clients are registered for the events it is monitoring.
742 @param name: Name of the event to observe
744 @raise KeyError: When an observer for the given event is not regist
746 et = event.EventType(name)
747 # see if an observer already exists for this event
748 ob = self.observers[et.name]
750 if ob.getClientRefCount() == 0:
751 ob.unregister(self.reg, name)
752 del self.observers[et.name]