SGR / urxvt mouse encoding — Mouse-report wire formats (CSI M / CSI < / CSI)
The three on-wire formats a terminal uses to report mouse events: legacy CSI M Cb Cx Cy, modern SGR ?1006, and urxvt ?1015 — what's actually in the input stream after DECSET ?100x enables tracking.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\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 ... 4dDescription
Enabling mouse tracking via DECSET (`?1000`/`?1002`/`?1003` — see `dec-mouse-tracking`) is only half the story; you also need to know what format the events arrive in. **Legacy X10/X11** (`\x1b[M<Cb><Cx><Cy>`) is the default: a fixed 6-byte sequence where each of `Cb`, `Cx`, `Cy` is a **single byte** computed as `value + 32` (offset to avoid C0 collisions). Button info is encoded in `Cb`: low 2 bits = button index (0/1/2 = left/middle/right, 3 = release), bit 5 = motion, bit 4 = control, bit 3 = alt, bit 2 = shift, bits 6+7 = wheel/extra buttons. The killer flaw: `Cx`/`Cy` saturate at 223 (255−32) — beyond column 223 they break. **SGR encoding** (`?1006`) fixes this by emitting `\x1b[<<Cb>;<Cx>;<Cy>M` for press and `m` for release — ASCII decimal numbers, no saturation, M/m disambiguates press from release on the same button (legacy used Cb=3 for any release, losing which button was let go). **urxvt encoding** (`?1015`) is an interim variant: `\x1b[<Cb+32>;<Cx>;<Cy>M` — decimal Cx/Cy (no 223 limit) but the legacy `+32` on Cb is preserved and there's no M/m differentiation. SGR (1006) is the only modern choice — kitty, alacritty, wezterm, ghostty, Windows Terminal, iTerm2 all emit it preferentially. Parser code should accept all three formats but write greenfield code targeting only 1006.
Spec citation: xterm-ctlseqs (Mouse Tracking)
Parameters
| Cb | Button + modifier bitmask. Legacy/urxvt: byte = value+32. SGR (1006): ASCII decimal, no offset. |
| Cx, Cy | 1-based column / row. Legacy: single byte value+32 (caps at column 223). SGR/urxvt: ASCII decimal (no limit). |
Examples
# 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}Terminal support
- xterm
- yes
- Linux console (fbcon)
- partial
- macOS Terminal.app
- yes
- iTerm2
- yes
- Windows Terminal
- yes
- cmd.exe / ConPTY
- partial
- kitty
- yes
- alacritty
- yes
- WezTerm
- yes
- Ghostty
- yes
- GNOME Terminal
- yes
- Konsole
- yes
- tmux
- no
- GNU screen
- no
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | partial | yes | yes | yes | partial | yes | yes | yes | yes | yes | yes | no | no |