# Set Cursor Style (DECSCUSR)

> Choose the cursor's shape, and whether it blinks.

- **Sequence:** `CSI Ps SP q`
- **Defaults:** Ps = 1
- **VT510:** [DECSCUSR—Set Cursor Style](https://vt100.net/docs/vt510-rm/DECSCUSR.html)
- **xterm:** [Functions using CSI](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)

Set the shape of the cursor, and whether it blinks. `SP` is a space, `20`,
used here as an intermediate byte.

| `Ps` | Cursor |
| --- | --- |
| `0`, `1`, or left out | blinking block |
| `2` | steady block |
| `3` | blinking underline |
| `4` | steady underline |
| `5` | blinking bar |
| `6` | steady bar |

The VT510 manual defines `0` to `4`, and calls the blinking block the
default. The bar, `5` and `6`, is xterm's addition. DECSCUSR came with the
VT520, so DEC STD 070 does not have it. It changes only how the cursor
looks: whether it is shown at all is [DECTCEM](https://control-codes.page/modes/dectcem/index.html.md)'s job.

Nothing about the cursor's style is in the cell grid, so the cases ask the
terminal for it with DECRQSS, `DCS $ q SP q ST`, which both the VT510 manual
and xterm list for DECSCUSR (see [DCS](https://control-codes.page/esc/dcs/index.html.md)). The answer is
`DCS 1 $ r Ps SP q ST`, with the style as `Ps`.

`0`, or no parameter, is where libghostty-vt departs from the manuals. It
reads it as "go back to the host's default style", and the host chooses that
default with libghostty-vt's `DEFAULT_CURSOR_STYLE` and
`DEFAULT_CURSOR_BLINK` options. Left unset, as the cases here leave them, the
default is a steady block, so `CSI 0 SP q` reports `2`. Before any DECSCUSR
at all, the cursor is the same steady block.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | No | not in the manual; DECSCUSR came with the VT520 |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | Yes | [DECSCUSR](https://vt100.net/docs/vt510-rm/DECSCUSR.html); styles `0` to `4`; the bars, `5` and `6`, are xterm's |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Yes |  |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Yes | `0` is the host's default style, a steady block unless set (DECSCUSR-5) |
| kitty | 0.49.1, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/kitty.json) | Partly | fails DECSCUSR-4 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 DECSCUSR-5 of the 5 cases that show it working |
| pyte | 0.8.2, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/pyte.json) | No | fails 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 DECSCUSR-2, DECSCUSR-3, DECSCUSR-4, DECSCUSR-5 of the 5 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | Yes | `0` is ConEmu's default rather than a blinking block; [ANSI escape codes](https://conemu.github.io/en/AnsiEscapeCodes.html) |
| VTE | 0.84.1, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/vte.json) | Partly | fails DECSCUSR-5 of the 5 cases that show it working |

## Validation

### DECSCUSR-1: Steady block

Input, one step per line:

```text
\e[2\sq
\eP$q\sq\e\\ # ask for the style
```

Expected screen:

```text
|_____|
reply \eP1$r2 q\e\\
```

### DECSCUSR-2: Blinking underline

Input, one step per line:

```text
\e[3\sq
\eP$q\sq\e\\
```

Expected screen:

```text
|_____|
reply \eP1$r3 q\e\\
```

### DECSCUSR-3: Steady bar

Input, one step per line:

```text
\e[6\sq
\eP$q\sq\e\\
```

Expected screen:

```text
|_____|
reply \eP1$r6 q\e\\
```

### DECSCUSR-4: Blinking block

Input, one step per line:

```text
\e[1\sq
\eP$q\sq\e\\
```

Expected screen:

```text
|_____|
reply \eP1$r1 q\e\\
```

### DECSCUSR-5: Zero is a blinking block

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

Input, one step per line:

```text
\e[2\sq   # steady block
\e[0\sq   # back to the default
\eP$q\sq\e\\
```

Expected screen:

```text
|_____|
reply \eP1$r1 q\e\\
```

libghostty-vt answers `\eP1$r2 q\e\\`, a steady block. Its DECSCUSR handler
in `stream.zig` turns `0` and an omitted parameter into `.default`, and
`setCursorStyle` in `Terminal.zig` takes the host's default shape and blink,
which unless the host sets them are a block that does not blink. The VT510
manual makes `0` a blinking block, the default, and xterm's ctlseqs says
"Ps = 0 ⇒ blinking block", which its
[`CASE_DECSCUSR`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L4949-L4969)
does. A host can make libghostty-vt agree by setting `DEFAULT_CURSOR_BLINK`.

---

This is the Markdown version of <https://control-codes.page/csi/decscusr/>. 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>.
