Merge branch 'kbd-no-virtual-modifiers'

Closes #2009
This commit is contained in:
Daniel Eklöf 2025-03-31 13:04:02 +02:00
commit a50f78c599
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 151 additions and 8 deletions

View file

@ -68,8 +68,20 @@
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]).
* 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.
* 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
### Deprecated ### Deprecated

View file

@ -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 in upper case. For example, *Control+Shift+V* will never trigger, but
*Control+Shift+v* will. *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. *xkbcli interactive-wayland* can be useful for finding keysym names.

52
input.c
View file

@ -620,6 +620,54 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
if (seat->kbd.mod_num != XKB_MOD_INVALID) if (seat->kbd.mod_num != XKB_MOD_INVALID)
seat->kbd.kitty_significant |= 1 << seat->kbd.mod_num; 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_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"); 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; 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) #if defined(_DEBUG)
char depressed[256]; char depressed[256];
char latched[256]; char latched[256];

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);
}
} }
} }

View file

@ -136,6 +136,8 @@ struct seat {
xkb_mod_mask_t legacy_significant; /* Significant modifiers for the legacy keyboard protocol */ 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 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_up;
xkb_keycode_t key_arrow_down; xkb_keycode_t key_arrow_down;