跳到主要内容
ansicode

DECSET ?1000 / ?1006 — 鼠标跟踪

以转义序列形式接收鼠标点击 / 拖拽 / 滚轮事件。

字节形式

涵盖所有常见的字符串字面量写法,方便正反查找。

\\x1b[\x1b[?1000h (click only) \x1b[?1002h (cell drag) \x1b[?1003h (any motion) \x1b[?1006h (SGR encoding)
\\033[\033[?1000h …
\\e[\e[?1000h …
ESC [ESC [ ? <N> h / l
hex1b 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)

示例

bash
printf '\033[?1000h\033[?1006h'  # enable click+SGR\nprintf '\033[?1000l\033[?1006l'  # disable
python
import sys; sys.stdout.write('\x1b[?1000h\x1b[?1006h')
go
fmt.Print("\x1b[?1000h\x1b[?1006h")
javascript
process.stdout.write('\x1b[?1000h\x1b[?1006h')
c
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
不支持

相关序列

在家族食谱中

DEC 食谱 · 5. 鼠标跟踪 —— `?1000` + `?1006`