From 58910856c8a86095883625009a089cef464edc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 29 Mar 2025 10:34:40 +0100 Subject: [PATCH 1/4] input: xkb: ignore virtual modifiers Some compositors (mutter/GNOME is one) adds _virtual_ modifiers to the set of active modifiers when e.g. Alt, Meta, Super or Hyper is pressed. For example, pressing Alt+b would result in *both* the Alt *and* the Mod1 modifier being set. Since foot makes close to zero assumptions on how the modifiers should be interpreted, this causes various breakages. For example, a foot shortcut defined as Mod1+b will not match, since the Alt modifiers is also set. This has forced users to redefine/override some of the default key bindings to include the additional modifiers. It also causes issues with the kitty keyboard protocol, for some key combinations. Mainly whether or not to use unshifted key or not, resulting in incorrect escape sequences. Since all the "real" modifiers are always set as well, we can safely ignore the virtual modifiers. Closes #2009 --- CHANGELOG.md | 12 ++++++++++++ input.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wayland.h | 2 ++ 3 files changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466947cd..92b7524f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,8 +68,20 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). +* XKB: virtual modifiers are now ignored. This works around various + issues seen when running foot under mutter (GNOME) ([#2009][2009]): + - Some key combinations generating the wrong escape sequence in the + kitty keyboard protocol. + - some of foot's default shortcuts not working (mainly those using + `Mod1`) out of the box. + - **Note: if you have custom key bindings in `foot.ini` that + includes one or more of the `Alt`, `Meta`, `Super`, `Hyper`, + `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, + you need to update them; i.e. remove the virtual modifier(s), + leaving only the real modifiers (`Mod1`, `Mod2` etc).** [2006]: https://codeberg.org/dnkl/foot/issues/2006 +[2009]: https://codeberg.org/dnkl/foot/issues/2009 ### Deprecated diff --git a/input.c b/input.c index cbe8e1c7..abaac8eb 100644 --- a/input.c +++ b/input.c @@ -620,6 +620,54 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, if (seat->kbd.mod_num != XKB_MOD_INVALID) seat->kbd.kitty_significant |= 1 << seat->kbd.mod_num; + + /* + * Create a mask of all "virtual" modifiers. Some compositors + * add these *in addition* to the "real" modifiers (Mod1, + * Mod2, etc). + * + * Since our modifier logic (both for internal shortcut + * processing, and e.g. the kitty keyboard protocol) makes + * very few assumptions on available modifiers, which keys map + * to which modifier etc, the presence of virtual modifiers + * causes various things to break. + * + * For example, if a foot shortcut is Mod1+b (i.e. Alt+b), it + * won't match if the compositor _also_ sets the Alt modifier + * (the corresponding shortcut in foot would be Alt+Mod1+b). + * + * See https://codeberg.org/dnkl/foot/issues/2009 + * + * Mutter (GNOME) is known to set the virtual modifiers in + * addtiion to the real modifiers. + * + * As far as I know, there's no compositor that _only_ sets + * virtual modifiers (don't think that's even legal...?) + */ + { + xkb_mod_index_t alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_ALT); + xkb_mod_index_t meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_META); + xkb_mod_index_t super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SUPER); + xkb_mod_index_t hyper = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_HYPER); + xkb_mod_index_t num_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_NUM); + xkb_mod_index_t scroll_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SCROLL); + xkb_mod_index_t level_three = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL3); + xkb_mod_index_t level_five = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL5); + + xkb_mod_index_t ignore = 0; + + if (alt != XKB_MOD_INVALID) ignore |= 1 << alt; + if (meta != XKB_MOD_INVALID) ignore |= 1 << meta; + if (super != XKB_MOD_INVALID) ignore |= 1 << super; + if (hyper != XKB_MOD_INVALID) ignore |= 1 << hyper; + if (num_lock != XKB_MOD_INVALID) ignore |= 1 << num_lock; + if (scroll_lock != XKB_MOD_INVALID) ignore |= 1 << scroll_lock; + if (level_three != XKB_MOD_INVALID) ignore |= 1 << level_three; + if (level_five != XKB_MOD_INVALID) ignore |= 1 << level_five; + + seat->kbd.virtual_modifiers = ignore; + } + seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP"); seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); } @@ -1759,6 +1807,10 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, { struct seat *seat = data; + mods_depressed &= ~seat->kbd.virtual_modifiers; + mods_latched &= ~seat->kbd.virtual_modifiers; + mods_locked &= ~seat->kbd.virtual_modifiers; + #if defined(_DEBUG) char depressed[256]; char latched[256]; diff --git a/wayland.h b/wayland.h index 6215d708..37dd7860 100644 --- a/wayland.h +++ b/wayland.h @@ -136,6 +136,8 @@ struct seat { xkb_mod_mask_t legacy_significant; /* Significant modifiers for the legacy keyboard protocol */ xkb_mod_mask_t kitty_significant; /* Significant modifiers for the kitty keyboard protocol */ + xkb_mod_mask_t virtual_modifiers; /* Set of modifiers to completely ignore */ + xkb_keycode_t key_arrow_up; xkb_keycode_t key_arrow_down; From dc99cf735888d0aba31194b8c59e92eed7e1bc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 10:11:30 +0200 Subject: [PATCH 2/4] key-binding: recognize virtual modifiers, and translate to the corresponding real modifier. --- CHANGELOG.md | 9 ++++-- key-binding.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b7524f..7dcc8372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,8 +68,9 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). -* XKB: virtual modifiers are now ignored. This works around various - issues seen when running foot under mutter (GNOME) ([#2009][2009]): +* XKB: virtual modifiers in keyboard events from the compositor are + now ignored. This works around various issues seen when running foot + under mutter (GNOME) ([#2009][2009]): - Some key combinations generating the wrong escape sequence in the kitty keyboard protocol. - some of foot's default shortcuts not working (mainly those using @@ -79,6 +80,10 @@ `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, you need to update them; i.e. remove the virtual modifier(s), leaving only the real modifiers (`Mod1`, `Mod2` etc).** +* Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of + `Mod4` etc) in key bindings are now recognized as being virtual, and + are automatically mapped to the corresponding real modifier. This + means you can use e.g. `Alt+b` instead of `Mod1+b`. [2006]: https://codeberg.org/dnkl/foot/issues/2006 [2009]: https://codeberg.org/dnkl/foot/issues/2009 diff --git a/key-binding.c b/key-binding.c index 1c131e72..e5b7ac81 100644 --- a/key-binding.c +++ b/key-binding.c @@ -13,12 +13,21 @@ #include "wayland.h" #include "xmalloc.h" +struct vmod_map { + const char *name; + xkb_mod_mask_t virtual_mask; + xkb_mod_mask_t real_mask; +}; + struct key_set { struct key_binding_set public; const struct config *conf; const struct seat *seat; size_t conf_ref_count; + + /* Virtual to real modifier mappings */ + struct vmod_map vmods[8]; }; typedef tll(struct key_set) bind_set_list_t; @@ -44,6 +53,50 @@ key_binding_manager_destroy(struct key_binding_manager *mgr) free(mgr); } +static void +initialize_vmod_mappings(struct key_set *set) +{ + if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL) + return; + + set->vmods[0].name = XKB_VMOD_NAME_ALT; + set->vmods[1].name = XKB_VMOD_NAME_HYPER; + set->vmods[2].name = XKB_VMOD_NAME_LEVEL3; + set->vmods[3].name = XKB_VMOD_NAME_LEVEL5; + set->vmods[4].name = XKB_VMOD_NAME_META; + set->vmods[5].name = XKB_VMOD_NAME_NUM; + set->vmods[6].name = XKB_VMOD_NAME_SCROLL; + set->vmods[7].name = XKB_VMOD_NAME_SUPER; + + struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap); + xassert(scratch_state != NULL); + + for (size_t i = 0; i < ALEN(set->vmods); i++) { + xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index( + set->seat->kbd.xkb_keymap, set->vmods[i].name); + + if (virt_idx != XKB_MOD_INVALID) { + xkb_mod_mask_t vmask = 1 << virt_idx; + xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0); + set->vmods[i].real_mask = xkb_state_serialize_mods( + scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask; + set->vmods[i].virtual_mask = vmask; + + LOG_DBG("%s: 0x%04x -> 0x%04x", + set->vmods[i].name, + set->vmods[i].virtual_mask, + set->vmods[i].real_mask); + } else { + set->vmods[i].virtual_mask = 0; + set->vmods[i].real_mask = 0; + + LOG_DBG("%s: virtual modifier not available", set->vmods[i].name); + } + } + + xkb_state_unref(scratch_state); +} + void key_binding_new_for_seat(struct key_binding_manager *mgr, const struct seat *seat) @@ -67,6 +120,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr, }; tll_push_back(mgr->binding_sets, set); + initialize_vmod_mappings(&tll_back(mgr->binding_sets)); LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1", (void *)&tll_back(mgr->binding_sets), @@ -107,6 +161,7 @@ key_binding_new_for_conf(struct key_binding_manager *mgr, }; tll_push_back(mgr->binding_sets, set); + initialize_vmod_mappings(&tll_back(mgr->binding_sets)); load_keymap(&tll_back(mgr->binding_sets)); @@ -405,18 +460,35 @@ sort_binding_list(key_binding_list_t *list) } static xkb_mod_mask_t -mods_to_mask(const struct seat *seat, const config_modifier_list_t *mods) +mods_to_mask(const struct seat *seat, + const struct vmod_map *vmods, size_t vmod_count, + const config_modifier_list_t *mods) { xkb_mod_mask_t mask = 0; tll_foreach(*mods, it) { - xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item); + const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item); if (idx == XKB_MOD_INVALID) { LOG_ERR("%s: invalid modifier name", it->item); continue; } - mask |= 1 << idx; + xkb_mod_mask_t mod = 1 << idx; + + /* Check if this is a virtual modifier, and if so, use the + real modifier it maps to instead */ + for (size_t i = 0; i < vmod_count; i++) { + if (vmods[i].virtual_mask == mod) { + mask |= vmods[i].real_mask; + mod = 0; + + LOG_DBG("%s: virtual modifier, mapped to 0x%04x", + it->item, vmods[i].real_mask); + break; + } + } + + mask |= mod; } return mask; @@ -429,7 +501,8 @@ convert_key_binding(struct key_set *set, { const struct seat *seat = set->seat; - xkb_mod_mask_t mods = mods_to_mask(seat, &conf_binding->modifiers); + xkb_mod_mask_t mods = mods_to_mask( + seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers); xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods); struct key_binding binding = { @@ -487,7 +560,7 @@ convert_mouse_binding(struct key_set *set, .type = MOUSE_BINDING, .action = conf_binding->action, .aux = &conf_binding->aux, - .mods = mods_to_mask(set->seat, &conf_binding->modifiers), + .mods = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers), .m = { .button = conf_binding->m.button, .count = conf_binding->m.count, @@ -528,7 +601,8 @@ load_keymap(struct key_set *set) convert_mouse_bindings(set); set->public.selection_overrides = mods_to_mask( - set->seat, &set->conf->mouse.selection_override_modifiers); + set->seat, set->vmods, ALEN(set->vmods), + &set->conf->mouse.selection_override_modifiers); } void @@ -538,8 +612,10 @@ key_binding_load_keymap(struct key_binding_manager *mgr, tll_foreach(mgr->binding_sets, it) { struct key_set *set = &it->item; - if (set->seat == seat) + if (set->seat == seat) { + initialize_vmod_mappings(set); load_keymap(set); + } } } From a43614f098236adf3b876cf8d6fbf1e72a1297ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 10:13:19 +0200 Subject: [PATCH 3/4] doc: foot.ini: mention virtual modifiers are allowed --- doc/foot.ini.5.scd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c663223d..043600d2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1160,7 +1160,8 @@ Note that if *Shift* is one of the modifiers, the _key_ *must not* be in upper case. For example, *Control+Shift+V* will never trigger, but *Control+Shift+v* will. -Note that *Alt* is usually called *Mod1*. +The default key bindings all use "real" modifiers (*Mod1*, *Mod4* +etc), but "virtual" modifiers (*Alt*, *Super* etc) are allowed. *xkbcli interactive-wayland* can be useful for finding keysym names. From 0d8c7db962f43255ed0c2e98d9a36253bdddbd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 11:08:22 +0200 Subject: [PATCH 4/4] changelog: reword, and remove section that no longer applies --- CHANGELOG.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dcc8372..bc297e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,18 +68,13 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). -* XKB: virtual modifiers in keyboard events from the compositor are - now ignored. This works around various issues seen when running foot +* Virtual modifiers in keyboard events from the compositor are now + supported. This works around various issues seen when running foot under mutter (GNOME) ([#2009][2009]): - Some key combinations generating the wrong escape sequence in the kitty keyboard protocol. - some of foot's default shortcuts not working (mainly those using `Mod1`) out of the box. - - **Note: if you have custom key bindings in `foot.ini` that - includes one or more of the `Alt`, `Meta`, `Super`, `Hyper`, - `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, - you need to update them; i.e. remove the virtual modifier(s), - leaving only the real modifiers (`Mod1`, `Mod2` etc).** * Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of `Mod4` etc) in key bindings are now recognized as being virtual, and are automatically mapped to the corresponding real modifier. This