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,
rather than converting to HSL and adjusting the luminance
([#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
[2009]: https://codeberg.org/dnkl/foot/issues/2009
### 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
*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.

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

View file

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

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