Module: Tuile::TerminalBackground

Defined in:
lib/tuile/terminal_background.rb

Overview

Detects whether the terminal background is light or dark, so Screen can pick Tuile::Theme::LIGHT or Tuile::Theme::DARK automatically at startup.

Two mechanisms, in order of reliability:

  1. **OSC 11 query** — writes ‘ESC ] 11 ; ? BEL` to the terminal; modern terminals (xterm, kitty, alacritty, wezterm, iTerm2, GNOME Terminal, Windows Terminal) reply on stdin with the background color (`e]11;rgb:RRRR/GGGG/BBBB` + BEL or ST). The color’s relative luminance against a 0.5 threshold decides light vs dark. Terminals that don’t support the query simply never reply, so the read is bounded by a short timeout.

  2. **‘COLORFGBG` env var** — rxvt/konsole export `“fg;bg”` ANSI palette indices. Less reliable (stale across SSH/tmux, often unset); used only when OSC 11 yields nothing.

**Timing matters**: the OSC 11 reply arrives on stdin, so the query must complete before EventQueue#start_key_thread owns stdin —otherwise the reply bytes get consumed as garbage keystrokes. Screen calls TerminalBackground.detect from its constructor, which apps run before Screen#run_event_loop; don’t call this after the event loop started.

Constant Summary collapse

QUERY_TIMEOUT =

How long to wait for the OSC 11 reply. Generous for a local terminal; bounded so unsupporting terminals (which never reply) don’t stall startup.

Returns:

  • (Float)

    seconds.

0.1
QUERY =

The OSC 11 background-color query, BEL-terminated.

Returns:

  • (String)
"\e]11;?\a"
REPLY =

Matches the OSC 11 reply. Components are 1–4 hex digits each (terminals vary); ‘rgba:` (4 components) also matches — the alpha tail is ignored.

Returns:

  • (Regexp)
%r{\e\]11;rgba?:(\h{1,4})/(\h{1,4})/(\h{1,4})}
NOTIFY_ON =

Enables mode 2031: the terminal pushes a color-scheme report (‘e[?997;1n` dark / `e[?997;2n` light) whenever the OS appearance flips — see EventQueue::ColorSchemeEvent. Terminals without support ignore the sequence. Written by Screen#run_event_loop.

Returns:

  • (String)
"\e[?2031h"
NOTIFY_OFF =

Disables mode 2031 again; written when the event loop exits.

Returns:

  • (String)
"\e[?2031l"

Class Method Summary collapse

Class Method Details

.detect(input: $stdin, output: $stdout, env: ENV, timeout: QUERY_TIMEOUT) ⇒ Symbol?

Detects the terminal background. Queries OSC 11 when both ‘input` and `output` are TTYs, falling back to `COLORFGBG`.

Parameters:

  • input (IO) (defaults to: $stdin)

    where the OSC 11 reply arrives (the TTY input).

  • output (IO) (defaults to: $stdout)

    where the query is written (the TTY output).

  • env (Hash{String => String}) (defaults to: ENV)

    environment for the ‘COLORFGBG` fallback; defaults to `ENV` (which duck-types the `[]` lookup).

  • timeout (Numeric) (defaults to: QUERY_TIMEOUT)

    max seconds to wait for the OSC 11 reply.

Returns:

  • (Symbol, nil)

    ‘:light`, `:dark`, or nil when undetectable.



63
64
65
66
# File 'lib/tuile/terminal_background.rb', line 63

def detect(input: $stdin, output: $stdout, env: ENV, timeout: QUERY_TIMEOUT)
  osc = query_osc11(input, output, timeout) if input.tty? && output.tty?
  osc || from_colorfgbg(env["COLORFGBG"])
end