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 return True to indicate this event should not be allowed to pass
208 to other AT-SPI observers or the underlying application.
210 @param ev: Keyboard event
211 @type ev: Accessibility.DeviceEvent
212 @return: Should the event be consumed (True) or allowed to pass on to other
213 AT-SPI observers (False)?
216 # wrap the device event
217 ev = event.DeviceEvent(ev)
218 return self.registry.handleDeviceEvent(ev, self)
220 class _EventObserver(_Observer, Accessibility__POA.EventListener):
222 Observes all non-keyboard AT-SPI events. Can be reused across event types.
224 def register(self, reg, name):
226 Starts monitoring for the given event.
228 @param name: Name of the event to start monitoring
230 @param reg: Reference to the raw registry object
231 @type reg: Accessibility.Registry
233 reg.registerGlobalEventListener(self._this(), name)
235 def unregister(self, reg, name):
237 Stops monitoring for the given event.
239 @param name: Name of the event to stop monitoring
241 @param reg: Reference to the raw registry object
242 @type reg: Accessibility.Registry
244 reg.deregisterGlobalEventListener(self._this(), name)
246 def queryInterface(self, repo_id):
248 Reports that this class only implements the AT-SPI DeviceEventListener
249 interface. Required by AT-SPI.
251 @param repo_id: Request for an interface
252 @type repo_id: string
253 @return: The underlying CORBA object for the device event listener
254 @rtype: Accessibility.EventListener
256 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
261 def notifyEvent(self, ev):
263 Notifies the L{Registry} that an event has occurred. Wraps the raw event
264 object in our L{Event} class to support automatic ref and unref calls.
265 Aborts on any exception indicating the event could not be wrapped.
267 @param ev: AT-SPI event signal (anything but keyboard)
268 @type ev: Accessibility.Event
270 # wrap raw event so ref counts are correct before queueing
272 self.registry.handleEvent(ev)
274 class Registry(object):
276 Wraps the Accessibility.Registry to provide more Pythonic registration for
279 This object should be treated as a singleton, but such treatment is not
280 enforced. You can construct another instance of this object and give it a
281 reference to the Accessibility.Registry singleton. Doing so is harmless and
284 @ivar async: Should event dispatch to local listeners be decoupled from event
285 receiving from the registry?
287 @ivar reg: Reference to the real, wrapped registry object
288 @type reg: Accessibility.Registry
289 @ivar dev: Reference to the device controller
290 @type dev: Accessibility.DeviceEventController
291 @ivar queue: Queue of events awaiting local dispatch
292 @type queue: Queue.Queue
293 @ivar clients: Map of event names to client listeners
294 @type clients: dictionary
295 @ivar observers: Map of event names to AT-SPI L{_Observer} objects
296 @type observers: dictionary
298 def __init__(self, reg):
300 Stores a reference to the AT-SPI registry. Gets and stores a reference
301 to the DeviceEventController.
303 @param reg: Reference to the AT-SPI registry daemon
304 @type reg: Accessibility.Registry
308 self.dev = self.reg.getDeviceEventController()
309 self.queue = Queue.Queue()
315 @return: This instance of the registry
320 def start(self, async=False, gil=True):
322 Enter the main loop to start receiving and dispatching events.
324 @param async: Should event dispatch be asynchronous (decoupled) from
325 event receiving from the AT-SPI registry?
327 @param gil: Add an idle callback which releases the Python GIL for a few
328 milliseconds to allow other threads to run? Necessary if other threads
329 will be used in this process.
338 except KeyboardInterrupt, e:
339 # store the exception for later
340 releaseGIL.keyboard_exception = e
343 # make room for an exception if one occurs during the
344 releaseGIL.keyboard_exception = None
345 i = gobject.idle_add(releaseGIL)
347 # enter the main loop
351 # clear all observers
352 for name, ob in self.observers.items():
353 ob.unregister(self.reg, name)
355 gobject.source_remove(i)
356 if releaseGIL.keyboard_exception is not None:
357 # raise an keyboard exception we may have gotten earlier
358 raise releaseGIL.keyboard_exception
360 def stop(self, *args):
361 '''Quits the main loop.'''
365 # ignore errors when quitting (probably already quitting)
369 def getDesktopCount(self):
371 Gets the number of available desktops.
373 @return: Number of desktops
375 @raise LookupError: When the count cannot be retrieved
378 return self.reg.getDesktopCount()
382 def getDesktop(self, i):
384 Gets a reference to the i-th desktop.
386 @param i: Which desktop to get
388 @return: Desktop reference
389 @rtype: Accessibility.Desktop
390 @raise LookupError: When the i-th desktop cannot be retrieved
393 return self.reg.getDesktop(i)
397 def registerEventListener(self, client, *names):
399 Registers a new client callback for the given event names. Supports
400 registration for all subevents if only partial event name is specified.
401 Do not include a trailing colon.
403 For example, 'object' will register for all object events,
404 'object:property-change' will register for all property change events,
405 and 'object:property-change:accessible-parent' will register only for the
406 parent property change event.
408 Registered clients will not be automatically removed when the client dies.
409 To ensure the client is properly garbage collected, call
410 L{deregisterEventListener}.
412 @param client: Callable to be invoked when the event occurs
413 @type client: callable
414 @param names: List of full or partial event names
415 @type names: list of string
418 # store the callback for each specific event name
419 self._registerClients(client, name)
421 def deregisterEventListener(self, client, *names):
423 Unregisters an existing client callback for the given event names. Supports
424 unregistration for all subevents if only partial event name is specified.
425 Do not include a trailing colon.
427 This method must be called to ensure a client registered by
428 L{registerEventListener} is properly garbage collected.
430 @param client: Client callback to remove
431 @type client: callable
432 @param names: List of full or partial event names
433 @type names: list of string
434 @return: Were event names specified for which the given client was not
440 # remove the callback for each specific event name
441 missed |= self._unregisterClients(client, name)
444 def registerKeystrokeListener(self, client, key_set=[], mask=0,
445 kind=(constants.KEY_PRESSED_EVENT,
446 constants.KEY_RELEASED_EVENT),
447 synchronous=True, preemptive=True,
450 Registers a listener for key stroke events.
452 @param client: Callable to be invoked when the event occurs
453 @type client: callable
454 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
455 to indicate all keys.
456 @type key_set: list of integer
457 @param mask: When the mask is None, the codes in the key_set will be
458 monitored only when no modifier is held. When the mask is an
459 integer, keys in the key_set will be monitored only when the modifiers in
460 the mask are held. When the mask is an iterable over more than one
461 integer, keys in the key_set will be monitored when any of the modifier
462 combinations in the set are held.
463 @type mask: integer, iterable, None
464 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
467 @param synchronous: Should the callback notification be synchronous, giving
468 the client the chance to consume the event?
469 @type synchronous: boolean
470 @param preemptive: Should the callback be allowed to preempt / consume the
472 @type preemptive: boolean
473 @param global_: Should callback occur even if an application not supporting
474 AT-SPI is in the foreground? (requires xevie)
475 @type global_: boolean
478 # see if we already have an observer for this client
479 ob = self.clients[client]
481 # create a new device observer for this client
482 ob = _DeviceObserver(self, synchronous, preemptive, global_)
483 # store the observer to client mapping, and the inverse
484 self.clients[ob] = client
485 self.clients[client] = ob
487 # None means all modifier combinations
488 mask = utils.allModifiers()
489 # register for new keystrokes on the observer
490 ob.register(self.dev, key_set, mask, kind)
492 def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
493 kind=(constants.KEY_PRESSED_EVENT,
494 constants.KEY_RELEASED_EVENT)):
496 Deregisters a listener for key stroke events.
498 @param client: Callable to be invoked when the event occurs
499 @type client: callable
500 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
501 to indicate all keys.
502 @type key_set: list of integer
503 @param mask: When the mask is None, the codes in the key_set will be
504 monitored only when no modifier is held. When the mask is an
505 integer, keys in the key_set will be monitored only when the modifiers in
506 the mask are held. When the mask is an iterable over more than one
507 integer, keys in the key_set will be monitored when any of the modifier
508 combinations in the set are held.
509 @type mask: integer, iterable, None
510 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
513 @raise KeyError: When the client isn't already registered for events
515 # see if we already have an observer for this client
516 ob = self.clients[client]
518 # None means all modifier combinations
519 mask = utils.allModifiers()
520 # register for new keystrokes on the observer
521 ob.unregister(self.dev, key_set, mask, kind)
523 def generateKeyboardEvent(self, keycode, keysym, kind):
525 Generates a keyboard event. One of the keycode or the keysym parameters
526 should be specified and the other should be None. The kind parameter is
527 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
528 KEY_SYM, or KEY_STRING.
530 @param keycode: Hardware keycode or None
531 @type keycode: integer
532 @param keysym: Symbolic key string or None
534 @param kind: Kind of event to synthesize
538 self.dev.generateKeyboardEvent(keycode, '', kind)
540 self.dev.generateKeyboardEvent(None, keysym, kind)
542 def generateMouseEvent(self, x, y, name):
544 Generates a mouse event at the given absolute x and y coordinate. The kind
545 of event generated is specified by the name. For example, MOUSE_B1P
546 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
549 @param x: Horizontal coordinate, usually left-hand oriented
551 @param y: Vertical coordinate, usually left-hand oriented
553 @param name: Name of the event to generate
556 self.dev.generateMouseEvent(x, y, name)
558 def handleDeviceEvent(self, event, ob):
560 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
561 in the order they were registered for the given AT-SPI event. If any
562 client returns True, callbacks cease for the event for clients of this registry
563 instance. Clients of other registry instances and clients in other processes may
564 be affected depending on the values of synchronous and preemptive used when invoking
565 L{registerKeystrokeListener}.
567 @note: Asynchronous dispatch of device events is not supported.
569 @param event: AT-SPI device event
570 @type event: L{event.DeviceEvent}
571 @param ob: Observer that received the event
572 @type ob: L{_DeviceObserver}
574 @return: Should the event be consumed (True) or allowed to pass on to other
575 AT-SPI observers (False)?
579 # try to get the client registered for this event type
580 client = self.clients[ob]
582 # client may have unregistered recently, ignore event
584 # make the call to the client
586 return client(event) or event.consume
588 # print the exception, but don't let it stop notification
589 traceback.print_exc()
591 def handleEvent(self, event):
593 Handles an AT-SPI event by either queuing it for later dispatch when the
594 L{Registry.async} flag is set, or dispatching it immediately.
596 @param event: AT-SPI event
597 @type event: L{event.Event}
601 self.queue.put_nowait(event)
603 # dispatch immediately
604 self._dispatchEvent(event)
606 def _dispatchEvent(self, event):
608 Dispatches L{event.Event}s to registered clients. Clients are called in
609 the order they were registered for the given AT-SPI event. If any client
610 returns True, callbacks cease for the event for clients of this registry
611 instance. Clients of other registry instances and clients in other processes
614 @param event: AT-SPI event
615 @type event: L{event.Event}
619 # try to get the client registered for this event type
620 clients = self.clients[et.name]
623 # we may not have registered for the complete subtree of events
624 # if our tree does not list all of a certain type (e.g.
625 # object:state-changed:*); try again with klass and major only
626 if et.detail is not None:
627 # Strip the 'detail' field.
628 clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
629 elif et.minor is not None:
630 # The event could possibly be object:state-changed:*.
631 clients = self.clients['%s:%s' % (et.klass, et.major)]
633 # client may have unregistered recently, ignore event
635 # make the call to each client
637 for client in clients:
639 consume = client(event) or False
641 # print the exception, but don't let it stop notification
642 traceback.print_exc()
643 if consume or event.consume:
644 # don't allow further processing if a client returns True
647 def flushEvents(self):
649 Flushes the event queue by destroying it and recreating it.
651 self.queue = Queue.Queue()
653 def pumpQueuedEvents(self, num=-1):
655 Provides asynch processing of events in the queue by executeing them with
656 _dispatchEvent() (as is done immediately when synch processing).
657 This method would normally be called from a main loop or idle function.
659 @param num: Number of events to pump. If number is negative it pumps
660 the entire queue. Default is -1.
662 @return: True if queue is not empty after events were pumped.
666 # Dequeue as many events as currently in the queue.
667 num = self.queue.qsize()
668 for i in xrange(num):
670 # get next waiting event
671 event = self.queue.get_nowait()
674 self._dispatchEvent(event)
676 return not self.queue.empty()
678 def _registerClients(self, client, name):
680 Internal method that recursively associates a client with AT-SPI event
681 names. Allows a client to incompletely specify an event name in order to
682 register for subevents without specifying their full names manually.
684 @param client: Client callback to receive event notifications
685 @type client: callable
686 @param name: Partial or full event name
690 # look for an event name in our event tree dictionary
691 events = constants.EVENT_TREE[name]
693 # if the event name doesn't exist, it's a leaf event meaning there are
694 # no subtypes for that event
695 # add this client to the list of clients already in the dictionary
696 # using the event name as the key; if there are no clients yet for this
697 # event, insert an empty list into the dictionary before appending
699 et = event.EventType(name)
700 clients = self.clients.setdefault(et.name, [])
702 # if this succeeds, this client is already registered for the given
703 # event type, so ignore the request
704 clients.index(client)
706 # else register the client
707 clients.append(client)
708 self._registerObserver(name)
710 # if the event name does exist in the tree, there are subevents for
711 # this event; loop through them calling this method again to get to
714 self._registerClients(client, e)
716 def _unregisterClients(self, client, name):
718 Internal method that recursively unassociates a client with AT-SPI event
719 names. Allows a client to incompletely specify an event name in order to
720 unregister for subevents without specifying their full names manually.
722 @param client: Client callback to receive event notifications
723 @type client: callable
724 @param name: Partial or full event name
729 # look for an event name in our event tree dictionary
730 events = constants.EVENT_TREE[name]
733 # if the event name doesn't exist, it's a leaf event meaning there are
734 # no subtypes for that event
735 # get the list of registered clients and try to remove the one provided
736 et = event.EventType(name)
737 clients = self.clients[et.name]
738 clients.remove(client)
739 self._unregisterObserver(name)
740 except (ValueError, KeyError):
741 # ignore any exceptions indicating the client is not registered
744 # if the event name does exist in the tree, there are subevents for this
745 # event; loop through them calling this method again to get to the leaf
748 missed |= self._unregisterClients(client, e)
751 def _registerObserver(self, name):
753 Creates a new L{_Observer} to watch for events of the given type or
754 returns the existing observer if one is already registered. One
755 L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
756 any event name not found in the tree.
758 @param name: Raw name of the event to observe
760 @return: L{_Observer} object that is monitoring the event
763 et = event.EventType(name)
765 # see if an observer already exists for this event
766 ob = self.observers[et.name]
768 # build a new observer if one does not exist
769 ob = _EventObserver(self)
770 # we have to register for the raw name because it may be different from
771 # the parsed name determined by EventType (e.g. trailing ':' might be
773 ob.register(self.reg, name)
774 self.observers[et.name] = ob
775 # increase our client ref count so we know someone new is watching for the
780 def _unregisterObserver(self, name):
782 Destroys an existing L{_Observer} for the given event type only if no
783 clients are registered for the events it is monitoring.
785 @param name: Name of the event to observe
787 @raise KeyError: When an observer for the given event is not regist
789 et = event.EventType(name)
790 # see if an observer already exists for this event
791 ob = self.observers[et.name]
793 if ob.getClientRefCount() == 0:
794 ob.unregister(self.reg, name)
795 del self.observers[et.name]