DEC 私有模式 —— 备用屏幕、光标、鼠标、焦点
DEC 私有模式 set / reset。`ESC [ ? n h` 与 `ESC [ ? n l` 家族 —— 备用屏幕缓冲、光标可见性、括号粘贴、焦点事件、鼠标追踪、应用小键盘、自动换行等。最初是 DEC VT 系列的扩展,已被 xterm 系全部终端普遍采纳。
17 条序列
模式食谱 —— 六个 DEC 开关搭出一个 TUI
DEC 私有模式是每一个全屏 TUI 的底层机制 —— vim、less、tmux、htop、fzf 全都跑在这套方言里。下面六步:先认 ? 前缀语法(私有模式与 ECMA-48 标准模式靠它分开);再是 alt-screen 这块基石;接着是重绘期间的光标显隐;之后是括号粘贴让你能区分键入与 Cmd-V;然后用 SGR 扩展编码做鼠标跟踪;最后用焦点事件 + 同步更新做不闪屏的实时 UI。
1.
?前缀 —— DECSET 与 SM 的分水岭这一族每个码都在参数前面带一个
?——\x1b[?25h显示光标,\x1b[?25l隐藏。?是把序列归为 **DEC 私有模式**(DECSET / DECRST)而不是 ECMA-48 公有模式(SM / RM,不带?)的关键。同一个数字不带?要么完全无效,要么撞上完全无关的模式 ——\x1b[4h打开插入模式(IRM),跟?4(DECSCLM 平滑滚动)八竿子打不到一起。结尾字节h表示**打开**,l表示**关闭**。多个模式可以用;合并:\x1b[?25;1049h一条同时隐藏光标 + 切到 alt-screen。2. 备用屏 ——
\x1b[?1049h/l切换到备用屏是 TUI 最经典的一招 —— vim、less、tmux、htop 启动时都发
\x1b[?1049h,退出时发\x1b[?1049l。1049 这个变种一次做三件事:保存光标位置、切到独立屏幕缓冲区、清空那个缓冲。退出(l)会恢复光标 + 切回原主屏,所以用户原来的 shell 滚动历史保持原样 —— TUI 画的东西完全不会泄漏到滚动日志里。老的?1047只切缓冲不保存光标,最原始的?47甚至不清空备用缓冲 —— 2000 年前的终端只有?47,现代 TUI 一律用?1049。崩溃时漏发结尾的l会让用户卡在半截 vim 画面里出不来 —— 务必装上 SIGINT / SIGTERM 处理器,在退出前补发一次。3. 光标显隐与闪烁 ——
?25、?12重绘屏幕之前先用
\x1b[?25l隐藏光标,画完再用\x1b[?25h显示 —— 否则光标会跟着写入位置满屏跳,看起来像闪屏 bug。闪烁属性是独立的另一个开关:\x1b[?12h开启闪烁、\x1b[?12l关闭,跟?25**互不影响**。所以「隐藏 + 不闪烁」就是\x1b[?25l\x1b[?12l;代码编辑器常要的「可见但稳定不闪」是\x1b[?25h\x1b[?12l。DECTCEM(?25)在所有现代终端通用;?12闪烁开关 xterm、kitty、alacritty、wezterm、iTerm2、ghostty 都支持,但 Windows Terminal 1.20 之前的版本和 Linux console 都忽略。要改光标 *形状*(方块 / 下划线 / 竖线)用 DECSCUSR\x1b[N q—— 那是 CSI 原语,不是 DEC 私有模式。4. 括号粘贴 ——
\x1b[?2004h没有括号粘贴的话,你的 shell 或编辑器根本分不清
cat | sudo rm -rf /是用户一个字一个字键入的,还是一整段 Cmd-V 粘进来的 —— 字节流上两者一模一样。发\x1b[?2004h之后终端开始给粘贴内容包标记:前面加\x1b[200~,后面加\x1b[201~。这样你的输入循环就能识别:夹在两个标记之间的全是剪贴板来的,别自动缩进、别遇到\n就执行、里面的Ctrl-C也别解读。Bash 4.4+、zsh、fish、vim、neovim、emacs、所有基于 readline 的 REPL 默认都启用。最容易踩的坑是退出时忘记发\x1b[?2004l—— 如果你的 TUI 崩溃时把括号粘贴留在打开状态,用户在 shell 里每次粘贴前都会先看到字面的^[[200~,得敲reset(1)才救得回来。5. 鼠标跟踪 ——
?1000+?1006用
\x1b[?1000h打开点击事件(X10 / VT200 格式)—— 终端把每次按下报作\x1b[Mbxy,其中b、x、y是单字节(值为x + 32、y + 32)。坑在这里:单字节坐标意味着第 224 列以后就会回卷,你的 TUI 会以为用户点了第 1 列。**?1000h一定要配?1006h** 切到 SGR 扩展编码 —— 上报变成\x1b[<b;x;yM(按下)/\x1b[<b;x;ym(释放),用十进制整数,没有上限。再加?1002h可同时接收按住时的拖拽事件;?1003h接收 *所有* 移动事件(开销大 —— 每移动一像素都触发)。各项关闭用对应的l。退出时务必发\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l把用户终端恢复原状,否则用户在 shell 里每次点击都会看到一堆字面 escape 码。6. 焦点事件与同步更新 ——
?1004、?2026两个让 live UI 更稳的便利码。
\x1b[?1004h打开焦点上报 —— 窗口获得键盘焦点时终端发\x1b[I,失去焦点时发\x1b[O。可以在用户切去别的窗口时暂停 live tail 或时钟刷新,回来时再恢复 —— htop、fzf、tig、lazygit 都这么做。\x1b[?2026h是更新的同步更新模式(kitty / iTerm2 / wezterm / ghostty / foot / 2022 年以来基于 VTE 的终端都支持):?2026h与?2026l之间写入的内容全部先在终端侧缓冲,然后一次原子提交到屏幕 —— 不会出现画到一半的帧,全屏重绘也不闪。流程是?2026h→ 发出本帧所有光标移动与单元格更新 →?2026l。不识别这个模式的终端会静默忽略(写入仍然顺序到达,只是没了原子保证),所以可以无条件发出,不必先做能力探测。
本家族的全部序列
- DECSC / DECRC — 保存与恢复光标
\x1b7 (save) \x1b8 (restore)保存并恢复光标状态(位置 + 属性)。
- DECSET 1049 — 备用屏幕缓冲
\x1b[?1049h (enter) \x1b[?1049l (leave)切换到独立屏幕缓冲区(vim/less 启动时使用)。
- DECTCEM ?25 — 显示/隐藏光标
\x1b[?25h (show) \x1b[?25l (hide)显示或隐藏文本光标。
- DECSET ?2004 — 括号粘贴模式
\x1b[?2004h (enable) \x1b[?2004l (disable)将粘贴文本用独立的转义标记包裹,让应用区分粘贴与键入。
- DECSET ?1000 / ?1006 — 鼠标跟踪
\x1b[?1000h (click only) \x1b[?1002h (cell drag) \x1b[?1003h (any motion) \x1b[?1006h (SGR encoding)以转义序列形式接收鼠标点击 / 拖拽 / 滚轮事件。
- DECAWM ?7 — 自动换行模式
\x1b[?7h (enable wrap) \x1b[?7l (disable)切换光标到达右边距时是否自动换到下一行(默认开启)。
- DECSET ?1004 — 焦点进出事件
\x1b[?1004h (enable) \x1b[?1004l (disable)让终端在窗口获得或失去键盘焦点时上报事件。
- DECSET ?2026 — 同步更新模式
\x1b[?2026h (begin frame) \x1b[?2026l (end frame)在帧结束信号到来前缓冲屏幕更新 —— 全屏重绘时消除闪烁。
- DECSET ?12 — 光标闪烁
\x1b[?12h (start blinking) \x1b[?12l (stop blinking)启用或关闭光标闪烁属性 —— 与光标形状无关。
- DECOM ?6 — 起点模式(把光标定位限制在滚动区域内)
\x1b[?6h (origin = region) \x1b[?6l (origin = screen)让光标的行列原点 (1,1) 变为 DECSTBM 滚动区域的左上角,而非整屏左上角。
- DECSET ?1047 — 仅切换备用屏(不保存光标)
\x1b[?1047h (enter alt) \x1b[?1047l (leave alt)切换备用屏但不保存光标 —— 比 ?1049 更原始的形式。
- DECSDM — Sixel 显示模式(CSI ? 80 h / l)
\x1b[?80h (set) \x1b[?80l (reset)选择 sixel 渲染完成后光标的停留位置 —— 图像左上角(设置)或图像下方(重置,现代默认)。
- DECSCNM — 反向视频屏幕模式(CSI ? 5 h / l)
\x1b[?5h (reverse) \x1b[?5l (normal)在整屏范围内全局交换前景色与背景色 —— 全屏的反向视频开关,与逐格 SGR 7 不同。
- DECSCLM — 平滑滚动模式(CSI ? 4 h / l)
\x1b[?4h (smooth) \x1b[?4l (jump)在平滑(逐帧一行的动画)与跳跃(瞬时)滚动间切换 —— DEC VT100 硬件时代的设置,现代模拟器几乎全部忽略。
- DECARM — 按键自动重复模式(CSI ? 8 h / l)
\x1b[?8h (repeat) \x1b[?8l (no repeat)切换终端在按住按键时是否重复发送字节 —— 对游戏与编辑器很重要,按住 `j` 不应狂刷缓冲。
- DECCOLM — 80 / 132 列模式(CSI ? 3 h / l)
\x1b[?3h (132 cols) \x1b[?3l (80 cols)在 80 列与 132 列页宽间切换 —— DEC VT100 旗舰「宽模式」开关,现代模拟器普遍以资源开关默认禁用。
- DECNCSM —— 切列宽时不清屏(`CSI ? 95 h / l`)
\x1b[?95h (set — preserve) \x1b[?95l (reset — clear)抑制 DECCOLM(及 DECSCPP)切列宽时隐含的清屏副作用。DECNCSM 开启后,在 80 / 132 列之间切换会保留单元内容,而非清空。