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}