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};
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    /// Modes the application has taken a position on, by calling the matching
118    /// `enable_*` / `disable_*` method or by having one adopted on its behalf.
119    ///
120    /// The mode fields above cannot carry this: `false` reads the same whether
121    /// the app turned the mode off or never mentioned it. Discovery adopts a
122    /// preferred mode only for a mode absent from this set, so an explicit
123    /// `disable_*` issued before the terminal ever reports is not quietly
124    /// undone by the report when it arrives.
125    pub chosen: BTreeSet<Mode>,
126}
127
128impl Default for State {
129    fn default() -> Self {
130        Self {
131            cursor_style: CursorStyle::Default,
132            mouse: None,
133            bracketed_paste: false,
134            focus_events: false,
135            color_scheme_updates: false,
136            visibility_reports: false,
137            in_band_resize: false,
138            window_title: None,
139            icon_name: None,
140            foreground_color: None,
141            background_color: None,
142            cursor_color: None,
143            palette: BTreeMap::new(),
144            modify_other_keys: ModifyOtherKeysMode::Disabled,
145            pointer_shape: None,
146            progress: None,
147            kitty_keyboard: KittyKeyboardFlags::empty(),
148            alt_screen: false,
149            cursor_visible: true,
150            grapheme_clusters: false,
151            chosen: BTreeSet::new(),
152        }
153    }
154}
155
156/// What the terminal told us about itself.
157///
158/// This holds the replies themselves, not a summary of them: the
159/// [`ModeSetting`] reported for every mode that was asked about, the raw
160/// device-attribute lists, the reported colors, and so on. A reply that says
161/// "I do not recognize that" is recorded too, so `None` from an accessor
162/// generally means the terminal never answered, which is different from
163/// answering no. [`termcap`](Self::termcap) is the exception, folding both
164/// into `None`; use [`termcap_reports`](Self::termcap_reports) to separate
165/// them.
166///
167/// Everything here is what the terminal reported, never what the facade told
168/// it. The two are easy to confuse where both exist: a
169/// [`background_color`](Self::background_color) recorded here stays the
170/// terminal's own default even after
171/// [`set_background_color`](super::Program::set_background_color) overrides
172/// it.
173///
174/// Only replies land here, so questions the environment can also answer are
175/// deliberately absent. Direct-color support is the example: `COLORTERM` and
176/// `TERM` establish it as readily as an XTGETTCAP reply does, so the answer is
177/// [`Screen::color_profile`](crate::screen::Screen::color_profile), which
178/// folds in both, and what remains here is the reply itself via
179/// [`supports_termcap`](Self::supports_termcap).
180///
181/// The facade records these as reply events flow through
182/// [`read_event`](super::Program::read_event) /
183/// [`try_read_event`](super::Program::try_read_event), whichever way the
184/// question was put: see
185/// [`query_capabilities`](super::Program::query_capabilities), which asks for
186/// only some of this, and takes extra bytes so you can ask for the rest. An
187/// individual `request_*` method fills a single entry, and a few reports
188/// arrive unprompted once their mode is on, such as a color-scheme change
189/// under DEC mode 2031. Read it back with
190/// [`Program::capabilities`](super::Program::capabilities).
191#[derive(Debug, Clone, Default, PartialEq, Eq)]
192pub struct Capabilities {
193    pub(super) modes: BTreeMap<Mode, ModeSetting>,
194    pub(super) primary_device_attributes: Option<Vec<Option<u32>>>,
195    pub(super) secondary_device_attributes: Option<Vec<Option<u32>>>,
196    pub(super) tertiary_device_attributes: Option<String>,
197    pub(super) kitty_keyboard: Option<KittyKeyboardFlags>,
198    pub(super) modify_other_keys: Option<ModifyOtherKeysMode>,
199    pub(super) terminal_name: Option<String>,
200    pub(super) termcap: BTreeMap<String, Option<String>>,
201    pub(super) foreground_color: Option<Color>,
202    pub(super) background_color: Option<Color>,
203    pub(super) cursor_color: Option<Color>,
204    pub(super) palette: BTreeMap<u8, Color>,
205    pub(super) color_scheme: Option<ColorScheme>,
206    pub(super) kitty_graphics: bool,
207}
208
209impl Capabilities {
210    /// The [`ModeSetting`] the terminal reported for `mode`, or `None` if it
211    /// never reported on that mode.
212    ///
213    /// Use this when the distinction matters: [`ModeSetting::Set`] means the
214    /// mode is currently on, [`ModeSetting::PermanentlySet`] means it cannot
215    /// be turned off, and [`ModeSetting::NotRecognized`] is a definite "no"
216    /// rather than silence.
217    pub fn mode(&self, mode: Mode) -> Option<ModeSetting> {
218        self.modes.get(&mode).copied()
219    }
220
221    /// Whether the terminal reported `mode` as available, in any state.
222    ///
223    /// ```ignore
224    /// use uncurses::ansi::mode::Mode;
225    ///
226    /// if program.capabilities().supports(Mode::MOUSE_SGR_PIXEL) {
227    ///     // pixel-accurate mouse reporting is available
228    /// }
229    /// ```
230    pub fn supports(&self, mode: Mode) -> bool {
231        self.mode(mode).is_some_and(ModeSetting::is_available)
232    }
233
234    /// Every mode report recorded so far, keyed by mode.
235    pub fn modes(&self) -> &BTreeMap<Mode, ModeSetting> {
236        &self.modes
237    }
238
239    /// The raw Primary DA (`CSI c`) attribute list, or `None` if the terminal
240    /// never answered. Entries are `None` where the terminal sent an empty
241    /// parameter.
242    ///
243    /// The first parameter is the terminal's architectural service class, not
244    /// a capability: `6` identifies a VT102 and `64` a VT420. Capability
245    /// numbers live in the parameters after it, and only for a VT220-class
246    /// terminal or later (`62`, `63`, `64`, `65`); xterm puts it as "the
247    /// VT100-style response parameters do not mean anything by themselves".
248    /// So `CSI ? 4 ; 6 c` is a VT132 identifying itself, even though `4` is
249    /// also the capability number for Sixel graphics on a VT220-class reply.
250    /// Read the class first and interpret the rest against it, rather than
251    /// searching the list for a number.
252    ///
253    /// The capability numbers are assigned by DEC and extended by terminal
254    /// authors, so they are reported unparsed.
255    pub fn primary_device_attributes(&self) -> Option<&[Option<u32>]> {
256        self.primary_device_attributes.as_deref()
257    }
258
259    /// The raw Secondary DA (`CSI > c`) attribute list, or `None` if the
260    /// terminal never answered. Conventionally terminal type, firmware
261    /// version, and hardware option, but the meaning of each entry varies by
262    /// terminal, so it is reported unparsed.
263    pub fn secondary_device_attributes(&self) -> Option<&[Option<u32>]> {
264        self.secondary_device_attributes.as_deref()
265    }
266
267    /// The Tertiary DA (`CSI = c`) terminal unit ID, or `None` if the
268    /// terminal never answered.
269    pub fn tertiary_device_attributes(&self) -> Option<&str> {
270        self.tertiary_device_attributes.as_deref()
271    }
272
273    /// Whether the terminal has answered a Kitty graphics query, which is the
274    /// protocol's own support test: a terminal that does not implement it
275    /// stays silent.
276    pub fn kitty_graphics(&self) -> bool {
277        self.kitty_graphics
278    }
279
280    /// The Kitty keyboard enhancements the terminal reported, or `None` if it
281    /// never answered `CSI ? u`. An answer of
282    /// [`empty`](KittyKeyboardFlags::empty) means the protocol is supported
283    /// with no enhancements currently active.
284    pub fn kitty_keyboard(&self) -> Option<KittyKeyboardFlags> {
285        self.kitty_keyboard
286    }
287
288    /// The xterm modifyOtherKeys mode the terminal reported, or `None` if it
289    /// never answered `CSI ? 4 m`.
290    pub fn modify_other_keys(&self) -> Option<ModifyOtherKeysMode> {
291        self.modify_other_keys
292    }
293
294    /// The terminal's self-reported name from XTVERSION (for example
295    /// `"XTerm(380)"`), or `None` if it never answered.
296    pub fn terminal_name(&self) -> Option<&str> {
297        self.terminal_name.as_deref()
298    }
299
300    /// The value the terminal reported for the XTGETTCAP capability `name`,
301    /// or `None` if it reported the capability as unsupported or was never
302    /// asked. Boolean capabilities report an empty string, so use
303    /// [`supports_termcap`](Self::supports_termcap) to test for presence.
304    pub fn termcap(&self, name: &str) -> Option<&str> {
305        self.termcap.get(name)?.as_deref()
306    }
307
308    /// Whether the terminal reported the XTGETTCAP capability `name` as
309    /// supported. `false` both for a capability reported unsupported and for
310    /// one never asked about; tell them apart with
311    /// [`termcap_reports`](Self::termcap_reports).
312    pub fn supports_termcap(&self, name: &str) -> bool {
313        matches!(self.termcap.get(name), Some(Some(_)))
314    }
315
316    /// Every XTGETTCAP reply recorded so far, keyed by capability name. A
317    /// value of `None` is the terminal reporting that capability as
318    /// unsupported, which is different from the key being absent.
319    pub fn termcap_reports(&self) -> &BTreeMap<String, Option<String>> {
320        &self.termcap
321    }
322
323    /// The terminal's default foreground color (`OSC 10`), or `None` if it
324    /// never answered.
325    pub fn foreground_color(&self) -> Option<Color> {
326        self.foreground_color
327    }
328
329    /// The terminal's default background color (`OSC 11`), or `None` if it
330    /// never answered.
331    pub fn background_color(&self) -> Option<Color> {
332        self.background_color
333    }
334
335    /// The terminal's cursor color (`OSC 12`), or `None` if it never
336    /// answered.
337    pub fn cursor_color(&self) -> Option<Color> {
338        self.cursor_color
339    }
340
341    /// The color the terminal reported for palette entry `index`
342    /// (`OSC 4 ; index ; ?`), or `None` if it never answered for that entry.
343    pub fn palette_color(&self, index: u8) -> Option<Color> {
344        self.palette.get(&index).copied()
345    }
346
347    /// Every palette color reported so far, keyed by index.
348    pub fn palette(&self) -> &BTreeMap<u8, Color> {
349        &self.palette
350    }
351
352    /// Whether the terminal is in its dark or light scheme (DEC mode 2031),
353    /// or `None` if it never reported one. Updated as the scheme changes
354    /// while [`enable_color_scheme_updates`](super::Program::enable_color_scheme_updates)
355    /// is on. This is the terminal's own preference flag, which a terminal
356    /// can report independently of the colors it uses; when it is absent,
357    /// [`background_color`](Self::background_color) is the fallback.
358    pub fn color_scheme(&self) -> Option<ColorScheme> {
359        self.color_scheme
360    }
361}