Skip to main content
ansicode

DECSET ?1000 / ?1006 — Mouse tracking

Receive mouse click / drag / scroll events as escape sequences.

Byte forms

Every common string-literal form so you can paste-and-search either direction.

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

Description

Mouse reporting in modern terminals is layered: a *target* mode selects which events the terminal will report, and an *encoding* mode selects how the bytes are framed. Target modes are mutually exclusive: ?1000 reports button-press + release only (the original "X10 compatibility" mode), ?1002 adds drag events while any button is held, ?1003 reports every cursor motion regardless of button state. Encoding modes layer on top: ?1005 (UTF-8 — deprecated, broken under multi-byte locales), ?1015 (urxvt — an integer-CSI form, less common today), and ?1006 (SGR — the de-facto modern standard, ASCII-only, no 223-column limit, recommended by xterm-ctlseqs since xterm 268). Without an encoding mode the legacy "X10" framing applies: 3 bytes after \x1b[M, with column and row encoded as byte values offset by 32, capping addressable cells at column / row 223 — fine for an 80×24 VT but broken on any modern terminal larger than 223 cells. The portable enable is \x1b[?1000h\x1b[?1006h (click + SGR) or \x1b[?1002h\x1b[?1006h (click + drag + SGR).

The mouse-tracking-protocols pitfall warns that the ?1000 vs ?1006 distinction trips up parser implementations even today — a TUI that emits ?1000h without also emitting ?1006h on a kitty / wezterm / Ghostty terminal will receive coordinates in the legacy 223-byte-offset frame and then mis-interpret columns 224+ as control characters. Linux console / fbcon implements mouse reporting via gpm (partial in the matrix — works only if gpm is running, which is rare on modern distros). ConPTY 1809+ forwards mouse events but legacy Windows cmd.exe does not. tmux passes mouse events through with the caveat that tmux set -g mouse on *captures* mouse events at the tmux layer (for pane select / resize) before forwarding — applications inside tmux see only events tmux chooses to pass; tmux set -g mouse off restores raw forwarding. GNU screen 4.x and 5.0 BLOCK mouse events entirely (no passthrough at the multiplexer layer — the screen-strips-mouse quirk documented in lib/terminals.ts). Apple Terminal.app supports ?1000 / ?1006 but emits cell coordinates rather than pixel coordinates for click events even when ?1003 is on.

The matching close is per-mode: \x1b[?1000l / \x1b[?1002l / \x1b[?1003l for target modes, \x1b[?1006l for SGR encoding. Forgotten cleanup is the classic "shell now spams ^[[<35;42;7M on every cursor move" symptom — fix with reset or by explicitly running all four l toggles. The robust pattern is the same as for bracketed paste: pair every enable with a cleanup-block disable, or use XTSAVE / XTRESTORE (\x1b[?1000s / \x1b[?1000r) to restore parent state on exit. Mouse-tracking events are *not* generated when the terminal window is unfocused — pair with dec-focus-events (?1004) if your TUI needs to know about focus boundaries. Related: dec-bracketed-paste (the same enable / close discipline applies), alt-screen (TUIs entering alt-screen typically enable mouse in the same atomic block), dec-focus-events (focus reporting, the complementary input-event stream).

Spec citation: xterm-ctlseqs (Mouse Tracking)

Examples

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");

Used in

Real-world tools that emit this sequence — anchors the bytes to commands you've already used.

  • tmux`set -g mouse on` enables `?1000h` + `?1006h` (SGR-encoded) so pane resize / scrollback works with the mouse
  • vim, neovim`set mouse=a` — click to position cursor, drag to visual-select, scroll wheel scrolls buffer
  • less -R --mousescrollback via wheel, click-to-set-mark on long files
  • htop, btop — click on column headers to sort
  • fzf --mouse (preview pane scroll + select)

Frequently asked

Short answers to the questions developers actually search for this sequence.

Why does my mouse-coord parsing break past column 224?
You're stuck on the legacy X10 encoding (no ?1006h enabled). X10 packs row and column into single bytes offset by 32 (ASCII space), which caps addressable cells at row / column 223 — past that the byte hits the high-bit non-printable range and parsers desync. Enable SGR mouse encoding alongside your target mode: \x1b[?1000h\x1b[?1006h (click + SGR) or \x1b[?1002h\x1b[?1006h (drag + SGR). SGR encoding frames coordinates as decimal CSI parameters — no per-byte limit.
Why don't mouse events arrive even after I sent \x1b[?1000h?
Three usual suspects: (1) stdin is in cooked mode — the kernel buffers until newline before delivering bytes; switch to raw via termios.tcsetattr(... ICANON cleared) or stty -icanon -echo. (2) A multiplexer between your app and the terminal is intercepting — tmux needs set -g mouse on to forward, screen needs mousetrack on. (3) The terminal itself disables mouse reporting in settings — iTerm2 *Preferences → Pointer → Reporting* is on by default but can be toggled off per profile.

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
partial
GNU screen
no

Related sequences

In the family cookbook

DEC cookbook · 5. Mouse tracking — `?1000` + `?1006`