DECRPM 解码 —— 解析 DECRQM 回复(`CSI ? Ps ; Pm $ y`)
解析 DECRQM 回复 CSI ? Ps ; Pm $ y(以及 CSI Ps ; Pm $ y)—— Pm 数值说明该模式当前是 设置 / 重置 / 永久开 / 永久关 / 不识别 中的哪一种。
字节形式
涵盖所有常见的字符串字面量写法,方便正反查找。
\x1b[?<Ps>;<Pm>$y (private) \x1b[<Ps>;<Pm>$y (ANSI)\033[?2004;1$y\e[?2004;1$yESC [ ? Ps ; Pm $ y1b 5b 3f ... 24 79说明
DECRPM —— Report Private Mode(私有式)或 Report Mode(ANSI 式)—— 是终端对 DECRQM 查询(私有模式 CSI ? Ps $ p、ANSI 模式 CSI Ps $ p,见 decrqm)的回复。回复回显被查询的 Ps,再加一个 Pm 值,其小型枚举精确表达终端对该模式的认识。
Pm 取值:
- 0 —— *不识别该模式*。视为不支持。别再探测同一 Ps;按会话缓存为「不支持」。
- 1 —— *当前为「设置」*。等效于最近一次执行了 \x1b[?<Ps>h(ANSI 式则 \x1b[<Ps>h)。
- 2 —— *当前为「重置」*。等效于最近一次执行了 \x1b[?<Ps>l(ANSI 式则 \x1b[<Ps>l)。
- 3 —— *永久设置*。终端硬编码为开;发 \x1b[?<Ps>l 是空操作。
- 4 —— *永久重置*。终端硬编码为关;发 \x1b[?<Ps>h 是空操作。
回复中的 ? 与请求中的 ? 镜像 —— 查询私有模式(如 ?2004 括号粘贴、?1000 鼠标)得 \x1b[?2004;Pm$y;查询 ANSI 模式(如 4 IRM、20 LNM)得 \x1b[4;Pm$y,*无* ?。消费者必须按请求形态原样匹配 —— 交错探测私有 / ANSI 时回复会被错路由。
推荐消费者模式(Python 风伪码): ``` 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))] ```
「永久设置 / 永久重置」的真实含义:终端用 Pm=3 / Pm=4 表达「该特性开 / 关且无法用转义码切换」。例如:
- 一直常开括号粘贴的终端(部分用户通过 xterm X 资源配置)对 ?2004 回 Pm=3。
- 旧 Linux console 对 ?1000(鼠标追踪)回 Pm=4,因内核 vt 驱动从未实现。
- ConPTY 对多数 DEC 私有模式(如 ?9 X10 鼠标、?1015 urxvt 鼠标)回 Pm=4,因 conhost 翻译层把它们写死为关。
读取目的下把 Pm=3 / Pm=4 等同 Pm=1 / Pm=2 —— 模式确在该状态 —— 但别再发会被静默忽略的 h/l。
厂商兼容性:
- xterm、kitty、iTerm2、WezTerm、Ghostty、Konsole:完全合规 —— 五种 Pm 都能正确返回。
- Alacritty:仅实现 Pm ∈ {0, 1, 2} —— 永不返回 3 / 4。依赖 permaset/permareset 分支的工具需把「缺失」视作「未知但可能支持」。
- Linux console(vt):只对常见模式(?1、?7、?25)回;其余完全静默(无回复 —— 务必加超时)。
- Windows Terminal:1.20 起完全合规;早期构建会对部分实际支持的模式回 Pm=0。
- cmd.exe / ConPTY:受限 —— 多数探测即使被支持也回 Pm=0。
- macOS Terminal:部分实现;部分模式即便能 h/l 切换也回 Pm=0 而不是 Pm=1 / Pm=2。在 macOS Terminal 上别信 DECRQM 做特性检测 —— 改用行为探测。
DECRQM + DECRPM vs DECRQSS(见 dcs-decrqss):DECRQM 是*通用模式状态*查询 —— 开 / 关 / 永久开 / 永久关 / 未知。DECRQSS 返回可设置资源的真实*值*(SGR 栈、DECSCUSR 形状、DECSTBM 行列、DECSCL 一致性级别)。「括号粘贴开了吗」用 DECRQM;「当前光标是什么形状」用 DECRQSS。TUI 启动探测通常两者都发:DECRQM 发现能力,DECRQSS 快照资源以便退出时恢复。
规范出处: xterm-ctlseqs (DECRPM, CSI ? Ps ; Pm $ y / CSI Ps ; Pm $ y)
参数
| Ps | 回显发起的 DECRQM 查询的模式号。若同时发出多个探测,按回复 Ps 与请求匹配以消歧。 |
| Pm | 0 不识别、1 设置、2 重置、3 永久设置(硬编码开)、4 永久重置(硬编码关)。 |
示例
# 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}终端支持
- xterm
- 支持
- Linux console (fbcon)
- 部分
- macOS Terminal.app
- 部分
- iTerm2
- 支持
- Windows Terminal
- 支持
- cmd.exe / ConPTY
- 部分
- kitty
- 支持
- alacritty
- 部分
- WezTerm
- 支持
- Ghostty
- 支持
- GNOME Terminal
- 部分
- Konsole
- 支持
- tmux
- 支持
- GNU screen
- 支持
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 部分 | 部分 | 支持 | 支持 | 部分 | 支持 | 部分 | 支持 | 支持 | 部分 | 支持 | 支持 | 支持 |