^[control-codes live in libghostty-vt

Private modes

?2031 Dark and Light Mode Detection

Have the terminal tell the program whether its colors are dark or light, and again whenever they change.

CSI ? 2031 h/l
Specification
Dark and Light Mode detection, Contour VT extensions

A program that picks its own colors wants to know whether it is drawing on a dark background or a light one, and to hear about it when the desktop switches from one to the other, or the user changes the terminal’s profile. CSI ? 996 n asks, and the terminal answers with a private Device Status Report, CSI ? 997 ; 1 n for dark or CSI ? 997 ; 2 n for light. With CSI ? 2031 h set, the terminal sends the same report on its own whenever its colors change between the two; CSI ? 2031 l stops the reports.

It is neither xterm’s nor DEC’s. Its definition is Dark and Light Mode detection, kept by the Contour terminal’s authors with their other VT extensions, read at commit ab57ab3. It is short. The report is sent when the terminal’s palette changes because the user changed the profile, directly or by changing the operating system’s theme, and not otherwise. It does not say that setting the mode sends a report, nor how a terminal decides that its colors are dark, and terminals differ on both:

  • Ghostty answers from its configuration’s theme state, which follows the operating system’s appearance when the configuration gives a light and a dark theme, and sends a report when that changes or the configuration is reloaded.
  • kitty calls its colors dark when its default background is (is_dark), and sends a report when its colors change other than by an escape code.
  • foot answers from which of its two themes is active, [colors-dark] or [colors-light], and sends a report when it switches theme (csi.c, term_theme_switch_to_dark).
  • VTE calls its colors dark when its default background is no lighter than its default foreground, and sends a report when the program hosting it changes either of them, but not when an escape code does (set_color, is_color_palette_dark).

None of them sends a report when the mode is set, so a program that wants the state at the start asks with CSI ? 996 n as well as setting the mode. A program finds out whether a terminal has it by asking with DECRQM: an answer of 0, not recognized, or no answer at all, means it does not.

A default xterm has no mode 2031. It answers DECRQM for it with 0, as it does for any mode it does not know (do_dec_rqm), and does not answer CSI ? 996 n, which falls through to the empty default of its DSR dispatch (CASE_DSR). The cases follow xterm.

libghostty-vt has the mode, report_color_scheme in modes.zig, off to begin with and reset by RIS, and answers DECRQM for it with 1 or 2, so the first case is a known difference. Whether the colors are dark is the host’s to say: CSI ? 996 n is answered only through a callback the host sets with GHOSTTY_TERMINAL_OPT_COLOR_SCHEME, and is dropped without one. This site’s wrapper sets none, so libghostty-vt here answers the query no more than xterm does. Sending a report when the colors change is the host’s job too, as Ghostty does from its own configuration.

Support

TerminalVersionSupportNotes
VT220manual, 2nd ed., 1984Nonot in the manual
VT510manual, 1st ed., 1993Nonot in the manual; not in Table 5-6
xtermpatch 412, xterm-411aNonot recognized; DECRQM reports 0, and CSI ? 996 n is not answered
libghostty-vt0f171f6YesCOLORSCHEME-1: implements the mode; the query is answered through a callback the host sets, and reports are the host’s to send
Konsole26.11.70, a24c3d71Nono mode 2031 and no CSI ? 996 n
kitty0.49.2, its documentationYesdark when the default background is (window.py, screen.c); since 0.38.1
foot1.28.0Yesdark when the [colors-dark] theme is active (csi.c); since 1.23.0
VTE0.84.1, sourceYesdark when the default background is no lighter than the foreground (vte.cc); since 0.82.0

Validation

COLORSCHEME-1: A default xterm does not recognize it

\e[?2031$p
\e[?2031h
\e[?2031$p
|____|
reply \e[?2031;0$y\e[?2031;0$y

COLORSCHEME-2: Setting it sends nothing

\e[?2031h
|____|
reply none

COLORSCHEME-3: A default xterm does not answer the query

\e[?996n
|____|
reply none

COLORSCHEME-4: The screen does not change

A
\e[?2031h
\e[?996n
\e[?2031l
B
|AB__|
cursor 1,3
Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.