ae6fb16d6a811cf3a39e688334a30239a6ba9dd9
[platform/core/uifw/at-spi2-atk.git] / pyatspi / registry.py
1 #Copyright (C) 2008 Codethink Ltd
2 #copyright: Copyright (c) 2005, 2007 IBM Corporation
3
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.
7
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.
15
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}
19
20 #authors: Peter Parente, Mark Doffman
21
22 import signal
23 import time
24 import weakref
25 import Queue
26 import traceback
27 import gobject
28 import utils
29 import constants
30 import event
31
32 ATSPI_DEVICE_EVENT_CONTROLLER = 'org.freedesktop.atspi.DeviceEventController'
33 ATSPI_DEVICE_EVENT_LISTENER = 'org.freedesktop.atspi.DeviceEventListener'
34
35 class _Observer(object):
36         """
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. 
41         
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.
46         
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
51         """
52         def __init__(self, registry):
53                 """
54                 Stores a reference to the creating L{Registry}. Intializes the reference
55                 count on this object to zero.
56                 
57                 @param registry: The L{Registry} that created this observer
58                 @type registry: weakref.proxy to L{Registry}
59                 """
60                 self.registry = weakref.proxy(registry)
61                 self.ref_count = 0
62
63         def clientRef(self):
64                 """
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}.
68                 """
69                 self.ref_count += 1
70                 
71         def clientUnref(self):
72                 """             
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}.
76                 """
77                 self.ref_count -= 1
78                 
79         def getClientRefCount(self):
80                 """
81                 @return: Current Python reference count on this L{_Observer}
82                 @rtype: integer
83                 """
84                 return self.ref_count
85         
86         def ref(self): 
87                 """Required by CORBA. Does nothing."""
88                 pass
89                 
90         def unref(self): 
91                 """Required by CORBA. Does nothing."""
92                 pass
93
94 class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
95         """
96         Observes keyboard press and release events.
97         
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
103         @type mask: integer
104         @ivar kind: Kind of events to monitor
105         @type kind: integer
106         @ivar mode: Keyboard event mode
107         @type mode: Accessibility.EventListenerMode
108         """
109         def __init__(self, registry, synchronous, preemptive, global_):
110                 """
111                 Creates a mode object that defines when key events will be received from 
112                 the system. Stores all other information for later registration.
113                 
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
122                 """
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_             
128          
129         def register(self, dc, key_set, mask, kind):
130                 """
131                 Starts keyboard event monitoring.
132                 
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
138                         unapply all at once
139                 @type mask: integer, iterable, or None
140                 @param kind: Kind of events to monitor
141                 @type kind: integer
142                 """
143                 try:
144                         # check if the mask is iterable
145                         iter(mask)
146                 except TypeError:
147                         # register a single integer if not
148                         dc.registerKeystrokeListener(self._this(), key_set, mask, kind, 
149                                                                                                                                          self.mode)
150                 else:
151                         for m in mask:
152                                 dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
153
154         def unregister(self, dc, key_set, mask, kind):
155                 """
156                 Stops keyboard event monitoring.
157                 
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
163                         unapply all at once
164                 @type mask: integer, iterable, or None
165                 @param kind: Kind of events to monitor
166                 @type kind: integer
167                 """
168                 try:
169                         # check if the mask is iterable
170                         iter(mask)
171                 except TypeError:
172                         # unregister a single integer if not
173                         dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
174                 else:
175                         for m in mask:
176                                 dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
177                         
178         def queryInterface(self, repo_id):
179                 """
180                 Reports that this class only implements the AT-SPI DeviceEventListener 
181                 interface. Required by AT-SPI.
182                 
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
187                 """
188                 if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
189                         return self._this()
190                 else:
191                         return None
192
193         def notifyEvent(self, ev):
194                 """
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.
199                 
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)?
204                 @rtype: boolean
205                 """
206                 # wrap the device event
207                 ev = event.DeviceEvent(ev)
208                 return self.registry.handleDeviceEvent(ev, self)
209
210 class _EventObserver(_Observer, Accessibility__POA.EventListener):
211         """
212         Observes all non-keyboard AT-SPI events. Can be reused across event types.
213         """
214         def register(self, reg, name):
215                 """
216                 Starts monitoring for the given event.
217                 
218                 @param name: Name of the event to start monitoring
219                 @type name: string
220                 @param reg: Reference to the raw registry object
221                 @type reg: Accessibility.Registry
222                 """
223                 reg.registerGlobalEventListener(self._this(), name)
224                 
225         def unregister(self, reg, name):
226                 """
227                 Stops monitoring for the given event.
228                 
229                 @param name: Name of the event to stop monitoring
230                 @type name: string
231                 @param reg: Reference to the raw registry object
232                 @type reg: Accessibility.Registry
233                 """
234                 reg.deregisterGlobalEventListener(self._this(), name)
235
236         def queryInterface(self, repo_id):
237                 """
238                 Reports that this class only implements the AT-SPI DeviceEventListener 
239                 interface. Required by AT-SPI.
240
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
245                 """
246                 if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
247                         return self._this()
248                 else:
249                         return None
250
251         def notifyEvent(self, ev):
252                 """
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.
256                 
257                 @param ev: AT-SPI event signal (anything but keyboard)
258                 @type ev: Accessibility.Event
259                 """
260                 # wrap raw event so ref counts are correct before queueing
261                 ev = event.Event(ev)
262                 self.registry.handleEvent(ev)
263
264 class Registry(object):
265         """
266         Wraps the Accessibility.Registry to provide more Pythonic registration for
267         events. 
268         
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
272         has no point.
273         
274         @ivar async: Should event dispatch to local listeners be decoupled from event
275                 receiving from the registry?
276         @type async: boolean
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
287         """
288         def __init__(self, reg):
289                 """
290                 Stores a reference to the AT-SPI registry. Gets and stores a reference
291                 to the DeviceEventController.
292                 
293                 @param reg: Reference to the AT-SPI registry daemon
294                 @type reg: Accessibility.Registry
295                 """
296                 self.async = None
297                 self.reg = reg
298                 self.dev = self.reg.getDeviceEventController()
299                 self.queue = Queue.Queue()
300                 self.clients = {}
301                 self.observers = {}
302                 
303         def __call__(self):
304                 """
305                 @return: This instance of the registry
306                 @rtype: L{Registry}
307                 """
308                 return self
309         
310         def start(self, async=False, gil=True):
311                 """
312                 Enter the main loop to start receiving and dispatching events.
313                 
314                 @param async: Should event dispatch be asynchronous (decoupled) from 
315                         event receiving from the AT-SPI registry?
316                 @type async: boolean
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.
320                 @type gil: boolean
321                 """
322                 self.async = async
323                 
324                 if gil:
325                         def releaseGIL():
326                                 try:
327                                         time.sleep(1e-5)
328                                 except KeyboardInterrupt, e:
329                                         # store the exception for later
330                                         releaseGIL.keyboard_exception = e
331                                         self.stop()
332                                 return True
333                         # make room for an exception if one occurs during the 
334                         releaseGIL.keyboard_exception = None
335                         i = gobject.idle_add(releaseGIL)
336                         
337                 # enter the main loop
338                 try:
339                         bonobo.main()
340                 finally:
341                         # clear all observers
342                         for name, ob in self.observers.items():
343                                 ob.unregister(self.reg, name)
344                         if gil:
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
349
350         def stop(self, *args):
351                 """Quits the main loop."""
352                 try:
353                         bonobo.main_quit()
354                 except RuntimeError:
355                         # ignore errors when quitting (probably already quitting)
356                         pass
357                 self.flushEvents()
358                 
359         def getDesktopCount(self):
360                 """
361                 Gets the number of available desktops.
362                 
363                 @return: Number of desktops
364                 @rtype: integer
365                 @raise LookupError: When the count cannot be retrieved
366                 """
367                 try:
368                         return self.reg.getDesktopCount()
369                 except Exception:
370                         raise LookupError
371                 
372         def getDesktop(self, i):
373                 """
374                 Gets a reference to the i-th desktop.
375                 
376                 @param i: Which desktop to get
377                 @type i: integer
378                 @return: Desktop reference
379                 @rtype: Accessibility.Desktop
380                 @raise LookupError: When the i-th desktop cannot be retrieved
381                 """
382                 try:
383                         return self.reg.getDesktop(i)
384                 except Exception, e:
385                         raise LookupError(e)
386                 
387         def registerEventListener(self, client, *names):
388                 """
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.
392                 
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.
397                 
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}.
401
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
406                 """
407                 for name in names:
408                         # store the callback for each specific event name
409                         self._registerClients(client, name)
410
411         def deregisterEventListener(self, client, *names):
412                 """
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.
416                 
417                 This method must be called to ensure a client registered by
418                 L{registerEventListener} is properly garbage collected.
419
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
425                         registered?
426                 @rtype: boolean
427                 """
428                 missed = False
429                 for name in names:
430                         # remove the callback for each specific event name
431                         missed |= self._unregisterClients(client, name)
432                 return missed
433
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, 
438                                                                                                                                 global_=False):
439                 """
440                 Registers a listener for key stroke events.
441                 
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 
455                         KEY_RELEASED_EVENT.
456                 @type kind: list
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
461                         event?
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
466                 """
467                 try:
468                         # see if we already have an observer for this client
469                         ob = self.clients[client]
470                 except KeyError:
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
476                 if mask is None:
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)
481
482         def deregisterKeystrokeListener(self, client, key_set=[], mask=0, 
483                                                                                                                                         kind=(constants.KEY_PRESSED_EVENT, 
484                                                                                                                                                                 constants.KEY_RELEASED_EVENT)):
485                 """
486                 Deregisters a listener for key stroke events.
487                 
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 
501                         KEY_RELEASED_EVENT.
502                 @type kind: list
503                 @raise KeyError: When the client isn't already registered for events
504                 """
505                 # see if we already have an observer for this client
506                 ob = self.clients[client]
507                 if mask is None:
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)
512
513         def generateKeyboardEvent(self, keycode, keysym, kind):
514                 """
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.
519                 
520                 @param keycode: Hardware keycode or None
521                 @type keycode: integer
522                 @param keysym: Symbolic key string or None
523                 @type keysym: string
524                 @param kind: Kind of event to synthesize
525                 @type kind: integer
526                 """
527                 if keysym is None:
528                         self.dev.generateKeyboardEvent(keycode, '', kind)
529                 else:
530                         self.dev.generateKeyboardEvent(None, keysym, kind)
531         
532         def generateMouseEvent(self, x, y, name):
533                 """
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 
537                 double-click).
538                 
539                 @param x: Horizontal coordinate, usually left-hand oriented
540                 @type x: integer
541                 @param y: Vertical coordinate, usually left-hand oriented
542                 @type y: integer
543                 @param name: Name of the event to generate
544                 @type name: string
545                 """
546                 self.dev.generateMouseEvent(x, y, name)
547                 
548         def handleDeviceEvent(self, event, ob):
549                 """
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}. 
556                 
557                 @note: Asynchronous dispatch of device events is not supported.
558                 
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}
563
564                 @return: Should the event be consumed (True) or allowed to pass on to other
565                         AT-SPI observers (False)?
566                 @rtype: boolean
567                 """
568                 try:
569                         # try to get the client registered for this event type
570                         client = self.clients[ob]
571                 except KeyError:
572                         # client may have unregistered recently, ignore event
573                         return False
574                 # make the call to the client
575                 try:
576                         return client(event) or event.consume
577                 except Exception:
578                         # print the exception, but don't let it stop notification
579                         traceback.print_exc()
580  
581         def handleEvent(self, event):
582                 """             
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.
585
586                 @param event: AT-SPI event
587                 @type event: L{event.Event}
588                 """
589                 if self.async:
590                         # queue for now
591                         self.queue.put_nowait(event)
592                 else:
593                         # dispatch immediately
594                         self._dispatchEvent(event)
595
596         def _dispatchEvent(self, event):
597                 """
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 
602                 are unaffected.
603
604                 @param event: AT-SPI event
605                 @type event: L{event.Event}
606                 """
607                 et = event.type
608                 try:
609                         # try to get the client registered for this event type
610                         clients = self.clients[et.name]
611                 except KeyError:
612                         try:
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)]
622                         except KeyError:
623                                 # client may have unregistered recently, ignore event
624                                 return
625                 # make the call to each client
626                 consume = False
627                 for client in clients:
628                         try:
629                                 consume = client(event) or False
630                         except Exception:
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
635                                 break
636
637         def flushEvents(self):
638                 """
639                 Flushes the event queue by destroying it and recreating it.
640                 """
641                 self.queue = Queue.Queue()
642
643         def pumpQueuedEvents(self, num=-1):
644                 """
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.
648
649                 @param num: Number of events to pump. If number is negative it pumps
650                 the entire queue. Default is -1.
651                 @type num: integer
652                 @return: True if queue is not empty after events were pumped.
653                 @rtype: boolean
654                 """
655                 if num < 0:
656                         # Dequeue as many events as currently in the queue.
657                         num = self.queue.qsize()
658                 for i in xrange(num):
659                         try:
660                                 # get next waiting event
661                                 event = self.queue.get_nowait()
662                         except Queue.Empty:
663                                 break
664                         self._dispatchEvent(event)
665
666                 return not self.queue.empty()
667  
668         def _registerClients(self, client, name):
669                 """
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.
673                 
674                 @param client: Client callback to receive event notifications
675                 @type client: callable
676                 @param name: Partial or full event name
677                 @type name: string
678                 """
679                 try:
680                         # look for an event name in our event tree dictionary
681                         events = constants.EVENT_TREE[name]
682                 except KeyError:
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 
688                         # the client
689                         et = event.EventType(name)
690                         clients = self.clients.setdefault(et.name, [])
691                         try:
692                                 # if this succeeds, this client is already registered for the given
693                                 # event type, so ignore the request
694                                 clients.index(client)
695                         except ValueError:
696                                 # else register the client
697                                 clients.append(client)
698                                 self._registerObserver(name)
699                 else:
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
702                                 # the leaf events
703                                 for e in events:
704                                         self._registerClients(client, e)
705                         
706         def _unregisterClients(self, client, name):
707                 """
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.
711                 
712                 @param client: Client callback to receive event notifications
713                 @type client: callable
714                 @param name: Partial or full event name
715                 @type name: string
716                 """
717                 missed = False
718                 try:
719                         # look for an event name in our event tree dictionary
720                         events = constants.EVENT_TREE[name]
721                 except KeyError:
722                         try:
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
732                                 missed = True
733                         return missed
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
736                 # events
737                 for e in events:
738                         missed |= self._unregisterClients(client, e)
739                 return missed
740         
741         def _registerObserver(self, name):
742                 """             
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.
747          
748                 @param name: Raw name of the event to observe
749                 @type name: string
750                 @return: L{_Observer} object that is monitoring the event
751                 @rtype: L{_Observer}
752                 """
753                 et = event.EventType(name)
754                 try:
755                         # see if an observer already exists for this event
756                         ob = self.observers[et.name]
757                 except KeyError:
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 
762                         # missing)
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 
766                 # event
767                 ob.clientRef()
768                 return ob
769                 
770         def _unregisterObserver(self, name):
771                 """             
772                 Destroys an existing L{_Observer} for the given event type only if no
773                 clients are registered for the events it is monitoring.
774                 
775                 @param name: Name of the event to observe
776                 @type name: string
777                 @raise KeyError: When an observer for the given event is not regist
778                 """
779                 et = event.EventType(name)
780                 # see if an observer already exists for this event
781                 ob = self.observers[et.name]
782                 ob.clientUnref()
783                 if ob.getClientRefCount() == 0:
784                         ob.unregister(self.reg, name)
785                         del self.observers[et.name]