Skip to content

Kitty keyboard protocol: implement higher levels (report-all-keys-as-esc, associated text, event types, alternate keys) #153

Description

@matej21

Summary

Implement the higher levels of the kitty keyboard protocol, building on the level-1 ("disambiguate escape codes") support added in 71e69e81.

Level 1 is intentionally isolated to crates/okena-terminal/src/input.rs (kitty_disambiguate_bytes) and the KittyKeyboardFlags plumbing, because it only rewrites keys that already bypass the GPUI text-input path. The remaining levels touch the delicate text-input dispatch and need their own focused effort + manual cross-platform verification.

Scope (what's still missing)

alacritty_terminal already tracks all of these flags in TermMode; okena only honors DISAMBIGUATE_ESC_CODES today. To implement:

  • REPORT_ALL_KEYS_AS_ESC (0b1000) — every key, including plain printable characters, must be reported as CSI u and must not be delivered as text. Requires routing text away from GPUI's InputHandler when the flag is active.
  • REPORT_ASSOCIATED_TEXT (0b10000) — include the produced text as codepoints in the CSI u sequence (CSI key ; mods ; text u). Needs the resolved character at key-down time, which is not available for IME / dead-key / compose input.
  • REPORT_EVENT_TYPES (0b10) — emit press / repeat / release event types (CSI key ; mods:event u). Requires wiring key-up events, which the current input dispatch does not handle.
  • REPORT_ALTERNATE_KEYS (0b100) — include shifted / base-layout alternate key codes (CSI key:shifted:base ; mods u). Needs richer key info than okena's current KeyEvent { key: String, key_char, modifiers } carries.
  • Extend KittyKeyboardFlags (currently a single disambiguate_escape_codes field) and Terminal::kitty_keyboard_flags() to expose all the flags.
  • Full functional-key code table (F-keys, Home/End/PageUp/Down/Insert/Delete, keypad) in the CSI u / functional forms the protocol expects.

Architectural considerations

This is the part that made us split it out (see discussion). It is an extension of an existing pattern, not a violation — but it lands in the most sensitive part of input:

  1. Mode-dependent text routing. When REPORT_ALL_KEYS_AS_ESC is on, text keys must be intercepted at on_key_down and the InputHandler text path suppressed. The view already branches on terminal mode per keystroke (is_app_cursor_mode(), now kitty_keyboard_flags()), so this extends that — but it must be done carefully so normal typing never regresses when the flag is off.
  2. IME / dead keys / compose. Bypassing InputHandler bypasses OS text composition. REPORT_ASSOCIATED_TEXT wants the composed character at key-down, which doesn't exist yet for IME. Even reference terminals have caveats here. This is the main correctness hazard and the reason manual cross-platform testing (macOS IME, Linux compose) is required.
  3. Key-release events. REPORT_EVENT_TYPES needs key-up delivery to the terminal element.
  4. Flag-advertising asymmetry. alacritty answers the app's CSI ? u query with whatever flags the app pushed, regardless of what okena actually encodes. With only level 1 implemented, an app that requested a higher level is told it's active but receives legacy bytes for the keys we don't yet encode. (Level 1 is never worse than the previous all-legacy behavior; this matters when implementing partial higher levels.) A clean fix would be to clamp the accepted flags to what okena can honor — which needs a sidecar that filters the kitty set/push CSI u sequences alacritty currently handles internally.

References

  • Kitty keyboard protocol spec: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
  • alacritty's own encoder (alacritty/src/input/keyboard.rs) is the closest reference, since okena consumes alacritty's TermMode flags.
  • Level-1 implementation + plumbing to mirror: commit 71e69e81 (crates/okena-terminal/src/input.rs, terminal/modes.rs).

Acceptance

  • neovim / kitty's kitty +kitten show_key -m kitty report correct codes across press/repeat/release.
  • No regression to normal typing, IME, or dead-key/compose input when the protocol is off (the common case).
  • Unit tests for the encoder cover each level's representative cases, alongside the existing level-1 tests in input::tests.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions