# Mouse Tracking (?9, ?1000 to ?1003, ?1005 to ?1007, ?1015, ?1016)

> Ask the terminal to report mouse buttons and movement, and choose how the reports are encoded.

- **Sequence:** `CSI ? 1000 h/l`
- **?9**, X10 Mouse: `CSI ? 9 h/l`
- **?1001**, Highlight Mouse Tracking: `CSI ? 1001 h/l`
- **?1002**, Button-Event Mouse Tracking: `CSI ? 1002 h/l`
- **?1003**, Any-Event Mouse Tracking: `CSI ? 1003 h/l`
- **?1005**, UTF-8 Mouse Encoding: `CSI ? 1005 h/l`
- **?1006**, SGR Mouse Encoding: `CSI ? 1006 h/l`
- **?1007**, Alternate Scroll: `CSI ? 1007 h/l`
- **?1015**, urxvt Mouse Encoding: `CSI ? 1015 h/l`
- **?1016**, SGR Pixel Mouse Encoding: `CSI ? 1016 h/l`
- **xterm:** [Mouse Tracking](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)

These modes ask the terminal to send the program a report when the mouse is
used over it, instead of using the mouse itself for selecting text. They are
xterm's, in DEC's private space. Two groups of them work together: one
chooses *which* events are reported, the other *how* a report is encoded.

| Mode | Reports |
| --- | --- |
| `9` | a button press only (X10 compatibility) |
| `1000` | presses and releases |
| `1001` | presses and releases, with highlight tracking |
| `1002` | presses, releases, and movement while a button is down |
| `1003` | presses, releases, and all movement |

| Mode | Encoding |
| --- | --- |
| *none* | `CSI M` and three bytes: the button and the coordinates, each plus 32; coordinates beyond 223 cannot be sent |
| `1005` | the same, with the coordinates in UTF-8 |
| `1006` | SGR: `CSI < b ; x ; y M` for a press, `m` for a release, in decimal |
| `1015` | urxvt: `CSI b ; x ; y M`, in decimal |
| `1016` | SGR, with the coordinates in pixels rather than cells |

Mode `1007`, *alternate scroll*, has the mouse wheel send cursor up and
down keys while the [alternate screen](https://control-codes.page/modes/altscreen/index.html.md) is showing and the
wheel is not being reported
([`AlternateScroll`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/scrollbar.c#L755-L783)); [focus events](https://control-codes.page/modes/focus/index.html.md),
`1004`, have their own page.

The reports go to the program, so a case cannot see them; what it can see is
what DECRQM says. The cases follow xterm, which keeps one value for each
group, not a flag per mode
([`dpmodes`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L7807-L7849),
[`really_set_mousemode`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L7432-L7439)):

- Setting one event mode replaces the one before it, and DECRQM reports
  only the latest as set. Resetting *any* of them turns tracking off.
- Setting one encoding replaces the one before it too, but resetting an
  encoding takes effect only if it is the one in use: xterm's comment says
  "a reset is only effective against the matching mode".
- Alternate scroll is off unless the `alternateScroll` resource turns it on
  ([`charproc.c`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L422)).
- [RIS](https://control-codes.page/esc/ris/index.html.md) turns tracking and the encoding off; [DECSTR](https://control-codes.page/csi/decstr/index.html.md) does not
  ([`ReallyReset`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L14432-L14443)).

libghostty-vt tracks the events the same way, with one value, but DECRQM
answers from a separate flag for each mode, so it reports a replaced mode as
still set. Resetting any encoding goes back to the default encoding even if
another is in use, which DECRQM does not show either: in that case it still
reports the other as set. It does not know mode `1001`, and it starts with
alternate scroll on. The cases those touch are known differences.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | No | not in the manual |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | No | not in the manual; not in [Table 5-6](https://vt100.net/docs/vt510-rm/DECRPM.html) |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Yes | alternate scroll is off unless the `alternateScroll` resource is set |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Partly | no mode `1001`; DECRQM answers each mode's own flag; alternate scroll on by default (MOUSE-3 to MOUSE-7, MOUSE-10) |
| kitty | 0.49.1, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/kitty.json) | Partly | fails MOUSE-6 of the 5 cases that show it working |
| foot | 1.28.0, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/foot.json) | Partly | fails MOUSE-6, MOUSE-9 of the 5 cases that show it working |
| xterm.js headless | 6.0.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/xtermjs.json) | Partly | fails MOUSE-6, MOUSE-9 of the 5 cases that show it working |
| alacritty_terminal | 0.26.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/alacritty.json) | Partly | fails MOUSE-6, MOUSE-9 of the 5 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | Partly | lists `9`, `1000`, `1002`, `1003`, `1005`, `1006` and `1015`; not `1001`, `1007` or `1016`; [ANSI escape codes](https://conemu.github.io/en/AnsiEscapeCodes.html) |
| rxvt-unicode | 9.31, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/urxvt.json) | Partly | fails MOUSE-6, MOUSE-9, MOUSE-12 of the 5 cases that show it working |
| VTE | 0.84.1, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/vte.json) | Partly | fails MOUSE-9 of the 5 cases that show it working |

## Validation

### MOUSE-1: Off to begin with

Input, one step per line:

```text
\e[?1000$p
\e[?1002$p
\e[?1003$p
\e[?1006$p
```

Expected screen:

```text
|____|
reply \e[?1000;2$y\e[?1002;2$y\e[?1003;2$y\e[?1006;2$y
```

### MOUSE-2: Set and reset

Input, one step per line:

```text
\e[?1002h
\e[?1002$p
\e[?1002l
\e[?1002$p
```

Expected screen:

```text
|____|
reply \e[?1002;1$y\e[?1002;2$y
```

### MOUSE-3: One event mode at a time

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1000h
\e[?1002h  # replaces 1000
\e[?1000$p
\e[?1002$p
```

Expected screen:

```text
|____|
reply \e[?1000;2$y\e[?1002;1$y
```

### MOUSE-4: X10 replaces the others too

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1003h
\e[?9h
\e[?9$p
\e[?1003$p
```

Expected screen:

```text
|____|
reply \e[?9;1$y\e[?1003;2$y
```

### MOUSE-5: Resetting any event mode turns tracking off

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1002h
\e[?1000l  # not the one that was set
\e[?1002$p
```

Expected screen:

```text
|____|
reply \e[?1002;2$y
```

### MOUSE-6: Highlight tracking

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1001h
\e[?1001$p
```

Expected screen:

```text
|____|
reply \e[?1001;1$y
```

### MOUSE-7: One encoding at a time

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1006h
\e[?1015h  # replaces 1006
\e[?1006$p
\e[?1015$p
```

Expected screen:

```text
|____|
reply \e[?1006;2$y\e[?1015;1$y
```

### MOUSE-8: Resetting another encoding leaves this one

Input, one step per line:

```text
\e[?1006h
\e[?1015l
\e[?1006$p
```

Expected screen:

```text
|____|
reply \e[?1006;1$y
```

### MOUSE-9: UTF-8 and pixel encodings

Input, one step per line:

```text
\e[?1005h
\e[?1005$p
\e[?1016h
\e[?1016$p
```

Expected screen:

```text
|____|
reply \e[?1005;1$y\e[?1016;1$y
```

### MOUSE-10: Alternate scroll is off by default

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\e[?1007$p
```

Expected screen:

```text
|____|
reply \e[?1007;2$y
```

### MOUSE-11: DECSTR leaves tracking on

Input, one step per line:

```text
\e[?1002h
\e[?1006h
\e[!p
\e[?1002$p
\e[?1006$p
```

Expected screen:

```text
|____|
reply \e[?1002;1$y\e[?1006;1$y
```

### MOUSE-12: RIS turns it off

Input, one step per line:

```text
\e[?1002h
\e[?1006h
\ec
\e[?1002$p
\e[?1006$p
```

Expected screen:

```text
|____|
reply \e[?1002;2$y\e[?1006;2$y
```

---

This is the Markdown version of <https://control-codes.page/modes/mouse/>. On that page every validation case runs live in libghostty-vt, the terminal emulation core of Ghostty, compiled to WebAssembly.

The validation cases are written in the notation described in <https://control-codes.page/notation/index.html.md>.
