]> git.sur5r.net Git - i3/i3/commitdiff
Use libxkbcommon for translating keysyms, support all XKB groups.
authorMichael Stapelberg <michael@stapelberg.de>
Sun, 23 Aug 2015 20:49:32 +0000 (22:49 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Wed, 26 Aug 2015 07:56:42 +0000 (09:56 +0200)
fixes #1835

This commit improves the translation of keysyms to keycodes by loading
keymaps using libxkbcommon-x11 and using libxkbcommon for figuring out
the keymap, depending on each keybinding’s modifiers. This way, the
upper layers of complex layouts are now usable with i3’s bindsym
directive, such as de_neo’s layer 3 and higher.

Furthermore, the commit generalizes the handling of different XKB
groups. We formerly had support only for two separate groups, the
default group 1, and group 2. While Mode_switch is only one way to
switch to group 2, we called the binding option Mode_switch. With this
commit, the new names Group1, Group2 (an alias for Mode_switch), Group3
and Group4 are introduced for configuring bindings. This is only useful
for advanced keyboard layouts, such as people loading two keyboard
layouts and switching between them (us, ru seems to be a popular
combination).

When grabbing keys, one can only specify the modifier mask, but not an
XKB state mask (or value), so we still dynamically unbind and re-bind
keys whenever the XKB group changes.

The commit was manually tested using the following i3 config:

    bindsym Group4+n nop heya from group 4
    bindsym Group3+n nop heya from group 3
    bindsym Group2+n nop heya from group 2
    bindsym n nop heya
    bindsym shift+N nop explicit shift binding
    bindsym shift+r nop implicit shift binding
    bindcode Group2+38 nop fallback overwritten in group 2 only
    bindcode 38 nop fallback

…with the following layout:

    setxkbmap -layout "us,ua,ru,de" -variant ",winkeys,,neo" \
      -option "grp:shift_caps_toggle,grp_led:scroll" \
      -model pc104 -rules evdev

By default (xkb group 1, us layout), pressing “n” will result in the
“heya” message appearing. Pressing “a” will result in the “fallback”
message appearing. “j” is not triggered.

By pressing Shift+CapsLock you switch to the next group (xkb group 2, ua
layout). Pressing “a” will result in the “fallback overwritten in group
2 only” message, pressing “n” will still result in “heya”. “j” is not
triggered.

In the next group (xkb group 3, ru layout), pressing “a” will result in
the “fallback” message again, pressing “n” will result in “heya”,
“j” is not triggered.

In the last group (xkb group 4, de_neo layout), pressing “a” will still
result in “fallback”, pressing “n” will result in “heya”, pressing “j”
will result in “heya from group 4”.

Pressing shift+n results in “explicit shift binding”, pressing shift+r
results in “implicit shift binding”. This ensures that keysym
translation falls back to looking at non-shift keys (“r” can be used
instead of ”R”) and that the order of keybindings doesn’t play a role
(“bindsym n” does not override “bindsym shift+n”, even though it’s
specified earlier in the config).

The fallback behavior ensures use-cases such as ticket #1775 are still
covered.

Only binding keys when the X server is in the corresponding XKB group
ensures use-cases such as ticket #585 are still covered.

16 files changed:
docs/ipc
docs/userguide
include/bindings.h
include/config_directives.h
include/data.h
parser-specs/config.spec
src/bindings.c
src/click.c
src/config.c
src/config_directives.c
src/config_parser.c
src/handlers.c
src/i3.mk
src/ipc.c
src/key_press.c
src/main.c

index 4462e772ba405cfc5332bea42eed400922085076..5113d79bf60abc178c798ab1f33ca30873fa7b9b 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -773,8 +773,8 @@ The +binding (object)+ field contains details about the binding that was run:
 
 command (string)::
        The i3 command that is configured to run for this binding.
-mods (array of strings)::
-       The modifier keys that were configured with this binding.
+event_state_mask (array of strings)::
+       The group and modifier keys that were configured with this binding.
 input_code (integer)::
        If the binding was configured with +bindcode+, this will be the key code
        that was given for the binding. If the binding is a mouse binding, it will be
@@ -792,7 +792,7 @@ input_type (string)::
  "change": "run",
  "binding": {
   "command": "nop",
-  "mods": [
+  "event_state_mask": [
     "shift",
     "ctrl"
   ],
index 9316a4d598073202c51a114b334e70b4764d49ff..25f5a4ba0036752b39fdf1465f2f04c1fbcd179e 100644 (file)
@@ -368,8 +368,8 @@ after the keys have been released.
 
 *Syntax*:
 ----------------------------------
-bindsym [--release] [<Modifiers>+]<keysym> command
-bindcode [--release] [<Modifiers>+]<keycode> command
+bindsym [--release] [<Group>+][<Modifiers>+]<keysym> command
+bindcode [--release] [<Group>+][<Modifiers>+]<keycode> command
 ----------------------------------
 
 *Examples*:
@@ -395,12 +395,13 @@ Available Modifiers:
 Mod1-Mod5, Shift, Control::
 Standard modifiers, see +xmodmap(1)+
 
-Mode_switch::
-Unlike other window managers, i3 can use Mode_switch as a modifier. This allows
-you to remap capslock (for example) to Mode_switch and use it for both: typing
-umlauts or special characters 'and' having some comfortably reachable key
-bindings. For example, when typing, capslock+1 or capslock+2 for switching
-workspaces is totally convenient. Try it :-).
+Group1, Group2, Group3, Group4::
+When using multiple keyboard layouts (e.g. with `setxkbmap -layout us,ru`), you
+can specify in which XKB group (also called “layout”) a keybinding should be
+active. By default, keybindings are translated in Group1 and are active in all
+groups. If you want to override keybindings in one of your layouts, specify the
+corresponding group. For backwards compatibility, the group “Mode_switch” is an
+alias for Group2.
 
 [[mousebindings]]
 
index 75a719e9469e7d84d650440c6a2146d1a08b20a5..88b4a6cc96ff347fb6b70a907779a1d081c1481d 100644 (file)
@@ -31,7 +31,7 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
  *
  */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
+void grab_all_keys(xcb_connection_t *conn);
 
 /**
  * Returns a pointer to the Binding that matches the given xcb event or NULL if
@@ -52,6 +52,21 @@ void translate_keysyms(void);
  */
 void switch_mode(const char *new_mode);
 
+/**
+ * Reorders bindings by event_state_mask descendingly so that get_binding()
+ * correctly matches more specific bindings before more generic bindings. Take
+ * the following binding configuration as an example:
+ *
+ *   bindsym n nop lower-case n pressed
+ *   bindsym Shift+n nop upper-case n pressed
+ *
+ * Without reordering, the first binding’s event_state_mask of 0x0 would match
+ * the actual event_stat_mask of 0x1 and hence trigger instead of the second
+ * keybinding.
+ *
+ */
+void reorder_bindings(void);
+
 /**
  * Checks for duplicate key bindings (the same keycode or keysym is configured
  * more than once). If a duplicate binding is found, a message is printed to
@@ -74,3 +89,9 @@ void binding_free(Binding *bind);
  *
  */
 CommandResult *run_binding(Binding *bind, Con *con);
+
+/**
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ *
+ */
+bool load_keymap(void);
index fc34a85f751e3862796915e9fc5462a7b5e12078..72646651b2955d71992d3e84b524c9e540ce9a95 100644 (file)
 #include "config_parser.h"
 
 /**
- * A utility function to convert a string of modifiers to the corresponding bit
- * mask.
+ * A utility function to convert a string containing the group and modifiers to
+ * the corresponding bit mask.
  */
-uint32_t modifiers_from_str(const char *str);
+i3_event_state_mask_t event_state_from_str(const char *str);
 
 /** The beginning of the prototype for every cfg_ function. */
 #define I3_CFG Match *current_match, struct ConfigResultIR *result
index 66000e45462fa03fda2b5f2b9548aa023b14272d..634cca674f5bcb7707c1336d5be92ac5a8ecc891 100644 (file)
@@ -74,18 +74,6 @@ typedef enum { ADJ_NONE = 0,
                ADJ_UPPER_SCREEN_EDGE = (1 << 2),
                ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
 
-enum {
-    BIND_NONE = 0,
-    BIND_SHIFT = XCB_MOD_MASK_SHIFT,     /* (1 << 0) */
-    BIND_CONTROL = XCB_MOD_MASK_CONTROL, /* (1 << 2) */
-    BIND_MOD1 = XCB_MOD_MASK_1,          /* (1 << 3) */
-    BIND_MOD2 = XCB_MOD_MASK_2,          /* (1 << 4) */
-    BIND_MOD3 = XCB_MOD_MASK_3,          /* (1 << 5) */
-    BIND_MOD4 = XCB_MOD_MASK_4,          /* (1 << 6) */
-    BIND_MOD5 = XCB_MOD_MASK_5,          /* (1 << 7) */
-    BIND_MODE_SWITCH = (1 << 8)
-};
-
 /**
  * Container layouts. See Con::layout.
  */
@@ -107,6 +95,25 @@ typedef enum {
     B_MOUSE = 1
 } input_type_t;
 
+/**
+ * Bitmask for matching XCB_XKB_GROUP_1 to XCB_XKB_GROUP_4.
+ */
+typedef enum {
+    I3_XKB_GROUP_MASK_ANY = 0,
+    I3_XKB_GROUP_MASK_1 = (1 << 0),
+    I3_XKB_GROUP_MASK_2 = (1 << 1),
+    I3_XKB_GROUP_MASK_3 = (1 << 2),
+    I3_XKB_GROUP_MASK_4 = (1 << 3)
+} i3_xkb_group_mask_t;
+
+/**
+ * The lower 16 bits contain a xcb_key_but_mask_t, the higher 16 bits contain
+ * an i3_xkb_group_mask_t. This type is necessary for the fallback logic to
+ * work when handling XKB groups (see ticket #1775) and makes the code which
+ * locates keybindings upon KeyPress/KeyRelease events simpler.
+ */
+typedef uint32_t i3_event_state_mask_t;
+
 /**
  * Mouse pointer warping modes.
  */
@@ -269,8 +276,10 @@ struct Binding {
     /** Keycode to bind */
     uint32_t keycode;
 
-    /** Bitmask consisting of BIND_MOD_1, BIND_MODE_SWITCH, … */
-    uint32_t mods;
+    /** Bitmask which is applied against event->state for KeyPress and
+     * KeyRelease events to determine whether this binding applies to the
+     * current state. */
+    i3_event_state_mask_t event_state_mask;
 
     /** Symbol the user specified in configfile, if any. This needs to be
      * stored with the binding to be able to re-convert it into a keycode
index 3767acf8a4847ce21ea074b079c0e04e6dbaaa89..1191157c6cbeb651208772f9916b479580e84638 100644 (file)
@@ -316,7 +316,7 @@ state BINDING:
       ->
   whole_window = '--whole-window'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
       ->
@@ -369,7 +369,7 @@ state MODE_BINDING:
       ->
   whole_window = '--whole-window'
       ->
-  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
+  modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
       ->
index adca55464d6076da40f981d6a782d59126a3a2bd..f6ed086b32c346ed09285dc713277a6b31c00f71 100644 (file)
@@ -9,6 +9,10 @@
 #include "all.h"
 
 #include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-x11.h>
+
+static struct xkb_context *xkb_context;
+static struct xkb_keymap *xkb_keymap;
 
 pid_t command_error_nagbar_pid = -1;
 
@@ -73,8 +77,19 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
             return NULL;
         }
     }
-    new_binding->mods = modifiers_from_str(modifiers);
     new_binding->command = sstrdup(command);
+    new_binding->event_state_mask = event_state_from_str(modifiers);
+    int group_bits_set = 0;
+    if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_1)
+        group_bits_set++;
+    if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_2)
+        group_bits_set++;
+    if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_3)
+        group_bits_set++;
+    if (new_binding->event_state_mask & I3_XKB_GROUP_MASK_4)
+        group_bits_set++;
+    if (group_bits_set > 1)
+        ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n");
 
     struct Mode *mode = mode_from_name(modename);
     TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings);
@@ -86,18 +101,23 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
     if (bind->input_type != B_KEYBOARD)
         return;
 
-    DLOG("Grabbing %d with modifiers %d (with mod_mask_lock %d)\n", keycode, bind->mods, bind->mods | XCB_MOD_MASK_LOCK);
 /* Grab the key in all combinations */
 #define GRAB_KEY(modifier)                                                                       \
     do {                                                                                         \
         xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
     } while (0)
-    int mods = bind->mods;
-    if ((bind->mods & BIND_MODE_SWITCH) != 0) {
-        mods &= ~BIND_MODE_SWITCH;
-        if (mods == 0)
-            mods = XCB_MOD_MASK_ANY;
-    }
+    int mods = bind->event_state_mask;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_1) && xkb_current_group != XCB_XKB_GROUP_1)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_2) && xkb_current_group != XCB_XKB_GROUP_2)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_3) && xkb_current_group != XCB_XKB_GROUP_3)
+        return;
+    if (((mods >> 16) & I3_XKB_GROUP_MASK_4) && xkb_current_group != XCB_XKB_GROUP_4)
+        return;
+    mods &= 0xFFFF;
+    DLOG("Grabbing keycode %d with event state mask 0x%x (mods 0x%x)\n",
+         keycode, bind->event_state_mask, mods);
     GRAB_KEY(mods);
     GRAB_KEY(mods | xcb_numlock_mask);
     GRAB_KEY(mods | XCB_MOD_MASK_LOCK);
@@ -108,7 +128,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
  *
  */
-void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
+void grab_all_keys(xcb_connection_t *conn) {
     Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
         if (bind->input_type != B_KEYBOARD)
@@ -120,9 +140,8 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
             continue;
         }
 
-        xcb_keycode_t *walk = bind->translated_to;
         for (uint32_t i = 0; i < bind->number_keycodes; i++)
-            grab_keycode_for_binding(conn, bind, *walk++);
+            grab_keycode_for_binding(conn, bind, bind->translated_to[i]);
     }
 }
 
@@ -131,7 +150,7 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
  * keycode or NULL if no such binding exists.
  *
  */
-static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_code, input_type_t input_type) {
+static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_release, uint16_t input_code, input_type_t input_type) {
     Binding *bind;
 
     if (!is_release) {
@@ -146,12 +165,15 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
     }
 
     TAILQ_FOREACH(bind, bindings, bindings) {
-        /* First compare the modifiers (unless this is a
+        DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n",
+             bind->event_state_mask, state_filtered,
+             ((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no");
+        /* First compare the state_filtered (unless this is a
          * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
          * event) */
         if (bind->input_type != input_type)
             continue;
-        if (bind->mods != modifiers &&
+        if ((state_filtered & bind->event_state_mask) != bind->event_state_mask &&
             (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
              !is_release))
             continue;
@@ -195,49 +217,89 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_
  *
  */
 Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
-    bool is_release = (event->response_type == XCB_KEY_RELEASE || event->response_type == XCB_BUTTON_RELEASE);
-
-    input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE || event->response_type == XCB_BUTTON_PRESS)
-                                   ? B_MOUSE
-                                   : B_KEYBOARD);
-
-    uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
-    uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
+    const bool is_release = (event->response_type == XCB_KEY_RELEASE ||
+                             event->response_type == XCB_BUTTON_RELEASE);
+
+    const input_type_t input_type = ((event->response_type == XCB_BUTTON_RELEASE ||
+                                      event->response_type == XCB_BUTTON_PRESS)
+                                         ? B_MOUSE
+                                         : B_KEYBOARD);
+
+    const uint16_t event_state = ((xcb_key_press_event_t *)event)->state;
+    const uint16_t event_detail = ((xcb_key_press_event_t *)event)->detail;
+
+    /* Remove the numlock bit */
+    i3_event_state_mask_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+    DLOG("(removed numlock, state = 0x%x)\n", state_filtered);
+    /* Transform the keyboard_group from bit 13 and bit 14 into an
+     * i3_xkb_group_mask_t, so that get_binding() can just bitwise AND the
+     * configured bindings against |state_filtered|.
+     *
+     * These bits are only set because we set the XKB client flags
+     * XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
+     * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED. See also doc/kbproto
+     * section 2.2.2:
+     * http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Computing_A_State_Field_from_an_XKB_State */
+    switch ((event_state & 0x6000) >> 13) {
+        case XCB_XKB_GROUP_1:
+            state_filtered |= (I3_XKB_GROUP_MASK_1 << 16);
+            break;
+        case XCB_XKB_GROUP_2:
+            state_filtered |= (I3_XKB_GROUP_MASK_2 << 16);
+            break;
+        case XCB_XKB_GROUP_3:
+            state_filtered |= (I3_XKB_GROUP_MASK_3 << 16);
+            break;
+        case XCB_XKB_GROUP_4:
+            state_filtered |= (I3_XKB_GROUP_MASK_4 << 16);
+            break;
+    }
+    state_filtered &= ~0x6000;
+    DLOG("(transformed keyboard group, state = 0x%x)\n", state_filtered);
+    return get_binding(state_filtered, is_release, event_detail, input_type);
+}
 
-    /* Remove the numlock bit, all other bits are modifiers we can bind to */
-    uint16_t state_filtered = event_state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
-    DLOG("(removed numlock, state = %d)\n", state_filtered);
-    /* Only use the lower 8 bits of the state (modifier masks) so that mouse
-     * button masks are filtered out */
-    state_filtered &= 0xFF;
-    DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
+struct resolve {
+    /* The binding which we are resolving. */
+    Binding *bind;
 
-    if (xkb_current_group == XCB_XKB_GROUP_2)
-        state_filtered |= BIND_MODE_SWITCH;
+    /* |bind|’s keysym (translated to xkb_keysym_t), e.g. XKB_KEY_R. */
+    xkb_keysym_t keysym;
 
-    DLOG("(checked mode_switch, state %d)\n", state_filtered);
+    /* The xkb state built from the user-provided modifiers and group. */
+    struct xkb_state *xkb_state;
 
-    /* Find the binding */
-    Binding *bind = get_binding(state_filtered, is_release, event_detail, input_type);
+    /* Like |xkb_state|, just without the shift modifier, if shift was specified. */
+    struct xkb_state *xkb_state_no_shift;
+};
 
-    /* No match? Then the user has Mode_switch enabled but does not have a
-     * specific keybinding. Fall back to the default keybindings (without
-     * Mode_switch). Makes it much more convenient for users of a hybrid
-     * layout (like {us, ru} or {dvorak, us}, see e.g. ticket #1775). */
-    if (bind == NULL) {
-        state_filtered &= ~(BIND_MODE_SWITCH);
-        DLOG("no match, new state_filtered = %d\n", state_filtered);
-        if ((bind = get_binding(state_filtered, is_release, event_detail, input_type)) == NULL) {
-            /* This is not a real error since we can have release and
-             * non-release bindings. On a press event for which there is only a
-             * !release-binding, but no release-binding, the corresponding
-             * release event will trigger this. No problem, though. */
-            DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
-                 state_filtered, event_detail);
-        }
+/*
+ * add_keycode_if_matches is called for each keycode in the keymap and will add
+ * the keycode to |data->bind| if the keycode can result in the keysym
+ * |data->resolving|.
+ *
+ */
+static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) {
+    const struct resolve *resolving = data;
+    xkb_keysym_t sym = xkb_state_key_get_one_sym(resolving->xkb_state, key);
+    if (sym != resolving->keysym) {
+        /* Check if Shift was specified, and try resolving the symbol without
+         * shift, so that “bindsym $mod+Shift+a nop” actually works. */
+        const xkb_layout_index_t layout = xkb_state_key_get_layout(resolving->xkb_state, key);
+        if (layout == XKB_LAYOUT_INVALID)
+            return;
+        if (xkb_state_key_get_level(resolving->xkb_state, key, layout) != 1)
+            return;
+        sym = xkb_state_key_get_one_sym(resolving->xkb_state_no_shift, key);
+        if (sym != resolving->keysym)
+            return;
     }
-
-    return bind;
+    Binding *bind = resolving->bind;
+    bind->number_keycodes++;
+    bind->translated_to = srealloc(bind->translated_to,
+                                   (sizeof(xcb_keycode_t) *
+                                    bind->number_keycodes));
+    bind->translated_to[bind->number_keycodes - 1] = key;
 }
 
 /*
@@ -245,16 +307,19 @@ Binding *get_binding_from_xcb_event(xcb_generic_event_t *event) {
  *
  */
 void translate_keysyms(void) {
-    Binding *bind;
-    xcb_keysym_t keysym;
-    int col;
-    xcb_keycode_t i, min_keycode, max_keycode;
-
-    const bool mode_switch = (xkb_current_group == XCB_XKB_GROUP_2);
+    struct xkb_state *dummy_state = xkb_state_new(xkb_keymap);
+    if (dummy_state == NULL) {
+        ELOG("Could not create XKB state, cannot translate keysyms.\n");
+        return;
+    }
 
-    min_keycode = xcb_get_setup(conn)->min_keycode;
-    max_keycode = xcb_get_setup(conn)->max_keycode;
+    struct xkb_state *dummy_state_no_shift = xkb_state_new(xkb_keymap);
+    if (dummy_state_no_shift == NULL) {
+        ELOG("Could not create XKB state, cannot translate keysyms.\n");
+        return;
+    }
 
+    Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
         if (bind->input_type == B_MOUSE) {
             char *endptr;
@@ -271,36 +336,66 @@ void translate_keysyms(void) {
             continue;
 
         /* We need to translate the symbol to a keycode */
-        keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
+        const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
         if (keysym == XKB_KEY_NoSymbol) {
             ELOG("Could not translate string to key symbol: \"%s\"\n",
                  bind->symbol);
             continue;
         }
 
-        /* Base column we use for looking up key symbols. We always consider
-         * the base column and the corresponding shift column, so without
-         * mode_switch, we look in 0 and 1, with mode_switch we look in 2 and
-         * 3. */
-        col = (bind->mods & BIND_MODE_SWITCH || mode_switch ? 2 : 0);
-
+        xkb_layout_index_t group = XCB_XKB_GROUP_1;
+        if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2)
+            group = XCB_XKB_GROUP_2;
+        else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3)
+            group = XCB_XKB_GROUP_3;
+        else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4)
+            group = XCB_XKB_GROUP_4;
+
+        DLOG("group = %d, event_state_mask = %d, &2 = %s, &3 = %s, &4 = %s\n", group,
+             bind->event_state_mask,
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_2) ? "yes" : "no",
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_3) ? "yes" : "no",
+             (bind->event_state_mask & I3_XKB_GROUP_MASK_4) ? "yes" : "no");
+        (void)xkb_state_update_mask(
+            dummy_state,
+            (bind->event_state_mask & 0x1FFF) /* xkb_mod_mask_t base_mods, */,
+            0 /* xkb_mod_mask_t latched_mods, */,
+            0 /* xkb_mod_mask_t locked_mods, */,
+            0 /* xkb_layout_index_t base_group, */,
+            0 /* xkb_layout_index_t latched_group, */,
+            group /* xkb_layout_index_t locked_group, */);
+
+        (void)xkb_state_update_mask(
+            dummy_state_no_shift,
+            (bind->event_state_mask & 0x1FFF) & ~XCB_KEY_BUT_MASK_SHIFT /* xkb_mod_mask_t base_mods, */,
+            0 /* xkb_mod_mask_t latched_mods, */,
+            0 /* xkb_mod_mask_t locked_mods, */,
+            0 /* xkb_layout_index_t base_group, */,
+            0 /* xkb_layout_index_t latched_group, */,
+            group /* xkb_layout_index_t locked_group, */);
+
+        struct resolve resolving = {
+            .bind = bind,
+            .keysym = keysym,
+            .xkb_state = dummy_state,
+            .xkb_state_no_shift = dummy_state_no_shift,
+        };
         FREE(bind->translated_to);
         bind->number_keycodes = 0;
-
-        for (i = min_keycode; i && i <= max_keycode; i++) {
-            if ((xcb_key_symbols_get_keysym(keysyms, i, col) != keysym) &&
-                (xcb_key_symbols_get_keysym(keysyms, i, col + 1) != keysym))
-                continue;
-            bind->number_keycodes++;
-            bind->translated_to = srealloc(bind->translated_to,
-                                           (sizeof(xcb_keycode_t) *
-                                            bind->number_keycodes));
-            bind->translated_to[bind->number_keycodes - 1] = i;
+        xkb_keymap_key_for_each(xkb_keymap, add_keycode_if_matches, &resolving);
+        char *keycodes = sstrdup("");
+        for (uint32_t n = 0; n < bind->number_keycodes; n++) {
+            char *tmp;
+            sasprintf(&tmp, "%s %d", keycodes, bind->translated_to[n]);
+            free(keycodes);
+            keycodes = tmp;
         }
-
-        DLOG("Translated symbol \"%s\" to %d keycode (mods %d)\n", bind->symbol,
-             bind->number_keycodes, bind->mods);
+        DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
+             bind->event_state_mask, bind->symbol, keysym, keycodes, bind->number_keycodes);
+        free(keycodes);
     }
+
+    xkb_state_unref(dummy_state);
 }
 
 /*
@@ -319,7 +414,7 @@ void switch_mode(const char *new_mode) {
         ungrab_all_keys(conn);
         bindings = mode->bindings;
         translate_keysyms();
-        grab_all_keys(conn, false);
+        grab_all_keys(conn);
 
         char *event_msg;
         sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
@@ -333,6 +428,61 @@ void switch_mode(const char *new_mode) {
     ELOG("ERROR: Mode not found\n");
 }
 
+static void reorder_bindings_of_mode(struct Mode *mode) {
+    struct bindings_head *reordered = scalloc(1, sizeof(struct bindings_head));
+    TAILQ_INIT(reordered);
+    /* 20 bits are in use in an i3_event_state_mask_t. */
+    for (int n = 19; n >= 0; n--) {
+        Binding *current;
+        for (current = TAILQ_FIRST(mode->bindings); current != TAILQ_END(mode->bindings); ) {
+            /* Advance |current| so that we can use TAILQ_REMOVE safely. */
+            Binding *bind = current;
+            current = TAILQ_NEXT(current, bindings);
+            if ((bind->event_state_mask & (1 << n)) == 0)
+                continue;
+            TAILQ_REMOVE(mode->bindings, bind, bindings);
+            TAILQ_INSERT_TAIL(reordered, bind, bindings);
+        }
+    }
+    /* Move over the bindings with event_state_mask == 0x0. */
+    Binding *current;
+    for (current = TAILQ_FIRST(mode->bindings); current != TAILQ_END(mode->bindings); ) {
+        /* Advance |current| so that we can use TAILQ_REMOVE safely. */
+        Binding *bind = current;
+        current = TAILQ_NEXT(current, bindings);
+        assert(bind->event_state_mask == 0);
+        TAILQ_REMOVE(mode->bindings, bind, bindings);
+        TAILQ_INSERT_TAIL(reordered, bind, bindings);
+    }
+    assert(TAILQ_EMPTY(mode->bindings));
+    /* Free the old bindings_head, which is now empty. */
+    free(mode->bindings);
+    mode->bindings = reordered;
+}
+
+/*
+ * Reorders bindings by event_state_mask descendingly so that get_binding()
+ * correctly matches more specific bindings before more generic bindings. Take
+ * the following binding configuration as an example:
+ *
+ *   bindsym n nop lower-case n pressed
+ *   bindsym Shift+n nop upper-case n pressed
+ *
+ * Without reordering, the first binding’s event_state_mask of 0x0 would match
+ * the actual event_stat_mask of 0x1 and hence trigger instead of the second
+ * keybinding.
+ *
+ */
+void reorder_bindings(void) {
+    struct Mode *mode;
+    SLIST_FOREACH(mode, &modes, modes) {
+        const bool current_mode = (mode->bindings == bindings);
+        reorder_bindings_of_mode(mode);
+        if (current_mode)
+            bindings = mode->bindings;
+    }
+}
+
 /*
  * Checks for duplicate key bindings (the same keycode or keysym is configured
  * more than once). If a duplicate binding is found, a message is printed to
@@ -370,17 +520,17 @@ void check_for_duplicate_bindings(struct context *context) {
             /* Check if the keycodes or modifiers are different. If so, they
              * can't be duplicate */
             if (bind->keycode != current->keycode ||
-                bind->mods != current->mods ||
+                bind->event_state_mask != current->event_state_mask ||
                 bind->release != current->release)
                 continue;
 
             context->has_errors = true;
             if (current->keycode != 0) {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
-                     current->mods, current->keycode, current->command);
+                ELOG("Duplicate keybinding in config file:\n  state mask 0x%x with keycode %d, command \"%s\"\n",
+                     current->event_state_mask, current->keycode, current->command);
             } else {
-                ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
-                     current->mods, current->symbol, current->command);
+                ELOG("Duplicate keybinding in config file:\n  state mask 0x%x with keysym %s, command \"%s\"\n",
+                     current->event_state_mask, current->symbol, current->command);
             }
         }
     }
@@ -466,3 +616,28 @@ CommandResult *run_binding(Binding *bind, Con *con) {
 
     return result;
 }
+
+/*
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ *
+ */
+bool load_keymap(void) {
+    if (xkb_context == NULL) {
+        if ((xkb_context = xkb_context_new(0)) == NULL) {
+            ELOG("Could not create xkbcommon context\n");
+            return false;
+        }
+    }
+
+    struct xkb_keymap *new_keymap;
+    const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
+    DLOG("device_id = %d\n", device_id);
+    if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
+        ELOG("xkb_x11_keymap_new_from_device failed\n");
+        return false;
+    }
+    xkb_keymap_unref(xkb_keymap);
+    xkb_keymap = new_keymap;
+
+    return true;
+}
index bd2dcb9fdac2bb5a93d8769e42b3749f93234fff..92a1a1fa20fe26bf301d7712cd63dde3a2f542c6 100644 (file)
@@ -229,7 +229,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
 
     /* get the floating con */
     Con *floatingcon = con_inside_floating(con);
-    const bool proportional = (event->state & BIND_SHIFT);
+    const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
     const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED);
 
     /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
index b0c6f1a4f544beb2bd874ed1ef4205cfd3d2df3f..0dc59365289d548c6c64c3164f7e383faa65c09d 100644 (file)
@@ -214,7 +214,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     if (reload) {
         translate_keysyms();
-        grab_all_keys(conn, false);
+        grab_all_keys(conn);
     }
 
     if (config.font.type == FONT_TYPE_NONE) {
index 6032dc11392911f17864819b7ecc4441a46259df..463b38e773cecc6e3acc505bbd3e60978a82fc9b 100644 (file)
@@ -161,32 +161,40 @@ static bool eval_boolstr(const char *str) {
 }
 
 /*
- * A utility function to convert a string of modifiers to the corresponding bit
- * mask.
+ * A utility function to convert a string containing the group and modifiers to
+ * the corresponding bit mask.
  */
-uint32_t modifiers_from_str(const char *str) {
+i3_event_state_mask_t event_state_from_str(const char *str) {
     /* It might be better to use strtok() here, but the simpler strstr() should
      * do for now. */
-    uint32_t result = 0;
+    i3_event_state_mask_t result = 0;
     if (str == NULL)
         return result;
     if (strstr(str, "Mod1") != NULL)
-        result |= BIND_MOD1;
+        result |= XCB_KEY_BUT_MASK_MOD_1;
     if (strstr(str, "Mod2") != NULL)
-        result |= BIND_MOD2;
+        result |= XCB_KEY_BUT_MASK_MOD_2;
     if (strstr(str, "Mod3") != NULL)
-        result |= BIND_MOD3;
+        result |= XCB_KEY_BUT_MASK_MOD_3;
     if (strstr(str, "Mod4") != NULL)
-        result |= BIND_MOD4;
+        result |= XCB_KEY_BUT_MASK_MOD_4;
     if (strstr(str, "Mod5") != NULL)
-        result |= BIND_MOD5;
+        result |= XCB_KEY_BUT_MASK_MOD_5;
     if (strstr(str, "Control") != NULL ||
         strstr(str, "Ctrl") != NULL)
-        result |= BIND_CONTROL;
+        result |= XCB_KEY_BUT_MASK_CONTROL;
     if (strstr(str, "Shift") != NULL)
-        result |= BIND_SHIFT;
-    if (strstr(str, "Mode_switch") != NULL)
-        result |= BIND_MODE_SWITCH;
+        result |= XCB_KEY_BUT_MASK_SHIFT;
+
+    if (strstr(str, "Group1") != NULL)
+        result |= (I3_XKB_GROUP_MASK_1 << 16);
+    if (strstr(str, "Group2") != NULL ||
+        strstr(str, "Mode_switch") != NULL)
+        result |= (I3_XKB_GROUP_MASK_2 << 16);
+    if (strstr(str, "Group3") != NULL)
+        result |= (I3_XKB_GROUP_MASK_3 << 16);
+    if (strstr(str, "Group4") != NULL)
+        result |= (I3_XKB_GROUP_MASK_4 << 16);
     return result;
 }
 
@@ -260,7 +268,7 @@ CFGFUN(floating_maximum_size, const long width, const long height) {
 }
 
 CFGFUN(floating_modifier, const char *modifiers) {
-    config.floating_modifier = modifiers_from_str(modifiers);
+    config.floating_modifier = event_state_from_str(modifiers);
 }
 
 CFGFUN(default_orientation, const char *orientation) {
index 6393efe913aa209ba9332b059a24f5be765f1d11..044234834f2e79f32e897303e5bd157c75225794 100644 (file)
@@ -997,6 +997,7 @@ bool parse_file(const char *f, bool use_nagbar) {
     yajl_gen_free(config_output->json_gen);
 
     check_for_duplicate_bindings(context);
+    reorder_bindings();
 
     if (use_nagbar && (context->has_errors || context->has_warnings)) {
         ELOG("FYI: You are using i3 version %s\n", i3_version);
index 714e183b3e19bbc6f25755c4b9d440dd62d8e730..1daefbc912de21b21f93aeb9c33c6d3d6165e01f 100644 (file)
@@ -16,7 +16,6 @@
 #include <float.h>
 #include <sys/time.h>
 #include <xcb/randr.h>
-#include <X11/XKBlib.h>
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-monitor.h>
 
@@ -264,7 +263,7 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
 
     ungrab_all_keys(conn);
     translate_keysyms();
-    grab_all_keys(conn, false);
+    grab_all_keys(conn);
 
     return;
 }
@@ -1338,7 +1337,9 @@ void handle_event(int type, xcb_generic_event_t *event) {
             keysyms = xcb_key_symbols_alloc(conn);
             ungrab_all_keys(conn);
             translate_keysyms();
-            grab_all_keys(conn, false);
+            grab_all_keys(conn);
+            if (((xcb_xkb_new_keyboard_notify_event_t *)event)->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
+                (void)load_keymap();
         } else if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
             if (event_is_ignored(event->sequence, type)) {
                 DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
@@ -1349,22 +1350,16 @@ void handle_event(int type, xcb_generic_event_t *event) {
                 keysyms = xcb_key_symbols_alloc(conn);
                 ungrab_all_keys(conn);
                 translate_keysyms();
-                grab_all_keys(conn, false);
+                grab_all_keys(conn);
+                (void)load_keymap();
             }
         } else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
             DLOG("xkb state group = %d\n", state->group);
-
-            /* See The XKB Extension: Library Specification, section 14.1 */
-            /* We check if the current group (each group contains
-             * two levels) has been changed. Mode_switch activates
-             * group XCB_XKB_GROUP_2 */
             if (xkb_current_group == state->group)
                 return;
             xkb_current_group = state->group;
-            DLOG("Mode_switch %s\n", (xkb_current_group == XCB_XKB_GROUP_1 ? "disabled" : "enabled"));
             ungrab_all_keys(conn);
-            translate_keysyms();
-            grab_all_keys(conn, (xkb_current_group == XCB_XKB_GROUP_2));
+            grab_all_keys(conn);
         }
 
         return;
index 76c1da899ce7610489a28eefe98446010eb3cd1c..8472106ec5328f18fa82843e9927d25801a54618 100644 (file)
--- a/src/i3.mk
+++ b/src/i3.mk
@@ -5,8 +5,8 @@ CLEAN_TARGETS += clean-i3
 i3_SOURCES           := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
 i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
 i3_HEADERS           := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
-i3_CFLAGS             = $(XKB_COMMON_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
-i3_LIBS               = $(XKB_COMMON_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
+i3_CFLAGS             = $(XKB_COMMON_CFLAGS) $(XKB_COMMON_X11_CFLAGS) $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(XCB_WM_CFLAGS) $(XCURSOR_CFLAGS) $(PANGO_CFLAGS) $(YAJL_CFLAGS) $(LIBEV_CFLAGS) $(PCRE_CFLAGS) $(LIBSN_CFLAGS)
+i3_LIBS               = $(XKB_COMMON_LIBS) $(XKB_COMMON_X11_LIBS) $(XCB_LIBS) $(XCB_XKB_LIBS) $(XCB_KBD_LIBS) $(XCB_WM_LIBS) $(XCURSOR_LIBS) $(PANGO_LIBS) $(YAJL_LIBS) $(LIBEV_LIBS) $(PCRE_LIBS) $(LIBSN_LIBS) -lm -lpthread
 
 # When using clang, we use pre-compiled headers to speed up the build. With
 # gcc, this actually makes the build slower.
index 021bdd7bf1b2f57e5c989a807866d43232c1d43c..ad7ef1cb14d8dadede9e40927b8924eb8adba53b 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -121,56 +121,92 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
     y(map_close);
 }
 
-static void dump_binding(yajl_gen gen, Binding *bind) {
-    y(map_open);
-    ystr("input_code");
-    y(integer, bind->keycode);
-
-    ystr("input_type");
-    ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
-
-    ystr("symbol");
-    if (bind->symbol == NULL)
-        y(null);
-    else
-        ystr(bind->symbol);
-
-    ystr("command");
-    ystr(bind->command);
-
-    ystr("mods");
+static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
     y(array_open);
-    for (int i = 0; i < 8; i++) {
-        if (bind->mods & (1 << i)) {
+    for (int i = 0; i < 20; i++) {
+        if (bind->event_state_mask & (1 << i)) {
             switch (1 << i) {
-                case XCB_MOD_MASK_SHIFT:
+                case XCB_KEY_BUT_MASK_SHIFT:
                     ystr("shift");
                     break;
-                case XCB_MOD_MASK_LOCK:
+                case XCB_KEY_BUT_MASK_LOCK:
                     ystr("lock");
                     break;
-                case XCB_MOD_MASK_CONTROL:
+                case XCB_KEY_BUT_MASK_CONTROL:
                     ystr("ctrl");
                     break;
-                case XCB_MOD_MASK_1:
+                case XCB_KEY_BUT_MASK_MOD_1:
                     ystr("Mod1");
                     break;
-                case XCB_MOD_MASK_2:
+                case XCB_KEY_BUT_MASK_MOD_2:
                     ystr("Mod2");
                     break;
-                case XCB_MOD_MASK_3:
+                case XCB_KEY_BUT_MASK_MOD_3:
                     ystr("Mod3");
                     break;
-                case XCB_MOD_MASK_4:
+                case XCB_KEY_BUT_MASK_MOD_4:
                     ystr("Mod4");
                     break;
-                case XCB_MOD_MASK_5:
+                case XCB_KEY_BUT_MASK_MOD_5:
                     ystr("Mod5");
                     break;
+                case XCB_KEY_BUT_MASK_BUTTON_1:
+                    ystr("Button1");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_2:
+                    ystr("Button2");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_3:
+                    ystr("Button3");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_4:
+                    ystr("Button4");
+                    break;
+                case XCB_KEY_BUT_MASK_BUTTON_5:
+                    ystr("Button5");
+                    break;
+                case (I3_XKB_GROUP_MASK_1 << 16):
+                    ystr("Group1");
+                    break;
+                case (I3_XKB_GROUP_MASK_2 << 16):
+                    ystr("Group2");
+                    break;
+                case (I3_XKB_GROUP_MASK_3 << 16):
+                    ystr("Group3");
+                    break;
+                case (I3_XKB_GROUP_MASK_4 << 16):
+                    ystr("Group4");
+                    break;
             }
         }
     }
     y(array_close);
+}
+
+static void dump_binding(yajl_gen gen, Binding *bind) {
+    y(map_open);
+    ystr("input_code");
+    y(integer, bind->keycode);
+
+    ystr("input_type");
+    ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
+
+    ystr("symbol");
+    if (bind->symbol == NULL)
+        y(null);
+    else
+        ystr(bind->symbol);
+
+    ystr("command");
+    ystr(bind->command);
+
+    // This key is only provided for compatibility, new programs should use
+    // event_state_mask instead.
+    ystr("mods");
+    dump_event_state_mask(gen, bind);
+
+    ystr("event_state_mask");
+    dump_event_state_mask(gen, bind);
 
     y(map_close);
 }
index 88d09a0ce510429db8a3713ed9c0b28954371821..aa6d815066beaf423142edb1a456664fbde12e3f 100644 (file)
  *
  */
 void handle_key_press(xcb_key_press_event_t *event) {
-    bool key_release = (event->response_type == XCB_KEY_RELEASE);
+    const bool key_release = (event->response_type == XCB_KEY_RELEASE);
 
     last_timestamp = event->time;
 
-    DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
+    DLOG("%s %d, state raw = 0x%x\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);
 
     Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
 
index 5281d19c74a3bba7e48f3b14f9571005887d6c9f..b4ec3720ec34c1e5f7b52d452e8065f806ac649a 100644 (file)
@@ -556,6 +556,37 @@ int main(int argc, char *argv[]) {
                               0xff,
                               0xff,
                               NULL);
+
+        /* Setting both, XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE and
+         * XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED, will lead to the
+         * X server sending us the full XKB state in KeyPress and KeyRelease:
+         * https://sources.debian.net/src/xorg-server/2:1.17.2-1.1/xkb/xkbEvents.c/?hl=927#L927
+         */
+        xcb_xkb_per_client_flags_reply_t *pcf_reply;
+        /* The last three parameters are unset because they are only relevant
+         * when using a feature called “automatic reset of boolean controls”:
+         * http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Automatic_Reset_of_Boolean_Controls
+         * */
+        pcf_reply = xcb_xkb_per_client_flags_reply(
+            conn,
+            xcb_xkb_per_client_flags(
+                conn,
+                XCB_XKB_ID_USE_CORE_KBD,
+                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+                XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE | XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED,
+                0 /* uint32_t ctrlsToChange */,
+                0 /* uint32_t autoCtrls */,
+                0 /* uint32_t autoCtrlsValues */),
+            NULL);
+        if (pcf_reply == NULL ||
+            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE)) {
+            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_GRABS_USE_XKB_STATE\n");
+        }
+        if (pcf_reply == NULL ||
+            !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED)) {
+            ELOG("Could not set XCB_XKB_PER_CLIENT_FLAG_LOOKUP_STATE_WHEN_GRABBED\n");
+        }
+        free(pcf_reply);
         xkb_base = extreply->first_event;
     }
 
@@ -569,8 +600,11 @@ int main(int argc, char *argv[]) {
 
     xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms);
 
+    if (!load_keymap())
+        die("Could not load keymap\n");
+
     translate_keysyms();
-    grab_all_keys(conn, false);
+    grab_all_keys(conn);
 
     bool needs_tree_init = true;
     if (layout_path) {