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
32 ATSPI_DEVICE_EVENT_CONTROLLER = 'org.freedesktop.atspi.DeviceEventController'
33 ATSPI_DEVICE_EVENT_LISTENER = 'org.freedesktop.atspi.DeviceEventListener'
35 class _Observer(object):
37 Parent class for all event observers. Dispatches all received events to the
38 L{Registry} that created this L{_Observer}. Provides basic reference counting
39 functionality needed by L{Registry} to determine when an L{_Observer} can be
40 released for garbage collection.
42 The reference counting provided by this class is independent of the reference
43 counting used by CORBA. Keeping the counts separate makes it easier for the
44 L{Registry} to detect when an L{_Observer} can be freed in the
45 L{Registry._unregisterObserver} method.
47 @ivar registry: Reference to the L{Registry} that created this L{_Observer}
48 @type registry: weakref.proxy to L{Registry}
49 @ivar ref_count: Reference count on this L{_Observer}
50 @type ref_count: integer
52 def __init__(self, registry):
54 Stores a reference to the creating L{Registry}. Intializes the reference
55 count on this object to zero.
57 @param registry: The L{Registry} that created this observer
58 @type registry: weakref.proxy to L{Registry}
60 self.registry = weakref.proxy(registry)
65 Increments the Python reference count on this L{_Observer} by one. This
66 method is called when a new client is registered in L{Registry} to receive
67 notification of an event type monitored by this L{_Observer}.
71 def clientUnref(self):
73 Decrements the pyatspi reference count on this L{_Observer} by one. This
74 method is called when a client is unregistered in L{Registry} to stop
75 receiving notifications of an event type monitored by this L{_Observer}.
79 def getClientRefCount(self):
81 @return: Current Python reference count on this L{_Observer}
87 """Required by CORBA. Does nothing."""
91 """Required by CORBA. Does nothing."""
94 class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
96 Observes keyboard press and release events.
98 @ivar registry: The L{Registry} that created this observer
99 @type registry: L{Registry}
100 @ivar key_set: Set of keys to monitor
101 @type key_set: list of integer
102 @ivar mask: Watch for key events while these modifiers are held
104 @ivar kind: Kind of events to monitor
106 @ivar mode: Keyboard event mode
107 @type mode: Accessibility.EventListenerMode
109 def __init__(self, registry, synchronous, preemptive, global_):
111 Creates a mode object that defines when key events will be received from
112 the system. Stores all other information for later registration.
114 @param registry: The L{Registry} that created this observer
115 @type registry: L{Registry}
116 @param synchronous: Handle the key event synchronously?
117 @type synchronous: boolean
118 @param preemptive: Allow event to be consumed?
119 @type preemptive: boolean
120 @param global_: Watch for events on inaccessible applications too?
121 @type global_: boolean
123 _Observer.__init__(self, registry)
124 self.mode = Accessibility.EventListenerMode()
125 self.mode.preemptive = preemptive
126 self.mode.synchronous = synchronous
127 self.mode._global = global_
129 def register(self, dc, key_set, mask, kind):
131 Starts keyboard event monitoring.
133 @param dc: Reference to a device controller
134 @type dc: Accessibility.DeviceEventController
135 @param key_set: Set of keys to monitor
136 @type key_set: list of integer
137 @param mask: Integer modifier mask or an iterable over multiple masks to
139 @type mask: integer, iterable, or None
140 @param kind: Kind of events to monitor
144 # check if the mask is iterable
147 # register a single integer if not
148 dc.registerKeystrokeListener(self._this(), key_set, mask, kind,
152 dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
154 def unregister(self, dc, key_set, mask, kind):
156 Stops keyboard event monitoring.
158 @param dc: Reference to a device controller
159 @type dc: Accessibility.DeviceEventController
160 @param key_set: Set of keys to monitor
161 @type key_set: list of integer
162 @param mask: Integer modifier mask or an iterable over multiple masks to
164 @type mask: integer, iterable, or None
165 @param kind: Kind of events to monitor
169 # check if the mask is iterable
172 # unregister a single integer if not
173 dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
176 dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
178 def queryInterface(self, repo_id):
180 Reports that this class only implements the AT-SPI DeviceEventListener
181 interface. Required by AT-SPI.
183 @param repo_id: Request for an interface
184 @type repo_id: string
185 @return: The underlying CORBA object for the device event listener
186 @rtype: Accessibility.EventListener
188 if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
193 def notifyEvent(self, ev):
195 Notifies the L{Registry} that an event has occurred. Wraps the raw event
196 object in our L{Event} class to support automatic ref and unref calls. An
197 observer can return True to indicate this event should not be allowed to pass
198 to other AT-SPI observers or the underlying application.
200 @param ev: Keyboard event
201 @type ev: Accessibility.DeviceEvent
202 @return: Should the event be consumed (True) or allowed to pass on to other
203 AT-SPI observers (False)?
206 # wrap the device event
207 ev = event.DeviceEvent(ev)
208 return self.registry.handleDeviceEvent(ev, self)
210 class _EventObserver(_Observer, Accessibility__POA.EventListener):
212 Observes all non-keyboard AT-SPI events. Can be reused across event types.
214 def register(self, reg, name):
216 Starts monitoring for the given event.
218 @param name: Name of the event to start monitoring
220 @param reg: Reference to the raw registry object
221 @type reg: Accessibility.Registry
223 reg.registerGlobalEventListener(self._this(), name)
225 def unregister(self, reg, name):
227 Stops monitoring for the given event.
229 @param name: Name of the event to stop monitoring
231 @param reg: Reference to the raw registry object
232 @type reg: Accessibility.Registry
234 reg.deregisterGlobalEventListener(self._this(), name)
236 def queryInterface(self, repo_id):
238 Reports that this class only implements the AT-SPI DeviceEventListener
239 interface. Required by AT-SPI.
241 @param repo_id: Request for an interface
242 @type repo_id: string
243 @return: The underlying CORBA object for the device event listener
244 @rtype: Accessibility.EventListener
246 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
251 def notifyEvent(self, ev):
253 Notifies the L{Registry} that an event has occurred. Wraps the raw event
254 object in our L{Event} class to support automatic ref and unref calls.
255 Aborts on any exception indicating the event could not be wrapped.
257 @param ev: AT-SPI event signal (anything but keyboard)
258 @type ev: Accessibility.Event
260 # wrap raw event so ref counts are correct before queueing
262 self.registry.handleEvent(ev)
264 class Registry(object):
266 Wraps the Accessibility.Registry to provide more Pythonic registration for
269 This object should be treated as a singleton, but such treatment is not
270 enforced. You can construct another instance of this object and give it a
271 reference to the Accessibility.Registry singleton. Doing so is harmless and
274 @ivar async: Should event dispatch to local listeners be decoupled from event
275 receiving from the registry?
277 @ivar reg: Reference to the real, wrapped registry object
278 @type reg: Accessibility.Registry
279 @ivar dev: Reference to the device controller
280 @type dev: Accessibility.DeviceEventController
281 @ivar queue: Queue of events awaiting local dispatch
282 @type queue: Queue.Queue
283 @ivar clients: Map of event names to client listeners
284 @type clients: dictionary
285 @ivar observers: Map of event names to AT-SPI L{_Observer} objects
286 @type observers: dictionary
288 def __init__(self, reg):
290 Stores a reference to the AT-SPI registry. Gets and stores a reference
291 to the DeviceEventController.
293 @param reg: Reference to the AT-SPI registry daemon
294 @type reg: Accessibility.Registry
298 self.dev = self.reg.getDeviceEventController()
299 self.queue = Queue.Queue()
305 @return: This instance of the registry
310 def start(self, async=False, gil=True):
312 Enter the main loop to start receiving and dispatching events.
314 @param async: Should event dispatch be asynchronous (decoupled) from
315 event receiving from the AT-SPI registry?
317 @param gil: Add an idle callback which releases the Python GIL for a few
318 milliseconds to allow other threads to run? Necessary if other threads
319 will be used in this process.
328 except KeyboardInterrupt, e:
329 # store the exception for later
330 releaseGIL.keyboard_exception = e
333 # make room for an exception if one occurs during the
334 releaseGIL.keyboard_exception = None
335 i = gobject.idle_add(releaseGIL)
337 # enter the main loop
341 # clear all observers
342 for name, ob in self.observers.items():
343 ob.unregister(self.reg, name)
345 gobject.source_remove(i)
346 if releaseGIL.keyboard_exception is not None:
347 # raise an keyboard exception we may have gotten earlier
348 raise releaseGIL.keyboard_exception
350 def stop(self, *args):
351 """Quits the main loop."""
355 # ignore errors when quitting (probably already quitting)
359 def getDesktopCount(self):
361 Gets the number of available desktops.
363 @return: Number of desktops
365 @raise LookupError: When the count cannot be retrieved
368 return self.reg.getDesktopCount()
372 def getDesktop(self, i):
374 Gets a reference to the i-th desktop.
376 @param i: Which desktop to get
378 @return: Desktop reference
379 @rtype: Accessibility.Desktop
380 @raise LookupError: When the i-th desktop cannot be retrieved
383 return self.reg.getDesktop(i)
387 def registerEventListener(self, client, *names):
389 Registers a new client callback for the given event names. Supports
390 registration for all subevents if only partial event name is specified.
391 Do not include a trailing colon.
393 For example, 'object' will register for all object events,
394 'object:property-change' will register for all property change events,
395 and 'object:property-change:accessible-parent' will register only for the
396 parent property change event.
398 Registered clients will not be automatically removed when the client dies.
399 To ensure the client is properly garbage collected, call
400 L{deregisterEventListener}.
402 @param client: Callable to be invoked when the event occurs
403 @type client: callable
404 @param names: List of full or partial event names
405 @type names: list of string
408 # store the callback for each specific event name
409 self._registerClients(client, name)
411 def deregisterEventListener(self, client, *names):
413 Unregisters an existing client callback for the given event names. Supports
414 unregistration for all subevents if only partial event name is specified.
415 Do not include a trailing colon.
417 This method must be called to ensure a client registered by
418 L{registerEventListener} is properly garbage collected.
420 @param client: Client callback to remove
421 @type client: callable
422 @param names: List of full or partial event names
423 @type names: list of string
424 @return: Were event names specified for which the given client was not
430 # remove the callback for each specific event name
431 missed |= self._unregisterClients(client, name)
434 def registerKeystrokeListener(self, client, key_set=[], mask=0,
435 kind=(constants.KEY_PRESSED_EVENT,
436 constants.KEY_RELEASED_EVENT),
437 synchronous=True, preemptive=True,
440 Registers a listener for key stroke events.
442 @param client: Callable to be invoked when the event occurs
443 @type client: callable
444 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
445 to indicate all keys.
446 @type key_set: list of integer
447 @param mask: When the mask is None, the codes in the key_set will be
448 monitored only when no modifier is held. When the mask is an
449 integer, keys in the key_set will be monitored only when the modifiers in
450 the mask are held. When the mask is an iterable over more than one
451 integer, keys in the key_set will be monitored when any of the modifier
452 combinations in the set are held.
453 @type mask: integer, iterable, None
454 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
457 @param synchronous: Should the callback notification be synchronous, giving
458 the client the chance to consume the event?
459 @type synchronous: boolean
460 @param preemptive: Should the callback be allowed to preempt / consume the
462 @type preemptive: boolean
463 @param global_: Should callback occur even if an application not supporting
464 AT-SPI is in the foreground? (requires xevie)
465 @type global_: boolean
468 # see if we already have an observer for this client
469 ob = self.clients[client]
471 # create a new device observer for this client
472 ob = _DeviceObserver(self, synchronous, preemptive, global_)
473 # store the observer to client mapping, and the inverse
474 self.clients[ob] = client
475 self.clients[client] = ob
477 # None means all modifier combinations
478 mask = utils.allModifiers()
479 # register for new keystrokes on the observer
480 ob.register(self.dev, key_set, mask, kind)
482 def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
483 kind=(constants.KEY_PRESSED_EVENT,
484 constants.KEY_RELEASED_EVENT)):
486 Deregisters a listener for key stroke events.
488 @param client: Callable to be invoked when the event occurs
489 @type client: callable
490 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
491 to indicate all keys.
492 @type key_set: list of integer
493 @param mask: When the mask is None, the codes in the key_set will be
494 monitored only when no modifier is held. When the mask is an
495 integer, keys in the key_set will be monitored only when the modifiers in
496 the mask are held. When the mask is an iterable over more than one
497 integer, keys in the key_set will be monitored when any of the modifier
498 combinations in the set are held.
499 @type mask: integer, iterable, None
500 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
503 @raise KeyError: When the client isn't already registered for events
505 # see if we already have an observer for this client
506 ob = self.clients[client]
508 # None means all modifier combinations
509 mask = utils.allModifiers()
510 # register for new keystrokes on the observer
511 ob.unregister(self.dev, key_set, mask, kind)
513 def generateKeyboardEvent(self, keycode, keysym, kind):
515 Generates a keyboard event. One of the keycode or the keysym parameters
516 should be specified and the other should be None. The kind parameter is
517 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
518 KEY_SYM, or KEY_STRING.
520 @param keycode: Hardware keycode or None
521 @type keycode: integer
522 @param keysym: Symbolic key string or None
524 @param kind: Kind of event to synthesize
528 self.dev.generateKeyboardEvent(keycode, '', kind)
530 self.dev.generateKeyboardEvent(None, keysym, kind)
532 def generateMouseEvent(self, x, y, name):
534 Generates a mouse event at the given absolute x and y coordinate. The kind
535 of event generated is specified by the name. For example, MOUSE_B1P
536 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
539 @param x: Horizontal coordinate, usually left-hand oriented
541 @param y: Vertical coordinate, usually left-hand oriented
543 @param name: Name of the event to generate
546 self.dev.generateMouseEvent(x, y, name)
548 def handleDeviceEvent(self, event, ob):
550 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
551 in the order they were registered for the given AT-SPI event. If any
552 client returns True, callbacks cease for the event for clients of this registry
553 instance. Clients of other registry instances and clients in other processes may
554 be affected depending on the values of synchronous and preemptive used when invoking
555 L{registerKeystrokeListener}.
557 @note: Asynchronous dispatch of device events is not supported.
559 @param event: AT-SPI device event
560 @type event: L{event.DeviceEvent}
561 @param ob: Observer that received the event
562 @type ob: L{_DeviceObserver}
564 @return: Should the event be consumed (True) or allowed to pass on to other
565 AT-SPI observers (False)?
569 # try to get the client registered for this event type
570 client = self.clients[ob]
572 # client may have unregistered recently, ignore event
574 # make the call to the client
576 return client(event) or event.consume
578 # print the exception, but don't let it stop notification
579 traceback.print_exc()
581 def handleEvent(self, event):
583 Handles an AT-SPI event by either queuing it for later dispatch when the
584 L{Registry.async} flag is set, or dispatching it immediately.
586 @param event: AT-SPI event
587 @type event: L{event.Event}
591 self.queue.put_nowait(event)
593 # dispatch immediately
594 self._dispatchEvent(event)
596 def _dispatchEvent(self, event):
598 Dispatches L{event.Event}s to registered clients. Clients are called in
599 the order they were registered for the given AT-SPI event. If any client
600 returns True, callbacks cease for the event for clients of this registry
601 instance. Clients of other registry instances and clients in other processes
604 @param event: AT-SPI event
605 @type event: L{event.Event}
609 # try to get the client registered for this event type
610 clients = self.clients[et.name]
613 # we may not have registered for the complete subtree of events
614 # if our tree does not list all of a certain type (e.g.
615 # object:state-changed:*); try again with klass and major only
616 if et.detail is not None:
617 # Strip the 'detail' field.
618 clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
619 elif et.minor is not None:
620 # The event could possibly be object:state-changed:*.
621 clients = self.clients['%s:%s' % (et.klass, et.major)]
623 # client may have unregistered recently, ignore event
625 # make the call to each client
627 for client in clients:
629 consume = client(event) or False
631 # print the exception, but don't let it stop notification
632 traceback.print_exc()
633 if consume or event.consume:
634 # don't allow further processing if a client returns True
637 def flushEvents(self):
639 Flushes the event queue by destroying it and recreating it.
641 self.queue = Queue.Queue()
643 def pumpQueuedEvents(self, num=-1):
645 Provides asynch processing of events in the queue by executeing them with
646 _dispatchEvent() (as is done immediately when synch processing).
647 This method would normally be called from a main loop or idle function.
649 @param num: Number of events to pump. If number is negative it pumps
650 the entire queue. Default is -1.
652 @return: True if queue is not empty after events were pumped.
656 # Dequeue as many events as currently in the queue.
657 num = self.queue.qsize()
658 for i in xrange(num):
660 # get next waiting event
661 event = self.queue.get_nowait()
664 self._dispatchEvent(event)
666 return not self.queue.empty()
668 def _registerClients(self, client, name):
670 Internal method that recursively associates a client with AT-SPI event
671 names. Allows a client to incompletely specify an event name in order to
672 register for subevents without specifying their full names manually.
674 @param client: Client callback to receive event notifications
675 @type client: callable
676 @param name: Partial or full event name
680 # look for an event name in our event tree dictionary
681 events = constants.EVENT_TREE[name]
683 # if the event name doesn't exist, it's a leaf event meaning there are
684 # no subtypes for that event
685 # add this client to the list of clients already in the dictionary
686 # using the event name as the key; if there are no clients yet for this
687 # event, insert an empty list into the dictionary before appending
689 et = event.EventType(name)
690 clients = self.clients.setdefault(et.name, [])
692 # if this succeeds, this client is already registered for the given
693 # event type, so ignore the request
694 clients.index(client)
696 # else register the client
697 clients.append(client)
698 self._registerObserver(name)
700 # if the event name does exist in the tree, there are subevents for
701 # this event; loop through them calling this method again to get to
704 self._registerClients(client, e)
706 def _unregisterClients(self, client, name):
708 Internal method that recursively unassociates a client with AT-SPI event
709 names. Allows a client to incompletely specify an event name in order to
710 unregister for subevents without specifying their full names manually.
712 @param client: Client callback to receive event notifications
713 @type client: callable
714 @param name: Partial or full event name
719 # look for an event name in our event tree dictionary
720 events = constants.EVENT_TREE[name]
723 # if the event name doesn't exist, it's a leaf event meaning there are
724 # no subtypes for that event
725 # get the list of registered clients and try to remove the one provided
726 et = event.EventType(name)
727 clients = self.clients[et.name]
728 clients.remove(client)
729 self._unregisterObserver(name)
730 except (ValueError, KeyError):
731 # ignore any exceptions indicating the client is not registered
734 # if the event name does exist in the tree, there are subevents for this
735 # event; loop through them calling this method again to get to the leaf
738 missed |= self._unregisterClients(client, e)
741 def _registerObserver(self, name):
743 Creates a new L{_Observer} to watch for events of the given type or
744 returns the existing observer if one is already registered. One
745 L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
746 any event name not found in the tree.
748 @param name: Raw name of the event to observe
750 @return: L{_Observer} object that is monitoring the event
753 et = event.EventType(name)
755 # see if an observer already exists for this event
756 ob = self.observers[et.name]
758 # build a new observer if one does not exist
759 ob = _EventObserver(self)
760 # we have to register for the raw name because it may be different from
761 # the parsed name determined by EventType (e.g. trailing ':' might be
763 ob.register(self.reg, name)
764 self.observers[et.name] = ob
765 # increase our client ref count so we know someone new is watching for the
770 def _unregisterObserver(self, name):
772 Destroys an existing L{_Observer} for the given event type only if no
773 clients are registered for the events it is monitoring.
775 @param name: Name of the event to observe
777 @raise KeyError: When an observer for the given event is not regist
779 et = event.EventType(name)
780 # see if an observer already exists for this event
781 ob = self.observers[et.name]
783 if ob.getClientRefCount() == 0:
784 ob.unregister(self.reg, name)
785 del self.observers[et.name]