input: kitty: update to latest version of the spec

Starting with kitty 0.32.0, the modifier bits during modifier key
events behave differently, compared to before. Or, rather, they have
now been spec:ed; before, behavior was different on e.g. MacOS, and
Linux.

The new behavior is this:

On key press, the modifier bits in the kitty key event *includes* the
pressed modifier key.

On key release, the modifier bits in the kitty key event does *not*
include the released modifier key.

In other words, The modifier bits reflects the state *after* the key
event.

This is the exact opposite of what foot did before this patch.

The patch is really pretty small: in order to include the key in the
modifier set, we simulate a key press to update the XKB state, using
xkb_state_uppate_key(). For key pressed, we simulate an XKB_KEY_DOWN
event, and for key releases we simulate an XKB_KEY_UP event.

Then we re-retrieve the modifers, both the full set, and the consumed
set.

Closes #1561
This commit is contained in:
Daniel Eklöf 2024-01-22 16:39:26 +01:00
parent 6ed1c28d2c
commit 4ee4f47065
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 71 additions and 14 deletions

View file

@ -65,9 +65,14 @@
process. This ensures the terminal itself does not "lock" a process. This ensures the terminal itself does not "lock" a
directory; for example, preventing a mount point from being directory; for example, preventing a mount point from being
unmounted ([#1528][1528]). unmounted ([#1528][1528]).
* Kitty keyboard protocol: updated behavior of modifiers bits during
modifier key events, to match the (new [#6913][kitty-6913]) behavior
in kitty >= 0.32.0 ([#1561][1561]).
[1526]: https://codeberg.org/dnkl/foot/issues/1526 [1526]: https://codeberg.org/dnkl/foot/issues/1526
[1528]: https://codeberg.org/dnkl/foot/issues/1528 [1528]: https://codeberg.org/dnkl/foot/issues/1528
[1561]: https://codeberg.org/dnkl/foot/issues/1561
[kitty-6913]: https://github.com/kovidgoyal/kitty/issues/6913
### Deprecated ### Deprecated

80
input.c
View file

@ -1135,19 +1135,77 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term,
if (!disambiguate && !report_all_as_escapes && pressed) if (!disambiguate && !report_all_as_escapes && pressed)
return legacy_kbd_protocol(seat, term, ctx); return legacy_kbd_protocol(seat, term, ctx);
const xkb_mod_mask_t mods = ctx->mods & seat->kbd.kitty_significant;
const xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
seat->kbd.xkb_state, ctx->key, XKB_CONSUMED_MODE_GTK) & seat->kbd.kitty_significant;
const xkb_mod_mask_t effective = mods & ~consumed;
const xkb_mod_mask_t caps_num =
(seat->kbd.mod_caps != XKB_MOD_INVALID ? 1 << seat->kbd.mod_caps : 0) |
(seat->kbd.mod_num != XKB_MOD_INVALID ? 1 << seat->kbd.mod_num : 0);
const xkb_keysym_t sym = ctx->sym; const xkb_keysym_t sym = ctx->sym;
const uint32_t *utf32 = ctx->utf32; const uint32_t *utf32 = ctx->utf32;
const uint8_t *const utf8 = ctx->utf8.buf; const uint8_t *const utf8 = ctx->utf8.buf;
const size_t count = ctx->utf8.count; const size_t count = ctx->utf8.count;
/* Lookup sym in the pre-defined keysym table */
const struct kitty_key_data *info = bsearch(
&sym, kitty_keymap, ALEN(kitty_keymap), sizeof(kitty_keymap[0]),
&kitty_search);
xassert(info == NULL || info->sym == sym);
xkb_mod_mask_t mods = 0;
xkb_mod_mask_t consumed = 0;
if (info != NULL && info->is_modifier) {
/*
* Special-case modifier keys.
*
* Normally, the "current" XKB state reflects the state
* *before* the current key event. In other words, the
* modifiers for key events that affect the modifier state
* (e.g. one of the control keys, or shift keys etc) does
* *not* include the key itself.
*
* Put another way, if you press "control", the modifier set
* is empty in the key press event, but contains "ctrl" in the
* release event.
*
* The kitty protocol mandates the modifier list contain the
* key itself, in *both* the press and release event.
*
* We handle this by updating the XKB state to *include* the
* current key, retrieve the set of modifiers (including the
* set of consumed modifiers), and then revert the XKB update.
*/
xkb_state_update_key(
seat->kbd.xkb_state, ctx->key, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
get_current_modifiers(seat, &mods, NULL, ctx->key);
consumed = xkb_state_key_get_consumed_mods2(
seat->kbd.xkb_state, ctx->key, XKB_CONSUMED_MODE_GTK);
#if 0
/*
* TODO: according to the XKB docs, state updates should
* always be in pairs: each press should be followed by a
* release. However, doing this just breaks the xkb state.
*
* *Not* pairing the above press/release with a corresponding
* release/press appears to do exactly what we want.
*/
xkb_state_update_key(
seat->kbd.xkb_state, ctx->key, pressed ? XKB_KEY_UP : XKB_KEY_DOWN);
#endif
} else {
mods = ctx->mods;
/* Re-retrieve the consumed modifiers using the GTK mode, to
better match kitty. */
consumed = xkb_state_key_get_consumed_mods2(
seat->kbd.xkb_state, ctx->key, XKB_CONSUMED_MODE_GTK);
}
mods &= seat->kbd.kitty_significant;
consumed &= seat->kbd.kitty_significant;
const xkb_mod_mask_t effective = mods & ~consumed;
const xkb_mod_mask_t caps_num =
(seat->kbd.mod_caps != XKB_MOD_INVALID ? 1 << seat->kbd.mod_caps : 0) |
(seat->kbd.mod_num != XKB_MOD_INVALID ? 1 << seat->kbd.mod_num : 0);
bool is_text = count > 0 && utf32 != NULL && (effective & ~caps_num) == 0; bool is_text = count > 0 && utf32 != NULL && (effective & ~caps_num) == 0;
for (size_t i = 0; utf32[i] != U'\0'; i++) { for (size_t i = 0; utf32[i] != U'\0'; i++) {
if (!iswprint(utf32[i])) { if (!iswprint(utf32[i])) {
@ -1159,12 +1217,6 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term,
const bool report_associated_text = const bool report_associated_text =
(flags & KITTY_KBD_REPORT_ASSOCIATED) && is_text && !released; (flags & KITTY_KBD_REPORT_ASSOCIATED) && is_text && !released;
/* Lookup sym in the pre-defined keysym table */
const struct kitty_key_data *info = bsearch(
&sym, kitty_keymap, ALEN(kitty_keymap), sizeof(kitty_keymap[0]),
&kitty_search);
xassert(info == NULL || info->sym == sym);
if (composing) { if (composing) {
/* We never emit anything while composing, *except* modifiers /* We never emit anything while composing, *except* modifiers
* (and only in report-all-keys-as-escape-codes mode) */ * (and only in report-all-keys-as-escape-codes mode) */