diff --git a/include/config/keybind.h b/include/config/keybind.h index 352ee8fd..82dabf9f 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -6,11 +6,17 @@ #include #define MAX_KEYSYMS 32 +#define MAX_KEYCODES 16 + +struct server; struct keybind { uint32_t modifiers; xkb_keysym_t *keysyms; size_t keysyms_len; + xkb_keycode_t keycodes[MAX_KEYCODES]; + size_t keycodes_len; + int keycodes_layout; struct wl_list actions; /* struct action.link */ struct wl_list link; /* struct rcxml.keybinds */ }; @@ -30,4 +36,5 @@ uint32_t parse_modifier(const char *symname); bool keybind_the_same(struct keybind *a, struct keybind *b); +void keybind_update_keycodes(struct server *server); #endif /* LABWC_KEYBIND_H */ diff --git a/src/config/keybind.c b/src/config/keybind.c index 9a0ac197..54a15e90 100644 --- a/src/config/keybind.c +++ b/src/config/keybind.c @@ -10,6 +10,7 @@ #include "common/mem.h" #include "config/keybind.h" #include "config/rcxml.h" +#include "labwc.h" uint32_t parse_modifier(const char *symname) @@ -42,6 +43,70 @@ keybind_the_same(struct keybind *a, struct keybind *b) return true; } +static void +update_keycodes_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) +{ + struct keybind *keybind; + const xkb_keysym_t *syms; + xkb_layout_index_t layout = *(xkb_layout_index_t *)data; + int nr_syms = xkb_keymap_key_get_syms_by_level(keymap, key, layout, 0, &syms); + if (!nr_syms) { + return; + } + wl_list_for_each(keybind, &rc.keybinds, link) { + if (keybind->keycodes_layout >= 0 + && (xkb_layout_index_t)keybind->keycodes_layout != layout) { + /* Prevent storing keycodes from multiple layouts */ + continue; + } + for (int i = 0; i < nr_syms; i++) { + xkb_keysym_t sym = syms[i]; + for (size_t j = 0; j < keybind->keysyms_len; j++) { + if (sym != keybind->keysyms[j]) { + continue; + } + /* Found keycode for sym */ + if (keybind->keycodes_len == MAX_KEYCODES) { + wlr_log(WLR_ERROR, + "Already stored %lu keycodes for keybind", + keybind->keycodes_len); + break; + } + bool keycode_exists = false; + for (size_t k = 0; k < keybind->keycodes_len; k++) { + if (keybind->keycodes[k] == key) { + keycode_exists = true; + break; + } + } + if (keycode_exists) { + continue; + } + keybind->keycodes[keybind->keycodes_len++] = key; + keybind->keycodes_layout = layout; + } + } + } +} + +void +keybind_update_keycodes(struct server *server) +{ + struct xkb_state *state = server->seat.keyboard_group->keyboard.xkb_state; + struct xkb_keymap *keymap = xkb_state_get_keymap(state); + + struct keybind *keybind; + wl_list_for_each(keybind, &rc.keybinds, link) { + keybind->keycodes_len = 0; + keybind->keycodes_layout = -1; + } + xkb_layout_index_t layouts = xkb_keymap_num_layouts(keymap); + for (xkb_layout_index_t i = 0; i < layouts; i++) { + wlr_log(WLR_DEBUG, "Found layout %s", xkb_keymap_layout_get_name(keymap, i)); + xkb_keymap_key_for_each(keymap, update_keycodes_iter, &i); + } +} + struct keybind * keybind_create(const char *keybind) { diff --git a/src/keyboard.c b/src/keyboard.c index 7a3190cc..01756516 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -86,7 +86,7 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data) } static bool -handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym) +handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym, xkb_keycode_t code) { struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { @@ -98,11 +98,23 @@ handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym) && !actions_contain_toggle_keybinds(&keybind->actions)) { continue; } - for (size_t i = 0; i < keybind->keysyms_len; i++) { - if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { - key_state_store_pressed_keys_as_bound(); - actions_run(NULL, server, &keybind->actions, 0); - return true; + if (sym == XKB_KEY_NoSymbol) { + /* Use keycodes */ + for (size_t i = 0; i < keybind->keycodes_len; i++) { + if (keybind->keycodes[i] == code) { + key_state_store_pressed_keys_as_bound(); + actions_run(NULL, server, &keybind->actions, 0); + return true; + } + } + } else { + /* Use syms */ + for (size_t i = 0; i < keybind->keysyms_len; i++) { + if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { + key_state_store_pressed_keys_as_bound(); + actions_run(NULL, server, &keybind->actions, 0); + return true; + } } } } @@ -301,16 +313,58 @@ handle_compositor_keybindings(struct keyboard *keyboard, goto out; } - /* Handle compositor key bindings */ + /* + * Handle compositor keybinds + * + * When matching against keybinds, we process the input keys in the + * following order of precedence: + * a. Keycodes (of physical keys) (not if keybind is layoutDependent) + * b. Translated keysyms (taking into account modifiers, so if Shift+1 + * were pressed on a us keyboard, the keysym would be '!') + * c. Raw keysyms (ignoring modifiers such as shift, so in the above + * example the keysym would just be '1') + * + * The reasons for this approach are: + * 1. To make keybinds keyboard-layout agnostic (by checking keycodes + * before keysyms). This means that in a multi-layout situation, + * keybinds work regardless of which layout is active at the time + * of the key-press. + * 2. To support keybinds relating to keysyms that are only available + * in a particular layout, for example å, ä and ö. + * 3. To support keybinds that are only valid with a modifier, for + * example the numpad keys with NumLock enabled: KP_x. These would + * only be matched by the translated keysyms. + * 4. To support keybinds such as `S-1` (by checking raw keysyms). + * + * Reason 4 will also be satisfied by matching the keycodes. However, + * when a keybind is configured to be layoutDependent we still need + * the raw keysym fallback. + */ + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { - for (int i = 0; i < translated.nr_syms; i++) { - handled |= handle_keybinding(server, modifiers, translated.syms[i]); - } + /* First try keycodes */ + handled |= handle_keybinding(server, modifiers, XKB_KEY_NoSymbol, keycode); if (handled) { + wlr_log(WLR_DEBUG, "keycodes matched"); goto out; } + + /* Then fall back to keysyms */ + for (int i = 0; i < translated.nr_syms; i++) { + handled |= handle_keybinding(server, modifiers, + translated.syms[i], keycode); + } + if (handled) { + wlr_log(WLR_DEBUG, "translated keysyms matched"); + goto out; + } + + /* And finally test for keysyms without modifier */ for (int i = 0; i < raw.nr_syms; i++) { - handled |= handle_keybinding(server, modifiers, raw.syms[i]); + handled |= handle_keybinding(server, modifiers, raw.syms[i], keycode); + } + if (handled) { + wlr_log(WLR_DEBUG, "raw keysyms matched"); } } @@ -413,6 +467,8 @@ keyboard_init(struct seat *seat) } xkb_context_unref(context); wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); + + keybind_update_keycodes(seat->server); } void diff --git a/src/server.c b/src/server.c index 227b641f..6457a7b5 100644 --- a/src/server.c +++ b/src/server.c @@ -57,6 +57,7 @@ reload_config_and_theme(void) regions_reconfigure(g_server); resize_indicator_reconfigure(g_server); kde_server_decoration_update_default(); + keybind_update_keycodes(g_server); } static int