* registry.py (Registry.pumpQueuedEvents): Added this method for
[platform/core/uifw/at-spi2-atk.git] / pyatspi / registry.py
1 '''
2 Registry that hides some of the details of registering for AT-SPI events and
3 starting and stopping the main program loop.
4
5 @todo: PP: when to destroy device listener?
6
7 @author: Peter Parente
8 @organization: IBM Corporation
9 @copyright: Copyright (c) 2005, 2007 IBM Corporation
10 @license: LGPL
11
12 This library is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Library General Public
14 License as published by the Free Software Foundation; either
15 version 2 of the License, or (at your option) any later version.
16
17 This library is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 Library General Public License for more details.
21
22 You should have received a copy of the GNU Library General Public
23 License along with this library; if not, write to the
24 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 Boston, MA 02111-1307, USA.
26
27 Portions of this code originally licensed and copyright (c) 2005, 2007
28 IBM Corporation under the BSD license, available at
29 U{http://www.opensource.org/licenses/bsd-license.php}
30 '''
31 import signal
32 import time
33 import weakref
34 import Queue
35 import traceback
36 import ORBit
37 import bonobo
38 import gobject
39 import Accessibility
40 import Accessibility__POA
41 import utils
42 import constants
43 import event
44
45 class _Observer(object):
46   '''
47   Parent class for all event observers. Dispatches all received events to the 
48   L{Registry} that created this L{_Observer}. Provides basic reference counting
49   functionality needed by L{Registry} to determine when an L{_Observer} can be
50   released for garbage collection. 
51   
52   The reference counting provided by this class is independent of the reference
53   counting used by CORBA. Keeping the counts separate makes it easier for the
54   L{Registry} to detect when an L{_Observer} can be freed in the 
55   L{Registry._unregisterObserver} method.
56   
57   @ivar registry: Reference to the L{Registry} that created this L{_Observer}
58   @type registry: weakref.proxy to L{Registry}
59   @ivar ref_count: Reference count on this L{_Observer}
60   @type ref_count: integer
61   '''
62   def __init__(self, registry):
63     '''
64     Stores a reference to the creating L{Registry}. Intializes the reference
65     count on this object to zero.
66     
67     @param registry: The L{Registry} that created this observer
68     @type registry: weakref.proxy to L{Registry}
69     '''
70     self.registry = weakref.proxy(registry)
71     self.ref_count = 0
72
73   def clientRef(self):
74     '''
75     Increments the Python reference count on this L{_Observer} by one. This
76     method is called when a new client is registered in L{Registry} to receive
77     notification of an event type monitored by this L{_Observer}.
78     '''
79     self.ref_count += 1
80     
81   def clientUnref(self):
82     '''    
83     Decrements the pyatspi reference count on this L{_Observer} by one. This
84     method is called when a client is unregistered in L{Registry} to stop
85     receiving notifications of an event type monitored by this L{_Observer}.
86     '''
87     self.ref_count -= 1
88     
89   def getClientRefCount(self):
90     '''
91     @return: Current Python reference count on this L{_Observer}
92     @rtype: integer
93     '''
94     return self.ref_count
95   
96   def ref(self): 
97     '''Required by CORBA. Does nothing.'''
98     pass
99     
100   def unref(self): 
101     '''Required by CORBA. Does nothing.'''
102     pass
103
104 class _DeviceObserver(_Observer, Accessibility__POA.DeviceEventListener):
105   '''
106   Observes keyboard press and release events.
107   
108   @ivar registry: The L{Registry} that created this observer
109   @type registry: L{Registry}
110   @ivar key_set: Set of keys to monitor
111   @type key_set: list of integer
112   @ivar mask: Watch for key events while these modifiers are held
113   @type mask: integer
114   @ivar kind: Kind of events to monitor
115   @type kind: integer
116   @ivar mode: Keyboard event mode
117   @type mode: Accessibility.EventListenerMode
118   '''
119   def __init__(self, registry, synchronous, preemptive, global_):
120     '''
121     Creates a mode object that defines when key events will be received from 
122     the system. Stores all other information for later registration.
123     
124     @param registry: The L{Registry} that created this observer
125     @type registry: L{Registry}
126     @param synchronous: Handle the key event synchronously?
127     @type synchronous: boolean
128     @param preemptive: Allow event to be consumed?
129     @type preemptive: boolean
130     @param global_: Watch for events on inaccessible applications too?
131     @type global_: boolean
132     '''
133     _Observer.__init__(self, registry)   
134     self.mode = Accessibility.EventListenerMode()
135     self.mode.preemptive = preemptive
136     self.mode.synchronous = synchronous
137     self.mode._global = global_    
138    
139   def register(self, dc, key_set, mask, kind):
140     '''
141     Starts keyboard event monitoring.
142     
143     @param dc: Reference to a device controller
144     @type dc: Accessibility.DeviceEventController
145     @param key_set: Set of keys to monitor
146     @type key_set: list of integer
147     @param mask: Integer modifier mask or an iterable over multiple masks to
148       unapply all at once
149     @type mask: integer, iterable, or None
150     @param kind: Kind of events to monitor
151     @type kind: integer
152     '''
153     try:
154       # check if the mask is iterable
155       iter(mask)
156     except TypeError:
157       # register a single integer if not
158       dc.registerKeystrokeListener(self._this(), key_set, mask, kind, 
159                                    self.mode)
160     else:
161       for m in mask:
162         dc.registerKeystrokeListener(self._this(), key_set, m, kind, self.mode)
163
164   def unregister(self, dc, key_set, mask, kind):
165     '''
166     Stops keyboard event monitoring.
167     
168     @param dc: Reference to a device controller
169     @type dc: Accessibility.DeviceEventController
170     @param key_set: Set of keys to monitor
171     @type key_set: list of integer
172     @param mask: Integer modifier mask or an iterable over multiple masks to
173       unapply all at once
174     @type mask: integer, iterable, or None
175     @param kind: Kind of events to monitor
176     @type kind: integer
177     '''
178     try:
179       # check if the mask is iterable
180       iter(mask)
181     except TypeError:
182       # unregister a single integer if not
183       dc.deregisterKeystrokeListener(self._this(), key_set, mask, kind)
184     else:
185       for m in mask:
186         dc.deregisterKeystrokeListener(self._this(), key_set, m, kind)
187       
188   def queryInterface(self, repo_id):
189     '''
190     Reports that this class only implements the AT-SPI DeviceEventListener 
191     interface. Required by AT-SPI.
192     
193     @param repo_id: Request for an interface 
194     @type repo_id: string
195     @return: The underlying CORBA object for the device event listener
196     @rtype: Accessibility.EventListener
197     '''
198     if repo_id == utils.getInterfaceIID(Accessibility.DeviceEventListener):
199       return self._this()
200     else:
201       return None
202
203   def notifyEvent(self, ev):
204     '''
205     Notifies the L{Registry} that an event has occurred. Wraps the raw event 
206     object in our L{Event} class to support automatic ref and unref calls. An
207     observer can return True to indicate this event should not be allowed to pass 
208     to other AT-SPI observers or the underlying application.
209     
210     @param ev: Keyboard event
211     @type ev: Accessibility.DeviceEvent
212     @return: Should the event be consumed (True) or allowed to pass on to other
213       AT-SPI observers (False)?
214     @rtype: boolean
215     '''
216     # wrap the device event
217     ev = event.DeviceEvent(ev)
218     return self.registry.handleDeviceEvent(ev, self)
219
220 class _EventObserver(_Observer, Accessibility__POA.EventListener):
221   '''
222   Observes all non-keyboard AT-SPI events. Can be reused across event types.
223   '''
224   def register(self, reg, name):
225     '''
226     Starts monitoring for the given event.
227     
228     @param name: Name of the event to start monitoring
229     @type name: string
230     @param reg: Reference to the raw registry object
231     @type reg: Accessibility.Registry
232     '''
233     reg.registerGlobalEventListener(self._this(), name)
234     
235   def unregister(self, reg, name):
236     '''
237     Stops monitoring for the given event.
238     
239     @param name: Name of the event to stop monitoring
240     @type name: string
241     @param reg: Reference to the raw registry object
242     @type reg: Accessibility.Registry
243     '''
244     reg.deregisterGlobalEventListener(self._this(), name)
245
246   def queryInterface(self, repo_id):
247     '''
248     Reports that this class only implements the AT-SPI DeviceEventListener 
249     interface. Required by AT-SPI.
250
251     @param repo_id: Request for an interface 
252     @type repo_id: string
253     @return: The underlying CORBA object for the device event listener
254     @rtype: Accessibility.EventListener
255     '''
256     if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
257       return self._this()
258     else:
259       return None
260
261   def notifyEvent(self, ev):
262     '''
263     Notifies the L{Registry} that an event has occurred. Wraps the raw event 
264     object in our L{Event} class to support automatic ref and unref calls.
265     Aborts on any exception indicating the event could not be wrapped.
266     
267     @param ev: AT-SPI event signal (anything but keyboard)
268     @type ev: Accessibility.Event
269     '''
270     # wrap raw event so ref counts are correct before queueing
271     ev = event.Event(ev)
272     self.registry.handleEvent(ev)
273
274 class Registry(object):
275   '''
276   Wraps the Accessibility.Registry to provide more Pythonic registration for
277   events. 
278   
279   This object should be treated as a singleton, but such treatment is not
280   enforced. You can construct another instance of this object and give it a
281   reference to the Accessibility.Registry singleton. Doing so is harmless and
282   has no point.
283   
284   @ivar async: Should event dispatch to local listeners be decoupled from event
285     receiving from the registry?
286   @type async: boolean
287   @ivar reg: Reference to the real, wrapped registry object
288   @type reg: Accessibility.Registry
289   @ivar dev: Reference to the device controller
290   @type dev: Accessibility.DeviceEventController
291   @ivar queue: Queue of events awaiting local dispatch
292   @type queue: Queue.Queue
293   @ivar clients: Map of event names to client listeners
294   @type clients: dictionary
295   @ivar observers: Map of event names to AT-SPI L{_Observer} objects
296   @type observers: dictionary
297   '''
298   def __init__(self, reg):
299     '''
300     Stores a reference to the AT-SPI registry. Gets and stores a reference
301     to the DeviceEventController.
302     
303     @param reg: Reference to the AT-SPI registry daemon
304     @type reg: Accessibility.Registry
305     '''
306     self.async = None
307     self.reg = reg
308     self.dev = self.reg.getDeviceEventController()
309     self.queue = Queue.Queue()
310     self.clients = {}
311     self.observers = {}
312     
313   def __call__(self):
314     '''
315     @return: This instance of the registry
316     @rtype: L{Registry}
317     '''
318     return self
319   
320   def start(self, async=False, gil=True):
321     '''
322     Enter the main loop to start receiving and dispatching events.
323     
324     @param async: Should event dispatch be asynchronous (decoupled) from 
325       event receiving from the AT-SPI registry?
326     @type async: boolean
327     @param gil: Add an idle callback which releases the Python GIL for a few
328       milliseconds to allow other threads to run? Necessary if other threads
329       will be used in this process.
330     @type gil: boolean
331     '''
332     self.async = async
333     
334     if gil:
335       def releaseGIL():
336         try:
337           time.sleep(1e-5)
338         except KeyboardInterrupt, e:
339           # store the exception for later
340           releaseGIL.keyboard_exception = e
341           self.stop()
342         return True
343       # make room for an exception if one occurs during the 
344       releaseGIL.keyboard_exception = None
345       i = gobject.idle_add(releaseGIL)
346       
347     # enter the main loop
348     try:
349       bonobo.main()
350     finally:
351       # clear all observers
352       for name, ob in self.observers.items():
353         ob.unregister(self.reg, name)
354       if gil:
355         gobject.source_remove(i)
356         if releaseGIL.keyboard_exception is not None:
357           # raise an keyboard exception we may have gotten earlier
358           raise releaseGIL.keyboard_exception
359
360   def stop(self, *args):
361     '''Quits the main loop.'''
362     try:
363       bonobo.main_quit()
364     except RuntimeError:
365       # ignore errors when quitting (probably already quitting)
366       pass
367     self.flushEvents()
368     
369   def getDesktopCount(self):
370     '''
371     Gets the number of available desktops.
372     
373     @return: Number of desktops
374     @rtype: integer
375     @raise LookupError: When the count cannot be retrieved
376     '''
377     try:
378       return self.reg.getDesktopCount()
379     except Exception:
380       raise LookupError
381     
382   def getDesktop(self, i):
383     '''
384     Gets a reference to the i-th desktop.
385     
386     @param i: Which desktop to get
387     @type i: integer
388     @return: Desktop reference
389     @rtype: Accessibility.Desktop
390     @raise LookupError: When the i-th desktop cannot be retrieved
391     '''
392     try:
393       return self.reg.getDesktop(i)
394     except Exception, e:
395       raise LookupError(e)
396     
397   def registerEventListener(self, client, *names):
398     '''
399     Registers a new client callback for the given event names. Supports 
400     registration for all subevents if only partial event name is specified.
401     Do not include a trailing colon.
402     
403     For example, 'object' will register for all object events, 
404     'object:property-change' will register for all property change events,
405     and 'object:property-change:accessible-parent' will register only for the
406     parent property change event.
407     
408     Registered clients will not be automatically removed when the client dies.
409     To ensure the client is properly garbage collected, call 
410     L{deregisterEventListener}.
411
412     @param client: Callable to be invoked when the event occurs
413     @type client: callable
414     @param names: List of full or partial event names
415     @type names: list of string
416     '''
417     for name in names:
418       # store the callback for each specific event name
419       self._registerClients(client, name)
420
421   def deregisterEventListener(self, client, *names):
422     '''
423     Unregisters an existing client callback for the given event names. Supports 
424     unregistration for all subevents if only partial event name is specified.
425     Do not include a trailing colon.
426     
427     This method must be called to ensure a client registered by
428     L{registerEventListener} is properly garbage collected.
429
430     @param client: Client callback to remove
431     @type client: callable
432     @param names: List of full or partial event names
433     @type names: list of string
434     @return: Were event names specified for which the given client was not
435       registered?
436     @rtype: boolean
437     '''
438     missed = False
439     for name in names:
440       # remove the callback for each specific event name
441       missed |= self._unregisterClients(client, name)
442     return missed
443
444   def registerKeystrokeListener(self, client, key_set=[], mask=0, 
445                                 kind=(constants.KEY_PRESSED_EVENT, 
446                                       constants.KEY_RELEASED_EVENT),
447                                 synchronous=True, preemptive=True, 
448                                 global_=False):
449     '''
450     Registers a listener for key stroke events.
451     
452     @param client: Callable to be invoked when the event occurs
453     @type client: callable
454     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
455       to indicate all keys.
456     @type key_set: list of integer
457     @param mask: When the mask is None, the codes in the key_set will be 
458       monitored only when no modifier is held. When the mask is an 
459       integer, keys in the key_set will be monitored only when the modifiers in
460       the mask are held. When the mask is an iterable over more than one 
461       integer, keys in the key_set will be monitored when any of the modifier
462       combinations in the set are held.
463     @type mask: integer, iterable, None
464     @param kind: Kind of events to watch, KEY_PRESSED_EVENT or 
465       KEY_RELEASED_EVENT.
466     @type kind: list
467     @param synchronous: Should the callback notification be synchronous, giving
468       the client the chance to consume the event?
469     @type synchronous: boolean
470     @param preemptive: Should the callback be allowed to preempt / consume the
471       event?
472     @type preemptive: boolean
473     @param global_: Should callback occur even if an application not supporting
474       AT-SPI is in the foreground? (requires xevie)
475     @type global_: boolean
476     '''
477     try:
478       # see if we already have an observer for this client
479       ob = self.clients[client]
480     except KeyError:
481       # create a new device observer for this client
482       ob = _DeviceObserver(self, synchronous, preemptive, global_)
483       # store the observer to client mapping, and the inverse
484       self.clients[ob] = client
485       self.clients[client] = ob
486     if mask is None:
487       # None means all modifier combinations
488       mask = utils.allModifiers()
489     # register for new keystrokes on the observer
490     ob.register(self.dev, key_set, mask, kind)
491
492   def deregisterKeystrokeListener(self, client, key_set=[], mask=0, 
493                                   kind=(constants.KEY_PRESSED_EVENT, 
494                                         constants.KEY_RELEASED_EVENT)):
495     '''
496     Deregisters a listener for key stroke events.
497     
498     @param client: Callable to be invoked when the event occurs
499     @type client: callable
500     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
501       to indicate all keys.
502     @type key_set: list of integer
503     @param mask: When the mask is None, the codes in the key_set will be 
504       monitored only when no modifier is held. When the mask is an 
505       integer, keys in the key_set will be monitored only when the modifiers in
506       the mask are held. When the mask is an iterable over more than one 
507       integer, keys in the key_set will be monitored when any of the modifier
508       combinations in the set are held.
509     @type mask: integer, iterable, None
510     @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or 
511       KEY_RELEASED_EVENT.
512     @type kind: list
513     @raise KeyError: When the client isn't already registered for events
514     '''
515     # see if we already have an observer for this client
516     ob = self.clients[client]
517     if mask is None:
518       # None means all modifier combinations
519       mask = utils.allModifiers()
520     # register for new keystrokes on the observer
521     ob.unregister(self.dev, key_set, mask, kind)
522
523   def generateKeyboardEvent(self, keycode, keysym, kind):
524     '''
525     Generates a keyboard event. One of the keycode or the keysym parameters
526     should be specified and the other should be None. The kind parameter is 
527     required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
528     KEY_SYM, or KEY_STRING.
529     
530     @param keycode: Hardware keycode or None
531     @type keycode: integer
532     @param keysym: Symbolic key string or None
533     @type keysym: string
534     @param kind: Kind of event to synthesize
535     @type kind: integer
536     '''
537     if keysym is None:
538       self.dev.generateKeyboardEvent(keycode, '', kind)
539     else:
540       self.dev.generateKeyboardEvent(None, keysym, kind)
541   
542   def generateMouseEvent(self, x, y, name):
543     '''
544     Generates a mouse event at the given absolute x and y coordinate. The kind
545     of event generated is specified by the name. For example, MOUSE_B1P 
546     (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3 
547     double-click).
548     
549     @param x: Horizontal coordinate, usually left-hand oriented
550     @type x: integer
551     @param y: Vertical coordinate, usually left-hand oriented
552     @type y: integer
553     @param name: Name of the event to generate
554     @type name: string
555     '''
556     self.dev.generateMouseEvent(x, y, name)
557     
558   def handleDeviceEvent(self, event, ob):
559     '''
560     Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
561     in the order they were registered for the given AT-SPI event. If any
562     client returns True, callbacks cease for the event for clients of this registry 
563     instance. Clients of other registry instances and clients in other processes may 
564     be affected depending on the values of synchronous and preemptive used when invoking
565     L{registerKeystrokeListener}. 
566     
567     @note: Asynchronous dispatch of device events is not supported.
568     
569     @param event: AT-SPI device event
570     @type event: L{event.DeviceEvent}
571     @param ob: Observer that received the event
572     @type ob: L{_DeviceObserver}
573
574     @return: Should the event be consumed (True) or allowed to pass on to other
575       AT-SPI observers (False)?
576     @rtype: boolean
577     '''
578     try:
579       # try to get the client registered for this event type
580       client = self.clients[ob]
581     except KeyError:
582       # client may have unregistered recently, ignore event
583       return False
584     # make the call to the client
585     try:
586       return client(event) or event.consume
587     except Exception:
588       # print the exception, but don't let it stop notification
589       traceback.print_exc()
590  
591   def handleEvent(self, event):
592     '''    
593     Handles an AT-SPI event by either queuing it for later dispatch when the
594     L{Registry.async} flag is set, or dispatching it immediately.
595
596     @param event: AT-SPI event
597     @type event: L{event.Event}
598     '''
599     if self.async:
600       # queue for now
601       self.queue.put_nowait(event)
602     else:
603       # dispatch immediately
604       self._dispatchEvent(event)
605
606   def _dispatchEvent(self, event):
607     '''
608     Dispatches L{event.Event}s to registered clients. Clients are called in
609     the order they were registered for the given AT-SPI event. If any client
610     returns True, callbacks cease for the event for clients of this registry 
611     instance. Clients of other registry instances and clients in other processes 
612     are unaffected.
613
614     @param event: AT-SPI event
615     @type event: L{event.Event}
616     '''
617     et = event.type
618     try:
619       # try to get the client registered for this event type
620       clients = self.clients[et.name]
621     except KeyError:
622       try:
623         # we may not have registered for the complete subtree of events
624         # if our tree does not list all of a certain type (e.g.
625         # object:state-changed:*); try again with klass and major only
626         if et.detail is not None:
627           # Strip the 'detail' field.
628           clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
629         elif et.minor is not None:
630           # The event could possibly be object:state-changed:*.
631           clients = self.clients['%s:%s' % (et.klass, et.major)]
632       except KeyError:
633         # client may have unregistered recently, ignore event
634         return
635     # make the call to each client
636     consume = False
637     for client in clients:
638       try:
639         consume = client(event) or False
640       except Exception:
641         # print the exception, but don't let it stop notification
642         traceback.print_exc()
643       if consume or event.consume:
644         # don't allow further processing if a client returns True
645         break
646
647   def flushEvents(self):
648     '''
649     Flushes the event queue by destroying it and recreating it.
650     '''
651     self.queue = Queue.Queue()
652
653   def pumpQueuedEvents(self, num=-1):
654     '''
655     Provides asynch processing of events in the queue by executeing them with 
656     _dispatchEvent() (as is done immediately when synch processing). 
657     This method would normally be called from a main loop or idle function.
658
659     @param num: Number of events to pump. If number is negative it pumps
660     the entire queue. Default is -1.
661     @type num: integer
662     @return: True if queue is not empty after events were pumped.
663     @rtype: boolean
664     '''
665     if num < 0:
666       # Dequeue as many events as currently in the queue.
667       num = self.queue.qsize()
668     for i in xrange(num):
669       try:
670         # get next waiting event
671         event = self.queue.get_nowait()
672       except Queue.Empty:
673         break
674       self._dispatchEvent(event)
675
676     return not self.queue.empty()
677  
678   def _registerClients(self, client, name):
679     '''
680     Internal method that recursively associates a client with AT-SPI event 
681     names. Allows a client to incompletely specify an event name in order to 
682     register for subevents without specifying their full names manually.
683     
684     @param client: Client callback to receive event notifications
685     @type client: callable
686     @param name: Partial or full event name
687     @type name: string
688     '''
689     try:
690       # look for an event name in our event tree dictionary
691       events = constants.EVENT_TREE[name]
692     except KeyError:
693       # if the event name doesn't exist, it's a leaf event meaning there are
694       # no subtypes for that event
695       # add this client to the list of clients already in the dictionary 
696       # using the event name as the key; if there are no clients yet for this 
697       # event, insert an empty list into the dictionary before appending 
698       # the client
699       et = event.EventType(name)
700       clients = self.clients.setdefault(et.name, [])
701       try:
702         # if this succeeds, this client is already registered for the given
703         # event type, so ignore the request
704         clients.index(client)
705       except ValueError:
706         # else register the client
707         clients.append(client)
708         self._registerObserver(name)
709     else:
710         # if the event name does exist in the tree, there are subevents for
711         # this event; loop through them calling this method again to get to
712         # the leaf events
713         for e in events:
714           self._registerClients(client, e)
715       
716   def _unregisterClients(self, client, name):
717     '''
718     Internal method that recursively unassociates a client with AT-SPI event 
719     names. Allows a client to incompletely specify an event name in order to 
720     unregister for subevents without specifying their full names manually.
721     
722     @param client: Client callback to receive event notifications
723     @type client: callable
724     @param name: Partial or full event name
725     @type name: string
726     '''
727     missed = False
728     try:
729       # look for an event name in our event tree dictionary
730       events = constants.EVENT_TREE[name]
731     except KeyError:
732       try:
733         # if the event name doesn't exist, it's a leaf event meaning there are
734         # no subtypes for that event
735         # get the list of registered clients and try to remove the one provided
736         et = event.EventType(name)
737         clients = self.clients[et.name]
738         clients.remove(client)
739         self._unregisterObserver(name)
740       except (ValueError, KeyError):
741         # ignore any exceptions indicating the client is not registered
742         missed = True
743       return missed
744     # if the event name does exist in the tree, there are subevents for this 
745     # event; loop through them calling this method again to get to the leaf
746     # events
747     for e in events:
748       missed |= self._unregisterClients(client, e)
749     return missed
750   
751   def _registerObserver(self, name):
752     '''    
753     Creates a new L{_Observer} to watch for events of the given type or
754     returns the existing observer if one is already registered. One
755     L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
756     any event name not found in the tree.
757    
758     @param name: Raw name of the event to observe
759     @type name: string
760     @return: L{_Observer} object that is monitoring the event
761     @rtype: L{_Observer}
762     '''
763     et = event.EventType(name)
764     try:
765       # see if an observer already exists for this event
766       ob = self.observers[et.name]
767     except KeyError:
768       # build a new observer if one does not exist
769       ob = _EventObserver(self)
770       # we have to register for the raw name because it may be different from
771       # the parsed name determined by EventType (e.g. trailing ':' might be 
772       # missing)
773       ob.register(self.reg, name)
774       self.observers[et.name] = ob
775     # increase our client ref count so we know someone new is watching for the 
776     # event
777     ob.clientRef()
778     return ob
779     
780   def _unregisterObserver(self, name):
781     '''    
782     Destroys an existing L{_Observer} for the given event type only if no
783     clients are registered for the events it is monitoring.
784     
785     @param name: Name of the event to observe
786     @type name: string
787     @raise KeyError: When an observer for the given event is not regist
788     '''
789     et = event.EventType(name)
790     # see if an observer already exists for this event
791     ob = self.observers[et.name]
792     ob.clientUnref()
793     if ob.getClientRefCount() == 0:
794       ob.unregister(self.reg, name)
795       del self.observers[et.name]