# Screenshot (OSC 533)

> A proposal for a program to ask its terminal for a region of the screen, as text, as VT sequences, or as an image.

- **Sequence:** `OSC 533 ; top ; left ; bottom ; right ; format ST`
- **Specification:** [VT sequence: screenshot, Contour issue #161](https://github.com/contour-terminal/contour/issues/161)

*Screenshot* lets a program ask its terminal for what is on the screen: a
rectangle of it, returned as plain text, as the VT sequences that would
draw it again, or as a picture. Christian Parpart proposed it for
[Contour](https://github.com/contour-terminal/contour) in January 2021, in
[issue #161](https://github.com/contour-terminal/contour/issues/161), which
is still open. A
[pull request](https://github.com/contour-terminal/contour/pull/2088),
also open, implements it with a specification that changes the reply
considerably; it is described below.

## The request, as proposed

```
OSC 533 ; top ; left ; bottom ; right ; format ST
```

The four numbers name the region. Without them the request is for the whole
main page, and any of them left out takes whatever value keeps to that, so
an omitted `bottom` is the last row. The issue does not say whether rows
and columns count from zero or one. `format` defaults to PNG, and the issue
lists what should be supported without giving them numbers: plain text, VT
sequences, sixel, PNG, and raw RGB or RGBA.

## The reply, as proposed

```
OSC 533 ; top ; left ; lines ; columns ; format ; Pt ST
```

`Pt` is the screenshot, and the other fields echo the request with every
default filled in. The region comes back as a height and width, `lines`
and `columns`, rather than the bottom and right edges it was asked for
with. The issue says nothing about how the data is encoded.

## Security

Reading the screen hands whatever is on it to the program that asked, so
the issue suggests a per-profile setting, `allow`, `deny` or `ask`, with
`ask` the default, and a dialog that can preview the image before it is
handed over. It also suggests a command, `contour screenshot`, that would
take a screenshot and save it to a file or the clipboard.

## The revision in pull request 2088

The specification in the
[pull request](https://github.com/contour-terminal/contour/blob/11a819c1aa416efc8ebce9c0f708835fd51e0609/docs/vt-extensions/osc-533-screenshot.md),
as it stood at commit `11a819c`, keeps the number and the idea and settles
what the issue left open. It names two problems with the reply as
first proposed. It is an OSC 533 like the request, so a reply recorded by
`script(1)` or a log and later played back to a terminal would read as a
new request. And its screen content is unescaped: the VT-sequence format
contains `ESC` by construction, so an `ST` inside it ends the reply early
and hands the rest to the program that asked, as though it were input.

The request gains an identifier at the front, and every field may be left
empty:

```
OSC 533 ; Pid ; Pt ; Pl ; Pb ; Pr ; Pf ST
```

| Parameter | Meaning | Default |
| --- | --- | --- |
| `Pid` | An opaque request identifier, echoed in every reply | `0` |
| `Pt` | Top row | `1` |
| `Pl` | Left column | `1` |
| `Pb` | Bottom row | the last row |
| `Pr` | Right column | the last column |
| `Pf` | Format | `0` |

The coordinates count from one and are inclusive on all four edges, as
[DECRQCRA](https://control-codes.page/csi/decrqcra/index.html.md) and DEC's other rectangular-area functions count
them; zero means the default, and a coordinate beyond the page is taken as
its edge. Unlike those, the region is measured from the top left of the
page and not from the origin, so [DECOM](https://control-codes.page/modes/decom/index.html.md) does not move it.
Only the main page of the current buffer can be read, not the scrollback.
A bare `OSC 533 ST` therefore asks for the whole page as plain text, where
the issue's default was PNG.

| `Pf` | Format |
| --- | --- |
| `0` | Plain text: UTF-8, each row ended by LF, blank cells as spaces |
| `1` | VT sequences: the same text with the SGR sequences for its colors, each row ended by CRLF |
| `2` | Sixel: a complete sixel `DCS … ST` of the region as rendered |
| `3` | PNG: a whole PNG file of the region as rendered |

Raw RGBA is left out: a page-sized region would be close to a megabyte. A
number not in the table is answered as an unsupported format. The two
picture formats are a crop of a frame the terminal actually drew, so they
include the cursor, any selection, and the scrollback position if the user
has scrolled, and the answer to them comes a frame or two after the
request. Sixel has no alpha channel and states colors in percentages, so it
can be one step off in a channel and leaves a mostly transparent pixel
unpainted.

The reply is a [privacy message](https://control-codes.page/esc/sos/index.html.md), not an OSC, so it cannot be
mistaken for a request:

```
PM 533 ; Pid ; Ps ; Pt ; Pl ; Pb ; Pr ; Pf ; Pw ; Ph ; data ST    (Ps = 1)
PM 533 ; Pid ; Ps ST                                              (otherwise)
```

The region is echoed as it was resolved, in the request's own terms of
edges, not as a height and width. `Pw` and `Ph` are the image's size in
pixels, `0` for the text formats, there so a reader can size a buffer
before decoding anything. The data is **base64**, split into chunks of 4095
bytes before encoding, a multiple of three so that only the last chunk is
padded; each chunk is a message of its own with the same header, and a
message with `Ps` `0` always ends the screenshot, even an empty one.

| `Ps` | Meaning |
| --- | --- |
| `0` | End of the screenshot |
| `1` | A chunk of data |
| `2` | Denied, by the user or by configuration |
| `3` | The request could not be parsed |
| `4` | Not a format the protocol has |
| `5` | The region is inverted and names no cell |
| `6` | A format this session cannot produce now, such as a picture with no renderer attached |

Every request is answered, a refusal included, so a program that writes
one and waits for a read is never left waiting, even while a dialog asks
the user. For example, the first five rows and twenty columns as VT
sequences, as request `42`:

```
ESC ] 533 ; 42 ; 1 ; 1 ; 5 ; 20 ; 1 ESC \
ESC ^ 533 ; 42 ; 1 ; 1 ; 1 ; 5 ; 20 ; 1 ; 0 ; 0 ; data ESC \
ESC ^ 533 ; 42 ; 0 ESC \
```

without the spaces, which are there only to make it readable.

The `ask` default and the per-profile setting are kept. The revision notes
that no registry assigns OSC numbers, and that it found no terminal using
533.

---

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