Coverage Report - orca.keybindings

ModuleCoverage %
orca.keybindings
44%
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