1 #Copyright (C) 2008 Codethink Ltd
2 #copyright: Copyright (c) 2005, 2007 IBM Corporation
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.
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.
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}
20 #authors: Peter Parente, Mark Doffman
24 import gobject as _gobject
26 from desktop import Desktop as _Desktop
28 from event import EventType as _EventType
29 from event import event_type_to_signal_reciever as _event_type_to_signal_reciever
31 from applicationcache import TestApplicationCache, ApplicationCache
33 from dbus.mainloop.glib import DBusGMainLoop as _DBusGMainLoop
35 from Queue import Queue
36 from deviceevent import *
37 from deviceevent import _TestDeviceEventController
39 _DBusGMainLoop(set_as_default=True)
41 #------------------------------------------------------------------------------
43 class _Registry(object):
45 Wraps the Accessibility.Registry to provide more Pythonic registration for
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
53 @ivar async: Should event dispatch to local listeners be decoupled from event
54 receiving from the registry?
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
70 Stores a reference to the AT-SPI registry. Gets and stores a reference
71 to the DeviceEventController.
73 @param reg: Reference to the AT-SPI registry daemon
74 @type reg: Accessibility.Registry
76 self._bus = _dbus.SessionBus()
79 if "ATSPI_TEST_APP_NAME" in _os.environ.keys():
80 app_name = _os.environ["ATSPI_TEST_APP_NAME"]
83 self._app_cache = TestApplicationCache(self, self._bus, app_name)
84 self.dev = _TestDeviceEventController()
86 self._app_cache = ApplicationCache(self, self._bus)
87 self.dev = DeviceEventController(self._bus)
89 self._event_listeners = {}
91 # All of this special casing is for the 'faked'
92 # events caused by cache updates.
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 = {}
104 self.deviceClients = {}
108 @return: This instance of the registry
116 This is the accessible application cache through which
117 all accessible objects are accessed.
119 return self._app_cache
121 def start(self, async=False, gil=True):
123 Enter the main loop to start receiving and dispatching events.
125 @param async: Should event dispatch be asynchronous (decoupled) from
126 event receiving from the AT-SPI registry?
128 @param gil: Add an idle callback which releases the Python GIL for a few
129 milliseconds to allow other threads to run? Necessary if other threads
130 will be used in this process.
131 Note - No Longer used.
134 self._loop = _gobject.MainLoop()
137 except KeyboardInterrupt:
140 def stop(self, *args):
141 """Quits the main loop."""
145 def getDesktopCount(self):
147 Gets the number of available desktops.
149 @return: Number of desktops
154 def getDesktop(self, i):
156 Gets a reference to the i-th desktop.
158 @param i: Which desktop to get
160 @return: Desktop reference
161 @rtype: Accessibility.Desktop
163 return _Desktop(self._app_cache)
165 # -------------------------------------------------------------------------------
167 def _callClients(self, register, event):
168 for client in register.keys():
171 def _notifyNameChange(self, event):
172 self._callClients(self._name_listeners, event)
174 def _notifyDescriptionChange(self, event):
175 self._callClients(self._description_listeners, event)
177 def _notifyParentChange(self, event):
178 self._callClients(self._parent_listeners, event)
180 def _notifyChildrenChange(self, event):
181 self._callClients(self._children_changed_listeners, event)
183 def _registerFake(self, type, register, client, *names):
185 Registers a client from a register of clients
186 for 'Fake' events emitted by the cache.
189 registered = register[client]
192 register[client] = registered
195 new_type = _EventType(name)
196 if new_type.is_subtype(type):
197 registered.append(new_type.name)
199 def _deregisterFake(self, type, register, client, *names):
201 Deregisters a client from a register of clients
202 for 'Fake' events emitted by the cache.
205 registered = register[client]
210 remove_type = _EventType(name)
212 for i in range(0, len(registered) - 1):
213 type_name = registered[i]
214 registered_type = _EventType(type_name)
216 if remove_type.is_subtype(registered_type):
220 del(register[client])
222 # -------------------------------------------------------------------------------
224 def registerEventListener(self, client, *names):
226 Registers a new client callback for the given event names. Supports
227 registration for all subevents if only partial event name is specified.
228 Do not include a trailing colon.
230 For example, 'object' will register for all object events,
231 'object:property-change' will register for all property change events,
232 and 'object:property-change:accessible-parent' will register only for the
233 parent property change event.
235 Registered clients will not be automatically removed when the client dies.
236 To ensure the client is properly garbage collected, call
237 L{deregisterEventListener}.
239 @param client: Callable to be invoked when the event occurs
240 @type client: callable
241 @param names: List of full or partial event names
242 @type names: list of string
245 registered = self._event_listeners[client]
248 self._event_listeners[client] = registered
251 new_type = _EventType(name)
252 registered.append((new_type.name,
253 _event_type_to_signal_reciever(self._bus, self._app_cache, client, new_type)))
255 self._registerFake(self._name_type, self._name_listeners, client, *names)
256 self._registerFake(self._description_type, self._description_listeners, client, *names)
257 self._registerFake(self._parent_type, self._parent_listeners, client, *names)
258 self._registerFake(self._children_changed_type, self._children_changed_listeners, client, *names)
260 def deregisterEventListener(self, client, *names):
262 Unregisters an existing client callback for the given event names. Supports
263 unregistration for all subevents if only partial event name is specified.
264 Do not include a trailing colon.
266 This method must be called to ensure a client registered by
267 L{registerEventListener} is properly garbage collected.
269 @param client: Client callback to remove
270 @type client: callable
271 @param names: List of full or partial event names
272 @type names: list of string
273 @return: Were event names specified for which the given client was not
278 registered = self._event_listeners[client]
280 # Presumably if were trying to deregister a client with
281 # no names then the return type is always true.
287 remove_type = _EventType(name)
288 for i in range (0, len(registered)):
289 type_name, signal_match = registered[i]
290 registered_type = _EventType(type_name)
292 if remove_type.is_subtype(registered_type):
293 signal_match.remove()
299 del(self._event_listeners[client])
301 #TODO Do these account for missing also?
302 self._deregisterFake(self._name_type, self._name_listeners, client, *names)
303 self._deregisterFake(self._description_type, self._description_listeners, client, *names)
304 self._deregisterFake(self._parent_type, self._parent_listeners, client, *names)
305 self._deregisterFake(self._children_changed_type, self._children_changed_listeners, client, *names)
309 # -------------------------------------------------------------------------------
311 def registerKeystrokeListener(self,
315 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT),
320 Registers a listener for key stroke events.
322 @param client: Callable to be invoked when the event occurs
323 @type client: callable
324 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
325 to indicate all keys.
326 @type key_set: list of integer
327 @param mask: When the mask is None, the codes in the key_set will be
328 monitored only when no modifier is held. When the mask is an
329 integer, keys in the key_set will be monitored only when the modifiers in
330 the mask are held. When the mask is an iterable over more than one
331 integer, keys in the key_set will be monitored when any of the modifier
332 combinations in the set are held.
333 @type mask: integer, iterable, None
334 @param kind: Kind of events to watch, KEY_PRESSED_EVENT or
337 @param synchronous: Should the callback notification be synchronous, giving
338 the client the chance to consume the event?
339 @type synchronous: boolean
340 @param preemptive: Should the callback be allowed to preempt / consume the
342 @type preemptive: boolean
343 @param global_: Should callback occur even if an application not supporting
344 AT-SPI is in the foreground? (requires xevie)
345 @type global_: boolean
348 # see if we already have an observer for this client
349 ob = self.deviceClients[client]
351 # create a new device observer for this client
352 ob = KeyboardDeviceEventListener(self, synchronous, preemptive, global_)
353 # store the observer to client mapping, and the inverse
354 self.deviceClients[ob] = client
355 self.deviceClients[client] = ob
357 # None means all modifier combinations
358 mask = utils.allModifiers()
359 # register for new keystrokes on the observer
360 ob.register(self.dev, key_set, mask, kind)
362 def deregisterKeystrokeListener(self,
366 kind=(KEY_PRESSED_EVENT, KEY_RELEASED_EVENT)):
368 Deregisters a listener for key stroke events.
370 @param client: Callable to be invoked when the event occurs
371 @type client: callable
372 @param key_set: Set of hardware key codes to stop monitoring. Leave empty
373 to indicate all keys.
374 @type key_set: list of integer
375 @param mask: When the mask is None, the codes in the key_set will be
376 monitored only when no modifier is held. When the mask is an
377 integer, keys in the key_set will be monitored only when the modifiers in
378 the mask are held. When the mask is an iterable over more than one
379 integer, keys in the key_set will be monitored when any of the modifier
380 combinations in the set are held.
381 @type mask: integer, iterable, None
382 @param kind: Kind of events to stop watching, KEY_PRESSED_EVENT or
385 @raise KeyError: When the client isn't already registered for events
387 # see if we already have an observer for this client
388 ob = self.deviceClients[client]
390 # None means all modifier combinations
391 mask = utils.allModifiers()
392 # register for new keystrokes on the observer
393 ob.unregister(self.dev, key_set, mask, kind)
395 def handleDeviceEvent(self, event, ob):
397 Dispatches L{event.DeviceEvent}s to registered clients. Clients are called
398 in the order they were registered for the given AT-SPI event. If any
399 client returns True, callbacks cease for the event for clients of this registry
400 instance. Clients of other registry instances and clients in other processes may
401 be affected depending on the values of synchronous and preemptive used when invoking
402 L{registerKeystrokeListener}.
404 @note: Asynchronous dispatch of device events is not supported.
406 @param event: AT-SPI device event
407 @type event: L{event.DeviceEvent}
408 @param ob: Observer that received the event
409 @type ob: L{KeyboardDeviceEventListener}
411 @return: Should the event be consumed (True) or allowed to pass on to other
412 AT-SPI observers (False)?
416 # try to get the client registered for this event type
417 client = self.clients[ob]
419 # client may have unregistered recently, ignore event
421 # make the call to the client
423 return client(event) or event.consume
425 # print the exception, but don't let it stop notification
426 traceback.print_exc()
428 # -------------------------------------------------------------------------------
430 def pumpQueuedEvents (self):
432 No Longer needed all application events are asyncronous.
436 def flushEvents (self):
438 No Longer needed all application events are asyncronous.
442 # -------------------------------------------------------------------------------
444 def generateKeyboardEvent(self, keycode, keysym, kind):
446 Generates a keyboard event. One of the keycode or the keysym parameters
447 should be specified and the other should be None. The kind parameter is
448 required and should be one of the KEY_PRESS, KEY_RELEASE, KEY_PRESSRELEASE,
449 KEY_SYM, or KEY_STRING.
451 @param keycode: Hardware keycode or None
452 @type keycode: integer
453 @param keysym: Symbolic key string or None
455 @param kind: Kind of event to synthesize
459 self.dev.generateKeyboardEvent(keycode, '', kind)
461 self.dev.generateKeyboardEvent(None, keysym, kind)
463 def generateMouseEvent(self, x, y, name):
465 Generates a mouse event at the given absolute x and y coordinate. The kind
466 of event generated is specified by the name. For example, MOUSE_B1P
467 (button 1 press), MOUSE_REL (relative motion), MOUSE_B3D (butten 3
470 @param x: Horizontal coordinate, usually left-hand oriented
472 @param y: Vertical coordinate, usually left-hand oriented
474 @param name: Name of the event to generate
477 self.dev.generateMouseEvent(x, y, name)