Skip to main content

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}