diff --git a/docs/ipc b/docs/ipc index 4462e772..5113d79b 100644 --- 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" ], diff --git a/docs/userguide b/docs/userguide index 9316a4d5..25f5a4ba 100644 --- a/docs/userguide +++ b/docs/userguide @@ -368,8 +368,8 @@ after the keys have been released. *Syntax*: ---------------------------------- -bindsym [--release] [+] command -bindcode [--release] [+] command +bindsym [--release] [+][+] command +bindcode [--release] [+][+] 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]] diff --git a/include/bindings.h b/include/bindings.h index 75a719e9..88b4a6cc 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -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); diff --git a/include/config_directives.h b/include/config_directives.h index fc34a85f..72646651 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -12,10 +12,10 @@ #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 diff --git a/include/data.h b/include/data.h index 66000e45..634cca67 100644 --- a/include/data.h +++ b/include/data.h @@ -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 diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 3767acf8..1191157c 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -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' -> '+' -> diff --git a/src/bindings.c b/src/bindings.c index adca5546..f6ed086b 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -9,6 +9,10 @@ #include "all.h" #include +#include + +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); + const 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); + const 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 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, 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); - - if (xkb_current_group == XCB_XKB_GROUP_2) - state_filtered |= BIND_MODE_SWITCH; - - DLOG("(checked mode_switch, state %d)\n", state_filtered); - - /* Find the binding */ - Binding *bind = get_binding(state_filtered, is_release, event_detail, input_type); - - /* 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); - } + /* 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); +} - return bind; +struct resolve { + /* The binding which we are resolving. */ + Binding *bind; + + /* |bind|’s keysym (translated to xkb_keysym_t), e.g. XKB_KEY_R. */ + xkb_keysym_t keysym; + + /* The xkb state built from the user-provided modifiers and group. */ + struct xkb_state *xkb_state; + + /* Like |xkb_state|, just without the shift modifier, if shift was specified. */ + struct xkb_state *xkb_state_no_shift; +}; + +/* + * 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; + } + 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) { + 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; + } + + 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; - 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); - - min_keycode = xcb_get_setup(conn)->min_keycode; - max_keycode = xcb_get_setup(conn)->max_keycode; - 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; +} diff --git a/src/click.c b/src/click.c index bd2dcb9f..92a1a1fa 100644 --- a/src/click.c +++ b/src/click.c @@ -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 */ diff --git a/src/config.c b/src/config.c index b0c6f1a4..0dc59365 100644 --- a/src/config.c +++ b/src/config.c @@ -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) { diff --git a/src/config_directives.c b/src/config_directives.c index 6032dc11..463b38e7 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -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) { diff --git a/src/config_parser.c b/src/config_parser.c index 6393efe9..04423483 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -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); diff --git a/src/handlers.c b/src/handlers.c index 714e183b..1daefbc9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -16,7 +16,6 @@ #include #include #include -#include #define SN_API_NOT_YET_FROZEN 1 #include @@ -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; diff --git a/src/i3.mk b/src/i3.mk index 76c1da89..8472106e 100644 --- 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. diff --git a/src/ipc.c b/src/ipc.c index 021bdd7b..ad7ef1cb 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -121,6 +121,68 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) { y(map_close); } +static void dump_event_state_mask(yajl_gen gen, Binding *bind) { + y(array_open); + for (int i = 0; i < 20; i++) { + if (bind->event_state_mask & (1 << i)) { + switch (1 << i) { + case XCB_KEY_BUT_MASK_SHIFT: + ystr("shift"); + break; + case XCB_KEY_BUT_MASK_LOCK: + ystr("lock"); + break; + case XCB_KEY_BUT_MASK_CONTROL: + ystr("ctrl"); + break; + case XCB_KEY_BUT_MASK_MOD_1: + ystr("Mod1"); + break; + case XCB_KEY_BUT_MASK_MOD_2: + ystr("Mod2"); + break; + case XCB_KEY_BUT_MASK_MOD_3: + ystr("Mod3"); + break; + case XCB_KEY_BUT_MASK_MOD_4: + ystr("Mod4"); + break; + 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"); @@ -138,39 +200,13 @@ static void dump_binding(yajl_gen gen, Binding *bind) { ystr("command"); ystr(bind->command); + // This key is only provided for compatibility, new programs should use + // event_state_mask instead. ystr("mods"); - y(array_open); - for (int i = 0; i < 8; i++) { - if (bind->mods & (1 << i)) { - switch (1 << i) { - case XCB_MOD_MASK_SHIFT: - ystr("shift"); - break; - case XCB_MOD_MASK_LOCK: - ystr("lock"); - break; - case XCB_MOD_MASK_CONTROL: - ystr("ctrl"); - break; - case XCB_MOD_MASK_1: - ystr("Mod1"); - break; - case XCB_MOD_MASK_2: - ystr("Mod2"); - break; - case XCB_MOD_MASK_3: - ystr("Mod3"); - break; - case XCB_MOD_MASK_4: - ystr("Mod4"); - break; - case XCB_MOD_MASK_5: - ystr("Mod5"); - break; - } - } - } - y(array_close); + dump_event_state_mask(gen, bind); + + ystr("event_state_mask"); + dump_event_state_mask(gen, bind); y(map_close); } diff --git a/src/key_press.c b/src/key_press.c index 88d09a0c..aa6d8150 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -18,11 +18,11 @@ * */ 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); diff --git a/src/main.c b/src/main.c index 5281d19c..b4ec3720 100644 --- a/src/main.c +++ b/src/main.c @@ -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) {