Skip to main content

uncurses/program/
mod.rs

1//! [`Program`] — the interactive terminal session.
2//!
3//! `Program<I, O>` is what you build a terminal application on. It owns the
4//! things a session needs and a [`Screen`] to render with:
5//!
6//! - a [`Terminal`] for the raw-mode lifecycle,
7//! - an [`EventSource`] for decoded input,
8//! - the terminal and input modes (mouse, bracketed paste, focus reporting,
9//!   in-band resize, titles, colors, cursor style, keyboard enhancements),
10//!   tracked so they can be torn down on a shell handoff and re-applied after,
11//! - the [`Capabilities`] the terminal has reported, recorded from replies as
12//!   they pass through the read path.
13//!
14//! Drawing is not on `Program`. Reach the renderer with
15//! [`screen_mut`](Program::screen_mut) and call
16//! [`render`](Screen::render) on it — that is the only `render` in the crate,
17//! and the only `flush`.
18//!
19//! Construction is inert: [`Program::new`] (and the [`stdio`](Program::stdio)
20//! / [`open`](Program::open) shortcuts) only build the program. Begin a
21//! session with [`Program::init`], which enters raw mode. Nothing is probed
22//! unless you ask: call [`Program::query_capabilities`] for that. Teardown is
23//! explicit: there is **no** `Drop`.
24//! Hand the terminal back to the shell with [`Program::finish`] (consume),
25//! [`Program::pause`] (keep, e.g. to shell out), or [`Program::suspend`]
26//! (pause, then stop the process with `SIGTSTP`); resume a
27//! paused/suspended program with [`Program::resume`].
28//!
29//! ```no_run
30//! use uncurses::program::Program;
31//! use uncurses::style::Style;
32//! use uncurses::text::TextSurface;
33//!
34//! # fn main() -> std::io::Result<()> {
35//! let mut program = Program::open()?; // build over /dev/tty
36//! program.init()?; // raw mode; probes nothing on its own
37//! program.enter_alt_screen()?;
38//!
39//! let screen = program.screen_mut();
40//! screen.set_str((0, 0), "hello", Style::default());
41//! screen.render()?;
42//!
43//! let event = program.read_event()?; // reply tracking is automatic
44//! program.finish()?; // restore the terminal
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! # Options and defaults
50//!
51//! [`init`](Program::init) uses [`ProgramOptions::default`];
52//! [`init_with`](Program::init_with) takes an explicit [`ProgramOptions`] to
53//! choose whether to enable bracketed paste and mouse tracking at startup.
54//! Those take effect immediately at init.
55//!
56//! The three `prefer_*` fields are discovery-driven instead: they enable
57//! grapheme-cluster mode, in-band resize, and synchronized output only once
58//! the terminal reports the mode as available. Since a program never probes
59//! on its own, that means calling
60//! [`query_capabilities`](Program::query_capabilities) and
61//! reading the replies (see [`capabilities`](Program::capabilities)).
62//!
63//! [`Terminal`]: crate::terminal::Terminal
64//! [`EventSource`]: crate::event::EventSource
65
66mod modes;
67mod state;
68#[cfg(test)]
69mod tests;
70
71pub use crate::ansi::program_status::{ProgramStatus, ProgramStatusKind, ProgramStatusState};
72pub use state::Capabilities;
73
74use std::collections::VecDeque;
75use std::io::{self, Write};
76use std::sync::{Arc, Mutex};
77use std::time::Duration;
78
79use bitflags::bitflags;
80
81use crate::ansi::{mode, progress};
82use crate::color::Profile;
83use crate::event::Input;
84use crate::event::{DecoderFlags, Event, EventSource};
85use crate::layout::{Position, Size};
86use crate::renderer::Optimizations;
87use crate::screen::Screen;
88use crate::terminal::Terminal;
89
90/// An interactive terminal session composing a [`Terminal`], an
91/// [`EventSource`], and a [`Screen`] to render with. See
92/// the [module documentation](self) for the lifecycle.
93///
94/// `Program` is [`Send`] and [`Sync`] whenever its input and output handles
95/// are, so it can be moved onto another thread or held across an `.await`
96/// point in a multi-threaded async runtime.
97///
98/// [`Terminal`]: crate::terminal::Terminal
99/// [`EventSource`]: crate::event::EventSource
100pub struct Program<I, O>
101where
102    I: Input,
103    O: Write,
104{
105    /// Owns the raw-mode state and answers the fd-bound queries (window size,
106    /// is-a-tty). Never written through: output goes through `screen`, which
107    /// holds a copy of the same handle, so one staging buffer keeps
108    /// everything in order.
109    terminal: Terminal<I, O>,
110    /// The renderer. Reach it with [`screen`](Self::screen) /
111    /// [`screen_mut`](Self::screen_mut).
112    screen: Screen<O>,
113    /// Input source behind the read path ([`Self::read_event`] and friends).
114    /// Held in an `Arc<Mutex<_>>`; the lock is uncontended in the common
115    /// single-reader case.
116    source: Arc<Mutex<EventSource<I>>>,
117    /// Events handed back by [`Self::unread_event`], which the read path
118    /// drains before the source. Kept here rather than in `source` because
119    /// these were already observed: routing them back through the source
120    /// would observe them a second time, and a reply counts once.
121    unread: VecDeque<Event>,
122    state: state::State,
123    /// Terminal capabilities, recorded by intercepting replies as they pass
124    /// through the read path. Empty until [`Self::query_capabilities`] is
125    /// called and the replies are read.
126    caps: Capabilities,
127    /// Desired default behaviors, set by [`Self::init_with`].
128    options: ProgramOptions,
129    /// Last observed full terminal size in cells, from resize and
130    /// `WindowCellSize` reports. `None` until first observed.
131    window_cells: Option<Size>,
132    /// Last observed full terminal size in pixels, from resize (when it
133    /// carries pixel dimensions) and `WindowPixelSize` reports. `None`
134    /// until first observed.
135    window_pixels: Option<Size>,
136    /// Cell size in pixels as reported by the last `CSI 16 t` reply, kept
137    /// until another reply replaces it. `None` until the terminal answers
138    /// one; [`cell_pixels`](Self::cell_pixels) falls back to dividing the
139    /// window sizes.
140    cell_pixels: Option<Size>,
141    /// Physical screen coordinate (0-based, from the terminal's top-left) of
142    /// the managed area's top-left cell, tracked for inline sessions. Only
143    /// meaningful inline; fullscreen [`origin`](Self::origin) is always
144    /// `(0, 0)`. Refreshed by [`request_origin`](Self::request_origin), whose
145    /// reply is captured in [`observe_event`](Self::observe_event).
146    origin: Position,
147    /// How many origin `CSI 6n` requests are outstanding, so
148    /// [`observe_event`](Self::observe_event) knows which
149    /// [`CursorPosition`](Event::CursorPosition) replies are ours to capture.
150    /// A count rather than a flag so a burst of requests keeps the last
151    /// reply instead of the first.
152    origin_queries_pending: u16,
153}
154
155/// Options for session setup, capability queries, and mode preferences.
156///
157/// [`Program::init_with`] applies `bracketed_paste`, `mouse`, and `legacy_keys`.
158/// It stores the other options for later use.
159///
160/// The `query_*` fields select optional requests for
161/// [`Program::query_capabilities`]. Call that method to send the queries.
162///
163/// The `prefer_*` fields control mode adoption when [`Program::observe_event`]
164/// receives a report that confirms support for the corresponding mode.
165#[derive(Debug, Clone)]
166pub struct ProgramOptions {
167    /// Enable bracketed paste at init. Defaults to `true`.
168    pub bracketed_paste: bool,
169    /// Enable mouse tracking at init with the given [`MouseTracking`] extras
170    /// (see [`Program::enable_mouse`]). The request is emitted unconditionally;
171    /// terminals ignore modes they do not support and degrade gracefully.
172    /// Defaults to `None` (mouse tracking off).
173    pub mouse: Option<MouseTracking>,
174    /// Enable grapheme-cluster mode (DEC mode 2027) once the terminal reports
175    /// it as available, so the terminal and the [`Screen`] measure text the
176    /// same way. Defaults to `true`.
177    ///
178    /// Nothing is emitted until that report arrives, which requires
179    /// [`query_capabilities`](Program::query_capabilities) and a read loop.
180    /// Set to `false` to keep per-code-point measurement, or call
181    /// [`enable_grapheme_clusters`](Program::enable_grapheme_clusters)
182    /// yourself to opt in without waiting for a report.
183    pub prefer_grapheme_clusters: bool,
184    /// Enable in-band resize notifications (DEC mode 2048) once the terminal
185    /// reports them as available, so resizes arrive on the event stream with
186    /// pixel dimensions instead of through `SIGWINCH`. Defaults to `true`.
187    ///
188    /// Nothing is emitted until that report arrives, which requires
189    /// [`query_capabilities`](Program::query_capabilities) and a read loop.
190    /// Set to `false` to stay on the signal path, or call
191    /// [`enable_in_band_resize`](Program::enable_in_band_resize) yourself to
192    /// opt in without waiting for a report.
193    pub prefer_in_band_resize: bool,
194    /// Wrap each frame in synchronized-output markers (DEC mode 2026) once the
195    /// terminal reports them as available, so a frame is presented in one
196    /// piece instead of tearing. Defaults to `true`.
197    ///
198    /// Unlike the two above this emits nothing of its own: synchronized output
199    /// is a render property, so adopting it only tells the [`Screen`] to start
200    /// bracketing frames. Set to `false` to keep frames unwrapped for the whole
201    /// session. [`Screen::set_synchronized_output`] drives the same property
202    /// directly, but the program cannot see a choice made there, so a call made
203    /// before the terminal's first report is still overridden by adoption; use
204    /// this field when the decision has to hold from the start.
205    ///
206    /// [`Screen::set_synchronized_output`]: crate::screen::Screen::set_synchronized_output
207    pub prefer_synchronized_output: bool,
208    /// Include Backarrow mode (DECBKM) in [`Program::query_capabilities`].
209    ///
210    /// Defaults to `false`. Initialization sends no queries.
211    /// [`Program::observe_event`] applies recognized replies independently of
212    /// this option, including replies to [`Program::request_mode`].
213    pub query_backspace_mode: bool,
214    /// Include the default foreground color in [`Program::query_capabilities`].
215    ///
216    /// Defaults to `false`.
217    pub query_foreground_color: bool,
218    /// Include the default background color in [`Program::query_capabilities`].
219    ///
220    /// Defaults to `false`.
221    pub query_background_color: bool,
222    /// Include the cursor color in [`Program::query_capabilities`].
223    ///
224    /// Defaults to `false`.
225    pub query_cursor_color: bool,
226    /// Include these palette indices in [`Program::query_capabilities`].
227    ///
228    /// Defaults to an empty list. Queries follow the supplied order.
229    pub query_palette_colors: Vec<u8>,
230    /// Include OSC 7501 support in [`Program::query_capabilities`].
231    ///
232    /// Defaults to `false`. Initialization sends no queries.
233    /// [`Program::observe_event`] records support replies regardless of this
234    /// option. Reports and record lifetime are independent of query selection.
235    pub query_program_status: bool,
236    /// How to read the ambiguous legacy keys.
237    ///
238    /// Defaults to [`empty`](DecoderFlags::empty). LF reads as Ctrl+J;
239    /// [`DecoderFlags::LF_IS_ENTER`] selects Enter instead. See [`DecoderFlags`]
240    /// for all mappings. Use [`Program::set_decoder_flags`] to change the
241    /// active flags after initialization.
242    pub legacy_keys: DecoderFlags,
243}
244
245bitflags! {
246    /// Optional mouse tracking features layered on top of basic button
247    /// tracking.
248    ///
249    /// When mouse tracking is enabled, button-event tracking (presses,
250    /// releases, and drags) and SGR encoding are always requested; these flags
251    /// add optional extras on top. An empty set ([`MouseTracking::empty()`])
252    /// means basic tracking with no extras.
253    ///
254    /// Mouse tracking is turned *off* through [`Program::disable_mouse`] or by
255    /// leaving [`ProgramOptions::mouse`] as `None`, not by an empty flag set.
256    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
257    pub struct MouseTracking: u8 {
258        /// Report pointer motion with no button held (any-event tracking).
259        /// Adds hover motion on terminals that support it.
260        const MOTION = 1 << 0;
261        /// Request pixel coordinates (SGR-pixel). Terminals that support it
262        /// report pixels; the rest fall back to SGR cell coordinates.
263        const PIXELS = 1 << 1;
264    }
265}
266
267/// A progress indication reported to the terminal with `OSC 9;4`, shown in
268/// the taskbar, tab, or window chrome by terminals that support it.
269///
270/// Set it with [`Program::set_progress_state`] and take it down with
271/// [`Program::reset_progress_state`]. Percentages are clamped to `0..=100`
272/// when emitted.
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
274pub enum ProgressState {
275    /// Determinate progress at the given percentage.
276    Normal(u8),
277    /// A failed operation, at the given percentage. Usually red.
278    Error(u8),
279    /// An operation needing attention, at the given percentage. Usually
280    /// yellow. Named "paused" in ConEmu, which originated the sequence.
281    Warning(u8),
282    /// Work in progress of unknown duration. Usually a pulsing bar.
283    Indeterminate,
284}
285
286impl ProgressState {
287    /// Emit the `OSC 9;4` sequence for this state.
288    fn write<W: Write>(self, w: &mut W) -> io::Result<()> {
289        match self {
290            ProgressState::Normal(p) => progress::write_set_progress_bar(w, p.into()),
291            ProgressState::Error(p) => progress::write_set_error_progress_bar(w, p.into()),
292            ProgressState::Warning(p) => progress::write_set_warning_progress_bar(w, p.into()),
293            ProgressState::Indeterminate => w.write_all(progress::SET_INDETERMINATE_PROGRESS_BAR),
294        }
295    }
296}
297
298impl Default for ProgramOptions {
299    fn default() -> Self {
300        Self {
301            bracketed_paste: true,
302            mouse: None,
303            prefer_grapheme_clusters: true,
304            prefer_in_band_resize: true,
305            prefer_synchronized_output: true,
306            query_backspace_mode: false,
307            query_foreground_color: false,
308            query_background_color: false,
309            query_cursor_color: false,
310            query_palette_colors: Vec::new(),
311            query_program_status: false,
312            legacy_keys: DecoderFlags::empty(),
313        }
314    }
315}
316
317impl<I, O> Program<I, O>
318where
319    I: Input,
320    O: Write,
321{
322    // --- The terminal ----------------------------------------------------
323
324    /// Borrow the [`Terminal`] this program drives.
325    ///
326    /// Shared on purpose: the program keeps ownership, so its record of the
327    /// modes and raw-mode state it emitted stays the authority for
328    /// [`finish`](Self::finish). Everything the program itself changes has a
329    /// method here. The borrow is not a seal, though. `Terminal::set_state`
330    /// takes `&self`, so a caller holding this can still change raw mode
331    /// behind the program's back, and the restore record will not know.
332    pub fn terminal(&self) -> &Terminal<I, O> {
333        &self.terminal
334    }
335
336    /// The environment the [`Terminal`] reads variables from.
337    ///
338    /// This is where the answers the terminal never sends live: `TERM`,
339    /// `COLORTERM`, `TERM_PROGRAM`, and the rest. Shorthand for
340    /// [`terminal().env()`](crate::terminal::Terminal::env), and the
341    /// counterpart to [`capabilities`](Self::capabilities), which holds only
342    /// what the terminal answered.
343    pub fn env(&self) -> &dyn crate::terminal::Env {
344        self.terminal.env()
345    }
346
347    // --- The screen ------------------------------------------------------
348
349    /// Borrow the [`Screen`] this program renders with.
350    pub fn screen(&self) -> &Screen<O> {
351        &self.screen
352    }
353
354    /// Borrow the [`Screen`] mutably — this is how you draw.
355    ///
356    /// ```no_run
357    /// # use uncurses::program::Program;
358    /// # use uncurses::style::Style;
359    /// # use uncurses::text::TextSurface;
360    /// # fn main() -> std::io::Result<()> {
361    /// # let mut program = Program::open()?;
362    /// let screen = program.screen_mut();
363    /// screen.set_str((0, 0), "hello", Style::default());
364    /// screen.render()?;
365    /// # Ok(())
366    /// # }
367    /// ```
368    ///
369    /// Drawing is the expected use. The screen's *render properties* are also
370    /// reachable here, and setting one directly moves only how frames are
371    /// drawn — it emits no mode, so the terminal never hears about it. Prefer
372    /// the program's own [`enter_alt_screen`](Self::enter_alt_screen),
373    /// [`hide_cursor`](Self::hide_cursor), and
374    /// [`enable_grapheme_clusters`](Self::enable_grapheme_clusters), which
375    /// emit the mode *and* move the property together. Teardown follows what
376    /// this program emitted, so a property changed behind its back is not
377    /// undone by [`finish`](Self::finish) and does not survive a
378    /// [`pause`](Self::pause) / [`resume`](Self::resume) round trip.
379    pub fn screen_mut(&mut self) -> &mut Screen<O> {
380        &mut self.screen
381    }
382
383    // --- Event delegates -------------------------------------------------
384
385    /// Return the event source's current legacy decoder flags.
386    ///
387    /// See [`DecoderFlags`] for the available key mappings.
388    pub fn decoder_flags(&self) -> DecoderFlags {
389        self.source.lock().unwrap().decoder_flags()
390    }
391
392    /// Set the legacy decoder flags for subsequent input decoding.
393    ///
394    /// Already-decoded events keep their values, including events returned
395    /// through [`unread_event`](Self::unread_event). This updates the shared
396    /// [`EventSource`] and leaves [`ProgramOptions::legacy_keys`] unchanged.
397    /// Terminal modes remain unchanged.
398    pub fn set_decoder_flags(&mut self, flags: DecoderFlags) {
399        self.source.lock().unwrap().set_decoder_flags(flags);
400    }
401
402    /// Drive the input source for up to `timeout`, returning whether any
403    /// event became available. See [`EventSource::poll`].
404    pub fn poll_event(&self, timeout: Option<Duration>) -> io::Result<bool> {
405        if !self.unread.is_empty() {
406            return Ok(true);
407        }
408        let ready = self.source.lock().unwrap().poll(timeout)?;
409        Ok(ready)
410    }
411
412    /// Take the next queued event without reading more input.
413    /// Returns `Ok(None)` when the queue is empty. See [`EventSource::try_read`].
414    ///
415    /// Calls [`observe_event`](Self::observe_event) for events from the source.
416    /// Observation can enable preferred terminal modes and flush output.
417    /// Events returned through [`unread_event`](Self::unread_event) are already
418    /// observed.
419    ///
420    /// # Errors
421    ///
422    /// Returns any error from [`observe_event`](Self::observe_event), including
423    /// output errors. The event has already been removed from the queue.
424    pub fn try_read_event(&mut self) -> io::Result<Option<Event>> {
425        if let Some(event) = self.unread.pop_front() {
426            return Ok(Some(event));
427        }
428        let Some(event) = self.source.lock().unwrap().try_read() else {
429            return Ok(None);
430        };
431        self.observe_event(&event)?;
432        Ok(Some(event))
433    }
434
435    /// Block until the next event, tracking capabilities as it passes
436    /// through. See [`EventSource::read`].
437    pub fn read_event(&mut self) -> io::Result<Event> {
438        if let Some(event) = self.unread.pop_front() {
439            return Ok(event);
440        }
441        let event = self.source.lock().unwrap().read()?;
442        self.observe_event(&event)?;
443        Ok(event)
444    }
445
446    /// Return an event to the front of the input queue, so the next
447    /// [`read_event`](Self::read_event) / [`try_read_event`](Self::try_read_event)
448    /// yields it before anything already queued. Restore a batch in original
449    /// order by unreading in reverse.
450    ///
451    /// The event was observed on its way out and is deliberately not observed
452    /// again on the way back in: a reply counts once, and observing it twice
453    /// would match it against two of the requests still in flight. These
454    /// events are therefore held by the program, not returned to the shared
455    /// [`EventSource`] — use [`EventSource::unread`] through
456    /// [`event_source`](Self::event_source) for events the program never saw.
457    pub fn unread_event(&mut self, event: Event) {
458        self.unread.push_front(event);
459    }
460
461    /// A shared handle to the input source behind
462    /// [`read_event`](Self::read_event) and friends, for driving input from a
463    /// separate reader over the same decoder rather than a second one racing
464    /// the same file descriptor.
465    ///
466    /// The main use is async input: build an
467    /// [`EventStream`](crate::event::EventStream) with
468    /// [`EventStream::from_shared`](crate::event::EventStream::from_shared) from
469    /// this handle and poll it on your executor.
470    ///
471    /// Events taken this way bypass the program, so capability tracking does
472    /// not run on them — feed each one to
473    /// [`observe_event`](Self::observe_event) yourself.
474    ///
475    /// Sharing one source between a live reader and the program's own
476    /// [`read_event`](Self::read_event) is best-effort: an event goes to
477    /// whichever consumer drains it first, so pick one reader in steady state.
478    pub fn event_source(&self) -> Arc<Mutex<EventSource<I>>> {
479        Arc::clone(&self.source)
480    }
481
482    /// Build an async [`EventStream`](crate::event::EventStream) over this
483    /// program's input, for reading events with `events.next().await` inside a
484    /// `select!` on any executor. The stream shares the program's decoder, so
485    /// it does not race a second reader on the same file descriptor.
486    ///
487    /// The stream hands back events directly, so — unlike
488    /// [`read_event`](Self::read_event) — capability tracking does not run.
489    /// Pass each event to [`observe_event`](Self::observe_event) to keep it
490    /// alive. Read through the stream *or* through `read_event` in steady
491    /// state, not both at once: a shared source hands each event to whichever
492    /// consumer drains it first.
493    #[cfg(feature = "async")]
494    pub fn event_stream(&self) -> crate::event::EventStream<I>
495    where
496        I: 'static,
497    {
498        crate::event::EventStream::from_shared(Arc::clone(&self.source))
499    }
500
501    // --- Capabilities and geometry ---------------------------------------
502
503    /// What the terminal has told us about itself so far, as the replies
504    /// themselves rather than a summary. Empty until the terminal has answered
505    /// something, whichever way the question was put:
506    /// [`query_capabilities`](Self::query_capabilities), an individual
507    /// `request_*` method, or a report the terminal sends unprompted, such as a
508    /// color-scheme change under DEC mode 2031.
509    pub fn capabilities(&self) -> &Capabilities {
510        &self.caps
511    }
512
513    /// Last observed full terminal size in cells, or `None` before the first
514    /// observation. This is the whole terminal, which inline differs from the
515    /// managed area returned by [`Screen::size`].
516    pub fn window_cells(&self) -> Option<Size> {
517        self.window_cells
518    }
519
520    /// Last observed full terminal size in pixels, or `None` when the
521    /// terminal has not reported one.
522    pub fn window_pixels(&self) -> Option<Size> {
523        self.window_pixels
524    }
525
526    /// Size of one character cell in pixels, or `None` when the terminal has
527    /// reported nothing to derive it from.
528    ///
529    /// Prefers the terminal's own `CSI 16 t` reply (see
530    /// [`request_cell_pixel_size`](Self::request_cell_pixel_size)) and
531    /// otherwise divides [`window_pixels`](Self::window_pixels) by
532    /// [`window_cells`](Self::window_cells), which only approximates it: the
533    /// window pixel size includes any padding the terminal draws around the
534    /// grid, so the quotient can be a pixel or two short.
535    ///
536    /// The `CSI 16 t` value is the last one the terminal reported, and it is
537    /// kept until another reply replaces it. A font-size change resizes the
538    /// cell without any reply, so call
539    /// [`request_cell_pixel_size`](Self::request_cell_pixel_size) again after
540    /// a resize to refresh it.
541    pub fn cell_pixels(&self) -> Option<Size> {
542        if let Some(cell) = self.cell_pixels.filter(|c| c.width > 0 && c.height > 0) {
543            return Some(cell);
544        }
545        let pixels = self.window_pixels?;
546        let cells = self.window_cells?;
547        if cells.width == 0 || cells.height == 0 {
548            return None;
549        }
550        let cell = Size::new(pixels.width / cells.width, pixels.height / cells.height);
551        (cell.width > 0 && cell.height > 0).then_some(cell)
552    }
553
554    /// The terminal's self-reported name from its XTVERSION reply (e.g.
555    /// `"XTerm(380)"`), or `None` when it has not answered. Shorthand for
556    /// [`Capabilities::terminal_name`].
557    pub fn terminal_name(&self) -> Option<&str> {
558        self.caps.terminal_name()
559    }
560
561    /// Convert a mouse event carrying pixel coordinates into cell
562    /// coordinates, using [`cell_pixels`](Self::cell_pixels). Returns `None`
563    /// when the cell size is unknown. It is not refreshed on its own: call
564    /// [`request_cell_pixel_size`](Self::request_cell_pixel_size) at startup,
565    /// and again after a resize or a font-size change.
566    pub fn mouse_pixels_to_cells(&self, mouse: crate::event::Mouse) -> Option<crate::event::Mouse> {
567        // A cell size the terminal reported is exact, so dividing by it lands
568        // in the right cell. The derived one is a truncated quotient and is
569        // narrower than the real cell whenever the pixel size is not an exact
570        // multiple of the grid, so dividing by that drifts right across the
571        // row and runs off the end. Scale across the grid in that case.
572        if let Some(cell) = self.cell_pixels.filter(|c| c.width > 0 && c.height > 0) {
573            return Some(crate::event::Mouse::new(
574                mouse.x / cell.width,
575                mouse.y / cell.height,
576                mouse.button,
577                mouse.modifiers,
578            ));
579        }
580        let pixels = self.window_pixels?;
581        let cells = self.window_cells?;
582        if pixels.width == 0 || pixels.height == 0 || cells.width == 0 || cells.height == 0 {
583            return None;
584        }
585        Some(crate::event::mouse_pixel_to_cell(
586            mouse,
587            pixels.width,
588            pixels.height,
589            cells.width,
590            cells.height,
591        ))
592    }
593
594    /// The tracked physical screen coordinate of the managed area's top-left
595    /// cell. Always `(0, 0)` in fullscreen. Inline it holds whatever the last
596    /// [`request_origin`](Self::request_origin) reply reported, and stays at
597    /// `(0, 0)` until you make that call.
598    pub fn origin(&self) -> Position {
599        if self.screen.fullscreen() {
600            Position::ORIGIN
601        } else {
602            self.origin
603        }
604    }
605
606    /// Translate a mouse event's screen coordinates into coordinates relative
607    /// to the managed area, by subtracting the tracked [`origin`](Self::origin).
608    /// A no-op in fullscreen, where the origin is `(0, 0)`, and inline until
609    /// [`request_origin`](Self::request_origin) has answered.
610    pub fn mouse_to_origin(&self, mouse: crate::event::Mouse) -> crate::event::Mouse {
611        let origin = self.origin();
612        crate::event::Mouse::new(
613            mouse.x.saturating_sub(origin.x),
614            mouse.y.saturating_sub(origin.y),
615            mouse.button,
616            mouse.modifiers,
617        )
618    }
619
620    /// Cache a fresh terminal size. Pure bookkeeping: nothing is written, and
621    /// the pixel dimensions are only updated when the size carried them. Some
622    /// platforms (the Windows console) report cell sizes only, leaving
623    /// [`window_pixels`](Self::window_pixels) at its last known value; call
624    /// [`request_window_pixel_size`](Self::request_window_pixel_size) to
625    /// refresh it.
626    fn cache_window_size(&mut self, ws: crate::terminal::Winsize) {
627        self.window_cells = Some(Size::new(ws.col, ws.row));
628        if ws.xpixel > 0 && ws.ypixel > 0 {
629            self.window_pixels = Some(Size::new(ws.xpixel, ws.ypixel));
630        }
631    }
632
633    /// Clip a queried origin so the whole managed area stays on screen: when
634    /// the area is shorter than the terminal, its top row sits no lower than
635    /// `terminal_height - area_height`.
636    fn clip_origin(&self, pos: Position) -> Position {
637        let height = self.screen.size().height;
638        let terminal_height = self.window_cells.map_or(height, |s| s.height);
639        let max_y = terminal_height.saturating_sub(height);
640        Position::new(pos.x, pos.y.min(max_y))
641    }
642
643    /// Apply an event to the program's capability tracking. The event is
644    /// inspected, never consumed.
645    ///
646    /// [`read_event`](Self::read_event) and
647    /// [`try_read_event`](Self::try_read_event) call this for you — you only
648    /// need it when you take events from somewhere else, namely the async
649    /// [`event_stream`](Self::event_stream) or a shared
650    /// [`event_source`](Self::event_source).
651    ///
652    /// Observe each event exactly once. A second call on an event a read
653    /// already observed is not harmless: replies are matched against the
654    /// requests still in flight, so observing one reply twice consumes two
655    /// requests and the answer to the second goes unrecorded.
656    ///
657    /// Capability-report replies to the queries you fire with
658    /// [`query_capabilities`](Self::query_capabilities) and the individual
659    /// `request_*` methods are recorded into
660    /// [`capabilities`](Self::capabilities), window-size reports update
661    /// [`window_cells`](Self::window_cells) /
662    /// [`window_pixels`](Self::window_pixels), and the render-affecting
663    /// reports are applied to the [`Screen`].
664    ///
665    /// A reply is recorded when it says what it is about: a mode report
666    /// carries its mode, an XTGETTCAP reply its capability names, a palette
667    /// reply its index. A DECRPSS setting report
668    /// ([`Event::SettingReport`]) is identified by the DECRQSS request that
669    /// provoked it, and that request is yours, so the report is delivered to
670    /// you to match against what you asked.
671    ///
672    /// Observing is otherwise passive, with one class of exception: a mode
673    /// report proving support for grapheme clusters or in-band resize enables
674    /// that mode when the matching
675    /// [`ProgramOptions`] `prefer_*` field is set, which writes to the
676    /// terminal. That adoption happens only while the application has taken no
677    /// position of its own: calling the mode's `enable_*` or `disable_*`
678    /// method, in either direction and at any point, settles it for good, and
679    /// adoption itself counts as settling it. So each mode is adopted at most
680    /// once, and never against an explicit choice.
681    ///
682    /// Observing never queries. Nothing here asks the terminal a question, so
683    /// no reply appears on the event stream that the application did not ask
684    /// for. Values the terminal only reports on request, such as the pixel
685    /// sizes and the inline [`origin`](Self::origin), go stale until you call
686    /// the matching `request_*` method.
687    ///
688    /// ```ignore
689    /// // Async loop: the stream bypasses the program, so observe explicitly.
690    /// use tokio_stream::StreamExt;
691    ///
692    /// let mut events = program.event_stream();
693    /// while let Some(ev) = events.next().await {
694    ///     let ev = ev?;
695    ///     program.observe_event(&ev)?;
696    ///     // ... handle ev ...
697    ///     program.screen_mut().render()?;
698    /// }
699    /// ```
700    pub fn observe_event(&mut self, event: &Event) -> io::Result<()> {
701        use crate::ansi::mode::Mode;
702        match *event {
703            Event::ModeReport { mode, setting } => {
704                // Record every report, including "not recognized": a definite
705                // no is information an app may want, and is not the same as
706                // the terminal staying silent.
707                self.caps.modes.insert(mode, setting);
708                if mode == Mode::BACKARROW_KEY && setting.is_recognized() {
709                    self.source
710                        .lock()
711                        .unwrap()
712                        .set_backspace_mode(setting.is_set());
713                }
714                // Adopt a preferred mode only while the application has taken
715                // no position on it. Calling enable_* or disable_* records the
716                // position, and adopting records it too, so a mode is adopted
717                // at most once and an explicit choice is never overridden --
718                // including one made before the terminal ever reported, when
719                // the mode field alone still reads as its default. The
720                // caller's options are only read, never rewritten.
721                if setting.is_available() && !self.state.chosen.contains(&mode) {
722                    match mode {
723                        // Render-affecting and free to adopt: the screen emits
724                        // the 2026 markers per frame, so knowing the terminal
725                        // understands them is all it takes. Turn it off for the
726                        // session with `prefer_synchronized_output`, or per
727                        // frame with [`Screen::set_synchronized_output`].
728                        Mode::SYNCHRONIZED_OUTPUT => {
729                            if self.options.prefer_synchronized_output {
730                                self.screen.set_synchronized_output(true);
731                            }
732                            self.state.chosen.insert(mode);
733                        }
734                        Mode::UNICODE_CORE if self.options.prefer_grapheme_clusters => {
735                            self.enable_grapheme_clusters()?;
736                        }
737                        Mode::IN_BAND_RESIZE if self.options.prefer_in_band_resize => {
738                            self.enable_in_band_resize()?;
739                        }
740                        _ => {}
741                    }
742                }
743            }
744            Event::KittyKeyboardEnhancements(flags) => self.caps.kitty_keyboard = Some(flags),
745            // Any modifyOtherKeys report (`CSI > 4 ; n m`) answers our
746            // query, so a reply means the terminal recognizes the feature.
747            Event::ModifyOtherKeys(mode) => self.caps.modify_other_keys = Some(mode),
748            Event::PrimaryDeviceAttributes(ref attrs) => {
749                // Stored unparsed: the attribute numbers are a terminal-author
750                // extension point, so callers test the ones they care about.
751                self.caps.primary_device_attributes = Some(attrs.clone());
752            }
753            Event::SecondaryDeviceAttributes(ref attrs) => {
754                self.caps.secondary_device_attributes = Some(attrs.clone());
755            }
756            Event::TertiaryDeviceAttributes(ref id) => {
757                self.caps.tertiary_device_attributes = Some(id.clone());
758            }
759            Event::TerminalName(ref report) => {
760                self.caps.terminal_name = Some(report.clone());
761            }
762            // A graphics response is the Kitty protocol's own support test:
763            // terminals that do not implement it stay silent.
764            Event::KittyGraphics { .. } => self.caps.kitty_graphics = true,
765            Event::ProgramStatus(ref report) => {
766                self.caps.program_status = Some(report.clone());
767            }
768            // The terminal's own colors, as distinct from the overrides the
769            // facade installs (tracked in `state`).
770            Event::ForegroundColor(color) => self.caps.foreground_color = Some(color),
771            Event::BackgroundColor(color) => self.caps.background_color = Some(color),
772            Event::CursorColor(color) => self.caps.cursor_color = Some(color),
773            Event::PaletteColor { index, color } => {
774                self.caps.palette.insert(index, color);
775            }
776            // Mode 2031 keeps sending these as the scheme changes, so the
777            // record is the current scheme rather than a one-time answer.
778            Event::ColorScheme(scheme) => self.caps.color_scheme = Some(scheme),
779            // Cache the full terminal size as it changes. Refitting the
780            // managed area is left to the app (call autoresize() as desired).
781            Event::Resize(ws) => {
782                self.cache_window_size(ws);
783            }
784            Event::WindowCellSize { width, height } => {
785                self.window_cells = Some(Size::new(width, height));
786            }
787            Event::WindowPixelSize { width, height } => {
788                self.window_pixels = Some(Size::new(width, height));
789            }
790            Event::CellPixelSize { width, height } => {
791                self.cell_pixels = Some(Size::new(width, height));
792            }
793            // Capture the reply to our own `request_origin`. Observing never
794            // consumes, so an application that also queries the cursor still
795            // sees this event.
796            Event::CursorPosition { position, .. } if self.origin_queries_pending > 0 => {
797                self.origin_queries_pending -= 1;
798                self.origin = self.clip_origin(position);
799            }
800            Event::Termcap {
801                recognized,
802                ref entries,
803            } => {
804                // A failure reply echoes the requested names, so it is
805                // recorded as an explicit "not supported" rather than
806                // dropped.
807                for (name, value) in entries {
808                    self.caps.termcap.insert(
809                        name.clone(),
810                        recognized.then(|| value.clone().unwrap_or_default()),
811                    );
812                }
813                // A truecolor capability upgrades the renderer's profile.
814                // The profile, not this record, is the answer to "can I send
815                // 24-bit color": the environment can establish it just as
816                // well, without any terminal reply to record here.
817                if self.caps.supports_termcap("RGB") || self.caps.supports_termcap("Tc") {
818                    self.screen
819                        .set_color_profile(crate::color::Profile::TrueColor);
820                }
821            }
822            _ => {}
823        }
824        Ok(())
825    }
826
827    /// Enable [`TABS`](Optimizations::TABS) and [`BS`](Optimizations::BS),
828    /// the two optimizations raw mode makes safe: `\t` and `\x08` now
829    /// reach the terminal intact instead of being rewritten on the way.
830    ///
831    /// On Unix raw mode clears `OPOST`, disabling output processing
832    /// wholesale, so the kernel can no longer expand `\t` into spaces. On
833    /// Windows it enables virtual-terminal processing, which is the same
834    /// bargain. So this reads no terminal state: `make_raw` returning
835    /// `Ok` *is* the answer, and no `$TERM` baseline carries either flag,
836    /// so a program that never enters raw mode emits escape sequences
837    /// instead.
838    ///
839    /// [`ONLCR`](Optimizations::ONLCR) is deliberately untouched. Raw
840    /// mode makes it false, but it is opt-in: a caller who set it knows
841    /// something about their output path that we do not, and clobbering
842    /// that is worse than the bytes it would save.
843    ///
844    /// Runs after every successful `make_raw`, including
845    /// [`resume`](Self::resume), since whatever ran while paused may have
846    /// put the terminal back into cooked mode.
847    #[cfg(any(unix, windows))]
848    fn enable_tabs_and_bs(&mut self) {
849        let opts = self.screen.optimizations().with_tabs(true).with_bs(true);
850        self.screen.set_optimizations(opts);
851    }
852
853    /// Whether the host is Apple's `Terminal.app`, which does not support
854    /// most of the queried features and mishandles the queries themselves.
855    fn is_apple_terminal(&self) -> bool {
856        self.terminal.get_env("TERM_PROGRAM").as_deref() == Some("Apple_Terminal")
857    }
858
859    /// The major version of Apple's `Terminal.app`, parsed from
860    /// `TERM_PROGRAM_VERSION` (e.g. `"470"` or `"470.1"` yield `470`).
861    /// `None` when the variable is absent or not numeric.
862    fn apple_terminal_version(&self) -> Option<u32> {
863        let raw = self.terminal.get_env("TERM_PROGRAM_VERSION")?;
864        raw.split('.').next()?.trim().parse().ok()
865    }
866
867    /// Detect the environment-derived color profile and apply it to the
868    /// screen, clamping to no color when the output half is not a terminal
869    /// (e.g. redirected to a file or pipe). `is_tty` is the output's
870    /// terminal status; the caller supplies it since the platform handle
871    /// bounds live on `init_with`.
872    fn apply_env_color_profile(&mut self, is_tty: bool) {
873        let profile = Profile::detect_from(self.terminal.env(), is_tty);
874        self.screen.set_color_profile(profile);
875    }
876
877    /// Reconcile the terminal's hardware tab stops with the every-eight
878    /// columns layout the renderer assumes. A prior program may have left
879    /// arbitrary stops behind, which would make the `HT` (`\t`) moves the
880    /// cursor planner emits land on the wrong columns. Modern terminals
881    /// reset in one cursor-safe write via DECST8C; the rest get the
882    /// portable TBC-then-HTS fallback. Staged and flushed so it reaches
883    /// the terminal even when capability queries are disabled.
884    ///
885    /// Runs whether or not `TABS` is set: the stops belong to the
886    /// terminal, not to our willingness to use them, so turning `TABS` on
887    /// later must not find them unknown.
888    fn reset_tab_stops(&mut self) -> io::Result<()> {
889        if Optimizations::supports_decst8c(self.terminal.env()) {
890            self.screen
891                .write_all(crate::ansi::screen::SET_TAB_EVERY_8_COLUMNS)?;
892        } else {
893            let width = self.screen.size().width;
894            crate::ansi::screen::write_reset_tab_stops_every_8(&mut self.screen, width)?;
895        }
896        self.screen.flush()
897    }
898
899    /// Reset LNM (ANSI mode 20) so a `\n` moves the cursor down without
900    /// touching the column.
901    ///
902    /// The cursor planner emits a bare `\n` for downward moves and, unless
903    /// [`Optimizations::ONLCR`] says the host's line discipline expands it,
904    /// carries the column across unchanged. A terminal left in LNM by a prior
905    /// program breaks that assumption on the terminal's side of the wire:
906    /// LNM makes a *received* LF return to column 1, so every horizontal leg
907    /// planned after a `\n` starts from a column the cursor is not in.
908    ///
909    /// No query first. LNM reset is the documented default — the VT510
910    /// manual asks that it be kept reset — so there is nothing to learn from
911    /// asking, and this follows [`reset_tab_stops`](Self::reset_tab_stops) in
912    /// imposing the state the planner assumes rather than trusting what it
913    /// inherited. Reset on every raw-mode entry, since a program run during a
914    /// [`pause`](Self::pause) can set it while we are not looking.
915    ///
916    /// Staged and flushed so it reaches the terminal even when capability
917    /// queries are disabled.
918    fn reset_lnm(&mut self) -> io::Result<()> {
919        mode::Mode::LINE_FEED_NEW_LINE.reset(&mut self.screen)?;
920        self.screen.flush()
921    }
922
923    /// Hand the terminal back: reset every staged mode and the managed area to defaults, and flush. The
924    /// caller restores the saved raw-mode state afterward.
925    fn teardown(&mut self) -> io::Result<()> {
926        self.reset()?;
927        self.screen.flush()
928    }
929}
930
931impl<I, O> Program<I, O>
932where
933    I: Input + Copy,
934    O: Write + Copy,
935{
936    /// Build the screen and event source over `terminal`, sizing the managed
937    /// area to `size`. The color profile and renderer optimizations are
938    /// detected from the terminal's environment. The terminal is
939    /// left as-is.
940    fn with_render(terminal: Terminal<I, O>, size: (u16, u16)) -> io::Result<Self> {
941        let env = terminal.env();
942        // Provisional profile; init_with reapplies it with the real
943        // output-is-tty signal via apply_env_color_profile.
944        let color_profile = Profile::detect_from(env, true);
945        let optimizations = Optimizations::from_env(env);
946        let mut screen = Screen::new(terminal.output(), size);
947        screen.set_color_profile(color_profile);
948        screen.set_optimizations(optimizations);
949
950        let source = Arc::new(Mutex::new(EventSource::new(terminal.input())?));
951        Ok(Self {
952            terminal,
953            screen,
954            source,
955            unread: VecDeque::new(),
956            state: state::State::default(),
957            caps: Capabilities::default(),
958            options: ProgramOptions::default(),
959            window_cells: None,
960            window_pixels: None,
961            cell_pixels: None,
962            origin: Position::ORIGIN,
963            origin_queries_pending: 0,
964        })
965    }
966
967    /// Probe the terminal for its capabilities, then flush.
968    ///
969    /// A [`Program`] never queries the terminal on its own — call this when
970    /// you want [`capabilities`](Self::capabilities) populated. It writes the
971    /// default query set (Kitty keyboard, the DECRQM modes behind
972    /// [`Capabilities`], XTVERSION, xterm modifyOtherKeys, and — when the
973    /// environment did not already imply true color — XTGETTCAP `RGB`/`Tc`),
974    /// then the optional queries selected in [`ProgramOptions`], then `extra`,
975    /// then a Primary DA request.
976    ///
977    /// `extra` is written verbatim, so it can carry any additional query
978    /// escapes you want answered under the same Primary DA terminator. Pass
979    /// `&[]` for none.
980    ///
981    /// The DECRQM, XTVERSION, and XTGETTCAP queries are skipped on Apple's
982    /// `Terminal.app`, which mishandles them. Nothing is recorded in their
983    /// place: [`capabilities`](Self::capabilities) keeps reporting only what
984    /// the terminal actually said. Its known direct-color support is applied
985    /// to the renderer's color profile alone.
986    ///
987    /// # Draining the replies is yours
988    ///
989    /// This method only *writes*. The replies arrive asynchronously as
990    /// ordinary events, and reading them is the caller's job — nothing here
991    /// waits. Primary DA is sent last precisely so its reply terminates the
992    /// stream: read events until [`Event::PrimaryDeviceAttributes`] lands and
993    /// every earlier reply has necessarily arrived, at which point
994    /// [`capabilities`](Self::capabilities) is complete.
995    ///
996    /// If you never read that far, the unread replies are still sitting in the
997    /// input buffer when you restore the terminal, and the shell will see them
998    /// as typed input. A terminal that answers nothing never sends the Primary
999    /// DA reply either, so bound the wait yourself with
1000    /// [`poll_event`](Self::poll_event).
1001    ///
1002    /// ```no_run
1003    /// # use uncurses::{program::Program, event::Event};
1004    /// # use std::time::{Duration, Instant};
1005    /// # fn main() -> std::io::Result<()> {
1006    /// let mut program = Program::stdio()?;
1007    /// program.init()?;
1008    /// program.query_capabilities(&[])?;
1009    ///
1010    /// let deadline = Instant::now() + Duration::from_millis(300);
1011    /// while let Some(timeout) = deadline.checked_duration_since(Instant::now()) {
1012    ///     if !program.poll_event(Some(timeout))? {
1013    ///         break;
1014    ///     }
1015    ///     if matches!(program.try_read_event()?, Some(Event::PrimaryDeviceAttributes(_))) {
1016    ///         break;
1017    ///     }
1018    /// }
1019    /// let caps = program.capabilities();
1020    /// # program.finish()
1021    /// # }
1022    /// ```
1023    ///
1024    /// [`Event::PrimaryDeviceAttributes`]: crate::event::Event::PrimaryDeviceAttributes
1025    pub fn query_capabilities(&mut self, extra: &[u8]) -> io::Result<()> {
1026        use crate::ansi::color;
1027        use crate::ansi::ctrl::{REQUEST_PRIMARY_DA, REQUEST_XTVERSION};
1028        use crate::ansi::kitty::REQUEST_KITTY_KEYBOARD;
1029        use crate::ansi::mode::Mode;
1030        use crate::ansi::termcap::write_xtgettcap;
1031
1032        // The env-derived profile is already applied by init_with via
1033        // apply_env_color_profile; read it back to decide whether there is
1034        // headroom to upgrade via XTGETTCAP.
1035        let profile = self.screen.color_profile();
1036
1037        // Always-safe queries.
1038        self.screen.write_all(REQUEST_KITTY_KEYBOARD)?;
1039
1040        if !self.is_apple_terminal() {
1041            for mode in [
1042                Mode::SYNCHRONIZED_OUTPUT,
1043                Mode::UNICODE_CORE,
1044                Mode::IN_BAND_RESIZE,
1045                Mode::VISIBILITY_REPORTS,
1046                Mode::MOUSE_NORMAL,
1047                Mode::MOUSE_BUTTON,
1048                Mode::MOUSE_ANY,
1049                Mode::MOUSE_SGR,
1050                Mode::MOUSE_SGR_PIXEL,
1051            ] {
1052                mode.request(&mut self.screen)?;
1053            }
1054            if self.options.query_backspace_mode {
1055                Mode::BACKARROW_KEY.request(&mut self.screen)?;
1056            }
1057            self.screen.write_all(REQUEST_XTVERSION)?;
1058            self.screen
1059                .write_all(crate::ansi::xterm::QUERY_MODIFY_OTHER_KEYS)?;
1060            if profile < Profile::TrueColor {
1061                // One key per query: some terminals only answer the first
1062                // capability when several are batched in a single request.
1063                write_xtgettcap(&mut self.screen, &["RGB"])?;
1064                write_xtgettcap(&mut self.screen, &["Tc"])?;
1065            }
1066        } else {
1067            // Terminal.app gained direct-color support in the build shipped
1068            // with macOS Tahoe. It does not answer capability queries, so the
1069            // renderer is upgraded from the version alone; `capabilities()`
1070            // keeps reporting only what the terminal actually said.
1071            if profile < Profile::TrueColor
1072                && self.apple_terminal_version().is_some_and(|v| v >= 470)
1073            {
1074                self.screen.set_color_profile(Profile::TrueColor);
1075            }
1076        }
1077
1078        if self.options.query_foreground_color {
1079            self.screen.write_all(color::REQUEST_FOREGROUND_COLOR)?;
1080        }
1081        if self.options.query_background_color {
1082            self.screen.write_all(color::REQUEST_BACKGROUND_COLOR)?;
1083        }
1084        if self.options.query_cursor_color {
1085            self.screen.write_all(color::REQUEST_CURSOR_COLOR)?;
1086        }
1087        for &index in &self.options.query_palette_colors {
1088            color::write_request_palette_color(&mut self.screen, index)?;
1089        }
1090        if self.options.query_program_status {
1091            self.screen
1092                .write_all(crate::ansi::program_status::REQUEST_PROGRAM_STATUS)?;
1093        }
1094
1095        self.screen.write_all(extra)?;
1096        self.screen.write_all(REQUEST_PRIMARY_DA)?;
1097        self.screen.flush()
1098    }
1099}
1100
1101#[cfg(unix)]
1102impl<I, O> Program<I, O>
1103where
1104    I: Input + Copy + std::os::fd::AsFd,
1105    O: Write + Copy + std::os::fd::AsFd,
1106{
1107    /// Construct a program over `terminal` without touching the terminal:
1108    /// size the screen to it and create an [`EventSource`] on its input
1109    /// half. The terminal is left as-is; call [`Self::init`] to enter raw
1110    /// mode and begin a session.
1111    pub fn new(terminal: Terminal<I, O>) -> io::Result<Self> {
1112        let ws = terminal.get_window_size()?;
1113        Self::with_render(terminal, (ws.col, ws.row))
1114    }
1115
1116    /// Begin a session with the default [`ProgramOptions`]. See
1117    /// [`Self::init_with`].
1118    pub fn init(&mut self) -> io::Result<()> {
1119        self.init_with(ProgramOptions::default())
1120    }
1121
1122    /// Begin a session: enter raw mode and apply the initialization settings
1123    /// from `options`. Store query choices and mode preferences for later use.
1124    ///
1125    /// The `query_*` options take effect when you call
1126    /// [`query_capabilities`](Self::query_capabilities).
1127    /// The `prefer_*` options apply when reported capabilities confirm mode support.
1128    ///
1129    /// Call once after [`Self::new`], before rendering.
1130    pub fn init_with(&mut self, options: ProgramOptions) -> io::Result<()> {
1131        self.options = options;
1132        // The decoder is the only thing that can act on this, and it is
1133        // behind the source, so the option is carried there as it is taken.
1134        self.set_decoder_flags(self.options.legacy_keys);
1135        self.terminal.make_raw()?;
1136        self.enable_tabs_and_bs();
1137        self.reset_lnm()?;
1138        self.autoresize()?;
1139        // Apply the env color profile on every path so output downsamples
1140        // correctly even when capability queries are skipped. Disable color
1141        // when the output is not a terminal (redirected to a file or pipe).
1142        let is_tty = self.terminal.is_terminal().1;
1143        self.apply_env_color_profile(is_tty);
1144        self.reset_tab_stops()?;
1145        if self.options.bracketed_paste {
1146            self.enable_bracketed_paste()?;
1147        }
1148        if let Some(tracking) = self.options.mouse {
1149            self.enable_mouse(tracking)?;
1150        }
1151        Ok(())
1152    }
1153
1154    /// Query the current terminal window size (output half first, input as
1155    /// fallback). This is a live query; the cached
1156    /// [`window_cells`](Self::window_cells) /
1157    /// [`window_pixels`](Self::window_pixels) accessors return the
1158    /// last-observed values without I/O.
1159    pub fn get_window_size(&self) -> io::Result<crate::terminal::Winsize> {
1160        self.terminal.get_window_size()
1161    }
1162
1163    /// Re-query the terminal size and resize the managed area to fit: the full
1164    /// terminal size when fullscreen, or the terminal width with the current
1165    /// managed height preserved when inline. Refreshes the cached
1166    /// [`window_cells`](Self::window_cells), and
1167    /// [`window_pixels`](Self::window_pixels) when the platform reports pixel
1168    /// dimensions. Nothing is asked of the terminal: this reads the size the
1169    /// operating system already knows. On platforms whose size query carries
1170    /// no pixel dimensions (the Windows console), refresh those with
1171    /// [`request_window_pixel_size`](Self::request_window_pixel_size).
1172    ///
1173    /// When the managed area already fits the queried size, this returns
1174    /// without resizing and without repainting, so it is safe to call on every
1175    /// resize report. Terminals send a report per pixel of a window drag while
1176    /// the cell grid only changes at cell boundaries, so most reports ask for a
1177    /// size the area already has. To re-establish the area whatever the size,
1178    /// call [`Screen::resize`](crate::screen::Screen::resize) instead.
1179    pub fn autoresize(&mut self) -> io::Result<()> {
1180        let Ok(ws) = self.terminal.get_window_size() else {
1181            // Keep the current size when the query fails rather than
1182            // collapsing the managed area to zero.
1183            return Ok(());
1184        };
1185        self.cache_window_size(ws);
1186        let height = match self.screen.fullscreen() {
1187            true => ws.row,
1188            false => self.screen.size().height,
1189        };
1190        // Terminals report resizes for changes that leave the cell grid alone
1191        // (a font change that keeps rows and columns, a window move on some
1192        // terminals). This runs on every one of them, and `Screen::resize`
1193        // repaints unconditionally, so skip the ones that change nothing
1194        // rather than flickering on each. A caller that wants the repaint
1195        // anyway calls `Screen::resize` directly.
1196        if self.screen.size() == crate::layout::Size::new(ws.col, height) {
1197            return Ok(());
1198        }
1199        self.screen.resize((ws.col, height));
1200        Ok(())
1201    }
1202
1203    /// Consume the program and hand the terminal back to the shell: tear down
1204    /// every staged mode, reset the managed area, flush, and restore the
1205    /// terminal's prior state.
1206    ///
1207    /// The terminal state is restored even when the teardown writes fail, so a
1208    /// broken pipe cannot leave the terminal in raw mode. The teardown error is
1209    /// still returned.
1210    pub fn finish(mut self) -> io::Result<()> {
1211        // Restore even when teardown fails. Teardown writes to the output
1212        // half, so a broken pipe or a closed terminal fails it routinely, and
1213        // returning early there would leave the terminal raw. `finish` consumes
1214        // the program, so there would be nothing left to retry with.
1215        let teardown = self.teardown();
1216        let restore = self.terminal.restore();
1217        teardown.and(restore)
1218    }
1219
1220    /// Hand the terminal back to the shell without consuming the program,
1221    /// e.g. to run a child process. Re-enter with [`Self::resume`]. Like
1222    /// [`Self::finish`] but keeps the program so the session can continue, and
1223    /// likewise restores the terminal even when the teardown writes fail.
1224    pub fn pause(&mut self) -> io::Result<()> {
1225        // Restore even when teardown fails, for the same reason as `finish`:
1226        // the caller asked for the terminal back, and a failed write to it is
1227        // not a reason to keep it raw.
1228        let teardown = self.teardown();
1229        let restore = self.terminal.restore();
1230        teardown.and(restore)
1231    }
1232
1233    /// Re-acquire the terminal after a [`Self::pause`] or [`Self::suspend`]:
1234    /// re-enter raw mode, refit the managed area to the current viewport, re-apply
1235    /// the saved render state and modes, and force a full repaint.
1236    ///
1237    /// Re-enables [`TABS`](Optimizations::TABS) and
1238    /// [`BS`](Optimizations::BS) and resets the hardware tab stops, since
1239    /// whatever ran while paused may have disturbed both.
1240    pub fn resume(&mut self) -> io::Result<()> {
1241        self.terminal.make_raw()?;
1242        self.enable_tabs_and_bs();
1243        self.reset_lnm()?;
1244        self.autoresize()?;
1245        self.reset_tab_stops()?;
1246        self.restore()?;
1247        self.screen.invalidate();
1248        self.screen.flush()
1249    }
1250
1251    /// Suspend the process: [`pause`](Self::pause) the program, then stop
1252    /// the process with `SIGTSTP`. Returns once the process is
1253    /// foregrounded again; the caller should then call [`Self::resume`].
1254    pub fn suspend(&mut self) -> io::Result<()> {
1255        self.pause()?;
1256        // SAFETY: raise is async-signal-safe.
1257        unsafe { libc::raise(libc::SIGTSTP) };
1258        Ok(())
1259    }
1260}
1261
1262#[cfg(windows)]
1263impl<I, O> Program<I, O>
1264where
1265    I: Input + Copy + std::os::windows::io::AsHandle,
1266    O: Write + Copy + std::os::windows::io::AsHandle,
1267{
1268    /// Construct a program over `terminal` without touching the terminal:
1269    /// size the screen to it and create an [`EventSource`] on its input
1270    /// half. The terminal is left as-is; call [`Self::init`] to enter raw
1271    /// mode and begin a session.
1272    pub fn new(terminal: Terminal<I, O>) -> io::Result<Self> {
1273        let ws = terminal.get_window_size()?;
1274        Self::with_render(terminal, (ws.col, ws.row))
1275    }
1276
1277    /// Begin a session with the default [`ProgramOptions`]. See
1278    /// [`Self::init_with`].
1279    pub fn init(&mut self) -> io::Result<()> {
1280        self.init_with(ProgramOptions::default())
1281    }
1282
1283    /// Begin a session: enter raw mode and apply the initialization settings
1284    /// from `options`. Store query choices and mode preferences for later use.
1285    ///
1286    /// The `query_*` options take effect when you call
1287    /// [`query_capabilities`](Self::query_capabilities).
1288    /// The `prefer_*` options apply when reported capabilities confirm mode support.
1289    ///
1290    /// Call once after [`Self::new`], before rendering.
1291    pub fn init_with(&mut self, options: ProgramOptions) -> io::Result<()> {
1292        self.options = options;
1293        // The decoder is the only thing that can act on this, and it is
1294        // behind the source, so the option is carried there as it is taken.
1295        self.set_decoder_flags(self.options.legacy_keys);
1296        self.terminal.make_raw()?;
1297        self.enable_tabs_and_bs();
1298        self.reset_lnm()?;
1299        self.autoresize()?;
1300        // Apply the env color profile on every path so output downsamples
1301        // correctly even when capability queries are skipped. Disable color
1302        // when the output is not a terminal (redirected to a file or pipe).
1303        let is_tty = self.terminal.is_terminal().1;
1304        self.apply_env_color_profile(is_tty);
1305        self.reset_tab_stops()?;
1306        if self.options.bracketed_paste {
1307            self.enable_bracketed_paste()?;
1308        }
1309        if let Some(tracking) = self.options.mouse {
1310            self.enable_mouse(tracking)?;
1311        }
1312        Ok(())
1313    }
1314
1315    /// Query the current terminal window size (output half first, input as
1316    /// fallback). This is a live query; the cached
1317    /// [`window_cells`](Self::window_cells) /
1318    /// [`window_pixels`](Self::window_pixels) accessors return the
1319    /// last-observed values without I/O.
1320    pub fn get_window_size(&self) -> io::Result<crate::terminal::Winsize> {
1321        self.terminal.get_window_size()
1322    }
1323
1324    /// Re-query the terminal size and resize the managed area to fit: the full
1325    /// terminal size when fullscreen, or the terminal width with the current
1326    /// managed height preserved when inline. Refreshes the cached
1327    /// [`window_cells`](Self::window_cells), and
1328    /// [`window_pixels`](Self::window_pixels) when the platform reports pixel
1329    /// dimensions. Nothing is asked of the terminal: this reads the size the
1330    /// operating system already knows. On platforms whose size query carries
1331    /// no pixel dimensions (the Windows console), refresh those with
1332    /// [`request_window_pixel_size`](Self::request_window_pixel_size).
1333    ///
1334    /// When the managed area already fits the queried size, this returns
1335    /// without resizing and without repainting, so it is safe to call on every
1336    /// resize report. Terminals send a report per pixel of a window drag while
1337    /// the cell grid only changes at cell boundaries, so most reports ask for a
1338    /// size the area already has. To re-establish the area whatever the size,
1339    /// call [`Screen::resize`](crate::screen::Screen::resize) instead.
1340    pub fn autoresize(&mut self) -> io::Result<()> {
1341        let Ok(ws) = self.terminal.get_window_size() else {
1342            // Keep the current size when the query fails rather than
1343            // collapsing the managed area to zero.
1344            return Ok(());
1345        };
1346        self.cache_window_size(ws);
1347        let height = match self.screen.fullscreen() {
1348            true => ws.row,
1349            false => self.screen.size().height,
1350        };
1351        // Terminals report resizes for changes that leave the cell grid alone
1352        // (a font change that keeps rows and columns, a window move on some
1353        // terminals). This runs on every one of them, and `Screen::resize`
1354        // repaints unconditionally, so skip the ones that change nothing
1355        // rather than flickering on each. A caller that wants the repaint
1356        // anyway calls `Screen::resize` directly.
1357        if self.screen.size() == crate::layout::Size::new(ws.col, height) {
1358            return Ok(());
1359        }
1360        self.screen.resize((ws.col, height));
1361        Ok(())
1362    }
1363
1364    /// Consume the program and hand the terminal back to the shell: tear down
1365    /// every staged mode, reset the managed area, flush, and restore the
1366    /// terminal's prior state.
1367    ///
1368    /// The terminal state is restored even when the teardown writes fail, so a
1369    /// broken pipe cannot leave the terminal in raw mode. The teardown error is
1370    /// still returned.
1371    pub fn finish(mut self) -> io::Result<()> {
1372        let teardown = self.teardown();
1373        let restore = self.terminal.restore();
1374        teardown.and(restore)
1375    }
1376
1377    /// Hand the terminal back to the shell without consuming the program,
1378    /// e.g. to run a child process. Re-enter with [`Self::resume`].
1379    pub fn pause(&mut self) -> io::Result<()> {
1380        let teardown = self.teardown();
1381        let restore = self.terminal.restore();
1382        teardown.and(restore)
1383    }
1384
1385    /// Re-acquire the terminal after a [`Self::pause`]: re-enter raw mode,
1386    /// refit the managed area to the current viewport, re-apply the saved
1387    /// render state and modes, and force a full repaint.
1388    ///
1389    /// Re-enables [`TABS`](Optimizations::TABS) and
1390    /// [`BS`](Optimizations::BS) and resets the hardware tab stops, since
1391    /// whatever ran while paused may have disturbed both.
1392    pub fn resume(&mut self) -> io::Result<()> {
1393        self.terminal.make_raw()?;
1394        self.enable_tabs_and_bs();
1395        self.reset_lnm()?;
1396        self.autoresize()?;
1397        self.reset_tab_stops()?;
1398        self.restore()?;
1399        self.screen.invalidate();
1400        self.screen.flush()
1401    }
1402}
1403
1404impl Program<crate::terminal::Stdin, crate::terminal::Stdout> {
1405    /// Build a program over the process stdio (`stdin` + `stdout`).
1406    pub fn stdio() -> io::Result<Self> {
1407        Self::new(Terminal::stdio())
1408    }
1409}
1410
1411impl Program<crate::terminal::TtyInput, crate::terminal::TtyOutput> {
1412    /// Build a program over the controlling terminal (`/dev/tty`, or
1413    /// `CONIN$`/`CONOUT$` on Windows), useful when stdio is redirected.
1414    pub fn open() -> io::Result<Self> {
1415        Self::new(Terminal::open()?)
1416    }
1417}