From 4ee4f47065d7e1a77a18d3b029f94ccab8ddb3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 22 Jan 2024 16:39:26 +0100 Subject: [PATCH] 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 --- CHANGELOG.md | 5 ++++ input.c | 80 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01309223..cb62594e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,9 +65,14 @@ process. This ensures the terminal itself does not "lock" a directory; for example, preventing a mount point from being 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 [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 diff --git a/input.c b/input.c index e3613a90..ba118e67 100644 --- a/input.c +++ b/input.c @@ -1135,19 +1135,77 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, if (!disambiguate && !report_all_as_escapes && pressed) 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 uint32_t *utf32 = ctx->utf32; const uint8_t *const utf8 = ctx->utf8.buf; 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; for (size_t i = 0; utf32[i] != U'\0'; i++) { if (!iswprint(utf32[i])) { @@ -1159,12 +1217,6 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, const bool report_associated_text = (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) { /* We never emit anything while composing, *except* modifiers * (and only in report-all-keys-as-escape-codes mode) */