Add kitty keyboard protocol support (finish #4912) - #5405
Conversation
Implement the kitty progressive keyboard enhancement protocol, allowing tmux to negotiate richer key encoding with supporting terminals (ghostty, kitty, foot, etc.) and forward it to applications that opt in. New server option 'kitty-keys' (off/on/always): - off: disabled (default) - on: negotiate kitty protocol with the outer terminal via CSI ? u auto-detection; inner applications receive kitty encoding only when they explicitly push CSI > N u - always: force kitty disambiguate mode on all inner applications, similar to extended-keys always Protocol support: - Input: full CSI u / CSI ~ / CSI letter parser for kitty-encoded keys, including modifier colon subfields and event types (release events are discarded) - Output: kitty CSI u encoder with reverse mapping tables for functional keys and legacy arrow/F-key sequences - Inner application negotiation: CSI > N u (push), CSI < N u (pop), CSI ? u (query), CSI = N u (set) handled via input.c state machine with flag stack and alternate screen save/restore - Auto-detection: CSI ? u query sent to outer terminal; response auto-adds the kitkeys terminal feature and activates the parser Disambiguate mode (flag 1) follows the kitty spec for legacy fallback: - Tab, Enter, Backspace without modifiers send raw bytes - Ctrl+key combinations that map to C0 control codes (Ctrl+C, Ctrl+D, Ctrl+Z, etc.) send the raw C0 byte when Ctrl is the sole modifier - Unmodified printable ASCII uses legacy encoding - Caps Lock and Num Lock modifiers are stripped on both input and output since they are only meaningful with report-all-keys (flag 8+) New key definitions: F13-F35, keypad keys, media keys, modifier keys, Super/Hyper/RealMeta modifier bits, Caps Lock/Num Lock modifier bits. Key string parsing and display supports Super-, Hyper-, and RealMeta- prefixes. Terminal feature 'kitkeys' adds Enkitk/Dskitk capabilities. Foot included in default features; other terminals auto-detected via the CSI ? u query or configurable via terminal-features.
Mask unsupported kitty progressive flags in input and tty parser paths. Track outer-terminal kitty push/pop state to keep stack hygiene across feature updates and shutdown. Add focused regress/kitty-keys.sh coverage for encoding, query/set/push/pop semantics, parser discard behavior, and push/pop balance.
Add deterministic query-sequence tests for omitted/default parameters, mode 2/3 semantics, stack depth/underflow, and masked unsupported flags. Add parser robustness checks for unsupported progressive fields and incomplete kitty CSI-u sequences. Add fallback attach checks with Enkitk/Dskitk disabled for kitty-keys=always and kitty-keys=on.
Split kitty-specific input and tty logic into input-kitty.c and tty-kitty.c, and wire them through tmux.h and Makefile.am. Replace per-screen kitty stack array with current/saved two-slot state and update CSI push/pop/set/query handling accordingly. Adopt Sp-/Hy- modifier prefixes, drop RealMeta parsing/emission and kitty bit 0x20 mapping, and standardize terminology to VT10x/extended and function key wording. Expand regress/kitty-keys.sh expectations for the single saved kitty level behavior and updated terminology.
Remove the direct ENKITK emission from tty_update_features() and let tty_push_kitty() remain the single path that emits kitty push control sequences. This prevents duplicate push traffic during repeated feature updates and restores kitty push/pop balance in regress/kitty-keys.sh.
The kitty keyboard parser in tty_keys_kitty() returned bare KEYC_UP, KEYC_DOWN, KEYC_RIGHT, KEYC_LEFT without the KEYC_CURSOR flag. The normal VT10x raw key table in tty-keys.c sets KEYC_CURSOR on all four arrow keys, so the two input paths disagreed. Without KEYC_CURSOR, input_key() always stripped it (vacuously) at line 665 and matched the CSI entries (\033[A-D) regardless of whether the inner pane had application cursor mode enabled. When an inner application sent smkx (\033[?1h) and expected the terminfo-advertised kcuu1=\033OA (SS3 format), it instead received \033[A (CSI format). GNU readline hardcodes bindings for both CSI and SS3 arrow formats, so bash and gdb were unaffected. Applications using libedit, reline, or prompt_toolkit that strictly follow terminfo saw the unrecognized CSI sequence displayed literally as ^[[A, breaking history navigation and line editing. Add KEYC_CURSOR to the four arrow key cases in the kitty parser switch statement, matching the VT10x tree behavior. With the flag present, input_key() now preserves it when MODE_KCURSOR is active and correctly selects the SS3 output entries.
When extended-keys=always forces MODE_KEYS_EXTENDED on the pane screen and the inner application also requests kitty keyboard mode via CSI =1u, unmodified keys (printable characters, Tab, Enter, Space) are silently dropped. The kitty encoder correctly returns -1 for these keys (they should be sent as raw bytes per spec), but the fallback routes through the extended-keys encoder which has no case for zero modifiers, so input_key() returns without writing anything. Fix three sites: - input-keys.c: when kitty mode is active and input_key_kitty() punts, route directly to input_key_vt10x() for raw byte encoding instead of falling into the extended-keys switch. - screen.c (screen_reinit): make kitty-keys and extended-keys mutually exclusive; kitty protocol is a superset of CSI u, so kitty-keys=always takes priority. - screen-write.c (screen_write_reset): same mutual exclusion on terminal hard reset (RIS/DECSTR), which previously re-applied MODE_KEYS_EXTENDED without clearing kitty state, recreating the conflict. Add regression tests covering the combined mode scenario.
The kitty keyboard protocol escape sequences are universal and not something terminals implement differently, so the terminfo-style capability indirection is unnecessary. Hardcode the sequences directly and remove the kitkeys terminal feature.
When an application inside tmux requests kitty keyboard disambiguation with kitty-keys=on, mirror the visible screen's kitty flags onto the outer terminal instead of leaving the outer terminal in legacy mode. This lets ambiguous legacy combinations such as Ctrl-3 reach the pane as CSI-u rather than being collapsed to Escape. Also add an end-to-end regression that exercises the kitty-keys=on path and verifies Ctrl-3 is delivered as CSI 51;5u after the pane requests disambiguation.
When a pane enables kitty keyboard disambiguation, tmux must still pass text-producing input through as plain UTF-8 unless report-all mode is enabled. tmux was incorrectly encoding non-ASCII text through the CSI-u path in input_key_kitty(). For composed text such as è, it also used tmux's internal packed utf8_char value as the CSI-u numeric field instead of the actual Unicode codepoint, producing invalid sequences like CSI 1107339459u. Fix this by: - falling back to raw UTF-8 for text-producing keys in disambiguate-only mode, including non-ASCII text - converting tmux's internal Unicode representation back to a real codepoint before emitting CSI-u when CSI-u encoding is actually needed Add a regression for requested kitty mode with UTF-8 text input, alongside the existing requested Ctrl-3 coverage.`
Drop the public always value from kitty-keys and delete the runtime behavior that existed only for that mode. Keep kitty-keys as a two-value choice (off/on) and make on truthful: tmux now enables kitty keyboard handling only after the outer terminal has negotiated kitty support and pane state requests it. This change: - removes always from the option table - deletes unconditional kitty push/state seeding paths in tty/screen code - removes the input parser fallback that was enabled only by always - preserves active pushed kitty flags across capability query replies - rewrites the kitty regression to negotiate support explicitly under on - verifies set -g kitty-keys always now fails as an invalid value
Collapse kitty and xterm/csi-u enhanced key handling into a single configuration surface by removing the separate kitty-keys option and adding kitty as an extended-keys-format choice. Gate pane input state, key encoding, and outer tty negotiation on the selected format so tmux exposes only one enhanced key protocol at a time. When extended-keys=always, force kitty disambiguation for kitty format and xterm mode 1 for xterm/csi-u. Preserve mixed-client behavior by falling back to legacy input for clients that do not support the selected protocol, including popup jobs, while keeping pane_key_mode and the documentation truthful. Update the regressions to cover the exclusive protocol model and pin the input-keys regression to xterm format explicitly.
Preserve the outer terminal's current kitty keyboard flags from CSI ? u so tmux can restore them correctly after pushing its own state. Recompute kitty mode during feature refreshes using the client's current visible screen so pane-requested kitty flags are not dropped until the next redraw. Add regressions for eager kitty enablement, tmux bindings before pane requests, wrong-format requests in both on and always modes, and correct pane_key_mode reporting. Update the manpage to match the actual fallback behavior.
Master renamed client.prompt_string to client.prompt; update the kitty PR's server_client_get_screen to use the new field name.
Legacy tilde sequences (CSI 8~, CSI 11~, etc) are in the kitty tilde table and should be parsed whenever the outer terminal supports the kitty protocol, not only when disambiguate mode is actively pushed. The parser was gated on kitty_enabled_flags, which is 0 when no pane has pushed kitty mode — causing legacy function-key sequences to fall through to the extended-keys parser, which only handles CSI 27;mods;num ~ and CSI num;mods u, not plain CSI num ~. Gate on kitty_supported_flags instead so the kitty parser handles these sequences whenever the terminal has negotiated kitty support.
Replace Sp- with s- and Hy- with H- to match emacs conventions and feedback from nicm/craigbarnes on the original PR. The prefixes are case-sensitive (lowercase only) to avoid clashing with S- (Shift).
|
Thanks for picking this up. I will take a look at it at some point soon. |
| break; | ||
| case 1: /* partial */ | ||
| goto partial_key; | ||
| case -2: /* release event, discard */ |
There was a problem hiding this comment.
I hope this PR would address release events.
|
Can you rebase this on to There is no need to change I don't think release should be done now, it should be a separate change on top of this because it will need more changes to be able to represent release keys. |
|
Are you still working on this? |
|
Just looking at this, I notice you use this term 'legacy' a to refer to the plain unextended way of sending key srokes. I would avoid this term |
|
@brynbellomy Are you still working on this? |
Follow-on to #4912 -- intends to address the remaining feedback from @nicm.