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}