brush_core/sys/unix/
terminal.rs

1//! Terminal utilities.
2
3use crate::{error, sys, terminal};
4use std::{io::IsTerminal, os::fd::AsFd};
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    /// * `fd` - The file descriptor of the terminal.
19    pub fn from_term(fd: impl AsFd) -> Result<Self, error::Error> {
20        let termios = nix::sys::termios::tcgetattr(fd)?;
21        Ok(Self { termios })
22    }
23
24    /// Applies the terminal settings to the terminal associated with the given file descriptor.
25    ///
26    /// # Arguments
27    ///
28    /// * `fd` - The file descriptor of the terminal.
29    pub fn apply_to_term(&self, fd: impl AsFd) -> Result<(), error::Error> {
30        nix::sys::termios::tcsetattr(fd, nix::sys::termios::SetArg::TCSANOW, &self.termios)?;
31        Ok(())
32    }
33
34    /// Applies the given high-level terminal settings to this configuration. Does not modify any
35    /// terminal itself.
36    ///
37    /// # Arguments
38    ///
39    /// * `settings` - The high-level terminal settings to apply to this configuration.
40    pub fn update(&mut self, settings: &terminal::Settings) {
41        if let Some(echo_input) = &settings.echo_input {
42            if *echo_input {
43                self.termios.local_flags |= nix::sys::termios::LocalFlags::ECHO;
44            } else {
45                self.termios.local_flags -= nix::sys::termios::LocalFlags::ECHO;
46            }
47        }
48
49        if let Some(line_input) = &settings.line_input {
50            if *line_input {
51                self.termios.local_flags |= nix::sys::termios::LocalFlags::ICANON;
52            } else {
53                self.termios.local_flags -= nix::sys::termios::LocalFlags::ICANON;
54            }
55        }
56
57        if let Some(interrupt_signals) = &settings.interrupt_signals {
58            if *interrupt_signals {
59                self.termios.local_flags |= nix::sys::termios::LocalFlags::ISIG;
60            } else {
61                self.termios.local_flags -= nix::sys::termios::LocalFlags::ISIG;
62            }
63        }
64
65        if let Some(output_nl_as_nlcr) = &settings.output_nl_as_nlcr {
66            if *output_nl_as_nlcr {
67                self.termios.output_flags |=
68                    nix::sys::termios::OutputFlags::OPOST | nix::sys::termios::OutputFlags::ONLCR;
69            } else {
70                self.termios.output_flags -= nix::sys::termios::OutputFlags::ONLCR;
71            }
72        }
73    }
74}
75
76/// Get the process ID of this process's parent.
77pub fn get_parent_process_id() -> Option<sys::process::ProcessId> {
78    Some(nix::unistd::getppid().as_raw())
79}
80
81/// Get the process group ID for this process's process group.
82pub fn get_process_group_id() -> Option<sys::process::ProcessId> {
83    Some(nix::unistd::getpgrp().as_raw())
84}
85
86/// Get the foreground process ID of the attached terminal.
87pub fn get_foreground_pid() -> Option<sys::process::ProcessId> {
88    nix::unistd::tcgetpgrp(std::io::stdin())
89        .ok()
90        .map(|pgid| pgid.as_raw())
91}
92
93/// Move the specified process to the foreground of the attached terminal.
94pub fn move_to_foreground(pid: sys::process::ProcessId) -> Result<(), error::Error> {
95    nix::unistd::tcsetpgrp(std::io::stdin(), nix::unistd::Pid::from_raw(pid))?;
96    Ok(())
97}
98
99/// Moves the current process to the foreground of the attached terminal.
100pub fn move_self_to_foreground() -> Result<(), error::Error> {
101    if std::io::stdin().is_terminal() {
102        let pgid = nix::unistd::getpgid(None)?;
103
104        // TODO: jobs: This sometimes fails with ENOTTY even though we checked that stdin is a
105        // terminal. We should investigate why this is happening.
106        let _ = nix::unistd::tcsetpgrp(std::io::stdin(), pgid);
107    }
108
109    Ok(())
110}