From 6b80751010af2f6b841fe78e1a12349d8c15173f Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Tue, 5 Sep 2023 15:18:41 +0200 Subject: [PATCH 1/2] keybinds: prefer keycodes over keysyms This allows keyboard layout agnostic keybinds in a multi layout configuration. Fixes: #1069 --- include/config/keybind.h | 7 ++++ src/config/keybind.c | 65 +++++++++++++++++++++++++++++++++ src/keyboard.c | 78 ++++++++++++++++++++++++++++++++++------ src/server.c | 1 + 4 files changed, 140 insertions(+), 11 deletions(-) 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 From c1c624daf0511f52727613ac890c959a66d1c58f Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:15:37 +0200 Subject: [PATCH 2/2] keybinds: add optional layoutDependent argument This allows to define keybinds as layout dependent. E.g. keybinds only trigger if the configured key exists in the currently active keyboard layout. The keybind will also only trigger on the physical key that is mapped to the configured key in the active layout. By default the new argument is false which means all keybinds by default are layout agnostic. This optional argument can be used to restore the earlier default behavior of having keys layout dependent. --- docs/labwc-config.5.scd | 12 ++++++++++-- include/config/keybind.h | 1 + src/config/keybind.c | 3 +++ src/config/rcxml.c | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 8f37ea89..a68ab7e0 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -268,14 +268,22 @@ Therefore, where multiple objects of the same kind are required (for example ## KEYBOARD -** - Define a key binding in the format *modifier-key*, where supported +** + Define a *key* binding in the format *modifier-key*, where supported modifiers include S (shift); C (control); A (alt); W (super). Unlike Openbox, multiple space-separated key combinations and key-chains are not supported. The application "wev" (wayland event viewer) is packaged in a lot of distributions and can be used to view all available keynames. + *layoutDependent* [yes|no] + Make this specific keybind depend on the currently active keyboard + layout. If enabled, a keybind using a key which does not exist in + the currently active layout will not be executed. The physical key + to trigger a keybind may also change along with the active layout. + If set to "no" (or is absent) the keybind will be layout agnostic. + Default is no. + ** Keybind action. See labwc-action(5) diff --git a/include/config/keybind.h b/include/config/keybind.h index 82dabf9f..c61392aa 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -14,6 +14,7 @@ struct keybind { uint32_t modifiers; xkb_keysym_t *keysyms; size_t keysyms_len; + bool use_syms_only; xkb_keycode_t keycodes[MAX_KEYCODES]; size_t keycodes_len; int keycodes_layout; diff --git a/src/config/keybind.c b/src/config/keybind.c index 54a15e90..f741b2bd 100644 --- a/src/config/keybind.c +++ b/src/config/keybind.c @@ -59,6 +59,9 @@ update_keycodes_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) /* Prevent storing keycodes from multiple layouts */ continue; } + if (keybind->use_syms_only) { + 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++) { diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 5b079cae..c8da660b 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -267,6 +267,8 @@ fill_keybind(char *nodename, char *content) } else if (!current_keybind) { wlr_log(WLR_ERROR, "expect element first. " "nodename: '%s' content: '%s'", nodename, content); + } else if (!strcasecmp(nodename, "layoutDependent")) { + set_bool(content, ¤t_keybind->use_syms_only); } else if (!strcmp(nodename, "name.action")) { current_keybind_action = action_create(content); if (current_keybind_action) {