Under the hood
The pseudo-terminal, VTE, and the half century of escape sequences a terminal has to cope with.
A page of kat800.
A terminal has to deal with half a century of conventions for how terminals talk to programs. This is what kat800 handles under the surface.
The pseudo-terminal
A PTY is a kernel mechanism that creates a pair of file descriptors, master and slave, which together behave like a real hardware serial terminal. The slave end looks like an ordinary tty device, /dev/pts/N, to any process that opens it. The master end is what kat800 reads from and writes to.
Between the two sits the kernel’s line discipline: the layer handling input echoing, line buffering and control-character translation. Press Ctrl+C and kat800 writes one byte, 0x03, to the master; the line discipline turns it into SIGINT for the foreground process group. The child only ever sees an ordinary tty.
When kat800 is attached to a tmux session the PTY pairs live on the tmux server instead. tmux holds the master ends, and kat800 renders what tmux forwards over the control-mode connection. Detach, and those PTYs and everything running on them stay alive on the server.
VTE, the rendering engine
kat800 uses libvte, GNOME’s terminal emulation library, to render pane output. VTE implements the VT220 and xterm state machine: it parses the byte stream from each master descriptor, interprets escape sequences, maintains cursor position and colour attributes, manages the alternate screen buffer that vim and htop use, and draws to a GTK surface.
Using libvte means inheriting two decades of compatibility work: wide characters, combining characters, 24-bit colour, bracketed paste and mouse tracking are all handled there rather than reimplemented. kat800 gets VTE 0.84 from amber-vte, because Linux Mint’s own 0.76 lacks the termprops the shell integration relies on.
Half a century of escape sequences
Every byte a program sends to a terminal is either printable text or a control sequence: how it moves the cursor, sets colour, clears the screen. There has never been a single agreed standard for what those byte sequences should be.
| Year | What happened |
|---|---|
| 1963 | ASCII standardises the escape character, 0x1B, as a control extender. Its use for terminal control is left undefined: it means only “escape from normal character interpretation”. |
| 1975 | DEC’s VT52 uses two-character sequences: ESC A for cursor up, ESC J to erase to end of screen. There was no room for parameters. |
| 1978 | The VT100 introduces the Control Sequence Introducer, ESC [: a two-byte prefix, then parameters, then a final command byte. ESC [2J clears the screen; ESC [32m sets green. Every terminal emulator alive today still uses this structure. |
| 1978 | Termcap, and later terminfo, describe each terminal’s capabilities in a database and let curses translate requests into the right bytes. $TERM selects the entry. It acknowledged the chaos rather than resolving it, and every terminal still depends on it. |
| 1979 | ANSI X3.64 is published, the standard the VT100 had followed in draft (ECMA-48, its European twin, dates from 1976). In practice each vendor added its own sequences and omitted the parts it found inconvenient, so the standard became a floor rather than a ceiling. |
| 1984-1999 | xterm grows mouse tracking, window titles and, in 1999, 256 colours, with dozens of non-standard extensions; rxvt, Konsole and GNOME Terminal each add incompatible supersets. F1 sends ESC OP in xterm and ESC [11~ in rxvt, and programs have to accept both. |
| 2021 | The Kitty keyboard protocol proposes encoding every key combination, modifiers included, as an unambiguous CSI sequence. foot and WezTerm adopt it, among others; VTE does not. |
The same key, four byte sequences
This is why Shift+F1 works in one application and does nothing in another, and why a terminal has to be careful about what it intercepts and what it passes through.
| Key | VT100 / xterm | rxvt | VTE | Kitty protocol |
|---|---|---|---|---|
F1 |
ESC OP |
ESC [11~ |
ESC OP |
ESC [P |
Shift+F1 |
ESC [1;2P |
ESC [23~ |
ESC [1;2P |
ESC [1;2P |
Ctrl+Left |
ESC [1;5D |
ESC Od |
ESC [1;5D |
ESC [1;5D |
Backspace |
0x7F DEL |
0x08 BS |
0x7F DEL |
0x7F DEL |
Alt+Enter |
ESC 0x0D |
ESC 0x0D |
ESC 0x0D |
ESC [13;3u |
Ctrl+Shift+U |
undefined | undefined | input method (IBus) | ESC [117;6u |
How kat800 handles it
One VTE widget per pane. Each pane is its own VTE 0.84 widget with an independent VT220 and xterm state machine (cursor position, colour attributes, alternate screen mode) kept by VTE rather than reimplemented. kat800’s job is wiring panes to the right byte streams and staying out of VTE’s way.
Control mode instead of a raw stream. Attached with tmux -CC, kat800 receives structured notifications rather than a raw escape stream, and feeds each pane’s output into its own widget. Applications see whatever terminal type the remote tmux is set to, usually tmux-256color or screen-256color, which curses-based programs already know how to target.
Leader-key isolation. The leader key, Ctrl+A by default, is consumed before input reaches any pane, so it never appears in a program’s input stream. Press it twice and the second press cancels the chord and sends the keystroke on.