key-binding: recognize virtual modifiers, and translate to the corresponding real modifier.

This commit is contained in:
Daniel Eklöf 2025-03-31 10:11:30 +02:00
parent 58910856c8
commit dc99cf7358
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 90 additions and 9 deletions

View file

@ -68,8 +68,9 @@
colors has not configured) is now done by linear RGB interpolation, colors has not configured) is now done by linear RGB interpolation,
rather than converting to HSL and adjusting the luminance rather than converting to HSL and adjusting the luminance
([#2006][2006]). ([#2006][2006]).
* XKB: virtual modifiers are now ignored. This works around various * XKB: virtual modifiers in keyboard events from the compositor are
issues seen when running foot under mutter (GNOME) ([#2009][2009]): 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 - Some key combinations generating the wrong escape sequence in the
kitty keyboard protocol. kitty keyboard protocol.
- some of foot's default shortcuts not working (mainly those using - some of foot's default shortcuts not working (mainly those using
@ -79,6 +80,10 @@
`NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers,
you need to update them; i.e. remove the virtual modifier(s), you need to update them; i.e. remove the virtual modifier(s),
leaving only the real modifiers (`Mod1`, `Mod2` etc).** 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 [2006]: https://codeberg.org/dnkl/foot/issues/2006
[2009]: https://codeberg.org/dnkl/foot/issues/2009 [2009]: https://codeberg.org/dnkl/foot/issues/2009

View file

@ -13,12 +13,21 @@
#include "wayland.h" #include "wayland.h"
#include "xmalloc.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_set {
struct key_binding_set public; struct key_binding_set public;
const struct config *conf; const struct config *conf;
const struct seat *seat; const struct seat *seat;
size_t conf_ref_count; size_t conf_ref_count;
/* Virtual to real modifier mappings */
struct vmod_map vmods[8];
}; };
typedef tll(struct key_set) bind_set_list_t; typedef tll(struct key_set) bind_set_list_t;
@ -44,6 +53,50 @@ key_binding_manager_destroy(struct key_binding_manager *mgr)
free(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 void
key_binding_new_for_seat(struct key_binding_manager *mgr, key_binding_new_for_seat(struct key_binding_manager *mgr,
const struct seat *seat) 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); 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", LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1",
(void *)&tll_back(mgr->binding_sets), (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); tll_push_back(mgr->binding_sets, set);
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
load_keymap(&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 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; xkb_mod_mask_t mask = 0;
tll_foreach(*mods, it) { 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) { if (idx == XKB_MOD_INVALID) {
LOG_ERR("%s: invalid modifier name", it->item); LOG_ERR("%s: invalid modifier name", it->item);
continue; 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; return mask;
@ -429,7 +501,8 @@ convert_key_binding(struct key_set *set,
{ {
const struct seat *seat = set->seat; 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); xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods);
struct key_binding binding = { struct key_binding binding = {
@ -487,7 +560,7 @@ convert_mouse_binding(struct key_set *set,
.type = MOUSE_BINDING, .type = MOUSE_BINDING,
.action = conf_binding->action, .action = conf_binding->action,
.aux = &conf_binding->aux, .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 = { .m = {
.button = conf_binding->m.button, .button = conf_binding->m.button,
.count = conf_binding->m.count, .count = conf_binding->m.count,
@ -528,7 +601,8 @@ load_keymap(struct key_set *set)
convert_mouse_bindings(set); convert_mouse_bindings(set);
set->public.selection_overrides = mods_to_mask( 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 void
@ -538,8 +612,10 @@ key_binding_load_keymap(struct key_binding_manager *mgr,
tll_foreach(mgr->binding_sets, it) { tll_foreach(mgr->binding_sets, it) {
struct key_set *set = &it->item; struct key_set *set = &it->item;
if (set->seat == seat) if (set->seat == seat) {
initialize_vmod_mappings(set);
load_keymap(set); load_keymap(set);
}
} }
} }