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'
37 class _Observer(object):
39 Parent class for all event observers. Dispatches all received events to the
40 L{Registry} that created this L{_Observer}. Provides basic reference counting
41 functionality needed by L{Registry} to determine when an L{_Observer} can be
42 released for garbage collection.
44 The reference counting provided by this class is independent of the reference
45 counting used by CORBA. Keeping the counts separate makes it easier for the
46 L{Registry} to detect when an L{_Observer} can be freed in the
47 L{Registry._unregisterObserver} method.
49 @ivar registry: Reference to the L{Registry} that created this L{_Observer}
50 @type registry: weakref.proxy to L{Registry}
51 @ivar ref_count: Reference count on this L{_Observer}
52 @type ref_count: integer
54 def __init__(self, registry):
56 Stores a reference to the creating L{Registry}. Intializes the reference
57 count on this object to zero.
59 @param registry: The L{Registry} that created this observer
60 @type registry: weakref.proxy to L{Registry}
62 self.registry = weakref.proxy(registry)
67 Increments the Python reference count on this L{_Observer} by one. This
68 method is called when a new client is registered in L{Registry} to receive
69 notification of an event type monitored by this L{_Observer}.
73 def clientUnref(self):
75 Decrements the pyatspi reference count on this L{_Observer} by one. This
76 method is called when a client is unregistered in L{Registry} to stop
77 receiving notifications of an event type monitored by this L{_Observer}.
81 def getClientRefCount(self):
83 @return: Current Python reference count on this L{_Observer}
89 """Required by CORBA. Does nothing."""
93 """Required by CORBA. Does nothing."""
96 class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
98 Observes keyboard press and release events.
100 @ivar registry: The L{Registry} that created this observer
101 @type registry: L{Registry}
102 @ivar key_set: Set of keys to monitor
103 @type key_set: list of integer
104 @ivar mask: Watch for key events while these modifiers are held
106 @ivar kind: Kind of events to monitor
108 @ivar mode: Keyboard event mode
109 @type mode: Accessibility.EventListenerMode
111 def __init__(self, registry, synchronous, preemptive, global_):
113 Creates a mode object that defines when key events will be received from
114 the system. Stores all other information for later registration.
116 @param registry: The L{Registry} that created this observer
117 @type registry: L{Registry}
118 @param synchronous: Handle the key event synchronously?
119 @type synchronous: boolean
120 @param preemptive: Allow event to be consumed?
121 @type preemptive: boolean
122 @param global_: Watch for events on inaccessible applications too?
123 @type global_: boolean
125 _Observer.__init__(self, registry)
126 self.mode = Accessibility.EventListenerMode()
127 self.mode.preemptive = preemptive
128 self.mode.synchronous = synchronous
129 self.mode._global = global_
131 def register(self, dc, key_set, mask, kind):
133 Starts keyboard event monitoring.
135 @param dc: Reference to a device controller
136 @type dc: Accessibility.DeviceEventController
137 @param key_set: Set of keys to monitor
138 @type key_set: list of integer
139 @param mask: Integer modifier mask or an iterable over multiple masks to
141 @type mask: integer, iterable, or None
142 @param kind: Kind of events to monitor
146 # check if the mask is iterable
149 # register a single integer if not
150 dc.registerKeystrokeListener(self._this(), key_set, mask, kind,
154 dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
156 def unregister(self, dc, key_set, mask, kind):
158 Stops keyboard event monitoring.
160 @param dc: Reference to a device controller
161 @type dc: Accessibility.DeviceEventController
162 @param key_set: Set of keys to monitor
163 @type key_set: list of integer
164 @param mask: Integer modifier mask or an iterable over multiple masks to
166 @type mask: integer, iterable, or None
167 @param kind: Kind of events to monitor
171 # check if the mask is iterable
174 # unregister a single integer if not
175 dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
178 dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
180 def queryInterface(self, repo_id):
182 Reports that this class only implements the AT-SPI DeviceEventListener
183 interface. Required by AT-SPI.
185 @param repo_id: Request for an interface
186 @type repo_id: string
187 @return: The underlying CORBA object for the device event listener
188 @rtype: Accessibility.EventListener
190 if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
195 def notifyEvent(self, ev):
197 Notifies the L{Registry} that an event has occurred. Wraps the raw event
198 object in our L{Event} class to support automatic ref and unref calls. An
199 observer can return True to indicate this event should not be allowed to pass
200 to other AT-SPI observers or the underlying application.
202 @param ev: Keyboard event
203 @type ev: Accessibility.DeviceEvent
204 @return: Should the event be consumed (True) or allowed to pass on to other
205 AT-SPI observers (False)?
208 # wrap the device event
209 ev = event.DeviceEvent(ev)
210 return self.registry.handleDeviceEvent(ev, self)
212 class _EventObserver(_Observer, Accessibility__POA.EventListener):
214 Observes all non-keyboard AT-SPI events. Can be reused across event types.
216 def register(self, reg, name):
218 Starts monitoring for the given event.
220 @param name: Name of the event to start monitoring
222 @param reg: Reference to the raw registry object
223 @type reg: Accessibility.Registry
225 reg.registerGlobalEventListener(self._this(), name)
227 def unregister(self, reg, name):
229 Stops monitoring for the given event.
231 @param name: Name of the event to stop monitoring
233 @param reg: Reference to the raw registry object
234 @type reg: Accessibility.Registry
236 reg.deregisterGlobalEventListener(self._this(), name)
238 def queryInterface(self, repo_id):
240 Reports that this class only implements the AT-SPI DeviceEventListener
241 interface. Required by AT-SPI.
243 @param repo_id: Request for an interface
244 @type repo_id: string
245 @return: The underlying CORBA object for the device event listener
246 @rtype: Accessibility.EventListener
248 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
253 def notifyEvent(self, ev):
255 Notifies the L{Registry} that an event has occurred. Wraps the raw event
256 object in our L{Event} class to support automatic ref and unref calls.
257 Aborts on any exception indicating the event could not be wrapped.
259 @param ev: AT-SPI event signal (anything but keyboard)
260 @type ev: Accessibility.Event
262 # wrap raw event so ref counts are correct before queueing
264 self.registry.handleEvent(ev)
266 class DeviceEvent(object):
268 Wraps an AT-SPI device event with a more Pythonic interface. Primarily adds
269 a consume attribute which can be used to cease propagation of a device event.
271 @ivar consume: Should this event be consumed and not allowed to pass on to
272 observers further down the dispatch chain in this process or possibly
274 @type consume: boolean
275 @ivar type: Kind of event, KEY_PRESSED_EVENT or KEY_RELEASED_EVENT
276 @type type: Accessibility.EventType
277 @ivar id: Serial identifier for this key event
279 @ivar hw_code: Hardware scan code for the key
280 @type hw_code: integer
281 @ivar modifiers: Modifiers held at the time of the key event
282 @type modifiers: integer
283 @ivar timestamp: Time at which the event occurred relative to some platform
284 dependent starting point (e.g. XWindows start time)
285 @type timestamp: integer
286 @ivar event_string: String describing the key pressed (e.g. keysym)
287 @type event_string: string
288 @ivar is_text: Is the event representative of text to be inserted (True), or
289 of a control key (False)?
290 @type is_text: boolean
292 def __init__(self, event):
294 Attaches event data to this object.
296 @param event: Event object
297 @type event: Accessibility.DeviceEvent
300 self.type = event.type
302 self.hw_code = event.hw_code
303 self.modifiers = event.modifiers
304 self.timestamp = event.timestamp
305 self.event_string = event.event_string
306 self.is_text = event.is_text
310 Builds a human readable representation of the event.
312 @return: Event description
315 if self.type == constants.KEY_PRESSED_EVENT:
317 elif self.type == constants.KEY_RELEASED_EVENT:
326 \tis_text: %s""" % (kind, self.hw_code, self.event_string, self.modifiers,
327 self.id, self.timestamp, self.is_text)
331 Wraps an AT-SPI event with a more Pythonic interface managing exceptions,
332 the differences in any_data across versions, and the reference counting of
333 accessibles provided with the event.
335 @note: All unmarked attributes of this class should be considered public
336 readable and writable as the class is acting as a record object.
338 @ivar consume: Should this event be consumed and not allowed to pass on to
339 observers further down the dispatch chain in this process?
340 @type consume: boolean
341 @ivar type: The type of the AT-SPI event
342 @type type: L{EventType}
343 @ivar detail1: First AT-SPI event parameter
344 @type detail1: integer
345 @ivar detail2: Second AT-SPI event parameter
346 @type detail2: integer
347 @ivar any_data: Extra AT-SPI data payload
348 @type any_data: object
349 @ivar host_application: Application owning the event source
350 @type host_application: Accessibility.Application
351 @ivar source_name: Name of the event source at the time of event dispatch
352 @type source_name: string
353 @ivar source_role: Role of the event source at the time of event dispatch
354 @type source_role: Accessibility.Role
355 @ivar source: Source of the event
356 @type source: Accessibility.Accessible
358 def __init__(self, event):
360 Extracts information from the provided event. If the event is a "normal"
361 event, pulls the detail1, detail2, any_data, and source values out of the
362 given object and stores it in this object. If the event is a device event,
363 key ID is stored in detail1, scan code is stored in detail2, key name,
364 key modifiers (e.g. ALT, CTRL, etc.), is text flag, and timestamp are
365 stored as a 4-tuple in any_data, and source is None (since key events are
368 @param event: Event from an AT-SPI callback
369 @type event: Accessibility.Event or Accessibility.DeviceEvent
371 # always start out assuming no consume
373 self.type = EventType(event.type)
374 self.detail1 = event.detail1
375 self.detail2 = event.detail2
376 # store the event source and increase the reference count since event
377 # sources are borrowed references; the AccessibleMixin automatically
378 # decrements it later
381 except AttributeError:
383 self.source = event.source
385 # process any_data in a at-spi version independent manner
386 details = event.any_data.value()
388 # see if we have a "new" any_data object which is an EventDetails struct
389 self.any_data = details.any_data.value()
391 # any kind of error means we have an "old" any_data object and None of
392 # the extra data so set them to None
393 self.any_data = details
394 self.host_application = None
395 self.source_name = None
396 self.source_role = None
398 # the rest of the data should be here, so retrieve it
399 self.host_application = details.host_application
400 self.source_name = details.source_name
401 self.source_role = details.source_role
403 # if we received an accessible, be sure to increment the ref count
405 except AttributeError:
408 # if we received a host application, be sure to increment the ref count
409 self.host_application.ref()
410 except AttributeError:
415 Builds a human readable representation of the event including event type,
416 parameters, and source info.
418 @return: Event description
421 return '%s(%s, %s, %s)\n\tsource: %s\n\thost_application: %s' % \
422 (self.type, self.detail1, self.detail2, self.any_data,
423 self.source, self.host_application)
425 class EventType(str):
427 Wraps the AT-SPI event type string so its components can be accessed
428 individually as klass (can't use the keyword class), major, minor, and detail
429 (klass:major:minor:detail).
431 @note: All attributes of an instance of this class should be considered
432 public readable as it is acting a a struct.
433 @ivar klass: Most general event type identifier (object, window, mouse, etc.)
435 @ivar major: Second level event type description
437 @ivar minor: Third level event type description
439 @ivar detail: Lowest level event type description
441 @ivar name: Full, unparsed event name as received from AT-SPI
443 @cvar format: Names of the event string components
444 @type format: 4-tuple of string
446 format = ('klass', 'major', 'minor', 'detail')
448 def __init__(self, name):
450 Parses the full AT-SPI event name into its components
451 (klass:major:minor:detail). If the provided event name is an integer
452 instead of a string, then the event is really a device event.
454 @param name: Full AT-SPI event name
456 @raise AttributeError: When the given event name is not a valid string
458 # get rid of any leading and trailing ':' separators
459 self.value = name.strip(':')
460 self.name = self.value # Backward compatability
466 # split type according to delimiters
467 split = self.value.split(':', 3)
468 # loop over all the components
469 for i in xrange(len(split)):
470 # store values of attributes in this object
471 setattr(self, self.format[i], split[i])
473 class Registry(object):
475 Wraps the Accessibility.Registry to provide more Pythonic registration for
478 This object should be treated as a singleton, but such treatment is not
479 enforced. You can construct another instance of this object and give it a
480 reference to the Accessibility.Registry singleton. Doing so is harmless and
483 @ivar async: Should event dispatch to local listeners be decoupled from event
484 receiving from the registry?
486 @ivar reg: Reference to the real, wrapped registry object
487 @type reg: Accessibility.Registry
488 @ivar dev: Reference to the device controller
489 @type dev: Accessibility.DeviceEventController
490 @ivar queue: Queue of events awaiting local dispatch
491 @type queue: Queue.Queue
492 @ivar clients: Map of event names to client listeners
493 @type clients: dictionary
494 @ivar observers: Map of event names to AT-SPI L{_Observer} objects
495 @type observers: dictionary
498 _REGISTRY_NAME = 'org.freedesktop.atspi.Registry'
500 def __init__(self, app_name=None):
502 Stores a reference to the AT-SPI registry. Gets and stores a reference
503 to the DeviceEventController.
505 @param reg: Reference to the AT-SPI registry daemon
506 @type reg: Accessibility.Registry
508 self._bus = dbus.SessionBus()
510 self._app_name = app_name
511 self._cache = TestApplicationCache(self._bus, app_name)
515 #self.dev = self.reg.getDeviceEventController()
516 #self.queue = Queue.Queue()
522 @return: This instance of the registry
527 def start(self, async=False, gil=True):
529 Enter the main loop to start receiving and dispatching events.
531 @param async: Should event dispatch be asynchronous (decoupled) from
532 event receiving from the AT-SPI registry?
534 @param gil: Add an idle callback which releases the Python GIL for a few
535 milliseconds to allow other threads to run? Necessary if other threads
536 will be used in this process.
537 Note - No Longer used.
540 self._loop = gobject.MainLoop()
543 def stop(self, *args):
544 """Quits the main loop."""
548 def getDesktopCount(self):
550 Gets the number of available desktops.
552 @return: Number of desktops
557 def getDesktop(self, i):
559 Gets a reference to the i-th desktop.
561 @param i: Which desktop to get
563 @return: Desktop reference
564 @rtype: Accessibility.Desktop
566 return Desktop(self._cache)
568 def registerEventListener(self, client, *names):
570 Registers a new client callback for the given event names. Supports
571 registration for all subevents if only partial event name is specified.
572 Do not include a trailing colon.
574 For example, 'object' will register for all object events,
575 'object:property-change' will register for all property change events,
576 and 'object:property-change:accessible-parent' will register only for the
577 parent property change event.
579 Registered clients will not be automatically removed when the client dies.
580 To ensure the client is properly garbage collected, call
581 L{deregisterEventListener}.
583 @param client: Callable to be invoked when the event occurs
584 @type client: callable
585 @param names: List of full or partial event names
586 @type names: list of string
589 # store the callback for each specific event name
590 self._registerClients(client, name)
592 def deregisterEventListener(self, client, *names):
594 Unregisters an existing client callback for the given event names. Supports
595 unregistration for all subevents if only partial event name is specified.
596 Do not include a trailing colon.
598 This method must be called to ensure a client registered by
599 L{registerEventListener} is properly garbage collected.
601 @param client: Client callback to remove
602 @type client: callable
603 @param names: List of full or partial event names
604 @type names: list of string
605 @return: Were event names specified for which the given client was not
611 # remove the callback for each specific event name
612 missed |= self._unregisterClients(client, name)
615 def registerKeystrokeListener(self, client, key_set=[], mask=0,
616 kind=(constants.KEY_PRESSED_EVENT,
617 constants.KEY_RELEASED_EVENT),
618 synchronous=True, preemptive=True,
621 Registers a listener for key stroke events.
623 @param client: Callable to be invoked when the event occurs
624 @type client: callable
625 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
626 to indicate all keys.
627 @type key_set: list of integer
628 @param mask: When the mask is None, the codes in the key_set will be
629 monitored only when no modifier is held. When the mask is an
630 integer, keys in the key_set will be monitored only when the modifiers in
631 the mask are held. When the mask is an iterable over more than one
632 integer, keys in the key_set will be monitored when any of the modifier
633 combinations in the set are held.
634 @type mask: integer, iterable, None
635 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
638 @param synchronous: Should the callback notification be synchronous, giving
639 the client the chance to consume the event?
640 @type synchronous: boolean
641 @param preemptive: Should the callback be allowed to preempt / consume the
643 @type preemptive: boolean
644 @param global_: Should callback occur even if an application not supporting
645 AT-SPI is in the foreground? (requires xevie)
646 @type global_: boolean
649 # see if we already have an observer for this client
650 ob = self.clients[client]
652 # create a new device observer for this client
653 ob = _DeviceObserver(self, synchronous, preemptive, global_)
654 # store the observer to client mapping, and the inverse
655 self.clients[ob] = client
656 self.clients[client] = ob
658 # None means all modifier combinations
659 mask = utils.allModifiers()
660 # register for new keystrokes on the observer
661 ob.register(self.dev, key_set, mask, kind)
663 def deregisterKeystrokeListener(self, client, key_set=[], mask=0,
664 kind=(constants.KEY_PRESSED_EVENT,
665 constants.KEY_RELEASED_EVENT)):
667 Deregisters a listener for key stroke events.
669 @param client: Callable to be invoked when the event occurs
670 @type client: callable
671 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
672 to indicate all keys.
673 @type key_set: list of integer
674 @param mask: When the mask is None, the codes in the key_set will be
675 monitored only when no modifier is held. When the mask is an
676 integer, keys in the key_set will be monitored only when the modifiers in
677 the mask are held. When the mask is an iterable over more than one
678 integer, keys in the key_set will be monitored when any of the modifier
679 combinations in the set are held.
680 @type mask: integer, iterable, None
681 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
684 @raise KeyError: When the client isn't already registered for events
686 # see if we already have an observer for this client
687 ob = self.clients[client]
689 # None means all modifier combinations
690 mask = utils.allModifiers()
691 # register for new keystrokes on the observer
692 ob.unregister(self.dev, key_set, mask, kind)
694 def generateKeyboardEvent(self, keycode, keysym, kind):
696 Generates a keyboard event. One of the keycode or the keysym parameters
697 should be specified and the other should be None. The kind parameter is
698 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
699 KEY_SYM, or KEY_STRING.
701 @param keycode: Hardware keycode or None
702 @type keycode: integer
703 @param keysym: Symbolic key string or None
705 @param kind: Kind of event to synthesize
709 self.dev.generateKeyboardEvent(keycode, '', kind)
711 self.dev.generateKeyboardEvent(None, keysym, kind)
713 def generateMouseEvent(self, x, y, name):
715 Generates a mouse event at the given absolute x and y coordinate. The kind
716 of event generated is specified by the name. For example, MOUSE_B1P
717 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
720 @param x: Horizontal coordinate, usually left-hand oriented
722 @param y: Vertical coordinate, usually left-hand oriented
724 @param name: Name of the event to generate
727 self.dev.generateMouseEvent(x, y, name)
729 def handleDeviceEvent(self, event, ob):
731 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
732 in the order they were registered for the given AT-SPI event. If any
733 client returns True, callbacks cease for the event for clients of this registry
734 instance. Clients of other registry instances and clients in other processes may
735 be affected depending on the values of synchronous and preemptive used when invoking
736 L{registerKeystrokeListener}.
738 @note: Asynchronous dispatch of device events is not supported.
740 @param event: AT-SPI device event
741 @type event: L{event.DeviceEvent}
742 @param ob: Observer that received the event
743 @type ob: L{_DeviceObserver}
745 @return: Should the event be consumed (True) or allowed to pass on to other
746 AT-SPI observers (False)?
750 # try to get the client registered for this event type
751 client = self.clients[ob]
753 # client may have unregistered recently, ignore event
755 # make the call to the client
757 return client(event) or event.consume
759 # print the exception, but don't let it stop notification
760 traceback.print_exc()
762 def handleEvent(self, event):
764 Handles an AT-SPI event by either queuing it for later dispatch when the
765 L{Registry.async} flag is set, or dispatching it immediately.
767 @param event: AT-SPI event
768 @type event: L{event.Event}
772 self.queue.put_nowait(event)
774 # dispatch immediately
775 self._dispatchEvent(event)
777 def _dispatchEvent(self, event):
779 Dispatches L{event.Event}s to registered clients. Clients are called in
780 the order they were registered for the given AT-SPI event. If any client
781 returns True, callbacks cease for the event for clients of this registry
782 instance. Clients of other registry instances and clients in other processes
785 @param event: AT-SPI event
786 @type event: L{event.Event}
790 # try to get the client registered for this event type
791 clients = self.clients[et.name]
794 # we may not have registered for the complete subtree of events
795 # if our tree does not list all of a certain type (e.g.
796 # object:state-changed:*); try again with klass and major only
797 if et.detail is not None:
798 # Strip the 'detail' field.
799 clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
800 elif et.minor is not None:
801 # The event could possibly be object:state-changed:*.
802 clients = self.clients['%s:%s' % (et.klass, et.major)]
804 # client may have unregistered recently, ignore event
806 # make the call to each client
808 for client in clients:
810 consume = client(event) or False
812 # print the exception, but don't let it stop notification
813 traceback.print_exc()
814 if consume or event.consume:
815 # don't allow further processing if a client returns True
818 def flushEvents(self):
820 Flushes the event queue by destroying it and recreating it.
822 self.queue = Queue.Queue()
824 def pumpQueuedEvents(self, num=-1):
826 Provides asynch processing of events in the queue by executeing them with
827 _dispatchEvent() (as is done immediately when synch processing).
828 This method would normally be called from a main loop or idle function.
830 @param num: Number of events to pump. If number is negative it pumps
831 the entire queue. Default is -1.
833 @return: True if queue is not empty after events were pumped.
837 # Dequeue as many events as currently in the queue.
838 num = self.queue.qsize()
839 for i in xrange(num):
841 # get next waiting event
842 event = self.queue.get_nowait()
845 self._dispatchEvent(event)
847 return not self.queue.empty()
849 def _registerClients(self, client, name):
851 Internal method that recursively associates a client with AT-SPI event
852 names. Allows a client to incompletely specify an event name in order to
853 register for subevents without specifying their full names manually.
855 @param client: Client callback to receive event notifications
856 @type client: callable
857 @param name: Partial or full event name
861 # look for an event name in our event tree dictionary
862 events = constants.EVENT_TREE[name]
864 # if the event name doesn't exist, it's a leaf event meaning there are
865 # no subtypes for that event
866 # add this client to the list of clients already in the dictionary
867 # using the event name as the key; if there are no clients yet for this
868 # event, insert an empty list into the dictionary before appending
870 et = event.EventType(name)
871 clients = self.clients.setdefault(et.name, [])
873 # if this succeeds, this client is already registered for the given
874 # event type, so ignore the request
875 clients.index(client)
877 # else register the client
878 clients.append(client)
879 self._registerObserver(name)
881 # if the event name does exist in the tree, there are subevents for
882 # this event; loop through them calling this method again to get to
885 self._registerClients(client, e)
887 def _unregisterClients(self, client, name):
889 Internal method that recursively unassociates a client with AT-SPI event
890 names. Allows a client to incompletely specify an event name in order to
891 unregister for subevents without specifying their full names manually.
893 @param client: Client callback to receive event notifications
894 @type client: callable
895 @param name: Partial or full event name
900 # look for an event name in our event tree dictionary
901 events = constants.EVENT_TREE[name]
904 # if the event name doesn't exist, it's a leaf event meaning there are
905 # no subtypes for that event
906 # get the list of registered clients and try to remove the one provided
907 et = event.EventType(name)
908 clients = self.clients[et.name]
909 clients.remove(client)
910 self._unregisterObserver(name)
911 except (ValueError, KeyError):
912 # ignore any exceptions indicating the client is not registered
915 # if the event name does exist in the tree, there are subevents for this
916 # event; loop through them calling this method again to get to the leaf
919 missed |= self._unregisterClients(client, e)
922 def _registerObserver(self, name):
924 Creates a new L{_Observer} to watch for events of the given type or
925 returns the existing observer if one is already registered. One
926 L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
927 any event name not found in the tree.
929 @param name: Raw name of the event to observe
931 @return: L{_Observer} object that is monitoring the event
934 et = event.EventType(name)
936 # see if an observer already exists for this event
937 ob = self.observers[et.name]
939 # build a new observer if one does not exist
940 ob = _EventObserver(self)
941 # we have to register for the raw name because it may be different from
942 # the parsed name determined by EventType (e.g. trailing ':' might be
944 ob.register(self.reg, name)
945 self.observers[et.name] = ob
946 # increase our client ref count so we know someone new is watching for the
951 def _unregisterObserver(self, name):
953 Destroys an existing L{_Observer} for the given event type only if no
954 clients are registered for the events it is monitoring.
956 @param name: Name of the event to observe
958 @raise KeyError: When an observer for the given event is not regist
960 et = event.EventType(name)
961 # see if an observer already exists for this event
962 ob = self.observers[et.name]
964 if ob.getClientRefCount() == 0:
965 ob.unregister(self.reg, name)
966 del self.observers[et.name]