CSI = u Kitty Keyboard Protocol
Ask the terminal to report keys unambiguously, with every modifier and with releases, through a stack of flags.
CSI = Ps ; Pm u
The usual way of sending keys loses information: Esc is the same byte that starts an escape sequence, Control and i is the same as Tab, most keys with Control and Shift together cannot be sent at all, and a key’s release is never sent. The kitty terminal’s keyboard protocol fixes this, and a program turns on as much of it as it needs with a set of flags:
| Flag | Asks for |
|---|---|
1 | disambiguate: send Esc, and keys with Control or Alt, as CSI code ; modifiers u |
2 | report event types: presses, repeats and releases |
4 | report alternate keys: the shifted key and the key’s place on a US layout |
8 | report all keys as escape codes, ordinary text included |
16 | report the text a key produces along with it |
The control sequences that set them, all with the final u, are defined in kitty’s Comprehensive keyboard handling in terminals:
| Sequence | Does |
|---|---|
CSI = flags ; mode u | set the flags: mode 1, the default, replaces them; 2 turns the given ones on; 3 turns them off |
CSI ? u | ask for the flags; answered CSI ? flags u |
CSI > flags u | push the current flags on a stack and set flags, 0 if left out |
CSI < n u | pop n entries, 1 if left out; emptying the stack clears the flags |
The main screen and the alternate screen each have their own stack, so that an editor can change the flags on the alternate screen without knowing what the shell had set. A program finds out whether a terminal has the protocol by sending CSI ? u and then DA: a terminal that answers DA without answering CSI ? u does not.
A default xterm does not have it. Its parse tables send CSI ? u, CSI > u and CSI = u to the ground state, and ignore any sequence that starts CSI < (dec_table, dec2_table, dec3_table, csi_table). xterm has its own way of sending keys with modifiers, XTMODKEYS, which the protocol’s documentation argues against. The one CSI … u it does have is plain CSI u, with no prefix, which restores the cursor saved by CSI s, as SCO’s console did (CASE_ANSI_RC); that works the same in libghostty-vt.
libghostty-vt implements the protocol, with the two stacks, so it answers CSI ? u and the flags change what its key encoder sends. The cases follow a default xterm, which leaves every question unanswered, so those that ask are known differences.
Validation
KITTY-1: The query is not answered
\e[?u
|________|
reply none
KITTY-2: Detecting the protocol
\e[?u # the flags, if it has them
\e[c # DA, which every terminal answers
|________|
reply \e[?62;22c
KITTY-3: Setting flags
\e[=5u # 1 and 4
\e[=2;2u # and 2
\e[?u
|________|
reply none
KITTY-4: Push and pop
\e[>1u
\e[>3u
\e[<u
\e[?u
|________|
reply none
KITTY-5: Nothing on screen
A
\e[>1u
\e[=31u
\e[<u
B
|AB______|
cursor 1,3
KITTY-6: Without a prefix, CSI u restores the cursor
\e[2;3H
\e[s # save
\e[1;1H
\e[u # restore
X
|________|
|__X_____|
cursor 2,4