DECRPM decoder — parsing the DECRQM reply (`CSI ? Ps ; Pm $ y`)
How to read the `CSI ? Ps ; Pm $ y` (and `CSI Ps ; Pm $ y`) reply that DECRQM returns — the `Pm` value tells you whether the mode is set / reset / always-set / always-reset / unrecognised.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1b[?<Ps>;<Pm>$y (private) \x1b[<Ps>;<Pm>$y (ANSI)\033[?2004;1$y\e[?2004;1$yESC [ ? Ps ; Pm $ y1b 5b 3f ... 24 79Description
DECRPM — Report Private Mode (private form) or Report Mode (ANSI form) — is the reply terminals emit in response to a DECRQM query (`CSI ? Ps $ p` for private modes, `CSI Ps $ p` for ANSI modes; see `decrqm`). The reply echoes the queried `Ps` and adds a `Pm` value whose small enum tells you exactly what the terminal knows about that mode. **`Pm` values**: - **`0`** — *mode not recognised*. Treat the mode as unsupported. Don't keep probing this `Ps`; cache the result as 'unsupported' for the session. - **`1`** — *mode is currently set*. Equivalent to `\x1b[?<Ps>h` (or `\x1b[<Ps>h` for ANSI) having been the last operation on it. - **`2`** — *mode is currently reset*. Equivalent to `\x1b[?<Ps>l` (or `\x1b[<Ps>l` for ANSI) having been the last operation on it. - **`3`** — *mode is permanently set*. The terminal hardwires it on; sending `\x1b[?<Ps>l` is a no-op. - **`4`** — *mode is permanently reset*. The terminal hardwires it off; sending `\x1b[?<Ps>h` is a no-op. The `?` in the reply mirrors the `?` in the request — querying a private mode (e.g. `?2004` bracketed paste, `?1000` mouse) yields `\x1b[?2004;Pm$y`, while querying an ANSI mode (e.g. `4` IRM, `20` LNM) yields `\x1b[4;Pm$y` *without* the `?`. Consumers must match the request shape literally — a stream that interleaves private and ANSI probes will mis-route replies otherwise. **Recommended consumer pattern** (Python-ish pseudocode): ``` def probe_mode(ps, private=True, timeout=0.1): prefix = '?' if private else '' sys.stdout.write(f'\x1b[{prefix}{ps}$p') sys.stdout.flush() reply = read_until_finalbyte('y', timeout) # CSI ... $ y m = re.match(rf'\x1b\[{re.escape(prefix)}(\d+);(\d+)\$y', reply or '') if not m or int(m.group(1)) != ps: return 'no-reply' return {0:'unknown', 1:'set', 2:'reset', 3:'permaset', 4:'permareset'}[int(m.group(2))] ``` **What 'permanently set/reset' really means**: terminals use `Pm=3` / `Pm=4` to signal 'this feature is on/off and you can't toggle it from escape codes'. Examples: - A terminal that ships with bracketed paste always-on (some users configure xterm this way via X resources) returns `Pm=3` for `?2004`. - Older Linux console returns `Pm=4` for `?1000` (mouse tracking) since the kernel `vt` driver never implemented it. - ConPTY returns `Pm=4` for many DEC private modes (e.g. `?9` X10 mouse, `?1015` urxvt mouse) because the conhost translation layer fixes those features off. Treat `Pm=3` / `Pm=4` the same as `Pm=1` / `Pm=2` for read purposes — the mode IS in that state — but don't bother emitting an h/l toggle that would be silently ignored. **Vendor compliance**: - **xterm**, **kitty**, **iTerm2**, **WezTerm**, **Ghostty**, **Konsole**: full spec — return all five `Pm` values appropriately. - **Alacritty**: implements `Pm ∈ {0, 1, 2}` only — never returns `3` or `4`. Tools that branch on permaset/permareset must treat 'absent' as 'unknown but probably supported'. - **Linux console** (`vt`): replies to a small whitelist of common modes (`?1`, `?7`, `?25`); silent on everything else (no reply at all — be sure to time out). - **Windows Terminal**: full compliance ≥ 1.20. Earlier builds returned `Pm=0` for some supported modes. - **cmd.exe / ConPTY**: limited — most probes return `Pm=0` even for modes the terminal honours. - **macOS Terminal**: partial; some modes return `Pm=0` instead of `Pm=1`/`Pm=2` even when state-toggling works. Don't trust DECRQM on macOS Terminal for feature detection — use behavioural probes instead. **DECRQM + DECRPM vs DECRQSS** (see `dcs-decrqss`): DECRQM is the *generic mode-state* query — yes/no/permaset/permareset/unknown. DECRQSS returns the actual *value* of a settable resource (SGR stack, DECSCUSR shape, DECSTBM rows/cols, DECSCL conformance level). Use DECRQM for 'is bracketed paste on?', DECRQSS for 'which cursor shape is currently active?'. A useful TUI startup probe usually fires both: DECRQM to discover capabilities + DECRQSS to snapshot resources for later restore.
Spec citation: xterm-ctlseqs (DECRPM, CSI ? Ps ; Pm $ y / CSI Ps ; Pm $ y)
Parameters
| Ps | Echoes the mode number from the originating DECRQM query. Match the reply's Ps to your request to disambiguate when multiple probes are in flight. |
| Pm | 0 unrecognised, 1 set, 2 reset, 3 permanently set (hardwired on), 4 permanently reset (hardwired off). |
Examples
# Probe bracketed-paste; expect ?2004;1$y if on, ?2004;2$y if off.\nprintf '\\033[?2004$p'\nIFS= read -rs -d y -t 0.1 reply </dev/tty 2>/dev/null\necho \"DECRPM: ${reply#$'\\033'}y\"import sys, re, termios, tty, select\n\ndef probe(ps, private=True, timeout=0.1):\n prefix = '?' if private else ''\n fd = sys.stdin.fileno(); old = termios.tcgetattr(fd)\n try:\n tty.setcbreak(fd)\n sys.stdout.write(f'\\x1b[{prefix}{ps}$p'); sys.stdout.flush()\n buf = ''\n while True:\n r,_,_ = select.select([fd], [], [], timeout)\n if not r: break\n buf += sys.stdin.read(1)\n if buf.endswith('y'): break\n finally:\n termios.tcsetattr(fd, termios.TCSADRAIN, old)\n m = re.match(rf'\\x1b\\[{re.escape(prefix)}(\\d+);(\\d+)\\$y', buf)\n if not m or int(m.group(1)) != ps: return 'no-reply'\n return ['unknown','set','reset','permaset','permareset'][int(m.group(2))]\n\nprint(probe(2004))// After DECRQM probe, switch on the Pm enum.\nswitch pm {\ncase 0: cap[ps] = capUnknown // not recognised\ncase 1: cap[ps] = capOn // currently set\ncase 2: cap[ps] = capOff // currently reset\ncase 3: cap[ps] = capHardwiredOn // can't toggle off — skip restore\ncase 4: cap[ps] = capHardwiredOff // can't toggle on — skip enable\n}// Node parser — match DECRPM reply, return {ps, state}.\nfunction parseDECRPM(buf) {\n const m = /\\x1b\\[(\\??)(\\d+);(\\d+)\\$y/.exec(buf);\n if (!m) return null;\n return {\n private: m[1] === '?',\n ps: Number(m[2]),\n state: ['unknown','set','reset','permaset','permareset'][Number(m[3])] ?? 'invalid',\n };\n}/* Skeleton — emit DECRQM for ?1006 (SGR mouse), parse DECRPM Pm. */\nfprintf(stdout, \"\\x1b[?1006$p\"); fflush(stdout);\n/* read until 'y'; then sscanf buf, \"\\x1b[?%d;%d$y\", &ps, &pm) */\nint ps = 0, pm = 0;\n/* ...read loop... */\nif (ps == 1006) {\n static const char *st[] = {\"unknown\",\"set\",\"reset\",\"permaset\",\"permareset\"};\n printf(\"SGR mouse: %s\\n\", (pm>=0 && pm<=4) ? st[pm] : \"invalid\");\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
- partial
- Konsole
- yes
- tmux
- no
- GNU screen
- no
| 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 | partial | yes | no | no |