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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 部分 | 部分 | 支持 | 支持 | 部分 | 支持 | 部分 | 支持 | 支持 | 部分 | 支持 | 不支持 | 不支持 |