Skip to main content

rich_interact/
session.rs

1//! The terminal session: raw mode, the alternate screen, mouse and paste
2//! modes, the kitty keyboard protocol, synchronized output, and their
3//! restoration on every way out (#489).
4//!
5//! Everything a [`Session`] turns on is recorded in a process-wide flag
6//! set, and undone by whichever comes first: [`Session::leave`], dropping
7//! the session (an early return, `?`), or a panic, through a hook installed
8//! on first use that restores the terminal before the panic message prints
9//! (a panic caught by [`catch_panic`], which the session survives, leaves
10//! it alone).
11//! Ctrl+C arrives as a key in raw mode, so it ends the event loop the
12//! ordinary way. On Unix, SIGTERM, SIGHUP and SIGQUIT restore the terminal
13//! too, from a thread that then takes the signal's default action. Ctrl+Z
14//! (a key in raw mode) and SIGTSTP suspend: the terminal is given back, the
15//! process stops as the shell expects, and on `fg` the modes come back and
16//! the view repaints. [`Session::handoff`] gives the terminal to another program
17//! (`$EDITOR`, a pager) and takes it back.
18//!
19//! A session drives the terminal with one library, its
20//! [`BackendKind`]: crossterm by default, or termion (#677) or termwiz
21//! (#678) behind their features. The library reads keys, the mouse and
22//! resizes, measures the terminal, and turns raw mode and the alternate
23//! screen on and off; what is turned on, and how it is given back on every
24//! way out above, is the same whichever it is.
25//!
26//! Where the terminal has synchronized output (DEC private mode 2026), each
27//! frame is written as one synchronized update: between `CSI ? 2026 h` and
28//! `CSI ? 2026 l`, so the terminal shows it whole, without tearing, and a
29//! screen reader that follows the cursor does not see it jump across each
30//! changed region. The terminal is asked in the start-up query that asks
31//! for the kitty keyboard protocol, so detecting it costs no extra wait;
32//! `RICH_SYNC_OUTPUT` and [`SessionOptions::synchronized_output`] override
33//! what it answers.
34//!
35//! The terminal's modes are process-wide, so only one session exists at a
36//! time: starting a second while the first is alive (a component that
37//! calls [`run`](crate::run) from inside another) fails with
38//! [`io::ErrorKind::ResourceBusy`] and changes nothing.
39
40#[cfg(target_arch = "wasm32")]
41use crate::crossterm_stub as crossterm;
42use std::cell::Cell;
43use std::fmt;
44use std::io::{self, Write};
45use std::panic::UnwindSafe;
46use std::process::Command;
47use std::sync::Once;
48use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
49use std::time::{Duration, Instant};
50
51#[cfg(target_arch = "wasm32")]
52use crate::crossterm_stub::{from_crossterm, from_crossterm_kitty};
53use crate::event::Event;
54#[cfg(not(target_arch = "wasm32"))]
55use crate::event::{from_crossterm, from_crossterm_kitty};
56
57#[cfg(all(unix, feature = "termion"))]
58mod with_termion;
59#[cfg(feature = "termwiz")]
60mod with_termwiz;
61
62const RAW: u8 = 1;
63const ALTERNATE: u8 = 2;
64const MOUSE: u8 = 4;
65const PASTE: u8 = 8;
66const KITTY: u8 = 16;
67/// Frames are written as synchronized updates: the restore ends one a write
68/// cut short left open.
69const SYNC: u8 = 32;
70/// The mouse is reported in pixels (SGR-Pixels, mode 1016; #819).
71const PIXELS: u8 = 64;
72
73/// The kitty keyboard protocol's flags a session pushes: disambiguate the
74/// keys a legacy terminal sends alike (1) and report releases and repeats
75/// (2). Not every key as an escape code (8): typed text would then arrive
76/// as a base key and modifiers rather than the character the layout makes,
77/// so plain text keys, Enter, Tab and Backspace report no release. The
78/// terminal keeps a stack of flags for each screen, so they are pushed
79/// after entering the alternate screen and popped before leaving it.
80/// termwiz reads the first flag's keys but not the second's releases, so
81/// a termwiz session pushes the first only.
82const PUSH_KITTY: &str = "\x1b[>3u";
83/// The flags [`SessionOptions::all_keys`] pushes: those two, every key as
84/// an escape code (8), so text keys report releases and modifier keys
85/// report themselves, and the alternate keys (4), so a shifted key still
86/// arrives as the character the layout makes (`!`, not Shift and `1`).
87const PUSH_KITTY_ALL: &str = "\x1b[>15u";
88#[cfg(feature = "termwiz")]
89const PUSH_KITTY_KEYS: &str = "\x1b[>1u";
90const POP_KITTY: &str = "\x1b[<1u";
91
92/// What the terminal answered the start-up query ([`Answers::encode`]): 0
93/// not asked yet. Asked once a process, since asking can wait for a
94/// terminal that does not answer.
95static ANSWERS: AtomicU8 = AtomicU8::new(0);
96
97/// Begins a synchronized update (DEC private mode 2026): the terminal holds
98/// the screen until [`END_SYNCHRONIZED_UPDATE`].
99pub const BEGIN_SYNCHRONIZED_UPDATE: &str = "\x1b[?2026h";
100/// Ends a synchronized update: the terminal shows what was written since
101/// [`BEGIN_SYNCHRONIZED_UPDATE`] in one go.
102pub const END_SYNCHRONIZED_UPDATE: &str = "\x1b[?2026l";
103/// Asks whether the terminal knows mode 2026 (DECRQM). Sent first, before
104/// the kitty query and the device attributes: a reply arriving after the
105/// session stopped waiting then runs into theirs, which crossterm's parser
106/// reads as one answer of its own, never as keys.
107#[cfg(unix)]
108const ASK_SYNC: &str = "\x1b[?2026$p";
109/// The environment variable that turns synchronized output off (`0`) or on
110/// (`1`) whatever the terminal answers.
111pub const SYNC_OUTPUT_ENV: &str = "RICH_SYNC_OUTPUT";
112/// Asks whether the terminal knows SGR-Pixels mouse reports (mode 1016,
113/// DECRQM), in the start-up query after synchronized output's question.
114#[cfg(unix)]
115const ASK_PIXELS: &str = "\x1b[?1016$p";
116/// The environment variable that turns pixel-precise mouse reports off
117/// (`0`) whatever the terminal answers. On by default where the terminal
118/// knows mode 1016 and says how big its cells are.
119pub const PIXEL_MOUSE_ENV: &str = "RICH_PIXEL_MOUSE";
120
121/// Whether `RICH_PIXEL_MOUSE`'s `value` allows pixel reports: anything but
122/// `0` (unset too).
123fn pixels_allowed(value: Option<&str>) -> bool {
124    value.map(str::trim) != Some("0")
125}
126
127/// `frame` as one synchronized update; nothing for an empty frame.
128pub fn synchronized_update(frame: &str) -> String {
129    if frame.is_empty() {
130        return String::new();
131    }
132    let mut out = String::with_capacity(
133        BEGIN_SYNCHRONIZED_UPDATE.len() + frame.len() + END_SYNCHRONIZED_UPDATE.len(),
134    );
135    out.push_str(BEGIN_SYNCHRONIZED_UPDATE);
136    out.push_str(frame);
137    out.push_str(END_SYNCHRONIZED_UPDATE);
138    out
139}
140
141/// What `RICH_SYNC_OUTPUT`'s `value` forces: `0` off, `1` on; anything else,
142/// or unset, leaves it to the terminal's answer.
143fn sync_override(value: Option<&str>) -> Option<bool> {
144    match value.map(str::trim) {
145        Some("0") => Some(false),
146        Some("1") => Some(true),
147        _ => None,
148    }
149}
150
151/// What the environment forces synchronized output to, if anything.
152fn sync_from_env() -> Option<bool> {
153    sync_override(std::env::var(SYNC_OUTPUT_ENV).ok().as_deref())
154}
155
156/// What is currently turned on, for the panic hook.
157static ACTIVE: AtomicU8 = AtomicU8::new(0);
158static HOOK: Once = Once::new();
159/// Whether a [`Session`] is alive. Held from `start` until drop.
160static OWNED: AtomicBool = AtomicBool::new(false);
161/// Whether the live session paints to standard error, for the panic hook.
162static ON_STDERR: AtomicBool = AtomicBool::new(false);
163/// The library the live session drives the terminal with
164/// ([`BackendKind::index`]), for the panic hook and the signal thread.
165static LIBRARY: AtomicU8 = AtomicU8::new(0);
166
167/// The library a [`Session`] drives the terminal with
168/// ([`SessionOptions::backend`]).
169///
170/// Each reads keys, the mouse and resizes its own way and turns raw mode
171/// and the alternate screen on and off itself; the modes a session turns
172/// on, and their restoration on every way out, are the same for all of
173/// them. termion and termwiz are behind the crate's features of the same
174/// names, off by default.
175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
176#[non_exhaustive]
177pub enum BackendKind {
178    /// crossterm, on every platform: the default. With a terminal that has
179    /// the kitty keyboard protocol, every key is
180    /// [`exact`](crate::Key::exact) and releases arrive.
181    #[default]
182    Crossterm,
183    /// termion (feature `termion`), on Unix. Keys arrive as a legacy
184    /// terminal sends them, whatever the terminal: termion does not read
185    /// the kitty keyboard protocol. The mouse comes without modifiers, and
186    /// without movement while no button is held.
187    #[cfg(all(unix, feature = "termion"))]
188    Termion,
189    /// termwiz (feature `termwiz`), on Unix and Windows. With a terminal
190    /// that has the kitty keyboard protocol every key is exact, but releases
191    /// do not arrive: termwiz does not read them. Its probe of the terminal
192    /// (terminfo, `COLORTERM`) decides the colours an
193    /// [`EventLoop`](crate::EventLoop) paints with.
194    #[cfg(feature = "termwiz")]
195    Termwiz,
196}
197
198impl BackendKind {
199    /// Every backend this build has, crossterm first.
200    pub const ALL: &'static [BackendKind] = &[
201        BackendKind::Crossterm,
202        #[cfg(all(unix, feature = "termion"))]
203        BackendKind::Termion,
204        #[cfg(feature = "termwiz")]
205        BackendKind::Termwiz,
206    ];
207
208    /// The library's name: `crossterm`, `termion` or `termwiz`.
209    pub fn name(self) -> &'static str {
210        match self {
211            BackendKind::Crossterm => "crossterm",
212            #[cfg(all(unix, feature = "termion"))]
213            BackendKind::Termion => "termion",
214            #[cfg(feature = "termwiz")]
215            BackendKind::Termwiz => "termwiz",
216        }
217    }
218
219    /// The backend named `name` (any case), if this build has it.
220    pub fn from_name(name: &str) -> Option<BackendKind> {
221        BackendKind::ALL
222            .iter()
223            .copied()
224            .find(|kind| kind.name().eq_ignore_ascii_case(name))
225    }
226
227    fn index(self) -> u8 {
228        match self {
229            BackendKind::Crossterm => 0,
230            #[cfg(all(unix, feature = "termion"))]
231            BackendKind::Termion => 1,
232            #[cfg(feature = "termwiz")]
233            BackendKind::Termwiz => 2,
234        }
235    }
236
237    /// The library the live session uses.
238    fn live() -> BackendKind {
239        let index = LIBRARY.load(Ordering::SeqCst);
240        BackendKind::ALL
241            .iter()
242            .copied()
243            .find(|kind| kind.index() == index)
244            .unwrap_or_default()
245    }
246
247    /// Turn raw mode on or off. `wait`: wait for the library's lock (off
248    /// in the panic hook, whose thread may hold it).
249    fn raw_mode(self, on: bool, wait: bool) -> io::Result<()> {
250        let _ = wait;
251        match self {
252            BackendKind::Crossterm if on => crossterm::terminal::enable_raw_mode(),
253            BackendKind::Crossterm => crossterm::terminal::disable_raw_mode(),
254            #[cfg(all(unix, feature = "termion"))]
255            BackendKind::Termion => with_termion::raw_mode(on, wait),
256            #[cfg(feature = "termwiz")]
257            BackendKind::Termwiz => with_termwiz::raw_mode(on, wait),
258        }
259    }
260
261    /// Whether the library enters and leaves the alternate screen with a
262    /// call of its own, keeping track of it (termwiz), rather than having
263    /// the session write it with the other modes.
264    fn owns_alternate_screen(self) -> bool {
265        #[cfg(feature = "termwiz")]
266        if self == BackendKind::Termwiz {
267            return true;
268        }
269        false
270    }
271
272    /// Enter or leave the alternate screen through a library that
273    /// [owns it](Self::owns_alternate_screen); `None` for one that has the
274    /// session write it, as [`alternate_sequence`](Self::alternate_sequence)
275    /// says.
276    fn alternate_screen(self, on: bool, wait: bool) -> Option<io::Result<()>> {
277        let _ = (on, wait);
278        match self {
279            #[cfg(feature = "termwiz")]
280            BackendKind::Termwiz => Some(with_termwiz::alternate_screen(on, wait)),
281            _ => None,
282        }
283    }
284
285    /// The sequence that enters (`on`) or leaves the alternate screen, for
286    /// a library without a call of its own: termion's
287    /// `ToAlternateScreen` and `ToMainScreen`, and crossterm's same bytes.
288    fn alternate_sequence(self, on: bool) -> String {
289        match self {
290            #[cfg(all(unix, feature = "termion"))]
291            BackendKind::Termion if on => termion::screen::ToAlternateScreen.to_string(),
292            #[cfg(all(unix, feature = "termion"))]
293            BackendKind::Termion => termion::screen::ToMainScreen.to_string(),
294            _ if on => "\x1b[?1049h".into(),
295            _ => "\x1b[?1049l".into(),
296        }
297    }
298
299    /// The kitty keyboard flags this library reads keys with.
300    fn kitty_push(self) -> &'static str {
301        match self {
302            #[cfg(feature = "termwiz")]
303            BackendKind::Termwiz => PUSH_KITTY_KEYS,
304            _ => PUSH_KITTY,
305        }
306    }
307
308    /// What to write once the library's raw mode is off, for what turning
309    /// it off leaves on: termwiz's raw mode sets xterm's modifyOtherKeys to
310    /// level 2 and its cooked mode to 1, and only dropping its terminal
311    /// sets it back to 0, as a shell or a program handed the terminal
312    /// expects.
313    fn after_raw_mode(self) -> &'static str {
314        match self {
315            #[cfg(all(unix, feature = "termwiz"))]
316            BackendKind::Termwiz => "\x1b[>4;0m",
317            _ => "",
318        }
319    }
320
321    /// Give back what the library holds once the session is over.
322    fn release(self) {
323        match self {
324            #[cfg(all(unix, feature = "termion"))]
325            BackendKind::Termion => with_termion::release(),
326            #[cfg(feature = "termwiz")]
327            BackendKind::Termwiz => with_termwiz::release(),
328            _ => {}
329        }
330    }
331}
332
333impl fmt::Display for BackendKind {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        f.write_str(self.name())
336    }
337}
338
339/// Where a session paints.
340#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
341pub enum Output {
342    /// Standard output.
343    #[default]
344    Stdout,
345    /// Standard error, which leaves standard output to the answer a script
346    /// captures: `choice=$(rich choose a b c)`. Keys still come from the
347    /// terminal, which crossterm opens itself when standard input is a pipe.
348    Stderr,
349}
350
351impl Output {
352    /// Whether the stream is a terminal.
353    pub fn is_terminal(self) -> bool {
354        use std::io::IsTerminal;
355        match self {
356            Output::Stdout => io::stdout().is_terminal(),
357            Output::Stderr => io::stderr().is_terminal(),
358        }
359    }
360
361    fn write(self, text: &str) -> io::Result<()> {
362        match self {
363            Output::Stdout => {
364                let mut out = io::stdout().lock();
365                out.write_all(text.as_bytes())?;
366                out.flush()
367            }
368            Output::Stderr => {
369                let mut out = io::stderr().lock();
370                out.write_all(text.as_bytes())?;
371                out.flush()
372            }
373        }
374    }
375
376    /// The live session's stream.
377    fn live() -> Output {
378        if ON_STDERR.load(Ordering::SeqCst) {
379            Output::Stderr
380        } else {
381            Output::Stdout
382        }
383    }
384}
385
386/// Whether keys can be read from a terminal: standard input, or the
387/// process's controlling terminal when standard input is a pipe.
388pub(crate) fn keys_available() -> bool {
389    use std::io::IsTerminal;
390    if io::stdin().is_terminal() {
391        return true;
392    }
393    #[cfg(unix)]
394    let terminal = "/dev/tty";
395    #[cfg(windows)]
396    let terminal = "CONIN$";
397    #[cfg(any(unix, windows))]
398    return std::fs::OpenOptions::new()
399        .read(true)
400        .write(true)
401        .open(terminal)
402        .is_ok();
403    #[cfg(not(any(unix, windows)))]
404    false
405}
406
407/// Undo whatever `ACTIVE` records. Safe to call twice. `wait`: as for
408/// [`BackendKind::raw_mode`].
409fn restore(wait: bool) -> io::Result<()> {
410    let active = ACTIVE.swap(0, Ordering::SeqCst);
411    if active == 0 {
412        return Ok(());
413    }
414    let library = BackendKind::live();
415    let out = undo(active, library);
416    // Every step is tried, so a failed one leaves no other on; the first
417    // error is the one returned.
418    let written = Output::live().write(&out);
419    let left = if active & ALTERNATE != 0 {
420        library.alternate_screen(false, wait).unwrap_or(Ok(()))
421    } else {
422        Ok(())
423    };
424    let raw = if active & RAW != 0 {
425        let off = library.raw_mode(false, wait);
426        let after = match library.after_raw_mode() {
427            "" => Ok(()),
428            after => Output::live().write(after),
429        };
430        off.and(after)
431    } else {
432        Ok(())
433    };
434    written.and(left).and(raw)
435}
436
437/// The sequences that turn off what `active` records: all of it, but for
438/// the alternate screen of a library that leaves it itself. A synchronized
439/// update first, so a frame cut short (a panic, a signal mid-write) is
440/// shown and the rest are not held.
441fn undo(active: u8, library: BackendKind) -> String {
442    let mut out = String::new();
443    if active & SYNC != 0 {
444        out.push_str(END_SYNCHRONIZED_UPDATE);
445    }
446    if active & KITTY != 0 {
447        out.push_str(POP_KITTY);
448    }
449    if active & PIXELS != 0 {
450        out.push_str("\x1b[?1016l");
451    }
452    if active & MOUSE != 0 {
453        out.push_str("\x1b[?1006l\x1b[?1015l\x1b[?1003l\x1b[?1002l\x1b[?1000l");
454    }
455    if active & PASTE != 0 {
456        out.push_str("\x1b[?2004l");
457    }
458    out.push_str("\x1b[?25h");
459    if active & ALTERNATE != 0 && !library.owns_alternate_screen() {
460        out.push_str(&library.alternate_sequence(false));
461    }
462    out
463}
464
465/// Raw mode is on for a line read without echo
466/// ([`StdLineIo::read_secret`](crate::policy::StdLineIo)), outside any
467/// session: the signal thread turns it off too.
468static RAW_LINE: AtomicBool = AtomicBool::new(false);
469#[cfg(unix)]
470static SIGNALS: Once = Once::new();
471
472/// Record that a line is being read in raw mode (`on`), or no longer is.
473pub(crate) fn raw_line(on: bool) {
474    if on {
475        watch_signals();
476    }
477    RAW_LINE.store(on, Ordering::SeqCst);
478}
479
480/// SIGTERM, SIGHUP and SIGQUIT end the process without unwinding, so
481/// neither `Drop` nor the panic hook would give the terminal back. From
482/// the first session on, a thread waits for them: it restores whatever is
483/// on, then takes the signal's default action (the process ends as it
484/// would have). The handlers stay installed once the session ends, since
485/// removing them would leave the signals ignored; with nothing on they
486/// only take the default action.
487fn watch_signals() {
488    #[cfg(unix)]
489    SIGNALS.call_once(|| {
490        use signal_hook::consts::{SIGHUP, SIGQUIT, SIGTERM, SIGTSTP};
491        let Ok(mut signals) =
492            signal_hook::iterator::Signals::new([SIGTERM, SIGHUP, SIGQUIT, SIGTSTP])
493        else {
494            return;
495        };
496        let _ = std::thread::Builder::new()
497            .name("rich-interact-signals".into())
498            .spawn(move || {
499                for signal in signals.forever() {
500                    if signal == SIGTSTP {
501                        suspend();
502                        RESUMED.store(true, Ordering::SeqCst);
503                    } else {
504                        restore_for_signal();
505                        run_teardowns();
506                        let _ = signal_hook::low_level::emulate_default_handler(signal);
507                    }
508                }
509            });
510    });
511}
512
513/// What [`on_terminate`] registered.
514type Teardown = Box<dyn Fn() + Send + Sync>;
515static TEARDOWNS: std::sync::Mutex<Vec<Teardown>> = std::sync::Mutex::new(Vec::new());
516
517/// Run `teardown` when SIGTERM, SIGHUP or SIGQUIT ends the process, from
518/// the first session on: on the signal thread, after the terminal is given
519/// back and before the signal's default action ends the process. Those
520/// signals end it without unwinding, so a `Drop` that stops a child
521/// process or removes temporary files never runs; this is where to do that
522/// work. Keep it short: the process ends when it returns. Teardowns run in
523/// the order they were registered, once each. Not on Windows, which has
524/// none of those signals.
525pub fn on_terminate(teardown: impl Fn() + Send + Sync + 'static) {
526    TEARDOWNS
527        .lock()
528        .unwrap_or_else(|e| e.into_inner())
529        .push(Box::new(teardown));
530}
531
532/// Run and forget every teardown [`on_terminate`] registered.
533#[cfg(unix)]
534fn run_teardowns() {
535    let all = std::mem::take(&mut *TEARDOWNS.lock().unwrap_or_else(|e| e.into_inner()));
536    for teardown in all {
537        teardown();
538    }
539}
540
541/// [`restore`] from the signal thread. The sequences go straight to the
542/// terminal rather than through the standard stream's lock, which the
543/// interrupted thread may hold.
544#[cfg(unix)]
545fn restore_for_signal() {
546    let active = ACTIVE.swap(0, Ordering::SeqCst);
547    let library = BackendKind::live();
548    if active != 0 {
549        write_direct(&undo(active, library));
550    }
551    if active & ALTERNATE != 0 {
552        let _ = library.alternate_screen(false, true);
553    }
554    if active & RAW != 0 {
555        let _ = library.raw_mode(false, true);
556        write_direct(library.after_raw_mode());
557    }
558    if RAW_LINE.swap(false, Ordering::SeqCst) {
559        let _ = crossterm::terminal::disable_raw_mode();
560    }
561}
562
563/// Set when the process came back from a suspend it did not start itself
564/// (SIGTSTP from outside): [`Session::read`] then asks for a repaint.
565static RESUMED: AtomicBool = AtomicBool::new(false);
566
567/// Give the terminal back, stop the process as a shell's job control
568/// expects (Ctrl+Z, `kill -TSTP`), and when it continues (`fg`, SIGCONT)
569/// turn back on what was on. SIGSTOP cannot be caught, so it still stops
570/// with the modes on.
571/// Held for the whole of a suspend, from giving the terminal back until its
572/// modes are on again, and by [`Session::leave`]. A resume from an outside
573/// SIGTSTP runs on the signal thread while the event loop runs on, so
574/// without it a session that ended during the stop (a key queued, a timer
575/// due) would have its modes turned back on after it restored them.
576/// `enter` holds it while it marks the modes on and writes them.
577static SUSPENDING: std::sync::Mutex<()> = std::sync::Mutex::new(());
578
579#[cfg(unix)]
580fn suspend() {
581    let _suspending = SUSPENDING.lock().unwrap_or_else(|e| e.into_inner());
582    let active = ACTIVE.load(Ordering::SeqCst);
583    let raw_line = RAW_LINE.load(Ordering::SeqCst);
584    let library = BackendKind::live();
585    restore_for_signal();
586    // Stops here until SIGCONT.
587    let _ = signal_hook::low_level::emulate_default_handler(signal_hook::consts::SIGTSTP);
588    if active & RAW != 0 {
589        let _ = library.raw_mode(true, true);
590    }
591    if raw_line {
592        let _ = crossterm::terminal::enable_raw_mode();
593    }
594    RAW_LINE.store(raw_line, Ordering::SeqCst);
595    let mut out = String::new();
596    if active & ALTERNATE != 0 {
597        if library.alternate_screen(true, true).is_none() {
598            out.push_str(&library.alternate_sequence(true));
599        }
600        out.push_str("\x1b[H");
601    }
602    if active & KITTY != 0 {
603        out.push_str(library.kitty_push());
604    }
605    if active & MOUSE != 0 {
606        out.push_str("\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
607    }
608    if active & PIXELS != 0 {
609        out.push_str("\x1b[?1016h");
610    }
611    if active & PASTE != 0 {
612        out.push_str("\x1b[?2004h");
613    }
614    if active != 0 {
615        out.push_str("\x1b[?25l");
616    }
617    ACTIVE.fetch_or(active, Ordering::SeqCst);
618    write_direct(&out);
619}
620
621/// `text` straight to the terminal, else to the session's stream.
622#[cfg(unix)]
623fn write_direct(text: &str) {
624    if text.is_empty() {
625        return;
626    }
627    let direct = std::fs::OpenOptions::new()
628        .write(true)
629        .open("/dev/tty")
630        .and_then(|mut tty| tty.write_all(text.as_bytes()));
631    if direct.is_err() {
632        let _ = Output::live().write(text);
633    }
634}
635
636/// A library's lock, for a backend that keeps its terminal where the panic
637/// hook and the signal thread reach it. With `wait` off it gives up at once
638/// rather than wait for a holder, which may be the panicking thread itself.
639#[cfg(any(all(unix, feature = "termion"), feature = "termwiz"))]
640fn lock<T>(mutex: &std::sync::Mutex<T>, wait: bool) -> io::Result<std::sync::MutexGuard<'_, T>> {
641    use std::sync::TryLockError;
642    if wait {
643        return Ok(mutex.lock().unwrap_or_else(|e| e.into_inner()));
644    }
645    match mutex.try_lock() {
646        Ok(guard) => Ok(guard),
647        Err(TryLockError::Poisoned(e)) => Ok(e.into_inner()),
648        Err(TryLockError::WouldBlock) => Err(io::ErrorKind::WouldBlock.into()),
649    }
650}
651
652/// How long the session waits for the terminal to answer a query: a
653/// terminal that answers does so within a few milliseconds, even over a
654/// slow link well inside this.
655#[cfg(unix)]
656const ANSWER_WAIT: Duration = Duration::from_secs(1);
657
658/// How much longer it waits once keys come instead of an answer: someone
659/// is typing, and a terminal that has not answered by then likely never
660/// will. The keys are kept, not lost.
661#[cfg(unix)]
662const ANSWER_WAIT_TYPING: Duration = Duration::from_millis(250);
663
664/// What [`ask_terminal`] read: everything, answer or not (keys typed
665/// meanwhile among it), and whether `done` said the answer was complete.
666#[cfg(unix)]
667struct Asked {
668    bytes: Vec<u8>,
669    answered: bool,
670}
671
672/// Ask the terminal `query` through `/dev/tty`, and read until `done` says
673/// the answer is complete, for [`ANSWER_WAIT`] at most (a terminal that
674/// does not answer), or [`ANSWER_WAIT_TYPING`] after keys come. What it
675/// returns holds anything else read meanwhile, such as keys typed, for
676/// the reader; `None` only when the terminal cannot be opened or asked.
677#[cfg(unix)]
678fn ask_terminal(query: &str, done: impl Fn(&[u8]) -> bool) -> Option<Asked> {
679    use std::io::Read;
680    use std::os::unix::io::AsRawFd;
681    let mut tty = std::fs::OpenOptions::new()
682        .read(true)
683        .write(true)
684        .open("/dev/tty")
685        .ok()?;
686    tty.write_all(query.as_bytes()).ok()?;
687    tty.flush().ok()?;
688    let mut end = Instant::now() + ANSWER_WAIT;
689    let mut typing = false;
690    let mut bytes = Vec::new();
691    loop {
692        if done(&bytes) {
693            return Some(Asked {
694                bytes,
695                answered: true,
696            });
697        }
698        if !typing && !strip_answers(&bytes).is_empty() {
699            typing = true;
700            end = end.min(Instant::now() + ANSWER_WAIT_TYPING);
701        }
702        let left = end.saturating_duration_since(Instant::now());
703        if left.is_zero() {
704            break;
705        }
706        let mut ready = [filedescriptor::pollfd {
707            fd: tty.as_raw_fd(),
708            events: filedescriptor::POLLIN,
709            revents: 0,
710        }];
711        match filedescriptor::poll(&mut ready, Some(left)) {
712            Ok(0) => break,
713            Ok(_) => {}
714            Err(filedescriptor::Error::Poll(e)) if e.kind() == io::ErrorKind::Interrupted => {
715                continue;
716            }
717            Err(_) => break,
718        }
719        let mut buffer = [0u8; 256];
720        match tty.read(&mut buffer) {
721            Ok(0) | Err(_) => break,
722            Ok(read) => bytes.extend_from_slice(&buffer[..read]),
723        }
724    }
725    Some(Asked {
726        bytes,
727        answered: false,
728    })
729}
730
731/// `bytes` without the private answers in it (see [`private_answers`]),
732/// nor one cut short at its end: what was typed meanwhile.
733#[cfg(any(unix, test))]
734fn strip_answers(bytes: &[u8]) -> Vec<u8> {
735    let mut rest = Vec::new();
736    let mut at = 0;
737    while at < bytes.len() {
738        if !bytes[at..].starts_with(b"\x1b[?") {
739            rest.push(bytes[at]);
740            at += 1;
741            continue;
742        }
743        let mut end = at + 3;
744        while end < bytes.len() && (0x20..=0x3f).contains(&bytes[end]) {
745            end += 1;
746        }
747        match bytes.get(end) {
748            // Cut short: it is not keys.
749            None => break,
750            Some(0x40..=0x7e) => at = end + 1,
751            Some(_) => at = end,
752        }
753    }
754    rest
755}
756
757/// One complete `ESC [ ? <parameters> <intermediates> <final>` in what the
758/// terminal answered: the shape of the device attributes answer (`c`), the
759/// kitty flags one (`u`) and DECRQM's (`$ y`).
760#[cfg(any(unix, test))]
761#[derive(Clone, Copy, Debug, PartialEq, Eq)]
762struct PrivateAnswer<'a> {
763    parameters: &'a [u8],
764    intermediates: &'a [u8],
765    last: u8,
766}
767
768/// Each complete private answer in `bytes`, in order. Anything else (keys
769/// typed meanwhile, an answer cut short) is passed over.
770#[cfg(any(unix, test))]
771fn private_answers(bytes: &[u8]) -> impl Iterator<Item = PrivateAnswer<'_>> + '_ {
772    let mut at = 0;
773    std::iter::from_fn(move || {
774        while at + 3 <= bytes.len() {
775            if !bytes[at..].starts_with(b"\x1b[?") {
776                at += 1;
777                continue;
778            }
779            let start = at + 3;
780            let mut end = start;
781            while end < bytes.len() && (0x30..=0x3f).contains(&bytes[end]) {
782                end += 1;
783            }
784            let middle = end;
785            while end < bytes.len() && (0x20..=0x2f).contains(&bytes[end]) {
786                end += 1;
787            }
788            match bytes.get(end) {
789                None => return None,
790                Some(&last @ 0x40..=0x7e) => {
791                    at = end + 1;
792                    return Some(PrivateAnswer {
793                        parameters: &bytes[start..middle],
794                        intermediates: &bytes[middle..end],
795                        last,
796                    });
797                }
798                // Not an answer: look again from the byte that broke it.
799                Some(_) => at = end,
800            }
801        }
802        None
803    })
804}
805
806/// What the terminal answered the start-up query.
807#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
808struct Answers {
809    /// It has the kitty keyboard protocol.
810    kitty: bool,
811    /// It knows synchronized output (mode 2026).
812    sync: bool,
813    /// It knows SGR-Pixels mouse reports (mode 1016).
814    pixels: bool,
815}
816
817impl Answers {
818    /// For [`ANSWERS`]: never 0.
819    fn encode(self) -> u8 {
820        1 | (u8::from(self.kitty) << 1) | (u8::from(self.sync) << 2) | (u8::from(self.pixels) << 3)
821    }
822
823    fn decode(bits: u8) -> Answers {
824        Answers {
825            kitty: bits & 2 != 0,
826            sync: bits & 4 != 0,
827            pixels: bits & 8 != 0,
828        }
829    }
830
831    /// The answers in `bytes`, once the device attributes, which every
832    /// terminal answers last, have come; `None` before. A DECRQM report of
833    /// mode 2026 set, reset or permanently set (1, 2, 3) means the terminal
834    /// knows it; not recognised (0) or permanently reset (4), or no report,
835    /// means it does not.
836    #[cfg(any(unix, test))]
837    fn read(bytes: &[u8]) -> Option<Answers> {
838        let mut answers = Answers::default();
839        for answer in private_answers(bytes) {
840            match (answer.last, answer.intermediates) {
841                (b'c', b"") => return Some(answers),
842                (b'u', b"") => answers.kitty = true,
843                (b'y', b"$") => {
844                    if let Some(state) = answer.parameters.strip_prefix(b"2026;") {
845                        answers.sync = matches!(state, b"1" | b"2" | b"3");
846                    }
847                    if let Some(state) = answer.parameters.strip_prefix(b"1016;") {
848                        answers.pixels = matches!(state, b"1" | b"2" | b"3");
849                    }
850                }
851                _ => {}
852            }
853        }
854        None
855    }
856}
857
858/// The start-up query, asked directly: synchronized output, the kitty
859/// keyboard protocol's flags, then the device attributes, which every
860/// terminal answers, so a terminal without the others answers only the
861/// last. One round trip for both questions. Also what was typed while the
862/// session waited (keys pressed as the program opened), for the reader.
863#[cfg(unix)]
864fn ask_start() -> (Answers, Vec<u8>) {
865    let query = format!("{ASK_SYNC}{ASK_PIXELS}\x1b[?u\x1b[c");
866    match ask_terminal(&query, |bytes| Answers::read(bytes).is_some()) {
867        Some(asked) => {
868            let answers = match asked.answered {
869                true => Answers::read(&asked.bytes).unwrap_or_default(),
870                false => Answers::default(),
871            };
872            (answers, strip_answers(&asked.bytes))
873        }
874        None => (Answers::default(), Vec::new()),
875    }
876}
877
878/// The first complete cursor position report (`ESC [ <row> ; <column> R`)
879/// in `bytes`: its row (0-based), and where it is. Anything else (a key
880/// typed meanwhile, a mouse report, an answer cut short) is passed over.
881#[cfg(any(all(unix, any(feature = "termion", feature = "termwiz")), test))]
882fn cursor_report(bytes: &[u8]) -> Option<(u16, std::ops::Range<usize>)> {
883    let digits = |from: usize| {
884        from + bytes[from..]
885            .iter()
886            .take_while(|b| b.is_ascii_digit())
887            .count()
888    };
889    let mut at = 0;
890    while let Some(found) = bytes[at..].windows(2).position(|w| w == b"\x1b[") {
891        let start = at + found;
892        let row_end = digits(start + 2);
893        if row_end > start + 2 && bytes.get(row_end) == Some(&b';') {
894            let end = digits(row_end + 1);
895            if end > row_end + 1 && bytes.get(end) == Some(&b'R') {
896                let row = std::str::from_utf8(&bytes[start + 2..row_end]).map(str::parse::<u16>);
897                if let Ok(Ok(row)) = row {
898                    return Some((row.saturating_sub(1), start..end + 1));
899                }
900            }
901        }
902        at = start + 1;
903    }
904    None
905}
906
907/// The cursor's row (0-based), asked directly (`CSI 6 n`), and the rest of
908/// what was read with the answer (keys typed meanwhile), for the reader.
909#[cfg(all(unix, any(feature = "termion", feature = "termwiz")))]
910fn ask_cursor_row() -> (Option<u16>, Vec<u8>) {
911    let Some(Asked { bytes, .. }) = ask_terminal("\x1b[6n", |bytes| cursor_report(bytes).is_some())
912    else {
913        return (None, Vec::new());
914    };
915    match cursor_report(&bytes) {
916        Some((row, at)) => {
917            let mut rest = bytes[..at.start].to_vec();
918            rest.extend_from_slice(&bytes[at.end..]);
919            (Some(row), rest)
920        }
921        None => (None, bytes),
922    }
923}
924
925/// The terminal's cell size in pixels (width, height), from the window
926/// size's pixel fields (`TIOCGWINSZ`); `None` where the terminal leaves
927/// them 0 or there is none, and then the mouse stays in cells.
928fn cell_size() -> Option<(u16, u16)> {
929    let size = crossterm::terminal::window_size().ok()?;
930    cell_of(size.columns, size.rows, size.width, size.height)
931}
932
933/// One cell's size for a window of `columns` by `rows` cells and `width`
934/// by `height` pixels; `None` when any is 0.
935fn cell_of(columns: u16, rows: u16, width: u16, height: u16) -> Option<(u16, u16)> {
936    if columns == 0 || rows == 0 || width < columns || height < rows {
937        return None;
938    }
939    Some((width / columns, height / rows))
940}
941
942thread_local! {
943    /// How many [`catch_panic`] calls are running on this thread.
944    static CATCHING: Cell<usize> = const { Cell::new(0) };
945}
946
947/// [`std::panic::catch_unwind`] for code that runs inside a live session
948/// and carries on after a panic (a plugin component's
949/// [`PluginView`](crate::plugin::PluginView)): the session's panic hook
950/// neither gives the terminal back nor prints the panic message over the
951/// view for a panic caught here, since the session goes on. A panic nothing
952/// catches still restores the terminal before its message prints.
953pub fn catch_panic<R>(f: impl FnOnce() -> R + UnwindSafe) -> std::thread::Result<R> {
954    CATCHING.with(|depth| depth.set(depth.get() + 1));
955    let result = std::panic::catch_unwind(f);
956    CATCHING.with(|depth| depth.set(depth.get() - 1));
957    result
958}
959
960fn install_hook() {
961    HOOK.call_once(|| {
962        let previous = std::panic::take_hook();
963        std::panic::set_hook(Box::new(move |info| {
964            // Caught on this thread while a session is live: the session
965            // runs on, and the catcher reports the panic itself.
966            if CATCHING.with(Cell::get) > 0 && ACTIVE.load(Ordering::SeqCst) != 0 {
967                return;
968            }
969            let _ = restore(false);
970            previous(info);
971        }));
972    });
973}
974
975/// What a session turns on.
976#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
977pub struct SessionOptions {
978    /// Take over the whole screen, and give it back as it was on exit.
979    /// Without it, components paint inline, below the cursor.
980    pub alternate_screen: bool,
981    /// Report mouse clicks and the wheel.
982    pub mouse: bool,
983    /// Deliver pasted text as one [`Event::Paste`].
984    pub bracketed_paste: bool,
985    /// Where to paint.
986    pub output: Output,
987    /// Read keys as a legacy terminal sends them, even from a terminal with
988    /// the kitty keyboard protocol. Without it, a terminal that answers the
989    /// protocol's query has it turned on: Ctrl+I and Tab are told apart
990    /// (see [`Key::matches`](crate::Key::matches)), and key releases arrive
991    /// as [`Event::KeyUp`] (with crossterm; see [`BackendKind`]).
992    pub legacy_keys: bool,
993    /// The library that drives the terminal: crossterm unless this says
994    /// otherwise.
995    pub backend: BackendKind,
996    /// Write each frame as one synchronized update (DEC private mode 2026):
997    /// `Some(true)` always, `Some(false)` never. `None`, the default, leaves
998    /// it to `RICH_SYNC_OUTPUT` (`0` off, `1` on) and then to the terminal,
999    /// which is asked in the kitty keyboard protocol's start-up query. A
1000    /// session that asks nothing ([`legacy_keys`](Self::legacy_keys), the
1001    /// termion backend, standard output not a terminal) leaves it off
1002    /// unless this or the environment turns it on.
1003    pub synchronized_output: Option<bool>,
1004    /// With the kitty keyboard protocol, have every key reported as an
1005    /// escape code: releases then arrive for text keys too (letters,
1006    /// Space, Enter), and modifier keys pressed on their own arrive as
1007    /// [`KeyCode::Modifier`](crate::KeyCode::Modifier), as a game wants
1008    /// them. Off by default: typed text then arrives as a base key and the
1009    /// modifiers held, which loses characters an input method composes.
1010    /// crossterm only (termwiz reads no releases); see
1011    /// [`Session::releases_reported`].
1012    pub all_keys: bool,
1013}
1014
1015/// Ends a [`Backend::read`] under way early, from any thread: a value made
1016/// on another thread is then picked up at once, rather than when the read's
1017/// timeout runs out. Clones wake the same reads. A wake that comes while
1018/// no read is under way ends the next one at once, so none is lost; a
1019/// read woken returns `None`, as if its timeout had passed.
1020///
1021/// [`Session`] waits on it with the terminal (a self-pipe beside the
1022/// terminal in one `poll`, on Unix; elsewhere it looks between short
1023/// waits). Any other backend, or a loop of one's own, can wait on it with
1024/// [`wait`](Waker::wait).
1025///
1026/// ```
1027/// use rich_interact::Waker;
1028/// use std::time::Duration;
1029///
1030/// let waker = Waker::new();
1031/// let other = waker.clone();
1032/// std::thread::spawn(move || other.wake());
1033/// assert!(waker.wait(Duration::from_secs(5)));
1034/// // Taken: the next wait waits.
1035/// assert!(!waker.wait(Duration::from_millis(1)));
1036/// ```
1037#[derive(Clone)]
1038pub struct Waker(std::sync::Arc<WakeState>);
1039
1040struct WakeState {
1041    woken: std::sync::Mutex<bool>,
1042    changed: std::sync::Condvar,
1043    /// A byte is written to the second end on a wake, so a `poll` on the
1044    /// first sees it.
1045    #[cfg(unix)]
1046    pipe: Option<(
1047        std::os::unix::net::UnixStream,
1048        std::os::unix::net::UnixStream,
1049    )>,
1050}
1051
1052impl Default for Waker {
1053    fn default() -> Waker {
1054        Waker::new()
1055    }
1056}
1057
1058impl fmt::Debug for Waker {
1059    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1060        f.debug_struct("Waker")
1061            .field("woken", &*self.lock())
1062            .finish()
1063    }
1064}
1065
1066impl Waker {
1067    pub fn new() -> Waker {
1068        #[cfg(unix)]
1069        let pipe = std::os::unix::net::UnixStream::pair()
1070            .ok()
1071            .filter(|(a, b)| a.set_nonblocking(true).is_ok() && b.set_nonblocking(true).is_ok());
1072        Waker(std::sync::Arc::new(WakeState {
1073            woken: std::sync::Mutex::new(false),
1074            changed: std::sync::Condvar::new(),
1075            #[cfg(unix)]
1076            pipe,
1077        }))
1078    }
1079
1080    fn lock(&self) -> std::sync::MutexGuard<'_, bool> {
1081        self.0.woken.lock().unwrap_or_else(|e| e.into_inner())
1082    }
1083
1084    /// End the read under way, or the next one.
1085    pub fn wake(&self) {
1086        let mut woken = self.lock();
1087        if std::mem::replace(&mut *woken, true) {
1088            // Woken already: the byte is there.
1089            return;
1090        }
1091        #[cfg(unix)]
1092        if let Some((_, writer)) = &self.0.pipe {
1093            use std::io::Write as _;
1094            let _ = (&*writer).write(&[1]);
1095        }
1096        self.0.changed.notify_all();
1097    }
1098
1099    /// Whether it was woken since the last call (or [`wait`](Self::wait));
1100    /// it is not woken after.
1101    pub fn take(&self) -> bool {
1102        let mut woken = self.lock();
1103        #[cfg(unix)]
1104        if let Some((reader, _)) = &self.0.pipe {
1105            use std::io::Read as _;
1106            let mut drained = [0u8; 16];
1107            while matches!((&*reader).read(&mut drained), Ok(n) if n > 0) {}
1108        }
1109        std::mem::replace(&mut *woken, false)
1110    }
1111
1112    /// Wait until woken, `timeout` at most; whether it was (and it is not
1113    /// woken after).
1114    pub fn wait(&self, timeout: Duration) -> bool {
1115        {
1116            let woken = self.lock();
1117            let _ = self
1118                .0
1119                .changed
1120                .wait_timeout_while(woken, timeout, |woken| !*woken)
1121                .unwrap_or_else(|e| e.into_inner());
1122        }
1123        self.take()
1124    }
1125
1126    /// What a `poll` waits on to see a wake: readable once woken, until
1127    /// [`take`](Self::take).
1128    #[cfg(unix)]
1129    pub(crate) fn fd(&self) -> Option<std::os::unix::io::RawFd> {
1130        use std::os::unix::io::AsRawFd;
1131        self.0.pipe.as_ref().map(|(reader, _)| reader.as_raw_fd())
1132    }
1133}
1134
1135/// How long a read waits between looks at its [`Waker`] where it cannot
1136/// wait on it with the terminal.
1137const WAKE_SLICE: Duration = Duration::from_millis(4);
1138
1139/// The longest a read woken over and over goes without looking at the
1140/// terminal: a look on every wake costs a wake rate on some terminals
1141/// (macOS's), none starves the keys.
1142const KEYS_SLICE: Duration = Duration::from_millis(20);
1143
1144/// What a read waits on to see a [`Waker`]'s wake: its pipe on Unix.
1145#[cfg(unix)]
1146pub(crate) type WakeFd = std::os::unix::io::RawFd;
1147#[cfg(not(unix))]
1148pub(crate) type WakeFd = ();
1149
1150/// Wait up to `wait` for input on `tty` or a wake on `wake`; whether input
1151/// came, and no wake. A signal ends the wait early, as nothing.
1152#[cfg(unix)]
1153fn input_before_wake(
1154    tty: std::os::unix::io::RawFd,
1155    wake: std::os::unix::io::RawFd,
1156    wait: Duration,
1157) -> io::Result<bool> {
1158    use filedescriptor::{POLLIN, poll, pollfd};
1159    let mut ready = [
1160        pollfd {
1161            fd: tty,
1162            events: POLLIN,
1163            revents: 0,
1164        },
1165        pollfd {
1166            fd: wake,
1167            events: POLLIN,
1168            revents: 0,
1169        },
1170    ];
1171    match poll(&mut ready, Some(wait)) {
1172        Ok(_) => Ok(ready[0].revents != 0 && ready[1].revents == 0),
1173        Err(filedescriptor::Error::Poll(e)) if e.kind() == io::ErrorKind::Interrupted => Ok(false),
1174        Err(e) => Err(io::Error::other(e.to_string())),
1175    }
1176}
1177
1178/// Where the event loop reads events and writes paints: the terminal, or
1179/// the [headless](crate::headless) driver.
1180pub trait Backend {
1181    /// (columns, rows).
1182    fn size(&self) -> (u16, u16);
1183    /// The next event, or `None` when `timeout` passes first. `None` for
1184    /// `timeout` waits as long as it takes.
1185    fn read(&mut self, timeout: Option<Duration>) -> io::Result<Option<Event>>;
1186    /// Write and flush.
1187    fn write(&mut self, text: &str) -> io::Result<()>;
1188    /// Whether [`write_frame`](Backend::write_frame) writes each frame as
1189    /// one synchronized update. The default: no.
1190    fn synchronized_output(&self) -> bool {
1191        false
1192    }
1193    /// Write a frame the painter made: nothing at all when it is empty,
1194    /// and with [`synchronized_output`](Backend::synchronized_output)
1195    /// between `CSI ? 2026 h` and `CSI ? 2026 l`, in the one write.
1196    fn write_frame(&mut self, frame: &str) -> io::Result<()> {
1197        if frame.is_empty() {
1198            Ok(())
1199        } else if self.synchronized_output() {
1200            self.write(&synchronized_update(frame))
1201        } else {
1202            self.write(frame)
1203        }
1204    }
1205    /// Time since the backend started (virtual in the headless driver).
1206    fn elapsed(&self) -> Duration;
1207    /// Run `command` with the terminal as it was before the session, then
1208    /// take the terminal back. Returns the exit code.
1209    fn handoff(&mut self, command: &mut Command) -> io::Result<Option<i32>>;
1210    /// Whether the region is the alternate screen (painted from the top).
1211    fn alternate_screen(&self) -> bool;
1212    /// The terminal row an inline region started on, for placing mouse
1213    /// events; 0 when unknown. The alternate screen always starts at 0.
1214    fn origin(&self) -> u16 {
1215        0
1216    }
1217    /// The plain text of a view just painted: the headless driver records
1218    /// it.
1219    fn painted(&mut self, text: &str) {
1220        let _ = text;
1221    }
1222    /// Whether Ctrl+Z suspends (a real terminal on Unix), rather than
1223    /// reaching the component as a key.
1224    fn can_suspend(&self) -> bool {
1225        false
1226    }
1227    /// Give the terminal back, stop until the shell continues the process,
1228    /// then take the terminal back. Only called when
1229    /// [`can_suspend`](Backend::can_suspend).
1230    fn suspend(&mut self) -> io::Result<()> {
1231        Ok(())
1232    }
1233    /// Whether the process was suspended from outside (SIGTSTP) and has
1234    /// continued since the last call: the screen is then the shell's.
1235    fn take_resumed(&mut self) -> bool {
1236        false
1237    }
1238    /// Whether [`copy`](Backend::copy) reaches a clipboard (#488): `Ok`
1239    /// when it does, or why not. The default: it does not.
1240    fn clipboard(&self) -> Result<(), String> {
1241        Err("this backend has no clipboard".into())
1242    }
1243    /// Put `text` on the clipboard. Only called when
1244    /// [`clipboard`](Backend::clipboard) is `Ok`.
1245    fn copy(&mut self, text: &str) -> io::Result<()> {
1246        let _ = text;
1247        Ok(())
1248    }
1249    /// A [`Waker`] that ends a [`read`](Backend::read) under way early,
1250    /// for another thread to call when it has something for the loop; the
1251    /// same one each time. The default: none, so what another thread sends
1252    /// waits for the read's timeout.
1253    fn waker(&mut self) -> Option<Waker> {
1254        None
1255    }
1256    /// Which key releases the backend reports (see
1257    /// [`Releases`](crate::Releases)). The default: none.
1258    fn releases_reported(&self) -> crate::Releases {
1259        crate::Releases::None
1260    }
1261}
1262
1263/// How a session reads the terminal: the library's reader, opened on the
1264/// first [`Session::enter`] and kept across hand-offs and suspends.
1265enum Input {
1266    Crossterm,
1267    #[cfg(all(unix, feature = "termion"))]
1268    Termion(with_termion::Reader),
1269    #[cfg(feature = "termwiz")]
1270    Termwiz {
1271        held: crate::event::HeldButton,
1272        probed: with_termwiz::Probed,
1273    },
1274}
1275
1276impl Input {
1277    fn open(kind: BackendKind, output: Output) -> io::Result<Input> {
1278        let _ = output;
1279        Ok(match kind {
1280            BackendKind::Crossterm => Input::Crossterm,
1281            #[cfg(all(unix, feature = "termion"))]
1282            BackendKind::Termion => Input::Termion(with_termion::Reader::open()?),
1283            #[cfg(feature = "termwiz")]
1284            BackendKind::Termwiz => Input::Termwiz {
1285                held: Default::default(),
1286                probed: with_termwiz::open(output)?,
1287            },
1288        })
1289    }
1290}
1291
1292/// The real terminal: keys from it, paints to standard output or standard
1293/// error ([`SessionOptions::output`]), through the library
1294/// [`SessionOptions::backend`] names.
1295pub struct Session {
1296    options: SessionOptions,
1297    start: Instant,
1298    active: bool,
1299    /// The row the cursor was on when the session started (inline, with
1300    /// the mouse on).
1301    origin: u16,
1302    /// Whether the terminal takes OSC 52 copies, detected at the start.
1303    clipboard: rich_ext::clipboard::Clipboard,
1304    /// Whether keys are read with the kitty keyboard protocol.
1305    kitty: bool,
1306    /// Whether frames are written as synchronized updates.
1307    sync: bool,
1308    /// The cells' size in pixels while the mouse is reported in pixels
1309    /// (mode 1016): each report is read back into cells with it.
1310    cell: Option<(u16, u16)>,
1311    /// `None` until the first `enter`.
1312    input: Option<Input>,
1313    /// The [`Waker`] handed out, once one was.
1314    waker: Option<Waker>,
1315    /// Keys typed while the start-up query waited for its answer, read
1316    /// before anything else.
1317    typed: std::collections::VecDeque<Event>,
1318    /// When the terminal was last looked at, so a session woken over and
1319    /// over still reads its keys (see [`KEYS_SLICE`]).
1320    looked: Instant,
1321    /// The terminal crossterm reads, when it is not standard input, opened
1322    /// to wait on it beside the waker.
1323    #[cfg(unix)]
1324    tty: Option<std::fs::File>,
1325}
1326
1327/// What the terminal answered the start-up query, asked once a process
1328/// with `ask` (with raw mode on), and the bytes typed while it was asked:
1329/// a terminal that answers none of it, not even the device attributes
1330/// query at its end, keeps the session waiting (a second, or a quarter
1331/// of one once keys come).
1332fn answered(ask: impl FnOnce() -> (Answers, Vec<u8>)) -> (Answers, Vec<u8>) {
1333    match ANSWERS.load(Ordering::SeqCst) {
1334        0 => {
1335            let (answers, typed) = if query_reaches_terminal() {
1336                ask()
1337            } else {
1338                (Answers::default(), Vec::new())
1339            };
1340            ANSWERS.store(answers.encode(), Ordering::SeqCst);
1341            (answers, typed)
1342        }
1343        bits => (Answers::decode(bits), Vec::new()),
1344    }
1345}
1346
1347/// Whether the start-up query is asked. crossterm's own keyboard query
1348/// meant to write to `/dev/tty`, but opened it read-only, so it went to
1349/// standard output: in `answer=$(rich write)` it would have landed in the
1350/// answer, and the terminal's reply at the shell prompt afterwards. The
1351/// session asks directly now, but still only with standard output a
1352/// terminal, so a captured answer starts as it did; every backend asks the
1353/// same way, so a terminal is asked or not whichever drives it.
1354fn query_reaches_terminal() -> bool {
1355    use std::io::IsTerminal;
1356    io::stdout().is_terminal()
1357}
1358
1359impl Session {
1360    /// Turn on raw mode and the options. Fails, changing nothing, when the
1361    /// terminal cannot do raw mode or another session is alive.
1362    pub fn start(options: SessionOptions) -> io::Result<Session> {
1363        if OWNED
1364            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
1365            .is_err()
1366        {
1367            return Err(io::Error::new(
1368                io::ErrorKind::ResourceBusy,
1369                "a terminal session is already running",
1370            ));
1371        }
1372        install_hook();
1373        watch_signals();
1374        // From here on, dropping the session releases ownership, including
1375        // when `enter` fails part way.
1376        let mut session = Session {
1377            options,
1378            start: Instant::now(),
1379            active: false,
1380            origin: 0,
1381            clipboard: rich_ext::clipboard::Clipboard::detect(
1382                &crate::clipboard::SessionEnvironment,
1383            ),
1384            kitty: false,
1385            sync: false,
1386            cell: None,
1387            input: None,
1388            waker: None,
1389            typed: Default::default(),
1390            looked: Instant::now(),
1391            #[cfg(unix)]
1392            tty: None,
1393        };
1394        session.enter()?;
1395        Ok(session)
1396    }
1397
1398    fn enter(&mut self) -> io::Result<()> {
1399        let library = self.options.backend;
1400        ON_STDERR.store(self.options.output == Output::Stderr, Ordering::SeqCst);
1401        LIBRARY.store(library.index(), Ordering::SeqCst);
1402        if self.input.is_none() {
1403            self.input = Some(Input::open(library, self.options.output)?);
1404        }
1405        library.raw_mode(true, true)?;
1406        ACTIVE.fetch_or(RAW, Ordering::SeqCst);
1407        // Active as soon as anything is on, so a failure below still
1408        // restores on drop.
1409        self.active = true;
1410        let mut out = String::new();
1411        if self.options.alternate_screen {
1412            ACTIVE.fetch_or(ALTERNATE, Ordering::SeqCst);
1413            match library.alternate_screen(true, true) {
1414                Some(entered) => entered?,
1415                None => out.push_str(&library.alternate_sequence(true)),
1416            }
1417            out.push_str("\x1b[H");
1418        }
1419        // The start-up query asks for the kitty keyboard protocol and
1420        // synchronized output in one round trip. With nothing to read the
1421        // protocol's keys (legacy keys, termion), it is not asked at all:
1422        // synchronized output then waits on being forced.
1423        let answers = if self.options.legacy_keys {
1424            Answers::default()
1425        } else {
1426            self.answered()
1427        };
1428        self.kitty = answers.kitty;
1429        if self.kitty {
1430            out.push_str(match (library, self.options.all_keys) {
1431                (BackendKind::Crossterm, true) => PUSH_KITTY_ALL,
1432                _ => library.kitty_push(),
1433            });
1434        }
1435        self.sync = self
1436            .options
1437            .synchronized_output
1438            .or_else(sync_from_env)
1439            .unwrap_or(answers.sync);
1440        if self.sync {
1441            ACTIVE.fetch_or(SYNC, Ordering::SeqCst);
1442        }
1443        let (mouse, paste) = self.capable();
1444        if self.options.mouse && mouse {
1445            out.push_str("\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
1446            ACTIVE.fetch_or(MOUSE, Ordering::SeqCst);
1447            // Pixels where the terminal knows them and says how big its
1448            // cells are, else cells (1006) as ever (#819).
1449            self.cell = None;
1450            if answers.pixels
1451                && pixels_allowed(std::env::var(PIXEL_MOUSE_ENV).ok().as_deref())
1452                && let Some(cell) = cell_size()
1453            {
1454                out.push_str("\x1b[?1016h");
1455                ACTIVE.fetch_or(PIXELS, Ordering::SeqCst);
1456                self.cell = Some(cell);
1457            }
1458        }
1459        if self.options.bracketed_paste && paste {
1460            out.push_str("\x1b[?2004h");
1461            ACTIVE.fetch_or(PASTE, Ordering::SeqCst);
1462        }
1463        out.push_str("\x1b[?25l");
1464        {
1465            // The kitty flags are a stack: marked pushed with the write
1466            // that pushes them, under the suspend's lock, so a suspend
1467            // between the two neither pops flags not yet pushed nor
1468            // pushes them on `fg` before this pushes them again.
1469            let _suspending = SUSPENDING.lock().unwrap_or_else(|e| e.into_inner());
1470            if self.kitty {
1471                ACTIVE.fetch_or(KITTY, Ordering::SeqCst);
1472            }
1473            self.options.output.write(&out)?;
1474        }
1475        // Where an inline region starts, so clicks land on the right rows.
1476        // Asking writes a query to standard output, so only when that is
1477        // the terminal being painted.
1478        if self.options.mouse
1479            && !self.options.alternate_screen
1480            && self.options.output == Output::Stdout
1481        {
1482            self.origin = self.cursor_row().unwrap_or(0);
1483        }
1484        Ok(())
1485    }
1486
1487    /// What the terminal answered the start-up query, for a library that
1488    /// reads the kitty keyboard protocol's keys; nothing asked for one that
1489    /// does not. Keys typed while it was asked go to the reader, so none
1490    /// is lost to a terminal slow to answer, or one that never does.
1491    fn answered(&mut self) -> Answers {
1492        let (answers, typed) = match self.options.backend {
1493            #[cfg(unix)]
1494            BackendKind::Crossterm => answered(ask_start),
1495            // Windows: crossterm's own answer, which reads no reply.
1496            #[cfg(not(unix))]
1497            BackendKind::Crossterm => answered(|| {
1498                let kitty = crossterm::terminal::supports_keyboard_enhancement().unwrap_or(false);
1499                (
1500                    Answers {
1501                        kitty,
1502                        ..Answers::default()
1503                    },
1504                    Vec::new(),
1505                )
1506            }),
1507            // termion's reader does not know the protocol's keys.
1508            #[cfg(all(unix, feature = "termion"))]
1509            BackendKind::Termion => (Answers::default(), Vec::new()),
1510            #[cfg(all(unix, feature = "termwiz"))]
1511            BackendKind::Termwiz => answered(ask_start),
1512            #[cfg(all(not(unix), feature = "termwiz"))]
1513            BackendKind::Termwiz => (Answers::default(), Vec::new()),
1514        };
1515        #[cfg(unix)]
1516        if !typed.is_empty() {
1517            match &mut self.input {
1518                #[cfg(feature = "termwiz")]
1519                Some(Input::Termwiz { probed, .. }) => with_termwiz::unread(probed, &typed),
1520                // crossterm reads only the terminal: what was typed is read
1521                // here, as the legacy terminal it still is before the
1522                // protocol is pushed.
1523                _ => self.typed.extend(crate::event::from_legacy_bytes(&typed)),
1524            }
1525        }
1526        // Elsewhere nothing is read before the library's reader opens.
1527        #[cfg(not(unix))]
1528        debug_assert!(typed.is_empty());
1529        answers
1530    }
1531
1532    /// Whether the library's probe of the terminal allows the mouse and
1533    /// bracketed paste.
1534    fn capable(&self) -> (bool, bool) {
1535        match &self.input {
1536            #[cfg(feature = "termwiz")]
1537            Some(Input::Termwiz { probed, .. }) => (probed.mouse, probed.paste),
1538            _ => (true, true),
1539        }
1540    }
1541
1542    /// The cursor's row, asked of the terminal. Keys that came with the
1543    /// answer go to the reader.
1544    fn cursor_row(&mut self) -> Option<u16> {
1545        match self.options.backend {
1546            BackendKind::Crossterm => crossterm::cursor::position().ok().map(|(_, row)| row),
1547            #[cfg(all(unix, any(feature = "termion", feature = "termwiz")))]
1548            #[allow(unreachable_patterns)]
1549            _ => {
1550                let (row, rest) = ask_cursor_row();
1551                match &mut self.input {
1552                    #[cfg(feature = "termion")]
1553                    Some(Input::Termion(reader)) => reader.unread(&rest),
1554                    #[cfg(feature = "termwiz")]
1555                    Some(Input::Termwiz { probed, .. }) => with_termwiz::unread(probed, &rest),
1556                    _ => {}
1557                }
1558                row
1559            }
1560            #[cfg(all(not(unix), feature = "termwiz"))]
1561            _ => None,
1562        }
1563    }
1564
1565    /// The terminal's size, as the library measures it.
1566    fn measure(&self) -> io::Result<(u16, u16)> {
1567        match &self.input {
1568            #[cfg(all(unix, feature = "termion"))]
1569            Some(Input::Termion(reader)) => reader.size(),
1570            #[cfg(feature = "termwiz")]
1571            Some(Input::Termwiz { .. }) => with_termwiz::size(),
1572            _ => crossterm::terminal::size(),
1573        }
1574    }
1575
1576    /// The next event within `wait`, or `None`: the wait passed, or the
1577    /// library read something components do not see (focus changes, a
1578    /// sequence it does not know).
1579    /// `event` with a pixel report read back into cells, while the mouse
1580    /// is reported in pixels; a resize measures the cells again.
1581    fn in_cells(&mut self, event: Event) -> Event {
1582        let Some(cell) = self.cell else {
1583            return event;
1584        };
1585        match event {
1586            Event::Mouse(mouse) => match mouse.reread_as_pixels(cell) {
1587                Some(read) => Event::Mouse(read),
1588                None => Event::Mouse(mouse),
1589            },
1590            Event::Resize { .. } => {
1591                if let Some(size) = cell_size() {
1592                    self.cell = Some(size);
1593                }
1594                event
1595            }
1596            event => event,
1597        }
1598    }
1599
1600    fn next_event(&mut self, wait: Duration) -> io::Result<Option<Event>> {
1601        let kitty = self.kitty;
1602        #[cfg(unix)]
1603        let wake: Option<WakeFd> = self.waker.as_ref().and_then(Waker::fd);
1604        #[cfg(not(unix))]
1605        let wake: Option<WakeFd> = None;
1606        match &mut self.input {
1607            #[cfg(all(unix, feature = "termion"))]
1608            Some(Input::Termion(reader)) => reader.next(wait, wake),
1609            #[cfg(feature = "termwiz")]
1610            Some(Input::Termwiz { held, probed }) => Ok(with_termwiz::poll(probed, wait, wake)?
1611                .and_then(|event| {
1612                    if kitty {
1613                        crate::event::from_termwiz_kitty(event, held)
1614                    } else {
1615                        crate::event::from_termwiz(event, held)
1616                    }
1617                })),
1618            _ => {
1619                // With a waker: wait on the terminal and the waker together,
1620                // then let crossterm read what came. It may hold events
1621                // already read, so it is asked first; a millisecond, as with
1622                // no time at all it does not look at what it holds.
1623                #[cfg(unix)]
1624                if let Some(wake) = wake {
1625                    if !crossterm::event::poll(Duration::from_millis(1))? {
1626                        let tty = self.crossterm_tty()?;
1627                        // Nothing, or woken: back to the loop.
1628                        if !input_before_wake(tty, wake, wait)? {
1629                            return Ok(None);
1630                        }
1631                        // Input: a sequence's rest may come after its
1632                        // start, as crossterm would wait for it.
1633                        if !crossterm::event::poll(wait.min(Duration::from_millis(10)))? {
1634                            return Ok(None);
1635                        }
1636                    }
1637                } else if !crossterm::event::poll(wait)? {
1638                    return Ok(None);
1639                }
1640                #[cfg(not(unix))]
1641                {
1642                    let _ = wake;
1643                    if !crossterm::event::poll(wait)? {
1644                        return Ok(None);
1645                    }
1646                }
1647                let event = crossterm::event::read()?;
1648                Ok(if kitty {
1649                    from_crossterm_kitty(event)
1650                } else {
1651                    from_crossterm(event)
1652                })
1653            }
1654        }
1655    }
1656
1657    /// The terminal crossterm reads, as it finds it: standard input when
1658    /// that is a terminal, else `/dev/tty`.
1659    #[cfg(unix)]
1660    fn crossterm_tty(&mut self) -> io::Result<std::os::unix::io::RawFd> {
1661        use std::io::IsTerminal;
1662        use std::os::unix::io::AsRawFd;
1663        if io::stdin().is_terminal() {
1664            return Ok(io::stdin().as_raw_fd());
1665        }
1666        if self.tty.is_none() {
1667            self.tty = Some(std::fs::File::open("/dev/tty")?);
1668        }
1669        Ok(self.tty.as_ref().map_or(0, |tty| tty.as_raw_fd()))
1670    }
1671
1672    /// Restore the terminal. Also done on drop and on panic.
1673    pub fn leave(&mut self) -> io::Result<()> {
1674        if !std::mem::take(&mut self.active) {
1675            return Ok(());
1676        }
1677        // After any suspend under way has turned the modes back on, so
1678        // this restores them rather than being undone by it.
1679        let _suspending = SUSPENDING.lock().unwrap_or_else(|e| e.into_inner());
1680        restore(true)
1681    }
1682
1683    pub fn options(&self) -> SessionOptions {
1684        self.options
1685    }
1686
1687    /// Whether keys are read with the kitty keyboard protocol: the terminal
1688    /// has it, the backend reads it, and [`SessionOptions::legacy_keys`] is
1689    /// off. Every key is then [`exact`](crate::Key::exact), and with
1690    /// crossterm releases arrive.
1691    pub fn kitty_keys(&self) -> bool {
1692        self.kitty
1693    }
1694
1695    /// Which key releases arrive as [`Event::KeyUp`]: with crossterm and
1696    /// the kitty keyboard protocol, every key's but the text keys', or
1697    /// every key's with [`SessionOptions::all_keys`]; every key's on the
1698    /// Windows console; none otherwise.
1699    pub fn releases_reported(&self) -> crate::Releases {
1700        use crate::Releases;
1701        match self.options.backend {
1702            BackendKind::Crossterm if cfg!(windows) => Releases::All,
1703            BackendKind::Crossterm if self.kitty && self.options.all_keys => Releases::All,
1704            BackendKind::Crossterm if self.kitty => Releases::ExceptText,
1705            #[allow(unreachable_patterns)]
1706            _ => Releases::None,
1707        }
1708    }
1709
1710    /// Whether frames are written as synchronized updates: forced by
1711    /// [`SessionOptions::synchronized_output`] or `RICH_SYNC_OUTPUT`, or
1712    /// the terminal answered that it knows mode 2026.
1713    pub fn synchronized_output(&self) -> bool {
1714        self.sync
1715    }
1716
1717    /// The colours the backend's own probe of the terminal found, when it
1718    /// has one (termwiz's: terminfo and `COLORTERM`); `None` to leave it to
1719    /// rich's detection. `Some(None)` is no colour.
1720    pub fn color_system(&self) -> Option<Option<rich::ColorSystem>> {
1721        match &self.input {
1722            #[cfg(feature = "termwiz")]
1723            Some(Input::Termwiz { probed, .. }) => Some(probed.color),
1724            _ => None,
1725        }
1726    }
1727
1728    /// A resize to the current size, which repaints the whole view: after a
1729    /// suspend, what is on the screen is the shell's.
1730    fn repaint(&mut self) -> Event {
1731        if self.options.mouse
1732            && !self.options.alternate_screen
1733            && self.options.output == Output::Stdout
1734        {
1735            self.origin = self.cursor_row().unwrap_or(0);
1736        }
1737        let (columns, rows) = self.measure().unwrap_or((80, 24));
1738        Event::Resize { columns, rows }
1739    }
1740}
1741
1742impl Drop for Session {
1743    fn drop(&mut self) {
1744        let _ = self.leave();
1745        if self.input.take().is_some() {
1746            self.options.backend.release();
1747        }
1748        OWNED.store(false, Ordering::SeqCst);
1749    }
1750}
1751
1752impl Backend for Session {
1753    fn size(&self) -> (u16, u16) {
1754        self.measure().unwrap_or((80, 24))
1755    }
1756
1757    fn read(&mut self, timeout: Option<Duration>) -> io::Result<Option<Event>> {
1758        if let Some(event) = self.typed.pop_front() {
1759            return Ok(Some(event));
1760        }
1761        let end = timeout.map(|timeout| Instant::now() + timeout);
1762        loop {
1763            // Back from a SIGTSTP sent from outside: the screen is the
1764            // shell's, so repaint all of it.
1765            if RESUMED.load(Ordering::SeqCst) {
1766                return Ok(Some(self.repaint()));
1767            }
1768            // Woken: back to the loop, which has something to pick up. An
1769            // app woken over and over (frames arriving faster than it draws
1770            // them) would never read the keyboard, so every `KEYS_SLICE` a
1771            // key already waiting comes first, with no wait; the loop picks
1772            // up what woke it all the same.
1773            if self.waker.as_ref().is_some_and(Waker::take) {
1774                if self.looked.elapsed() < KEYS_SLICE {
1775                    return Ok(None);
1776                }
1777                self.looked = Instant::now();
1778                return Ok(self.next_event(Duration::ZERO)?.map(|e| self.in_cells(e)));
1779            }
1780            // Short waits, so a resume is noticed without a key; shorter
1781            // where the waker cannot be waited on with the terminal.
1782            let mut wait = match end {
1783                Some(end) => end.saturating_duration_since(Instant::now()),
1784                None => Duration::from_secs(3600),
1785            }
1786            .min(Duration::from_millis(250));
1787            if self.waker.is_some() && !cfg!(unix) {
1788                wait = wait.min(WAKE_SLICE);
1789            }
1790            // Events the library reports that components do not see (focus)
1791            // are skipped, not returned as a timeout.
1792            self.looked = Instant::now();
1793            if let Some(event) = self.next_event(wait)? {
1794                return Ok(Some(self.in_cells(event)));
1795            }
1796            if end.is_some_and(|end| Instant::now() >= end) {
1797                return Ok(None);
1798            }
1799        }
1800    }
1801
1802    fn write(&mut self, text: &str) -> io::Result<()> {
1803        self.options.output.write(text)
1804    }
1805
1806    /// The session's [`Waker`]: on Unix the terminal and it are waited on
1807    /// in one `poll`, on every backend; elsewhere a read looks at it every
1808    /// few milliseconds.
1809    fn waker(&mut self) -> Option<Waker> {
1810        Some(self.waker.get_or_insert_with(Waker::new).clone())
1811    }
1812
1813    fn releases_reported(&self) -> crate::Releases {
1814        Session::releases_reported(self)
1815    }
1816
1817    fn synchronized_output(&self) -> bool {
1818        self.sync
1819    }
1820
1821    fn clipboard(&self) -> Result<(), String> {
1822        let field = self.clipboard.field();
1823        if field.value {
1824            Ok(())
1825        } else {
1826            Err(field.reason.clone())
1827        }
1828    }
1829
1830    fn copy(&mut self, text: &str) -> io::Result<()> {
1831        let sequence = rich_ext::clipboard::osc52(text)
1832            .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error.to_string()))?;
1833        self.options.output.write(&sequence)
1834    }
1835
1836    fn elapsed(&self) -> Duration {
1837        self.start.elapsed()
1838    }
1839
1840    fn handoff(&mut self, command: &mut Command) -> io::Result<Option<i32>> {
1841        self.leave()?;
1842        let status = command.status();
1843        self.enter()?;
1844        Ok(status?.code())
1845    }
1846
1847    fn alternate_screen(&self) -> bool {
1848        self.options.alternate_screen
1849    }
1850
1851    fn origin(&self) -> u16 {
1852        self.origin
1853    }
1854
1855    fn can_suspend(&self) -> bool {
1856        cfg!(unix)
1857    }
1858
1859    fn suspend(&mut self) -> io::Result<()> {
1860        #[cfg(unix)]
1861        {
1862            suspend();
1863            self.repaint();
1864        }
1865        Ok(())
1866    }
1867
1868    fn take_resumed(&mut self) -> bool {
1869        RESUMED.swap(false, Ordering::SeqCst)
1870    }
1871}
1872
1873#[cfg(test)]
1874mod tests {
1875    use super::*;
1876
1877    #[test]
1878    fn backends_are_named() {
1879        assert_eq!(BackendKind::default(), BackendKind::Crossterm);
1880        assert_eq!(BackendKind::ALL[0], BackendKind::Crossterm);
1881        for kind in BackendKind::ALL {
1882            assert_eq!(BackendKind::from_name(kind.name()), Some(*kind));
1883            assert_eq!(
1884                BackendKind::from_name(&kind.name().to_uppercase()),
1885                Some(*kind)
1886            );
1887            assert_eq!(kind.to_string(), kind.name());
1888        }
1889        assert_eq!(BackendKind::from_name("curses"), None);
1890        assert_eq!(
1891            BackendKind::ALL.len(),
1892            1 + usize::from(cfg!(all(unix, feature = "termion")))
1893                + usize::from(cfg!(feature = "termwiz"))
1894        );
1895    }
1896
1897    #[test]
1898    fn every_backend_leaves_the_same_modes() {
1899        let all = RAW | ALTERNATE | MOUSE | PASTE | KITTY;
1900        let expected =
1901            "\x1b[<1u\x1b[?1006l\x1b[?1015l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?2004l\x1b[?25h";
1902        for kind in BackendKind::ALL {
1903            let out = undo(all, *kind);
1904            assert!(out.starts_with(expected), "{kind}: {out:?}");
1905            // The alternate screen last: in the sequences, or by the
1906            // library's own call.
1907            let rest = &out[expected.len()..];
1908            assert!(rest == "\x1b[?1049l" || rest.is_empty(), "{kind}: {rest:?}");
1909            assert_eq!(rest.is_empty(), kind.owns_alternate_screen());
1910        }
1911    }
1912
1913    #[test]
1914    fn answers_are_stripped_from_what_was_typed() {
1915        assert_eq!(strip_answers(b"\x1b[?2026;2$yq\x1b[?62c"), b"q");
1916        assert_eq!(strip_answers(b"ab\x1b[?62"), b"ab");
1917        assert_eq!(strip_answers(b"\x1b[A\x1b"), b"\x1b[A\x1b");
1918        assert!(strip_answers(b"\x1b[?0u\x1b[?62c").is_empty());
1919    }
1920
1921    #[test]
1922    fn private_answers_are_found() {
1923        let lasts = |bytes: &[u8]| -> Vec<u8> { private_answers(bytes).map(|a| a.last).collect() };
1924        assert_eq!(lasts(b"x\x1b[?0u\x1b[?62;22c"), b"uc");
1925        assert_eq!(lasts(b"\x1b[?62"), b"");
1926        let decrqm: Vec<_> = private_answers(b"\x1b[?2026;2$y").collect();
1927        assert_eq!(
1928            decrqm,
1929            [PrivateAnswer {
1930                parameters: b"2026;2",
1931                intermediates: b"$",
1932                last: b'y',
1933            }]
1934        );
1935        // An answer broken off by another sequence is passed over, and the
1936        // next one still read.
1937        assert_eq!(lasts(b"\x1b[?2026\x1b[?62c"), b"c");
1938    }
1939
1940    #[test]
1941    fn the_start_up_answers_are_read() {
1942        let read = |bytes: &[u8]| Answers::read(bytes);
1943        let both = Answers {
1944            kitty: true,
1945            sync: true,
1946            pixels: false,
1947        };
1948        let sync = Answers {
1949            sync: true,
1950            ..Answers::default()
1951        };
1952        // Supported: set or reset (recognised), or permanently set.
1953        assert_eq!(read(b"\x1b[?2026;2$y\x1b[?0u\x1b[?62;22c"), Some(both));
1954        assert_eq!(read(b"\x1b[?2026;1$y\x1b[?62c"), Some(sync));
1955        assert_eq!(read(b"\x1b[?2026;3$y\x1b[?62c"), Some(sync));
1956        // Not recognised, or permanently reset: not supported.
1957        assert_eq!(read(b"\x1b[?2026;0$y\x1b[?62c"), Some(Answers::default()));
1958        assert_eq!(read(b"\x1b[?2026;4$y\x1b[?62c"), Some(Answers::default()));
1959        // No report at all (a terminal that does not know DECRQM).
1960        assert_eq!(
1961            read(b"\x1b[?1u\x1b[?62c"),
1962            Some(Answers {
1963                kitty: true,
1964                ..Answers::default()
1965            })
1966        );
1967        // Another mode's report says nothing about 2026.
1968        assert_eq!(read(b"\x1b[?2004;1$y\x1b[?62c"), Some(Answers::default()));
1969        // Until the device attributes come, the answer is not complete:
1970        // the session waits, then goes on with neither.
1971        assert_eq!(read(b""), None);
1972        assert_eq!(read(b"\x1b[?2026;2$y\x1b[?0u"), None);
1973        assert_eq!(read(b"\x1b[?2026;2$y\x1b[?6"), None);
1974        // Keys typed while it waits, between and inside the answers' runs,
1975        // are passed over.
1976        assert_eq!(
1977            read(b"j\x1b[A\x1b[?2026;2$yk\x1b[?0u\r\x1b[?62;22c"),
1978            Some(both)
1979        );
1980        // SGR-Pixels (1016), asked after 2026, read on its own (#819).
1981        let pixels = Answers {
1982            pixels: true,
1983            ..Answers::default()
1984        };
1985        let all = Answers {
1986            pixels: true,
1987            ..both
1988        };
1989        assert_eq!(read(b"\x1b[?1016;2$y\x1b[?62c"), Some(pixels));
1990        assert_eq!(read(b"\x1b[?1016;1$y\x1b[?62c"), Some(pixels));
1991        assert_eq!(read(b"\x1b[?1016;0$y\x1b[?62c"), Some(Answers::default()));
1992        assert_eq!(read(b"\x1b[?1016;4$y\x1b[?62c"), Some(Answers::default()));
1993        assert_eq!(
1994            read(b"\x1b[?2026;2$y\x1b[?1016;2$y\x1b[?0u\x1b[?62;22c"),
1995            Some(all)
1996        );
1997        // 2026's report says nothing about 1016, and the other way round.
1998        assert_eq!(read(b"\x1b[?2026;2$y\x1b[?62c"), Some(sync));
1999        for answers in [Answers::default(), sync, both, pixels, all] {
2000            assert_ne!(answers.encode(), 0);
2001            assert_eq!(Answers::decode(answers.encode()), answers);
2002        }
2003    }
2004
2005    #[test]
2006    fn the_cell_size_comes_from_the_window_size_in_pixels() {
2007        assert_eq!(cell_of(80, 24, 800, 480), Some((10, 20)));
2008        // Rounded down: a window with a margin of a few pixels.
2009        assert_eq!(cell_of(80, 24, 805, 490), Some((10, 20)));
2010        // A terminal that leaves the pixel fields 0, or no window.
2011        assert_eq!(cell_of(80, 24, 0, 0), None);
2012        assert_eq!(cell_of(0, 0, 800, 480), None);
2013        assert_eq!(cell_of(80, 24, 40, 480), None);
2014    }
2015
2016    #[test]
2017    fn pixel_reports_are_allowed_unless_turned_off() {
2018        assert!(pixels_allowed(None));
2019        assert!(pixels_allowed(Some("1")));
2020        assert!(!pixels_allowed(Some("0")));
2021        assert!(!pixels_allowed(Some(" 0 ")));
2022    }
2023
2024    #[test]
2025    fn the_restore_turns_pixel_reports_off_before_the_mouse() {
2026        for kind in BackendKind::ALL {
2027            let out = undo(RAW | MOUSE | PIXELS, *kind);
2028            assert!(out.starts_with("\x1b[?1016l\x1b[?1006l"), "{kind}: {out:?}");
2029            assert!(!undo(RAW | MOUSE, *kind).contains("1016"));
2030        }
2031    }
2032
2033    #[test]
2034    fn a_cursor_report_is_found_among_other_input() {
2035        assert_eq!(cursor_report(b"\x1b[12;1R"), Some((11, 0..7)));
2036        // After an arrow key, a mouse report or a sequence cut short, and
2037        // with a key after it.
2038        assert_eq!(cursor_report(b"\x1b[A\x1b[12;1R"), Some((11, 3..10)));
2039        assert_eq!(
2040            cursor_report(b"\x1b[<0;2;13M\x1b[3;40Rx"),
2041            Some((2, 10..17))
2042        );
2043        assert_eq!(cursor_report(b"\x1b[1;\x1b[5;1R"), Some((4, 4..10)));
2044        // Not complete, or not a report.
2045        for bytes in [
2046            &b"\x1b[12;"[..],
2047            b"\x1b[12R",
2048            b"\x1b[;1R",
2049            b"\x1b[1;5C",
2050            b"12;1R",
2051        ] {
2052            assert_eq!(cursor_report(bytes), None, "{bytes:?}");
2053        }
2054    }
2055
2056    #[test]
2057    fn the_environment_forces_synchronized_output() {
2058        assert_eq!(sync_override(Some("0")), Some(false));
2059        assert_eq!(sync_override(Some("1")), Some(true));
2060        assert_eq!(sync_override(Some(" 1\n")), Some(true));
2061        assert_eq!(sync_override(None), None);
2062        assert_eq!(sync_override(Some("")), None);
2063        assert_eq!(sync_override(Some("auto")), None);
2064    }
2065
2066    #[test]
2067    fn a_frame_is_one_synchronized_update_and_an_empty_one_nothing() {
2068        assert_eq!(
2069            synchronized_update("\x1b[2;1Hhi\x1b[?25l"),
2070            "\x1b[?2026h\x1b[2;1Hhi\x1b[?25l\x1b[?2026l"
2071        );
2072        assert_eq!(synchronized_update(""), "");
2073    }
2074
2075    #[test]
2076    fn the_restore_ends_a_synchronized_update_first() {
2077        for kind in BackendKind::ALL {
2078            let out = undo(RAW | SYNC | MOUSE, *kind);
2079            assert!(out.starts_with("\x1b[?2026l\x1b[?1006l"), "{kind}: {out:?}");
2080            assert_eq!(out.matches("2026").count(), 1);
2081            // Off, nothing about it.
2082            assert!(!undo(RAW | MOUSE, *kind).contains("2026"));
2083        }
2084    }
2085}