Coverage Report - orca.keybindings

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