# Visibility Reports (?2033)

> Have the terminal tell the program whether its output can be seen, so that it can stop drawing while hidden.

- **Sequence:** `CSI ? 2033 h/l`
- **Specification:** [Private Mode for Terminal Visibility Reports](https://rockorager.dev/misc/visibility-reports/)

With `CSI ? 2033 h` set, the terminal tells the program whether its view can
be seen, so that a program can pause animations and other expensive drawing
while nobody is looking. The report is a private Device Status Report,
`CSI ? 999 ; Ps n`, where `Ps` is `1`, *potentially visible*, or `2`, *not
visible*. Setting the mode sends a report at once, even when it was already
set, and the terminal sends another each time the state changes;
`CSI ? 2033 l` stops them and sends nothing. A program that wants the state
just once asks with [`CSI ? 998 n`](https://control-codes.page/csi/decdsr/index.html.md), which is answered with the
same report and leaves the mode as it was.
The pattern is that of [dark and light mode detection](https://control-codes.page/modes/color-scheme/index.html.md),
`?2031`, whose query and report are `CSI ? 996 n` and `CSI ? 997 ; Ps n`.

Focus does not answer the question. A [focus report](https://control-codes.page/modes/focus/index.html.md) says
which view gets the keyboard, but an unfocused terminal can still be on
screen, and a focused one can be covered, in a background tab, or on a
workspace not being shown.

It is neither xterm's nor DEC's. Its definition is
[Private Mode for Terminal Visibility Reports](https://rockorager.dev/misc/visibility-reports/),
by Tim Culverhouse, dated July 25, 2026, read on October 23, 2026. A program
finds out whether a terminal has it by asking with DECRQM: `1` or `2` means
it does, and `0`, `4` or no answer means it does not.

The specification makes *potentially visible* the safe answer. A terminal
reports `2` only when it knows that the view cannot be seen, and `1`
whenever it does not know, or when any of the views showing the session
might be seen; a program takes any value it does not know as `1`. The view
meant is the session's own, not only its window: a tab that is not
selected, a pane not displayed, a window minimized, hidden, fully covered or
on another workspace, and a surface whose compositor has stopped presenting
it. On macOS a window whose `NSWindow.occlusionState` lacks
`NSWindowOcclusionStateVisible` is fully covered, and on Wayland an
`xdg_toplevel` in the `suspended` state can be taken as not visible; the
specification gives these as examples rather than requirements.

*Not visible* is a hint, not an instruction. The program may stop
animating, put off redrawing, or skip other work that only matters on
screen, but it should go on reading input and keeping the state it will
need, and draw its latest state as soon as a `1` arrives. It may not assume
that the terminal will stay hidden for any length of time.

A default xterm has no mode `2033`. It answers DECRQM for it with `0`, as it
does for any mode it does not know
([`do_dec_rqm`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/misc.c#L5855-L5869)),
and does not answer `CSI ? 998 n`, which falls through to the empty default
of its DSR dispatch
([`CASE_DSR`](https://github.com/ThomasDickey/xterm-snapshots/blob/xterm-411a/charproc.c#L4599-L4746)).
The cases follow xterm.

libghostty-vt implements it, added in Ghostty
[pull request #13494](https://github.com/ghostty-org/ghostty/pull/13494) by the
specification's author, so the cases that expect xterm's silence are known
differences. Mode `2033` is `report_visibility` in `modes.zig`, off to begin
with, and `?998` is in the DSR table in `device_status.zig`; setting the mode
or asking sends the report from `sendVisibilityReport` in
`stream_terminal.zig`. The state it reports is the terminal's
`flags.visible`, which starts out `true`, so libghostty-vt answers `1` until
its host says otherwise, and which [RIS](https://control-codes.page/esc/ris/index.html.md) leaves alone, since it
belongs to the view rather than to the terminal. Ghostty sets it from the
occlusion state its app reports, and on GTK 4.12 and later that includes the
window's `suspended` state, so a window on another workspace is reported as
not visible. libghostty-vt's C API offers no way to set it, so a program
embedding the library always gets `1`, as the cases here do.

## 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) | No | not recognized; DECRQM reports `0`, and `CSI ? 998 n` is not answered |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Yes | VISIBILITY-1 to VISIBILITY-3: implements the mode and the query; always potentially visible, as its C API cannot change that |
| kitty | 0.49.2, its documentation | Yes | sends a report when the mode is set and on each change ([`screen.c`](https://github.com/kovidgoyal/kitty/blob/v0.49.2/kitty/screen.c#L1786-L1798), [`set_mode_from_const`](https://github.com/kovidgoyal/kitty/blob/v0.49.2/kitty/screen.c#L2053-L2056)); since 0.48.2 |

## Validation

### VISIBILITY-1: A default xterm does not recognize it

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

Input, one step per line:

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

Expected screen:

```text
|____|
reply \e[?2033;0$y\e[?2033;0$y
```

### VISIBILITY-2: Setting it sends nothing in a default xterm

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

Input, one step per line:

```text
\e[?2033h
```

Expected screen:

```text
|____|
reply none
```

### VISIBILITY-3: A default xterm does not answer the query

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

Input, one step per line:

```text
\e[?998n
```

Expected screen:

```text
|____|
reply none
```

### VISIBILITY-4: The screen does not change

Input, one step per line:

```text
A
\e[?2033h
\e[?998n
\e[?2033l
B
```

Expected screen:

```text
|AB__|
cursor 1,3
```

---

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