uncurses/program/state.rs
1//! Terminal/input mode state owned by the [`Program`] facade.
2//!
3//! Most fields record a mode the facade has *emitted*, so it can tear the
4//! mode down on a shell handoff and re-apply it afterwards.
5//! [`chosen`](State::chosen) is the exception: it records which modes the app
6//! has decided for either way, so discovery can tell an explicit `disable_*`
7//! apart from silence.
8//!
9//! Three of them — [`alt_screen`](State::alt_screen),
10//! [`cursor_visible`](State::cursor_visible), and
11//! [`grapheme_clusters`](State::grapheme_clusters) — are mirrored by a render
12//! property on the [`Screen`](crate::screen::Screen) the facade draws with.
13//! They are tracked separately on purpose: the screen's copy says how to draw
14//! a frame, this one says what the terminal was told. Since
15//! [`screen_mut`](super::Program::screen_mut) lets an app move the render
16//! property on its own, inferring one from the other would make
17//! [`reset`](super::Program::reset) emit modes that were never set (or skip
18//! ones that were) and leave the terminal wedged after exit.
19//!
20//! [`Program`]: super::Program
21
22use std::collections::{BTreeMap, BTreeSet};
23
24use crate::ansi::cursor::CursorStyle;
25use crate::ansi::kitty::KittyKeyboardFlags;
26use crate::ansi::mode::{Mode, ModeSetting};
27use crate::color::Color;
28use crate::event::{ColorScheme, ModifyOtherKeysMode, ProgramStatusCapabilities};
29
30use super::MouseTracking;
31use super::ProgressState;
32
33/// Tracked non-render mode state for save/restore.
34#[derive(Debug, Clone)]
35pub(super) struct State {
36 /// Cursor style.
37 pub cursor_style: CursorStyle,
38 /// Requested mouse tracking, or `None` when mouse tracking is disabled.
39 pub mouse: Option<MouseTracking>,
40 /// Bracketed paste mode.
41 pub bracketed_paste: bool,
42 /// Focus in/out reporting (DECSET 1004).
43 pub focus_events: bool,
44 /// Color-scheme update notifications (DEC 2031). When `true`, the
45 /// terminal sends unsolicited reports as the user/OS toggles the
46 /// dark/light scheme. Reports the dark/light preference only, not the
47 /// actual colors.
48 pub color_scheme_updates: bool,
49 /// Terminal visibility reports (DEC 2033). When `true`, the terminal
50 /// sends an unsolicited report whenever the view becomes observable or
51 /// stops being observable, surfaced as [`Event::Visibility`].
52 ///
53 /// [`Event::Visibility`]: crate::event::Event::Visibility
54 pub visibility_reports: bool,
55 /// In-band resize notifications (DEC 2048). When `true`, the
56 /// terminal sends a `CSI 48 ; … t` report whenever the surface
57 /// changes size, surfaced as [`Event::Resize`].
58 ///
59 /// [`Event::Resize`]: crate::event::Event::Resize
60 pub in_band_resize: bool,
61 /// Window title set via [`OSC 2`] (or [`OSC 0`], which sets both this and
62 /// [`icon_name`](Self::icon_name)). `None` when no
63 /// [`set_window_title`](super::Program::set_window_title) or
64 /// [`set_title`](super::Program::set_title) override has been set.
65 ///
66 /// [`OSC 2`]: crate::ansi::title::write_window_title
67 /// [`OSC 0`]: crate::ansi::title::write_window_title_and_icon
68 pub window_title: Option<String>,
69 /// Icon name set via [`OSC 1`] (or [`OSC 0`], which sets both this and
70 /// [`window_title`](Self::window_title)). `None` when no
71 /// [`set_icon_title`](super::Program::set_icon_title) or
72 /// [`set_title`](super::Program::set_title) override has been set.
73 ///
74 /// [`OSC 1`]: crate::ansi::title::write_icon_name
75 /// [`OSC 0`]: crate::ansi::title::write_window_title_and_icon
76 pub icon_name: Option<String>,
77 /// Default foreground color override. `Some(c)` when the facade has
78 /// emitted `OSC 10` to install `c`; `None` when the terminal is
79 /// using its built-in default. Drives `OSC 110` on reset and
80 /// re-emission on restore.
81 pub foreground_color: Option<Color>,
82 /// Default background color override. See [`State::foreground_color`].
83 pub background_color: Option<Color>,
84 /// Cursor color override. See [`State::foreground_color`].
85 pub cursor_color: Option<Color>,
86 /// Indexed palette overrides set via `OSC 4`, keyed by palette index.
87 /// Drives `OSC 104 ; index` on reset and re-emission on restore.
88 pub palette: BTreeMap<u8, Color>,
89 /// Active xterm modifyOtherKeys mode (`CSI > 4 ; n m`). Drives
90 /// `CSI > 4 m` on reset and re-emission on restore.
91 pub modify_other_keys: ModifyOtherKeysMode,
92 /// Pointer (mouse cursor) shape override set via `OSC 22`. `None` when
93 /// using the terminal default. Drives the `OSC 22` reset on reset and
94 /// re-emission on restore.
95 pub pointer_shape: Option<String>,
96 /// Progress reported via `OSC 9;4`. `None` when no progress is being
97 /// reported. Drives the `OSC 9;4;0` removal on reset and re-emission on
98 /// restore.
99 pub progress: Option<ProgressState>,
100 /// Active Kitty keyboard enhancement flag set. The stack is
101 /// per-screen-buffer, so the program re-emits this onto whichever buffer
102 /// becomes active. `NONE` means no frame is set.
103 pub kitty_keyboard: KittyKeyboardFlags,
104 /// Whether the facade has put the terminal on the alternate screen buffer
105 /// (DECSET 1049). Mirrors [`Screen::fullscreen`](crate::screen::Screen::fullscreen)
106 /// while the app drives the buffer through
107 /// [`enter_alt_screen`](super::Program::enter_alt_screen) /
108 /// [`exit_alt_screen`](super::Program::exit_alt_screen).
109 pub alt_screen: bool,
110 /// Whether the terminal cursor is visible (DECTCEM). Mirrors
111 /// [`Screen::cursor_visible`](crate::screen::Screen::cursor_visible).
112 /// Starts `true`: a terminal shows its cursor until told otherwise.
113 pub cursor_visible: bool,
114 /// Whether grapheme-cluster mode is on (DEC 2027). Mirrors
115 /// [`Screen::grapheme_clusters`](crate::screen::Screen::grapheme_clusters).
116 pub grapheme_clusters: bool,
117 /// Application-selected Backarrow mode, restored after a pause.
118 /// The decoder separately tracks the current mode from incoming reports.
119 pub backspace_mode: bool,
120 /// Modes selected through the program's mode methods or adopted on the
121 /// application's behalf.
122 ///
123 /// The mode fields above cannot carry this: `false` reads the same whether
124 /// the app turned the mode off or never mentioned it. Discovery adopts a
125 /// preferred mode only for a mode absent from this set, so an explicit
126 /// `disable_*` issued before the terminal ever reports is not quietly
127 /// undone by the report when it arrives.
128 pub chosen: BTreeSet<Mode>,
129}
130
131impl Default for State {
132 fn default() -> Self {
133 Self {
134 cursor_style: CursorStyle::Default,
135 mouse: None,
136 bracketed_paste: false,
137 focus_events: false,
138 color_scheme_updates: false,
139 visibility_reports: false,
140 in_band_resize: false,
141 window_title: None,
142 icon_name: None,
143 foreground_color: None,
144 background_color: None,
145 cursor_color: None,
146 palette: BTreeMap::new(),
147 modify_other_keys: ModifyOtherKeysMode::Disabled,
148 pointer_shape: None,
149 progress: None,
150 kitty_keyboard: KittyKeyboardFlags::empty(),
151 alt_screen: false,
152 cursor_visible: true,
153 grapheme_clusters: false,
154 backspace_mode: false,
155 chosen: BTreeSet::new(),
156 }
157 }
158}
159
160/// What the terminal told us about itself.
161///
162/// This holds the replies themselves, not a summary of them: the
163/// [`ModeSetting`] reported for every mode that was asked about, the raw
164/// device-attribute lists, the reported colors, and so on. A reply that says
165/// "I do not recognize that" is recorded too, so `None` from an accessor
166/// generally means the terminal never answered, which is different from
167/// answering no. [`termcap`](Self::termcap) is the exception, folding both
168/// into `None`; use [`termcap_reports`](Self::termcap_reports) to separate
169/// them.
170///
171/// Everything here is what the terminal reported, never what the facade told
172/// it. The two are easy to confuse where both exist: a
173/// [`background_color`](Self::background_color) recorded here stays the
174/// terminal's own default even after
175/// [`set_background_color`](super::Program::set_background_color) overrides
176/// it.
177///
178/// Only replies land here, so questions the environment can also answer are
179/// deliberately absent. Direct-color support is the example: `COLORTERM` and
180/// `TERM` establish it as readily as an XTGETTCAP reply does, so the answer is
181/// [`Screen::color_profile`](crate::screen::Screen::color_profile), which
182/// folds in both, and what remains here is the reply itself via
183/// [`supports_termcap`](Self::supports_termcap).
184///
185/// The facade records these as reply events flow through
186/// [`read_event`](super::Program::read_event) /
187/// [`try_read_event`](super::Program::try_read_event), whichever way the
188/// question was put: see
189/// [`query_capabilities`](super::Program::query_capabilities), which asks for
190/// only some of this, and takes extra bytes so you can ask for the rest. An
191/// individual `request_*` method fills a single entry, and a few reports
192/// arrive unprompted once their mode is on, such as a color-scheme change
193/// under DEC mode 2031. Read it back with
194/// [`Program::capabilities`](super::Program::capabilities).
195#[derive(Debug, Clone, Default, PartialEq, Eq)]
196pub struct Capabilities {
197 pub(super) modes: BTreeMap<Mode, ModeSetting>,
198 pub(super) primary_device_attributes: Option<Vec<Option<u32>>>,
199 pub(super) secondary_device_attributes: Option<Vec<Option<u32>>>,
200 pub(super) tertiary_device_attributes: Option<String>,
201 pub(super) kitty_keyboard: Option<KittyKeyboardFlags>,
202 pub(super) modify_other_keys: Option<ModifyOtherKeysMode>,
203 pub(super) terminal_name: Option<String>,
204 pub(super) termcap: BTreeMap<String, Option<String>>,
205 pub(super) foreground_color: Option<Color>,
206 pub(super) background_color: Option<Color>,
207 pub(super) cursor_color: Option<Color>,
208 pub(super) palette: BTreeMap<u8, Color>,
209 pub(super) color_scheme: Option<ColorScheme>,
210 pub(super) kitty_graphics: bool,
211 pub(super) program_status: Option<ProgramStatusCapabilities>,
212}
213
214impl Capabilities {
215 /// The [`ModeSetting`] the terminal reported for `mode`, or `None` if it
216 /// never reported on that mode.
217 ///
218 /// Use this when the distinction matters: [`ModeSetting::Set`] means the
219 /// mode is currently on, [`ModeSetting::PermanentlySet`] means it cannot
220 /// be turned off, and [`ModeSetting::NotRecognized`] is a definite "no"
221 /// rather than silence.
222 pub fn mode(&self, mode: Mode) -> Option<ModeSetting> {
223 self.modes.get(&mode).copied()
224 }
225
226 /// Whether the terminal reported `mode` as available, in any state.
227 ///
228 /// ```ignore
229 /// use uncurses::ansi::mode::Mode;
230 ///
231 /// if program.capabilities().supports(Mode::MOUSE_SGR_PIXEL) {
232 /// // pixel-accurate mouse reporting is available
233 /// }
234 /// ```
235 pub fn supports(&self, mode: Mode) -> bool {
236 self.mode(mode).is_some_and(ModeSetting::is_available)
237 }
238
239 /// Every mode report recorded so far, keyed by mode.
240 pub fn modes(&self) -> &BTreeMap<Mode, ModeSetting> {
241 &self.modes
242 }
243
244 /// The raw Primary DA (`CSI c`) attribute list, or `None` if the terminal
245 /// never answered. Entries are `None` where the terminal sent an empty
246 /// parameter.
247 ///
248 /// The first parameter is the terminal's architectural service class, not
249 /// a capability: `6` identifies a VT102 and `64` a VT420. Capability
250 /// numbers live in the parameters after it, and only for a VT220-class
251 /// terminal or later (`62`, `63`, `64`, `65`); xterm puts it as "the
252 /// VT100-style response parameters do not mean anything by themselves".
253 /// So `CSI ? 4 ; 6 c` is a VT132 identifying itself, even though `4` is
254 /// also the capability number for Sixel graphics on a VT220-class reply.
255 /// Read the class first and interpret the rest against it, rather than
256 /// searching the list for a number.
257 ///
258 /// The capability numbers are assigned by DEC and extended by terminal
259 /// authors, so they are reported unparsed.
260 pub fn primary_device_attributes(&self) -> Option<&[Option<u32>]> {
261 self.primary_device_attributes.as_deref()
262 }
263
264 /// The raw Secondary DA (`CSI > c`) attribute list, or `None` if the
265 /// terminal never answered. Conventionally terminal type, firmware
266 /// version, and hardware option, but the meaning of each entry varies by
267 /// terminal, so it is reported unparsed.
268 pub fn secondary_device_attributes(&self) -> Option<&[Option<u32>]> {
269 self.secondary_device_attributes.as_deref()
270 }
271
272 /// The Tertiary DA (`CSI = c`) terminal unit ID, or `None` if the
273 /// terminal never answered.
274 pub fn tertiary_device_attributes(&self) -> Option<&str> {
275 self.tertiary_device_attributes.as_deref()
276 }
277
278 /// Whether the terminal has answered a Kitty graphics query, which is the
279 /// protocol's own support test: a terminal that does not implement it
280 /// stays silent.
281 pub fn kitty_graphics(&self) -> bool {
282 self.kitty_graphics
283 }
284
285 /// Accepted OSC 7501 states and kinds, or `None` before a reply.
286 ///
287 /// `None` means support remains unconfirmed. A reply confirms support
288 /// even when both lists are empty. The decoder supplies fixed defaults
289 /// for absent fields and preserves names from protocol extensions.
290 ///
291 /// Send a query with [`Program::request_program_status`](super::Program::request_program_status).
292 pub fn program_status(&self) -> Option<&ProgramStatusCapabilities> {
293 self.program_status.as_ref()
294 }
295
296 /// The Kitty keyboard enhancements the terminal reported, or `None` if it
297 /// never answered `CSI ? u`. An answer of
298 /// [`empty`](KittyKeyboardFlags::empty) means the protocol is supported
299 /// with no enhancements currently active.
300 pub fn kitty_keyboard(&self) -> Option<KittyKeyboardFlags> {
301 self.kitty_keyboard
302 }
303
304 /// The xterm modifyOtherKeys mode the terminal reported, or `None` if it
305 /// never answered `CSI ? 4 m`.
306 pub fn modify_other_keys(&self) -> Option<ModifyOtherKeysMode> {
307 self.modify_other_keys
308 }
309
310 /// The terminal's self-reported name from XTVERSION (for example
311 /// `"XTerm(380)"`), or `None` if it never answered.
312 pub fn terminal_name(&self) -> Option<&str> {
313 self.terminal_name.as_deref()
314 }
315
316 /// The value the terminal reported for the XTGETTCAP capability `name`,
317 /// or `None` if it reported the capability as unsupported or was never
318 /// asked. Boolean capabilities report an empty string, so use
319 /// [`supports_termcap`](Self::supports_termcap) to test for presence.
320 pub fn termcap(&self, name: &str) -> Option<&str> {
321 self.termcap.get(name)?.as_deref()
322 }
323
324 /// Whether the terminal reported the XTGETTCAP capability `name` as
325 /// supported. `false` both for a capability reported unsupported and for
326 /// one never asked about; tell them apart with
327 /// [`termcap_reports`](Self::termcap_reports).
328 pub fn supports_termcap(&self, name: &str) -> bool {
329 matches!(self.termcap.get(name), Some(Some(_)))
330 }
331
332 /// Every XTGETTCAP reply recorded so far, keyed by capability name. A
333 /// value of `None` is the terminal reporting that capability as
334 /// unsupported, which is different from the key being absent.
335 pub fn termcap_reports(&self) -> &BTreeMap<String, Option<String>> {
336 &self.termcap
337 }
338
339 /// The terminal's default foreground color (`OSC 10`), or `None` if it
340 /// never answered.
341 pub fn foreground_color(&self) -> Option<Color> {
342 self.foreground_color
343 }
344
345 /// The terminal's default background color (`OSC 11`), or `None` if it
346 /// never answered.
347 pub fn background_color(&self) -> Option<Color> {
348 self.background_color
349 }
350
351 /// The terminal's cursor color (`OSC 12`), or `None` if it never
352 /// answered.
353 pub fn cursor_color(&self) -> Option<Color> {
354 self.cursor_color
355 }
356
357 /// The color the terminal reported for palette entry `index`
358 /// (`OSC 4 ; index ; ?`), or `None` if it never answered for that entry.
359 pub fn palette_color(&self, index: u8) -> Option<Color> {
360 self.palette.get(&index).copied()
361 }
362
363 /// Every palette color reported so far, keyed by index.
364 pub fn palette(&self) -> &BTreeMap<u8, Color> {
365 &self.palette
366 }
367
368 /// Whether the terminal is in its dark or light scheme (DEC mode 2031),
369 /// or `None` if it never reported one. Updated as the scheme changes
370 /// while [`enable_color_scheme_updates`](super::Program::enable_color_scheme_updates)
371 /// is on. This is the terminal's own preference flag, which a terminal
372 /// can report independently of the colors it uses; when it is absent,
373 /// [`background_color`](Self::background_color) is the fallback.
374 pub fn color_scheme(&self) -> Option<ColorScheme> {
375 self.color_scheme
376 }
377}