1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2004-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 |
"""The main module for the Orca screen reader.""" |
21 |
|
|
22 |
1 |
__id__ = "$Id: orca.py 2529 2007-07-09 14:49:14Z wwalker $" |
23 |
1 |
__version__ = "$Revision: 2529 $" |
24 |
1 |
__date__ = "$Date: 2007-07-09 10:49:14 -0400 (Mon, 09 Jul 2007) $" |
25 |
1 |
__copyright__ = "Copyright (c) 2005-2007 Sun Microsystems Inc." |
26 |
1 |
__license__ = "LGPL" |
27 |
|
|
28 |
|
# We're going to force the name of the app to "orca" so pygtk |
29 |
|
# will end up showing us as "orca" to the AT-SPI. If we don't |
30 |
|
# do this, the name can end up being "-c". See bug 364452 at |
31 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=364452 for more |
32 |
|
# information. |
33 |
|
# |
34 |
1 |
import sys |
35 |
1 |
sys.argv[0] = "orca" |
36 |
|
|
37 |
1 |
try: |
38 |
|
# This can fail due to gtk not being available. We want to |
39 |
|
# be able to recover from that if possible. The main driver |
40 |
|
# for this is to allow "orca --text-setup" to work even if |
41 |
|
# the desktop is not running. |
42 |
|
# |
43 |
1 |
import gtk |
44 |
0 |
except: |
45 |
0 |
pass |
46 |
|
|
47 |
1 |
import getopt |
48 |
1 |
import os |
49 |
1 |
import signal |
50 |
1 |
import time |
51 |
1 |
import unicodedata |
52 |
|
|
53 |
1 |
import atspi |
54 |
1 |
import braille |
55 |
1 |
import debug |
56 |
1 |
import httpserver |
57 |
1 |
import keynames |
58 |
1 |
import keybindings |
59 |
1 |
import mag |
60 |
1 |
import orca_state |
61 |
1 |
import platform |
62 |
1 |
import rolenames |
63 |
1 |
import settings |
64 |
1 |
import speech |
65 |
|
|
66 |
1 |
from input_event import BrailleEvent |
67 |
1 |
from input_event import KeyboardEvent |
68 |
1 |
from input_event import MouseButtonEvent |
69 |
1 |
from input_event import InputEventHandler |
70 |
|
|
71 |
1 |
from orca_i18n import _ # for gettext support |
72 |
|
|
73 |
1 |
if settings.debugMemoryUsage: |
74 |
0 |
import gc |
75 |
0 |
gc.set_debug(gc.DEBUG_UNCOLLECTABLE |
76 |
|
| gc.DEBUG_COLLECTABLE |
77 |
|
| gc.DEBUG_INSTANCES |
78 |
|
| gc.DEBUG_OBJECTS |
79 |
0 |
| gc.DEBUG_SAVEALL) |
80 |
|
|
81 |
|
# The user-settings module (see loadUserSettings). |
82 |
|
# |
83 |
1 |
_userSettings = None |
84 |
|
|
85 |
|
# Command line options that override any other settings. |
86 |
|
# |
87 |
1 |
_commandLineSettings = {} |
88 |
|
|
89 |
|
######################################################################## |
90 |
|
# # |
91 |
|
# METHODS FOR HANDLING PRESENTATION MANAGERS # |
92 |
|
# # |
93 |
|
# A presentation manager is what reacts to AT-SPI object events as # |
94 |
|
# well as user input events (keyboard and Braille) to present info # |
95 |
|
# to the user. # |
96 |
|
# # |
97 |
|
######################################################################## |
98 |
|
|
99 |
|
# The known presentation managers (set up in start()) |
100 |
|
# |
101 |
1 |
_PRESENTATION_MANAGERS = None |
102 |
|
|
103 |
|
# The current presentation manager, which is an index into the |
104 |
|
# _PRESENTATION_MANAGERS list. |
105 |
|
# |
106 |
1 |
_currentPresentationManager = -1 |
107 |
|
|
108 |
1 |
def _switchToPresentationManager(index): |
109 |
|
"""Switches to the given presentation manager. |
110 |
|
|
111 |
|
Arguments: |
112 |
|
- index: an index into _PRESENTATION_MANAGERS |
113 |
|
""" |
114 |
|
|
115 |
|
global _currentPresentationManager |
116 |
|
|
117 |
1 |
if _currentPresentationManager >= 0: |
118 |
0 |
_PRESENTATION_MANAGERS[_currentPresentationManager].deactivate() |
119 |
|
|
120 |
1 |
_currentPresentationManager = index |
121 |
|
|
122 |
|
# Wrap the presenter index around. |
123 |
|
# |
124 |
1 |
if _currentPresentationManager >= len(_PRESENTATION_MANAGERS): |
125 |
0 |
_currentPresentationManager = 0 |
126 |
1 |
elif _currentPresentationManager < 0: |
127 |
0 |
_currentPresentationManager = len(_PRESENTATION_MANAGERS) - 1 |
128 |
|
|
129 |
1 |
_PRESENTATION_MANAGERS[_currentPresentationManager].activate() |
130 |
|
|
131 |
1 |
def _switchToNextPresentationManager(script=None, inputEvent=None): |
132 |
|
"""Switches to the next presentation manager. |
133 |
|
|
134 |
|
Arguments: |
135 |
|
- inputEvent: the InputEvent instance that caused this to be called. |
136 |
|
|
137 |
|
Returns True indicating the event should be consumed. |
138 |
|
""" |
139 |
|
|
140 |
0 |
_switchToPresentationManager(_currentPresentationManager + 1) |
141 |
0 |
return True |
142 |
|
|
143 |
|
######################################################################## |
144 |
|
# # |
145 |
|
# METHODS TO HANDLE APPLICATION LIST AND FOCUSED OBJECTS # |
146 |
|
# # |
147 |
|
######################################################################## |
148 |
|
|
149 |
1 |
def setLocusOfFocus(event, obj, notifyPresentationManager=True): |
150 |
|
"""Sets the locus of focus (i.e., the object with visual focus) and |
151 |
|
notifies the current presentation manager of the change. |
152 |
|
|
153 |
|
Arguments: |
154 |
|
- event: if not None, the Event that caused this to happen |
155 |
|
- obj: the Accessible with the new locus of focus. |
156 |
|
- notifyPresentationManager: if True, propagate this event |
157 |
|
""" |
158 |
|
|
159 |
1047 |
if obj == orca_state.locusOfFocus: |
160 |
144 |
return |
161 |
|
|
162 |
903 |
oldLocusOfFocus = orca_state.locusOfFocus |
163 |
903 |
if oldLocusOfFocus and not oldLocusOfFocus.valid: |
164 |
0 |
oldLocusOfFocus = None |
165 |
|
|
166 |
903 |
orca_state.locusOfFocus = obj |
167 |
903 |
if orca_state.locusOfFocus and not orca_state.locusOfFocus.valid: |
168 |
0 |
orca_state.locusOfFocus = None |
169 |
|
|
170 |
903 |
if orca_state.locusOfFocus: |
171 |
825 |
appname = "" |
172 |
825 |
if not orca_state.locusOfFocus.app: |
173 |
0 |
appname = "None" |
174 |
|
else: |
175 |
825 |
appname = "'" + orca_state.locusOfFocus.app.name + "'" |
176 |
|
|
177 |
825 |
debug.println(debug.LEVEL_FINE, |
178 |
825 |
"LOCUS OF FOCUS: app=%s name='%s' role='%s'" \ |
179 |
825 |
% (appname, |
180 |
825 |
orca_state.locusOfFocus.name, |
181 |
825 |
orca_state.locusOfFocus.role)) |
182 |
|
|
183 |
825 |
if event: |
184 |
825 |
debug.println(debug.LEVEL_FINE, |
185 |
825 |
" event='%s'" % event.type) |
186 |
|
else: |
187 |
0 |
debug.println(debug.LEVEL_FINE, |
188 |
0 |
" event=None") |
189 |
|
else: |
190 |
78 |
if event: |
191 |
78 |
debug.println(debug.LEVEL_FINE, |
192 |
78 |
"LOCUS OF FOCUS: None event='%s'" % event.type) |
193 |
|
else: |
194 |
0 |
debug.println(debug.LEVEL_FINE, |
195 |
0 |
"LOCUS OF FOCUS: None event=None") |
196 |
|
|
197 |
903 |
if notifyPresentationManager and _currentPresentationManager >= 0: |
198 |
903 |
_PRESENTATION_MANAGERS[_currentPresentationManager].\ |
199 |
903 |
locusOfFocusChanged(event, |
200 |
903 |
oldLocusOfFocus, |
201 |
903 |
orca_state.locusOfFocus) |
202 |
|
|
203 |
1 |
def visualAppearanceChanged(event, obj): |
204 |
|
"""Called (typically by scripts) when the visual appearance of an object |
205 |
|
changes and notifies the current presentation manager of the change. This |
206 |
|
method should not be called for objects whose visual appearance changes |
207 |
|
solely because of focus -- setLocusOfFocus is used for that. Instead, it |
208 |
|
is intended mostly for objects whose notional 'value' has changed, such as |
209 |
|
a checkbox changing state, a progress bar advancing, a slider moving, text |
210 |
|
inserted, caret moved, etc. |
211 |
|
|
212 |
|
Arguments: |
213 |
|
- event: if not None, the Event that caused this to happen |
214 |
|
- obj: the Accessible whose visual appearance changed. |
215 |
|
""" |
216 |
|
|
217 |
841 |
if _currentPresentationManager >= 0: |
218 |
841 |
_PRESENTATION_MANAGERS[_currentPresentationManager].\ |
219 |
841 |
visualAppearanceChanged(event, obj) |
220 |
|
|
221 |
1 |
def _onChildrenChanged(e): |
222 |
|
"""Tracks children-changed events on the desktop to determine when |
223 |
|
apps start and stop. |
224 |
|
|
225 |
|
Arguments: |
226 |
|
- e: at-spi event from the at-api registry |
227 |
|
""" |
228 |
|
|
229 |
3615 |
registry = atspi.Registry() |
230 |
3615 |
if e.source == registry.desktop: |
231 |
|
|
232 |
|
# If the desktop is empty, the user has logged out-- shutdown Orca |
233 |
|
# |
234 |
149 |
try: |
235 |
149 |
if registry.desktop.childCount == 0: |
236 |
0 |
speech.speak(_("Goodbye.")) |
237 |
0 |
shutdown() |
238 |
0 |
return |
239 |
0 |
except: # could be a CORBA.COMM_FAILURE |
240 |
0 |
debug.printException(debug.LEVEL_FINEST) |
241 |
0 |
shutdown() |
242 |
0 |
return |
243 |
|
|
244 |
1 |
def _onMouseButton(e): |
245 |
|
"""Tracks mouse button events, stopping any speech in progress. |
246 |
|
|
247 |
|
Arguments: |
248 |
|
- e: at-spi event from the at-api registry |
249 |
|
""" |
250 |
|
|
251 |
0 |
event = atspi.Event(e) |
252 |
0 |
orca_state.lastInputEvent = MouseButtonEvent(event) |
253 |
|
|
254 |
|
# A mouse button event looks like: mouse:button:1p, where the |
255 |
|
# number is the button number and the 'p' is either 'p' or 'r', |
256 |
|
# meaning pressed or released. We only want to stop speech on |
257 |
|
# button presses. |
258 |
|
# |
259 |
0 |
if event.type.endswith("p"): |
260 |
0 |
speech.stop() |
261 |
|
|
262 |
|
######################################################################## |
263 |
|
# # |
264 |
|
# Keyboard Event Recording Support # |
265 |
|
# # |
266 |
|
######################################################################## |
267 |
|
|
268 |
1 |
_recordingKeystrokes = False |
269 |
1 |
_keystrokesFile = None |
270 |
|
|
271 |
1 |
def _closeKeystrokeWindowAndRecord(entry, window): |
272 |
|
global _keystrokesFile |
273 |
0 |
window.destroy() |
274 |
0 |
entry_text = entry.get_text() |
275 |
0 |
_keystrokesFile = open(entry_text, 'w') |
276 |
|
|
277 |
1 |
def _closeKeystrokeWindowAndCancel(window): |
278 |
|
global _recordingKeystrokes |
279 |
0 |
window.destroy() |
280 |
0 |
_recordingKeystrokes = False |
281 |
|
|
282 |
1 |
def toggleKeystrokeRecording(script=None, inputEvent=None): |
283 |
|
"""Toggles the recording of keystrokes on and off. When the |
284 |
|
user presses the magic key (Pause), Orca will pop up a window |
285 |
|
requesting a filename. When the user presses the close button, |
286 |
|
Orca will start recording keystrokes to the file and will continue |
287 |
|
recording them until the user presses the magic key again. |
288 |
|
|
289 |
|
This functionality is used primarily to help gather keystroke |
290 |
|
information for regression testing purposes. The keystrokes are |
291 |
|
recorded in such a way that they can be played back via the |
292 |
|
src/tools/play_keystrokes.py utility. |
293 |
|
|
294 |
|
Arguments: |
295 |
|
- inputEvent: the key event (if any) which caused this to be called. |
296 |
|
|
297 |
|
Returns True indicating the event should be consumed. |
298 |
|
""" |
299 |
|
|
300 |
|
global _recordingKeystrokes |
301 |
|
global _keystrokesFile |
302 |
|
|
303 |
0 |
if _recordingKeystrokes: |
304 |
|
# If the filename entry window is still up, we don't have a file |
305 |
|
# yet. |
306 |
|
# |
307 |
0 |
if _keystrokesFile: |
308 |
0 |
_keystrokesFile.close() |
309 |
0 |
_keystrokesFile = None |
310 |
0 |
_recordingKeystrokes = False |
311 |
|
else: |
312 |
0 |
_recordingKeystrokes = True |
313 |
0 |
window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
314 |
0 |
window.set_title("Keystroke Filename") |
315 |
|
|
316 |
0 |
vbox = gtk.VBox(False, 0) |
317 |
0 |
window.add(vbox) |
318 |
0 |
vbox.show() |
319 |
|
|
320 |
0 |
entry = gtk.Entry() |
321 |
0 |
entry.set_max_length(50) |
322 |
0 |
entry.set_editable(True) |
323 |
0 |
entry.set_text("keystrokes.txt") |
324 |
0 |
entry.select_region(0, len(entry.get_text())) |
325 |
|
# For now, do not allow "Return" to close the window - the reason |
326 |
|
# for this is that the key press closes the window, and the key |
327 |
|
# release will end up getting recorded. |
328 |
|
# |
329 |
|
#entry.connect("activate", _closeKeystrokeWindow, window) |
330 |
0 |
vbox.pack_start(entry, True, True, 0) |
331 |
0 |
entry.show() |
332 |
|
|
333 |
0 |
hbox = gtk.HBox(False, 0) |
334 |
0 |
vbox.add(hbox) |
335 |
0 |
hbox.show() |
336 |
|
|
337 |
0 |
ok = gtk.Button(stock=gtk.STOCK_OK) |
338 |
0 |
ok.connect("clicked", lambda w: _closeKeystrokeWindowAndRecord(\ |
339 |
0 |
entry, \ |
340 |
0 |
window)) |
341 |
|
|
342 |
0 |
cancel = gtk.Button(stock=gtk.STOCK_CANCEL) |
343 |
0 |
cancel.connect("clicked", lambda w: _closeKeystrokeWindowAndCancel(\ |
344 |
0 |
window)) |
345 |
|
|
346 |
0 |
vbox.pack_start(cancel, True, True, 0) |
347 |
0 |
vbox.pack_start(ok, True, True, 0) |
348 |
|
|
349 |
0 |
ok.set_flags(gtk.CAN_DEFAULT) |
350 |
0 |
ok.grab_default() |
351 |
0 |
ok.show() |
352 |
0 |
cancel.show() |
353 |
|
|
354 |
0 |
window.set_modal(True) |
355 |
0 |
window.show() |
356 |
0 |
return True |
357 |
|
|
358 |
|
######################################################################## |
359 |
|
# # |
360 |
|
# DEBUG support. # |
361 |
|
# # |
362 |
|
######################################################################## |
363 |
|
|
364 |
1 |
def cycleDebugLevel(script=None, inputEvent=None): |
365 |
|
global _debugLevel |
366 |
|
|
367 |
0 |
level = debug.debugLevel |
368 |
|
|
369 |
0 |
if level == debug.LEVEL_ALL: |
370 |
0 |
level = debug.LEVEL_FINEST |
371 |
0 |
elif level == debug.LEVEL_FINEST: |
372 |
0 |
level = debug.LEVEL_FINER |
373 |
0 |
elif level == debug.LEVEL_FINER: |
374 |
0 |
level = debug.LEVEL_FINE |
375 |
0 |
elif level == debug.LEVEL_FINE: |
376 |
0 |
level = debug.LEVEL_CONFIGURATION |
377 |
0 |
elif level == debug.LEVEL_CONFIGURATION: |
378 |
0 |
level = debug.LEVEL_INFO |
379 |
0 |
elif level == debug.LEVEL_INFO: |
380 |
0 |
level = debug.LEVEL_WARNING |
381 |
0 |
elif level == debug.LEVEL_WARNING: |
382 |
0 |
level = debug.LEVEL_SEVERE |
383 |
0 |
elif level == debug.LEVEL_SEVERE: |
384 |
0 |
level = debug.LEVEL_OFF |
385 |
0 |
elif level == debug.LEVEL_OFF: |
386 |
0 |
level = debug.LEVEL_ALL |
387 |
|
|
388 |
0 |
debug.debugLevel = level |
389 |
|
|
390 |
0 |
if level == debug.LEVEL_ALL: |
391 |
0 |
speech.speak("Debug level all.") |
392 |
0 |
elif level == debug.LEVEL_FINEST: |
393 |
0 |
speech.speak("Debug level finest.") |
394 |
0 |
elif level == debug.LEVEL_FINER: |
395 |
0 |
speech.speak("Debug level finer.") |
396 |
0 |
elif level == debug.LEVEL_FINE: |
397 |
0 |
speech.speak("Debug level fine.") |
398 |
0 |
elif level == debug.LEVEL_CONFIGURATION: |
399 |
0 |
speech.speak("Debug level configuration.") |
400 |
0 |
elif level == debug.LEVEL_INFO: |
401 |
0 |
speech.speak("Debug level info.") |
402 |
0 |
elif level == debug.LEVEL_WARNING: |
403 |
0 |
speech.speak("Debug level warning.") |
404 |
0 |
elif level == debug.LEVEL_SEVERE: |
405 |
0 |
speech.speak("Debug level severe.") |
406 |
0 |
elif level == debug.LEVEL_OFF: |
407 |
0 |
speech.speak("Debug level off.") |
408 |
|
|
409 |
0 |
return True |
410 |
|
|
411 |
|
######################################################################## |
412 |
|
# # |
413 |
|
# METHODS FOR PRE-PROCESSING AND MASSAGING KEYBOARD EVENTS. # |
414 |
|
# # |
415 |
|
# All keyboard events are funnelled through here first. Orca itself # |
416 |
|
# might have global keybindings (e.g., to switch between presenters), # |
417 |
|
# but it will typically pass the event onto the currently active # |
418 |
|
# active presentation manager. # |
419 |
|
# # |
420 |
|
######################################################################## |
421 |
|
|
422 |
|
# Keybindings that Orca itself cares about. |
423 |
|
# |
424 |
1 |
_keyBindings = None |
425 |
|
|
426 |
|
# True if the orca modifier key is currently pressed. |
427 |
|
# |
428 |
1 |
_orcaModifierPressed = False |
429 |
|
|
430 |
1 |
def _isPrintableKey(event_string): |
431 |
|
"""Return an indication of whether this is an alphanumeric or |
432 |
|
punctuation key. |
433 |
|
|
434 |
|
Arguments: |
435 |
|
- event: the event string |
436 |
|
|
437 |
|
Returns True if this is an alphanumeric or punctuation key. |
438 |
|
""" |
439 |
|
|
440 |
0 |
if event_string == "space": |
441 |
0 |
reply = True |
442 |
|
else: |
443 |
0 |
unicodeString = event_string.decode("UTF-8") |
444 |
0 |
reply = (len(unicodeString) == 1) \ |
445 |
0 |
and (unicodeString.isalnum() or unicodeString.isspace() |
446 |
0 |
or unicodedata.category(unicodeString)[0] in ('P', 'S')) |
447 |
0 |
debug.println(debug.LEVEL_FINEST, |
448 |
0 |
"orca._echoPrintableKey: returning: %s" % reply) |
449 |
0 |
return reply |
450 |
|
|
451 |
1 |
def _isModifierKey(event_string): |
452 |
|
"""Return an indication of whether this is a modifier key. |
453 |
|
|
454 |
|
Arguments: |
455 |
|
- event: the event string |
456 |
|
|
457 |
|
Returns True if this is a modifier key |
458 |
|
""" |
459 |
|
|
460 |
|
# [[[TODO:richb - the Fn key on my laptop doesn't seem to generate an |
461 |
|
# event.]]] |
462 |
|
|
463 |
3579 |
modifierKeys = [ 'Alt_L', 'Alt_R', 'Control_L', 'Control_R', \ |
464 |
3579 |
'Shift_L', 'Shift_R', 'Meta_L', 'Meta_R' ] |
465 |
3579 |
modifierKeys.extend(settings.orcaModifierKeys) |
466 |
|
|
467 |
3579 |
reply = event_string in modifierKeys |
468 |
3579 |
debug.println(debug.LEVEL_FINEST, |
469 |
3579 |
"orca._echoModifierKey: returning: %s" % reply) |
470 |
3579 |
return reply |
471 |
|
|
472 |
1 |
def _isLockingKey(event_string): |
473 |
|
"""Return an indication of whether this is a locking key. |
474 |
|
|
475 |
|
Arguments: |
476 |
|
- event: the event string |
477 |
|
|
478 |
|
Returns True if this is a locking key. |
479 |
|
""" |
480 |
|
|
481 |
0 |
lockingKeys = [ "Caps_Lock", "Num_Lock", "Scroll_Lock" ] |
482 |
|
|
483 |
0 |
reply = event_string in lockingKeys \ |
484 |
0 |
and not event_string in settings.orcaModifierKeys |
485 |
0 |
debug.println(debug.LEVEL_FINEST, |
486 |
0 |
"orca._echoLockingKey: returning: %s" % reply) |
487 |
0 |
return reply |
488 |
|
|
489 |
1 |
def _isFunctionKey(event_string): |
490 |
|
"""Return an indication of whether this is a function key. |
491 |
|
|
492 |
|
Arguments: |
493 |
|
- event: the event string |
494 |
|
|
495 |
|
Returns True if this is a function key. |
496 |
|
""" |
497 |
|
|
498 |
|
# [[[TODO:richb - what should be done about the function keys on the left |
499 |
|
# side of my Sun keyboard and the other keys (like Scroll Lock), which |
500 |
|
# generate "Fn" key events?]]] |
501 |
|
|
502 |
0 |
functionKeys = [ "F1", "F2", "F3", "F4", "F5", "F6", |
503 |
0 |
"F7", "F8", "F9", "F10", "F11", "F12" ] |
504 |
|
|
505 |
0 |
reply = event_string in functionKeys |
506 |
0 |
debug.println(debug.LEVEL_FINEST, |
507 |
0 |
"orca._echoFunctionKey: returning: %s" % reply) |
508 |
0 |
return reply |
509 |
|
|
510 |
1 |
def _isActionKey(event_string): |
511 |
|
"""Return an indication of whether this is an action key. |
512 |
|
|
513 |
|
Arguments: |
514 |
|
- event: the event string |
515 |
|
|
516 |
|
Returns True if this is an action key. |
517 |
|
""" |
518 |
|
|
519 |
0 |
actionKeys = [ "Return", "Escape", "Tab", "BackSpace", "Delete", |
520 |
0 |
"Page_Up", "Page_Down", "Home", "End" ] |
521 |
|
|
522 |
0 |
reply = event_string in actionKeys |
523 |
0 |
debug.println(debug.LEVEL_FINEST, |
524 |
0 |
"orca._echoActionKey: returning: %s" % reply) |
525 |
0 |
return reply |
526 |
|
|
527 |
2 |
class KeyEventType: |
528 |
1 |
"""Definition of available key event types.""" |
529 |
|
|
530 |
|
"""An alphanumeric or punctuation key event.""" |
531 |
1 |
PRINTABLE = 'PRINTABLE' |
532 |
|
|
533 |
|
"""A modifier key event.""" |
534 |
1 |
MODIFIER = 'MODIFIER' |
535 |
|
|
536 |
|
"""A locking key event.""" |
537 |
1 |
LOCKING = 'LOCKING' |
538 |
|
|
539 |
|
"""A locking key lock event.""" |
540 |
1 |
LOCKING_LOCKED = 'LOCKING_LOCKED' |
541 |
|
|
542 |
|
"""A locking key unlock event.""" |
543 |
1 |
LOCKING_UNLOCKED = 'LOCKING_UNLOCKED' |
544 |
|
|
545 |
|
"""A function key event.""" |
546 |
1 |
FUNCTION = 'FUNCTION' |
547 |
|
|
548 |
|
"""An action key event.""" |
549 |
1 |
ACTION = 'ACTION' |
550 |
|
|
551 |
1 |
def _keyEcho(event): |
552 |
|
"""If the keyEcho setting is enabled, check to see what type of key |
553 |
|
event it is and echo it via speech, if the user wants that type of |
554 |
|
key echoed. |
555 |
|
|
556 |
|
Uppercase keys will be spoken using the "uppercase" voice style, |
557 |
|
whereas lowercase keys will be spoken using the "default" voice style. |
558 |
|
|
559 |
|
Arguments: |
560 |
|
- event: an AT-SPI DeviceEvent |
561 |
|
""" |
562 |
|
|
563 |
|
# If this keyboard event was for an object like a password text |
564 |
|
# field, then don't echo it. |
565 |
|
# |
566 |
1802 |
if orca_state.locusOfFocus \ |
567 |
1800 |
and (orca_state.locusOfFocus.role == rolenames.ROLE_PASSWORD_TEXT): |
568 |
0 |
return |
569 |
|
|
570 |
1802 |
event_string = event.event_string |
571 |
1802 |
debug.println(debug.LEVEL_FINEST, |
572 |
1802 |
"orca._keyEcho: string to echo: %s" % event_string) |
573 |
|
|
574 |
|
# If key echo is enabled, then check to see what type of key event |
575 |
|
# it is and echo it via speech, if the user wants that type of key |
576 |
|
# echoed. |
577 |
|
# |
578 |
1802 |
if settings.enableKeyEcho: |
579 |
|
|
580 |
0 |
if _isPrintableKey(event_string): |
581 |
0 |
if not settings.enablePrintableKeys: |
582 |
0 |
return |
583 |
0 |
type = KeyEventType.PRINTABLE |
584 |
|
|
585 |
0 |
elif _isModifierKey(event_string): |
586 |
0 |
if not settings.enableModifierKeys: |
587 |
0 |
return |
588 |
0 |
type = KeyEventType.MODIFIER |
589 |
|
|
590 |
0 |
elif _isLockingKey(event_string): |
591 |
0 |
if not settings.enableLockingKeys: |
592 |
0 |
return |
593 |
0 |
type = KeyEventType.LOCKING |
594 |
|
|
595 |
0 |
modifiers = event.modifiers |
596 |
|
|
597 |
0 |
if event_string == "Caps_Lock": |
598 |
0 |
if modifiers & (1 << atspi.Accessibility.MODIFIER_SHIFTLOCK): |
599 |
0 |
type = KeyEventType.LOCKING_UNLOCKED |
600 |
|
else: |
601 |
0 |
type = KeyEventType.LOCKING_LOCKED |
602 |
|
|
603 |
0 |
elif event_string == "Num_Lock": |
604 |
|
# [[[TODO: richb - we are not getting a correct modifier |
605 |
|
# state value returned when Num Lock is turned off. |
606 |
|
# Commenting out the speaking of the bogus on/off state |
607 |
|
# until this can be fixed.]]] |
608 |
|
# |
609 |
|
#if modifiers & (1 << atspi.Accessibility.MODIFIER_NUMLOCK): |
610 |
|
# type = KeyEventType.LOCKING_UNLOCKED |
611 |
|
#else: |
612 |
|
# type = KeyEventType.LOCKING_LOCKED |
613 |
0 |
pass |
614 |
|
|
615 |
0 |
elif _isFunctionKey(event_string): |
616 |
0 |
if not settings.enableFunctionKeys: |
617 |
0 |
return |
618 |
0 |
type = KeyEventType.FUNCTION |
619 |
|
|
620 |
0 |
elif _isActionKey(event_string): |
621 |
0 |
if not settings.enableActionKeys: |
622 |
0 |
return |
623 |
0 |
type = KeyEventType.ACTION |
624 |
|
|
625 |
|
else: |
626 |
0 |
debug.println(debug.LEVEL_FINEST, |
627 |
0 |
"orca._keyEcho: event string not handled: %s" % event_string) |
628 |
0 |
return |
629 |
|
|
630 |
0 |
debug.println(debug.LEVEL_FINEST, |
631 |
0 |
"orca._keyEcho: speaking: %s" % event_string) |
632 |
|
|
633 |
|
# We keep track of the time as means to let others know that |
634 |
|
# we are probably echoing a key and should not be interrupted. |
635 |
|
# |
636 |
0 |
orca_state.lastKeyEchoTime = time.time() |
637 |
|
|
638 |
0 |
speech.speakKeyEvent(event_string, type) |
639 |
|
|
640 |
1 |
def _processKeyCaptured(event): |
641 |
|
"""Called when a new key event arrives and orca_state.capturingKeys=True. |
642 |
|
(used for key bindings redefinition) |
643 |
|
""" |
644 |
|
|
645 |
0 |
if event.type == 0: |
646 |
0 |
if _isModifierKey(event.event_string) \ |
647 |
0 |
or event.event_string == "Return": |
648 |
0 |
pass |
649 |
0 |
elif event.event_string == "Escape": |
650 |
0 |
orca_state.capturingKeys = False |
651 |
|
else: |
652 |
|
# Translators: this is a spoken prompt letting the user know |
653 |
|
# Orca has recorded a new key combination (e.g., Alt+Ctrl+g) |
654 |
|
# based upon their input. |
655 |
|
# |
656 |
0 |
speech.speak(_("Key captured: %s. Press enter to confirm.") \ |
657 |
0 |
% str(event.event_string)) |
658 |
0 |
orca_state.lastCapturedKey = event |
659 |
|
else: |
660 |
|
pass |
661 |
0 |
return False |
662 |
|
|
663 |
1 |
def _processKeyboardEvent(event): |
664 |
|
"""The primary key event handler for Orca. Keeps track of various |
665 |
|
attributes, such as the lastInputEvent. Also calls keyEcho as well |
666 |
|
as any local keybindings before passing the event on to the active |
667 |
|
presentation manager. This method is called synchronously from the |
668 |
|
AT-SPI registry and should be performant. In addition, it |
669 |
|
must return True if it has consumed the event (and False if not). |
670 |
|
|
671 |
|
Arguments: |
672 |
|
- event: an AT-SPI DeviceEvent |
673 |
|
|
674 |
|
Returns True if the event should be consumed. |
675 |
|
""" |
676 |
|
global _orcaModifierPressed |
677 |
|
|
678 |
3579 |
orca_state.lastInputEventTimestamp = event.timestamp |
679 |
|
|
680 |
|
# Log the keyboard event for future playback, if desired. |
681 |
|
# Note here that the key event_string being output is |
682 |
|
# exactly what we received. The KeyboardEvent object, |
683 |
|
# however, will translate the event_string for control |
684 |
|
# characters to their upper case ASCII equivalent. |
685 |
|
# |
686 |
3579 |
string = atspi.KeystrokeListener.keyEventToString(event) |
687 |
3579 |
if _recordingKeystrokes and _keystrokesFile \ |
688 |
0 |
and (event.event_string != "Pause") \ |
689 |
0 |
and (event.event_string != "F21"): |
690 |
0 |
_keystrokesFile.write(string + "\n") |
691 |
3579 |
debug.printInputEvent(debug.LEVEL_FINE, string) |
692 |
|
|
693 |
3579 |
keyboardEvent = KeyboardEvent(event) |
694 |
|
|
695 |
|
# See if this is one of our special Orca modifier keys. |
696 |
|
# |
697 |
|
# [[[TODO: WDW - Note that just looking at the keycode should |
698 |
|
# suffice, but there is a "feature" in the Java Access Bridge |
699 |
|
# where it chooses to emit Java platform-independent keycodes |
700 |
|
# instead of the keycodes for the base platform: |
701 |
|
# |
702 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=106004 |
703 |
|
# http://bugzilla.gnome.org/show_bug.cgi?id=318615 |
704 |
|
# |
705 |
|
# So...we need to workaround this problem. |
706 |
|
# |
707 |
|
# If you make the following expression True we will get a positive |
708 |
|
# match for all keysyms associated with a given keysym specified |
709 |
|
# as an Orca modifier key. |
710 |
|
# |
711 |
|
# For example, assume the Orca modifier is set to \ for some |
712 |
|
# reason. The key that has \ on it produces \ without the Shift |
713 |
|
# key and | with the Shift key. If the following expression is |
714 |
|
# True, both the \ and | will be viewed as the Orca modifier. If |
715 |
|
# the following expression is False, only the \ will be viewed as |
716 |
|
# the Orca modifier (i.e., Shift+\ will still function as the | |
717 |
|
# character). In general, I think we want to avoid sucking in all |
718 |
|
# possible keysyms because it can have unexpected results.]]] |
719 |
|
# |
720 |
3579 |
if False: |
721 |
0 |
allPossibleKeysyms = [] |
722 |
0 |
for keysym in settings.orcaModifierKeys: |
723 |
0 |
allPossibleKeysyms.extend(keybindings.getAllKeysyms(keysym)) |
724 |
|
else: |
725 |
3579 |
allPossibleKeysyms = settings.orcaModifierKeys |
726 |
|
|
727 |
3579 |
isOrcaModifier = allPossibleKeysyms.count(keyboardEvent.event_string) > 0 |
728 |
|
|
729 |
3579 |
if event.type == atspi.Accessibility.KEY_PRESSED_EVENT: |
730 |
|
# Key presses always interrupt speech. |
731 |
|
# |
732 |
1802 |
speech.stop() |
733 |
|
|
734 |
|
# If learn mode is enabled, it will echo the keys. |
735 |
|
# |
736 |
1802 |
if not settings.learnModeEnabled: |
737 |
1802 |
_keyEcho(keyboardEvent) |
738 |
|
|
739 |
|
# We treat the Insert key as a modifier - so just swallow it and |
740 |
|
# set our internal state. |
741 |
|
# |
742 |
1802 |
if isOrcaModifier: |
743 |
5 |
_orcaModifierPressed = True |
744 |
|
|
745 |
1777 |
elif isOrcaModifier \ |
746 |
5 |
and (keyboardEvent.type == atspi.Accessibility.KEY_RELEASED_EVENT): |
747 |
5 |
_orcaModifierPressed = False |
748 |
|
|
749 |
3579 |
if _orcaModifierPressed: |
750 |
10 |
keyboardEvent.modifiers = keyboardEvent.modifiers \ |
751 |
10 |
| (1 << settings.MODIFIER_ORCA) |
752 |
|
|
753 |
|
# Orca gets first stab at the event. Then, the presenter gets |
754 |
|
# a shot. [[[TODO: WDW - might want to let the presenter try first? |
755 |
|
# The main reason this is staying as is is that we may not want |
756 |
|
# scripts to override fundamental Orca key bindings.]]] |
757 |
|
# |
758 |
3579 |
consumed = False |
759 |
3579 |
try: |
760 |
3579 |
if orca_state.capturingKeys: |
761 |
0 |
_processKeyCaptured(keyboardEvent) |
762 |
|
else: |
763 |
3579 |
consumed = _keyBindings.consumeKeyboardEvent(None, keyboardEvent) |
764 |
3579 |
if (not consumed) and (_currentPresentationManager >= 0): |
765 |
3579 |
consumed = _PRESENTATION_MANAGERS[_currentPresentationManager].\ |
766 |
3579 |
processKeyboardEvent(keyboardEvent) |
767 |
3579 |
if (not consumed) and settings.learnModeEnabled: |
768 |
0 |
if keyboardEvent.type \ |
769 |
0 |
== atspi.Accessibility.KEY_PRESSED_EVENT: |
770 |
0 |
clickCount = orca_state.activeScript.getClickCount(\ |
771 |
0 |
orca_state.lastInputEvent, |
772 |
0 |
keyboardEvent) |
773 |
0 |
if clickCount == 2: |
774 |
0 |
orca_state.activeScript.phoneticSpellCurrentItem(\ |
775 |
0 |
keyboardEvent.event_string) |
776 |
|
else: |
777 |
|
# Check to see if there are localized words to be |
778 |
|
# spoken for this key event. |
779 |
|
# |
780 |
0 |
braille.displayMessage(keyboardEvent.event_string) |
781 |
0 |
event_string = keyboardEvent.event_string |
782 |
0 |
event_string = keynames.getKeyName(event_string) |
783 |
0 |
speech.speak(event_string) |
784 |
0 |
consumed = True |
785 |
0 |
except: |
786 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
787 |
|
|
788 |
3579 |
orca_state.lastInputEvent = keyboardEvent |
789 |
|
|
790 |
|
# If this is a key event for a non-modifier key, save a handle to it. |
791 |
|
# This is needed to help determine user actions when a multi-key chord |
792 |
|
# has been pressed, and we might get the key events in different orders. |
793 |
|
# See comment #15 of bug #435201 for more details. |
794 |
|
# |
795 |
3579 |
if not _isModifierKey(keyboardEvent.event_string): |
796 |
3292 |
orca_state.lastNonModifierKeyEvent = keyboardEvent |
797 |
|
|
798 |
3579 |
return consumed or isOrcaModifier |
799 |
|
|
800 |
|
######################################################################## |
801 |
|
# # |
802 |
|
# METHODS FOR PRE-PROCESSING AND MASSAGING BRAILLE EVENTS. # |
803 |
|
# # |
804 |
|
######################################################################## |
805 |
|
|
806 |
1 |
def _processBrailleEvent(command): |
807 |
|
"""Called whenever a key is pressed on the Braille display. |
808 |
|
|
809 |
|
Arguments: |
810 |
|
- command: the BrlAPI command for the key that was pressed. |
811 |
|
|
812 |
|
Returns True if the event was consumed; otherwise False |
813 |
|
""" |
814 |
|
|
815 |
|
# [[[TODO: WDW - probably should add braille bindings to this module.]]] |
816 |
|
|
817 |
0 |
consumed = False |
818 |
|
|
819 |
|
# Braille key presses always interrupt speech. |
820 |
|
# |
821 |
0 |
speech.stop() |
822 |
|
|
823 |
0 |
event = BrailleEvent(command) |
824 |
0 |
orca_state.lastInputEvent = event |
825 |
|
|
826 |
0 |
try: |
827 |
0 |
consumed = _PRESENTATION_MANAGERS[_currentPresentationManager].\ |
828 |
0 |
processBrailleEvent(event) |
829 |
0 |
except: |
830 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
831 |
|
|
832 |
0 |
if (not consumed) and settings.learnModeEnabled: |
833 |
0 |
consumed = True |
834 |
|
|
835 |
0 |
return consumed |
836 |
|
|
837 |
|
######################################################################## |
838 |
|
# # |
839 |
|
# METHODS FOR HANDLING INITIALIZATION, SHUTDOWN, AND USE. # |
840 |
|
# # |
841 |
|
######################################################################## |
842 |
|
|
843 |
1 |
def _toggleSilenceSpeech(script=None, inputEvent=None): |
844 |
|
"""Toggle the silencing of speech. |
845 |
|
|
846 |
|
Returns True to indicate the input event has been consumed. |
847 |
|
""" |
848 |
0 |
speech.stop() |
849 |
0 |
if settings.silenceSpeech: |
850 |
0 |
settings.silenceSpeech = False |
851 |
|
# Translators: this is a spoken prompt letting the user know |
852 |
|
# that speech synthesis has been turned back on. |
853 |
|
# |
854 |
0 |
speech.speak(_("Speech enabled.")) |
855 |
|
else: |
856 |
|
# Translators: this is a spoken prompt letting the user know |
857 |
|
# that speech synthesis has been temporarily turned off. |
858 |
|
# |
859 |
0 |
speech.speak(_("Speech disabled.")) |
860 |
0 |
settings.silenceSpeech = True |
861 |
0 |
return True |
862 |
|
|
863 |
1 |
def loadUserSettings(script=None, inputEvent=None): |
864 |
|
"""Loads (and reloads) the user settings module, reinitializing |
865 |
|
things such as speech if necessary. |
866 |
|
|
867 |
|
Returns True to indicate the input event has been consumed. |
868 |
|
""" |
869 |
|
|
870 |
|
global _userSettings |
871 |
|
|
872 |
|
# Shutdown the output drivers and give them a chance to die. |
873 |
|
# |
874 |
1 |
httpserver.shutdown() |
875 |
1 |
speech.shutdown() |
876 |
1 |
braille.shutdown() |
877 |
1 |
mag.shutdown() |
878 |
|
|
879 |
1 |
if _currentPresentationManager >= 0: |
880 |
0 |
_PRESENTATION_MANAGERS[_currentPresentationManager].deactivate() |
881 |
|
|
882 |
1 |
time.sleep(1) |
883 |
|
|
884 |
1 |
reloaded = False |
885 |
1 |
if _userSettings: |
886 |
0 |
try: |
887 |
0 |
reload(_userSettings) |
888 |
0 |
reloaded = True |
889 |
0 |
except ImportError: |
890 |
0 |
debug.printException(debug.LEVEL_FINEST) |
891 |
|
else: |
892 |
1 |
try: |
893 |
1 |
_userSettings = __import__("user-settings") |
894 |
0 |
except ImportError: |
895 |
0 |
debug.printException(debug.LEVEL_FINEST) |
896 |
|
|
897 |
|
# If any settings were added to the command line, they take |
898 |
|
# precedence over everything else. |
899 |
|
# |
900 |
1 |
for key in _commandLineSettings: |
901 |
0 |
settings.__dict__[key] = _commandLineSettings[key] |
902 |
|
|
903 |
1 |
if settings.enableSpeech: |
904 |
1 |
try: |
905 |
1 |
speech.init() |
906 |
1 |
if reloaded: |
907 |
|
# Translators: there is a keystroke to reload the user |
908 |
|
# preferences. This is a spoken prompt to let the user |
909 |
|
# know when the preferences has been reloaded. |
910 |
|
# |
911 |
0 |
speech.speak(_("Orca user settings reloaded.")) |
912 |
1 |
debug.println(debug.LEVEL_CONFIGURATION, |
913 |
1 |
"Speech module has been initialized.") |
914 |
0 |
except: |
915 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
916 |
0 |
debug.println(debug.LEVEL_SEVERE, |
917 |
0 |
"Could not initialize connection to speech.") |
918 |
|
else: |
919 |
0 |
debug.println(debug.LEVEL_CONFIGURATION, |
920 |
0 |
"Speech module has NOT been initialized.") |
921 |
|
|
922 |
1 |
if settings.enableBraille: |
923 |
1 |
try: |
924 |
1 |
braille.init(_processBrailleEvent, settings.tty) |
925 |
1 |
except: |
926 |
1 |
debug.printException(debug.LEVEL_WARNING) |
927 |
1 |
debug.println(debug.LEVEL_WARNING, |
928 |
1 |
"Could not initialize connection to braille.") |
929 |
|
|
930 |
1 |
if settings.enableMagnifier: |
931 |
0 |
try: |
932 |
0 |
mag.init() |
933 |
0 |
debug.println(debug.LEVEL_CONFIGURATION, |
934 |
0 |
"Magnification module has been initialized.") |
935 |
0 |
except: |
936 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
937 |
0 |
debug.println(debug.LEVEL_SEVERE, |
938 |
0 |
"Could not initialize connection to magnifier.") |
939 |
|
else: |
940 |
1 |
debug.println(debug.LEVEL_CONFIGURATION, |
941 |
1 |
"Magnification module has NOT been initialized.") |
942 |
|
|
943 |
|
# We don't want the Caps_Lock modifier to act as a locking |
944 |
|
# modifier if it used as the Orca modifier key. In addition, if |
945 |
|
# the KP_Insert key is used as the Orca modifier key, we want to |
946 |
|
# make sure we clear any other keysyms that might be in use on |
947 |
|
# that key since we won't be able to detect them as being the Orca |
948 |
|
# modifier key. For example, KP_Insert produces "KP_Insert" when |
949 |
|
# pressed by itself, but Shift+KP_Insert produces "0". |
950 |
|
# |
951 |
|
# The original values are saved/reset in the orca shell script. |
952 |
|
# |
953 |
|
# [[[TODO: WDW - we probably should just to a 'xmodmap -e "%s = %s"' |
954 |
|
# for all of the orcaModifierKeys, but saving/restoring the values |
955 |
|
# becomes a little more difficult. If we could assume a writeable |
956 |
|
# filesystem (we cannot), we could do a 'xmodmap -pke > /tmp/foo' |
957 |
|
# to save the keymap and a 'xmodmap /tmp/foo' to restore it. |
958 |
|
# For now, we'll just look at the Orca modifier keys we support |
959 |
|
# (Caps Lock, KP_Insert, and Insert).]]] |
960 |
|
# |
961 |
3 |
for keyName in settings.orcaModifierKeys: |
962 |
2 |
if keyName == "Caps_Lock": |
963 |
0 |
os.system('xmodmap -e "clear Lock"') |
964 |
2 |
if keyName in ["Caps_Lock", "KP_Insert", "Insert"]: |
965 |
2 |
command = 'xmodmap -e "keysym %s = %s"' % (keyName, keyName) |
966 |
2 |
os.system(command) |
967 |
|
|
968 |
1 |
if _currentPresentationManager >= 0: |
969 |
0 |
_PRESENTATION_MANAGERS[_currentPresentationManager].activate() |
970 |
|
|
971 |
1 |
_showMainWindowGUI() |
972 |
|
|
973 |
1 |
httpserver.init() |
974 |
|
|
975 |
1 |
return True |
976 |
|
|
977 |
1 |
def _showAppPreferencesGUI(script=None, inputEvent=None): |
978 |
|
"""Displays the user interace to configure the settings for a |
979 |
|
specific applications within Orca and set up those app-specific |
980 |
|
user preferences using a GUI. |
981 |
|
|
982 |
|
Returns True to indicate the input event has been consumed. |
983 |
|
""" |
984 |
|
|
985 |
0 |
try: |
986 |
0 |
module = __import__(settings.appGuiPreferencesModule, |
987 |
0 |
globals(), |
988 |
0 |
locals(), |
989 |
0 |
['']) |
990 |
0 |
module.showPreferencesUI() |
991 |
0 |
except: |
992 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
993 |
0 |
pass |
994 |
|
|
995 |
0 |
return True |
996 |
|
|
997 |
1 |
def _showPreferencesGUI(script=None, inputEvent=None): |
998 |
|
"""Displays the user interace to configure Orca and set up |
999 |
|
user preferences using a GUI. |
1000 |
|
|
1001 |
|
Returns True to indicate the input event has been consumed. |
1002 |
|
""" |
1003 |
|
|
1004 |
0 |
try: |
1005 |
0 |
module = __import__(settings.guiPreferencesModule, |
1006 |
0 |
globals(), |
1007 |
0 |
locals(), |
1008 |
0 |
['']) |
1009 |
0 |
module.showPreferencesUI() |
1010 |
0 |
except: |
1011 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1012 |
0 |
pass |
1013 |
|
|
1014 |
0 |
return True |
1015 |
|
|
1016 |
1 |
def _showMainWindowGUI(script=None, inputEvent=None): |
1017 |
|
"""Displays the Orca main window. |
1018 |
|
|
1019 |
|
Returns True to indicate the input event has been consumed. |
1020 |
|
""" |
1021 |
|
|
1022 |
1 |
try: |
1023 |
1 |
module = __import__(settings.mainWindowModule, |
1024 |
1 |
globals(), |
1025 |
1 |
locals(), |
1026 |
1 |
['']) |
1027 |
1 |
if settings.showMainWindow: |
1028 |
1 |
module.showMainUI() |
1029 |
|
else: |
1030 |
0 |
module.hideMainUI() |
1031 |
0 |
except: |
1032 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1033 |
0 |
pass |
1034 |
|
|
1035 |
1 |
return True |
1036 |
|
|
1037 |
1 |
def _showPreferencesConsole(script=None, inputEvent=None): |
1038 |
|
"""Displays the user interace to configure Orca and set up |
1039 |
|
user preferences via a command line interface. |
1040 |
|
|
1041 |
|
Returns True to indicate the input event has been consumed. |
1042 |
|
""" |
1043 |
|
|
1044 |
0 |
try: |
1045 |
0 |
module = __import__(settings.consolePreferencesModule, |
1046 |
0 |
globals(), |
1047 |
0 |
locals(), |
1048 |
0 |
['']) |
1049 |
0 |
module.showPreferencesUI() |
1050 |
0 |
except: |
1051 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1052 |
0 |
pass |
1053 |
|
|
1054 |
0 |
return True |
1055 |
|
|
1056 |
1 |
def quitOrca(script=None, inputEvent=None): |
1057 |
|
"""Quit Orca. Check if the user wants to confirm this action. |
1058 |
|
If so, show the confirmation GUI otherwise just shutdown. |
1059 |
|
|
1060 |
|
Returns True to indicate the input event has been consumed. |
1061 |
|
""" |
1062 |
|
|
1063 |
1 |
if settings.quitOrcaNoConfirmation: |
1064 |
0 |
shutdown() |
1065 |
|
else: |
1066 |
1 |
try: |
1067 |
1 |
module = __import__(settings.quitModule, |
1068 |
1 |
globals(), |
1069 |
1 |
locals(), |
1070 |
1 |
['']) |
1071 |
1 |
module.showQuitUI() |
1072 |
0 |
except: |
1073 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1074 |
0 |
pass |
1075 |
|
|
1076 |
1 |
return True |
1077 |
|
|
1078 |
1 |
def _showFindGUI(script=None, inputEvent=None): |
1079 |
|
"""Displays the user interace to perform an Orca Find. |
1080 |
|
|
1081 |
|
Returns True to indicate the input event has been consumed. |
1082 |
|
""" |
1083 |
|
|
1084 |
0 |
try: |
1085 |
0 |
module = __import__(settings.findModule, |
1086 |
0 |
globals(), |
1087 |
0 |
locals(), |
1088 |
0 |
['']) |
1089 |
0 |
module.showFindUI() |
1090 |
0 |
except: |
1091 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1092 |
0 |
pass |
1093 |
|
|
1094 |
|
# If True, this module has been initialized. |
1095 |
|
# |
1096 |
1 |
_initialized = False |
1097 |
|
|
1098 |
1 |
def init(registry): |
1099 |
|
"""Initialize the orca module, which initializes speech, braille, |
1100 |
|
and mag modules. Also builds up the application list, registers |
1101 |
|
for AT-SPI events, and creates scripts for all known applications. |
1102 |
|
|
1103 |
|
Returns True if the initialization procedure has run, or False if this |
1104 |
|
module has already been initialized. |
1105 |
|
""" |
1106 |
|
|
1107 |
|
global _initialized |
1108 |
|
global _keyBindings |
1109 |
|
|
1110 |
1 |
if _initialized: |
1111 |
0 |
return False |
1112 |
|
|
1113 |
|
# Do not hang on initialization if we can help it. |
1114 |
|
# |
1115 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1116 |
1 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
1117 |
1 |
signal.alarm(settings.timeoutTime) |
1118 |
|
|
1119 |
|
# Note that we have moved the Orca specific keybindings to the default |
1120 |
|
# script, so _keyBindings is currently empty. The logic is retained |
1121 |
|
# here, just in case we wish to reinstate them in the future. |
1122 |
|
# |
1123 |
1 |
_keyBindings = keybindings.KeyBindings() |
1124 |
|
|
1125 |
|
# Create and load an app's script when it is added to the desktop |
1126 |
|
# |
1127 |
1 |
registry.registerEventListener(_onChildrenChanged, |
1128 |
1 |
"object:children-changed:") |
1129 |
|
|
1130 |
|
# We also want to stop speech when a mouse button is pressed. |
1131 |
|
# |
1132 |
1 |
registry.registerEventListener(_onMouseButton, |
1133 |
1 |
"mouse:button") |
1134 |
|
|
1135 |
1 |
loadUserSettings() |
1136 |
|
|
1137 |
1 |
registry.registerKeystrokeListeners(_processKeyboardEvent) |
1138 |
|
|
1139 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1140 |
1 |
signal.alarm(0) |
1141 |
|
|
1142 |
1 |
_initialized = True |
1143 |
1 |
return True |
1144 |
|
|
1145 |
1 |
def start(registry): |
1146 |
|
"""Starts Orca. |
1147 |
|
""" |
1148 |
|
|
1149 |
|
global _PRESENTATION_MANAGERS |
1150 |
|
|
1151 |
1 |
if not _initialized: |
1152 |
0 |
init(registry) |
1153 |
|
|
1154 |
|
# Do not hang on startup if we can help it. |
1155 |
|
# |
1156 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1157 |
1 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
1158 |
1 |
signal.alarm(settings.timeoutTime) |
1159 |
|
|
1160 |
1 |
if not _PRESENTATION_MANAGERS: |
1161 |
1 |
import focus_tracking_presenter |
1162 |
|
_PRESENTATION_MANAGERS = \ |
1163 |
1 |
[focus_tracking_presenter.FocusTrackingPresenter()] |
1164 |
|
|
1165 |
1 |
_switchToPresentationManager(0) # focus_tracking_presenter |
1166 |
|
|
1167 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1168 |
1 |
signal.alarm(0) |
1169 |
|
|
1170 |
1 |
registry.start() |
1171 |
|
|
1172 |
1 |
def abort(exitCode=1): |
1173 |
0 |
os._exit(exitCode) |
1174 |
|
|
1175 |
1 |
def timeout(signum=None, frame=None): |
1176 |
0 |
debug.println(debug.LEVEL_SEVERE, |
1177 |
0 |
"TIMEOUT: something has hung. Aborting.") |
1178 |
0 |
debug.printStack(debug.LEVEL_ALL) |
1179 |
0 |
abort(50) |
1180 |
|
|
1181 |
1 |
def shutdown(script=None, inputEvent=None): |
1182 |
|
"""Exits Orca. Unregisters any event listeners and cleans up. Also |
1183 |
|
quits the bonobo main loop and resets the initialized state to False. |
1184 |
|
|
1185 |
|
Returns True if the shutdown procedure ran or False if this module |
1186 |
|
was never initialized. |
1187 |
|
""" |
1188 |
|
|
1189 |
|
global _initialized |
1190 |
|
|
1191 |
1 |
if not _initialized: |
1192 |
0 |
return False |
1193 |
|
|
1194 |
|
# Try to say goodbye, but be defensive if something has hung. |
1195 |
|
# |
1196 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1197 |
1 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
1198 |
1 |
signal.alarm(settings.timeoutTime) |
1199 |
|
|
1200 |
|
# Translators: this is what Orca speaks and brailles when it quits. |
1201 |
|
# |
1202 |
1 |
speech.speak(_("Goodbye.")) |
1203 |
1 |
braille.displayMessage(_("Goodbye.")) |
1204 |
|
|
1205 |
|
# Deregister our event listeners |
1206 |
|
# |
1207 |
1 |
registry = atspi.Registry() |
1208 |
1 |
registry.deregisterEventListener(_onChildrenChanged, |
1209 |
1 |
"object:children-changed:") |
1210 |
1 |
registry.deregisterEventListener(_onMouseButton, |
1211 |
1 |
"mouse:button") |
1212 |
|
|
1213 |
1 |
if _currentPresentationManager >= 0: |
1214 |
1 |
_PRESENTATION_MANAGERS[_currentPresentationManager].deactivate() |
1215 |
|
|
1216 |
|
# Shutdown all the other support. |
1217 |
|
# |
1218 |
1 |
if settings.enableSpeech: |
1219 |
1 |
speech.shutdown() |
1220 |
1 |
if settings.enableBraille: |
1221 |
1 |
braille.shutdown(); |
1222 |
1 |
if settings.enableMagnifier: |
1223 |
0 |
mag.shutdown(); |
1224 |
|
|
1225 |
1 |
registry.stop() |
1226 |
|
|
1227 |
1 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1228 |
1 |
signal.alarm(0) |
1229 |
|
|
1230 |
1 |
_initialized = False |
1231 |
1 |
return True |
1232 |
|
|
1233 |
1 |
exitCount = 0 |
1234 |
1 |
def shutdownOnSignal(signum, frame): |
1235 |
|
global exitCount |
1236 |
|
|
1237 |
0 |
debug.println(debug.LEVEL_ALL, |
1238 |
0 |
"Shutting down and exiting due to signal = %d" \ |
1239 |
0 |
% signum) |
1240 |
|
|
1241 |
0 |
debug.println(debug.LEVEL_ALL, "Current stack is:") |
1242 |
0 |
debug.printStack(debug.LEVEL_ALL) |
1243 |
|
|
1244 |
|
# Well...we'll try to exit nicely, but if we keep getting called, |
1245 |
|
# something bad is happening, so just quit. |
1246 |
|
# |
1247 |
0 |
if exitCount: |
1248 |
0 |
abort(signum) |
1249 |
|
else: |
1250 |
0 |
exitCount += 1 |
1251 |
|
|
1252 |
|
# Try to do a graceful shutdown if we can. |
1253 |
|
# |
1254 |
0 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1255 |
0 |
signal.signal(signal.SIGALRM, settings.timeoutCallback) |
1256 |
0 |
signal.alarm(settings.timeoutTime) |
1257 |
|
|
1258 |
0 |
try: |
1259 |
0 |
if _initialized: |
1260 |
0 |
shutdown() |
1261 |
|
else: |
1262 |
|
# We always want to try to shutdown speech since the |
1263 |
|
# speech servers are very persistent about living. |
1264 |
|
# |
1265 |
0 |
speech.shutdown() |
1266 |
0 |
shutdown() |
1267 |
0 |
cleanExit = True |
1268 |
0 |
except: |
1269 |
0 |
cleanExit = False |
1270 |
|
|
1271 |
0 |
if settings.timeoutCallback and (settings.timeoutTime > 0): |
1272 |
0 |
signal.alarm(0) |
1273 |
|
|
1274 |
0 |
if not cleanExit: |
1275 |
0 |
abort(signum) |
1276 |
|
|
1277 |
1 |
def abortOnSignal(signum, frame): |
1278 |
0 |
debug.println(debug.LEVEL_ALL, |
1279 |
0 |
"Aborting due to signal = %d" \ |
1280 |
0 |
% signum) |
1281 |
0 |
abort(signum) |
1282 |
|
|
1283 |
1 |
def usage(): |
1284 |
|
"""Prints out usage information.""" |
1285 |
0 |
print "Usage: orca [OPTION...]" |
1286 |
0 |
print |
1287 |
0 |
print "-?, --help Show this help message" |
1288 |
0 |
print "-v, --version %s" % platform.version |
1289 |
0 |
print "-s, --setup, --gui-setup Set up user preferences" |
1290 |
0 |
print "-t, --text-setup Set up user preferences (text version)" |
1291 |
0 |
print "-n, --no-setup Skip set up of user preferences" |
1292 |
0 |
print "-u, --user-prefs-dir=dirname Use alternate directory for user preferences" |
1293 |
0 |
print "-e, --enable=[speech|braille|braille-monitor|magnifier|main-window] Force use of option" |
1294 |
0 |
print "-d, --disable=[speech|braille|braille-monitor|magnifier|main-window] Prevent use of option" |
1295 |
0 |
print "-q, --quit Quits Orca (if shell script used)" |
1296 |
0 |
print |
1297 |
0 |
print "If Orca has not been previously set up by the user, Orca\nwill automatically launch the preferences set up unless\nthe -n or --no-setup option is used." |
1298 |
0 |
print |
1299 |
0 |
print "Report bugs to orca-list@gnome.org." |
1300 |
0 |
pass |
1301 |
|
|
1302 |
1 |
def main(): |
1303 |
|
"""The main entry point for Orca. The exit codes for Orca will |
1304 |
|
loosely be based on signals, where the exit code will be the |
1305 |
|
signal used to terminate Orca (if a signal was used). Otherwise, |
1306 |
|
an exit code of 0 means normal completion and an exit code of 50 |
1307 |
|
means Orca exited because of a hang.""" |
1308 |
|
|
1309 |
|
global _commandLineSettings |
1310 |
|
|
1311 |
|
# Method to call when we think something might be hung. |
1312 |
|
# |
1313 |
1 |
settings.timeoutCallback = timeout |
1314 |
|
|
1315 |
|
# Various signal handlers we want to listen for. |
1316 |
|
# |
1317 |
1 |
signal.signal(signal.SIGHUP, shutdownOnSignal) |
1318 |
1 |
signal.signal(signal.SIGINT, shutdownOnSignal) |
1319 |
1 |
signal.signal(signal.SIGTERM, shutdownOnSignal) |
1320 |
1 |
signal.signal(signal.SIGQUIT, shutdownOnSignal) |
1321 |
1 |
signal.signal(signal.SIGSEGV, abortOnSignal) |
1322 |
|
|
1323 |
|
# See if the desktop is running. If it is, the import of gtk will |
1324 |
|
# succeed. If it isn't, the import will fail. |
1325 |
|
# |
1326 |
1 |
desktopRunning = False |
1327 |
1 |
try: |
1328 |
1 |
if gtk.gdk.display_get_default(): |
1329 |
1 |
desktopRunning = True |
1330 |
0 |
except: |
1331 |
0 |
pass |
1332 |
|
|
1333 |
|
# Parse the command line options. |
1334 |
|
# |
1335 |
|
# Run the preferences setup if the user has specified |
1336 |
|
# "--setup" or "--text-setup" on the command line. If the |
1337 |
|
# desktop is not running, we will fallback to the console-based |
1338 |
|
# method as appropriate. |
1339 |
|
# |
1340 |
1 |
bypassSetup = False |
1341 |
1 |
setupRequested = False |
1342 |
1 |
showGUI = False |
1343 |
|
|
1344 |
|
# We hack a little here because the shell script to start orca can |
1345 |
|
# conflate all of command line arguments into one string, which is |
1346 |
|
# not what we want. We detect this by seeing if the length of the |
1347 |
|
# argument list is 1. |
1348 |
|
# |
1349 |
1 |
arglist = sys.argv[1:] |
1350 |
1 |
if len(arglist) == 1: |
1351 |
0 |
arglist = arglist[0].split() |
1352 |
|
|
1353 |
1 |
try: |
1354 |
|
# ? is for help |
1355 |
|
# e is for enabling a feature |
1356 |
|
# d is for disabling a feature |
1357 |
|
# h is for help |
1358 |
|
# u is for alternate user preferences location |
1359 |
|
# s is for setup |
1360 |
|
# n is for no setup |
1361 |
|
# t is for text setup |
1362 |
|
# v is for version |
1363 |
|
# |
1364 |
1 |
opts, args = getopt.getopt( |
1365 |
1 |
arglist, |
1366 |
1 |
"?stnvd:e:u:", |
1367 |
1 |
["help", |
1368 |
1 |
"user-prefs-dir=", |
1369 |
1 |
"enable=", |
1370 |
1 |
"disable=", |
1371 |
1 |
"setup", |
1372 |
1 |
"gui-setup", |
1373 |
1 |
"text-setup", |
1374 |
1 |
"no-setup", |
1375 |
1 |
"version"]) |
1376 |
1 |
for opt, val in opts: |
1377 |
0 |
if opt in ("-u", "--user-prefs-dir"): |
1378 |
0 |
userPrefsDir = val.strip(); |
1379 |
0 |
try: |
1380 |
0 |
os.chdir(userPrefsDir) |
1381 |
0 |
settings.userPrefsDir = userPrefsDir |
1382 |
0 |
except: |
1383 |
0 |
debug.printException(debug.LEVEL_FINEST) |
1384 |
|
|
1385 |
0 |
if opt in ("-e", "--enable"): |
1386 |
0 |
feature = val.strip() |
1387 |
0 |
if feature == "speech": |
1388 |
0 |
_commandLineSettings["enableSpeech"] = True |
1389 |
0 |
elif feature == "braille": |
1390 |
0 |
_commandLineSettings["enableBraille"] = True |
1391 |
0 |
elif feature == "braille-monitor": |
1392 |
0 |
_commandLineSettings["enableBrailleMonitor"] = True |
1393 |
0 |
elif feature == "magnifier": |
1394 |
0 |
_commandLineSettings["enableMagnifier"] = True |
1395 |
0 |
elif feature == "main-window": |
1396 |
0 |
_commandLineSettings["showMainWindow"] = True |
1397 |
|
else: |
1398 |
0 |
usage() |
1399 |
0 |
os._exit(2) |
1400 |
|
|
1401 |
0 |
if opt in ("-d", "--disable"): |
1402 |
0 |
feature = val.strip() |
1403 |
0 |
if feature == "speech": |
1404 |
0 |
_commandLineSettings["enableSpeech"] = False |
1405 |
0 |
elif feature == "braille": |
1406 |
0 |
_commandLineSettings["enableBraille"] = False |
1407 |
0 |
elif feature == "braille-monitor": |
1408 |
0 |
_commandLineSettings["enableBrailleMonitor"] = False |
1409 |
0 |
elif feature == "magnifier": |
1410 |
0 |
_commandLineSettings["enableMagnifier"] = False |
1411 |
0 |
elif feature == "main-window": |
1412 |
0 |
_commandLineSettings["showMainWindow"] = False |
1413 |
|
else: |
1414 |
0 |
usage() |
1415 |
0 |
os._exit(2) |
1416 |
|
|
1417 |
0 |
if opt in ("-s", "--gui-setup", "--setup"): |
1418 |
0 |
setupRequested = True |
1419 |
0 |
showGUI = desktopRunning |
1420 |
0 |
if opt in ("-t", "--text-setup"): |
1421 |
0 |
setupRequested = True |
1422 |
0 |
showGUI = False |
1423 |
0 |
if opt in ("-n", "--no-setup"): |
1424 |
0 |
bypassSetup = True |
1425 |
0 |
if opt in ("-?", "--help"): |
1426 |
0 |
usage() |
1427 |
0 |
os._exit(0) |
1428 |
0 |
if opt in ("-v", "--version"): |
1429 |
0 |
print "Orca %s" % platform.version |
1430 |
0 |
os._exit(0) |
1431 |
0 |
except: |
1432 |
0 |
debug.printException(debug.LEVEL_OFF) |
1433 |
0 |
usage() |
1434 |
0 |
os._exit(2) |
1435 |
|
|
1436 |
|
# Do not run Orca if accessibility has not been enabled. |
1437 |
|
# We do allow, however, one to force Orca to run via the |
1438 |
|
# "-n" switch. The main reason is so that things such |
1439 |
|
# as accessible login can work -- in those cases, the gconf |
1440 |
|
# setting is typically not set since the gdm user does not |
1441 |
|
# have a home. |
1442 |
|
# |
1443 |
1 |
a11yEnabled = settings.isAccessibilityEnabled() |
1444 |
1 |
if (not bypassSetup) and (not a11yEnabled): |
1445 |
0 |
_showPreferencesConsole() |
1446 |
0 |
abort() |
1447 |
|
|
1448 |
1 |
if setupRequested and (not bypassSetup) and (not showGUI): |
1449 |
0 |
_showPreferencesConsole() |
1450 |
|
|
1451 |
1 |
if not desktopRunning: |
1452 |
0 |
print "Cannot start Orca because it cannot connect" |
1453 |
0 |
print "to the Desktop. Please make sure the DISPLAY" |
1454 |
0 |
print "environment variable has been set." |
1455 |
0 |
return 1 |
1456 |
|
|
1457 |
1 |
userprefs = settings.userPrefsDir |
1458 |
1 |
sys.path.insert(0, userprefs) |
1459 |
1 |
sys.path.insert(0, '') # current directory |
1460 |
|
|
1461 |
1 |
registry = atspi.Registry() |
1462 |
1 |
init(registry) |
1463 |
|
|
1464 |
1 |
try: |
1465 |
1 |
message = _("Welcome to Orca.") |
1466 |
1 |
speech.speak(message) |
1467 |
1 |
braille.displayMessage(message) |
1468 |
0 |
except: |
1469 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
1470 |
|
|
1471 |
|
# Check to see if the user wants the configuration GUI. It's |
1472 |
|
# done here so that the user's existing preferences can be used |
1473 |
|
# to set the initial GUI state. We'll also force the set to |
1474 |
|
# be run if the preferences file doesn't exist, unless the |
1475 |
|
# user has bypassed any setup via the --no-setup switch. |
1476 |
|
# |
1477 |
1 |
if setupRequested and (not bypassSetup) and showGUI: |
1478 |
0 |
_showPreferencesGUI() |
1479 |
1 |
elif (not _userSettings) and (not bypassSetup): |
1480 |
0 |
if desktopRunning: |
1481 |
0 |
_showPreferencesGUI() |
1482 |
|
else: |
1483 |
0 |
_showPreferencesConsole() |
1484 |
|
|
1485 |
1 |
start(registry) # waits until we stop the registry |
1486 |
1 |
return 0 |
1487 |
|
|
1488 |
1 |
if __name__ == "__main__": |
1489 |
0 |
sys.exit(main()) |