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}