1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2005-2006 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 1818 2006-12-13 01:21:31Z wwalker $" |
24 |
1 |
__version__ = "$Revision: 1818 $" |
25 |
1 |
__date__ = "$Date: 2006-12-12 17:21:31 -0800 (Tue, 12 Dec 2006) $" |
26 |
1 |
__copyright__ = "Copyright (c) 2005-2006 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 |
_keycodeCache = {} |
46 |
|
|
47 |
1 |
def _getKeycode(keysym): |
48 |
|
"""Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that |
49 |
|
should match the event.hw_code for key events. |
50 |
|
|
51 |
|
This whole situation is caused by the fact that Solaris chooses |
52 |
|
to give us different keycodes for the same key, and the keypad |
53 |
|
is the primary place where this happens: if NumLock is not on, |
54 |
|
there is no telling the difference between keypad keys and the |
55 |
|
other navigation keys (e.g., arrows, page up/down, etc.). One, |
56 |
|
for example, would expect to get KP_End for the '1' key on the |
57 |
|
keypad if NumLock were not on. Instead, we get 'End' and the |
58 |
|
keycode for it matches the keycode for the other 'End' key. Odd. |
59 |
|
If NumLock is on, we at least get KP_* keys. |
60 |
|
|
61 |
|
So...when setting up keybindings, we say we're interested in |
62 |
|
KeySyms, but those keysyms are carefully chosen so as to result |
63 |
|
in a keycode that matches the actual key on the keyboard. This |
64 |
|
is why we use KP_1 instead of KP_End and so on in our keybindings. |
65 |
|
|
66 |
|
Arguments: |
67 |
|
- keysym: a string that is a valid representation of an XKeysym. |
68 |
|
|
69 |
|
Returns an integer representing a key code that should match the |
70 |
|
event.hw_code for key events. |
71 |
|
""" |
72 |
4765 |
if not _keycodeCache.has_key(keysym): |
73 |
47 |
keymap = gtk.gdk.keymap_get_default() |
74 |
|
|
75 |
|
# Find the numerical value of the keysym |
76 |
|
# |
77 |
47 |
keyval = gtk.gdk.keyval_from_name(keysym) |
78 |
47 |
if keyval == 0: |
79 |
0 |
return 0 |
80 |
|
|
81 |
|
# Now find the keycodes for the keysym. Since a keysym can |
82 |
|
# be associated with more than one key, we'll shoot for the |
83 |
|
# keysym that's in group 0, regardless of shift level (each |
84 |
|
# entry is of the form [keycode, group, level]). |
85 |
|
# |
86 |
47 |
_keycodeCache[keysym] = 0 |
87 |
47 |
entries = keymap.get_entries_for_keyval(keyval) |
88 |
47 |
if entries: |
89 |
46 |
for entry in entries: |
90 |
46 |
if entry[1] == 0: # group = 0 |
91 |
46 |
_keycodeCache[keysym] = entry[0] |
92 |
46 |
break |
93 |
0 |
if _keycodeCache[keysym] == 0: |
94 |
0 |
_keycodeCache[keysym] = entries[0][0] |
95 |
|
|
96 |
|
#print keysym, keyval, entries, _keycodeCache[keysym] |
97 |
|
|
98 |
4765 |
return _keycodeCache[keysym] |
99 |
|
|
100 |
1 |
def getModifierNames(mods): |
101 |
|
"""Gets the modifier names of a numeric modifier mask as a human |
102 |
|
consumable string. |
103 |
|
""" |
104 |
|
|
105 |
59 |
text = "" |
106 |
59 |
if mods & (1 << settings.MODIFIER_ORCA): |
107 |
34 |
text += _("Orca") + "+" |
108 |
|
#if mods & (1 << atspi.Accessibility.MODIFIER_NUMLOCK): |
109 |
|
# text += _("Num_Lock") + "+" |
110 |
59 |
if mods & 128: |
111 |
0 |
text += _("Alt_R") + "+" |
112 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_META3): |
113 |
0 |
text += _("Super") + "+" |
114 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_META2): |
115 |
0 |
text += _("Meta2") + "+" |
116 |
|
#if mods & (1 << atspi.Accessibility.MODIFIER_META): |
117 |
|
# text += _("Meta") + "+" |
118 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_ALT): |
119 |
0 |
text += _("Alt_L") + "+" |
120 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_CONTROL): |
121 |
1 |
text += _("Ctrl") + "+" |
122 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_SHIFTLOCK): |
123 |
0 |
text += _("Caps_Lock") + "+" |
124 |
59 |
if mods & (1 << atspi.Accessibility.MODIFIER_SHIFT): |
125 |
1 |
text += _("Shift") + "+" |
126 |
59 |
return text |
127 |
|
|
128 |
2 |
class KeyBinding: |
129 |
|
"""A single key binding, consisting of a keycode, a modifier mask, |
130 |
|
and the InputEventHandler. |
131 |
|
""" |
132 |
|
|
133 |
1 |
def __init__(self, keysymstring, modifier_mask, modifiers, handler): |
134 |
|
"""Creates a new key binding. |
135 |
|
|
136 |
|
Arguments: |
137 |
|
- keysymstring: the keysymstring - this is typically a string |
138 |
|
from /usr/include/X11/keysymdef.h with the preceding 'XK_' |
139 |
|
removed (e.g., XK_KP_Enter becomes the string 'KP_Enter'). |
140 |
|
- modifier_mask: bit mask where a set bit tells us what modifiers |
141 |
|
we care about (see atspi.Accessibility.MODIFIER_*) |
142 |
|
- modifiers: the state the modifiers we care about must be in for |
143 |
|
this key binding to match an input event (see also |
144 |
|
atspi.Accessibility.MODIFIER_*) |
145 |
|
- handler: the InputEventHandler for this key binding |
146 |
|
""" |
147 |
|
|
148 |
3671 |
self.keysymstring = keysymstring |
149 |
3671 |
self.modifier_mask = modifier_mask |
150 |
3671 |
self.modifiers = modifiers |
151 |
3671 |
self.handler = handler |
152 |
3671 |
self.keycode = None |
153 |
|
|
154 |
1 |
def matches(self, keycode, modifiers): |
155 |
|
"""Returns true if this key binding matches the given keycode and |
156 |
|
modifier state. |
157 |
|
""" |
158 |
|
|
159 |
|
# We lazily bind the keycode. The primary reason for doing this |
160 |
|
# is so that atspi does not have to be initialized before setting |
161 |
|
# keybindings in the user's preferences file. |
162 |
|
# |
163 |
196161 |
if not self.keycode: |
164 |
4765 |
self.keycode = _getKeycode(self.keysymstring) |
165 |
|
|
166 |
196161 |
if self.keycode == keycode: |
167 |
2183 |
result = modifiers & self.modifier_mask |
168 |
2183 |
return result == self.modifiers |
169 |
|
else: |
170 |
193978 |
return False |
171 |
|
|
172 |
2 |
class KeyBindings: |
173 |
|
"""Structure that maintains a set of KeyBinding instances. |
174 |
|
""" |
175 |
|
|
176 |
1 |
def __init__(self): |
177 |
127 |
self.keyBindings = [] |
178 |
|
|
179 |
1 |
def add(self, keyBinding): |
180 |
|
"""Adds the given KeyBinding instance to this set of keybindings. |
181 |
|
""" |
182 |
|
|
183 |
6026 |
self.keyBindings.append(keyBinding) |
184 |
|
|
185 |
1 |
def remove(self, keyBinding): |
186 |
|
"""Removes the given KeyBinding instance from this set of keybindings. |
187 |
|
""" |
188 |
|
|
189 |
0 |
for i in range(0, len(self.keyBindings)): |
190 |
0 |
if keyBinding == self.keyBindings[i]: |
191 |
0 |
del self.keyBindings[i] |
192 |
|
|
193 |
1 |
def removeByHandler(self, handler): |
194 |
|
"""Removes the given KeyBinding instance from this set of keybindings. |
195 |
|
""" |
196 |
0 |
i = len(self.keyBindings) |
197 |
0 |
while i > 0: |
198 |
0 |
if self.keyBindings[i - 1].handler == handler: |
199 |
0 |
del self.keyBindings[i - 1] |
200 |
0 |
i = i - 1 |
201 |
|
|
202 |
1 |
def hasKeyBinding (self, newKeyBinding, typeOfSearch="strict"): |
203 |
|
"""Return True if keyBinding is already in self.keyBindings. |
204 |
|
|
205 |
|
The typeOfSearch can be: |
206 |
|
"strict": matches description, modifiers, key |
207 |
|
"description": matches only description. |
208 |
|
"keys": matches only modifiers and key. |
209 |
|
""" |
210 |
|
|
211 |
102 |
hasIt = False |
212 |
|
|
213 |
102 |
for keyBinding in self.keyBindings: |
214 |
0 |
if typeOfSearch == "strict": |
215 |
0 |
if (keyBinding.handler._description \ |
216 |
|
== newKeyBinding.handler._description) \ |
217 |
|
and (keyBinding.keysymstring \ |
218 |
|
== newKeyBinding.keysymstring) \ |
219 |
|
and (keyBinding.modifier_mask \ |
220 |
|
== newKeyBinding.modifier_mask) \ |
221 |
|
and (keyBinding.modifiers \ |
222 |
|
== newKeyBinding.modifiers): |
223 |
0 |
hasIt = True |
224 |
0 |
elif typeOfSearch == "description": |
225 |
0 |
if keyBinding.handler._description \ |
226 |
|
== newKeyBinding.handler._description: |
227 |
0 |
hasIt = True |
228 |
0 |
elif typeOfSearch == "keys": |
229 |
0 |
if (keyBinding.keysymstring \ |
230 |
|
== newKeyBinding.keysymstring) \ |
231 |
|
and (keyBinding.modifier_mask \ |
232 |
|
== newKeyBinding.modifier_mask) \ |
233 |
|
and (keyBinding.modifiers \ |
234 |
|
== newKeyBinding.modifiers): |
235 |
0 |
hasIt = True |
236 |
|
|
237 |
102 |
return hasIt |
238 |
|
|
239 |
1 |
def getInputHandler(self, keyboardEvent): |
240 |
|
"""Returns the input handler of the key binding that matches the |
241 |
|
given keycode and modifiers, or None if no match exists. |
242 |
|
""" |
243 |
6668 |
handler = None |
244 |
202521 |
for keyBinding in self.keyBindings: |
245 |
196161 |
if keyBinding.matches(keyboardEvent.hw_code, \ |
246 |
196161 |
keyboardEvent.modifiers): |
247 |
308 |
handler = keyBinding.handler |
248 |
308 |
break |
249 |
6668 |
return handler |
250 |
|
|
251 |
1 |
def consumeKeyboardEvent(self, script, keyboardEvent): |
252 |
|
"""Attempts to consume the given keyboard event. If these |
253 |
|
keybindings have a handler for the given keyboardEvent, it is |
254 |
|
assumed the event will always be consumed. |
255 |
|
""" |
256 |
|
|
257 |
3411 |
consumed = False |
258 |
3411 |
handler = self.getInputHandler(keyboardEvent) |
259 |
3411 |
if handler: |
260 |
154 |
consumed = True |
261 |
154 |
if keyboardEvent.type == atspi.Accessibility.KEY_PRESSED_EVENT: |
262 |
77 |
try: |
263 |
77 |
handler.processInputEvent(script, keyboardEvent) |
264 |
0 |
except: |
265 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
266 |
|
|
267 |
3411 |
return consumed |