Skip to content

Add kitty keyboard protocol support (finish #4912) - #5405

Open
brynbellomy wants to merge 20 commits into
tmux:masterfrom
brynbellomy:add-kitty-keyboard-protocol-support
Open

Add kitty keyboard protocol support (finish #4912)#5405
brynbellomy wants to merge 20 commits into
tmux:masterfrom
brynbellomy:add-kitty-keyboard-protocol-support

Conversation

@brynbellomy

Copy link
Copy Markdown

Follow-on to #4912 -- intends to address the remaining feedback from @nicm.

sundbp and others added 19 commits March 5, 2026 17:51
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).
@nicm

nicm commented Jul 17, 2026

Copy link
Copy Markdown
Member

Thanks for picking this up. I will take a look at it at some point soon.

@nicm nicm mentioned this pull request Jul 17, 2026
17 tasks
Comment thread tty-keys.c
break;
case 1: /* partial */
goto partial_key;
case -2: /* release event, discard */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope this PR would address release events.

@nicm

nicm commented Jul 21, 2026

Copy link
Copy Markdown
Member

Can you rebase this on to master and fix conflicts?

There is no need to change KEYC_MASK_MODIFIERS etc, there is already room to add KEYC_SUPER and KEYC_HYPER.

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.

@nicm nicm moved this from Not Started to Waiting in Open Issues & PRs Jul 21, 2026
@nicm

nicm commented Jul 29, 2026

Copy link
Copy Markdown
Member

Are you still working on this?

scturtle added a commit to scturtle/dotfiles that referenced this pull request Jul 31, 2026
@mgrant0

mgrant0 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

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 "force_legacy" and negate the logic and use extended_encoding (or enable_extended_encoding) instead.

@mgrant0

mgrant0 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@brynbellomy Are you still working on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting

Development

Successfully merging this pull request may close these issues.

5 participants