X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fbindings.c;h=b9d994ea3ce566db2d96ed53bb35f79bb2b94254;hb=b255d140be9c5c46be89399e8d393a8311f56ddd;hp=cd91a3989c452c4d45189f474c39ea8994507b53;hpb=89d3f93bec03547cf5b07b1e120aa07374b2dd64;p=i3%2Fi3 diff --git a/src/bindings.c b/src/bindings.c index cd91a398..b9d994ea 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -2,13 +2,17 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2014 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * bindings.c: Functions for configuring, finding and, running bindings. */ #include "all.h" #include +#include + +static struct xkb_context *xkb_context; +static struct xkb_keymap *xkb_keymap; pid_t command_error_nagbar_pid = -1; @@ -23,7 +27,7 @@ const char *DEFAULT_BINDING_MODE = "default"; * the list of modes. * */ -static struct Mode *mode_from_name(const char *name) { +static struct Mode *mode_from_name(const char *name, bool pango_markup) { struct Mode *mode; /* Try to find the mode in the list of modes and return it */ @@ -33,9 +37,10 @@ static struct Mode *mode_from_name(const char *name) { } /* If the mode was not found, create a new one */ - mode = scalloc(sizeof(struct Mode)); + mode = scalloc(1, sizeof(struct Mode)); mode->name = sstrdup(name); - mode->bindings = scalloc(sizeof(struct bindings_head)); + mode->pango_markup = pango_markup; + mode->bindings = scalloc(1, sizeof(struct bindings_head)); TAILQ_INIT(mode->bindings); SLIST_INSERT_HEAD(&modes, mode, modes); @@ -49,10 +54,13 @@ static struct Mode *mode_from_name(const char *name) { * */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, - const char *release, const char *command, const char *modename) { - Binding *new_binding = scalloc(sizeof(Binding)); + const char *release, const char *border, const char *whole_window, + const char *command, const char *modename, bool pango_markup) { + Binding *new_binding = scalloc(1, sizeof(Binding)); DLOG("bindtype %s, modifiers %s, input code %s, release %s\n", bindtype, modifiers, input_code, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); + new_binding->border = (border != NULL); + new_binding->whole_window = (whole_window != NULL); if (strcmp(bindtype, "bindsym") == 0) { new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0 ? B_MOUSE @@ -60,19 +68,31 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch new_binding->symbol = sstrdup(input_code); } else { - // TODO: strtol with proper error handling - new_binding->keycode = atoi(input_code); + char *endptr; + long keycode = strtol(input_code, &endptr, 10); + new_binding->keycode = keycode; new_binding->input_type = B_KEYBOARD; - if (new_binding->keycode == 0) { + if (keycode == LONG_MAX || keycode == LONG_MIN || keycode < 0 || *endptr != '\0' || endptr == input_code) { ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code); FREE(new_binding); return NULL; } } - new_binding->mods = modifiers_from_str(modifiers); new_binding->command = sstrdup(command); - - struct Mode *mode = mode_from_name(modename); + new_binding->event_state_mask = event_state_from_str(modifiers); + int group_bits_set = 0; + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_1) + group_bits_set++; + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2) + group_bits_set++; + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3) + group_bits_set++; + if ((new_binding->event_state_mask >> 16) & 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, pango_markup); TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings); return new_binding; @@ -82,18 +102,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); @@ -104,12 +129,10 @@ 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 || - (bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) || - (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0)) + if (bind->input_type != B_KEYBOARD) continue; /* The easy case: the user specified a keycode directly. */ @@ -118,18 +141,38 @@ 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]); } } +/* + * Release the button grabs on all managed windows and regrab them, + * reevaluating which buttons need to be grabbed. + * + */ +void regrab_all_buttons(xcb_connection_t *conn) { + bool grab_scrollwheel = bindings_should_grab_scrollwheel_buttons(); + xcb_grab_server(conn); + + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->window == NULL) + continue; + + xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, con->window->id, XCB_BUTTON_MASK_ANY); + xcb_grab_buttons(conn, con->window->id, grab_scrollwheel); + } + + xcb_ungrab_server(conn); +} + /* * Returns a pointer to the Binding with the specified modifiers and * 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) { @@ -143,13 +186,34 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_ } } + const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000); + const uint32_t modifiers_state = (state_filtered & 0x0000FFFF); TAILQ_FOREACH(bind, bindings, bindings) { - /* First compare the modifiers (unless this is a + const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000); + /* modifiers_mask is a special case: a value of 0 does not mean “match all”, + * but rather “match exactly when no modifiers are present”. */ + const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF); + const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask); + bool mods_match; + if (modifiers_mask == 0) { + /* Verify no modifiers are pressed. A bitwise AND would lead to + * false positives, see issue #2002. */ + mods_match = (modifiers_state == 0); + } else { + mods_match = ((modifiers_state & modifiers_mask) == modifiers_mask); + } + const bool state_matches = (groups_match && mods_match); + + DLOG("binding groups_match = %s, mods_match = %s, state_matches = %s\n", + (groups_match ? "yes" : "no"), + (mods_match ? "yes" : "no"), + (state_matches ? "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_matches && (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || !is_release)) continue; @@ -158,9 +222,10 @@ static Binding *get_binding(uint16_t modifiers, bool is_release, uint16_t input_ * need to look in the array of translated keycodes for the event’s * keycode */ if (input_type == B_KEYBOARD && bind->symbol != NULL) { + xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; if (memmem(bind->translated_to, bind->number_keycodes * sizeof(xcb_keycode_t), - &input_code, sizeof(xcb_keycode_t)) == NULL) + &input_keycode, sizeof(xcb_keycode_t)) == NULL) continue; } else { /* This case is easier: The user specified a keycode */ @@ -192,49 +257,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 == XkbGroup2Index) - 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 ru). */ - 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; } /* @@ -242,20 +347,27 @@ 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; + 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; + } + bool has_errors = false; + Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type == B_MOUSE) { - int button = atoi(bind->symbol + (sizeof("button") - 1)); + char *endptr; + long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); bind->keycode = button; - if (button < 1) + if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) ELOG("Could not translate string to button: \"%s\"\n", bind->symbol); continue; @@ -265,35 +377,85 @@ 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 ? 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; + + /* check for duplicate bindings */ + Binding *check; + TAILQ_FOREACH(check, bindings, bindings) { + if (check == bind) + continue; + if (check->symbol != NULL) + continue; + if (check->keycode != bind->translated_to[n] || + check->event_state_mask != bind->event_state_mask || + check->release != bind->release) + continue; + has_errors = true; + ELOG("Duplicate keybinding in config file:\n keysym = %s, keycode = %d, state_mask = 0x%x\n", bind->symbol, check->keycode, bind->event_state_mask); + } } + 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); + xkb_state_unref(dummy_state_no_shift); - DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, - bind->number_keycodes); + if (has_errors) { + start_config_error_nagbar(current_configpath, true); } } @@ -313,10 +475,11 @@ 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); + sasprintf(&event_msg, "{\"change\":\"%s\", \"pango_markup\":%s}", + mode->name, (mode->pango_markup ? "true" : "false")); ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg); FREE(event_msg); @@ -327,6 +490,70 @@ void switch_mode(const char *new_mode) { ELOG("ERROR: Mode not found\n"); } +static int reorder_binding_cmp(const void *a, const void *b) { + Binding *first = *((Binding **)a); + Binding *second = *((Binding **)b); + if (first->event_state_mask < second->event_state_mask) { + return 1; + } else if (first->event_state_mask == second->event_state_mask) { + return 0; + } else { + return -1; + } +} + +static void reorder_bindings_of_mode(struct Mode *mode) { + /* Copy the bindings into an array, so that we can use qsort(3). */ + int n = 0; + Binding *current; + TAILQ_FOREACH(current, mode->bindings, bindings) { + n++; + } + Binding **tmp = scalloc(n, sizeof(Binding *)); + n = 0; + TAILQ_FOREACH(current, mode->bindings, bindings) { + tmp[n++] = current; + } + + qsort(tmp, n, sizeof(Binding *), reorder_binding_cmp); + + struct bindings_head *reordered = scalloc(1, sizeof(struct bindings_head)); + TAILQ_INIT(reordered); + for (int i = 0; i < n; i++) { + current = tmp[i]; + TAILQ_REMOVE(mode->bindings, current, bindings); + TAILQ_INSERT_TAIL(reordered, current, bindings); + } + free(tmp); + 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 @@ -349,8 +576,6 @@ void check_for_duplicate_bindings(struct context *context) { /* Check if one is using keysym while the other is using bindsym. * If so, skip. */ - /* XXX: It should be checked at a later place (when translating the - * keysym to keycodes) if there are any duplicates */ if ((bind->symbol == NULL && current->symbol != NULL) || (bind->symbol != NULL && current->symbol == NULL)) continue; @@ -364,22 +589,53 @@ 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); } } } } +/* + * Creates a dynamically allocated copy of bind. + */ +static Binding *binding_copy(Binding *bind) { + Binding *ret = smalloc(sizeof(Binding)); + *ret = *bind; + if (bind->symbol != NULL) + ret->symbol = sstrdup(bind->symbol); + if (bind->command != NULL) + ret->command = sstrdup(bind->command); + if (bind->translated_to != NULL) { + ret->translated_to = smalloc(sizeof(xcb_keycode_t) * bind->number_keycodes); + memcpy(ret->translated_to, bind->translated_to, sizeof(xcb_keycode_t) * bind->number_keycodes); + } + return ret; +} + +/* + * Frees the binding. If bind is null, it simply returns. + */ +void binding_free(Binding *bind) { + if (bind == NULL) { + return; + } + + FREE(bind->symbol); + FREE(bind->translated_to); + FREE(bind->command); + FREE(bind); +} + /* * Runs the given binding and handles parse errors. If con is passed, it will * execute the command binding with that container selected by criteria. @@ -390,14 +646,15 @@ void check_for_duplicate_bindings(struct context *context) { CommandResult *run_binding(Binding *bind, Con *con) { char *command; - /* We need to copy the command since “reload” may be part of the command, - * and then the memory that bind->command points to may not contain the + /* We need to copy the binding and command since “reload” may be part of + * the command, and then the memory that bind points to may not contain the * same data anymore. */ if (con == NULL) command = sstrdup(bind->command); else - sasprintf(&command, "[con_id=\"%d\"] %s", con, bind->command); + sasprintf(&command, "[con_id=\"%p\"] %s", con, bind->command); + Binding *bind_cp = binding_copy(bind); CommandResult *result = parse_command(command, NULL); free(command); @@ -423,7 +680,162 @@ CommandResult *run_binding(Binding *bind, Con *con) { free(pageraction); } - /* TODO: emit event for running a binding */ + ipc_send_binding_event("run", bind_cp); + binding_free(bind_cp); return result; } + +static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { + xcb_intern_atom_reply_t *atom_reply; + size_t content_max_words = 256; + + xcb_window_t root = root_screen->root; + + atom_reply = xcb_intern_atom_reply( + conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL); + if (atom_reply == NULL) + return -1; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { + /* We received an incomplete value. Ask again but with a properly + * adjusted size. */ + content_max_words += ceil(prop_reply->bytes_after / 4.0); + /* Repeat the request, with adjusted size */ + free(prop_reply); + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + } + if (xcb_get_property_value_length(prop_reply) == 0) { + free(atom_reply); + free(prop_reply); + return -1; + } + + const char *walk = (const char *)xcb_get_property_value(prop_reply); + int remaining = xcb_get_property_value_length(prop_reply); + for (int i = 0; i < 5 && remaining > 0; i++) { + const int len = strnlen(walk, remaining); + remaining -= len; + switch (i) { + case 0: + sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk); + break; + case 1: + sasprintf((char **)&(xkb_names->model), "%.*s", len, walk); + break; + case 2: + sasprintf((char **)&(xkb_names->layout), "%.*s", len, walk); + break; + case 3: + sasprintf((char **)&(xkb_names->variant), "%.*s", len, walk); + break; + case 4: + sasprintf((char **)&(xkb_names->options), "%.*s", len, walk); + break; + } + DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); + walk += (len + 1); + } + + free(atom_reply); + free(prop_reply); + return 0; +} + +/* + * 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 = NULL; + int32_t device_id; + if (xkb_supported && (device_id = xkb_x11_get_core_keyboard_device_id(conn)) > -1) { + 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; + } + } else { + /* Likely there is no XKB support on this server, possibly because it + * is a VNC server. */ + LOG("No XKB / core keyboard device? Assembling keymap from local RMLVO.\n"); + struct xkb_rule_names names = { + .rules = NULL, + .model = NULL, + .layout = NULL, + .variant = NULL, + .options = NULL}; + if (fill_rmlvo_from_root(&names) == -1) { + ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n"); + if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) { + ELOG("xkb_keymap_new_from_names(NULL) failed\n"); + return false; + } + } + new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0); + free((char *)names.rules); + free((char *)names.model); + free((char *)names.layout); + free((char *)names.variant); + free((char *)names.options); + if (new_keymap == NULL) { + ELOG("xkb_keymap_new_from_names(RMLVO) failed\n"); + return false; + } + } + xkb_keymap_unref(xkb_keymap); + xkb_keymap = new_keymap; + + return true; +} + +/* + * Returns true if the current config has any binding to a scroll wheel button + * (4 or 5) which is a whole-window binding. + * We need this to figure out whether we should grab all buttons or just 1-3 + * when managing a window. See #2049. + * + */ +bool bindings_should_grab_scrollwheel_buttons(void) { + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + /* We are only interested in whole window mouse bindings. */ + if (bind->input_type != B_MOUSE || !bind->whole_window) + continue; + + char *endptr; + long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); + if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) { + ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n"); + continue; + } + + /* If the binding is for either scrollwheel button, we need to grab everything. */ + if (button == 4 || button == 5) { + return true; + } + } + + return false; +}