Secondary DA reply decoder — `CSI > Pp ; Pv ; Pc c` model / firmware / cart
Decoder reference for the secondary Device Attributes reply — what each Pp model code means and how kitty / wezterm / alacritty / iTerm2 / Ghostty encode their version in Pv.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1b[>Pp;Pv;Pcc\033[>0;276;0c\e[>0;276;0cESC [ > Pp ; Pv ; Pc c1b 5b 3e ... 63Description
The *reply* shape for secondary DA (request side documented under csi-da). The application sends \x1b[>c (or \x1b[>0c); the terminal answers \x1b[>Pp;Pv;Pc c on stdin. TUIs branch on Pp to pick a feature set and on Pv to enable per-version workarounds — this page is the lookup table.
Pp — model code:
- 0 — DEC VT100 (or terminal claiming VT100 compatibility — common from minimal embedded emulators).
- 1 — DEC VT220.
- 2 — DEC VT240 / VT241.
- 18 — DEC VT330.
- 19 — DEC VT340.
- 24 — DEC VT320.
- 28 — DEC VT330 (alternative).
- 32 — Tek 4014.
- 41 — DEC VT420.
- 61 — DEC VT510 (and most xterm-class emulators, including xterm itself patch ≥ 95).
- 64 — DEC VT520.
- 65 — DEC VT525 (and Kitty, Alacritty, WezTerm, iTerm2 all use 65 as a 'modern xterm-class' marker by convention; Ghostty uses 65 too).
- 82 — rxvt.
- 83 — Eterm.
- 84 — sun terminal.
- 85 — rxvt-unicode (urxvt).
Pv — firmware / version:
- DEC hardware: literal ROM version (e.g. xterm patch level → Pv = 95+).
- xterm: Pv is the xterm patch level — \x1b[>61;392;0c means xterm patch 392.
- Kitty: Pv encodes the version — Pv = major*10000 + minor*100 + patch, so 0.41.1 → 4101 (older versions used base-100, check current behaviour).
- WezTerm: Pv is a build year-month — e.g. 4604 ≈ 2024.10 build.
- Alacritty: Pv is fixed at 0 (Alacritty doesn't expose version this way; use XTVERSION instead — see xtversion).
- iTerm2: Pv is fixed at 95 (frozen at the original xterm-95 marker for compatibility — use XTVERSION for the real version).
- Ghostty: Pv is the build patch level.
- Windows Terminal: Pv is the WT version major*100 + minor (e.g. 0 for 1.0, 18 for 1.18).
Pc — cartridge / serial:
- DEC hardware: ROM cartridge number, almost always 0 on serial-only models.
- Software emulators: always 0.
- Treat any non-zero Pc as informational only — no portable meaning.
Recommended consumer pattern: ``` match (Pp, Pv) { (65, v) if v >= 1000 => 'modern xterm-class' # Kitty / WezTerm / Alacritty / iTerm2 (64, _) => 'VT520 or VT520-compat' (61, _) => 'xterm or xterm-class' (41, _) => 'VT420 or VT420-compat' (1, _) => 'VT220 or VT220-compat' (0, _) => 'VT100 or minimal' _ => 'unknown — feature-detect via XTVERSION or DECRQM' } ```
Don't trust DA2 for fine-grained feature detection — Pp coverage is coarse and Pv encoding is per-emulator. For 'does this terminal support feature X' use XTVERSION (xtversion) for the emulator name + version, then DECRQM (decrqm) to query the specific mode bit. Use DA2 only for the broad model bucket.
Spec citation: xterm-ctlseqs (Secondary DA reply) / DEC VT320+ DA2
Parameters
| Pp | Model code — 0 VT100, 1 VT220, 41 VT420, 61 xterm-class, 64 VT520, 65 modern xterm-class (Kitty/WezTerm/Alacritty/iTerm2/Ghostty), 82 rxvt, 85 urxvt. |
| Pv | Firmware / version — encoding is per-emulator (xterm patch, Kitty major*10000+minor*100+patch, WezTerm year-month build, Alacritty=0, iTerm2=95). Use XTVERSION for portable version detection. |
| Pc | Cartridge / serial — almost always 0 on software emulators; informational only. |
Examples
# Ask + decode model bucket.\nold=$(stty -g); stty -echo raw min 0 time 1\nprintf '\033[>c' > /dev/tty\nIFS= read -r -d c reply < /dev/tty\nstty "$old"\npp=$(echo "$reply" | sed -nE 's/.*>([0-9]+);.*/\\1/p')\ncase "$pp" in\n 0) echo 'VT100 / minimal' ;;\n 1) echo 'VT220-compat' ;;\n 41) echo 'VT420-compat' ;;\n 61) echo 'xterm-class' ;;\n 65) echo 'modern xterm-class (Kitty/WezTerm/Alacritty/iTerm2/Ghostty)' ;;\n 82) echo 'rxvt' ;;\n 85) echo 'urxvt' ;;\n *) echo "unknown model: $pp" ;;\nesacimport sys, termios, tty, re\nMODELS = {0:'VT100/minimal',1:'VT220',41:'VT420',61:'xterm-class',64:'VT520',65:'modern xterm-class',82:'rxvt',85:'urxvt'}\nfd = sys.stdin.fileno(); old = termios.tcgetattr(fd); tty.setraw(fd)\ntry:\n sys.stdout.write('\x1b[>c'); sys.stdout.flush()\n buf = b''\n while not buf.endswith(b'c'): buf += sys.stdin.buffer.read1(64)\n m = re.search(rb'>(\d+);(\d+);(\d+)c', buf)\n pp, pv, pc = (int(x) for x in m.groups())\n print(f'model={MODELS.get(pp, "unknown")} ({pp}); version={pv}; cart={pc}')\nfinally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old)// Decode the (Pp, Pv) bucket — Pc is informational.\nvar models = map[int]string{0:\"VT100\",1:\"VT220\",41:\"VT420\",61:\"xterm-class\",64:\"VT520\",65:\"modern xterm-class\",82:\"rxvt\",85:\"urxvt\"}\nfunc decodeDA2(reply string) (string, int) {\n var pp, pv, pc int\n fmt.Sscanf(reply, \"\\x1b[>%d;%d;%dc\", &pp, &pv, &pc)\n return models[pp], pv\n}// Parse a DA2 reply string into a labelled bucket.\nfunction decodeDA2(reply) {\n const m = reply.match(/>(\d+);(\d+);(\d+)c/);\n if (!m) return null;\n const [pp, pv, pc] = m.slice(1).map(Number);\n const models = {0:'VT100',1:'VT220',41:'VT420',61:'xterm-class',64:'VT520',65:'modern xterm-class',82:'rxvt',85:'urxvt'};\n return { model: models[pp] || `unknown(${pp})`, version: pv, cartridge: pc };\n}/* Decode a DA2 reply already buffered in `reply`. */\nint pp = 0, pv = 0, pc = 0;\nif (sscanf(reply, \"\\x1b[>%d;%d;%dc\", &pp, &pv, &pc) == 3) {\n /* branch on pp / pv */\n}Terminal support
- xterm
- yes
- Linux console (fbcon)
- partial
- macOS Terminal.app
- partial
- iTerm2
- yes
- Windows Terminal
- yes
- cmd.exe / ConPTY
- partial
- kitty
- yes
- alacritty
- partial
- WezTerm
- yes
- Ghostty
- yes
- GNOME Terminal
- yes
- Konsole
- yes
- tmux
- yes
- GNU screen
- yes
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | partial | partial | yes | yes | partial | yes | partial | yes | yes | yes | yes | yes | yes |