ef285e13eef740ea12fef001112ecc0aba3650a4
[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     
368   def getDesktopCount(self):
369     '''
370     Gets the number of available desktops.
371     
372     @return: Number of desktops
373     @rtype: integer
374     @raise LookupError: When the count cannot be retrieved
375     '''
376     try:
377       return self.reg.getDesktopCount()
378     except Exception:
379       raise LookupError
380     
381   def getDesktop(self, i):
382     '''
383     Gets a reference to the i-th desktop.
384     
385     @param i: Which desktop to get
386     @type i: integer
387     @return: Desktop reference
388     @rtype: Accessibility.Desktop
389     @raise LookupError: When the i-th desktop cannot be retrieved
390     '''
391     try:
392       return self.reg.getDesktop(i)
393     except Exception, e:
394       raise LookupError(e)
395     
396   def registerEventListener(self, client, *names):
397     '''
398     Registers a new client callback for the given event names. Supports 
399     registration for all subevents if only partial event name is specified.
400     Do not include a trailing colon.
401     
402     For example, 'object' will register for all object events, 
403     'object:property-change' will register for all property change events,
404     and 'object:property-change:accessible-parent' will register only for the
405     parent property change event.
406     
407     Registered clients will not be automatically removed when the client dies.
408     To ensure the client is properly garbage collected, call 
409     L{deregisterEventListener}.
410
411     @param client: Callable to be invoked when the event occurs
412     @type client: callable
413     @param names: List of full or partial event names
414     @type names: list of string
415     '''
416     for name in names:
417       # store the callback for each specific event name
418       self._registerClients(client, name)
419
420   def deregisterEventListener(self, client, *names):
421     '''
422     Unregisters an existing client callback for the given event names. Supports 
423     unregistration for all subevents if only partial event name is specified.
424     Do not include a trailing colon.
425     
426     This method must be called to ensure a client registered by
427     L{registerEventListener} is properly garbage collected.
428
429     @param client: Client callback to remove
430     @type client: callable
431     @param names: List of full or partial event names
432     @type names: list of string
433     @return: Were event names specified for which the given client was not
434       registered?
435     @rtype: boolean
436     '''
437     missed = False
438     for name in names:
439       # remove the callback for each specific event name
440       missed |= self._unregisterClients(client, name)
441     return missed
442
443   def registerKeystrokeListener(self, client, key_set=[], mask=0, 
444                                 kind=(constants.KEY_PRESSED_EVENT, 
445                                       constants.KEY_RELEASED_EVENT),
446                                 synchronous=True, preemptive=True, 
447                                 global_=False):
448     '''
449     Registers a listener for key stroke events.
450     
451     @param client: Callable to be invoked when the event occurs
452     @type client: callable
453     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
454       to indicate all keys.
455     @type key_set: list of integer
456     @param mask: When the mask is None, the codes in the key_set will be 
457       monitored only when no modifier is held. When the mask is an 
458       integer, keys in the key_set will be monitored only when the modifiers in
459       the mask are held. When the mask is an iterable over more than one 
460       integer, keys in the key_set will be monitored when any of the modifier
461       combinations in the set are held.
462     @type mask: integer, iterable, None
463     @param kind: Kind of events to watch, KEY_PRESSED_EVENT or 
464       KEY_RELEASED_EVENT.
465     @type kind: list
466     @param synchronous: Should the callback notification be synchronous, giving
467       the client the chance to consume the event?
468     @type synchronous: boolean
469     @param preemptive: Should the callback be allowed to preempt / consume the
470       event?
471     @type preemptive: boolean
472     @param global_: Should callback occur even if an application not supporting
473       AT-SPI is in the foreground? (requires xevie)
474     @type global_: boolean
475     '''
476     try:
477       # see if we already have an observer for this client
478       ob = self.clients[client]
479     except KeyError:
480       # create a new device observer for this client
481       ob = _DeviceObserver(self, synchronous, preemptive, global_)
482       # store the observer to client mapping, and the inverse
483       self.clients[ob] = client
484       self.clients[client] = ob
485     if mask is None:
486       # None means all modifier combinations
487       mask = utils.allModifiers()
488     # register for new keystrokes on the observer
489     ob.register(self.dev, key_set, mask, kind)
490
491   def deregisterKeystrokeListener(self, client, key_set=[], mask=0, 
492                                   kind=(constants.KEY_PRESSED_EVENT, 
493                                         constants.KEY_RELEASED_EVENT)):
494     '''
495     Deregisters a listener for key stroke events.
496     
497     @param client: Callable to be invoked when the event occurs
498     @type client: callable
499     @param key_set: Set of hardware key codes to stop monitoring. Leave empty
500       to indicate all keys.
501     @type key_set: list of integer
502     @param mask: When the mask is None, the codes in the key_set will be 
503       monitored only when no modifier is held. When the mask is an 
504       integer, keys in the key_set will be monitored only when the modifiers in
505       the mask are held. When the mask is an iterable over more than one 
506       integer, keys in the key_set will be monitored when any of the modifier
507       combinations in the set are held.
508     @type mask: integer, iterable, None
509     @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or 
510       KEY_RELEASED_EVENT.
511     @type kind: list
512     @raise KeyError: When the client isn't already registered for events
513     '''
514     # see if we already have an observer for this client
515     ob = self.clients[client]
516     if mask is None:
517       # None means all modifier combinations
518       mask = utils.allModifiers()
519     # register for new keystrokes on the observer
520     ob.unregister(self.dev, key_set, mask, kind)
521
522   def generateKeyboardEvent(self, keycode, keysym, kind):
523     '''
524     Generates a keyboard event. One of the keycode or the keysym parameters
525     should be specified and the other should be None. The kind parameter is 
526     required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
527     KEY_SYM, or KEY_STRING.
528     
529     @param keycode: Hardware keycode or None
530     @type keycode: integer
531     @param keysym: Symbolic key string or None
532     @type keysym: string
533     @param kind: Kind of event to synthesize
534     @type kind: integer
535     '''
536     if keysym is None:
537       self.dev.generateKeyboardEvent(keycode, '', kind)
538     else:
539       self.dev.generateKeyboardEvent(None, keysym, kind)
540   
541   def generateMouseEvent(self, x, y, name):
542     '''
543     Generates a mouse event at the given absolute x and y coordinate. The kind
544     of event generated is specified by the name. For example, MOUSE_B1P 
545     (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3 
546     double-click).
547     
548     @param x: Horizontal coordinate, usually left-hand oriented
549     @type x: integer
550     @param y: Vertical coordinate, usually left-hand oriented
551     @type y: integer
552     @param name: Name of the event to generate
553     @type name: string
554     '''
555     self.dev.generateMouseEvent(x, y, name)
556     
557   def handleDeviceEvent(self, event, ob):
558     '''
559     Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
560     in the order they were registered for the given AT-SPI event. If any
561     client returns True, callbacks cease for the event for clients of this registry 
562     instance. Clients of other registry instances and clients in other processes may 
563     be affected depending on the values of synchronous and preemptive used when invoking
564     L{registerKeystrokeListener}. 
565     
566     @note: Asynchronous dispatch of device events is not supported.
567     
568     @param event: AT-SPI device event
569     @type event: L{event.DeviceEvent}
570     @param ob: Observer that received the event
571     @type ob: L{_DeviceObserver}
572
573     @return: Should the event be consumed (True) or allowed to pass on to other
574       AT-SPI observers (False)?
575     @rtype: boolean
576     '''
577     try:
578       # try to get the client registered for this event type
579       client = self.clients[ob]
580     except KeyError:
581       # client may have unregistered recently, ignore event
582       return False
583     # make the call to the client
584     try:
585       return client(event) or event.consume
586     except Exception:
587       # print the exception, but don't let it stop notification
588       traceback.print_exc()
589  
590   def handleEvent(self, event):
591     '''    
592     Handles an AT-SPI event by either queuing it for later dispatch when the
593     L{Registry.async} flag is set, or dispatching it immediately.
594
595     @param event: AT-SPI event
596     @type event: L{event.Event}
597     '''
598     if self.async:
599       # queue for now
600       self.queue.put_nowait(event)
601     else:
602       # dispatch immediately
603       self._dispatchEvent(event)
604
605   def _dispatchEvent(self, event):
606     '''
607     Dispatches L{event.Event}s to registered clients. Clients are called in
608     the order they were registered for the given AT-SPI event. If any client
609     returns True, callbacks cease for the event for clients of this registry 
610     instance. Clients of other registry instances and clients in other processes 
611     are unaffected.
612
613     @param event: AT-SPI event
614     @type event: L{event.Event}
615     '''
616     et = event.type
617     try:
618       # try to get the client registered for this event type
619       clients = self.clients[et.name]
620     except KeyError:
621       try:
622         # we may not have registered for the complete subtree of events
623         # if our tree does not list all of a certain type (e.g.
624         # object:state-changed:*); try again with klass and major only
625         if et.detail is not None:
626           # Strip the 'detail' field.
627           clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
628         elif et.minor is not None:
629           # The event could possibly be object:state-changed:*.
630           clients = self.clients['%s:%s' % (et.klass, et.major)]
631       except KeyError:
632         # client may have unregistered recently, ignore event
633         return
634     # make the call to each client
635     consume = False
636     for client in clients:
637       try:
638         consume = client(event) or False
639       except Exception:
640         # print the exception, but don't let it stop notification
641         traceback.print_exc()
642       if consume or event.consume:
643         # don't allow further processing if a client returns True
644         break
645
646   def _registerClients(self, client, name):
647     '''
648     Internal method that recursively associates a client with AT-SPI event 
649     names. Allows a client to incompletely specify an event name in order to 
650     register for subevents without specifying their full names manually.
651     
652     @param client: Client callback to receive event notifications
653     @type client: callable
654     @param name: Partial or full event name
655     @type name: string
656     '''
657     try:
658       # look for an event name in our event tree dictionary
659       events = constants.EVENT_TREE[name]
660     except KeyError:
661       # if the event name doesn't exist, it's a leaf event meaning there are
662       # no subtypes for that event
663       # add this client to the list of clients already in the dictionary 
664       # using the event name as the key; if there are no clients yet for this 
665       # event, insert an empty list into the dictionary before appending 
666       # the client
667       et = event.EventType(name)
668       clients = self.clients.setdefault(et.name, [])
669       try:
670         # if this succeeds, this client is already registered for the given
671         # event type, so ignore the request
672         clients.index(client)
673       except ValueError:
674         # else register the client
675         clients.append(client)
676         self._registerObserver(name)
677     else:
678         # if the event name does exist in the tree, there are subevents for
679         # this event; loop through them calling this method again to get to
680         # the leaf events
681         for e in events:
682           self._registerClients(client, e)
683       
684   def _unregisterClients(self, client, name):
685     '''
686     Internal method that recursively unassociates a client with AT-SPI event 
687     names. Allows a client to incompletely specify an event name in order to 
688     unregister for subevents without specifying their full names manually.
689     
690     @param client: Client callback to receive event notifications
691     @type client: callable
692     @param name: Partial or full event name
693     @type name: string
694     '''
695     missed = False
696     try:
697       # look for an event name in our event tree dictionary
698       events = constants.EVENT_TREE[name]
699     except KeyError:
700       try:
701         # if the event name doesn't exist, it's a leaf event meaning there are
702         # no subtypes for that event
703         # get the list of registered clients and try to remove the one provided
704         et = event.EventType(name)
705         clients = self.clients[et.name]
706         clients.remove(client)
707         self._unregisterObserver(name)
708       except (ValueError, KeyError):
709         # ignore any exceptions indicating the client is not registered
710         missed = True
711       return missed
712     # if the event name does exist in the tree, there are subevents for this 
713     # event; loop through them calling this method again to get to the leaf
714     # events
715     for e in events:
716       missed |= self._unregisterClients(client, e)
717     return missed
718   
719   def _registerObserver(self, name):
720     '''    
721     Creates a new L{_Observer} to watch for events of the given type or
722     returns the existing observer if one is already registered. One
723     L{_Observer} is created for each leaf in the L{constants.EVENT_TREE} or
724     any event name not found in the tree.
725    
726     @param name: Raw name of the event to observe
727     @type name: string
728     @return: L{_Observer} object that is monitoring the event
729     @rtype: L{_Observer}
730     '''
731     et = event.EventType(name)
732     try:
733       # see if an observer already exists for this event
734       ob = self.observers[et.name]
735     except KeyError:
736       # build a new observer if one does not exist
737       ob = _EventObserver(self)
738       # we have to register for the raw name because it may be different from
739       # the parsed name determined by EventType (e.g. trailing ':' might be 
740       # missing)
741       ob.register(self.reg, name)
742       self.observers[et.name] = ob
743     # increase our client ref count so we know someone new is watching for the 
744     # event
745     ob.clientRef()
746     return ob
747     
748   def _unregisterObserver(self, name):
749     '''    
750     Destroys an existing L{_Observer} for the given event type only if no
751     clients are registered for the events it is monitoring.
752     
753     @param name: Name of the event to observe
754     @type name: string
755     @raise KeyError: When an observer for the given event is not regist
756     '''
757     et = event.EventType(name)
758     # see if an observer already exists for this event
759     ob = self.observers[et.name]
760     ob.clientUnref()
761     if ob.getClientRefCount() == 0:
762       ob.unregister(self.reg, name)
763       del self.observers[et.name]