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 |
|
"""Provides support for defining keybindings and matching them to input |
21 |
1 |
events.""" |
22 |
|
|
23 |
1 |
__id__ = "$Id: keybindings.py 2351 2007-05-07 21:56:41Z wwalker $" |
24 |
1 |
__version__ = "$Revision: 2351 $" |
25 |
1 |
__date__ = "$Date: 2007-05-07 17:56:41 -0400 (Mon, 07 May 2007) $" |
26 |
1 |
__copyright__ = "Copyright (c) 2005-2007 Sun Microsystems Inc." |
27 |
1 |
__license__ = "LGPL" |
28 |
|
|
29 |
1 |
try: |
30 |
|
# This can fail due to gtk not being available. We want to |
31 |
|
# be able to recover from that if possible. The main driver |
32 |
|
# for this is to allow "orca --text-setup" to work even if |
33 |
|
# the desktop is not running. |
34 |
|
# |
35 |
1 |
import gtk |
36 |
0 |
except: |
37 |
0 |
pass |
38 |
|
|
39 |
1 |
import atspi |
40 |
1 |
import debug |
41 |
1 |
import settings |
42 |
|
|
43 |
1 |
from orca_i18n import _ # for gettext support |
44 |
|
|
45 |
1 |
_keysymsCache = {} |
46 |
1 |
_keycodeCache = {} |
47 |
|
|
48 |
1 |
def getAllKeysyms(keysym): |
49 |
|
"""Given a keysym, find all other keysyms associated with the key |
50 |
|
that is mapped to the given keysym. This allows us, for example, |
51 |
|
to determine that the key bound to KP_Insert is also bound to KP_0.""" |
52 |
|
|
53 |
0 |
if not _keysymsCache.has_key(keysym): |
54 |
|
# The keysym itself is always part of the list. |
55 |
|
# |
56 |
0 |
_keysymsCache[keysym] = [keysym] |
57 |
|
|
58 |
|
# Find the numerical value of the keysym |
59 |
|
# |
60 |
0 |
keyval = gtk.gdk.keyval_from_name(keysym) |
61 |
|
|
62 |
0 |
if keyval != 0: |
63 |
|
# Find the keycodes for the keysym. Since a keysym |
64 |
|
# can be associated with more than one key, we'll shoot |
65 |
|
# for the keysym that's in group 0, regardless of shift |
66 |
|
# level (each entry is of the form [keycode, group, |
67 |
|
# level]). |
68 |
|
# |
69 |
0 |
keymap = gtk.gdk.keymap_get_default() |
70 |
0 |
entries = keymap.get_entries_for_keyval(keyval) |
71 |
0 |
keycode = 0 |
72 |
0 |
if entries: |
73 |
0 |
for entry in entries: |
74 |
0 |
if entry[1] == 0: # group = 0 |
75 |
0 |
keycode = entry[0] |
76 |
0 |
break |
77 |
|
|
78 |
|
# Find the keysyms bound to the keycode. These are what |
79 |
|
# we are looking for. |
80 |
|
# |
81 |
0 |
if keycode != 0: |
82 |
0 |
entries = keymap.get_entries_for_keycode(keycode) |
83 |
0 |
if entries: |
84 |
0 |
for entry in entries: |
85 |
0 |
keyval = entry[0] |
86 |
0 |
name = gtk.gdk.keyval_name(keyval) |
87 |
0 |
if name and (name != keysym): |
88 |
0 |
_keysymsCache[keysym].append(name) |
89 |
|
|
90 |
0 |
return _keysymsCache[keysym] |
91 |
|
|
92 |
1 |
def getKeycode(keysym): |
93 |
|
"""Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that |
94 |
|
should match the event.hw_code for key events. |
95 |
|
|
96 |
|
This whole situation is caused by the fact that Solaris chooses |
97 |
|
to give us different keycodes for the same key, and the keypad |
98 |
|
is the primary place where this happens: if NumLock is not on, |
99 |
|
there is no telling the difference between keypad keys and the |
100 |
|
other navigation keys (e.g., arrows, page up/down, etc.). One, |
101 |
|
for example, would expect to get KP_End for the '1' key on the |
102 |
|
keypad if NumLock were not on. Instead, we get 'End' and the |
103 |
|
keycode for it matches the keycode for the other 'End' key. Odd. |
104 |
|
If NumLock is on, we at least get KP_* keys. |
105 |
|
|
106 |
|
So...when setting up keybindings, we say we're interested in |
107 |
|
KeySyms, but those keysyms are carefully chosen so as to result |
108 |
|
in a keycode that matches the actual key on the keyboard. This |
109 |
|
is why we use KP_1 instead of KP_End and so on in our keybindings. |
110 |
|
|
111 |
|
Arguments: |
112 |
|
- keysym: a string that is a valid representation of an XKeysym. |
113 |
|
|
114 |
|
Returns an integer representing a key code that should match the |
115 |
|
event.hw_code for key events. |
116 |
|
""" |
117 |
6354 |
if not _keycodeCache.has_key(keysym): |
118 |
50 |
keymap = gtk.gdk.keymap_get_default() |
119 |
|
|
120 |
|
# Find the numerical value of the keysym |
121 |
|
# |
122 |
50 |
keyval = gtk.gdk.keyval_from_name(keysym) |
123 |
50 |
if keyval == 0: |
124 |
0 |
return 0 |
125 |
|
|
126 |
|
# Now find the keycodes for the keysym. Since a keysym can |
127 |
|
# be associated with more than one key, we'll shoot for the |
128 |
|
# keysym that's in group 0, regardless of shift level (each |
129 |
|
# entry is of the form [keycode, group, level]). |
130 |
|
# |
131 |
50 |
_keycodeCache[keysym] = 0 |
132 |
50 |
entries = keymap.get_entries_for_keyval(keyval) |
133 |
50 |
if entries: |
134 |
49 |
for entry in entries: |
135 |
49 |
if entry[1] == 0: # group = 0 |
136 |
49 |
_keycodeCache[keysym] = entry[0] |
137 |
49 |
break |
138 |
0 |
if _keycodeCache[keysym] == 0: |
139 |
0 |
_keycodeCache[keysym] = entries[0][0] |
140 |
|
|
141 |
|
#print keysym, keyval, entries, _keycodeCache[keysym] |
142 |
|
|
143 |
6354 |
return _keycodeCache[keysym] |
144 |
|
|
145 |
1 |
def getModifierNames(mods): |
146 |
|
"""Gets the modifier names of a numeric modifier mask as a human |
147 |
|
consumable string. |
148 |
|
""" |
149 |
|
|
150 |
0 |
text = "" |
151 |
0 |
if mods & (1 << settings.MODIFIER_ORCA): |
152 |
0 |
text += _("Orca") + "+" |
153 |
|
#if mods & (1 << atspi.Accessibility.MODIFIER_NUMLOCK): |
154 |
|
# text += _("Num_Lock") + "+" |
155 |
0 |
if mods & 128: |
156 |
|
# Translators: this is presented in a GUI to represent the |
157 |
|
# "right alt" modifier. |
158 |
|
# |
159 |
0 |
text += _("Alt_R") + "+" |
160 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_META3): |
161 |
|
# Translators: this is presented in a GUI to represent the |
162 |
|
# "super" modifier. |
163 |
|
# |
164 |
0 |
text += _("Super") + "+" |
165 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_META2): |
166 |
|
# Translators: this is presented in a GUI to represent the |
167 |
|
# "meta 2" modifier. |
168 |
|
# |
169 |
0 |
text += _("Meta2") + "+" |
170 |
|
#if mods & (1 << atspi.Accessibility.MODIFIER_META): |
171 |
|
# text += _("Meta") + "+" |
172 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_ALT): |
173 |
|
# Translators: this is presented in a GUI to represent the |
174 |
|
# "left alt" modifier. |
175 |
|
# |
176 |
0 |
text += _("Alt_L") + "+" |
177 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_CONTROL): |
178 |
|
# Translators: this is presented in a GUI to represent the |
179 |
|
# "control" modifier. |
180 |
|
# |
181 |
0 |
text += _("Ctrl") + "+" |
182 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_SHIFTLOCK): |
183 |
|
# Translators: this is presented in a GUI to represent the |
184 |
|
# "caps lock" modifier. |
185 |
|
# |
186 |
0 |
text += _("Caps_Lock") + "+" |
187 |
0 |
if mods & (1 << atspi.Accessibility.MODIFIER_SHIFT): |
188 |
|
# Translators: this is presented in a GUI to represent the |
189 |
|
# "shift " modifier. |
190 |
|
# |
191 |
0 |
text += _("Shift") + "+" |
192 |
0 |
return text |
193 |
|
|
194 |
2 |
class KeyBinding: |
195 |
|
"""A single key binding, consisting of a keycode, a modifier mask, |
196 |
|
and the InputEventHandler. |
197 |
1 |
""" |
198 |
|
|
199 |
1 |
def __init__(self, keysymstring, modifier_mask, modifiers, handler): |
200 |
|
"""Creates a new key binding. |
201 |
|
|
202 |
|
Arguments: |
203 |
|
- keysymstring: the keysymstring - this is typically a string |
204 |
|
from /usr/include/X11/keysymdef.h with the preceding 'XK_' |
205 |
|
removed (e.g., XK_KP_Enter becomes the string 'KP_Enter'). |
206 |
|
- modifier_mask: bit mask where a set bit tells us what modifiers |
207 |
|
we care about (see atspi.Accessibility.MODIFIER_*) |
208 |
|
- modifiers: the state the modifiers we care about must be in for |
209 |
|
this key binding to match an input event (see also |
210 |
|
atspi.Accessibility.MODIFIER_*) |
211 |
|
- handler: the InputEventHandler for this key binding |
212 |
|
""" |
213 |
|
|
214 |
6241 |
self.keysymstring = keysymstring |
215 |
6241 |
self.modifier_mask = modifier_mask |
216 |
6241 |
self.modifiers = modifiers |
217 |
6241 |
self.handler = handler |
218 |
6241 |
self.keycode = None |
219 |
|
|
220 |
1 |
def matches(self, keycode, modifiers): |
221 |
|
"""Returns true if this key binding matches the given keycode and |
222 |
|
modifier state. |
223 |
|
""" |
224 |
|
|
225 |
|
# We lazily bind the keycode. The primary reason for doing this |
226 |
|
# is so that atspi does not have to be initialized before setting |
227 |
|
# keybindings in the user's preferences file. |
228 |
|
# |
229 |
269666 |
if not self.keycode: |
230 |
6354 |
self.keycode = getKeycode(self.keysymstring) |
231 |
|
|
232 |
269666 |
if self.keycode == keycode: |
233 |
1743 |
result = modifiers & self.modifier_mask |
234 |
1743 |
return result == self.modifiers |
235 |
|
else: |
236 |
267923 |
return False |
237 |
|
|
238 |
2 |
class KeyBindings: |
239 |
|
"""Structure that maintains a set of KeyBinding instances. |
240 |
1 |
""" |
241 |
|
|
242 |
1 |
def __init__(self): |
243 |
159 |
self.keyBindings = [] |
244 |
|
|
245 |
1 |
def __str__(self): |
246 |
0 |
result = "[\n" |
247 |
0 |
for keyBinding in self.keyBindings: |
248 |
0 |
result += " [%x %x %s %s]\n" % (keyBinding.modifier_mask, |
249 |
0 |
keyBinding.modifiers, |
250 |
0 |
keyBinding.keysymstring, |
251 |
0 |
keyBinding.handler._description) |
252 |
0 |
result += "]" |
253 |
0 |
return result |
254 |
|
|
255 |
1 |
def add(self, keyBinding): |
256 |
|
"""Adds the given KeyBinding instance to this set of keybindings. |
257 |
|
""" |
258 |
|
|
259 |
9243 |
self.keyBindings.append(keyBinding) |
260 |
|
|
261 |
1 |
def remove(self, keyBinding): |
262 |
|
"""Removes the given KeyBinding instance from this set of keybindings. |
263 |
|
""" |
264 |
|
|
265 |
0 |
for i in range(0, len(self.keyBindings)): |
266 |
0 |
if keyBinding == self.keyBindings[i]: |
267 |
0 |
del self.keyBindings[i] |
268 |
|
|
269 |
1 |
def removeByHandler(self, handler): |
270 |
|
"""Removes the given KeyBinding instance from this set of keybindings. |
271 |
|
""" |
272 |
0 |
i = len(self.keyBindings) |
273 |
0 |
while i > 0: |
274 |
0 |
if self.keyBindings[i - 1].handler == handler: |
275 |
0 |
del self.keyBindings[i - 1] |
276 |
0 |
i = i - 1 |
277 |
|
|
278 |
1 |
def hasKeyBinding (self, newKeyBinding, typeOfSearch="strict"): |
279 |
|
"""Return True if keyBinding is already in self.keyBindings. |
280 |
|
|
281 |
|
The typeOfSearch can be: |
282 |
|
"strict": matches description, modifiers, key |
283 |
|
"description": matches only description. |
284 |
|
"keys": matches only modifiers and key. |
285 |
|
""" |
286 |
|
|
287 |
0 |
hasIt = False |
288 |
|
|
289 |
0 |
for keyBinding in self.keyBindings: |
290 |
0 |
if typeOfSearch == "strict": |
291 |
0 |
if (keyBinding.handler._description \ |
292 |
0 |
== newKeyBinding.handler._description) \ |
293 |
0 |
and (keyBinding.keysymstring \ |
294 |
0 |
== newKeyBinding.keysymstring) \ |
295 |
0 |
and (keyBinding.modifier_mask \ |
296 |
0 |
== newKeyBinding.modifier_mask) \ |
297 |
0 |
and (keyBinding.modifiers \ |
298 |
0 |
== newKeyBinding.modifiers): |
299 |
0 |
hasIt = True |
300 |
0 |
elif typeOfSearch == "description": |
301 |
0 |
if keyBinding.handler._description \ |
302 |
0 |
== newKeyBinding.handler._description: |
303 |
0 |
hasIt = True |
304 |
0 |
elif typeOfSearch == "keys": |
305 |
0 |
if (keyBinding.keysymstring \ |
306 |
0 |
== newKeyBinding.keysymstring) \ |
307 |
0 |
and (keyBinding.modifier_mask \ |
308 |
0 |
== newKeyBinding.modifier_mask) \ |
309 |
0 |
and (keyBinding.modifiers \ |
310 |
0 |
== newKeyBinding.modifiers): |
311 |
0 |
hasIt = True |
312 |
|
|
313 |
0 |
return hasIt |
314 |
|
|
315 |
1 |
def getInputHandler(self, keyboardEvent): |
316 |
|
"""Returns the input handler of the key binding that matches the |
317 |
|
given keycode and modifiers, or None if no match exists. |
318 |
|
""" |
319 |
7351 |
handler = None |
320 |
276631 |
for keyBinding in self.keyBindings: |
321 |
269666 |
if keyBinding.matches(keyboardEvent.hw_code, \ |
322 |
269666 |
keyboardEvent.modifiers): |
323 |
386 |
handler = keyBinding.handler |
324 |
386 |
break |
325 |
7351 |
return handler |
326 |
|
|
327 |
1 |
def consumeKeyboardEvent(self, script, keyboardEvent): |
328 |
|
"""Attempts to consume the given keyboard event. If these |
329 |
|
keybindings have a handler for the given keyboardEvent, it is |
330 |
|
assumed the event will always be consumed. |
331 |
|
""" |
332 |
|
|
333 |
3772 |
consumed = False |
334 |
3772 |
handler = self.getInputHandler(keyboardEvent) |
335 |
3772 |
if handler: |
336 |
193 |
consumed = True |
337 |
193 |
if keyboardEvent.type == atspi.Accessibility.KEY_PRESSED_EVENT: |
338 |
97 |
try: |
339 |
97 |
handler.processInputEvent(script, keyboardEvent) |
340 |
0 |
except: |
341 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
342 |
|
|
343 |
3772 |
return consumed |