Skip to content

feat(terminal): make the Kitty keyboard protocol configurable - #180

Draft
Ayman Bagabas (aymanbagabas) wants to merge 1 commit into
mainfrom
aymanbagabas/kitty-keyboard-config
Draft

feat(terminal): make the Kitty keyboard protocol configurable#180
Ayman Bagabas (aymanbagabas) wants to merge 1 commit into
mainfrom
aymanbagabas/kitty-keyboard-config

Conversation

@aymanbagabas

Copy link
Copy Markdown
Member

Adds a kitty_keyboard profile setting (default on) and fixes a gap where the ghostty backend silently reported no Kitty keyboard flags at all.

Why

Two separate problems, one subject.

There was no way to test the legacy path. A TUI that negotiates the Kitty protocol still has to work on the many terminals that lack it, and that fallback was unreachable from tui-test: every backend that implemented the protocol always had it on.

ghostty never reported any flags. Emulator::keyboard_mode has a default returning KeyboardMode::empty(), and GhosttyEmu never overrode it. So every key reaching a ghostty session was legacy-encoded no matter what the child had negotiated: key press would send \x1b[A to a program that had asked for CSI u event reporting. libghostty-vt exposed the state as Terminal::kitty_keyboard_flags() the whole time; it was simply never called.

What

Profile::kitty_keyboard, default true, plumbed through tui-test.toml, the JS binding (kittyKeyboard), and the Python binding (kitty_keyboard).

Gating is native wherever the backend offers a switch:

backend how it is gated child's CSI ? u query
alacritty Config::kitty_keyboard unanswered, as on a terminal without the protocol
rio at the read; rio-vt takes no configuration still answered
ghostty at the read; Terminal takes no configuration still answered
xtermjs n/a, no implementation to gate n/a

alacritty's switch suppresses the push, pop, set and report paths, so a disabled session is indistinguishable from a terminal that never implemented the protocol. rio and ghostty have no equivalent, so they are gated where the mode is read and their query replies still go out. The comments at each site say exactly this rather than implying parity.

xterm.js

Reports no flags because the vendored bundle contains no implementation. Verified, not inferred: @xterm/headless 6.0.0 has no vtExtensions, and no handler for CSI > u, CSI = u, or CSI < u. That is a limit of the emulator rather than of the mapping onto it, which is what Divergences is for, so it is declared there as no_kitty_keyboard.

Support arrives upstream in the 6.1.0 beta line via vtExtensions.kittyKeyboard. Tracked in #179 with the steps to take once the vendored bundle gets there; the divergence flag is the one line to delete.

Tests

Two new conformance cases, so the contract is pinned for every backend rather than for the one that happened to be implemented first:

  • conformance_kitty_keyboard_modes_are_pushed_and_popped re-checks push, stack, and pop, with each of the five flags mapping to its own bit. Skipped for backends declaring no_kitty_keyboard.
  • conformance_kitty_keyboard_can_be_turned_off checks that a disabled profile reports no flags whatever the child pushes. Runs on all backends including xtermjs, since "off" is a claim every backend can honor.

A backend that under-reports now fails a named test instead of quietly falling back to legacy keys. This case is what would have caught the ghostty gap.

Plus a_profile_can_turn_the_kitty_keyboard_protocol_off for config parsing, and a default assertion in an_empty_config_yields_the_defaults.

cargo test --workspace --features tui-test-rs/ghostty,tui-test-rs/rio,tui-test-rs/xtermjs gives 423 passed, 0 failed.

Not in this PR

Routing key encoding to a backend's own encoder, and the related discovery that keys.rs models no DECCKM at all. Separate PR.

Add `kitty_keyboard` to `Profile`, defaulting to on. Turning it off makes a
session behave like a terminal that never implemented the protocol: the
child's mode pushes are ignored, `keyboard_mode` stays empty, and `key press`
keeps sending legacy encodings. That is the fallback path a TUI takes on the
many terminals without the protocol, and it was not testable before.

Gate it natively where the backend offers a switch. alacritty has
`Config::kitty_keyboard`, which suppresses the push, pop, set, and report
paths, so a child querying with `CSI ? u` gets no reply at all. rio and
ghostty take no such configuration, so they are gated where the mode is read
and their query replies still go out; the comments say so.

Also implement `Emulator::keyboard_mode` for ghostty, which was never wired
up and silently reported no flags, so every key reached a ghostty session
legacy-encoded no matter what the child had negotiated. libghostty-vt exposes
the state as `Terminal::kitty_keyboard_flags`.

xterm.js reports no flags because its bundle contains no implementation of
the protocol, which is a limit of the emulator rather than of the mapping
onto it, so it is declared as a conformance divergence. Support arrives with
`vtExtensions.kittyKeyboard` when the vendored bundle reaches 6.1.0 (#179).

The two new conformance cases pin the contract for every backend that claims
the protocol, so a backend that under-reports fails a named test instead of
quietly falling back to legacy keys.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant