# Window Operations (XTWINOPS)

> Move, resize and raise the window, report its size and state, and save and restore its title.

- **Sequence:** `CSI Ps ; Ps ; Ps t`
- **Defaults:** Ps = 0
- **xterm:** [Functions using CSI](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)

`CSI Ps ; Ps ; Ps t` is xterm's window manipulation sequence, taken from
dtterm. The first parameter says what to do:

| `Ps` | Does | Answers |
| --- | --- | --- |
| `1`, `2` | de-iconify, iconify | |
| `3 ; x ; y` | move the window | |
| `4 ; h ; w`, `8 ; rows ; cols` | resize it, in pixels or in characters | |
| `5`, `6`, `7` | raise, lower, refresh | |
| `9 ; n`, `10 ; n` | maximize, full screen | |
| `11` | report whether iconified | `CSI 1 t` or `CSI 2 t` |
| `13` | report the window's position | `CSI 3 ; x ; y t` |
| `14`, `15`, `16` | report the window, the screen, a character cell, in pixels | `CSI 4 ; h ; w t`, `CSI 5 ; h ; w t`, `CSI 6 ; h ; w t` |
| `18`, `19` | report the text area, the screen, in characters | `CSI 8 ; rows ; cols t`, `CSI 9 ; rows ; cols t` |
| `20`, `21` | report the icon name, the window title | `OSC L name ST`, `OSC l title ST` |
| `22 ; n`, `23 ; n` | push the title on a stack, pop it | |
| `24` and up | resize to that many lines, as DECSLPP; see below for `28` | |

For `22` and `23` the second parameter says which title: `0` both, `1` the
icon name, `2` the window title. The stack holds ten, and popping an empty
one does nothing
([`xtermPushTitle`, `xtermPopTitle`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/misc.c#L7977-L8034)).
Programs push the title before setting their own and pop it on leaving, so
the shell's title comes back.

xterm gates each operation by the `allowWindowOps` and `disallowedWindowOps`
resources. `allowWindowOps` is off by default, but that only applies the
list, and the default list names just `GetIconTitle` and `GetWinTitle` here,
since reporting a title back lets a program type whatever it put there into
the shell. A default xterm does everything else, resizing included
([`window_ops`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L8979-L9319),
[`main.h`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/main.h#L150-L175)).
The cases follow it.

Konsole once used `CSI 28 ; color t` to set the color of a tab's text, with
`color` a 24-bit number. Today's Konsole ignores it, and its source says it
is a Konsole extension from the KDE 3 days that is no longer implemented
([`Vt102Emulation.cpp`](https://invent.kde.org/utilities/konsole/-/blob/a24c3d71be3684f24d030aa2dbe9bd723d985233/src/Vt102Emulation.cpp#L2052)). To xterm, `28`
is just a number of lines: it resizes the window to 28 lines and ignores
the color.

libghostty-vt answers `14`, `16` and `18` through its host's `SIZE`
callback, which the runner installs, giving the case's own size and an
arbitrary cell of 6 by 13 pixels; the cases check only `18`, since the
pixel sizes depend on xterm's font. It does nothing with the rest, which are
the host's window to move: it has no title stack, and does not report
whether the window is iconified. Those cases 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 | Partly | only `24` and up, as [DECSLPP](https://vt100.net/docs/vt510-rm/DECSLPP.html) |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Partly | `20` and `21` are refused by the default `disallowedWindowOps`; the rest are allowed |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Partly | answers `14`, `16` and `18` through its host; no title stack or `11` (XTWINOPS-2, -4, -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 XTWINOPS-2 of the 4 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) | Yes | passes the 4 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 4 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) | No | fails the 4 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 XTWINOPS-2 of the 4 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | Partly | lists only `18`, `19` and `21`; [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 XTWINOPS-4, XTWINOPS-5 of the 4 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) | Yes | passes the 4 cases that show it working |

## Validation

### XTWINOPS-1: The text area in characters

Input, one step per line:

```text
\e[18t
```

Expected screen:

```text
|______|
|______|
reply \e[8;2;6t
```

### XTWINOPS-2: Whether the window is iconified

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

Input, one step per line:

```text
\e[11t
```

Expected screen:

```text
|______|
reply \e[1t
```

### XTWINOPS-3: The title is not reported

Input, one step per line:

```text
\e]2;secret\a
\e[21t
\e[20t
```

Expected screen:

```text
|______|
title "secret"
reply none
```

### XTWINOPS-4: Push and pop the title

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

Input, one step per line:

```text
\e]2;shell\a
\e[22;0t   # push
\e]2;editor\a
\e[23;0t   # pop
```

Expected screen:

```text
|______|
title "shell"
```

### XTWINOPS-5: A stack of them

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

Input, one step per line:

```text
\e]2;one\a
\e[22t
\e]2;two\a
\e[22t
\e]2;three\a
\e[23t
\e[23t
```

Expected screen:

```text
|______|
title "one"
```

### XTWINOPS-6: Popping only the icon name

Input, one step per line:

```text
\e]2;one\a
\e[22;1t   # push the icon name
\e]2;two\a
\e[23;1t   # pop it: the window title stays
```

Expected screen:

```text
|______|
title "two"
```

### XTWINOPS-7: Popping an empty stack

Input, one step per line:

```text
\e]2;same\a
\e[23;0t
```

Expected screen:

```text
|______|
title "same"
```

---

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