DECSET ?1000 / ?1006 — 鼠标跟踪
以转义序列形式接收鼠标点击 / 拖拽 / 滚轮事件。
字节形式
涵盖所有常见的字符串字面量写法,方便正反查找。
\x1b[?1000h (click only) \x1b[?1002h (cell drag) \x1b[?1003h (any motion) \x1b[?1006h (SGR encoding)\033[?1000h …\e[?1000h …ESC [ ? <N> h / l1b 5b 3f ... 68 / 6c说明
现代终端的鼠标上报分层:*目标*模式选择终端要上报哪些事件,*编码*模式选择字节如何成帧。目标模式互斥:?1000 只报告按下 + 释放(最早的「X10 兼容」模式),?1002 在按键保持期间额外报告拖拽,?1003 无论按键状态都报告所有光标移动。编码模式叠在其上:?1005(UTF-8 —— 已弃用,多字节 locale 下损坏)、?1015(urxvt —— 整数 CSI 形式,今天较少见)、?1006(SGR —— 事实上的现代标准,仅 ASCII,无 223 列限制,xterm 268 起被 xterm-ctlseqs 推荐)。不指定编码模式时使用「X10」遗留成帧:\x1b[M 后跟 3 字节,列与行以偏移 32 的字节值编码,可寻址单元上限为列 / 行 223 —— 对 80×24 的 VT 没问题,但任何超过 223 单元的现代终端都会损坏。可移植启用是 \x1b[?1000h\x1b[?1006h(点击 + SGR)或 \x1b[?1002h\x1b[?1006h(点击 + 拖拽 + SGR)。
mouse-tracking-protocols 这条 pitfall 提醒:即便到今天,?1000 与 ?1006 的区别仍会绊倒解析器实现 —— 在 kitty / wezterm / Ghostty 上发了 ?1000h 却没同时发 ?1006h 的 TUI 会收到 223-字节偏移 帧里的坐标,并把 224+ 列误解析成控制字符。Linux console / fbcon 通过 gpm 实现鼠标上报(矩阵中标 partial —— 仅在 gpm 在运行时工作,这在现代发行版上少见)。ConPTY 1809+ 转发鼠标事件,旧 Windows cmd.exe 不转发。tmux 透传鼠标事件,但要注意:tmux set -g mouse on 在 tmux 层 *截获* 鼠标事件(用于 pane 选择 / 调整),再决定是否转发给应用 —— tmux 内的应用只能看到 tmux 选择转发的部分;tmux set -g mouse off 恢复原样转发。GNU screen 4.x 与 5.0 完全 *阻断* 鼠标事件(多路复用器层无转发 —— 即 lib/terminals.ts 中记录的 screen-strips-mouse 怪癖)。Apple Terminal.app 支持 ?1000 / ?1006,但即便 ?1003 开启,点击事件也只发单元坐标而非像素坐标。
配对关闭按模式分发:目标模式用 \x1b[?1000l / \x1b[?1002l / \x1b[?1003l,SGR 编码用 \x1b[?1006l。忘记清理的典型症状是「shell 在光标每次移动时刷屏一堆 ^[[<35;42;7M」—— 用 reset 或显式发四个 l 开关复原。健壮模式与括号粘贴相同:每次启用配对一个清理块里的关闭,或用 XTSAVE / XTRESTORE(\x1b[?1000s / \x1b[?1000r)在退出时恢复父进程状态。鼠标跟踪事件在终端窗口失焦时 *不会* 产生 —— 如果 TUI 需要感知焦点边界,搭配 dec-focus-events(?1004)。相关:dec-bracketed-paste(同样的启用 / 关闭纪律)、alt-screen(进入 alt-screen 的 TUI 通常在同一原子块里启用鼠标)、dec-focus-events(焦点上报,互补的输入事件流)。
规范出处: xterm-ctlseqs (Mouse Tracking)
示例
printf '\033[?1000h\033[?1006h' # enable click+SGR\nprintf '\033[?1000l\033[?1006l' # disableimport sys; sys.stdout.write('\x1b[?1000h\x1b[?1006h')fmt.Print("\x1b[?1000h\x1b[?1006h")process.stdout.write('\x1b[?1000h\x1b[?1006h')printf("\x1b[?1000h\x1b[?1006h");在哪里用到
实际会发出该序列的工具——把抽象字节锚定到你已经用过的命令上。
- tmux`set -g mouse on` 开启 `?1000h` + `?1006h`(SGR 编码),使鼠标可调整窗格和翻动历史
- vim, neovim`set mouse=a`——点击定位光标、拖拽 visual 选区、滚轮翻页
- less -R --mouse滚轮翻页,长文件可点击设标记
- htop, btop — click on column headers to sort
- fzf --mouse (preview pane scroll + select)
常见问题
针对这条序列,开发者真正会去搜索的问题的简短回答。
- 为什么我的鼠标坐标解析过 224 列就崩了?
- 你卡在了 X10 旧编码上(没开
?1006h)。X10 把行 / 列各打包成偏移 32(ASCII 空格)的单字节,因此可寻址单元上限是行 / 列 223——过界字节进入高位不可打印范围,解析器失同步。在目标模式旁同时启用 SGR 鼠标编码:\x1b[?1000h\x1b[?1006h(点击 + SGR)或\x1b[?1002h\x1b[?1006h(拖拽 + SGR)。SGR 编码把坐标写成十进制 CSI 参数——没有逐字节上限。 - 为什么我发了
\x1b[?1000h之后还是收不到鼠标事件? - 三个常见嫌疑:(1) stdin 处于熟模式——内核等到换行才交字节;用
termios.tcsetattr(... 清除 ICANON)或stty -icanon -echo切到原始模式。(2) 你的程序和终端之间有多路复用器拦截——tmux 需要set -g mouse on转发,screen 需要mousetrack on。(3) 终端自己关了鼠标报告——iTerm2 *Preferences → Pointer → Reporting* 默认开但可按 profile 切。
终端支持
- 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 部分 | 支持 | 支持 | 支持 | 部分 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 部分 | 不支持 |