Fuzzball keyboard — v0.1 (implementable draft)
Subsystem letter: k. Framing, replies and errors: see README.
1. One mode
There is exactly one mode: structured key events. While it is on, every key event (press, repeat, release) is reported as one message; nothing is sent in legacy form. While it is off, the terminal sends legacy (xterm/VT) bytes.
Enable / disable (per screen; the main and alternate screens are independent):
ESC P = F z k s ; on=1[,v=0][,id=<n>] ESC \ enable
ESC P = F z k s ; on=0[,id=<n>] ESC \ disable
ESC P = F z k q ; id=<n> ESC \ query
→ ESC P = F z k r ; id=<n>,ok=1,on=1 ESC \
The mode is also turned off by RIS and when the program's screen is left (e.g. leaving the alternate screen restores the main screen's own state).
2. Event message
Sent by the terminal to the application:
ESC P = F z k e ; t=<p|r|u>,code=<phys>,key=<logical>[,mods=<n>][,locks=<n>][,text=<hex>][,ime=<phase>] ESC \
| field | meaning |
|---|---|
t |
p press, r repeat, u release. |
code |
physical key, as the USB HID usage name from the W3C UI Events code list, e.g. KeyA, Digit1, ArrowUp, ShiftLeft, Numpad7. Layout independent. |
key |
logical key: a W3C UI Events key name for non-text keys (Enter, Escape, F13, MediaPlayPause…), or U+XXXX for the unmodified character the key produces on the current layout (e.g. U+0061 for a, U+0444 for ф). |
mods |
bitmask of held modifiers: 1 Shift, 2 Alt, 4 Ctrl, 8 Super, 16 Hyper, 32 Meta, 64 AltGr. Omitted when 0. |
locks |
1 CapsLock, 2 NumLock, 4 ScrollLock. Omitted when 0. |
text |
UTF-8 text this event inserts, hex-encoded (so any character is safe on the wire). Omitted when none. Present for presses and repeats only. |
ime |
start, update, commit, cancel — composition state; see §3. |
Modifier keys themselves produce events (code=ShiftLeft,key=Shift), with mods
reflecting the state after the event.
Examples:
a ESC P = F z k e ; t=p,code=KeyA,key=U+0061,text=61 ESC \
Shift+a ESC P = F z k e ; t=p,code=KeyA,key=U+0061,mods=1,text=41 ESC \
release a ESC P = F z k e ; t=u,code=KeyA,key=U+0061 ESC \
Ctrl+c ESC P = F z k e ; t=p,code=KeyC,key=U+0063,mods=4 ESC \
Ctrl+ф (RU) ESC P = F z k e ; t=p,code=KeyA,key=U+0444,mods=4 ESC \
Enter ESC P = F z k e ; t=p,code=Enter,key=Enter,text=0d ESC \
Up (repeat) ESC P = F z k e ; t=r,code=ArrowUp,key=ArrowUp ESC \
Left Shift ESC P = F z k e ; t=p,code=ShiftLeft,key=Shift,mods=1 ESC \
Applications get both the physical key (for shortcuts that should not move with the
layout, e.g. games, vim's hjkl on Dvorak if desired) and the logical key (for shortcuts
by letter), without the terminal guessing which one is meant.
3. IME and composed text
Composition is reported separately from key presses so applications don't mistake pre-edit keystrokes for input:
ESC P = F z k e ; t=p,ime=start ESC \
ESC P = F z k e ; t=p,ime=update,text=e3818b ESC \ pre-edit "か", not inserted
ESC P = F z k e ; t=p,ime=commit,text=e6bca2e5ad97 ESC \ insert "漢字"
ESC P = F z k e ; t=p,ime=cancel ESC \
Key events consumed by the IME are not reported. Dead keys behave like a one-character
composition (start … commit). Applications that don't care about pre-edit only act on
commit.
4. Paste
Pasted text is not keyboard input; it stays on bracketed paste (mode 2004) whether or not this mode is on.
5. Capability keys
In the common capability reply: k=1 (mode available), k_ime=1 if IME events are
reported, k_release=1 if the platform delivers release events (some remote setups
cannot).
6. Differences from the Kitty keyboard protocol
- No flag bits: one mode, always full information.
- Physical and logical keys are separate named fields instead of numeric code points plus "alternate keys".
- Text is always explicit (hex), so control characters and non-BMP text are unambiguous.
- IME composition is part of the protocol.