2008-11-13 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / pyatspi / registry.py
1 #Copyright (C) 2008 Codethink Ltd
2 #copyright: Copyright (c) 2005, 2007 IBM Corporation
3
4 #This library is free software; you can redistribute it and/or
5 #modify it under the terms of the GNU Lesser General Public
6 #License version 2 as published by the Free Software Foundation.
7
8 #This program is distributed in the hope that it will be useful,
9 #but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #GNU General Public License for more details.
12 #You should have received a copy of the GNU Lesser General Public License
13 #along with this program; if not, write to the Free Software
14 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15
16 #Portions of this code originally licensed and copyright (c) 2005, 2007
17 #IBM Corporation under the BSD license, available at
18 #U{http://www.opensource.org/licenses/bsd-license.php}
19
20 #authors: Peter Parente, Mark Doffman
21
22 import os as _os
23 import dbus as _dbus
24 import gobject as _gobject
25
26 from desktop import Desktop as _Desktop
27
28 from event import EventType as _EventType
29 from event import event_type_to_signal_reciever as _event_type_to_signal_reciever
30
31 from applicationcache import TestApplicationCache, ApplicationCache
32
33 from dbus.mainloop.glib import DBusGMainLoop as _DBusGMainLoop
34
35 from Queue import Queue
36 from deviceevent import *
37 from deviceevent import _TestDeviceEventController
38
39 _DBusGMainLoop(set_as_default=True)
40
41 #------------------------------------------------------------------------------
42
43 class _Registry(object):
44         """
45         Wraps the Accessibility.Registry to provide more Pythonic registration for
46         events.
47
48         This object should be treated as a singleton, but such treatment is not
49         enforced. You can construct another instance of this object and give it a
50         reference to the Accessibility.Registry singleton. Doing so is harmless and
51         has no point.
52
53         @ivar async: Should event dispatch to local listeners be decoupled from event
54                 receiving from the registry?
55         @type async: boolean
56         @ivar reg: Reference to the real, wrapped registry object
57         @type reg: Accessibility.Registry
58         @ivar dev: Reference to the device controller
59         @type dev: Accessibility.DeviceEventController
60         @ivar queue: Queue of events awaiting local dispatch
61         @type queue: Queue.Queue
62         @ivar clients: Map of event names to client listeners
63         @type clients: dictionary
64         @ivar observers: Map of event names to AT-SPI L{_Observer} objects
65         @type observers: dictionary
66         """
67
68         def __init__(self):
69                 """
70                 Stores a reference to the AT-SPI registry. Gets and stores a reference
71                 to the DeviceEventController.
72
73                 @param reg: Reference to the AT-SPI registry daemon
74                 @type reg: Accessibility.Registry
75                 """
76                 self._bus = _dbus.SessionBus()
77
78                 app_name = None
79                 if "ATSPI_TEST_APP_NAME" in _os.environ.keys():
80                         app_name = _os.environ["ATSPI_TEST_APP_NAME"]
81
82                 if app_name:
83                         self._appcache = TestApplicationCache(self, self._bus, app_name)
84                         self.dev = _TestDeviceEventController()
85                 else:
86                         self._appcache = ApplicationCache(self, self._bus)
87                         self.dev = DeviceEventController(self._bus)
88
89                 self._event_listeners = {}
90
91                 # All of this special casing is for the 'faked'
92                 # events caused by cache updates.
93
94                 self._name_type = _EventType("object:property-change:name")
95                 self._name_listeners = {}
96                 self._description_type = _EventType("object:property-change:description")
97                 self._description_listeners = {}
98                 self._parent_type = _EventType("object:property-change:parent")
99                 self._parent_listeners = {}
100                 self._children_changed_type = _EventType("object:children-changed")
101                 self._children_changed_listeners = {}
102
103                 self.queue = Queue()
104                 self.clients = {}
105
106         def __call__(self):
107                 """
108                 @return: This instance of the registry
109                 @rtype: L{Registry}
110                 """
111                 return self
112
113         def start(self, async=False, gil=True):
114                 """
115                 Enter the main loop to start receiving and dispatching events.
116
117                 @param async: Should event dispatch be asynchronous (decoupled) from 
118                         event receiving from the AT-SPI registry?
119                 @type async: boolean
120                 @param gil: Add an idle callback which releases the Python GIL for a few
121                         milliseconds to allow other threads to run? Necessary if other threads
122                         will be used in this process.
123                         Note - No Longer used.
124                 @type gil: boolean
125                 """
126                 self._loop = _gobject.MainLoop()
127                 try:
128                         self._loop.run()
129                 except KeyboardInterrupt:
130                         pass
131
132         def stop(self, *args):
133                 """Quits the main loop."""
134                 self._loop.quit()
135                 self.flushEvents()
136
137         def getDesktopCount(self):
138                 """
139                 Gets the number of available desktops.
140
141                 @return: Number of desktops
142                 @rtype: integer
143                 """
144                 return 1
145
146         def getDesktop(self, i):
147                 """
148                 Gets a reference to the i-th desktop.
149
150                 @param i: Which desktop to get
151                 @type i: integer
152                 @return: Desktop reference
153                 @rtype: Accessibility.Desktop
154                 """
155                 return _Desktop(self._appcache)
156
157         def _callClients(self, register, event):
158                 for client in register.keys():
159                         client(event)
160
161         def _notifyNameChange(self, event):
162                 self._callClients(self._name_listeners, event)
163
164         def _notifyDescriptionChange(self, event):
165                 self._callClients(self._description_listeners, event)
166
167         def _notifyParentChange(self, event):
168                 self._callClients(self._parent_listeners, event)
169
170         def _notifyChildrenChange(self, event):
171                 self._callClients(self._children_changed_listeners, event)
172
173         def _registerFake(self, type, register, client, *names):
174                 """
175                 Registers a client from a register of clients
176                 for 'Fake' events emitted by the cache.
177                 """
178                 try:
179                         registered = register[client]
180                 except KeyError:
181                         registered = []
182                         register[client] = registered
183
184                 for name in names:
185                         new_type = _EventType(name)
186                         if new_type.is_subtype(type):
187                                 registered.append(new_type.name)
188
189         def _deregisterFake(self, type, register, client, *names):
190                 """
191                 Deregisters a client from a register of clients
192                 for 'Fake' events emitted by the cache.
193                 """
194                 try:
195                         registered = register[client]
196                 except KeyError:
197                         return True
198
199                 for name in names:
200                         remove_type = _EventType(name)
201
202                         for i in range(0, len(registered) - 1):
203                                 type_name = registered[i]
204                                 registered_type = _EventType(type_name)
205
206                                 if remove_type.is_subtype(registered_type):
207                                         del(registered[i])
208
209                 if registered == []:
210                         del(register[client])
211
212         def registerEventListener(self, client, *names):
213                 """
214                 Registers a new client callback for the given event names. Supports 
215                 registration for all subevents if only partial event name is specified.
216                 Do not include a trailing colon.
217
218                 For example, 'object' will register for all object events, 
219                 'object:property-change' will register for all property change events,
220                 and 'object:property-change:accessible-parent' will register only for the
221                 parent property change event.
222
223                 Registered clients will not be automatically removed when the client dies.
224                 To ensure the client is properly garbage collected, call 
225                 L{deregisterEventListener}.
226
227                 @param client: Callable to be invoked when the event occurs
228                 @type client: callable
229                 @param names: List of full or partial event names
230                 @type names: list of string
231                 """
232                 try:
233                         registered = self._event_listeners[client]
234                 except KeyError:
235                         registered = []
236                         self._event_listeners[client] = registered
237
238                 for name in names:
239                         new_type = _EventType(name)
240                         registered.append((new_type.name,
241                                            _event_type_to_signal_reciever(self._bus, self._appcache, client, new_type)))
242
243                 self._registerFake(self._name_type, self._name_listeners, client, *names)
244                 self._registerFake(self._description_type, self._description_listeners, client, *names)
245                 self._registerFake(self._parent_type, self._parent_listeners, client, *names)
246                 self._registerFake(self._children_changed_type, self._children_changed_listeners, client, *names)
247
248         def deregisterEventListener(self, client, *names):
249                 """
250                 Unregisters an existing client callback for the given event names. Supports 
251                 unregistration for all subevents if only partial event name is specified.
252                 Do not include a trailing colon.
253
254                 This method must be called to ensure a client registered by
255                 L{registerEventListener} is properly garbage collected.
256
257                 @param client: Client callback to remove
258                 @type client: callable
259                 @param names: List of full or partial event names
260                 @type names: list of string
261                 @return: Were event names specified for which the given client was not
262                         registered?
263                 @rtype: boolean
264                 """
265                 try:
266                         registered = self._event_listeners[client]
267                 except KeyError:
268                         # Presumably if were trying to deregister a client with
269                         # no names then the return type is always true.
270                         return True
271
272                 missing = False
273
274                 for name in names:
275                         remove_type = _EventType(name)
276
277                         for i in range(0, len(registered) - 1):
278                                 (type_name, signal_match) = registered[i]
279                                 registered_type = _EventType(type_name)
280
281                                 if remove_type.is_subtype(registered_type):
282                                         signal_match.remove()
283                                         del(registered[i])
284                                 else:
285                                         missing = True
286
287                 if registered == []:
288                         del(self._event_listeners[client])
289
290                 #TODO Do these account for missing also?
291                 self._deregisterFake(self._name_type, self._name_listeners, client, *names)
292                 self._deregisterFake(self._description_type, self._description_listeners, client, *names)
293                 self._deregisterFake(self._parent_type, self._parent_listeners, client, *names)
294                 self._deregisterFake(self._children_changed_type, self._children_changed_listeners, client, *names)
295
296                 return missing
297
298         def registerKeystrokeListener(self,
299                                       client,
300                                       key_set=[],
301                                       mask=0,
302                                       kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT),
303                                       synchronous=True,
304                                       preemptive=True,
305                                       global_=False):
306                 """
307                 Registers a listener for key stroke events.
308
309                 @param client: Callable to be invoked when the event occurs
310                 @type client: callable
311                 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
312                         to indicate all keys.
313                 @type key_set: list of integer
314                 @param mask: When the mask is None, the codes in the key_set will be 
315                         monitored only when no modifier is held. When the mask is an 
316                         integer, keys in the key_set will be monitored only when the modifiers in
317                         the mask are held. When the mask is an iterable over more than one 
318                         integer, keys in the key_set will be monitored when any of the modifier
319                         combinations in the set are held.
320                 @type mask: integer, iterable, None
321                 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or 
322                         KEY_RELEASED_EVENT.
323                 @type kind: list
324                 @param synchronous: Should the callback notification be synchronous, giving
325                         the client the chance to consume the event?
326                 @type synchronous: boolean
327                 @param preemptive: Should the callback be allowed to preempt / consume the
328                         event?
329                 @type preemptive: boolean
330                 @param global_: Should callback occur even if an application not supporting
331                         AT-SPI is in the foreground? (requires xevie)
332                 @type global_: boolean
333                 """
334                 try:
335                         # see if we already have an observer for this client
336                         ob = self.clients[client]
337                 except KeyError:
338                         # create a new device observer for this client
339                         ob = KeyboardDeviceEventListener(self, synchronous, preemptive, global_)
340                         # store the observer to client mapping, and the inverse
341                         self.clients[ob] = client
342                         self.clients[client] = ob
343                 if mask is None:
344                         # None means all modifier combinations
345                         mask = utils.allModifiers()
346                 # register for new keystrokes on the observer
347                 ob.register(self.dev, key_set, mask, kind)
348
349         def deregisterKeystrokeListener(self,
350                                         client,
351                                         key_set=[],
352                                         mask=0,
353                                         kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT)):
354                 """
355                 Deregisters a listener for key stroke events.
356
357                 @param client: Callable to be invoked when the event occurs
358                 @type client: callable
359                 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
360                         to indicate all keys.
361                 @type key_set: list of integer
362                 @param mask: When the mask is None, the codes in the key_set will be 
363                         monitored only when no modifier is held. When the mask is an 
364                         integer, keys in the key_set will be monitored only when the modifiers in
365                         the mask are held. When the mask is an iterable over more than one 
366                         integer, keys in the key_set will be monitored when any of the modifier
367                         combinations in the set are held.
368                 @type mask: integer, iterable, None
369                 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or 
370                         KEY_RELEASED_EVENT.
371                 @type kind: list
372                 @raise KeyError: When the client isn't already registered for events
373                 """
374                 # see if we already have an observer for this client
375                 ob = self.clients[client]
376                 if mask is None:
377                         # None means all modifier combinations
378                         mask = utils.allModifiers()
379                 # register for new keystrokes on the observer
380                 ob.unregister(self.dev, key_set, mask, kind)
381
382         def generateKeyboardEvent(self, keycode, keysym, kind):
383                 """
384                 Generates a keyboard event. One of the keycode or the keysym parameters
385                 should be specified and the other should be None. The kind parameter is 
386                 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
387                 KEY_SYM, or KEY_STRING.
388
389                 @param keycode: Hardware keycode or None
390                 @type keycode: integer
391                 @param keysym: Symbolic key string or None
392                 @type keysym: string
393                 @param kind: Kind of event to synthesize
394                 @type kind: integer
395                 """
396                 if keysym is None:
397                         self.dev.generateKeyboardEvent(keycode, '', kind)
398                 else:
399                         self.dev.generateKeyboardEvent(None, keysym, kind)
400
401         def generateMouseEvent(self, x, y, name):
402                 """
403                 Generates a mouse event at the given absolute x and y coordinate. The kind
404                 of event generated is specified by the name. For example, MOUSE_B1P 
405                 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3 
406                 double-click).
407
408                 @param x: Horizontal coordinate, usually left-hand oriented
409                 @type x: integer
410                 @param y: Vertical coordinate, usually left-hand oriented
411                 @type y: integer
412                 @param name: Name of the event to generate
413                 @type name: string
414                 """
415                 self.dev.generateMouseEvent(x, y, name)
416
417         def handleDeviceEvent(self, event, ob):
418                 """
419                 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
420                 in the order they were registered for the given AT-SPI event. If any
421                 client returns True, callbacks cease for the event for clients of this registry 
422                 instance. Clients of other registry instances and clients in other processes may 
423                 be affected depending on the values of synchronous and preemptive used when invoking
424                 L{registerKeystrokeListener}. 
425
426                 @note: Asynchronous dispatch of device events is not supported.
427
428                 @param event: AT-SPI device event
429                 @type event: L{event.DeviceEvent}
430                 @param ob: Observer that received the event
431                 @type ob: L{KeyboardDeviceEventListener}
432
433                 @return: Should the event be consumed (True) or allowed to pass on to other
434                         AT-SPI observers (False)?
435                 @rtype: boolean
436                 """
437                 try:
438                         # try to get the client registered for this event type
439                         client = self.clients[ob]
440                 except KeyError:
441                         # client may have unregistered recently, ignore event
442                         return False
443                 # make the call to the client
444                 try:
445                         return client(event) or event.consume
446                 except Exception:
447                         # print the exception, but don't let it stop notification
448                         traceback.print_exc()
449  
450         def handleEvent(self, event):
451                 """
452                 Handles an AT-SPI event by either queuing it for later dispatch when the
453                 L{Registry.async} flag is set, or dispatching it immediately.
454
455                 @param event: AT-SPI event
456                 @type event: L{event.Event}
457                 """
458                 if self.async:
459                         # queue for now
460                         self.queue.put_nowait(event)
461                 else:
462                         # dispatch immediately
463                         self._dispatchEvent(event)
464
465         def _dispatchEvent(self, event):
466                 """
467                 Dispatches L{event.Event}s to registered clients. Clients are called in
468                 the order they were registered for the given AT-SPI event. If any client
469                 returns True, callbacks cease for the event for clients of this registry 
470                 instance. Clients of other registry instances and clients in other processes 
471                 are unaffected.
472
473                 @param event: AT-SPI event
474                 @type event: L{event.Event}
475                 """
476                 et = event.type
477                 try:
478                         # try to get the client registered for this event type
479                         clients = self.clients[et.name]
480                 except KeyError:
481                         try:
482                                 # we may not have registered for the complete subtree of events
483                                 # if our tree does not list all of a certain type (e.g.
484                                 # object:state-changed:*); try again with klass and major only
485                                 if et.detail is not None:
486                                         # Strip the 'detail' field.
487                                         clients = self.clients['%s:%s:%s' % (et.klass, et.major, et.minor)]
488                                 elif et.minor is not None:
489                                         # The event could possibly be object:state-changed:*.
490                                         clients = self.clients['%s:%s' % (et.klass, et.major)]
491                         except KeyError:
492                                 # client may have unregistered recently, ignore event
493                                 return
494                 # make the call to each client
495                 consume = False
496                 for client in clients:
497                         try:
498                                 consume = client(event) or False
499                         except Exception:
500                                 # print the exception, but don't let it stop notification
501                                 traceback.print_exc()
502                         if consume or event.consume:
503                                 # don't allow further processing if a client returns True
504                                 break
505
506         def flushEvents(self):
507                 """
508                 Flushes the event queue by destroying it and recreating it.
509                 """
510                 self.queue = Queue()
511
512         def pumpQueuedEvents(self, num=-1):
513                 """
514                 Provides asynch processing of events in the queue by executeing them with 
515                 _dispatchEvent() (as is done immediately when synch processing). 
516                 This method would normally be called from a main loop or idle function.
517
518                 @param num: Number of events to pump. If number is negative it pumps
519                 the entire queue. Default is -1.
520                 @type num: integer
521                 @return: True if queue is not empty after events were pumped.
522                 @rtype: boolean
523                 """
524                 if num < 0:
525                         # Dequeue as many events as currently in the queue.
526                         num = self.queue.qsize()
527                 for i in xrange(num):
528                         try:
529                                 # get next waiting event
530                                 event = self.queue.get_nowait()
531                         except Queue.Empty:
532                                 break
533                         self._dispatchEvent(event)
534
535                 return not self.queue.empty()