Skip to main content
ansicode

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[\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

Description

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

CbButton + modifier bitmask. Legacy/urxvt: byte = value+32. SGR (1006): ASCII decimal, no offset.
Cx, Cy1-based column / row. Legacy: single byte value+32 (caps at column 223). SGR/urxvt: ASCII decimal (no limit).

Examples

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}

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

Related sequences