Skip to main content

brush_core/sys/unix/
terminal.rs

1//! Terminal utilities.
2
3use crate::{error, openfiles, sys, terminal};
4use std::{io::IsTerminal, os::fd::AsFd, path::PathBuf};
5
6/// Terminal configuration.
7#[derive(Clone, Debug)]
8pub struct Config {
9    termios: nix::sys::termios::Termios,
10}
11
12impl Config {
13    /// Creates a new `Config` from the actual terminal attributes of the terminal associated
14    /// with the given file descriptor.
15    ///
16    /// # Arguments
17    ///
18    /// * `file` - A reference to the open terminal.
19    pub fn from_term(file: &openfiles::OpenFile) -> Result<Self, error::Error> {
20        let fd = file.try_borrow_as_fd()?;
21        let termios = nix::sys::termios::tcgetattr(fd)?;
22        Ok(Self { termios })
23    }
24
25    /// Applies the terminal settings to the terminal associated with the given file descriptor.
26    ///
27    /// # Arguments
28    ///
29    /// * `file` - A reference to the open terminal.
30    pub fn apply_to_term(&self, file: &openfiles::OpenFile) -> Result<(), error::Error> {
31        let fd = file.try_borrow_as_fd()?;
32        nix::sys::termios::tcsetattr(fd, nix::sys::termios::SetArg::TCSANOW, &self.termios)?;
33        Ok(())
34    }
35
36    /// Applies the given high-level terminal settings to this configuration. Does not modify any
37    /// terminal itself.
38    ///
39    /// # Arguments
40    ///
41    /// * `settings` - The high-level terminal settings to apply to this configuration.
42    pub fn update(&mut self, settings: &terminal::Settings) {
43        if let Some(echo_input) = &settings.echo_input {
44            if *echo_input {
45                self.termios.local_flags |= nix::sys::termios::LocalFlags::ECHO;
46            } else {
47                self.termios.local_flags -= nix::sys::termios::LocalFlags::ECHO;
48            }
49        }
50
51        if let Some(line_input) = &settings.line_input {
52            if *line_input {
53                self.termios.local_flags |= nix::sys::termios::LocalFlags::ICANON;
54            } else {
55                self.termios.local_flags -= nix::sys::termios::LocalFlags::ICANON;
56            }
57        }
58
59        if let Some(interrupt_signals) = &settings.interrupt_signals {
60            if *interrupt_signals {
61                self.termios.local_flags |= nix::sys::termios::LocalFlags::ISIG;
62            } else {
63                self.termios.local_flags -= nix::sys::termios::LocalFlags::ISIG;
64            }
65        }
66
67        if let Some(output_nl_as_nlcr) = &settings.output_nl_as_nlcr {
68            if *output_nl_as_nlcr {
69                self.termios.output_flags |=
70                    nix::sys::termios::OutputFlags::OPOST | nix::sys::termios::OutputFlags::ONLCR;
71            } else {
72                self.termios.output_flags -= nix::sys::termios::OutputFlags::ONLCR;
73            }
74        }
75    }
76}
77
78/// Get the process ID of this process's parent.
79pub fn get_parent_process_id() -> Option<sys::process::ProcessId> {
80    Some(nix::unistd::getppid().as_raw())
81}
82
83/// Get the process group ID for this process's process group.
84pub fn get_process_group_id() -> Option<sys::process::ProcessId> {
85    Some(nix::unistd::getpgrp().as_raw())
86}
87
88/// Get the foreground process ID of the attached terminal.
89pub fn get_foreground_pid() -> Option<sys::process::ProcessId> {
90    nix::unistd::tcgetpgrp(std::io::stdin())
91        .ok()
92        .map(|pgid| pgid.as_raw())
93}
94
95/// Move the specified process to the foreground of the attached terminal.
96pub fn move_to_foreground(pid: sys::process::ProcessId) -> Result<(), error::Error> {
97    nix::unistd::tcsetpgrp(std::io::stdin(), nix::unistd::Pid::from_raw(pid))?;
98    Ok(())
99}
100
101/// Moves the current process to the foreground of the attached terminal.
102// This function needs to return `std::io::Error` so that the OS error code can be recovered.
103pub fn move_self_to_foreground() -> Result<(), std::io::Error> {
104    if std::io::stdin().is_terminal() {
105        let pgid = nix::unistd::getpgid(None)?;
106
107        // TODO(jobs): This sometimes fails with ENOTTY even though we checked that stdin is a
108        // terminal. We should investigate why this is happening.
109        let _ = nix::unistd::tcsetpgrp(std::io::stdin(), pgid);
110    }
111
112    Ok(())
113}
114
115/// Tries to get the path of the terminal device associated with the attached terminal.
116/// Returns `None` if there is no terminal attached or the lookup failed.
117pub fn try_get_terminal_device_path() -> Option<PathBuf> {
118    nix::unistd::ttyname(std::io::stdin()).ok()
119}