f1eafdccc9027a5d0e9a73ab07b04d6cd1c806a3
[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 set the L{Event} consume flag to True to indicate this event
208     should not be allowed to pass to other AT-SPI observers or the underlying
209     application.
210     
211     @param ev: Keyboard event
212     @type ev: Accessibility.DeviceEvent
213     @return: Should the event be consumed (True) or allowed to pass on to other
214       AT-SPI observers (False)?
215     @rtype: boolean
216     '''
217     # wrap the device event
218     ev = event.DeviceEvent(ev)
219     self.registry.handleDeviceEvent(ev, self)
220     return ev.consume
221
222 class _EventObserver(_Observer, Accessibility__POA.EventListener):
223   '''
224   Observes all non-keyboard AT-SPI events. Can be reused across event types.
225   '''
226   def register(self, reg, name):
227     '''
228     Starts monitoring for the given event.
229     
230     @param name: Name of the event to start monitoring
231     @type name: string
232     @param reg: Reference to the raw registry object
233     @type reg: Accessibility.Registry
234     '''
235     reg.registerGlobalEventListener(self._this(), name)
236     
237   def unregister(self, reg, name):
238     '''
239     Stops monitoring for the given event.
240     
241     @param name: Name of the event to stop monitoring
242     @type name: string
243     @param reg: Reference to the raw registry object
244     @type reg: Accessibility.Registry
245     '''
246     reg.deregisterGlobalEventListener(self._this(), name)
247
248   def queryInterface(self, repo_id):
249     '''
250     Reports that this class only implements the AT-SPI DeviceEventListener 
251     interface. Required by AT-SPI.
252
253     @param repo_id: Request for an interface 
254     @type repo_id: string
255     @return: The underlying CORBA object for the device event listener
256     @rtype: Accessibility.EventListener
257     '''
258     if repo_id == utils.getInterfaceIID(Accessibility.EventListener):
259       return self._this()
260     else:
261       return None
262
263   def notifyEvent(self, ev):
264     '''
265     Notifies the L{Registry} that an event has occurred. Wraps the raw event 
266     object in our L{Event} class to support automatic ref and unref calls.
267     Aborts on any exception indicating the event could not be wrapped.
268     
269     @param ev: AT-SPI event signal (anything but keyboard)
270     @type ev: Accessibility.Event
271     '''
272     # wrap raw event so ref counts are correct before queueing
273     ev = event.Event(ev)
274     self.registry.handleEvent(ev)
275
276 class Registry(object):
277   '''
278   Wraps the Accessibility.Registry to provide more Pythonic registration for
279   events. 
280   
281   This object should be treated as a singleton, but such treatment is not
282   enforced. You can construct another instance of this object and give it a
283   reference to the Accessibility.Registry singleton. Doing so is harmless and
284   has no point.
285   
286   @ivar async: Should event dispatch to local listeners be decoupled from event
287     receiving from the registry?
288   @type async: boolean
289   @ivar reg: Reference to the real, wrapped registry object
290   @type reg: Accessibility.Registry
291   @ivar dev: Reference to the device controller
292   @type dev: Accessibility.DeviceEventController
293   @ivar queue: Queue of events awaiting local dispatch
294   @type queue: Queue.Queue
295   @ivar clients: Map of event names to client listeners
296   @type clients: dictionary
297   @ivar observers: Map of event names to AT-SPI L{_Observer} objects
298   @type observers: dictionary
299   '''
300   def __init__(self, reg):
301     '''
302     Stores a reference to the AT-SPI registry. Gets and stores a reference
303     to the DeviceEventController.
304     
305     @param reg: Reference to the AT-SPI registry daemon
306     @type reg: Accessibility.Registry
307     '''
308     self.async = None
309     self.reg = reg
310     self.dev = self.reg.getDeviceEventController()
311     self.queue = Queue.Queue()
312     self.clients = {}
313     self.observers = {}
314     
315   def __call__(self):
316     '''
317     @return: This instance of the registry
318     @rtype: L{Registry}
319     '''
320     return self
321   
322   def start(self, async=False, gil=True):
323     '''
324     Enter the main loop to start receiving and dispatching events.
325     
326     @param async: Should event dispatch be asynchronous (decoupled) from 
327       event receiving from the AT-SPI registry?
328     @type async: boolean
329     @param gil: Add an idle callback which releases the Python GIL for a few
330       milliseconds to allow other threads to run? Necessary if other threads
331       will be used in this process.
332     @type gil: boolean
333     '''
334     self.async = async
335     
336     if gil:
337       def releaseGIL():
338         try:
339           time.sleep(1e-5)
340         except KeyboardInterrupt, e:
341           # store the exception for later
342           releaseGIL.keyboard_exception = e
343           self.stop()
344         return True
345       # make room for an exception if one occurs during the 
346       releaseGIL.keyboard_exception = None
347       i = gobject.idle_add(releaseGIL)
348       
349     # enter the main loop
350     try:
351       bonobo.main()
352     except KeyboardInterrupt, e:
353       # re-raise the keyboard interrupt
354       raise e
355     finally:
356       # clear all observers
357       for name, ob in self.observers.items():
358         ob.unregister(self.reg, name)
359       if gil:
360         gobject.source_remove(i)
361         if releaseGIL.keyboard_exception is not None:
362           # re-raise the keyboard interrupt we got during the GIL release
363           raise releaseGIL.keyboard_exception
364
365   def stop(self, *args):
366     '''Quits the main loop.'''
367     try:
368       bonobo.main_quit()
369     except RuntimeError:
370       # ignore errors when quitting (probably already quitting)
371       pass
372     
373   def getDesktopCount(self):
374     '''
375     Gets the number of available desktops.
376     
377     @return: Number of desktops
378     @rtype: integer
379     @raise LookupError: When the count cannot be retrieved
380     '''
381     try:
382       return self.reg.getDesktopCount()
383     except Exception:
384       raise LookupError
385     
386   def getDesktop(self, i):
387     '''
388     Gets a reference to the i-th desktop.
389     
390     @param i: Which desktop to get
391     @type i: integer
392     @return: Desktop reference
393     @rtype: Accessibility.Desktop
394     @raise LookupError: When the i-th desktop cannot be retrieved
395     '''
396     try:
397       return self.reg.getDesktop(i)
398     except Exception, e:
399       raise LookupError(e)
400     
401   def registerEventListener(self, client, *names):
402     '''
403     Registers a new client callback for the given event names. Supports 
404     registration for all subevents if only partial event name is specified.
405     Do not include a trailing colon.
406     
407     For example, 'object' will register for all object events, 
408     'object:property-change' will register for all property change events,
409     and 'object:property-change:accessible-parent' will register only for the
410     parent property change event.
411     
412     Registered clients will not be automatically removed when the client dies.
413     To ensure the client is properly garbage collected, call 
414     L{deregisterEventListener}.
415
416     @param client: Callable to be invoked when the event occurs
417     @type client: callable
418     @param names: List of full or partial event names
419     @type names: list of string
420     '''
421     for name in names:
422       # store the callback for each specific event name
423       self._registerClients(client, name)
424
425   def deregisterEventListener(self, client, *names):
426     '''
427     Unregisters an existing client callback for the given event names. Supports 
428     unregistration for all subevents if only partial event name is specified.
429     Do not include a trailing colon.
430     
431     This method must be called to ensure a client registered by
432     L{registerEventListener} is properly garbage collected.
433
434     @param client: Client callback to remove
435     @type client: callable
436     @param names: List of full or partial event names
437     @type names: list of string
438     @return: Were event names specified for which the given client was not
439       registered?
440     @rtype: boolean
441     '''
442     missed = False
443     for name in names:
444       # remove the callback for each specific event name
445       missed |= self._unregisterClients(client, name)
446     return missed
447
448   def registerKeystrokeListener(self, client, key_set=[], mask=0, 
449                                 kind=(constants.KEY_PRESSED_EVENT, 
450                                       constants.KEY_RELEASED_EVENT),
451                                 synchronous=True, preemptive=True, 
452                                 global_=False):
453     '''
454     Registers a listener for key stroke events.
455     
456     @param client: Callable to be invoked when the event occurs
457     @type client: callable
458     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
459       to indicate all keys.
460     @type key_set: list of integer
461     @param mask: When the mask is None, the codes in the key_set will be 
462       monitored only when no modifier is held. When the mask is an 
463       integer, keys in the key_set will be monitored only when the modifiers in
464       the mask are held. When the mask is an iterable over more than one 
465       integer, keys in the key_set will be monitored when any of the modifier
466       combinations in the set are held.
467     @type mask: integer, iterable, None
468     @param kind: Kind of events to watch, KEY_PRESSED_EVENT or 
469       KEY_RELEASED_EVENT.
470     @type kind: list
471     @param synchronous: Should the callback notification be synchronous, giving
472       the client the chance to consume the event?
473     @type synchronous: boolean
474     @param preemptive: Should the callback be allowed to preempt / consume the
475       event?
476     @type preemptive: boolean
477     @param global_: Should callback occur even if an application not supporting
478       AT-SPI is in the foreground? (requires xevie)
479     @type global_: boolean
480     '''
481     try:
482       # see if we already have an observer for this client
483       ob = self.clients[client]
484     except KeyError:
485       # create a new device observer for this client
486       ob = _DeviceObserver(self, synchronous, preemptive, global_)
487       # store the observer to client mapping, and the inverse
488       self.clients[ob] = client
489       self.clients[client] = ob
490     if mask is None:
491       # None means all modifier combinations
492       mask = utils.allModifiers()
493     # register for new keystrokes on the observer
494     ob.register(self.dev, key_set, mask, kind)
495
496   def deregisterKeystrokeListener(self, client, key_set=[], mask=0, 
497                                   kind=(constants.KEY_PRESSED_EVENT, 
498                                         constants.KEY_RELEASED_EVENT)):
499     '''
500     Deregisters a listener for key stroke events.
501     
502     @param client: Callable to be invoked when the event occurs
503     @type client: callable
504     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
505       to indicate all keys.
506     @type key_set: list of integer
507     @param mask: When the mask is None, the codes in the key_set will be 
508       monitored only when no modifier is held. When the mask is an 
509       integer, keys in the key_set will be monitored only when the modifiers in
510       the mask are held. When the mask is an iterable over more than one 
511       integer, keys in the key_set will be monitored when any of the modifier
512       combinations in the set are held.
513     @type mask: integer, iterable, None
514     @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or 
515       KEY_RELEASED_EVENT.
516     @type kind: list
517     @raise KeyError: When the client isn't already registered for events
518     '''
519     # see if we already have an observer for this client
520     ob = self.clients[client]
521     if mask is None:
522       # None means all modifier combinations
523       mask = utils.allModifiers()
524     # register for new keystrokes on the observer
525     ob.unregister(self.dev, key_set, mask, kind)
526
527   def generateKeyboardEvent(self, keycode, keysym, kind):
528     '''
529     Generates a keyboard event. One of the keycode or the keysym parameters
530     should be specified and the other should be None. The kind parameter is 
531     required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
532     KEY_SYM, or KEY_STRING.
533     
534     @param keycode: Hardware keycode or None
535     @type keycode: integer
536     @param keysym: Symbolic key string or None
537     @type keysym: string
538     @param kind: Kind of event to synthesize
539     @type kind: integer
540     '''
541     if keysym is None:
542       self.dev.generateKeyboardEvent(keycode, '', kind)
543     else:
544       self.dev.generateKeyboardEvent(None, keysym, kind)
545   
546   def generateMouseEvent(self, x, y, name):
547     '''
548     Generates a mouse event at the given absolute x and y coordinate. The kind
549     of event generated is specified by the name. For example, MOUSE_B1P 
550     (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3 
551     double-click).
552     
553     @param x: Horizontal coordinate, usually left-hand oriented
554     @type x: integer
555     @param y: Vertical coordinate, usually left-hand oriented
556     @type y: integer
557     @param name: Name of the event to generate
558     @type name: string
559     '''
560     self.dev.generateMouseEvent(x, y, name)
561     
562   def handleDeviceEvent(self, event, ob):
563     '''
564     Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
565     in the order they were registered for the given AT-SPI event. If any
566     client sets the L{event.DeviceEvent.consume} flag to True, callbacks cease
567     for the event for clients of this registry instance. Clients of other
568     registry instances and clients in other processes may be affected
569     depending on the values of synchronous and preemptive used when invoking
570     L{registerKeystrokeListener}. 
571     
572     @note: Asynchronous dispatch of device events is not supported.
573     
574     @param event: AT-SPI device event
575     @type event: L{event.DeviceEvent}
576     @param ob: Observer that received the event
577     @type ob: L{_DeviceObserver}
578     '''
579     try:
580       # try to get the client registered for this event type
581       client = self.clients[ob]
582     except KeyError:
583       # client may have unregistered recently, ignore event
584       return
585     # make the call to the client
586     try:
587       client(event)
588     except Exception:
589       # print the exception, but don't let it stop notification
590       traceback.print_exc()
591  
592   def handleEvent(self, event):
593     '''    
594     Handles an AT-SPI event by either queuing it for later dispatch when the
595     L{Registry.async} flag is set, or dispatching it immediately.
596
597     @param event: AT-SPI event
598     @type event: L{event.Event}
599     '''
600     if self.async:
601       # queue for now
602       self.queue.put_nowait(event)
603     else:
604       # dispatch immediately
605       self._dispatchEvent(event)
606
607   def _dispatchEvent(self, event):
608     '''
609     Dispatches L{event.Event}s to registered clients. Clients are called in
610     the order they were registered for the given AT-SPI event. If any client
611     sets the L{Event} consume flag to True, callbacks cease for the event for
612     clients of this registry instance. Clients of other registry instances and
613     clients in other processes are unaffected.
614
615     @param event: AT-SPI event
616     @type event: L{event.Event}
617     '''
618     try:
619       # try to get the client registered for this event type
620       clients = self.clients[event.type.name]
621     except KeyError:
622       # client may have unregistered recently, ignore event
623       return
624     # make the call to each client
625     for client in clients:
626       try:
627         client(event)
628       except Exception:
629         # print the exception, but don't let it stop notification
630         traceback.print_exc()
631       if event.consume:
632         # don't allow further processing if the consume flag is set
633         break
634
635   def _registerClients(self, client, name):
636     '''
637     Internal method that recursively associates a client with AT-SPI event 
638     names. Allows a client to incompletely specify an event name in order to 
639     register for subevents without specifying their full names manually.
640     
641     @param client: Client callback to receive event notifications
642     @type client: callable
643     @param name: Partial or full event name
644     @type name: string
645     '''
646     try:
647       # look for an event name in our event tree dictionary
648       events = constants.EVENT_TREE[name]
649     except KeyError:
650       # if the event name doesn't exist, it's a leaf event meaning there are
651       # no subtypes for that event
652       # add this client to the list of clients already in the dictionary 
653       # using the event name as the key; if there are no clients yet for this 
654       # event, insert an empty list into the dictionary before appending 
655       # the client
656       et = event.EventType(name)
657       clients = self.clients.setdefault(et.name, [])
658       try:
659         # if this succeeds, this client is already registered for the given
660         # event type, so ignore the request
661         clients.index(client)
662       except ValueError:
663         # else register the client
664         clients.append(client)
665         self._registerObserver(name)
666     else:
667         # if the event name does exist in the tree, there are subevents for
668         # this event; loop through them calling this method again to get to
669         # the leaf events
670         for e in events:
671           self._registerClients(client, e)
672       
673   def _unregisterClients(self, client, name):
674     '''
675     Internal method that recursively unassociates a client with AT-SPI event 
676     names. Allows a client to incompletely specify an event name in order to 
677     unregister for subevents without specifying their full names manually.
678     
679     @param client: Client callback to receive event notifications
680     @type client: callable
681     @param name: Partial or full event name
682     @type name: string
683     '''
684     missed = False
685     try:
686       # look for an event name in our event tree dictionary
687       events = constants.EVENT_TREE[name]
688     except KeyError:
689       try:
690         # if the event name doesn't exist, it's a leaf event meaning there are
691         # no subtypes for that event
692         # get the list of registered clients and try to remove the one provided
693         et = event.EventType(name)
694         clients = self.clients[et.name]
695         clients.remove(client)
696         self._unregisterObserver(name)
697       except (ValueError, KeyError):
698         # ignore any exceptions indicating the client is not registered
699         missed = True
700       return missed
701     # if the event name does exist in the tree, there are subevents for this 
702     # event; loop through them calling this method again to get to the leaf
703     # events
704     for e in events:
705       missed |= self._unregisterClients(client, e)
706     return missed
707   
708   def _registerObserver(self, name):
709     '''    
710     Creates a new L{_Observer} to watch for events of the given type or
711     returns the existing observer if one is already registered. One
712     L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
713     any event name not found in the tree.
714    
715     @param name: Raw name of the event to observe
716     @type name: string
717     @return: L{_Observer} object that is monitoring the event
718     @rtype: L{_Observer}
719     '''
720     et = event.EventType(name)
721     try:
722       # see if an observer already exists for this event
723       ob = self.observers[et.name]
724     except KeyError:
725       # build a new observer if one does not exist
726       ob = _EventObserver(self)
727       # we have to register for the raw name because it may be different from
728       # the parsed name determined by EventType (e.g. trailing ':' might be 
729       # missing)
730       ob.register(self.reg, name)
731       self.observers[et.name] = ob
732     # increase our client ref count so we know someone new is watching for the 
733     # event
734     ob.clientRef()
735     return ob
736     
737   def _unregisterObserver(self, name):
738     '''    
739     Destroys an existing L{_Observer} for the given event type only if no
740     clients are registered for the events it is monitoring.
741     
742     @param name: Name of the event to observe
743     @type name: string
744     @raise KeyError: When an observer for the given event is not regist
745     '''
746     et = event.EventType(name)
747     # see if an observer already exists for this event
748     ob = self.observers[et.name]
749     ob.clientUnref()
750     if ob.getClientRefCount() == 0:
751       ob.unregister(self.reg, name)
752       del self.observers[et.name]