# Kitty Keyboard Protocol (CSI ? u, CSI = u, CSI > u, CSI < u)

> Ask the terminal to report keys unambiguously, with every modifier and with releases, through a stack of flags.

- **Sequence:** `CSI = Ps ; Pm u`
- **Specification:** [Comprehensive keyboard handling in terminals, kitty documentation](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement)

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](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement):

| 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](https://control-codes.page/modes/altscreen/index.html.md) 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](https://control-codes.page/csi/da/index.html.md): 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`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/VTPrsTbl.c#L4198), [`dec2_table`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/VTPrsTbl.c#L4848),
[`dec3_table`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/VTPrsTbl.c#L5172), [`csi_table`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/VTPrsTbl.c#L555)).
xterm has its own way of sending keys with modifiers,
[XTMODKEYS](https://control-codes.page/csi/xtmodkeys/index.html.md), 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`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L4912-L4920)); 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

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?u
```

Expected screen:

```text
|________|
reply none
```

### KITTY-2: Detecting the protocol

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?u     # the flags, if it has them
\e[c      # DA, which every terminal answers
```

Expected screen:

```text
|________|
reply \e[?62;22c
```

### KITTY-3: Setting flags

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[=5u    # 1 and 4
\e[=2;2u  # and 2
\e[?u
```

Expected screen:

```text
|________|
reply none
```

### KITTY-4: Push and pop

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[>1u
\e[>3u
\e[<u
\e[?u
```

Expected screen:

```text
|________|
reply none
```

### KITTY-5: Nothing on screen

Input, one step per line:

```text
A
\e[>1u
\e[=31u
\e[<u
B
```

Expected screen:

```text
|AB______|
cursor 1,3
```

### KITTY-6: Without a prefix, CSI u restores the cursor

Input, one step per line:

```text
\e[2;3H
\e[s      # save
\e[1;1H
\e[u      # restore
X
```

Expected screen:

```text
|________|
|__X_____|
cursor 2,4
```

---

This is the Markdown version of <https://control-codes.page/csi/kitty-keyboard/>. On that page every validation case runs live in libghostty-vt, the terminal emulation core of Ghostty, compiled to WebAssembly.

The validation cases are written in the notation described in <https://control-codes.page/notation/index.html.md>.
