SGR / urxvt 鼠标编码 — 鼠标上报的线缆格式(CSI M / CSI < / CSI)
终端上报鼠标事件的三种线缆格式:传统 CSI M Cb Cx Cy、现代 SGR ?1006、urxvt ?1015 —— DECSET ?100x 启用追踪后,输入流里到底出现什么。
字节形式
涵盖所有常见的字符串字面量写法,方便正反查找。
\x1b[M<Cb><Cx><Cy> (legacy) \x1b[<<Cb>;<Cx>;<Cy>M|m (SGR ?1006) \x1b[<Cb>;<Cx>;<Cy>M (urxvt ?1015)\033[M ! ! !\e[<0;5;5MESC [ M Cb Cx Cy / ESC [ < Cb ; Cx ; Cy M|m / ESC [ Cb ; Cx ; Cy M1b 5b 4d ... / 1b 5b 3c ... 4d|6d / 1b 5b ... 4d说明
通过 DECSET(`?1000` / `?1002` / `?1003` —— 参见 `dec-mouse-tracking`)启用鼠标追踪只是一半,还要知道事件以什么格式抵达。**传统 X10/X11**(`\x1b[M<Cb><Cx><Cy>`)是默认值:固定 6 字节序列,每个 `Cb`、`Cx`、`Cy` 都是**单字节**,按 `值 + 32` 编码(偏移以避开 C0 字节)。按键信息在 `Cb` 里:低 2 位 = 按键索引(0 / 1 / 2 = 左 / 中 / 右,3 = 释放),第 5 位 = 移动,第 4 位 = Ctrl,第 3 位 = Alt,第 2 位 = Shift,第 6 / 7 位 = 滚轮 / 扩展键。致命缺陷:`Cx` / `Cy` 在 223(255−32)饱和 —— 223 列以外的事件无法上报。**SGR 编码**(`?1006`)用 `\x1b[<<Cb>;<Cx>;<Cy>M` 表示按下、`m` 表示释放修复此问题 —— ASCII 十进制数字,无饱和,M / m 区分按下与释放同一按键(传统格式释放都是 Cb=3,丢失了具体哪个键被松开)。**urxvt 编码**(`?1015`)是过渡变体:`\x1b[<Cb+32>;<Cx>;<Cy>M` —— Cx / Cy 十进制(无 223 限制),但 Cb 保留传统 `+32`,且不区分 M / m。SGR(1006)是现代唯一选择 —— kitty、alacritty、wezterm、ghostty、Windows Terminal、iTerm2 全都优先输出。解析器应同时接受三种格式,新代码只用 1006。
规范出处: xterm-ctlseqs (Mouse Tracking)
参数
| Cb | 按键 + 修饰位掩码。传统 / urxvt:字节 = 值 + 32。SGR(1006):ASCII 十进制,无偏移。 |
| Cx, Cy | 从 1 起算的列 / 行。传统:单字节值 + 32(列上限 223)。SGR / urxvt:ASCII 十进制(无上限)。 |
示例
# Show what arrives on stdin after enabling SGR mouse:\nprintf '\033[?1000h\033[?1006h' # enable\ncat | xxd # click → \x1b[<0;5;3M release → \x1b[<0;5;3m# Decode an SGR mouse report into (button, x, y, press/release):\nimport re\nm = re.match(r'\x1b\\[<(\\d+);(\\d+);(\\d+)([Mm])', '\x1b[<0;25;12M')\nbtn, x, y, kind = int(m[1]), int(m[2]), int(m[3]), m[4] # 0, 25, 12, 'M' (press)// Match SGR mouse report: \\x1b\\[<(\\d+);(\\d+);(\\d+)([Mm])\nre := regexp.MustCompile(`\\x1b\\[<(\\d+);(\\d+);(\\d+)([Mm])`)\nm := re.FindStringSubmatch("\x1b[<0;5;3M") // m[4] == "M" means press// Distinguish press vs release on the same button (impossible in legacy):\nconst m = '\x1b[<2;10;20m'.match(/\\x1b\\[<(\\d+);(\\d+);(\\d+)([Mm])/);\nconst released = m[4] === 'm'; // release of right button at (10,20)/* Detect column-223 saturation on legacy format: */\nif (cb_byte >= 32 && (cx_byte == 255 || cy_byte == 255)) {\n /* event beyond column 223 — request SGR encoding instead */\n printf("\x1b[?1006h");\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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 部分 | 支持 | 支持 | 支持 | 部分 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 不支持 | 不支持 |