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 L{pyLinAcc} reference count on this L{_Observer} by one.
84 This 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 reg: Reference to the raw registry object
144 @type reg: Accessibility.Registry
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 or iterable
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 reg: Reference to the raw registry object
169 @type reg: Accessibility.Registry
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 or iterable
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,
187 dc.deregisterKeystrokeListener(self._this(), key_set, m, kind,
190 def queryInterface(self, repo_id):
192 Reports that this class only implements the AT-SPI DeviceEventListener
193 interface. Required by AT-SPI.
195 @param repo_id: Request for an interface
196 @type repo_id: string
197 @return: The underlying CORBA object for the device event listener
198 @rtype: Accessibility.EventListener
200 if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
205 def notifyEvent(self, ev):
207 Notifies the L{Registry} that an event has occurred. Wraps the raw event
208 object in our L{Event} class to support automatic ref and unref calls. An
209 observer can set the L{Event} consume flag to True to indicate this event
210 should not be allowed to pass to other AT-SPI observers or the underlying
213 @param ev: Keyboard event
214 @type ev: Accessibility.DeviceEvent
215 @return: Should the event be consumed (True) or allowed to pass on to other
216 AT-SPI observers (False)?
219 # wrap the device event
220 ev = event.DeviceEvent(ev)
221 self.registry.handleDeviceEvent(ev, self)
224 class _EventObserver(_Observer, Accessibility__POA.EventListener):
226 Observes all non-keyboard AT-SPI events. Can be reused across event types.
228 def register(self, reg, name):
230 Starts monitoring for the given event.
232 @param name: Name of the event to start monitoring
234 @param reg: Reference to the raw registry object
235 @type reg: Accessibility.Registry
237 reg.registerGlobalEventListener(self._this(), name)
239 def unregister(self, reg, name):
241 Stops monitoring for the given event.
243 @param name: Name of the event to stop monitoring
245 @param reg: Reference to the raw registry object
246 @type reg: Accessibility.Registry
248 reg.deregisterGlobalEventListener(self._this(), name)
250 def queryInterface(self, repo_id):
252 Reports that this class only implements the AT-SPI DeviceEventListener
253 interface. Required by AT-SPI.
255 @param repo_id: Request for an interface
256 @type repo_id: string
257 @return: The underlying CORBA object for the device event listener
258 @rtype: Accessibility.EventListener
260 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
265 def notifyEvent(self, ev):
267 Notifies the L{Registry} that an event has occurred. Wraps the raw event
268 object in our L{Event} class to support automatic ref and unref calls.
269 Aborts on any exception indicating the event could not be wrapped.
271 @param ev: AT-SPI event signal (anything but keyboard)
272 @type ev: Accessibility.Event
274 # wrap raw event so ref counts are correct before queueing
276 self.registry.handleEvent(ev)
278 class Registry(object):
280 Wraps the Accessibility.Registry to provide more Pythonic registration for
283 This object should be treated as a singleton, but such treatment is not
284 enforced. You can construct another instance of this object and give it a
285 reference to the Accessibility.Registry singleton. Doing so is harmless and
291 @type reg: Accessibility.Registry
293 @type dev: Accessibility.DeviceEventController
295 @type queue: Queue.Queue
297 @type clients: dictionary
299 @type observers: dictionary
301 def __init__(self, reg):
303 Stores a reference to the AT-SPI registry. Gets and stores a reference
304 to the DeviceEventController.
306 @param reg: Reference to the AT-SPI registry daemon
307 @type reg: Accessibility.Registry
311 self.dev = self.reg.getDeviceEventController()
312 self.queue = Queue.Queue()
318 @return: This instance of the registry
323 def start(self, async=False, gil=True):
325 Enter the main loop to start receiving and dispatching events.
327 @param async: Should event dispatch be asynchronous (decoupled) from
328 event receiving from the AT-SPI registry?
330 @param gil: Add an idle callback which releases the Python GIL for a few
331 milliseconds to allow other threads to run? Necessary if other threads
332 will be used in this process.
337 # register a signal handler for gracefully killing the loop
338 signal.signal(signal.SIGINT, self.stop)
339 signal.signal(signal.SIGTERM, self.stop)
345 i = gobject.idle_add(releaseGIL)
347 # enter the main loop
351 gobject.source_remove(i)
353 def stop(self, *args):
354 '''Quits the main loop.'''
358 # ignore errors when quitting (probably already quitting)
361 def getDesktopCount(self):
363 Gets the number of available desktops.
365 @return: Number of desktops
367 @raise LookupError: When the count cannot be retrieved
370 return self.reg.getDesktopCount()
374 def getDesktop(self, i):
376 Gets a reference to the i-th desktop.
378 @param i: Which desktop to get
380 @return: Desktop reference
381 @rtype: Accessibility.Desktop
382 @raise LookupError: When the i-th desktop cannot be retrieved
385 return self.reg.getDesktop(i)
389 def registerEventListener(self, client, *names):
391 Registers a new client callback for the given event names. Supports
392 registration for all subevents if only partial event name is specified.
393 Do not include a trailing colon.
395 For example, 'object' will register for all object events,
396 'object:property-change' will register for all property change events,
397 and 'object:property-change:accessible-parent' will register only for the
398 parent property change event.
400 Registered clients will not be automatically removed when the client dies.
401 To ensure the client is properly garbage collected, call
402 L{Manager.removeClient}.
404 @param client: Callable to be invoked when the event occurs
405 @type client: callable
406 @param names: List of full or partial event names
407 @type names: list of string
410 # store the callback for each specific event name
411 self._registerClients(client, name)
413 def deregisterEventListener(self, client, *names):
415 Unregisters an existing client callback for the given event names. Supports
416 unregistration for all subevents if only partial event name is specified.
417 Do not include a trailing colon.
419 This method must be called to ensure a client registered by
420 L{Manager.addClient} is properly garbage collected.
422 @param client: Client callback to remove
423 @type client: callable
424 @param names: List of full or partial event names
425 @type names: list of string
426 @return: Were event names specified for which the given client was not
432 # remove the callback for each specific event name
433 missed |= self._unregisterClients(client, name)
436 def registerKeystrokeListener(self, client, key_set=[], mask=0,
437 kind=(constants.KEY_PRESSED_EVENT,
438 constants.KEY_RELEASED_EVENT),
439 synchronous=True, preemptive=True,
442 Registers a listener for key stroke events.
444 @param client: Callable to be invoked when the event occurs
445 @type client: callable
446 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
447 to indicate all keys.
448 @type key_set: list of integer
449 @param mask: When the mask is None, the codes in the key_set will be
450 monitored only when no modifier is held. When the mask is an
451 integer, keys in the key_set will be monitored only when the modifiers in
452 the mask are held. When the mask is an iterable over more than one
453 integer, keys in the key_set will be monitored when any of the modifier
454 combinations in the set are held.
456 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
459 @param synchronous: Should the callback notification be synchronous, giving
460 the client the chance to consume the event?
461 @type synchronous: boolean
462 @param preemptive: Should the callback be allowed to preempt / consume the
464 @type preemptive: boolean
465 @param global_: Should callback occur even if an application not supporting
466 AT-SPI is in the foreground? (requires xevie)
467 @type global_: boolean
470 # see if we already have an observer for this client
471 ob = self.clients[client]
473 # create a new device observer for this client
474 ob = _DeviceObserver(self, synchronous, preemptive, global_)
475 # store the observer to client mapping, and the inverse
476 self.clients[ob] = client
477 self.clients[client] = ob
478 # register for new keystrokes on the observer
479 ob.register(self.dev, key_set, mask, kind)
481 def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
482 kind=(constants.KEY_PRESSED_EVENT,
483 constants.KEY_RELEASED_EVENT)):
485 Deregisters a listener for key stroke events.
487 @param client: Callable to be invoked when the event occurs
488 @type client: callable
489 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
490 to indicate all keys.
491 @type key_set: list of integer
492 @param mask: When the mask is None, the codes in the key_set will be
493 monitored only when no modifier is held. When the mask is an
494 integer, keys in the key_set will be monitored only when the modifiers in
495 the mask are held. When the mask is an iterable over more than one
496 integer, keys in the key_set will be monitored when any of the modifier
497 combinations in the set are held.
499 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
502 @raise KeyError: When the client isn't already registered for events
504 # see if we already have an observer for this client
505 ob = self.clients[client]
506 # register for new keystrokes on the observer
507 ob.unregister(self.dev, key_set, mask, kind)
509 def generateKeyboardEvent(self, keycode, keysym, kind):
511 Generates a keyboard event. One of the keycode or the keysym parameters
512 should be specified and the other should be None. The kind parameter is
513 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
514 KEY_SYM, or KEY_STRING.
516 @param keycode: Hardware keycode or None
517 @type keycode: integer
518 @param keysym: Symbolic key string or None
520 @param kind: Kind of event to synthesize
524 self.dev.generateKeyboardEvent(keycode, '', kind)
526 self.dev.generateKeyboardEvent(None, keysym, kind)
528 def generateMouseEvent(self, x, y, name):
530 Generates a mouse event at the given absolute x and y coordinate. The kind
531 of event generated is specified by the name. For example, MOUSE_B1P
532 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
535 @param x: Horizontal coordinate, usually left-hand oriented
537 @param y: Vertical coordinate, usually left-hand oriented
539 @param name: Name of the event to generate
542 self.dev.generateMouseEvent(x, y, name)
544 def handleDeviceEvent(self, event, ob):
546 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
547 in the order they were registered for the given AT-SPI event. If any
548 client sets the L{event.DeviceEvent.consume} flag to True, callbacks cease
549 for the event for clients of this registry instance. Clients of other
550 registry instances and clients in other processes may be affected
551 depending on the values of synchronous and preemptive used when invoking
552 L{registerKeystrokeListener}.
554 @note: Asynchronous dispatch of device events is not supported.
556 @param event: AT-SPI device event
557 @type event: L{event.DeviceEvent}
558 @param ob: Observer that received the event
559 @type ob: L{_DeviceObserver}
562 # try to get the client registered for this event type
563 client = self.clients[ob]
565 # client may have unregistered recently, ignore event
567 # make the call to the client
571 # print the exception, but don't let it stop notification
572 traceback.print_exc()
574 def handleEvent(self, event):
576 Handles an AT-SPI event by either queuing it for later dispatch when the
577 L{async} flag is set, or dispatching it immediately.
579 @param event: AT-SPI event
580 @type event: L{event.Event}
584 self.queue.put_nowait(event)
586 # dispatch immediately
587 self._dispatchEvent(event)
589 def _dispatchEvent(self, event):
591 Dispatches L{event.Event}s to registered clients. Clients are called in
592 the order they were registered for the given AT-SPI event. If any client
593 sets the L{Event} consume flag to True, callbacks cease for the event for
594 clients of this registry instance. Clients of other registry instances and
595 clients in other processes are unaffected.
597 @param event: AT-SPI event
598 @type event: L{event.Event}
601 # try to get the client registered for this event type
602 clients = self.clients[event.type.name]
604 # client may have unregistered recently, ignore event
606 # make the call to each client
607 for client in clients:
611 # print the exception, but don't let it stop notification
612 traceback.print_exc()
614 # don't allow further processing if the consume flag is set
617 def _registerClients(self, client, name):
619 Internal method that recursively associates a client with AT-SPI event
620 names. Allows a client to incompletely specify an event name in order to
621 register for subevents without specifying their full names manually.
623 @param client: Client callback to receive event notifications
624 @type client: callable
625 @param name: Partial or full event name
629 # look for an event name in our event tree dictionary
630 events = constants.EVENT_TREE[name]
632 # if the event name doesn't exist, it's a leaf event meaning there are
633 # no subtypes for that event
634 # add this client to the list of clients already in the dictionary
635 # using the event name as the key; if there are no clients yet for this
636 # event, insert an empty list into the dictionary before appending
638 et = event.EventType(name)
639 clients = self.clients.setdefault(et.name, [])
641 # if this succeeds, this client is already registered for the given
642 # event type, so ignore the request
643 clients.index(client)
645 # else register the client
646 clients.append(client)
647 self._registerObserver(name)
649 # if the event name does exist in the tree, there are subevents for
650 # this event; loop through them calling this method again to get to
653 self._registerClients(client, e)
655 def _unregisterClients(self, client, name):
657 Internal method that recursively unassociates a client with AT-SPI event
658 names. Allows a client to incompletely specify an event name in order to
659 unregister for subevents without specifying their full names manually.
661 @param client: Client callback to receive event notifications
662 @type client: callable
663 @param name: Partial or full event name
668 # look for an event name in our event tree dictionary
669 events = constants.EVENT_TREE[name]
672 # if the event name doesn't exist, it's a leaf event meaning there are
673 # no subtypes for that event
674 # get the list of registered clients and try to remove the one provided
675 et = event.EventType(name)
676 clients = self.clients[et.name]
677 clients.remove(client)
678 self._unregisterObserver(name)
679 except (ValueError, KeyError):
680 # ignore any exceptions indicating the client is not registered
683 # if the event name does exist in the tree, there are subevents for this
684 # event; loop through them calling this method again to get to the leaf
687 missed |= self._unregisterClients(client, e)
690 def _registerObserver(self, name):
692 Creates a new L{_Observer} to watch for events of the given type or
693 returns the existing observer if one is already registered. One
694 L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
695 any event name not found in the tree.
697 @param name: Raw name of the event to observe
699 @return: L{_Observer} object that is monitoring the event
702 et = event.EventType(name)
704 # see if an observer already exists for this event
705 ob = self.observers[et.name]
707 # build a new observer if one does not exist
708 ob = _EventObserver(self)
709 # we have to register for the raw name because it may be different from
710 # the parsed name determined by EventType (e.g. trailing ':' might be
712 ob.register(self.reg, name)
713 self.observers[et.name] = ob
714 # increase our client ref count so we know someone new is watching for the
719 def _unregisterObserver(self, name):
721 Destroys an existing L{Observer} for the given event type only if no clients
722 are registered for the events it is monitoring.
724 @param name: Name of the event to observe
726 @raise KeyError: When an observer for the given event is not regist
728 et = event.EventType(name)
729 # see if an observer already exists for this event
730 ob = self.observers[et.name]
732 if ob.getClientRefCount() == 0:
733 ob.unregister(self.registry, name)
734 del self.observers[et.name]