brush_core/
terminal.rs

1//! Terminal control utilities.
2
3use crate::{error, openfiles, sys};
4
5/// Encapsulates the state of a controlled terminal.
6pub struct TerminalControl {
7    prev_fg_pid: Option<sys::process::ProcessId>,
8}
9
10impl TerminalControl {
11    /// Acquire the terminal for the shell.
12    pub fn acquire() -> Result<Self, error::Error> {
13        let prev_fg_pid = sys::terminal::get_foreground_pid();
14
15        // Break out into new process group.
16        // TODO: jobs: Investigate why this sometimes fails with EPERM.
17        let _ = sys::signal::lead_new_process_group();
18
19        // Take ownership.
20        sys::terminal::move_self_to_foreground()?;
21
22        // Mask out SIGTTOU.
23        sys::signal::mask_sigttou()?;
24
25        Ok(Self { prev_fg_pid })
26    }
27
28    fn try_release(&mut self) {
29        // Restore the previous foreground process group.
30        if let Some(pid) = self.prev_fg_pid {
31            if sys::terminal::move_to_foreground(pid).is_ok() {
32                self.prev_fg_pid = None;
33            }
34        }
35    }
36}
37
38impl Drop for TerminalControl {
39    fn drop(&mut self) {
40        self.try_release();
41    }
42}
43
44/// Describes high-level terminal settings that can be requested.
45#[derive(Default, bon::Builder)]
46pub struct Settings {
47    /// Whether to enable input echoing.
48    pub echo_input: Option<bool>,
49    /// Whether to enable line input (sometimes known as canonical mode).
50    pub line_input: Option<bool>,
51    /// Whether to disable interrupt signals and instead yield the control characters.
52    pub interrupt_signals: Option<bool>,
53    /// Whether to output newline characters as CRLF pairs.
54    pub output_nl_as_nlcr: Option<bool>,
55}
56
57/// Guard that automatically restores terminal settings on drop.
58pub struct AutoModeGuard {
59    initial: sys::terminal::Config,
60    file: openfiles::OpenFile,
61}
62
63impl AutoModeGuard {
64    /// Creates a new `AutoModeGuard` for the given file.
65    ///
66    /// # Arguments
67    ///
68    /// * `file` - The file representing the terminal to control.
69    pub fn new(file: openfiles::OpenFile) -> Result<Self, error::Error> {
70        let initial = sys::terminal::Config::from_term(&file)?;
71        Ok(Self { initial, file })
72    }
73
74    /// Applies the given terminal settings.
75    ///
76    /// # Arguments
77    ///
78    /// * `settings` - The terminal settings to apply.
79    pub fn apply_settings(&self, settings: &Settings) -> Result<(), error::Error> {
80        let mut config = sys::terminal::Config::from_term(&self.file)?;
81        config.update(settings);
82        config.apply_to_term(&self.file)?;
83
84        Ok(())
85    }
86}
87
88impl Drop for AutoModeGuard {
89    fn drop(&mut self) {
90        let _ = self.initial.apply_to_term(&self.file);
91    }
92}