Skip to main content

uncurses/program/
modes.rs

1//! Non-render terminal/input mode toggles for the [`Program`] facade —
2//! cursor style, mouse tracking, bracketed paste, focus reporting,
3//! color-scheme update reports, in-band resize reports, window title,
4//! progress reports, and the default foreground/background/cursor colors.
5//!
6//! Each setter emits its escape bytes through the owned renderer and
7//! flushes immediately, so the mode change takes effect on the terminal
8//! right away and the call returns [`io::Result<()>`](std::io::Result). A
9//! setter whose tracked value is unchanged is a no-op and performs no I/O.
10//!
11//! [`Program`]: super::Program
12
13use std::io::{self, Write};
14
15use crate::ansi::{self, color, cursor, kitty, mode, progress, status, xterm};
16use crate::color::Color;
17use crate::event::Input;
18
19use super::MouseTracking;
20use super::Program;
21use super::ProgressState;
22use super::cursor::CursorShape;
23
24/// DEC private modes for the mouse tracking modes and encodings this
25/// library supports. Reset together to unconditionally turn mouse
26/// reporting off.
27const MOUSE_MODES: &[mode::Mode] = &[
28    mode::Mode::MOUSE_X10,
29    mode::Mode::MOUSE_NORMAL,
30    mode::Mode::MOUSE_BUTTON,
31    mode::Mode::MOUSE_ANY,
32    mode::Mode::MOUSE_SGR,
33    mode::Mode::MOUSE_SGR_PIXEL,
34];
35
36impl<I: Input, O: Write> Program<I, O> {
37    /// Set the cursor shape and blinking state (`DECSCUSR`) and flush.
38    ///
39    /// * `shape` — the visual cursor shape ([`Block`](CursorShape::Block),
40    ///   [`Underline`](CursorShape::Underline), or [`Bar`](CursorShape::Bar)).
41    /// * `blinking` — whether the cursor blinks.
42    pub fn set_cursor_style(&mut self, shape: CursorShape, blinking: bool) -> io::Result<()> {
43        let style = shape.style(blinking);
44        cursor::write_cursor_style(&mut self.screen, style)?;
45        self.state.cursor_style = style;
46        self.screen.flush()
47    }
48
49    /// Ring the terminal bell (`BEL`) and flush.
50    pub fn beep(&mut self) -> io::Result<()> {
51        self.screen.write_all(b"\x07")?;
52        self.screen.flush()
53    }
54
55    /// Set the pointer (mouse cursor) shape (`OSC 22`) and flush.
56    ///
57    /// `shape` is a pointer shape name such as `"default"`, `"text"`, or
58    /// `"pointer"`. The shape is recorded for save/restore.
59    pub fn set_pointer_shape(&mut self, shape: &str) -> io::Result<()> {
60        cursor::write_set_pointer_shape(&mut self.screen, shape)?;
61        self.state.pointer_shape = Some(shape.to_string());
62        self.screen.flush()
63    }
64
65    /// Reset the pointer (mouse cursor) shape to the terminal default
66    /// (`OSC 22 ; default`) and flush.
67    ///
68    /// Uses the explicit `"default"` shape name rather than an empty one: some
69    /// terminals don't treat an empty `OSC 22` as a reset.
70    pub fn reset_pointer_shape(&mut self) -> io::Result<()> {
71        cursor::write_set_pointer_shape(&mut self.screen, "default")?;
72        self.state.pointer_shape = None;
73        self.screen.flush()
74    }
75
76    /// Report progress to the terminal (`OSC 9;4`) and flush.
77    ///
78    /// Terminals that support it show the progress in the taskbar, tab, or
79    /// window chrome; the rest ignore the sequence. Percentages are clamped
80    /// to `0..=100`.
81    ///
82    /// The state is recorded for save/restore: it is removed on a shell
83    /// handoff ([`pause`](Self::pause), [`suspend`](Self::suspend),
84    /// [`finish`](Self::finish)) and re-reported by
85    /// [`resume`](Self::resume). Take it down with
86    /// [`reset_progress_state`](Self::reset_progress_state).
87    pub fn set_progress_state(&mut self, progress: ProgressState) -> io::Result<()> {
88        progress.write(&mut self.screen)?;
89        self.state.progress = Some(progress);
90        self.screen.flush()
91    }
92
93    /// Remove the progress report (`OSC 9;4;0`) and flush.
94    pub fn reset_progress_state(&mut self) -> io::Result<()> {
95        self.screen.write_all(progress::RESET_PROGRESS_BAR)?;
96        self.state.progress = None;
97        self.screen.flush()
98    }
99
100    /// Enable mouse tracking and flush.
101    ///
102    /// This emits exactly what is asked for and does not consult terminal
103    /// [`capabilities`](Self::capabilities). Unsupported modes are ignored by
104    /// the terminal, and because the mode requests are mutually exclusive, each
105    /// terminal settles on the most capable variant it understands:
106    ///
107    /// * Tracking: button (`1000`) and button-event (`1002`) are always
108    ///   requested, so a terminal reports drag where it can and plain clicks
109    ///   otherwise. With [`MouseTracking::MOTION`], any-event tracking (`1003`)
110    ///   is added on top, so motion without a button held is reported where
111    ///   supported.
112    /// * Encoding: SGR (`1006`) is always requested, since the legacy byte
113    ///   encoding caps coordinates at 223 and SGR is universally supported.
114    ///   With [`MouseTracking::PIXELS`], SGR-pixel (`1016`) is added; terminals
115    ///   that support it report pixel coordinates, and the rest fall back to
116    ///   SGR cell coordinates.
117    ///
118    /// Pass [`MouseTracking::empty()`] for basic button tracking with no
119    /// extras. To turn mouse tracking off, call [`disable_mouse`](Self::disable_mouse).
120    ///
121    /// To learn which variant a terminal actually chose, read
122    /// [`capabilities`](Self::capabilities) (for example
123    /// [`supports(Mode::MOUSE_SGR_PIXEL)`](super::Capabilities::supports) to
124    /// tell whether pixels or cells will arrive). When pixel reporting is active, a
125    /// [`Mouse`](crate::event::Mouse) event's pixel coordinates can be converted
126    /// to cells with [`mouse_pixels_to_cells`](Self::mouse_pixels_to_cells).
127    ///
128    /// Mouse coordinates are physical screen coordinates. Inline, follow this
129    /// with [`request_origin`](Self::request_origin) to learn where the managed
130    /// area sits, so [`mouse_to_origin`](Self::mouse_to_origin) can map them
131    /// into it.
132    ///
133    /// The request is recorded for save/restore.
134    pub fn enable_mouse(&mut self, tracking: MouseTracking) -> io::Result<()> {
135        // Drop any prior tracking first so modes don't stack ambiguously.
136        mode::write_reset_mode(&mut self.screen, MOUSE_MODES)?;
137        self.write_mouse_modes(tracking, true)?;
138        self.state.mouse = Some(tracking);
139        self.screen.flush()
140    }
141
142    /// Disable all mouse tracking modes and encodings, and flush.
143    pub fn disable_mouse(&mut self) -> io::Result<()> {
144        mode::write_reset_mode(&mut self.screen, MOUSE_MODES)?;
145        self.state.mouse = None;
146        self.screen.flush()
147    }
148
149    /// Set or reset the mouse tracking modes and encoding for the given
150    /// tracking flags. `enable` selects set vs reset. The modes are emitted in
151    /// ascending order so that, where the requests are mutually exclusive, the
152    /// most capable supported variant wins (`1003` over `1002`/`1000`, `1016`
153    /// over `1006`).
154    fn write_mouse_modes(&mut self, tracking: MouseTracking, enable: bool) -> io::Result<()> {
155        // Always request plain and button-event tracking as a fallback pair,
156        // adding any-event tracking on top when motion is requested.
157        let mut modes = vec![mode::Mode::MOUSE_NORMAL, mode::Mode::MOUSE_BUTTON];
158        if tracking.contains(MouseTracking::MOTION) {
159            modes.push(mode::Mode::MOUSE_ANY);
160        }
161        // Always request SGR encoding; add SGR-pixel on top when pixels are
162        // requested. Terminals without pixel support fall back to SGR cells.
163        modes.push(mode::Mode::MOUSE_SGR);
164        if tracking.contains(MouseTracking::PIXELS) {
165            modes.push(mode::Mode::MOUSE_SGR_PIXEL);
166        }
167        if enable {
168            mode::write_set_mode(&mut self.screen, &modes)
169        } else {
170            mode::write_reset_mode(&mut self.screen, &modes)
171        }
172    }
173
174    /// Enable bracketed paste mode (DEC private mode 2004) and flush.
175    pub fn enable_bracketed_paste(&mut self) -> io::Result<()> {
176        mode::Mode::BRACKETED_PASTE.set(&mut self.screen)?;
177        self.state.bracketed_paste = true;
178        self.screen.flush()
179    }
180
181    /// Disable bracketed paste mode (DEC private mode 2004) and flush.
182    pub fn disable_bracketed_paste(&mut self) -> io::Result<()> {
183        mode::Mode::BRACKETED_PASTE.reset(&mut self.screen)?;
184        self.state.bracketed_paste = false;
185        self.screen.flush()
186    }
187
188    /// Enable focus in/out reporting (DEC private mode 1004) and flush.
189    pub fn enable_focus_events(&mut self) -> io::Result<()> {
190        mode::Mode::FOCUS.set(&mut self.screen)?;
191        self.state.focus_events = true;
192        self.screen.flush()
193    }
194
195    /// Disable focus in/out reporting (DEC private mode 1004) and flush.
196    pub fn disable_focus_events(&mut self) -> io::Result<()> {
197        mode::Mode::FOCUS.reset(&mut self.screen)?;
198        self.state.focus_events = false;
199        self.screen.flush()
200    }
201
202    /// Enable color-scheme update notifications (DEC private mode 2031) and
203    /// flush. The terminal then sends a `CSI ? 997 ; {1|2} n` report
204    /// whenever the user or operating system switches between dark and
205    /// light schemes; these surface as [`Event::ColorScheme`]. The report
206    /// indicates only the dark/light preference, not the actual colors.
207    ///
208    /// [`Event::ColorScheme`]: crate::event::Event::ColorScheme
209    pub fn enable_color_scheme_updates(&mut self) -> io::Result<()> {
210        mode::Mode::LIGHT_DARK.set(&mut self.screen)?;
211        self.state.color_scheme_updates = true;
212        self.screen.flush()
213    }
214
215    /// Disable color-scheme update notifications (DEC private mode 2031) and
216    /// flush.
217    pub fn disable_color_scheme_updates(&mut self) -> io::Result<()> {
218        mode::Mode::LIGHT_DARK.reset(&mut self.screen)?;
219        self.state.color_scheme_updates = false;
220        self.screen.flush()
221    }
222
223    /// Enable terminal visibility reports (DEC private mode 2033) and flush.
224    /// The terminal then sends a `CSI ? 999 ; {1|2} n` report whenever the
225    /// view stops being observable or becomes observable again, which the
226    /// decoder surfaces as [`Event::Visibility`]. Being covered by another
227    /// window, scrolled out of a tab, or on a minimized window all count.
228    ///
229    /// The report is advisory and asymmetric: [`Visibility::Hidden`] means
230    /// the terminal knows nothing can be seen, so a render can be skipped,
231    /// while [`Visibility::Visible`] only means it may be observable. A
232    /// terminal that never reports is therefore treated as visible, which is
233    /// what makes ignoring this mode safe.
234    ///
235    /// Visibility is independent of focus: an unfocused window is usually
236    /// still visible.
237    ///
238    /// [`Event::Visibility`]: crate::event::Event::Visibility
239    /// [`Visibility::Hidden`]: crate::event::Visibility::Hidden
240    /// [`Visibility::Visible`]: crate::event::Visibility::Visible
241    pub fn enable_visibility_reports(&mut self) -> io::Result<()> {
242        mode::Mode::VISIBILITY_REPORTS.set(&mut self.screen)?;
243        self.state.visibility_reports = true;
244        self.screen.flush()
245    }
246
247    /// Disable terminal visibility reports (DEC private mode 2033) and flush.
248    pub fn disable_visibility_reports(&mut self) -> io::Result<()> {
249        mode::Mode::VISIBILITY_REPORTS.reset(&mut self.screen)?;
250        self.state.visibility_reports = false;
251        self.screen.flush()
252    }
253
254    /// Ask the terminal to report its visibility once and flush.
255    ///
256    /// The reply arrives as [`Event::Visibility`], the same event the
257    /// unsolicited reports use. This does not enable or disable
258    /// [`enable_visibility_reports`](Self::enable_visibility_reports), so it
259    /// is the way to read visibility without subscribing to changes. A
260    /// terminal that does not implement DEC private mode 2033 answers
261    /// nothing at all, so never block waiting for this reply.
262    ///
263    /// [`Event::Visibility`]: crate::event::Event::Visibility
264    pub fn request_visibility(&mut self) -> io::Result<()> {
265        self.screen
266            .write_all(status::REQUEST_VISIBILITY_REPORT)
267            .and_then(|()| self.screen.flush())
268    }
269
270    /// Enable in-band resize notifications (DEC private mode 2048) and
271    /// flush. The terminal then reports every surface size change in-band
272    /// as a `CSI 48 ; height ; width ; ypixel ; xpixel t` sequence, which
273    /// the decoder surfaces as [`Event::Resize`] — no `SIGWINCH` handler
274    /// required. The event source stops synthesizing resizes from `SIGWINCH`
275    /// while this is on, so a size change is reported once, not twice.
276    ///
277    /// Only call this after [`capabilities`](Self::capabilities) reports
278    /// [`supports(Mode::IN_BAND_RESIZE)`](super::Capabilities::supports); a
279    /// terminal that ignores the mode would otherwise leave you with no
280    /// resize events at all.
281    ///
282    /// [`Event::Resize`]: crate::event::Event::Resize
283    pub fn enable_in_band_resize(&mut self) -> io::Result<()> {
284        mode::Mode::IN_BAND_RESIZE.set(&mut self.screen)?;
285        self.state.in_band_resize = true;
286        self.state.chosen.insert(mode::Mode::IN_BAND_RESIZE);
287        self.source.lock().unwrap().set_handle_resize(false);
288        self.screen.flush()
289    }
290
291    /// Disable in-band resize notifications (DEC private mode 2048) and
292    /// flush, handing resize reporting back to the `SIGWINCH` path.
293    pub fn disable_in_band_resize(&mut self) -> io::Result<()> {
294        mode::Mode::IN_BAND_RESIZE.reset(&mut self.screen)?;
295        self.state.in_band_resize = false;
296        self.state.chosen.insert(mode::Mode::IN_BAND_RESIZE);
297        self.source.lock().unwrap().set_handle_resize(true);
298        self.screen.flush()
299    }
300
301    /// Set both the window title and icon name (`OSC 0`) and flush.
302    ///
303    /// An empty `title` clears both overrides, restoring the terminal's
304    /// defaults; the state is recorded as unset so teardown and resume skip
305    /// them. To set just one, use
306    /// [`set_window_title`](Self::set_window_title) (`OSC 2`) or
307    /// [`set_icon_title`](Self::set_icon_title) (`OSC 1`).
308    pub fn set_title(&mut self, title: &str) -> io::Result<()> {
309        ansi::title::write_window_title_and_icon(&mut self.screen, title)?;
310        let stored = (!title.is_empty()).then(|| title.to_string());
311        self.state.window_title = stored.clone();
312        self.state.icon_name = stored;
313        self.screen.flush()
314    }
315
316    /// Set the window title only (`OSC 2`) and flush.
317    ///
318    /// An empty `title` clears the override, restoring the terminal's default
319    /// window title. Unlike [`set_title`](Self::set_title) (`OSC 0`), this
320    /// leaves the icon name untouched.
321    pub fn set_window_title(&mut self, title: &str) -> io::Result<()> {
322        ansi::title::write_window_title(&mut self.screen, title)?;
323        self.state.window_title = (!title.is_empty()).then(|| title.to_string());
324        self.screen.flush()
325    }
326
327    /// Set the icon name only (`OSC 1`) and flush.
328    ///
329    /// An empty `title` clears the override, restoring the terminal's default
330    /// icon name. Unlike [`set_title`](Self::set_title) (`OSC 0`), this leaves
331    /// the window title untouched.
332    pub fn set_icon_title(&mut self, title: &str) -> io::Result<()> {
333        ansi::title::write_icon_name(&mut self.screen, title)?;
334        self.state.icon_name = (!title.is_empty()).then(|| title.to_string());
335        self.screen.flush()
336    }
337
338    /// Enter the alternate screen buffer (DECSET 1049) and flush.
339    ///
340    /// The managed area becomes the whole viewport: the screen switches to
341    /// absolute addressing and repaints in full on the next
342    /// [`render`](crate::screen::Screen::render). The normal buffer, its scrollback, and the
343    /// shell prompt are left untouched underneath and come back on
344    /// [`exit_alt_screen`](Self::exit_alt_screen).
345    ///
346    /// Cursor visibility and the Kitty keyboard stack are per-screen-buffer
347    /// on some terminals, so both are re-asserted on the newly active buffer.
348    pub fn enter_alt_screen(&mut self) -> io::Result<()> {
349        self.set_alt_screen(true)
350    }
351
352    /// Leave the alternate screen buffer (DECRST 1049) and flush, restoring
353    /// the normal buffer and its scrollback. The managed area becomes an
354    /// inline band again, addressed with relative moves.
355    pub fn exit_alt_screen(&mut self) -> io::Result<()> {
356        self.set_alt_screen(false)
357    }
358
359    /// Emit the alternate-screen switch and bring the screen and the
360    /// per-buffer modes with it. Always emits the mode; the bookkeeping runs
361    /// only on an actual transition.
362    pub(super) fn set_alt_screen(&mut self, enter: bool) -> io::Result<()> {
363        let changed = self.state.alt_screen != enter;
364        if changed && enter {
365            // Capture the inline anchor before the buffer switch hides it.
366            self.screen.save_cursor();
367        }
368        if enter {
369            mode::Mode::ALT_SCREEN_SAVE_CURSOR.set(&mut self.screen)?;
370        } else {
371            mode::Mode::ALT_SCREEN_SAVE_CURSOR.reset(&mut self.screen)?;
372        }
373        self.state.alt_screen = enter;
374        if changed {
375            self.screen.set_fullscreen(enter);
376            // Some terminals track DECTCEM and the Kitty keyboard stack per
377            // screen buffer, so the newly active one can disagree with what
378            // this program asked for. Re-assert both.
379            if !self.state.cursor_visible {
380                mode::Mode::CURSOR_VISIBLE.reset(&mut self.screen)?;
381            } else {
382                mode::Mode::CURSOR_VISIBLE.set(&mut self.screen)?;
383            }
384            if !self.state.kitty_keyboard.is_empty() {
385                kitty::write_set_kitty_keyboard(
386                    &mut self.screen,
387                    self.state.kitty_keyboard,
388                    kitty::KittyKeyboardMode::Set,
389                )?;
390            }
391        }
392        self.screen.flush()
393    }
394
395    /// Show the terminal cursor (DECSET 25) and flush.
396    ///
397    /// Also tells the screen, which hides a visible cursor around each frame's
398    /// cell diff so it does not dance across cells as the renderer
399    /// repositions it.
400    pub fn show_cursor(&mut self) -> io::Result<()> {
401        mode::Mode::CURSOR_VISIBLE.set(&mut self.screen)?;
402        self.state.cursor_visible = true;
403        self.screen.set_cursor_visible(true);
404        self.screen.flush()
405    }
406
407    /// Hide the terminal cursor (DECRST 25) and flush.
408    pub fn hide_cursor(&mut self) -> io::Result<()> {
409        mode::Mode::CURSOR_VISIBLE.reset(&mut self.screen)?;
410        self.state.cursor_visible = false;
411        self.screen.set_cursor_visible(false);
412        self.screen.flush()
413    }
414
415    /// Enable Unicode core / grapheme-cluster mode (DECSET 2027) and flush,
416    /// switching the screen to measure text per extended grapheme cluster so
417    /// it agrees with the terminal.
418    ///
419    /// Only call this after [`capabilities`](Self::capabilities) reports
420    /// [`supports(Mode::UNICODE_CORE)`](super::Capabilities::supports); a
421    /// terminal that ignores the mode still measures per code point, and the
422    /// two disagreeing misplaces every cell after the first cluster on a line.
423    pub fn enable_grapheme_clusters(&mut self) -> io::Result<()> {
424        mode::Mode::UNICODE_CORE.set(&mut self.screen)?;
425        self.state.grapheme_clusters = true;
426        self.state.chosen.insert(mode::Mode::UNICODE_CORE);
427        self.screen.set_grapheme_clusters(true);
428        self.screen.flush()
429    }
430
431    /// Disable grapheme-cluster mode (DECRST 2027) and flush, returning the
432    /// screen to per-code-point (wcwidth-style) measurement.
433    pub fn disable_grapheme_clusters(&mut self) -> io::Result<()> {
434        mode::Mode::UNICODE_CORE.reset(&mut self.screen)?;
435        self.state.grapheme_clusters = false;
436        self.state.chosen.insert(mode::Mode::UNICODE_CORE);
437        self.screen.set_grapheme_clusters(false);
438        self.screen.flush()
439    }
440
441    /// Set the per-screen-buffer Kitty keyboard enhancements and flush.
442    /// `Some(flags)` enables the selected progressive-enhancement bits;
443    /// `None` disables every enhancement.
444    ///
445    /// The Kitty stack is per-screen-buffer, so the flags are re-applied on
446    /// the newly active buffer by [`enter_alt_screen`](Self::enter_alt_screen)
447    /// / [`exit_alt_screen`](Self::exit_alt_screen), as part of the switch.
448    pub fn set_kitty_keyboard(
449        &mut self,
450        flags: Option<kitty::KittyKeyboardFlags>,
451    ) -> io::Result<()> {
452        let flags = flags.unwrap_or_else(kitty::KittyKeyboardFlags::empty);
453        kitty::write_set_kitty_keyboard(&mut self.screen, flags, kitty::KittyKeyboardMode::Set)?;
454        self.state.kitty_keyboard = flags;
455        self.screen.flush()
456    }
457
458    /// Set the xterm modifyOtherKeys mode (`CSI > 4 ; n m`) and flush.
459    /// Passing [`ModifyOtherKeysMode::Disabled`] resets it (`CSI > 4 m`).
460    /// The mode is recorded so [`Program::finish`](super::Program::finish)
461    /// can reset it and [`Program::resume`](super::Program::resume) re-apply
462    /// it.
463    ///
464    /// [`ModifyOtherKeysMode::Disabled`]: crate::event::ModifyOtherKeysMode::Disabled
465    pub fn set_modify_other_keys(
466        &mut self,
467        mode: crate::event::ModifyOtherKeysMode,
468    ) -> io::Result<()> {
469        use crate::event::ModifyOtherKeysMode;
470        match mode {
471            ModifyOtherKeysMode::Disabled => {
472                self.screen.write_all(xterm::RESET_MODIFY_OTHER_KEYS)?
473            }
474            ModifyOtherKeysMode::Mode1 => self.screen.write_all(xterm::SET_MODIFY_OTHER_KEYS_1)?,
475            ModifyOtherKeysMode::Mode2 => self.screen.write_all(xterm::SET_MODIFY_OTHER_KEYS_2)?,
476        }
477        self.state.modify_other_keys = mode;
478        self.screen.flush()
479    }
480
481    /// Set the default foreground color (`OSC 10`) and flush. The color is
482    /// converted to 24-bit RGB and emitted as `rgb:RRRR/GGGG/BBBB`, and is
483    /// recorded so [`Program::finish`](super::Program::finish) can restore
484    /// the terminal default and [`Program::resume`](super::Program::resume)
485    /// can re-apply it.
486    pub fn set_foreground_color(&mut self, color: Color) -> io::Result<()> {
487        let (r, g, b) = color.to_rgb();
488        color::write_set_foreground_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
489        self.state.foreground_color = Some(color);
490        self.screen.flush()
491    }
492
493    /// Restore the terminal's default foreground color (`OSC 110`) and
494    /// flush.
495    pub fn reset_foreground_color(&mut self) -> io::Result<()> {
496        self.screen.write_all(color::RESET_FOREGROUND_COLOR)?;
497        self.state.foreground_color = None;
498        self.screen.flush()
499    }
500
501    /// Set the default background color (`OSC 11`) and flush. See
502    /// [`set_foreground_color`](Self::set_foreground_color) for
503    /// state-tracking semantics.
504    pub fn set_background_color(&mut self, color: Color) -> io::Result<()> {
505        let (r, g, b) = color.to_rgb();
506        color::write_set_background_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
507        self.state.background_color = Some(color);
508        self.screen.flush()
509    }
510
511    /// Restore the terminal's default background color (`OSC 111`) and
512    /// flush.
513    pub fn reset_background_color(&mut self) -> io::Result<()> {
514        self.screen.write_all(color::RESET_BACKGROUND_COLOR)?;
515        self.state.background_color = None;
516        self.screen.flush()
517    }
518
519    /// Set the cursor color (`OSC 12`) and flush. See
520    /// [`set_foreground_color`](Self::set_foreground_color) for
521    /// state-tracking semantics.
522    pub fn set_cursor_color(&mut self, color: Color) -> io::Result<()> {
523        let (r, g, b) = color.to_rgb();
524        color::write_set_cursor_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
525        self.state.cursor_color = Some(color);
526        self.screen.flush()
527    }
528
529    /// Restore the terminal's default cursor color (`OSC 112`) and flush.
530    pub fn reset_cursor_color(&mut self) -> io::Result<()> {
531        self.screen.write_all(color::RESET_CURSOR_COLOR)?;
532        self.state.cursor_color = None;
533        self.screen.flush()
534    }
535
536    /// Set a terminal palette color by index (`OSC 4`) and flush. The
537    /// override is tracked so [`Program::finish`](super::Program::finish) can
538    /// restore it and [`Program::resume`](super::Program::resume) re-apply it.
539    pub fn set_palette_color(&mut self, index: u8, color: Color) -> io::Result<()> {
540        let (r, g, b) = color.to_rgb();
541        color::write_set_palette_color(&mut self.screen, index, &color::xparse_rgb(r, g, b))?;
542        self.state.palette.insert(index, color);
543        self.screen.flush()
544    }
545
546    /// Reset a single terminal palette color to its default
547    /// (`OSC 104 ; index`) and flush.
548    pub fn reset_palette_color(&mut self, index: u8) -> io::Result<()> {
549        color::write_reset_palette_color(&mut self.screen, index)?;
550        self.state.palette.remove(&index);
551        self.screen.flush()
552    }
553
554    /// Reset the entire terminal palette to its defaults (`OSC 104`) and
555    /// flush, clearing every tracked palette override.
556    pub fn reset_palette_colors(&mut self) -> io::Result<()> {
557        self.screen.write_all(color::RESET_PALETTE_COLORS)?;
558        self.state.palette.clear();
559        self.screen.flush()
560    }
561
562    /// Stage the teardown of every mode currently held — non-render modes
563    /// (cursor style, mouse, paste, focus, colors, title, …) followed by the
564    /// render-coupled modes (cursor visibility, alternate screen, Kitty
565    /// keyboard, Unicode core) — returning the terminal to a clean baseline
566    /// before handing control back to the shell. Pure write — does not mutate
567    /// the tracked state, so a later [`restore`](Self::restore) re-applies the
568    /// same modes verbatim. The caller flushes.
569    pub(super) fn reset(&mut self) -> io::Result<()> {
570        // --- Non-render modes ---
571        if self.state.cursor_style != cursor::CursorStyle::Default {
572            cursor::write_cursor_style(&mut self.screen, cursor::CursorStyle::Default)?;
573        }
574        if self.state.bracketed_paste {
575            mode::Mode::BRACKETED_PASTE.reset(&mut self.screen)?;
576        }
577        if self.state.focus_events {
578            mode::Mode::FOCUS.reset(&mut self.screen)?;
579        }
580        if let Some(tracking) = self.state.mouse {
581            self.write_mouse_modes(tracking, false)?;
582        }
583        if self.state.color_scheme_updates {
584            mode::Mode::LIGHT_DARK.reset(&mut self.screen)?;
585        }
586        if self.state.visibility_reports {
587            mode::Mode::VISIBILITY_REPORTS.reset(&mut self.screen)?;
588        }
589        if self.state.in_band_resize {
590            mode::Mode::IN_BAND_RESIZE.reset(&mut self.screen)?;
591        }
592        if self.state.modify_other_keys != crate::event::ModifyOtherKeysMode::Disabled {
593            self.screen.write_all(xterm::RESET_MODIFY_OTHER_KEYS)?;
594        }
595        if self.state.foreground_color.is_some() {
596            self.screen.write_all(color::RESET_FOREGROUND_COLOR)?;
597        }
598        if self.state.background_color.is_some() {
599            self.screen.write_all(color::RESET_BACKGROUND_COLOR)?;
600        }
601        if self.state.cursor_color.is_some() {
602            self.screen.write_all(color::RESET_CURSOR_COLOR)?;
603        }
604        if !self.state.palette.is_empty() {
605            self.screen.write_all(color::RESET_PALETTE_COLORS)?;
606        }
607        match (&self.state.window_title, &self.state.icon_name) {
608            // Both set to the same string (e.g. via `set_title`): clear both
609            // with a single `OSC 0`.
610            (Some(w), Some(i)) if w == i => {
611                ansi::title::write_window_title_and_icon(&mut self.screen, "")?;
612            }
613            (window_title, icon_name) => {
614                if window_title.is_some() {
615                    ansi::title::write_window_title(&mut self.screen, "")?;
616                }
617                if icon_name.is_some() {
618                    ansi::title::write_icon_name(&mut self.screen, "")?;
619                }
620            }
621        }
622        if self.state.pointer_shape.is_some() {
623            cursor::write_set_pointer_shape(&mut self.screen, "default")?;
624        }
625        if self.state.progress.is_some() {
626            self.screen.write_all(progress::RESET_PROGRESS_BAR)?;
627        }
628
629        // --- Render-coupled modes ---
630        // Park the cursor below the managed area before any teardown, so the
631        // shell prompt lands where the user expects.
632        self.screen.park_cursor()?;
633        if !self.state.cursor_visible {
634            mode::Mode::CURSOR_VISIBLE.set(&mut self.screen)?;
635        }
636        let fullscreen = self.state.alt_screen;
637        // Clear the alt screen's kitty keyboard frame *before* leaving the alt
638        // screen — the stack is per-screen-buffer.
639        if fullscreen && !self.state.kitty_keyboard.is_empty() {
640            kitty::write_set_kitty_keyboard(
641                &mut self.screen,
642                kitty::KittyKeyboardFlags::empty(),
643                kitty::KittyKeyboardMode::Set,
644            )?;
645        }
646        if fullscreen {
647            mode::Mode::ALT_SCREEN_SAVE_CURSOR.reset(&mut self.screen)?;
648            self.screen.restore_cursor();
649        }
650        // Now on the main screen — clear its frame too.
651        if !self.state.kitty_keyboard.is_empty() {
652            kitty::write_set_kitty_keyboard(
653                &mut self.screen,
654                kitty::KittyKeyboardFlags::empty(),
655                kitty::KittyKeyboardMode::Set,
656            )?;
657        }
658        if self.state.grapheme_clusters {
659            mode::Mode::UNICODE_CORE.reset(&mut self.screen)?;
660        }
661        self.screen.invalidate_cursor();
662        Ok(())
663    }
664
665    /// Re-emit every mode held in the tracked state — the render-coupled
666    /// modes (Kitty keyboard, alternate screen, Unicode core, cursor
667    /// visibility) first, then the non-render modes — for any scenario where
668    /// the terminal was temporarily handed back to the shell. Pairs with
669    /// [`reset`](Self::reset). Pure write — does not mutate the tracked state.
670    /// The caller flushes.
671    pub(super) fn restore(&mut self) -> io::Result<()> {
672        // --- Render-coupled modes ---
673        // Re-apply the desired kitty keyboard flags on the main screen
674        // *before* entering the alt screen — the stack is per-buffer.
675        if !self.state.kitty_keyboard.is_empty() {
676            kitty::write_set_kitty_keyboard(
677                &mut self.screen,
678                self.state.kitty_keyboard,
679                kitty::KittyKeyboardMode::Set,
680            )?;
681        }
682        let fullscreen = self.state.alt_screen;
683        if fullscreen {
684            self.screen.save_cursor();
685            mode::Mode::ALT_SCREEN_SAVE_CURSOR.set(&mut self.screen)?;
686        }
687        // Now on the alt screen (if alt was active) — re-apply on the alt
688        // buffer too, since its stack is independent.
689        if fullscreen && !self.state.kitty_keyboard.is_empty() {
690            kitty::write_set_kitty_keyboard(
691                &mut self.screen,
692                self.state.kitty_keyboard,
693                kitty::KittyKeyboardMode::Set,
694            )?;
695        }
696        if self.state.grapheme_clusters {
697            mode::Mode::UNICODE_CORE.set(&mut self.screen)?;
698        }
699        if !self.state.cursor_visible {
700            mode::Mode::CURSOR_VISIBLE.reset(&mut self.screen)?;
701        }
702
703        // --- Non-render modes ---
704        if self.state.cursor_style != cursor::CursorStyle::Default {
705            cursor::write_cursor_style(&mut self.screen, self.state.cursor_style)?;
706        }
707        if self.state.color_scheme_updates {
708            mode::Mode::LIGHT_DARK.set(&mut self.screen)?;
709        }
710        if self.state.visibility_reports {
711            mode::Mode::VISIBILITY_REPORTS.set(&mut self.screen)?;
712        }
713        if self.state.in_band_resize {
714            mode::Mode::IN_BAND_RESIZE.set(&mut self.screen)?;
715        }
716        match self.state.modify_other_keys {
717            crate::event::ModifyOtherKeysMode::Mode1 => {
718                self.screen.write_all(xterm::SET_MODIFY_OTHER_KEYS_1)?;
719            }
720            crate::event::ModifyOtherKeysMode::Mode2 => {
721                self.screen.write_all(xterm::SET_MODIFY_OTHER_KEYS_2)?;
722            }
723            crate::event::ModifyOtherKeysMode::Disabled => {}
724        }
725        if self.state.bracketed_paste {
726            mode::Mode::BRACKETED_PASTE.set(&mut self.screen)?;
727        }
728        if self.state.focus_events {
729            mode::Mode::FOCUS.set(&mut self.screen)?;
730        }
731        if let Some(pref) = self.state.mouse {
732            self.write_mouse_modes(pref, true)?;
733        }
734        if let Some(c) = self.state.foreground_color {
735            let (r, g, b) = c.to_rgb();
736            color::write_set_foreground_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
737        }
738        if let Some(c) = self.state.background_color {
739            let (r, g, b) = c.to_rgb();
740            color::write_set_background_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
741        }
742        if let Some(c) = self.state.cursor_color {
743            let (r, g, b) = c.to_rgb();
744            color::write_set_cursor_color(&mut self.screen, &color::xparse_rgb(r, g, b))?;
745        }
746        for (&index, &c) in &self.state.palette {
747            let (r, g, b) = c.to_rgb();
748            color::write_set_palette_color(&mut self.screen, index, &color::xparse_rgb(r, g, b))?;
749        }
750        match (
751            self.state.window_title.clone(),
752            self.state.icon_name.clone(),
753        ) {
754            // Both set to the same string (e.g. via `set_title`): restore both
755            // with a single `OSC 0`.
756            (Some(w), Some(i)) if w == i => {
757                ansi::title::write_window_title_and_icon(&mut self.screen, &w)?;
758            }
759            (window_title, icon_name) => {
760                if let Some(title) = window_title {
761                    ansi::title::write_window_title(&mut self.screen, &title)?;
762                }
763                if let Some(name) = icon_name {
764                    ansi::title::write_icon_name(&mut self.screen, &name)?;
765                }
766            }
767        }
768        if let Some(shape) = self.state.pointer_shape.clone() {
769            cursor::write_set_pointer_shape(&mut self.screen, &shape)?;
770        }
771        if let Some(progress) = self.state.progress {
772            progress.write(&mut self.screen)?;
773        }
774        Ok(())
775    }
776
777    // --- Request delegates -----------------------------------------------
778    //
779    // Each writes a terminal query and flushes; the reply arrives later
780    // through the event flow. Replies that double as capability
781    // reports (mode, kitty keyboard) are recorded into
782    // [`capabilities`](Self::capabilities); value replies (cursor
783    // position, colors, pixel sizes) surface to the caller as events.
784
785    /// Request the window size in pixels (XTWINOPS `CSI 14 t`). Reply:
786    /// [`Event::WindowPixelSize`](crate::event::Event::WindowPixelSize).
787    pub fn request_window_pixel_size(&mut self) -> io::Result<()> {
788        self.screen
789            .write_all(crate::ansi::winop::REQUEST_WINDOW_PIXEL_SIZE)?;
790        self.screen.flush()
791    }
792
793    /// Request the character cell size in pixels (XTWINOPS `CSI 16 t`).
794    /// Reply: [`Event::CellPixelSize`](crate::event::Event::CellPixelSize).
795    pub fn request_cell_pixel_size(&mut self) -> io::Result<()> {
796        self.screen
797            .write_all(crate::ansi::winop::REQUEST_CELL_PIXEL_SIZE)?;
798        self.screen.flush()
799    }
800
801    /// Request the physical screen coordinate of the managed area's top-left
802    /// cell: park the cursor there and ask the terminal where it landed
803    /// (`CSI 6n`).
804    ///
805    /// The reply arrives asynchronously as a
806    /// [`CursorPosition`](crate::event::Event::CursorPosition) event and is
807    /// recorded (and clipped to keep the managed area on screen) by
808    /// [`observe_event`](Self::observe_event); read the result with
809    /// [`origin`](Self::origin). The event is still delivered to you.
810    ///
811    /// Call this once mouse mapping starts, again whenever the terminal
812    /// resizes, and again after [`resume`](Self::resume), each of which can
813    /// move the managed area. Without it the origin stays at `(0, 0)` and
814    /// [`mouse_to_origin`](Self::mouse_to_origin) is an identity.
815    ///
816    /// A no-op in fullscreen, where the origin is always `(0, 0)`.
817    pub fn request_origin(&mut self) -> io::Result<()> {
818        if self.screen.fullscreen() {
819            return Ok(());
820        }
821        // Park the cursor at the surface top-left; in relative-cursor inline
822        // mode that is the physical origin, so the reply *is* the origin.
823        // Stage the move rather than using move_cursor_to, which would flush
824        // before the query is written.
825        self.screen
826            .stage_move_cursor_to(crate::layout::Position::ORIGIN);
827        self.screen
828            .write_all(crate::ansi::status::REQUEST_CURSOR_POSITION)?;
829        self.origin_queries_pending = self.origin_queries_pending.saturating_add(1);
830        self.screen.flush()
831    }
832
833    /// Request the terminal's active Kitty keyboard flags (`CSI ? u`).
834    /// The reply is recorded in [`capabilities`](Self::capabilities).
835    pub fn request_kitty_keyboard(&mut self) -> io::Result<()> {
836        self.screen
837            .write_all(crate::ansi::kitty::REQUEST_KITTY_KEYBOARD)?;
838        self.screen.flush()
839    }
840
841    /// Request the terminal's modifyOtherKeys state (`CSI ? 4 m`). The
842    /// reply is recorded in [`capabilities`](Self::capabilities).
843    pub fn request_modify_other_keys(&mut self) -> io::Result<()> {
844        self.screen
845            .write_all(crate::ansi::xterm::QUERY_MODIFY_OTHER_KEYS)?;
846        self.screen.flush()
847    }
848
849    /// Request the default foreground color (`OSC 10 ; ? ST`). Reply:
850    /// [`Event::ForegroundColor`](crate::event::Event::ForegroundColor).
851    pub fn request_foreground_color(&mut self) -> io::Result<()> {
852        self.screen
853            .write_all(crate::ansi::color::REQUEST_FOREGROUND_COLOR)?;
854        self.screen.flush()
855    }
856
857    /// Request the default background color (`OSC 11 ; ? ST`). Reply:
858    /// [`Event::BackgroundColor`](crate::event::Event::BackgroundColor).
859    pub fn request_background_color(&mut self) -> io::Result<()> {
860        self.screen
861            .write_all(crate::ansi::color::REQUEST_BACKGROUND_COLOR)?;
862        self.screen.flush()
863    }
864
865    /// Request the cursor color (`OSC 12 ; ? ST`). Reply:
866    /// [`Event::CursorColor`](crate::event::Event::CursorColor).
867    pub fn request_cursor_color(&mut self) -> io::Result<()> {
868        self.screen
869            .write_all(crate::ansi::color::REQUEST_CURSOR_COLOR)?;
870        self.screen.flush()
871    }
872
873    /// Request a terminal palette color by index (`OSC 4 ; index ; ? ST`).
874    /// Reply: `OSC 4 ; index ; rgb:... ST`.
875    pub fn request_palette_color(&mut self, index: u8) -> io::Result<()> {
876        crate::ansi::color::write_request_palette_color(&mut self.screen, index)?;
877        self.screen.flush()
878    }
879
880    /// Request a terminal mode's current setting (DECRQM). Reply:
881    /// [`Event::ModeReport`](crate::event::Event::ModeReport).
882    ///
883    /// The reply's [`ModeSetting`](crate::ansi::mode::ModeSetting) reports whether
884    /// the mode is set, reset, or permanently fixed. A permanently reset mode
885    /// is recognized but can never be enabled, so check
886    /// [`ModeSetting::is_available`](crate::ansi::mode::ModeSetting::is_available)
887    /// before relying on it.
888    pub fn request_mode(&mut self, mode: crate::ansi::mode::Mode) -> io::Result<()> {
889        mode.request(&mut self.screen)?;
890        self.screen.flush()
891    }
892
893    /// Request the cursor position (`CSI 6 n`). Reply:
894    /// [`Event::CursorPosition`](crate::event::Event::CursorPosition).
895    pub fn request_cursor_position(&mut self) -> io::Result<()> {
896        self.screen
897            .write_all(crate::ansi::status::REQUEST_CURSOR_POSITION)?;
898        self.screen.flush()
899    }
900
901    /// Request the current color scheme (`CSI ? 996 n`): whether the
902    /// terminal's scheme is dark or light. This reports only the dark/light
903    /// preference, not the actual colors. Reply:
904    /// [`Event::ColorScheme`](crate::event::Event::ColorScheme).
905    pub fn request_color_scheme(&mut self) -> io::Result<()> {
906        self.screen
907            .write_all(crate::ansi::status::REQUEST_LIGHT_DARK_REPORT)?;
908        self.screen.flush()
909    }
910
911    /// Set the system clipboard contents (`OSC 52 ; c`). `data` is
912    /// base64-encoded for transport.
913    pub fn set_system_clipboard(&mut self, data: &[u8]) -> io::Result<()> {
914        crate::ansi::clipboard::write_set_clipboard(
915            &mut self.screen,
916            crate::ansi::clipboard::SYSTEM_CLIPBOARD,
917            data,
918        )?;
919        self.screen.flush()
920    }
921
922    /// Set the primary selection contents (`OSC 52 ; p`). `data` is
923    /// base64-encoded for transport.
924    pub fn set_primary_clipboard(&mut self, data: &[u8]) -> io::Result<()> {
925        crate::ansi::clipboard::write_set_clipboard(
926            &mut self.screen,
927            crate::ansi::clipboard::PRIMARY_CLIPBOARD,
928            data,
929        )?;
930        self.screen.flush()
931    }
932
933    /// Request the system clipboard contents (`OSC 52 ; c ; ?`). Reply:
934    /// [`Event::Clipboard`](crate::event::Event::Clipboard).
935    pub fn request_system_clipboard(&mut self) -> io::Result<()> {
936        crate::ansi::clipboard::write_request_clipboard(
937            &mut self.screen,
938            crate::ansi::clipboard::SYSTEM_CLIPBOARD,
939        )?;
940        self.screen.flush()
941    }
942
943    /// Request the primary selection contents (`OSC 52 ; p ; ?`). Reply:
944    /// [`Event::Clipboard`](crate::event::Event::Clipboard).
945    pub fn request_primary_clipboard(&mut self) -> io::Result<()> {
946        crate::ansi::clipboard::write_request_clipboard(
947            &mut self.screen,
948            crate::ansi::clipboard::PRIMARY_CLIPBOARD,
949        )?;
950        self.screen.flush()
951    }
952}