Skip to main content

uncurses/screen/
modes.rs

1//! Non-render terminal/input mode toggles for the [`Screen`] facade —
2//! cursor style, mouse tracking, bracketed paste, focus reporting,
3//! color-scheme update reports, in-band resize reports, window title,
4//! 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//! [`Screen`]: super::Screen
12
13use std::io::{self, Write};
14
15use crate::ansi::{self, color, cursor, kitty, mode, xterm};
16use crate::color::Color;
17use crate::event::Input;
18
19use super::MouseTracking;
20use super::Screen;
21use super::cursor::CursorShape;
22
23/// DEC private modes for the mouse tracking modes and encodings this
24/// library supports. Reset together to unconditionally turn mouse
25/// reporting off.
26const MOUSE_MODES: &[mode::Mode] = &[
27    mode::Mode::MOUSE_X10,
28    mode::Mode::MOUSE_NORMAL,
29    mode::Mode::MOUSE_BUTTON,
30    mode::Mode::MOUSE_ANY,
31    mode::Mode::MOUSE_SGR,
32    mode::Mode::MOUSE_SGR_PIXEL,
33];
34
35impl<I: Input, O: Write> Screen<I, O> {
36    /// Set the cursor shape and blinking state (`DECSCUSR`) and flush.
37    ///
38    /// * `shape` — the visual cursor shape ([`Block`](CursorShape::Block),
39    ///   [`Underline`](CursorShape::Underline), or [`Bar`](CursorShape::Bar)).
40    /// * `blinking` — whether the cursor blinks.
41    pub fn set_cursor_style(&mut self, shape: CursorShape, blinking: bool) -> io::Result<()> {
42        let style = shape.style(blinking);
43        cursor::write_cursor_style(&mut self.out_buf, style)?;
44        self.state.cursor_style = style;
45        self.flush()
46    }
47
48    /// Ring the terminal bell (`BEL`) and flush.
49    pub fn beep(&mut self) -> io::Result<()> {
50        self.out_buf.write_all(b"\x07")?;
51        self.flush()
52    }
53
54    /// Set the pointer (mouse cursor) shape (`OSC 22`) and flush.
55    ///
56    /// `shape` is a pointer shape name such as `"default"`, `"text"`, or
57    /// `"pointer"`. The shape is recorded for save/restore.
58    pub fn set_pointer_shape(&mut self, shape: &str) -> io::Result<()> {
59        cursor::write_set_pointer_shape(&mut self.out_buf, shape)?;
60        self.state.pointer_shape = Some(shape.to_string());
61        self.flush()
62    }
63
64    /// Reset the pointer (mouse cursor) shape to the terminal default
65    /// (`OSC 22 ; default`) and flush.
66    ///
67    /// Uses the explicit `"default"` shape name rather than an empty one: some
68    /// terminals don't treat an empty `OSC 22` as a reset.
69    pub fn reset_pointer_shape(&mut self) -> io::Result<()> {
70        cursor::write_set_pointer_shape(&mut self.out_buf, "default")?;
71        self.state.pointer_shape = None;
72        self.flush()
73    }
74
75    /// Enable mouse tracking and flush.
76    ///
77    /// This emits exactly what is asked for and does not consult terminal
78    /// [`capabilities`](Self::capabilities). Unsupported modes are ignored by
79    /// the terminal, and because the mode requests are mutually exclusive, each
80    /// terminal settles on the most capable variant it understands:
81    ///
82    /// * Tracking: button (`1000`) and button-event (`1002`) are always
83    ///   requested, so a terminal reports drag where it can and plain clicks
84    ///   otherwise. With [`MouseTracking::MOTION`], any-event tracking (`1003`)
85    ///   is added on top, so motion without a button held is reported where
86    ///   supported.
87    /// * Encoding: SGR (`1006`) is always requested, since the legacy byte
88    ///   encoding caps coordinates at 223 and SGR is universally supported.
89    ///   With [`MouseTracking::PIXELS`], SGR-pixel (`1016`) is added; terminals
90    ///   that support it report pixel coordinates, and the rest fall back to
91    ///   SGR cell coordinates.
92    ///
93    /// Pass [`MouseTracking::empty()`] for basic button tracking with no
94    /// extras. To turn mouse tracking off, call [`disable_mouse`](Self::disable_mouse).
95    ///
96    /// To learn which variant a terminal actually chose, read
97    /// [`capabilities`](Self::capabilities) (for example
98    /// [`mouse_sgr_pixel`](crate::screen::Capabilities::mouse_sgr_pixel) to tell
99    /// whether pixels or cells will arrive). When pixel reporting is active, a
100    /// [`Mouse`](crate::event::Mouse) event's pixel coordinates can be converted
101    /// to cells with [`mouse_pixels_to_cells`](Self::mouse_pixels_to_cells).
102    ///
103    /// The request is recorded for save/restore.
104    pub fn enable_mouse(&mut self, tracking: MouseTracking) -> io::Result<()> {
105        // Drop any prior tracking first so modes don't stack ambiguously.
106        mode::write_reset_mode(&mut self.out_buf, MOUSE_MODES)?;
107        self.write_mouse_modes(tracking, true)?;
108        self.state.mouse = Some(tracking);
109        self.flush()
110    }
111
112    /// Disable all mouse tracking modes and encodings, and flush.
113    pub fn disable_mouse(&mut self) -> io::Result<()> {
114        mode::write_reset_mode(&mut self.out_buf, MOUSE_MODES)?;
115        self.state.mouse = None;
116        self.flush()
117    }
118
119    /// Set or reset the mouse tracking modes and encoding for the given
120    /// tracking flags. `enable` selects set vs reset. The modes are emitted in
121    /// ascending order so that, where the requests are mutually exclusive, the
122    /// most capable supported variant wins (`1003` over `1002`/`1000`, `1016`
123    /// over `1006`).
124    fn write_mouse_modes(&mut self, tracking: MouseTracking, enable: bool) -> io::Result<()> {
125        // Always request plain and button-event tracking as a fallback pair,
126        // adding any-event tracking on top when motion is requested.
127        let mut modes = vec![mode::Mode::MOUSE_NORMAL, mode::Mode::MOUSE_BUTTON];
128        if tracking.contains(MouseTracking::MOTION) {
129            modes.push(mode::Mode::MOUSE_ANY);
130        }
131        // Always request SGR encoding; add SGR-pixel on top when pixels are
132        // requested. Terminals without pixel support fall back to SGR cells.
133        modes.push(mode::Mode::MOUSE_SGR);
134        if tracking.contains(MouseTracking::PIXELS) {
135            modes.push(mode::Mode::MOUSE_SGR_PIXEL);
136        }
137        if enable {
138            mode::write_set_mode(&mut self.out_buf, &modes)
139        } else {
140            mode::write_reset_mode(&mut self.out_buf, &modes)
141        }
142    }
143
144    /// Enable bracketed paste mode (DEC private mode 2004) and flush.
145    pub fn enable_bracketed_paste(&mut self) -> io::Result<()> {
146        mode::Mode::BRACKETED_PASTE.set(&mut self.out_buf)?;
147        self.state.bracketed_paste = true;
148        self.flush()
149    }
150
151    /// Disable bracketed paste mode (DEC private mode 2004) and flush.
152    pub fn disable_bracketed_paste(&mut self) -> io::Result<()> {
153        mode::Mode::BRACKETED_PASTE.reset(&mut self.out_buf)?;
154        self.state.bracketed_paste = false;
155        self.flush()
156    }
157
158    /// Enable focus in/out reporting (DEC private mode 1004) and flush.
159    pub fn enable_focus_events(&mut self) -> io::Result<()> {
160        mode::Mode::FOCUS.set(&mut self.out_buf)?;
161        self.state.focus_events = true;
162        self.flush()
163    }
164
165    /// Disable focus in/out reporting (DEC private mode 1004) and flush.
166    pub fn disable_focus_events(&mut self) -> io::Result<()> {
167        mode::Mode::FOCUS.reset(&mut self.out_buf)?;
168        self.state.focus_events = false;
169        self.flush()
170    }
171
172    /// Enable color-scheme update notifications (DEC private mode 2031) and
173    /// flush. The terminal then sends a `CSI ? 997 ; {1|2} n` report
174    /// whenever the user or operating system switches between dark and
175    /// light schemes; these surface as [`Event::ColorScheme`]. The report
176    /// indicates only the dark/light preference, not the actual colors.
177    ///
178    /// [`Event::ColorScheme`]: crate::event::Event::ColorScheme
179    pub fn enable_color_scheme_updates(&mut self) -> io::Result<()> {
180        mode::Mode::LIGHT_DARK.set(&mut self.out_buf)?;
181        self.state.color_scheme_updates = true;
182        self.flush()
183    }
184
185    /// Disable color-scheme update notifications (DEC private mode 2031) and
186    /// flush.
187    pub fn disable_color_scheme_updates(&mut self) -> io::Result<()> {
188        mode::Mode::LIGHT_DARK.reset(&mut self.out_buf)?;
189        self.state.color_scheme_updates = false;
190        self.flush()
191    }
192
193    /// Enable in-band resize notifications (DEC private mode 2048) and
194    /// flush. The terminal then reports every surface size change in-band
195    /// as a `CSI 48 ; height ; width ; ypixel ; xpixel t` sequence, which
196    /// the decoder surfaces as [`Event::Resize`] — no `SIGWINCH` handler
197    /// required.
198    ///
199    /// [`Event::Resize`]: crate::event::Event::Resize
200    pub fn enable_in_band_resize(&mut self) -> io::Result<()> {
201        mode::Mode::IN_BAND_RESIZE.set(&mut self.out_buf)?;
202        self.state.in_band_resize = true;
203        self.flush()
204    }
205
206    /// Disable in-band resize notifications (DEC private mode 2048) and
207    /// flush.
208    pub fn disable_in_band_resize(&mut self) -> io::Result<()> {
209        mode::Mode::IN_BAND_RESIZE.reset(&mut self.out_buf)?;
210        self.state.in_band_resize = false;
211        self.flush()
212    }
213
214    /// Set both the window title and icon name (`OSC 0`) and flush.
215    ///
216    /// An empty `title` clears both overrides, restoring the terminal's
217    /// defaults; the state is recorded as unset so teardown and resume skip
218    /// them. To set just one, use
219    /// [`set_window_title`](Self::set_window_title) (`OSC 2`) or
220    /// [`set_icon_title`](Self::set_icon_title) (`OSC 1`).
221    pub fn set_title(&mut self, title: &str) -> io::Result<()> {
222        ansi::title::write_window_title_and_icon(&mut self.out_buf, title)?;
223        let stored = (!title.is_empty()).then(|| title.to_string());
224        self.state.window_title = stored.clone();
225        self.state.icon_name = stored;
226        self.flush()
227    }
228
229    /// Set the window title only (`OSC 2`) and flush.
230    ///
231    /// An empty `title` clears the override, restoring the terminal's default
232    /// window title. Unlike [`set_title`](Self::set_title) (`OSC 0`), this
233    /// leaves the icon name untouched.
234    pub fn set_window_title(&mut self, title: &str) -> io::Result<()> {
235        ansi::title::write_window_title(&mut self.out_buf, title)?;
236        self.state.window_title = (!title.is_empty()).then(|| title.to_string());
237        self.flush()
238    }
239
240    /// Set the icon name only (`OSC 1`) and flush.
241    ///
242    /// An empty `title` clears the override, restoring the terminal's default
243    /// icon name. Unlike [`set_title`](Self::set_title) (`OSC 0`), this leaves
244    /// the window title untouched.
245    pub fn set_icon_title(&mut self, title: &str) -> io::Result<()> {
246        ansi::title::write_icon_name(&mut self.out_buf, title)?;
247        self.state.icon_name = (!title.is_empty()).then(|| title.to_string());
248        self.flush()
249    }
250
251    /// Set the xterm modifyOtherKeys mode (`CSI > 4 ; n m`) and flush.
252    /// Passing [`ModifyOtherKeysMode::Disabled`] resets it (`CSI > 4 m`).
253    /// The mode is recorded so [`Screen::finish`](super::Screen::finish)
254    /// can reset it and [`Screen::resume`](super::Screen::resume) re-apply
255    /// it.
256    ///
257    /// [`ModifyOtherKeysMode::Disabled`]: crate::event::ModifyOtherKeysMode::Disabled
258    pub fn set_modify_other_keys(
259        &mut self,
260        mode: crate::event::ModifyOtherKeysMode,
261    ) -> io::Result<()> {
262        use crate::event::ModifyOtherKeysMode;
263        match mode {
264            ModifyOtherKeysMode::Disabled => {
265                self.out_buf.write_all(xterm::RESET_MODIFY_OTHER_KEYS)?
266            }
267            ModifyOtherKeysMode::Mode1 => self.out_buf.write_all(xterm::SET_MODIFY_OTHER_KEYS_1)?,
268            ModifyOtherKeysMode::Mode2 => self.out_buf.write_all(xterm::SET_MODIFY_OTHER_KEYS_2)?,
269        }
270        self.state.modify_other_keys = mode;
271        self.flush()
272    }
273
274    /// Set the default foreground color (`OSC 10`) and flush. The color is
275    /// converted to 24-bit RGB and emitted as `rgb:RRRR/GGGG/BBBB`, and is
276    /// recorded so [`Screen::finish`](super::Screen::finish) can restore
277    /// the terminal default and [`Screen::resume`](super::Screen::resume)
278    /// can re-apply it.
279    pub fn set_foreground_color(&mut self, color: Color) -> io::Result<()> {
280        let (r, g, b) = color.to_rgb();
281        color::write_set_foreground_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
282        self.state.foreground_color = Some(color);
283        self.flush()
284    }
285
286    /// Restore the terminal's default foreground color (`OSC 110`) and
287    /// flush.
288    pub fn reset_foreground_color(&mut self) -> io::Result<()> {
289        self.out_buf.write_all(color::RESET_FOREGROUND_COLOR)?;
290        self.state.foreground_color = None;
291        self.flush()
292    }
293
294    /// Set the default background color (`OSC 11`) and flush. See
295    /// [`set_foreground_color`](Self::set_foreground_color) for
296    /// state-tracking semantics.
297    pub fn set_background_color(&mut self, color: Color) -> io::Result<()> {
298        let (r, g, b) = color.to_rgb();
299        color::write_set_background_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
300        self.state.background_color = Some(color);
301        self.flush()
302    }
303
304    /// Restore the terminal's default background color (`OSC 111`) and
305    /// flush.
306    pub fn reset_background_color(&mut self) -> io::Result<()> {
307        self.out_buf.write_all(color::RESET_BACKGROUND_COLOR)?;
308        self.state.background_color = None;
309        self.flush()
310    }
311
312    /// Set the cursor color (`OSC 12`) and flush. See
313    /// [`set_foreground_color`](Self::set_foreground_color) for
314    /// state-tracking semantics.
315    pub fn set_cursor_color(&mut self, color: Color) -> io::Result<()> {
316        let (r, g, b) = color.to_rgb();
317        color::write_set_cursor_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
318        self.state.cursor_color = Some(color);
319        self.flush()
320    }
321
322    /// Restore the terminal's default cursor color (`OSC 112`) and flush.
323    pub fn reset_cursor_color(&mut self) -> io::Result<()> {
324        self.out_buf.write_all(color::RESET_CURSOR_COLOR)?;
325        self.state.cursor_color = None;
326        self.flush()
327    }
328
329    /// Set a terminal palette color by index (`OSC 4`) and flush. The
330    /// override is tracked so [`Screen::finish`](super::Screen::finish) can
331    /// restore it and [`Screen::resume`](super::Screen::resume) re-apply it.
332    pub fn set_palette_color(&mut self, index: u8, color: Color) -> io::Result<()> {
333        let (r, g, b) = color.to_rgb();
334        color::write_set_palette_color(&mut self.out_buf, index, &color::xparse_rgb(r, g, b))?;
335        self.state.palette.insert(index, color);
336        self.flush()
337    }
338
339    /// Reset a single terminal palette color to its default
340    /// (`OSC 104 ; index`) and flush.
341    pub fn reset_palette_color(&mut self, index: u8) -> io::Result<()> {
342        color::write_reset_palette_color(&mut self.out_buf, index)?;
343        self.state.palette.remove(&index);
344        self.flush()
345    }
346
347    /// Reset the entire terminal palette to its defaults (`OSC 104`) and
348    /// flush, clearing every tracked palette override.
349    pub fn reset_palette_colors(&mut self) -> io::Result<()> {
350        self.out_buf.write_all(color::RESET_PALETTE_COLORS)?;
351        self.state.palette.clear();
352        self.flush()
353    }
354
355    /// Stage the teardown of every mode currently held — non-render modes
356    /// (cursor style, mouse, paste, focus, colors, title, …) followed by the
357    /// render-coupled modes (cursor visibility, alternate screen, Kitty
358    /// keyboard, Unicode core) — returning the terminal to a clean baseline
359    /// before handing control back to the shell. Pure write — does not mutate
360    /// the tracked state, so a later [`restore`](Self::restore) re-applies the
361    /// same modes verbatim. The caller flushes.
362    pub(super) fn reset(&mut self) -> io::Result<()> {
363        // --- Non-render modes ---
364        if self.state.cursor_style != cursor::CursorStyle::Default {
365            cursor::write_cursor_style(&mut self.out_buf, cursor::CursorStyle::Default)?;
366        }
367        if self.state.bracketed_paste {
368            mode::Mode::BRACKETED_PASTE.reset(&mut self.out_buf)?;
369        }
370        if self.state.focus_events {
371            mode::Mode::FOCUS.reset(&mut self.out_buf)?;
372        }
373        if let Some(tracking) = self.state.mouse {
374            self.write_mouse_modes(tracking, false)?;
375        }
376        if self.state.color_scheme_updates {
377            mode::Mode::LIGHT_DARK.reset(&mut self.out_buf)?;
378        }
379        if self.state.in_band_resize {
380            mode::Mode::IN_BAND_RESIZE.reset(&mut self.out_buf)?;
381        }
382        if self.state.modify_other_keys != crate::event::ModifyOtherKeysMode::Disabled {
383            self.out_buf.write_all(xterm::RESET_MODIFY_OTHER_KEYS)?;
384        }
385        if self.state.foreground_color.is_some() {
386            self.out_buf.write_all(color::RESET_FOREGROUND_COLOR)?;
387        }
388        if self.state.background_color.is_some() {
389            self.out_buf.write_all(color::RESET_BACKGROUND_COLOR)?;
390        }
391        if self.state.cursor_color.is_some() {
392            self.out_buf.write_all(color::RESET_CURSOR_COLOR)?;
393        }
394        if !self.state.palette.is_empty() {
395            self.out_buf.write_all(color::RESET_PALETTE_COLORS)?;
396        }
397        match (&self.state.window_title, &self.state.icon_name) {
398            // Both set to the same string (e.g. via `set_title`): clear both
399            // with a single `OSC 0`.
400            (Some(w), Some(i)) if w == i => {
401                ansi::title::write_window_title_and_icon(&mut self.out_buf, "")?;
402            }
403            (window_title, icon_name) => {
404                if window_title.is_some() {
405                    ansi::title::write_window_title(&mut self.out_buf, "")?;
406                }
407                if icon_name.is_some() {
408                    ansi::title::write_icon_name(&mut self.out_buf, "")?;
409                }
410            }
411        }
412        if self.state.pointer_shape.is_some() {
413            cursor::write_set_pointer_shape(&mut self.out_buf, "default")?;
414        }
415
416        // --- Render-coupled modes ---
417        // Walk to the bottom of the *last rendered* surface before any mode
418        // teardown, using the renderer's last-render height rather than the
419        // live height so a terminal that grew between the last render and quit
420        // does not push the post-quit cursor below where the user started.
421        let (_, last_height) = self.renderer.last_size();
422        if last_height > 0 {
423            self.renderer
424                .move_to(&mut self.out_buf, &self.front_buf, last_height - 1, 0)?;
425        }
426        if !self.state.cursor_visible {
427            mode::Mode::CURSOR_VISIBLE.set(&mut self.out_buf)?;
428        }
429        // Clear the alt screen's kitty keyboard frame *before* leaving the alt
430        // screen — the stack is per-screen-buffer.
431        if self.state.alt_screen && !self.state.kitty_keyboard.is_empty() {
432            kitty::write_set_kitty_keyboard(
433                &mut self.out_buf,
434                kitty::KittyKeyboardFlags::empty(),
435                kitty::KittyKeyboardMode::Set,
436            )?;
437        }
438        if self.state.alt_screen {
439            mode::Mode::ALT_SCREEN_SAVE_CURSOR.reset(&mut self.out_buf)?;
440            self.renderer.restore_cursor();
441        }
442        // Now on the main screen — clear its frame too.
443        if !self.state.kitty_keyboard.is_empty() {
444            kitty::write_set_kitty_keyboard(
445                &mut self.out_buf,
446                kitty::KittyKeyboardFlags::empty(),
447                kitty::KittyKeyboardMode::Set,
448            )?;
449        }
450        if self.state.grapheme_clusters {
451            mode::Mode::UNICODE_CORE.reset(&mut self.out_buf)?;
452        }
453
454        // The terminal is being handed back to the shell. Once it returns
455        // (e.g. after a suspend/resume, possibly with a resize that reflowed
456        // the surface), our model of where the cursor sits is void. Forget it
457        // so the next frame re-anchors at the current physical position
458        // instead of stepping up from a stale row and overwriting content
459        // above the surface.
460        self.renderer.invalidate_cursor();
461        Ok(())
462    }
463
464    /// Re-emit every mode held in the tracked state — the render-coupled
465    /// modes (Kitty keyboard, alternate screen, Unicode core, cursor
466    /// visibility) first, then the non-render modes — for any scenario where
467    /// the terminal was temporarily handed back to the shell. Pairs with
468    /// [`reset`](Self::reset). Pure write — does not mutate the tracked state.
469    /// The caller flushes.
470    pub(super) fn restore(&mut self) -> io::Result<()> {
471        // --- Render-coupled modes ---
472        // Re-apply the desired kitty keyboard flags on the main screen
473        // *before* entering the alt screen — the stack is per-buffer.
474        if !self.state.kitty_keyboard.is_empty() {
475            kitty::write_set_kitty_keyboard(
476                &mut self.out_buf,
477                self.state.kitty_keyboard,
478                kitty::KittyKeyboardMode::Set,
479            )?;
480        }
481        if self.state.alt_screen {
482            self.renderer.save_cursor();
483            mode::Mode::ALT_SCREEN_SAVE_CURSOR.set(&mut self.out_buf)?;
484        }
485        // Now on the alt screen (if alt was active) — re-apply on the alt
486        // buffer too, since its stack is independent.
487        if self.state.alt_screen && !self.state.kitty_keyboard.is_empty() {
488            kitty::write_set_kitty_keyboard(
489                &mut self.out_buf,
490                self.state.kitty_keyboard,
491                kitty::KittyKeyboardMode::Set,
492            )?;
493        }
494        if self.state.grapheme_clusters {
495            mode::Mode::UNICODE_CORE.set(&mut self.out_buf)?;
496        }
497        if !self.state.cursor_visible {
498            mode::Mode::CURSOR_VISIBLE.reset(&mut self.out_buf)?;
499        }
500
501        // --- Non-render modes ---
502        if self.state.cursor_style != cursor::CursorStyle::Default {
503            cursor::write_cursor_style(&mut self.out_buf, self.state.cursor_style)?;
504        }
505        if self.state.color_scheme_updates {
506            mode::Mode::LIGHT_DARK.set(&mut self.out_buf)?;
507        }
508        if self.state.in_band_resize {
509            mode::Mode::IN_BAND_RESIZE.set(&mut self.out_buf)?;
510        }
511        match self.state.modify_other_keys {
512            crate::event::ModifyOtherKeysMode::Mode1 => {
513                self.out_buf.write_all(xterm::SET_MODIFY_OTHER_KEYS_1)?;
514            }
515            crate::event::ModifyOtherKeysMode::Mode2 => {
516                self.out_buf.write_all(xterm::SET_MODIFY_OTHER_KEYS_2)?;
517            }
518            crate::event::ModifyOtherKeysMode::Disabled => {}
519        }
520        if self.state.bracketed_paste {
521            mode::Mode::BRACKETED_PASTE.set(&mut self.out_buf)?;
522        }
523        if self.state.focus_events {
524            mode::Mode::FOCUS.set(&mut self.out_buf)?;
525        }
526        if let Some(pref) = self.state.mouse {
527            self.write_mouse_modes(pref, true)?;
528        }
529        if let Some(c) = self.state.foreground_color {
530            let (r, g, b) = c.to_rgb();
531            color::write_set_foreground_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
532        }
533        if let Some(c) = self.state.background_color {
534            let (r, g, b) = c.to_rgb();
535            color::write_set_background_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
536        }
537        if let Some(c) = self.state.cursor_color {
538            let (r, g, b) = c.to_rgb();
539            color::write_set_cursor_color(&mut self.out_buf, &color::xparse_rgb(r, g, b))?;
540        }
541        for (&index, &c) in &self.state.palette {
542            let (r, g, b) = c.to_rgb();
543            color::write_set_palette_color(&mut self.out_buf, index, &color::xparse_rgb(r, g, b))?;
544        }
545        match (self.state.window_title.clone(), self.state.icon_name.clone()) {
546            // Both set to the same string (e.g. via `set_title`): restore both
547            // with a single `OSC 0`.
548            (Some(w), Some(i)) if w == i => {
549                ansi::title::write_window_title_and_icon(&mut self.out_buf, &w)?;
550            }
551            (window_title, icon_name) => {
552                if let Some(title) = window_title {
553                    ansi::title::write_window_title(&mut self.out_buf, &title)?;
554                }
555                if let Some(name) = icon_name {
556                    ansi::title::write_icon_name(&mut self.out_buf, &name)?;
557                }
558            }
559        }
560        if let Some(shape) = self.state.pointer_shape.clone() {
561            cursor::write_set_pointer_shape(&mut self.out_buf, &shape)?;
562        }
563        Ok(())
564    }
565
566    // --- Request delegates -----------------------------------------------
567    //
568    // Each writes a terminal query and flushes; the reply arrives later
569    // through the event flow. Replies that double as init capability
570    // reports (mode, kitty keyboard) are recorded into
571    // [`capabilities`](Self::capabilities); value replies (cursor
572    // position, colors, pixel sizes) surface to the caller as events.
573
574    /// Request the window size in pixels (XTWINOPS `CSI 14 t`). Reply:
575    /// [`Event::WindowPixelSize`](crate::event::Event::WindowPixelSize).
576    pub fn request_window_pixel_size(&mut self) -> io::Result<()> {
577        self.out_buf
578            .write_all(crate::ansi::winop::REQUEST_WINDOW_PIXEL_SIZE)?;
579        self.flush()
580    }
581
582    /// Request the character cell size in pixels (XTWINOPS `CSI 16 t`).
583    /// Reply: [`Event::CellPixelSize`](crate::event::Event::CellPixelSize).
584    pub fn request_cell_pixel_size(&mut self) -> io::Result<()> {
585        self.out_buf
586            .write_all(crate::ansi::winop::REQUEST_CELL_PIXEL_SIZE)?;
587        self.flush()
588    }
589
590    /// Request the terminal's active Kitty keyboard flags (`CSI ? u`).
591    /// The reply is recorded in [`capabilities`](Self::capabilities).
592    pub fn request_kitty_keyboard(&mut self) -> io::Result<()> {
593        self.out_buf
594            .write_all(crate::ansi::kitty::REQUEST_KITTY_KEYBOARD)?;
595        self.flush()
596    }
597
598    /// Request the terminal's modifyOtherKeys state (`CSI ? 4 m`). The
599    /// reply is recorded in [`capabilities`](Self::capabilities).
600    pub fn request_modify_other_keys(&mut self) -> io::Result<()> {
601        self.out_buf
602            .write_all(crate::ansi::xterm::QUERY_MODIFY_OTHER_KEYS)?;
603        self.flush()
604    }
605
606    /// Request the default foreground color (`OSC 10 ; ? ST`). Reply:
607    /// [`Event::ForegroundColor`](crate::event::Event::ForegroundColor).
608    pub fn request_foreground_color(&mut self) -> io::Result<()> {
609        self.out_buf
610            .write_all(crate::ansi::color::REQUEST_FOREGROUND_COLOR)?;
611        self.flush()
612    }
613
614    /// Request the default background color (`OSC 11 ; ? ST`). Reply:
615    /// [`Event::BackgroundColor`](crate::event::Event::BackgroundColor).
616    pub fn request_background_color(&mut self) -> io::Result<()> {
617        self.out_buf
618            .write_all(crate::ansi::color::REQUEST_BACKGROUND_COLOR)?;
619        self.flush()
620    }
621
622    /// Request the cursor color (`OSC 12 ; ? ST`). Reply:
623    /// [`Event::CursorColor`](crate::event::Event::CursorColor).
624    pub fn request_cursor_color(&mut self) -> io::Result<()> {
625        self.out_buf
626            .write_all(crate::ansi::color::REQUEST_CURSOR_COLOR)?;
627        self.flush()
628    }
629
630    /// Request a terminal palette color by index (`OSC 4 ; index ; ? ST`).
631    /// Reply: `OSC 4 ; index ; rgb:... ST`.
632    pub fn request_palette_color(&mut self, index: u8) -> io::Result<()> {
633        crate::ansi::color::write_request_palette_color(&mut self.out_buf, index)?;
634        self.flush()
635    }
636
637    /// Request a terminal mode's current setting (DECRQM). Reply:
638    /// [`Event::ModeReport`](crate::event::Event::ModeReport).
639    ///
640    /// The reply's [`ModeSetting`](crate::ansi::mode::ModeSetting) reports whether
641    /// the mode is set, reset, or permanently fixed. A permanently reset mode
642    /// is recognized but can never be enabled, so check
643    /// [`ModeSetting::is_available`](crate::ansi::mode::ModeSetting::is_available)
644    /// before relying on it.
645    pub fn request_mode(&mut self, mode: crate::ansi::mode::Mode) -> io::Result<()> {
646        mode.request(&mut self.out_buf)?;
647        self.flush()
648    }
649
650    /// Request the cursor position (`CSI 6 n`). Reply:
651    /// [`Event::CursorPosition`](crate::event::Event::CursorPosition).
652    pub fn request_cursor_position(&mut self) -> io::Result<()> {
653        self.out_buf
654            .write_all(crate::ansi::status::REQUEST_CURSOR_POSITION)?;
655        self.flush()
656    }
657
658    /// Request the current color scheme (`CSI ? 996 n`): whether the
659    /// terminal's scheme is dark or light. This reports only the dark/light
660    /// preference, not the actual colors. Reply:
661    /// [`Event::ColorScheme`](crate::event::Event::ColorScheme).
662    pub fn request_color_scheme(&mut self) -> io::Result<()> {
663        self.out_buf
664            .write_all(crate::ansi::status::REQUEST_LIGHT_DARK_REPORT)?;
665        self.flush()
666    }
667
668    /// Set the system clipboard contents (`OSC 52 ; c`). `data` is
669    /// base64-encoded for transport.
670    pub fn set_system_clipboard(&mut self, data: &[u8]) -> io::Result<()> {
671        crate::ansi::clipboard::write_set_clipboard(
672            &mut self.out_buf,
673            crate::ansi::clipboard::SYSTEM_CLIPBOARD,
674            data,
675        )?;
676        self.flush()
677    }
678
679    /// Set the primary selection contents (`OSC 52 ; p`). `data` is
680    /// base64-encoded for transport.
681    pub fn set_primary_clipboard(&mut self, data: &[u8]) -> io::Result<()> {
682        crate::ansi::clipboard::write_set_clipboard(
683            &mut self.out_buf,
684            crate::ansi::clipboard::PRIMARY_CLIPBOARD,
685            data,
686        )?;
687        self.flush()
688    }
689
690    /// Request the system clipboard contents (`OSC 52 ; c ; ?`). Reply:
691    /// [`Event::Clipboard`](crate::event::Event::Clipboard).
692    pub fn request_system_clipboard(&mut self) -> io::Result<()> {
693        crate::ansi::clipboard::write_request_clipboard(
694            &mut self.out_buf,
695            crate::ansi::clipboard::SYSTEM_CLIPBOARD,
696        )?;
697        self.flush()
698    }
699
700    /// Request the primary selection contents (`OSC 52 ; p ; ?`). Reply:
701    /// [`Event::Clipboard`](crate::event::Event::Clipboard).
702    pub fn request_primary_clipboard(&mut self) -> io::Result<()> {
703        crate::ansi::clipboard::write_request_clipboard(
704            &mut self.out_buf,
705            crate::ansi::clipboard::PRIMARY_CLIPBOARD,
706        )?;
707        self.flush()
708    }
709}