1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2005-2007 Sun Microsystems Inc. |
4 |
|
# |
5 |
|
# This library is free software; you can redistribute it and/or |
6 |
|
# modify it under the terms of the GNU Library General Public |
7 |
|
# License as published by the Free Software Foundation; either |
8 |
|
# version 2 of the License, or (at your option) any later version. |
9 |
|
# |
10 |
|
# This library is distributed in the hope that it will be useful, |
11 |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 |
|
# Library General Public License for more details. |
14 |
|
# |
15 |
|
# You should have received a copy of the GNU Library General Public |
16 |
|
# License along with this library; if not, write to the |
17 |
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 |
|
# Boston, MA 02111-1307, USA. |
19 |
|
|
20 |
1 |
"""Provides the interface to the AT-SPI Registry.""" |
21 |
|
|
22 |
1 |
__id__ = "$Id: atspi.py 2047 2007-02-23 01:52:09Z lmonsanto $" |
23 |
1 |
__version__ = "$Revision: 2047 $" |
24 |
1 |
__date__ = "$Date: 2007-02-22 17:52:09 -0800 (Thu, 22 Feb 2007) $" |
25 |
1 |
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc." |
26 |
1 |
__license__ = "LGPL" |
27 |
|
|
28 |
1 |
import signal |
29 |
1 |
import time |
30 |
|
|
31 |
1 |
import gobject |
32 |
1 |
gobject.threads_init() |
33 |
|
|
34 |
1 |
import bonobo |
35 |
1 |
import ORBit |
36 |
|
|
37 |
1 |
ORBit.load_typelib("Accessibility") |
38 |
|
|
39 |
|
# We will pass "orbit-io-thread" to initialize the ORB in threaded mode. |
40 |
|
# This should hopefully help address bug 319652: |
41 |
|
# |
42 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=319652 |
43 |
|
# |
44 |
|
# See also: |
45 |
|
# |
46 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=342614 |
47 |
|
# http://mail.gnome.org/archives/orbit-list/2005-December/msg00001.html |
48 |
|
# |
49 |
1 |
ORBit.CORBA.ORB_init(orb_id="orbit-io-thread") |
50 |
|
|
51 |
1 |
import Accessibility |
52 |
1 |
import Accessibility__POA |
53 |
|
|
54 |
1 |
import debug |
55 |
1 |
import rolenames |
56 |
1 |
import settings |
57 |
|
|
58 |
2 |
class Event: |
59 |
|
"""Converts the source of an event to an Accessible object. We |
60 |
|
need this since the event object we get from the atspi is |
61 |
|
read-only. So, we create this dummy event object to contain a copy |
62 |
|
of all the event members with the source converted to an |
63 |
|
Accessible. It is perfectly OK for event handlers to annotate this |
64 |
|
object with their own attributes. |
65 |
|
""" |
66 |
|
|
67 |
1 |
def __init__(self, e=None): |
68 |
20899 |
if e: |
69 |
20899 |
self.source = Accessible.makeAccessible(e.source) |
70 |
20899 |
self.type = e.type |
71 |
20899 |
self.detail1 = e.detail1 |
72 |
20899 |
self.detail2 = e.detail2 |
73 |
|
|
74 |
|
# If were talking to AT-SPI 1.7.0 or greater, we can get the |
75 |
|
# application information right away because it is tucked in |
76 |
|
# the EventDetails data new for 1.7.0. |
77 |
|
# |
78 |
20899 |
if e.any_data and (e.any_data.typecode().name) == "EventDetails": |
79 |
20899 |
details = e.any_data.value() |
80 |
20899 |
self.any_data = details.any_data |
81 |
20899 |
if self.source and details.host_application: |
82 |
20895 |
self.source.app = Accessible.makeAccessible( |
83 |
|
details.host_application) |
84 |
|
else: |
85 |
0 |
self.any_data = e.any_data |
86 |
|
|
87 |
|
# We need to make sure we reference any object that comes |
88 |
|
# to us via an any_data because we process events |
89 |
|
# asynchronously. If we don't reference them, we may |
90 |
|
# end up with OBJECT_NOT_EXIST errors. Please see |
91 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=395749 for |
92 |
|
# more information. |
93 |
|
# |
94 |
20899 |
if self.type == "object:active-descendant-changed": |
95 |
160 |
self.any_data = Accessible.makeAccessible( |
96 |
|
self.any_data.value()) |
97 |
20739 |
elif self.type == "object:text-changed:insert": |
98 |
712 |
self.any_data = self.any_data.value() |
99 |
20027 |
elif self.type == "object:text-changed:delete": |
100 |
37 |
self.any_data = self.any_data.value() |
101 |
|
else: |
102 |
0 |
self.source = None |
103 |
0 |
self.type = None |
104 |
0 |
self.detail1 = None |
105 |
0 |
self.detail2 = None |
106 |
0 |
self.any_data = None |
107 |
|
|
108 |
2 |
class Registry: |
109 |
|
"""Delegates to the actual AT-SPI Regisitry. |
110 |
|
""" |
111 |
|
|
112 |
|
# The "Borg" singleton model - ensures we're really |
113 |
|
# only connecting to the registry once. |
114 |
|
# |
115 |
1 |
__sharedState = {} |
116 |
1 |
__instanceCount = 0 |
117 |
|
|
118 |
1 |
__listeners=[] |
119 |
1 |
__keystrokeListeners=[] |
120 |
|
|
121 |
1 |
def __init__(self): |
122 |
|
|
123 |
|
# The "Borg" singleton model - ensures we're really |
124 |
|
# only connecting to the registry once. |
125 |
|
# |
126 |
2454 |
self.__dict__ = self.__sharedState |
127 |
2454 |
self.__instanceCount += 1 |
128 |
2454 |
if not self.__dict__.has_key("registry"): |
129 |
1 |
self.registry = bonobo.get_object( |
130 |
|
"OAFIID:Accessibility_Registry:1.0", |
131 |
1 |
"Accessibility/Registry") |
132 |
2454 |
if not self.__dict__.has_key("desktop"): |
133 |
1 |
self.desktop = self.registry.getDesktop(0) |
134 |
|
|
135 |
1 |
def __blockPreventor(self): |
136 |
|
"""[[[TODO: HACK to attempt to prevent deadlocks. We call time.sleep |
137 |
|
here as a means to sidestep the global interpreter lock (GIL).]]] |
138 |
|
""" |
139 |
0 |
if settings.gilSleepTime: |
140 |
0 |
time.sleep(settings.gilSleepTime) |
141 |
0 |
return True |
142 |
|
|
143 |
1 |
def start(self): |
144 |
|
"""Starts event notification with the AT-SPI Registry. This method |
145 |
|
only returns after 'stop' has been called. |
146 |
|
""" |
147 |
1 |
Accessible.init(self) |
148 |
|
|
149 |
|
# We'll try our own main loop to help debug things. Code borrowed |
150 |
|
# "The Whole PyGtk FAQ": http://www.async.com.br/faq/pygtk/ |
151 |
|
# |
152 |
1 |
if settings.useBonoboMain: |
153 |
1 |
debug.println(debug.LEVEL_CONFIGURATION, |
154 |
1 |
"atspi.start: using bonobo.main; " |
155 |
|
+ "gilSleepTime=%f" % settings.gilSleepTime) |
156 |
1 |
if settings.useBlockPreventor and settings.gilSleepTime: |
157 |
0 |
gobject.idle_add(self.__blockPreventor) |
158 |
1 |
bonobo.main() |
159 |
|
else: |
160 |
0 |
debug.println(debug.LEVEL_CONFIGURATION, |
161 |
0 |
"atspi.start: using our custom main loop; " |
162 |
|
+ "gilSleepTime=%f" % settings.gilSleepTime) |
163 |
0 |
self.running = True |
164 |
0 |
context = gobject.MainLoop().get_context() |
165 |
0 |
while self.running: |
166 |
0 |
if settings.gilSleepTime: |
167 |
0 |
time.sleep(settings.gilSleepTime) |
168 |
0 |
context.iteration(False) |
169 |
|
|
170 |
1 |
def stop(self): |
171 |
|
"""Unregisters any event or keystroke listeners registered with |
172 |
|
the AT-SPI Registry and then stops event notification with the |
173 |
|
AT-SPI Registry. |
174 |
|
""" |
175 |
1 |
Accessible.shutdown(self) |
176 |
257 |
for listener in (self.__listeners + self.__keystrokeListeners): |
177 |
256 |
listener.deregister() |
178 |
1 |
if settings.useBonoboMain: |
179 |
1 |
bonobo.main_quit() |
180 |
|
else: |
181 |
0 |
self.running = False |
182 |
|
|
183 |
1 |
def registerEventListener(self, callback, eventType): |
184 |
|
"""Registers the given eventType and callback with the Registry. |
185 |
|
|
186 |
|
Arguments: |
187 |
|
- callback: function to call with an AT-SPI event instance |
188 |
|
- eventType: string representing the type of event |
189 |
|
""" |
190 |
34 |
listener = EventListener(self.registry, callback, eventType) |
191 |
34 |
self.__listeners.append(listener) |
192 |
|
|
193 |
1 |
def deregisterEventListener(self, callback, eventType): |
194 |
|
"""Unregisters the given eventType and callback with the Registry. |
195 |
|
|
196 |
|
Arguments: |
197 |
|
- callback: function to call with an AT-SPI event instance |
198 |
|
- eventType: string representing the type of event |
199 |
|
""" |
200 |
34 |
found = True |
201 |
101 |
while len(self.__listeners) and found: |
202 |
650 |
for i in range(0, len(self.__listeners)): |
203 |
617 |
if (self.__listeners[i].callback == callback) \ |
204 |
|
and (self.__listeners[i].eventType == eventType): |
205 |
|
# The __del__ method of the listener will unregister it. |
206 |
|
# |
207 |
34 |
self.__listeners.pop(i) |
208 |
34 |
found = True |
209 |
34 |
break |
210 |
|
else: |
211 |
583 |
found = False |
212 |
|
|
213 |
1 |
def registerKeystrokeListeners(self, callback): |
214 |
|
"""Registers a single callback for all possible keystrokes. |
215 |
|
""" |
216 |
257 |
for i in range(0, (1 << (Accessibility.MODIFIER_NUMLOCK + 1))): |
217 |
256 |
self.__keystrokeListeners.append( |
218 |
|
KeystrokeListener(self.registry, |
219 |
256 |
callback, # callback |
220 |
256 |
[], # keyset |
221 |
256 |
i, # modifier mask |
222 |
256 |
[Accessibility.KEY_PRESSED_EVENT, |
223 |
|
Accessibility.KEY_RELEASED_EVENT], |
224 |
256 |
True, # synchronous |
225 |
256 |
True, # preemptive |
226 |
256 |
False)) # global |
227 |
|
|
228 |
|
######################################################################## |
229 |
|
# # |
230 |
|
# Event listener classes for global and keystroke events # |
231 |
|
# # |
232 |
|
######################################################################## |
233 |
|
|
234 |
2 |
class EventListener(Accessibility__POA.EventListener): |
235 |
|
"""Registers a callback directly with the AT-SPI Registry for the |
236 |
|
given event type. Most users of this module will not use this |
237 |
|
class directly, but will instead use the registerEventListener method |
238 |
|
of the Registry.""" |
239 |
|
|
240 |
1 |
def __init__(self, registry, callback, eventType): |
241 |
34 |
self.registry = registry |
242 |
34 |
self.callback = callback |
243 |
34 |
self.eventType = eventType |
244 |
34 |
self.register() |
245 |
|
|
246 |
35 |
def ref(self): pass |
247 |
|
|
248 |
35 |
def unref(self): pass |
249 |
|
|
250 |
1 |
def queryInterface(self, repo_id): |
251 |
0 |
thiz = None |
252 |
0 |
if repo_id == "IDL:Accessibility/EventListener:1.0": |
253 |
0 |
thiz = self._this() |
254 |
0 |
return thiz |
255 |
|
|
256 |
1 |
def register(self): |
257 |
34 |
self._default_POA().the_POAManager.activate() |
258 |
34 |
self.registry.registerGlobalEventListener(self._this(), |
259 |
34 |
self.eventType) |
260 |
34 |
self.__registered = True |
261 |
34 |
return self.__registered |
262 |
|
|
263 |
1 |
def deregister(self): |
264 |
34 |
if not self.__registered: |
265 |
0 |
return |
266 |
34 |
self.registry.deregisterGlobalEventListener(self._this(), |
267 |
34 |
self.eventType) |
268 |
34 |
self.__registered = False |
269 |
|
|
270 |
1 |
def notifyEvent(self, event): |
271 |
40471 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
272 |
40471 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
273 |
40471 |
signal.alarm(settings.timeoutTime) |
274 |
|
|
275 |
40471 |
try: |
276 |
40471 |
self.callback(event) |
277 |
0 |
except: |
278 |
0 |
debug.printException(debug.LEVEL_WARNING) |
279 |
|
|
280 |
40471 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
281 |
40471 |
signal.alarm(0) |
282 |
|
|
283 |
1 |
def __del__(self): |
284 |
34 |
self.deregister() |
285 |
|
|
286 |
2 |
class KeystrokeListener(Accessibility__POA.DeviceEventListener): |
287 |
|
"""Registers a callback directly with the AT-SPI Registry for the |
288 |
|
given keystroke. Most users of this module will not use this |
289 |
|
class directly, but will instead use the registerKeystrokeListeners |
290 |
|
method of the Registry.""" |
291 |
|
|
292 |
1 |
def keyEventToString(event): |
293 |
3257 |
return ("KEYEVENT: type=%d\n" % event.type) \ |
294 |
|
+ (" hw_code=%d\n" % event.hw_code) \ |
295 |
|
+ (" modifiers=%d\n" % event.modifiers) \ |
296 |
|
+ (" event_string=(%s)\n" % event.event_string) \ |
297 |
|
+ (" is_text=%s\n" % event.is_text) \ |
298 |
|
+ (" time=%f" % time.time()) |
299 |
|
|
300 |
1 |
keyEventToString = staticmethod(keyEventToString) |
301 |
|
|
302 |
1 |
def __init__(self, registry, callback, |
303 |
|
keyset, mask, type, synchronous, preemptive, isGlobal): |
304 |
256 |
self._default_POA().the_POAManager.activate() |
305 |
|
|
306 |
256 |
self.registry = registry |
307 |
256 |
self.callback = callback |
308 |
256 |
self.keyset = keyset |
309 |
256 |
self.mask = mask |
310 |
256 |
self.type = type |
311 |
256 |
self.mode = Accessibility.EventListenerMode() |
312 |
256 |
self.mode.synchronous = synchronous |
313 |
256 |
self.mode.preemptive = preemptive |
314 |
256 |
self.mode._global = isGlobal |
315 |
256 |
self.register() |
316 |
|
|
317 |
513 |
def ref(self): pass |
318 |
|
|
319 |
513 |
def unref(self): pass |
320 |
|
|
321 |
1 |
def queryInterface(self, repo_id): |
322 |
0 |
thiz = None |
323 |
0 |
if repo_id == "IDL:Accessibility/EventListener:1.0": |
324 |
0 |
thiz = self._this() |
325 |
0 |
return thiz |
326 |
|
|
327 |
1 |
def register(self): |
328 |
256 |
d = self.registry.getDeviceEventController() |
329 |
256 |
if d.registerKeystrokeListener(self._this(), |
330 |
256 |
self.keyset, |
331 |
256 |
self.mask, |
332 |
256 |
self.type, |
333 |
256 |
self.mode): |
334 |
256 |
self.__registered = True |
335 |
|
else: |
336 |
0 |
self.__registered = False |
337 |
256 |
return self.__registered |
338 |
|
|
339 |
1 |
def deregister(self): |
340 |
256 |
if not self.__registered: |
341 |
0 |
return |
342 |
256 |
d = self.registry.getDeviceEventController() |
343 |
256 |
d.deregisterKeystrokeListener(self._this(), |
344 |
256 |
self.keyset, |
345 |
256 |
self.mask, |
346 |
256 |
self.type) |
347 |
256 |
self.__registered = False |
348 |
|
|
349 |
1 |
def notifyEvent(self, event): |
350 |
|
"""Called by the at-spi registry when a key is pressed or released. |
351 |
|
|
352 |
|
Arguments: |
353 |
|
- event: an at-spi DeviceEvent |
354 |
|
|
355 |
|
Returns True if the event has been consumed. |
356 |
|
""" |
357 |
3257 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
358 |
3257 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
359 |
3257 |
signal.alarm(settings.timeoutTime) |
360 |
|
|
361 |
3257 |
try: |
362 |
3257 |
consumed = self.callback(event) |
363 |
0 |
except: |
364 |
0 |
debug.printException(debug.LEVEL_WARNING) |
365 |
0 |
consumed = False |
366 |
|
|
367 |
3257 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
368 |
3257 |
signal.alarm(0) |
369 |
|
|
370 |
3257 |
return consumed |
371 |
|
|
372 |
1 |
def __del__(self): |
373 |
0 |
self.deregister() |
374 |
|
|
375 |
|
######################################################################## |
376 |
|
# # |
377 |
|
# The Accessible class. # |
378 |
|
# # |
379 |
|
######################################################################## |
380 |
|
|
381 |
2 |
class Accessible: |
382 |
|
"""Wraps AT-SPI Accessible objects and caches properties such as |
383 |
|
name, description, and parent. |
384 |
|
|
385 |
|
It also adds some properties to the AT-SPI Accessible including |
386 |
|
the Application to which the object belongs. |
387 |
|
|
388 |
|
For efficiency purposes, this class also maintains a cache of all |
389 |
|
Accessible objects obtained so far, and will return an element |
390 |
|
from that cache instead of creating a duplicate object. |
391 |
|
""" |
392 |
|
|
393 |
|
# The cache of the currently active accessible objects. The key is |
394 |
|
# the AT-SPI Accessible, and the value is the Python Accessible. |
395 |
|
# [[[TODO: WDW - probably should look at the __new__ method as a means |
396 |
|
# to handle singletons.]]] |
397 |
|
# |
398 |
1 |
_cache = {} |
399 |
|
|
400 |
1 |
def init(registry): |
401 |
|
"""Registers various event listeners with the Registry to keep |
402 |
|
the Accessible cache up to date. |
403 |
|
|
404 |
|
Arguments: |
405 |
|
- registry: an instance of Registry |
406 |
|
""" |
407 |
1 |
registry.registerEventListener( |
408 |
|
Accessible._onNameChanged, |
409 |
1 |
"object:property-change:accessible-name") |
410 |
1 |
registry.registerEventListener( |
411 |
|
Accessible._onDescriptionChanged, |
412 |
1 |
"object:property-change:accessible-description") |
413 |
1 |
registry.registerEventListener( |
414 |
|
Accessible._onParentChanged, |
415 |
1 |
"object:property-change:accessible-parent") |
416 |
1 |
registry.registerEventListener( |
417 |
|
Accessible._onStateChanged, |
418 |
1 |
"object:state-changed:") |
419 |
1 |
registry.registerEventListener( |
420 |
|
Accessible._onChildrenChanged, |
421 |
1 |
"object:children-changed:") |
422 |
|
|
423 |
1 |
init = staticmethod(init) |
424 |
|
|
425 |
1 |
def shutdown(registry): |
426 |
|
"""Unregisters the event listeners that were registered in the |
427 |
|
init method. |
428 |
|
|
429 |
|
Arguments: |
430 |
|
- registry: an instance of Registry |
431 |
|
""" |
432 |
1 |
registry.deregisterEventListener( |
433 |
|
Accessible._onNameChanged, |
434 |
1 |
"object:property-change:accessible-name") |
435 |
1 |
registry.deregisterEventListener( |
436 |
|
Accessible._onDescriptionChanged, |
437 |
1 |
"object:property-change:accessible-description") |
438 |
1 |
registry.deregisterEventListener( |
439 |
|
Accessible._onParentChanged, |
440 |
1 |
"object:property-change:accessible-parent") |
441 |
1 |
registry.deregisterEventListener( |
442 |
|
Accessible._onStateChanged, |
443 |
1 |
"object:state-changed:") |
444 |
1 |
registry.deregisterEventListener( |
445 |
|
Accessible._onChildrenChanged, |
446 |
1 |
"object:children-changed:") |
447 |
|
|
448 |
1 |
shutdown = staticmethod(shutdown) |
449 |
|
|
450 |
1 |
def _onNameChanged(e): |
451 |
|
"""Core module event listener called when an object's name |
452 |
|
changes. Updates the cache accordingly. |
453 |
|
|
454 |
|
Arguments: |
455 |
|
- e: AT-SPI event from the AT-SPI registry |
456 |
|
""" |
457 |
|
|
458 |
268 |
if Accessible._cache.has_key(e.source): |
459 |
235 |
obj = Accessible._cache[e.source] |
460 |
235 |
if obj.__dict__.has_key("name"): |
461 |
107 |
del obj.__dict__["name"] |
462 |
235 |
if obj.__dict__.has_key("label"): |
463 |
0 |
del obj.__dict__["label"] |
464 |
|
|
465 |
1 |
_onNameChanged = staticmethod(_onNameChanged) |
466 |
|
|
467 |
1 |
def _onDescriptionChanged(e): |
468 |
|
"""Core module event listener called when an object's description |
469 |
|
changes. Updates the cache accordingly. |
470 |
|
|
471 |
|
Arguments: |
472 |
|
- e: AT-SPI event from the AT-SPI registry |
473 |
|
""" |
474 |
|
|
475 |
31 |
if Accessible._cache.has_key(e.source): |
476 |
28 |
obj = Accessible._cache[e.source] |
477 |
28 |
if obj.__dict__.has_key("description"): |
478 |
0 |
del obj.__dict__["description"] |
479 |
28 |
if obj.__dict__.has_key("label"): |
480 |
0 |
del obj.__dict__["label"] |
481 |
|
|
482 |
1 |
_onDescriptionChanged = staticmethod(_onDescriptionChanged) |
483 |
|
|
484 |
1 |
def _onParentChanged(e): |
485 |
|
"""Core module event listener called when an object's parent |
486 |
|
changes. Updates the cache accordingly. |
487 |
|
|
488 |
|
Arguments: |
489 |
|
- e: AT-SPI event from the AT-SPI registry |
490 |
|
""" |
491 |
|
|
492 |
|
# [[[TODO: WDW - I put this in here for now. The idea is that |
493 |
|
# we will probably get parent changed events for objects that |
494 |
|
# are or will soon be defunct, so let's just forget about the |
495 |
|
# object rather than try to keep the cache in sync.]]] |
496 |
|
# |
497 |
2211 |
if Accessible._cache.has_key(e.source): |
498 |
787 |
Accessible.deleteAccessible(e.source) |
499 |
2211 |
return |
500 |
|
|
501 |
1 |
_onParentChanged = staticmethod(_onParentChanged) |
502 |
|
|
503 |
1 |
def _onStateChanged(e): |
504 |
|
"""Core module event listener called when an object's state |
505 |
|
changes. Updates the cache accordingly. |
506 |
|
|
507 |
|
Arguments: |
508 |
|
- e: AT-SPI event from the AT-SPI registry |
509 |
|
""" |
510 |
|
|
511 |
6095 |
if Accessible._cache.has_key(e.source): |
512 |
|
# Let's get rid of defunct objects. We hate them. |
513 |
|
# |
514 |
4242 |
if e.type == "object:state-changed:defunct": |
515 |
957 |
Accessible.deleteAccessible(e.source) |
516 |
|
else: |
517 |
3285 |
obj = Accessible._cache[e.source] |
518 |
3285 |
if obj.__dict__.has_key("state"): |
519 |
0 |
del obj.state |
520 |
|
|
521 |
1 |
_onStateChanged = staticmethod(_onStateChanged) |
522 |
|
|
523 |
1 |
def _onChildrenChanged(e): |
524 |
|
"""Core module event listener called when an object's child count |
525 |
|
changes. Updates the cache accordingly. |
526 |
|
|
527 |
|
Arguments: |
528 |
|
- e: AT-SPI event from the AT-SPI registry |
529 |
|
""" |
530 |
|
|
531 |
2446 |
if Accessible._cache.has_key(e.source): |
532 |
1687 |
obj = Accessible._cache[e.source] |
533 |
1687 |
if obj.__dict__.has_key("childCount"): |
534 |
0 |
del obj.childCount |
535 |
|
|
536 |
1 |
_onChildrenChanged = staticmethod(_onChildrenChanged) |
537 |
|
|
538 |
1 |
def makeAccessible(acc): |
539 |
|
"""Make an Accessible. This is used instead of a simple calls to |
540 |
|
Accessible's constructor because the object may already be in the |
541 |
|
cache. |
542 |
|
|
543 |
|
Arguments: |
544 |
|
- acc: the AT-SPI Accessibility_Accessible |
545 |
|
|
546 |
|
Returns a Python Accessible. |
547 |
|
""" |
548 |
|
|
549 |
58028 |
obj = None |
550 |
|
|
551 |
58028 |
if not acc: |
552 |
6 |
return obj |
553 |
|
|
554 |
58022 |
if isinstance(acc, Accessible): |
555 |
0 |
debug.println( |
556 |
|
debug.LEVEL_WARNING, |
557 |
0 |
"WARNING: atspi.Accessible.makeAccessible:\n" |
558 |
|
" Parameter acc passed in is a\n" \ |
559 |
|
" Python Accessible instead of an\n" \ |
560 |
|
" AT-SPI Accessible.\n" |
561 |
|
" Returning Python Accessible.") |
562 |
0 |
return acc |
563 |
|
|
564 |
|
# [[[TODO: WDW - the AT-SPI appears to give us a different |
565 |
|
# accessible when we repeatedly ask for the same child of a |
566 |
|
# parent that manages its descendants. So...we probably |
567 |
|
# shouldn't cache those kind of children because we're likely |
568 |
|
# to cause a memory leak. Logged as bugzilla bug 319675.]]] |
569 |
|
# |
570 |
58022 |
if not settings.cacheAccessibles: |
571 |
0 |
obj = Accessible(acc) |
572 |
0 |
return obj |
573 |
|
|
574 |
58022 |
if Accessible._cache.has_key(acc): |
575 |
54128 |
obj = Accessible._cache[acc] |
576 |
54128 |
if not obj.valid: |
577 |
0 |
del Accessible._cache[acc] |
578 |
0 |
obj = None |
579 |
|
|
580 |
58022 |
if not obj: |
581 |
3894 |
obj = Accessible(acc) |
582 |
|
|
583 |
58022 |
if obj.valid: |
584 |
58022 |
Accessible._cache[acc] = obj |
585 |
|
else: |
586 |
0 |
obj = None |
587 |
|
|
588 |
58022 |
return obj |
589 |
|
|
590 |
1 |
makeAccessible = staticmethod(makeAccessible) |
591 |
|
|
592 |
1 |
def deleteAccessible(acc): |
593 |
|
"""Delete an Accessible from the cache if it exists. |
594 |
|
|
595 |
|
Arguments: |
596 |
|
- acc: the AT-SPI Accessibility_Accessible |
597 |
|
""" |
598 |
|
|
599 |
4087 |
if acc and Accessible._cache.has_key(acc): |
600 |
2401 |
try: |
601 |
2401 |
del Accessible._cache[acc] |
602 |
0 |
except: |
603 |
0 |
pass |
604 |
|
|
605 |
1 |
deleteAccessible = staticmethod(deleteAccessible) |
606 |
|
|
607 |
1 |
def __init__(self, acc): |
608 |
|
"""Obtains, and creates if necessary, a Python Accessible from |
609 |
|
an AT-SPI Accessibility_Accessible. Applications should not |
610 |
|
call this method, but should instead call makeAccessible. |
611 |
|
|
612 |
|
Arguments: |
613 |
|
- acc: the AT-SPI Accessibility_Accessible to back this object |
614 |
|
|
615 |
|
Returns the associated Python Accessible. |
616 |
|
""" |
617 |
|
|
618 |
|
# The setting of self._acc to None here is to help with manual |
619 |
|
# and unit testing of this module. Furthermore, it helps us |
620 |
|
# determine if this particular instance is really backed by an |
621 |
|
# object or not. |
622 |
|
# |
623 |
3894 |
self._acc = None |
624 |
|
|
625 |
|
# We'll also keep track of whether this object is any good to |
626 |
|
# us or not. This object will be deleted when a defunct event |
627 |
|
# is received for it, but anything could happen prior to that |
628 |
|
# event, so we keep this extra field around to help us. |
629 |
|
# |
630 |
3894 |
self.valid = False |
631 |
|
|
632 |
|
# [[[TODO: WDW - should do an assert here to make sure we're |
633 |
|
# getting a raw AT-SPI Accessible and not one of our own locally |
634 |
|
# cached Accessible instances. Logged as bugzilla bug 319673.]]] |
635 |
|
# |
636 |
3894 |
assert (not Accessible._cache.has_key(acc)), \ |
637 |
|
"Attempt to create an Accessible that's already been made." |
638 |
|
|
639 |
|
# See if we have an application. Via bridges such as the Java |
640 |
|
# access bridge, we might be getting a CORBA::Object, which is |
641 |
|
# of little use to us. We need to narrow it down to something |
642 |
|
# we can use. The first attempt is to see if we can get an |
643 |
|
# application out of it. Then we go for an accessible. |
644 |
|
# |
645 |
3894 |
self.accessible = None |
646 |
3894 |
try: |
647 |
3894 |
self.accessible = acc._narrow(Accessibility.Application) |
648 |
37 |
try: |
649 |
37 |
self.toolkitName = self.accessible.toolkitName |
650 |
0 |
except: |
651 |
0 |
self.toolkitName = None |
652 |
37 |
try: |
653 |
37 |
self.version = self.accessible.version |
654 |
0 |
except: |
655 |
0 |
self.version = None |
656 |
3857 |
except: |
657 |
3857 |
try: |
658 |
3857 |
self.accessible = acc._narrow(Accessibility.Accessible) |
659 |
0 |
except: |
660 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
661 |
0 |
debug.println(debug.LEVEL_SEVERE, |
662 |
0 |
"atspi.py:Accessible.__init__" \ |
663 |
|
+ " NOT GIVEN AN ACCESSIBLE!") |
664 |
0 |
self.accessible = None |
665 |
|
|
666 |
|
# Save a reference to the AT-SPI object. |
667 |
|
# |
668 |
3894 |
if self.accessible: |
669 |
3894 |
try: |
670 |
3894 |
self.accessible.ref() |
671 |
3894 |
self._acc = acc |
672 |
3894 |
self.valid = True |
673 |
0 |
except: |
674 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
675 |
|
|
676 |
1 |
def getRelationString(self): |
677 |
|
"""Returns a space-delimited string composed of the given object's |
678 |
|
Accessible relations attribute. This is for debug purposes. |
679 |
|
""" |
680 |
|
|
681 |
1835 |
relations = self.relations |
682 |
1835 |
relString = " " |
683 |
1837 |
for relation in relations: |
684 |
2 |
if relation.getRelationType() == Accessibility.RELATION_LABEL_FOR: |
685 |
0 |
relString += "LABEL_FOR " |
686 |
2 |
if relation.getRelationType() == Accessibility.RELATION_LABELLED_BY: |
687 |
2 |
relString += "LABELLED_BY " |
688 |
2 |
if relation.getRelationType() == \ |
689 |
|
Accessibility.RELATION_CONTROLLER_FOR: |
690 |
0 |
relString += "CONTROLLER_FOR " |
691 |
2 |
if relation.getRelationType() == \ |
692 |
|
Accessibility.RELATION_CONTROLLED_BY: |
693 |
0 |
relString += "CONTROLLED_BY " |
694 |
2 |
if relation.getRelationType() == Accessibility.RELATION_MEMBER_OF: |
695 |
0 |
relString += "MEMBER_OF " |
696 |
2 |
if relation.getRelationType() == Accessibility.RELATION_TOOLTIP_FOR: |
697 |
0 |
relString += "TOOLTIP_FOR " |
698 |
2 |
if relation.getRelationType() == \ |
699 |
|
Accessibility.RELATION_NODE_CHILD_OF: |
700 |
0 |
relString += "NODE_CHILD_OF " |
701 |
2 |
if relation.getRelationType() == Accessibility.RELATION_EXTENDED: |
702 |
0 |
relString += "RELATION_EXTENDED " |
703 |
2 |
if relation.getRelationType() == Accessibility.RELATION_FLOWS_TO: |
704 |
0 |
relString += "FLOWS_TO " |
705 |
2 |
if relation.getRelationType() == Accessibility.RELATION_FLOWS_FROM: |
706 |
0 |
relString += "FLOWS_FROM " |
707 |
2 |
if relation.getRelationType() == \ |
708 |
|
Accessibility.RELATION_SUBWINDOW_OF: |
709 |
0 |
relString += "SUBWINDOW_OF " |
710 |
2 |
if relation.getRelationType() == Accessibility.RELATION_EMBEDS: |
711 |
0 |
relString += "EMBEDS " |
712 |
2 |
if relation.getRelationType() == Accessibility.RELATION_EMBEDDED_BY: |
713 |
0 |
relString += "EMBEDDED_BY " |
714 |
2 |
if relation.getRelationType() == Accessibility.RELATION_POPUP_FOR: |
715 |
0 |
relString += "POPUP_FOR " |
716 |
2 |
if relation.getRelationType() == \ |
717 |
|
Accessibility.RELATION_PARENT_WINDOW_OF: |
718 |
0 |
relString += "WINDOW_OF " |
719 |
|
|
720 |
1835 |
return relString.strip() |
721 |
|
|
722 |
1 |
def getStateString(self): |
723 |
|
"""Returns a space-delimited string composed of the given object's |
724 |
|
Accessible state attribute. This is for debug purposes. |
725 |
|
""" |
726 |
|
|
727 |
1835 |
stateSet = self.state |
728 |
1835 |
stateString = " " |
729 |
1835 |
if stateSet.count(Accessibility.STATE_INVALID): |
730 |
0 |
stateString += "INVALID " |
731 |
1835 |
if stateSet.count(Accessibility.STATE_ACTIVE): |
732 |
31 |
stateString += "ACTIVE " |
733 |
1835 |
if stateSet.count(Accessibility.STATE_ARMED): |
734 |
90 |
stateString += "ARMED " |
735 |
1835 |
if stateSet.count(Accessibility.STATE_BUSY): |
736 |
0 |
stateString += "BUSY " |
737 |
1835 |
if stateSet.count(Accessibility.STATE_CHECKED): |
738 |
8 |
stateString += "CHECKED " |
739 |
1835 |
if stateSet.count(Accessibility.STATE_COLLAPSED): |
740 |
0 |
stateString += "COLLAPSED " |
741 |
1835 |
if stateSet.count(Accessibility.STATE_DEFUNCT): |
742 |
0 |
stateString += "DEFUNCT " |
743 |
1835 |
if stateSet.count(Accessibility.STATE_EDITABLE): |
744 |
543 |
stateString += "EDITABLE " |
745 |
1835 |
if stateSet.count(Accessibility.STATE_ENABLED): |
746 |
1835 |
stateString += "ENABLED " |
747 |
1835 |
if stateSet.count(Accessibility.STATE_EXPANDABLE): |
748 |
0 |
stateString += "EXPANDABLE " |
749 |
1835 |
if stateSet.count(Accessibility.STATE_EXPANDED): |
750 |
0 |
stateString += "EXPANDED " |
751 |
1835 |
if stateSet.count(Accessibility.STATE_FOCUSABLE): |
752 |
700 |
stateString += "FOCUSABLE " |
753 |
1835 |
if stateSet.count(Accessibility.STATE_FOCUSED): |
754 |
762 |
stateString += "FOCUSED " |
755 |
1835 |
if stateSet.count(Accessibility.STATE_HAS_TOOLTIP): |
756 |
0 |
stateString += "HAS_TOOLTIP " |
757 |
1835 |
if stateSet.count(Accessibility.STATE_HORIZONTAL): |
758 |
0 |
stateString += "HORIZONTAL " |
759 |
1835 |
if stateSet.count(Accessibility.STATE_ICONIFIED): |
760 |
0 |
stateString += "ICONIFIED " |
761 |
1835 |
if stateSet.count(Accessibility.STATE_MODAL): |
762 |
0 |
stateString += "MODAL " |
763 |
1835 |
if stateSet.count(Accessibility.STATE_MULTI_LINE): |
764 |
551 |
stateString += "MULTI_LINE " |
765 |
1835 |
if stateSet.count(Accessibility.STATE_MULTISELECTABLE): |
766 |
2 |
stateString += "MULTISELECTABLE " |
767 |
1835 |
if stateSet.count(Accessibility.STATE_OPAQUE): |
768 |
106 |
stateString += "OPAQUE " |
769 |
1835 |
if stateSet.count(Accessibility.STATE_PRESSED): |
770 |
0 |
stateString += "PRESSED " |
771 |
1835 |
if stateSet.count(Accessibility.STATE_RESIZABLE): |
772 |
34 |
stateString += "RESIZABLE " |
773 |
1835 |
if stateSet.count(Accessibility.STATE_SELECTABLE): |
774 |
234 |
stateString += "SELECTABLE " |
775 |
1835 |
if stateSet.count(Accessibility.STATE_SELECTED): |
776 |
95 |
stateString += "SELECTED " |
777 |
1835 |
if stateSet.count(Accessibility.STATE_SENSITIVE): |
778 |
1833 |
stateString += "SENSITIVE " |
779 |
1835 |
if stateSet.count(Accessibility.STATE_SHOWING): |
780 |
1808 |
stateString += "SHOWING " |
781 |
1835 |
if stateSet.count(Accessibility.STATE_SINGLE_LINE): |
782 |
64 |
stateString += "SINGLE_LINE " |
783 |
1835 |
if stateSet.count(Accessibility.STATE_STALE): |
784 |
0 |
stateString += "STALE " |
785 |
1835 |
if stateSet.count(Accessibility.STATE_TRANSIENT): |
786 |
64 |
stateString += "TRANSIENT " |
787 |
1835 |
if stateSet.count(Accessibility.STATE_VERTICAL): |
788 |
0 |
stateString += "VERTICAL " |
789 |
1835 |
if stateSet.count(Accessibility.STATE_VISIBLE): |
790 |
1811 |
stateString += "VISIBLE " |
791 |
1835 |
if stateSet.count(Accessibility.STATE_MANAGES_DESCENDANTS): |
792 |
35 |
stateString += "MANAGES_DESCENDANTS " |
793 |
1835 |
if stateSet.count(Accessibility.STATE_INDETERMINATE): |
794 |
0 |
stateString += "INDETERMINATE " |
795 |
|
|
796 |
1835 |
return stateString.strip() |
797 |
|
|
798 |
1 |
def accessibleNameToString(self): |
799 |
|
"""Returns the accessible's name in single quotes or |
800 |
|
the string None if the accessible does not have a name. |
801 |
|
""" |
802 |
|
|
803 |
10211 |
if self.name: |
804 |
5085 |
return "'" + self.name + "'" |
805 |
|
else: |
806 |
5108 |
return "None" |
807 |
|
|
808 |
1 |
def __del__(self): |
809 |
|
"""Unrefs the AT-SPI Accessible associated with this object. |
810 |
|
""" |
811 |
|
|
812 |
1954 |
if self.accessible: |
813 |
1954 |
try: |
814 |
1954 |
self.accessible.unref() |
815 |
11 |
except: |
816 |
11 |
pass |
817 |
1954 |
try: |
818 |
1954 |
Accessible.deleteAccessible(self._acc) |
819 |
1954 |
self.accessible = None |
820 |
1954 |
self._acc = None |
821 |
0 |
except: |
822 |
0 |
pass |
823 |
|
|
824 |
1 |
def __get_name(self): |
825 |
|
"""Returns the object's accessible name as a string. |
826 |
|
""" |
827 |
|
|
828 |
|
# Combo boxes don't seem to issue accessible-name changed |
829 |
|
# events, so we can't cache their names. The main culprit |
830 |
|
# here seems to be the combo box in gaim's "Join Chat" window. |
831 |
|
# |
832 |
15002 |
name = self.accessible.name |
833 |
|
|
834 |
14941 |
if name and settings.cacheValues \ |
835 |
|
and (self.role != rolenames.ROLE_COMBO_BOX): |
836 |
1015 |
self.name = name |
837 |
|
|
838 |
14941 |
return name |
839 |
|
|
840 |
1 |
def __get_description(self): |
841 |
|
"""Returns the object's accessible description as a string. |
842 |
|
""" |
843 |
|
|
844 |
220 |
description = self.accessible.description |
845 |
|
|
846 |
220 |
if description and settings.cacheValues and settings.cacheDescriptions: |
847 |
16 |
self.description = description |
848 |
|
|
849 |
220 |
return description |
850 |
|
|
851 |
1 |
def __get_parent(self): |
852 |
|
"""Returns the object's parent as a Python Accessible. If |
853 |
|
this object has no parent, None will be returned. |
854 |
|
""" |
855 |
|
|
856 |
|
# We will never set self.parent if the backing accessible doesn't |
857 |
|
# have a parent. The reason we do this is that we may sometimes |
858 |
|
# get events for objects without a parent, but then the object ends |
859 |
|
# up getting a parent later on. |
860 |
|
# |
861 |
12475 |
accParent = self.accessible.parent |
862 |
|
|
863 |
12349 |
if not accParent: |
864 |
9753 |
return None |
865 |
|
else: |
866 |
2596 |
parent = Accessible.makeAccessible(accParent); |
867 |
|
|
868 |
2596 |
if settings.cacheValues: |
869 |
2596 |
self.parent = parent |
870 |
|
|
871 |
2596 |
return parent; |
872 |
|
|
873 |
1 |
def __get_child_count(self): |
874 |
|
"""Returns the number of children for this object. |
875 |
|
""" |
876 |
|
|
877 |
9047 |
childCount = self.accessible.childCount |
878 |
|
|
879 |
|
# We don't want to cache this value because it's possible that it |
880 |
|
# will continually change. |
881 |
|
# if settings.cacheValues: |
882 |
|
# self.childCount = childCount |
883 |
|
|
884 |
9036 |
return childCount |
885 |
|
|
886 |
1 |
def __get_index(self): |
887 |
|
"""Returns the index of this object in its parent's child list. |
888 |
|
""" |
889 |
|
|
890 |
2229 |
index = self.accessible.getIndexInParent() |
891 |
|
|
892 |
|
# We don't want to cache this value because it's possible that it |
893 |
|
# will continually change. |
894 |
|
# if settings.cacheValues: |
895 |
|
# self.index = index |
896 |
|
|
897 |
2228 |
return index |
898 |
|
|
899 |
1 |
def __get_role(self): |
900 |
|
"""Returns the Accessible role name of this object as a string. |
901 |
|
This string is not localized and can be used for comparison. |
902 |
|
|
903 |
|
Note that this fudges the rolename of the object to match more closely |
904 |
|
what it is. The only thing that is being fudged right now is to |
905 |
|
coalesce radio and check menu items that are also submenus; gtk-demo |
906 |
|
has an example of this in its menus demo. |
907 |
|
""" |
908 |
|
|
909 |
8150 |
role = self.accessible.getRoleName() |
910 |
|
|
911 |
|
# [[[TODO: WDW - HACK to handle the situation where some |
912 |
|
# things might not be quite lined up with the ATK and AT-SPI. |
913 |
|
# That is, some roles might have ids but no string yet. See |
914 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=361757. |
915 |
|
# |
916 |
8129 |
if not len(role): |
917 |
0 |
try: |
918 |
0 |
roleId = self.accessible.getRole() |
919 |
0 |
if roleId == Accessibility.ROLE_LINK: |
920 |
0 |
role = rolenames.ROLE_LINK |
921 |
0 |
elif roleId == Accessibility.ROLE_INPUT_METHOD_WINDOW: |
922 |
0 |
role = rolenames.ROLE_INPUT_METHOD_WINDOW |
923 |
0 |
except: |
924 |
0 |
pass |
925 |
|
|
926 |
|
# [[[TODO: HACK to coalesce menu items with children into |
927 |
|
# menus. The menu demo in gtk-demo does this, and one |
928 |
|
# might view that as an edge case. But, in |
929 |
|
# gnome-terminal, "Terminal" -> "Set Character Encoding" |
930 |
|
# is a menu item with children, but it behaves like a |
931 |
|
# menu.]]] |
932 |
|
# |
933 |
8129 |
if (role == rolenames.ROLE_CHECK_MENU_ITEM) \ |
934 |
|
and (self.childCount > 0): |
935 |
0 |
role = rolenames.ROLE_CHECK_MENU |
936 |
8129 |
elif (role == rolenames.ROLE_RADIO_MENU_ITEM) \ |
937 |
|
and (self.childCount > 0): |
938 |
0 |
role = rolenames.ROLE_RADIO_MENU |
939 |
8129 |
elif (role == rolenames.ROLE_MENU_ITEM) \ |
940 |
|
and (self.childCount > 0): |
941 |
0 |
role = rolenames.ROLE_MENU |
942 |
|
|
943 |
|
# [[[TODO: HACK because Java gives us radio button role and |
944 |
|
# check box role for menu item objects, instead of giving us |
945 |
|
# radio menu item role and check menu item role (see SwingSet |
946 |
|
# menus).]]] |
947 |
|
# |
948 |
8129 |
if (self.parent) and (self.parent.role == rolenames.ROLE_MENU): |
949 |
180 |
if (role == rolenames.ROLE_RADIO_BUTTON): |
950 |
0 |
role = rolenames.ROLE_RADIO_MENU_ITEM |
951 |
180 |
elif (role == rolenames.ROLE_CHECK_BOX): |
952 |
0 |
role = rolenames.ROLE_CHECK_MENU_ITEM |
953 |
|
|
954 |
|
# [[[TODO: HACK because we sometimes get an object with an |
955 |
|
# unknown role but it's role changes later on and we are not |
956 |
|
# notified. An example of this is gnome-terminal. So...to |
957 |
|
# help with this, we will not cache the role if it is unknown. |
958 |
|
# See http://bugzilla.gnome.org/show_bug.cgi?id=344218 for |
959 |
|
# more info.]]] |
960 |
|
# |
961 |
8129 |
if role and settings.cacheValues \ |
962 |
|
and (role != rolenames.ROLE_UNKNOWN): |
963 |
3795 |
self.role = role |
964 |
|
|
965 |
8129 |
return role |
966 |
|
|
967 |
1 |
def __get_localized_rolename(self): |
968 |
|
"""Returns the Accessible role name of this object as a |
969 |
|
localized string. Most callers should use __get_role instead |
970 |
|
since it returns a non-localized string that can be used for |
971 |
|
comparison. |
972 |
|
""" |
973 |
|
|
974 |
1 |
localizedRoleName = self.accessible.getLocalizedRoleName() |
975 |
|
|
976 |
1 |
if localizedRoleName and settings.cacheValues: |
977 |
1 |
self.localizedRoleName = localizedRoleName |
978 |
|
|
979 |
1 |
return localizedRoleName |
980 |
|
|
981 |
1 |
def __get_state(self): |
982 |
|
"""Returns the Accessible StateSeq of this object, which is a |
983 |
|
sequence of Accessible StateTypes. |
984 |
|
""" |
985 |
|
|
986 |
48267 |
try: |
987 |
48267 |
stateSet = self.accessible.getState() |
988 |
96 |
except: |
989 |
96 |
stateSet = None |
990 |
48267 |
if stateSet: |
991 |
48171 |
try: |
992 |
48171 |
state = stateSet._narrow(Accessibility.StateSet).getStates() |
993 |
3 |
except: |
994 |
3 |
state = [] |
995 |
|
else: |
996 |
96 |
state = [] |
997 |
|
|
998 |
|
# [[[WDW - we don't seem to always get appropriate state changed |
999 |
|
# information, so we will not cache state information.]]] |
1000 |
|
# |
1001 |
|
#if state and settings.cacheValues: |
1002 |
|
# self.state = state |
1003 |
|
|
1004 |
48267 |
return state |
1005 |
|
|
1006 |
1 |
def __get_relations(self): |
1007 |
|
"""Returns the Accessible RelationSet of this object as a list. |
1008 |
|
""" |
1009 |
|
|
1010 |
20807 |
relations = [] |
1011 |
|
|
1012 |
20807 |
relationSet = self.accessible.getRelationSet() |
1013 |
|
|
1014 |
21837 |
for relation in relationSet: |
1015 |
1162 |
try: |
1016 |
1162 |
relations.append(relation._narrow(Accessibility.Relation)) |
1017 |
0 |
except: |
1018 |
0 |
pass |
1019 |
|
|
1020 |
20675 |
return relations |
1021 |
|
|
1022 |
1 |
def __get_app(self): |
1023 |
|
"""Returns the AT-SPI Accessibility_Application associated with this |
1024 |
|
object. Returns None if the application cannot be found (usually |
1025 |
|
the indication of an AT-SPI bug). |
1026 |
|
""" |
1027 |
|
|
1028 |
|
# [[[TODO: WDW - this code seems like it might break if this |
1029 |
|
# object is an application to begin with. Logged as bugzilla |
1030 |
|
# bug 319677.]]] |
1031 |
|
# |
1032 |
209 |
debug.println(debug.LEVEL_FINEST, |
1033 |
209 |
"Finding app for source.name=" \ |
1034 |
|
+ self.accessibleNameToString()) |
1035 |
209 |
obj = self |
1036 |
1393 |
while obj.parent and (obj != obj.parent): |
1037 |
1184 |
obj = obj.parent |
1038 |
1184 |
debug.println(debug.LEVEL_FINEST, |
1039 |
1184 |
"--> parent.name=" + obj.accessibleNameToString()) |
1040 |
|
|
1041 |
209 |
if (obj == obj.parent): |
1042 |
0 |
debug.println(debug.LEVEL_SEVERE, |
1043 |
0 |
"ERROR in Accessible.__get_app: obj == obj.parent!") |
1044 |
0 |
return None |
1045 |
209 |
elif (obj.role != rolenames.ROLE_APPLICATION): |
1046 |
3 |
debug.println(debug.LEVEL_FINEST, |
1047 |
3 |
"ERROR in Accessible.__get_app: top most parent " \ |
1048 |
|
"(name='%s') is of role %s" % (obj.name, obj.role)) |
1049 |
|
|
1050 |
|
# [[[TODO: We'll let this fall through for some cases. It |
1051 |
|
# seems as though we don't always end up with an |
1052 |
|
# application, but we do end up with *something* that is |
1053 |
|
# uniquely identifiable as the app. |
1054 |
|
# |
1055 |
3 |
if (obj.role != rolenames.ROLE_INVALID) \ |
1056 |
|
and (obj.role != rolenames.ROLE_FRAME): |
1057 |
3 |
return None |
1058 |
|
|
1059 |
206 |
debug.println(debug.LEVEL_FINEST, "Accessible app for %s is %s" \ |
1060 |
|
% (self.accessibleNameToString(), \ |
1061 |
|
obj.accessibleNameToString())) |
1062 |
|
|
1063 |
206 |
if settings.cacheValues: |
1064 |
206 |
self.app = obj |
1065 |
|
|
1066 |
206 |
return obj |
1067 |
|
|
1068 |
1 |
def __get_extents(self, coordinateType = 0): |
1069 |
|
"""Returns the object's accessible extents as an |
1070 |
|
Accessibility.BoundingBox object, or None if the object doesn't |
1071 |
|
implement the Accessibility Component interface. |
1072 |
|
|
1073 |
|
Arguments: |
1074 |
|
- coordinateType: 0 = get the extents in screen coordinates, |
1075 |
|
1 = get the extents in window coordinates |
1076 |
|
|
1077 |
|
Returns: |
1078 |
|
This object's accessible extents as an Accessibility.BoundingBox |
1079 |
|
object, or None if the object doesn't implement the Accessibility |
1080 |
|
Component interface. |
1081 |
|
""" |
1082 |
|
|
1083 |
438 |
component = self.component |
1084 |
|
|
1085 |
438 |
if not component: |
1086 |
0 |
return None |
1087 |
|
|
1088 |
438 |
extents = component.getExtents(coordinateType) |
1089 |
|
|
1090 |
|
# [[[TODO: WDW - caching the extents is dangerous because |
1091 |
|
# the object may move, resulting in the current extents |
1092 |
|
# becoming way out of date. Perhaps need to cache just |
1093 |
|
# the component interface and suffer the hit for getting |
1094 |
|
# the extents if we cannot figure out how to determine if |
1095 |
|
# the cached extents is out of date. Logged as bugzilla |
1096 |
|
# bug 319678.]]] |
1097 |
|
# |
1098 |
|
#if settings.cacheValues: |
1099 |
|
# self.extents = extents |
1100 |
|
|
1101 |
438 |
return extents |
1102 |
|
|
1103 |
1 |
def __get_action(self): |
1104 |
|
"""Returns an object that implements the Accessibility_Action |
1105 |
|
interface for this object, or None if this object doesn't implement |
1106 |
|
the Accessibility_Action interface. |
1107 |
|
""" |
1108 |
|
|
1109 |
415 |
action = self.accessible.queryInterface("IDL:Accessibility/Action:1.0") |
1110 |
|
|
1111 |
415 |
if action: |
1112 |
369 |
try: |
1113 |
369 |
action = action._narrow(Accessibility.Action) |
1114 |
0 |
except: |
1115 |
0 |
action = None |
1116 |
|
|
1117 |
415 |
if action and settings.cacheValues: |
1118 |
369 |
self.action = action |
1119 |
|
|
1120 |
415 |
return action |
1121 |
|
|
1122 |
1 |
def __get_component(self): |
1123 |
|
"""Returns an object that implements the Accessibility_Component |
1124 |
|
interface for this object, or None if this object doesn't implement |
1125 |
|
the Accessibility_Component interface. |
1126 |
|
""" |
1127 |
|
|
1128 |
389 |
component = self.accessible.queryInterface(\ |
1129 |
|
"IDL:Accessibility/Component:1.0") |
1130 |
|
|
1131 |
389 |
if component: |
1132 |
389 |
try: |
1133 |
389 |
component = component._narrow(Accessibility.Component) |
1134 |
0 |
except: |
1135 |
0 |
component = None |
1136 |
|
|
1137 |
389 |
if component and settings.cacheValues: |
1138 |
389 |
self.component = component |
1139 |
|
|
1140 |
389 |
return component |
1141 |
|
|
1142 |
1 |
def __get_hyperlink(self): |
1143 |
|
"""Returns an object that implements the Accessibility_Hyperlink |
1144 |
|
interface for this object, or None if this object doesn't implement |
1145 |
|
the Accessibility_Hyperlink interface. |
1146 |
|
""" |
1147 |
|
|
1148 |
0 |
hyperlink = self.accessible.queryInterface(\ |
1149 |
|
"IDL:Accessibility/Hyperlink:1.0") |
1150 |
|
|
1151 |
0 |
if hyperlink: |
1152 |
0 |
try: |
1153 |
0 |
hyperlink = hyperlink._narrow(Accessibility.Hyperlink) |
1154 |
0 |
except: |
1155 |
0 |
hyperlink = None |
1156 |
|
|
1157 |
0 |
if hyperlink and settings.cacheValues: |
1158 |
0 |
self.hyperlink = hyperlink |
1159 |
|
|
1160 |
0 |
return hyperlink |
1161 |
|
|
1162 |
1 |
def __get_hypertext(self): |
1163 |
|
"""Returns an object that implements the Accessibility_Hypertext |
1164 |
|
interface for this object, or None if this object doesn't implement |
1165 |
|
the Accessibility_Hypertext interface. |
1166 |
|
""" |
1167 |
|
|
1168 |
33 |
hypertext = self.accessible.queryInterface(\ |
1169 |
|
"IDL:Accessibility/Hypertext:1.0") |
1170 |
|
|
1171 |
33 |
if hypertext: |
1172 |
9 |
try: |
1173 |
9 |
hypertext = hypertext._narrow(Accessibility.Hypertext) |
1174 |
0 |
except: |
1175 |
0 |
hypertext = None |
1176 |
|
|
1177 |
33 |
if hypertext and settings.cacheValues: |
1178 |
9 |
self.hypertext = hypertext |
1179 |
|
|
1180 |
33 |
return hypertext |
1181 |
|
|
1182 |
1 |
def __get_image(self): |
1183 |
|
"""Returns an object that implements the Accessibility_Image |
1184 |
|
interface for this object, or None if this object doesn't implement |
1185 |
|
the Accessibility_Image interface. |
1186 |
|
""" |
1187 |
|
|
1188 |
307 |
image = self.accessible.queryInterface(\ |
1189 |
|
"IDL:Accessibility/Image:1.0") |
1190 |
|
|
1191 |
307 |
if image: |
1192 |
0 |
try: |
1193 |
0 |
image = image._narrow(Accessibility.Image) |
1194 |
0 |
except: |
1195 |
0 |
image = None |
1196 |
|
|
1197 |
307 |
if image and settings.cacheValues: |
1198 |
0 |
self.image = image |
1199 |
|
|
1200 |
307 |
return image |
1201 |
|
|
1202 |
1 |
def __get_selection(self): |
1203 |
|
"""Returns an object that implements the Accessibility_Selection |
1204 |
|
interface for this object, or None if this object doesn't implement |
1205 |
|
the Accessibility_Selection interface. |
1206 |
|
""" |
1207 |
|
|
1208 |
234 |
selection = self.accessible.queryInterface(\ |
1209 |
|
"IDL:Accessibility/Selection:1.0") |
1210 |
|
|
1211 |
234 |
if selection: |
1212 |
91 |
try: |
1213 |
91 |
selection = selection._narrow(Accessibility.Selection) |
1214 |
0 |
except: |
1215 |
0 |
selection = None |
1216 |
|
|
1217 |
234 |
if selection and settings.cacheValues: |
1218 |
91 |
self.selection = selection |
1219 |
|
|
1220 |
234 |
return selection |
1221 |
|
|
1222 |
1 |
def __get_table(self): |
1223 |
|
"""Returns an object that implements the Accessibility_Table |
1224 |
|
interface for this object, or None if this object doesn't implement |
1225 |
|
the Accessibility_Table interface. |
1226 |
|
""" |
1227 |
|
|
1228 |
1138 |
table = self.accessible.queryInterface("IDL:Accessibility/Table:1.0") |
1229 |
|
|
1230 |
1138 |
if table: |
1231 |
14 |
try: |
1232 |
14 |
table = table._narrow(Accessibility.Table) |
1233 |
0 |
except: |
1234 |
0 |
table = None |
1235 |
|
|
1236 |
1138 |
if table and settings.cacheValues: |
1237 |
14 |
self.table = table |
1238 |
|
|
1239 |
1138 |
return table |
1240 |
|
|
1241 |
1 |
def __get_text(self): |
1242 |
|
"""Returns an object that implements the Accessibility_Text |
1243 |
|
interface for this object, or None if this object doesn't implement |
1244 |
|
the Accessibility_Text interface. |
1245 |
|
""" |
1246 |
|
|
1247 |
11643 |
text = self.accessible.queryInterface("IDL:Accessibility/Text:1.0") |
1248 |
|
|
1249 |
11636 |
if text: |
1250 |
673 |
try: |
1251 |
673 |
text = text._narrow(Accessibility.Text) |
1252 |
0 |
except: |
1253 |
0 |
text = None |
1254 |
|
|
1255 |
11636 |
if text and settings.cacheValues: |
1256 |
673 |
self.text = text |
1257 |
|
|
1258 |
11636 |
return text |
1259 |
|
|
1260 |
1 |
def __get_value(self): |
1261 |
|
"""Returns an object that implements the Accessibility_Value |
1262 |
|
interface for this object, or None if this object doesn't implement |
1263 |
|
the Accessibility_Value interface. |
1264 |
|
""" |
1265 |
|
|
1266 |
4941 |
value = self.accessible.queryInterface("IDL:Accessibility/Value:1.0") |
1267 |
|
|
1268 |
4941 |
if value: |
1269 |
13 |
try: |
1270 |
13 |
value = value._narrow(Accessibility.Value) |
1271 |
0 |
except: |
1272 |
0 |
value = None |
1273 |
|
|
1274 |
4941 |
if value and settings.cacheValues: |
1275 |
13 |
self.value = value |
1276 |
|
|
1277 |
4941 |
return value |
1278 |
|
|
1279 |
1 |
def __get_attributes(self): |
1280 |
|
"""Returns an Accessibility_AttributeSet of the object. |
1281 |
|
""" |
1282 |
|
|
1283 |
174 |
try: |
1284 |
174 |
attributes = self.accessible.getAttributes() |
1285 |
174 |
except: |
1286 |
174 |
attributes = None |
1287 |
|
|
1288 |
174 |
return attributes |
1289 |
|
|
1290 |
1 |
def __getattr__(self, attr): |
1291 |
|
"""Created virtual attributes for the Accessible object to make |
1292 |
|
the syntax a bit nicer (e.g., acc.name rather than acc.name()). |
1293 |
|
This method is also called if and only if the given attribute |
1294 |
|
does not exist in the object. Thus, we're effectively lazily |
1295 |
|
building a cache to the remote object attributes here. |
1296 |
|
|
1297 |
|
Arguments: |
1298 |
|
- attr: a string indicating the attribute name to retrieve |
1299 |
|
|
1300 |
|
Returns the value of the given attribute. |
1301 |
|
""" |
1302 |
|
|
1303 |
1212396 |
if attr == "name": |
1304 |
15002 |
return self.__get_name() |
1305 |
1197394 |
elif attr == "description": |
1306 |
220 |
return self.__get_description() |
1307 |
1197174 |
elif attr == "parent": |
1308 |
12475 |
return self.__get_parent() |
1309 |
1184699 |
elif attr == "childCount": |
1310 |
9047 |
return self.__get_child_count() |
1311 |
1175652 |
elif attr == "index": |
1312 |
2229 |
return self.__get_index() |
1313 |
1173423 |
elif attr == "role": |
1314 |
8150 |
return self.__get_role() |
1315 |
1165273 |
elif attr == "localizedRoleName": |
1316 |
1 |
return self.__get_localized_rolename() |
1317 |
1165272 |
elif attr == "state": |
1318 |
48267 |
return self.__get_state() |
1319 |
1117005 |
elif attr == "relations": |
1320 |
20807 |
return self.__get_relations() |
1321 |
1096198 |
elif attr == "app": |
1322 |
209 |
return self.__get_app() |
1323 |
1095989 |
elif attr == "extents": |
1324 |
438 |
return self.__get_extents() |
1325 |
1095551 |
elif attr == "action": |
1326 |
415 |
return self.__get_action() |
1327 |
1095136 |
elif attr == "component": |
1328 |
389 |
return self.__get_component() |
1329 |
1094747 |
elif attr == "hyperlink": |
1330 |
0 |
return self.__get_hyperlink() |
1331 |
1094747 |
elif attr == "hypertext": |
1332 |
33 |
return self.__get_hypertext() |
1333 |
1094714 |
elif attr == "image": |
1334 |
307 |
return self.__get_image() |
1335 |
1094407 |
elif attr == "selection": |
1336 |
234 |
return self.__get_selection() |
1337 |
1094173 |
elif attr == "table": |
1338 |
1138 |
return self.__get_table() |
1339 |
1093035 |
elif attr == "text": |
1340 |
11643 |
return self.__get_text() |
1341 |
1081392 |
elif attr == "value": |
1342 |
4941 |
return self.__get_value() |
1343 |
1076451 |
elif attr == "attributes": |
1344 |
174 |
return self.__get_attributes() |
1345 |
1076277 |
elif attr.startswith('__') and attr.endswith('__'): |
1346 |
1076215 |
raise AttributeError, attr |
1347 |
|
else: |
1348 |
62 |
return self.__dict__[attr] |
1349 |
|
|
1350 |
1 |
def child(self, index): |
1351 |
|
"""Returns the specified child of this object. |
1352 |
|
|
1353 |
|
Arguments: |
1354 |
|
- index: an integer specifying which child to obtain |
1355 |
|
|
1356 |
|
Returns the child at the given index or raise an exception if the |
1357 |
|
index is out of bounds or the child is invalid. |
1358 |
|
""" |
1359 |
|
|
1360 |
|
# [[[TODO: WDW - the AT-SPI appears to give us a different accessible |
1361 |
|
# when we repeatedly ask for the same child of a parent that manages |
1362 |
|
# its descendants. So...we probably shouldn't cache those kind of |
1363 |
|
# children because we're likely to cause a memory leak.]]] |
1364 |
|
# |
1365 |
|
# Save away details we now know about this child |
1366 |
|
# |
1367 |
10972 |
newChild = None |
1368 |
10972 |
if index >= 0 and index < self.accessible.childCount: |
1369 |
10972 |
accChild = self.accessible.getChildAtIndex(index) |
1370 |
10972 |
if accChild: |
1371 |
10972 |
newChild = Accessible.makeAccessible(accChild) |
1372 |
10972 |
newChild.index = index |
1373 |
10972 |
newChild.parent = self |
1374 |
10972 |
newChild.app = self.app |
1375 |
|
|
1376 |
10972 |
if not newChild: |
1377 |
|
# The problem with a child not existing is a bad one. |
1378 |
|
# We want to issue a warning and we also want to know |
1379 |
|
# where it happens. |
1380 |
|
# |
1381 |
0 |
debug.printStack(debug.LEVEL_WARNING) |
1382 |
0 |
debug.println(debug.LEVEL_WARNING, |
1383 |
0 |
"Child at index %d is not an Accessible" % index) |
1384 |
|
|
1385 |
10972 |
return newChild |
1386 |
|
|
1387 |
1 |
def toString(self, indent="", includeApp=True): |
1388 |
|
|
1389 |
|
"""Returns a string, suitable for printing, that describes the |
1390 |
|
given accessible. |
1391 |
|
|
1392 |
|
Arguments: |
1393 |
|
- indent: A string to prefix the output with |
1394 |
|
- includeApp: If True, include information about the app |
1395 |
|
for this accessible. |
1396 |
|
""" |
1397 |
|
|
1398 |
1847 |
if includeApp: |
1399 |
1847 |
if self.app: |
1400 |
1847 |
string = indent + "app.name=%-20s " \ |
1401 |
0 |
% self.app.accessibleNameToString() |
1402 |
|
else: |
1403 |
0 |
string = indent + "app=None " |
1404 |
|
else: |
1405 |
0 |
string = indent |
1406 |
|
|
1407 |
1847 |
string += "name=%s role='%s' state='%s' relations='%s'" \ |
1408 |
|
% (self.accessibleNameToString(), |
1409 |
|
self.role, |
1410 |
|
self.getStateString(), |
1411 |
|
self.getRelationString()) |
1412 |
|
|
1413 |
1835 |
return string |
1414 |
|
|
1415 |
|
######################################################################## |
1416 |
|
# # |
1417 |
|
# Testing functions. # |
1418 |
|
# # |
1419 |
|
######################################################################## |
1420 |
|
|
1421 |
1 |
def __printTopObject(child): |
1422 |
0 |
parent = child |
1423 |
0 |
while parent: |
1424 |
0 |
if not parent.parent: |
1425 |
0 |
print "RAW TOP:", parent.name, parent.role |
1426 |
0 |
parent = parent.parent |
1427 |
0 |
if (child.parent): |
1428 |
0 |
accessible = Accessible.makeAccessible(child) |
1429 |
0 |
app = accessible.app |
1430 |
0 |
print "ACC TOP:", app.name, app.role |
1431 |
|
|
1432 |
1 |
def __printDesktops(): |
1433 |
0 |
registry = Registry().registry |
1434 |
0 |
print "There are %d desktops" % registry.getDesktopCount() |
1435 |
0 |
for i in range(0,registry.getDesktopCount()): |
1436 |
0 |
desktop = registry.getDesktop(i) |
1437 |
0 |
print " Desktop %d (name=%s) has %d apps" \ |
1438 |
|
% (i, desktop.name, desktop.childCount) |
1439 |
0 |
for j in range(0, desktop.childCount): |
1440 |
0 |
app = desktop.getChildAtIndex(j) |
1441 |
0 |
print " App %d: name=%s role=%s" \ |
1442 |
|
% (j, app.name, app.getRoleName()) |
1443 |
|
|
1444 |
1 |
def __notifyEvent(event): |
1445 |
0 |
print event.type, event.source.name, \ |
1446 |
|
event.detail1, event.detail2, \ |
1447 |
|
event.any_data |
1448 |
0 |
__printTopObject(event.source) |
1449 |
0 |
if not event.source.parent: |
1450 |
0 |
print "NO PARENT:", event.source.name, event.source.role |
1451 |
|
|
1452 |
1 |
def __notifyKeystroke(event): |
1453 |
0 |
print "keystroke type=%d hw_code=%d modifiers=%d event_string=(%s) " \ |
1454 |
|
"is_text=%s" \ |
1455 |
|
% (event.type, event.hw_code, event.modifiers, event.event_string, |
1456 |
|
event.is_text) |
1457 |
0 |
if event.event_string == "F12": |
1458 |
0 |
__shutdownAndExit(None, None) |
1459 |
0 |
return False |
1460 |
|
|
1461 |
1 |
def __shutdownAndExit(signum, frame): |
1462 |
0 |
Registry().stop() |
1463 |
0 |
print "Goodbye." |
1464 |
|
|
1465 |
1 |
def __test(): |
1466 |
0 |
eventTypes = [ |
1467 |
|
"focus:", |
1468 |
|
"mouse:rel", |
1469 |
|
"mouse:button", |
1470 |
|
"mouse:abs", |
1471 |
|
"keyboard:modifiers", |
1472 |
|
"object:property-change", |
1473 |
|
"object:property-change:accessible-name", |
1474 |
|
"object:property-change:accessible-description", |
1475 |
|
"object:property-change:accessible-parent", |
1476 |
|
"object:state-changed", |
1477 |
|
"object:state-changed:focused", |
1478 |
|
"object:selection-changed", |
1479 |
|
"object:children-changed" |
1480 |
|
"object:active-descendant-changed" |
1481 |
|
"object:visible-data-changed" |
1482 |
|
"object:text-selection-changed", |
1483 |
|
"object:text-caret-moved", |
1484 |
|
"object:text-changed", |
1485 |
|
"object:column-inserted", |
1486 |
|
"object:row-inserted", |
1487 |
|
"object:column-reordered", |
1488 |
|
"object:row-reordered", |
1489 |
|
"object:column-deleted", |
1490 |
|
"object:row-deleted", |
1491 |
|
"object:model-changed", |
1492 |
|
"object:link-selected", |
1493 |
|
"object:bounds-changed", |
1494 |
|
"window:minimize", |
1495 |
|
"window:maximize", |
1496 |
|
"window:restore", |
1497 |
|
"window:activate", |
1498 |
|
"window:create", |
1499 |
|
"window:deactivate", |
1500 |
|
"window:close", |
1501 |
|
"window:lower", |
1502 |
|
"window:raise", |
1503 |
|
"window:resize", |
1504 |
|
"window:shade", |
1505 |
|
"window:unshade", |
1506 |
|
"object:property-change:accessible-table-summary", |
1507 |
|
"object:property-change:accessible-table-row-header", |
1508 |
|
"object:property-change:accessible-table-column-header", |
1509 |
|
"object:property-change:accessible-table-summary", |
1510 |
|
"object:property-change:accessible-table-row-description", |
1511 |
|
"object:property-change:accessible-table-column-description", |
1512 |
|
"object:test", |
1513 |
|
"window:restyle", |
1514 |
|
"window:desktop-create", |
1515 |
|
"window:desktop-destroy" |
1516 |
|
] |
1517 |
|
|
1518 |
0 |
__printDesktops() |
1519 |
|
|
1520 |
0 |
registry = Registry() |
1521 |
0 |
for eventType in eventTypes: |
1522 |
0 |
registry.registerEventListener(__notifyEvent, eventType) |
1523 |
0 |
registry.registerKeystrokeListeners(__notifyKeystroke) |
1524 |
0 |
registry.start() |
1525 |
|
|
1526 |
1 |
if __name__ == "__main__": |
1527 |
0 |
signal.signal(signal.SIGINT, __shutdownAndExit) |
1528 |
0 |
signal.signal(signal.SIGQUIT, __shutdownAndExit) |
1529 |
0 |
__test() |