跳到主要内容
ansicode

SGR / urxvt 鼠标编码 — 鼠标上报的线缆格式(CSI M / CSI < / CSI)

终端上报鼠标事件的三种线缆格式:传统 CSI M Cb Cx Cy、现代 SGR ?1006、urxvt ?1015 —— DECSET ?100x 启用追踪后,输入流里到底出现什么。

字节形式

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

\\x1b[\x1b[M<Cb><Cx><Cy> (legacy) \x1b[<<Cb>;<Cx>;<Cy>M|m (SGR ?1006) \x1b[<Cb>;<Cx>;<Cy>M (urxvt ?1015)
\\033[\033[M ! ! !
\\e[\e[<0;5;5M
ESC [ESC [ M Cb Cx Cy / ESC [ < Cb ; Cx ; Cy M|m / ESC [ Cb ; Cx ; Cy M
hex1b 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 十进制(无上限)。

示例

bash
# 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
python
# 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)
go
// 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
javascript
// 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)
c
/* 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
不支持

相关序列