root/umit-keybinder/code/bind.c

Revision 5765, 13.0 kB (checked in by diogo, 22 months ago)

umit-keybinder added

  • Property svn:executable set to *
Line 
1/* bind.c
2 * Copyright (C) 2008 Alex Graveley
3 * Copyright (C) 2010 Ulrik Sverdrup <ulrik.sverdrup@gmail.com>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24#include <string.h>
25#include <stdio.h>
26#include <unistd.h>
27
28#include "keybinder.h"
29
30#include "gtk/gtk.h"
31#include "gdk/gdk.h"
32#include "gdk/gdkx.h"
33#include "X11/Xlib.h"
34#include "X11/XKBlib.h"
35
36/* Uncomment the next line to print a debug trace. */
37/* #define DEBUG */
38
39#ifdef DEBUG
40#  define TRACE(x) x
41#else
42#  define TRACE(x) do {} while (FALSE);
43#endif
44
45#define MODIFIERS_ERROR ((GdkModifierType)(-1))
46#define MODIFIERS_NONE 0
47
48/* Group to use: Which of configured keyboard Layouts
49 * Since grabbing a key blocks its use, we can't grab the corresponding
50 * (physical) keys for alternative layouts.
51 *
52 * Because of this, we interpret all keys relative to the default
53 * keyboard layout.
54 *
55 * For example, if you bind "w", the physical W key will respond to
56 * the bound key, even if you switch to a keyboard layout where the W key
57 * types a different letter.
58 */
59#define WE_ONLY_USE_ONE_GROUP 0
60
61
62struct Binding {
63        KeybinderHandler      handler;
64        void                 *user_data;
65        char                 *keystring;
66        /* GDK "distilled" values */
67        guint                 keyval;
68        GdkModifierType       modifiers;
69};
70
71static GSList *bindings = NULL;
72static guint32 last_event_time = 0;
73static gboolean processing_event = FALSE;
74
75/* Return the modifier mask that needs to be pressed to produce key in the
76 * given group (keyboard layout) and level ("shift level").
77 */
78static GdkModifierType
79FinallyGetModifiersForKeycode (XkbDescPtr xkb,
80                               KeyCode    key,
81                               uint     group,
82                               uint     level)
83{
84        int nKeyGroups;
85        int effectiveGroup;
86        XkbKeyTypeRec *type;
87        int k;
88
89        nKeyGroups = XkbKeyNumGroups(xkb, key);
90        if ((!XkbKeycodeInRange(xkb, key)) || (nKeyGroups == 0)) {
91                return MODIFIERS_ERROR;
92        }
93
94        /* Taken from GDK's MyEnhancedXkbTranslateKeyCode */
95        /* find the offset of the effective group */
96        effectiveGroup = group;
97        if (effectiveGroup >= nKeyGroups) {
98                unsigned groupInfo = XkbKeyGroupInfo(xkb,key);
99                switch (XkbOutOfRangeGroupAction(groupInfo)) {
100                        default:
101                                effectiveGroup %= nKeyGroups;
102                                break;
103                        case XkbClampIntoRange:
104                                effectiveGroup = nKeyGroups-1;
105                                break;
106                        case XkbRedirectIntoRange:
107                                effectiveGroup = XkbOutOfRangeGroupNumber(groupInfo);
108                                if (effectiveGroup >= nKeyGroups)
109                                        effectiveGroup = 0;
110                                break;
111                }
112        }
113        type = XkbKeyKeyType(xkb, key, effectiveGroup);
114        for (k = 0; k < type->map_count; k++) {
115                if (type->map[k].active && type->map[k].level == level) {
116                        if (type->preserve) {
117                                return (type->map[k].mods.mask &
118                                        ~type->preserve[k].mask);
119                        } else {
120                                return type->map[k].mods.mask;
121                        }
122                }
123        }
124        return MODIFIERS_NONE;
125}
126
127/* Grab or ungrab the keycode+modifiers combination, first plainly, and then
128 * including each ignorable modifier in turn.
129 */
130static gboolean
131grab_ungrab_with_ignorable_modifiers (GdkWindow *rootwin,
132                                      uint       keycode,
133                                      uint       modifiers,
134                                      gboolean   grab)
135{
136        guint i;
137        gboolean success = FALSE;
138
139        /* Ignorable modifiers */
140        guint mod_masks [] = {
141                0, /* modifier only */
142                GDK_MOD2_MASK,
143                GDK_LOCK_MASK,
144                GDK_MOD2_MASK | GDK_LOCK_MASK,
145        };
146
147        gdk_error_trap_push ();
148
149        for (i = 0; i < G_N_ELEMENTS (mod_masks); i++) {
150                if (grab) {
151                        XGrabKey (GDK_WINDOW_XDISPLAY (rootwin),
152                                  keycode,
153                                  modifiers | mod_masks [i],
154                                  GDK_WINDOW_XWINDOW (rootwin),
155                                  False,
156                                  GrabModeAsync,
157                                  GrabModeAsync);
158                } else {
159                        XUngrabKey (GDK_WINDOW_XDISPLAY (rootwin),
160                                    keycode,
161                                    modifiers | mod_masks [i],
162                                    GDK_WINDOW_XWINDOW (rootwin));
163                }
164        }
165        gdk_flush();
166        if (gdk_error_trap_pop()) {
167                TRACE (g_warning ("Failed grab/ungrab"));
168                if (grab) {
169                        /* On error, immediately release keys again */
170                        grab_ungrab_with_ignorable_modifiers(rootwin,
171                                                             keycode,
172                                                             modifiers,
173                                                             FALSE);
174                }
175        } else {
176                success = TRUE;
177        }
178        return success;
179}
180
181/* Grab or ungrab then keyval and modifiers combination, grabbing all key
182 * combinations yielding the same key values.
183 * Includes ignorable modifiers using grab_ungrab_with_ignorable_modifiers.
184 */
185static gboolean
186grab_ungrab (GdkWindow *rootwin,
187             uint       keyval,
188             uint       modifiers,
189             gboolean   grab)
190{
191        int k;
192        GdkKeymapKey *keys;
193        gint n_keys;
194        GdkModifierType add_modifiers;
195        XkbDescPtr xmap;
196        gboolean success = FALSE;
197
198        xmap = XkbGetMap(GDK_WINDOW_XDISPLAY(rootwin),
199                         XkbAllClientInfoMask,
200                         XkbUseCoreKbd);
201
202        gdk_keymap_get_entries_for_keyval(NULL, keyval, &keys, &n_keys);
203
204        if (n_keys == 0)
205                return FALSE;
206
207        for (k = 0; k < n_keys; k++) {
208                /* NOTE: We only bind for the first group,
209                 * so regardless of current keyboard layout, it will
210                 * grab the key from the default Layout.
211                 */
212                if (keys[k].group != WE_ONLY_USE_ONE_GROUP) {
213                        continue;
214                }
215
216                add_modifiers = FinallyGetModifiersForKeycode(xmap,
217                                                              keys[k].keycode,
218                                                              keys[k].group,
219                                                              keys[k].level);
220
221                if (add_modifiers == MODIFIERS_ERROR) {
222                        continue;
223                }
224                TRACE (g_print("grab/ungrab keycode: %d, lev: %d, grp: %d, ",
225                        keys[k].keycode, keys[k].level, keys[k].group));
226                TRACE (g_print("modifiers: 0x%x (consumed: 0x%x)\n",
227                               add_modifiers | modifiers, add_modifiers));
228                if (grab_ungrab_with_ignorable_modifiers(rootwin,
229                                                         keys[k].keycode,
230                                                         add_modifiers | modifiers,
231                                                         grab)) {
232
233                        success = TRUE;
234                } else {
235                        /* When grabbing, break on error */
236                        if (grab && !success) {
237                                break;
238                        }
239                }
240
241        }
242        g_free(keys);
243        XkbFreeClientMap(xmap, 0, TRUE);
244
245        return success;
246}
247
248static gboolean
249keyvalues_equal (guint kv1, guint kv2)
250{
251        return kv1 == kv2;
252}
253
254/* Compare modifier set equality,
255 * while accepting overloaded modifiers (MOD1 and META together)
256 */
257static gboolean
258modifiers_equal (GdkModifierType mf1, GdkModifierType mf2)
259{
260        GdkModifierType ignored = 0;
261
262        /* Accept MOD1 + META as MOD1 */
263        if (mf1 & mf2 & GDK_MOD1_MASK) {
264                ignored |= GDK_META_MASK;
265        }
266        /* Accept SUPER + HYPER as SUPER */
267        if (mf1 & mf2 & GDK_SUPER_MASK) {
268                ignored |= GDK_HYPER_MASK;
269        }
270        if ((mf1 & ~ignored) == (mf2 & ~ignored)) {
271                return TRUE;
272        }
273        return FALSE;
274}
275
276static gboolean
277do_grab_key (struct Binding *binding)
278{
279        gboolean success;
280        GdkWindow *rootwin = gdk_get_default_root_window ();
281        GdkKeymap *keymap = gdk_keymap_get_default ();
282
283
284        GdkModifierType modifiers;
285        guint keysym = 0;
286
287        if (keymap == NULL || rootwin == NULL)
288                return FALSE;
289
290        gtk_accelerator_parse(binding->keystring, &keysym, &modifiers);
291
292        if (keysym == 0)
293                return FALSE;
294
295        binding->keyval = keysym;
296        binding->modifiers = modifiers;
297        TRACE (g_print ("Grabbing keyval: %d, vmodifiers: 0x%x, name: %s\n",
298                        keysym, modifiers, binding->keystring));
299
300        /* Map virtual modifiers to non-virtual modifiers */
301        gdk_keymap_map_virtual_modifiers(keymap, &modifiers);
302
303        if (modifiers == binding->modifiers &&
304            (GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK) & modifiers) {
305                g_warning ("Failed to map virtual modifiers");
306                return FALSE;
307        }
308
309        success = grab_ungrab (rootwin, keysym, modifiers, TRUE /* grab */);
310
311        if (!success) {
312           g_warning ("Binding '%s' failed!", binding->keystring);
313        }
314
315        return success;
316}
317
318static gboolean
319do_ungrab_key (struct Binding *binding)
320{
321        GdkKeymap *keymap = gdk_keymap_get_default ();
322        GdkWindow *rootwin = gdk_get_default_root_window ();
323        GdkModifierType modifiers;
324
325        if (keymap == NULL || rootwin == NULL)
326                return FALSE;
327
328        TRACE (g_print ("Ungrabbing keyval: %d, vmodifiers: 0x%x, name: %s\n",
329                        binding->keyval, binding->modifiers, binding->keystring));
330
331        /* Map virtual modifiers to non-virtual modifiers */
332        modifiers = binding->modifiers;
333        gdk_keymap_map_virtual_modifiers(keymap, &modifiers);
334
335        grab_ungrab (rootwin, binding->keyval, modifiers, FALSE /* ungrab */);
336        return TRUE;
337}
338
339static GdkFilterReturn
340filter_func (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
341{
342        XEvent *xevent = (XEvent *) gdk_xevent;
343        GdkKeymap *keymap = gdk_keymap_get_default();
344        guint keyval;
345        GdkModifierType consumed, modifiers;
346        guint mod_mask = gtk_accelerator_get_default_mod_mask();
347        GSList *iter;
348
349        (void) event;
350        (void) data;
351
352        switch (xevent->type) {
353        case KeyPress:
354                modifiers = xevent->xkey.state;
355
356                TRACE (g_print ("Got KeyPress keycode: %d, modifiers: 0x%x\n", 
357                                xevent->xkey.keycode, 
358                                xevent->xkey.state));
359
360                gdk_keymap_translate_keyboard_state(
361                                keymap,
362                                xevent->xkey.keycode,
363                                modifiers,
364                                /* See top comment why we don't use this here:
365                                   XkbGroupForCoreState (xevent->xkey.state)
366                                 */
367                                WE_ONLY_USE_ONE_GROUP,
368                                &keyval, NULL, NULL, &consumed);
369
370                /* Map non-virtual to virtual modifiers */
371                modifiers &= ~consumed;
372                gdk_keymap_add_virtual_modifiers(keymap, &modifiers);
373                modifiers &= mod_mask;
374
375                TRACE (g_print ("Translated keyval: %d, vmodifiers: 0x%x, name: %s\n",
376                                keyval, modifiers,
377                                gtk_accelerator_name(keyval, modifiers)));
378
379                /*
380                 * Set the last event time for use when showing
381                 * windows to avoid anti-focus-stealing code.
382                 */
383                processing_event = TRUE;
384                last_event_time = xevent->xkey.time;
385
386                for (iter = bindings; iter != NULL; iter = iter->next) {
387                        struct Binding *binding = iter->data;
388
389                        if (keyvalues_equal(binding->keyval, keyval) &&
390                            modifiers_equal(binding->modifiers, modifiers)) {
391                                TRACE (g_print ("Calling handler for '%s'...\n", 
392                                                binding->keystring));
393
394                                (binding->handler) (binding->keystring, 
395                                                    binding->user_data);
396                        }
397                }
398
399                processing_event = FALSE;
400                break;
401        case KeyRelease:
402                TRACE (g_print ("Got KeyRelease! \n"));
403                break;
404        }
405
406        return GDK_FILTER_CONTINUE;
407}
408
409static void
410keymap_changed (GdkKeymap *map)
411{
412        GSList *iter;
413
414        (void) map;
415
416        TRACE (g_print ("Keymap changed! Regrabbing keys..."));
417
418        for (iter = bindings; iter != NULL; iter = iter->next) {
419                struct Binding *binding = iter->data;
420                do_ungrab_key (binding);
421        }
422
423        for (iter = bindings; iter != NULL; iter = iter->next) {
424                struct Binding *binding = iter->data;
425                do_grab_key (binding);
426        }
427}
428
429void
430keybinder_init ()
431{
432        GdkKeymap *keymap = gdk_keymap_get_default ();
433        GdkWindow *rootwin = gdk_get_default_root_window ();
434
435        gdk_window_add_filter (rootwin, filter_func, NULL);
436
437        /* Workaround: Make sure modmap is up to date
438         * There is possibly a bug in GTK+ where virtual modifiers are not
439         * mapped because the modmap is not updated. The following function
440         * updates it.
441         */
442        (void) gdk_keymap_have_bidi_layouts(keymap);
443
444
445        g_signal_connect (keymap, 
446                          "keys_changed",
447                          G_CALLBACK (keymap_changed),
448                          NULL);
449}
450
451gboolean
452keybinder_bind (const char *keystring,
453                KeybinderHandler handler,
454                void *user_data)
455{
456        struct Binding *binding;
457        gboolean success;
458
459        binding = g_new0 (struct Binding, 1);
460        binding->keystring = g_strdup (keystring);
461        binding->handler = handler;
462        binding->user_data = user_data;
463
464        /* Sets the binding's keycode and modifiers */
465        success = do_grab_key (binding);
466
467        if (success) {
468                bindings = g_slist_prepend (bindings, binding);
469        } else {
470                g_free (binding->keystring);
471                g_free (binding);
472        }
473        return success;
474}
475
476void
477keybinder_unbind (const char *keystring, KeybinderHandler handler)
478{
479        GSList *iter;
480
481        for (iter = bindings; iter != NULL; iter = iter->next) {
482                struct Binding *binding = iter->data;
483
484                if (strcmp (keystring, binding->keystring) != 0 ||
485                    handler != binding->handler) 
486                        continue;
487
488                do_ungrab_key (binding);
489
490                bindings = g_slist_remove (bindings, binding);
491
492                g_free (binding->keystring);
493                g_free (binding);
494                break;
495        }
496}
497
498guint32
499keybinder_get_current_event_time (void)
500{
501        if (processing_event)
502                return last_event_time;
503        else
504                return GDK_CURRENT_TIME;
505}
Note: See TracBrowser for help on using the browser.