Rust · 3371 bytes Raw Blame History
1 use nix::sys::termios::{tcgetattr, tcsetattr, SetArg, Termios};
2 use nix::unistd::{tcgetpgrp, tcsetpgrp, Pid};
3 use std::io;
4 use std::os::fd::AsFd;
5 use std::sync::Mutex;
6
7 /// Saved terminal attributes for restoration
8 static SAVED_TERMIOS: Mutex<Option<Termios>> = Mutex::new(None);
9
10 /// Give terminal control to a process group
11 ///
12 /// This allows the process group to read from and write to the terminal.
13 /// The shell should call this when putting a job in the foreground.
14 pub fn give_terminal_to(pgid: Pid) -> Result<(), nix::Error> {
15 let stdin = io::stdin();
16 tcsetpgrp(&stdin, pgid)
17 }
18
19 /// Save current terminal attributes
20 /// Call this at shell startup to preserve the initial terminal state
21 pub fn save_terminal_attrs() -> Result<(), nix::Error> {
22 let stdin = io::stdin();
23 let termios = tcgetattr(stdin.as_fd())?;
24 let mut saved = SAVED_TERMIOS.lock().unwrap();
25 *saved = Some(termios);
26 Ok(())
27 }
28
29 /// Restore saved terminal attributes
30 /// Call this when the shell exits or after a child misbehaves
31 pub fn restore_terminal_attrs() -> Result<(), nix::Error> {
32 let saved = SAVED_TERMIOS.lock().unwrap();
33 if let Some(ref termios) = *saved {
34 let stdin = io::stdin();
35 tcsetattr(stdin.as_fd(), SetArg::TCSADRAIN, termios)?;
36 }
37 Ok(())
38 }
39
40 /// Get current terminal attributes
41 pub fn get_terminal_attrs() -> Result<Termios, nix::Error> {
42 let stdin = io::stdin();
43 tcgetattr(stdin.as_fd())
44 }
45
46 /// Set terminal attributes
47 pub fn set_terminal_attrs(termios: &Termios) -> Result<(), nix::Error> {
48 let stdin = io::stdin();
49 tcsetattr(stdin.as_fd(), SetArg::TCSADRAIN, termios)
50 }
51
52 /// Get the current foreground process group of the terminal
53 pub fn get_terminal_pgid() -> Result<Pid, nix::Error> {
54 let stdin = io::stdin();
55 tcgetpgrp(&stdin)
56 }
57
58 /// Restore terminal control to the shell
59 ///
60 /// The shell should call this when a foreground job completes or is suspended.
61 pub fn restore_shell_terminal(shell_pgid: Pid) -> Result<(), nix::Error> {
62 let stdin = io::stdin();
63 tcsetpgrp(&stdin, shell_pgid)
64 }
65
66 /// Initialize shell for job control
67 ///
68 /// This should be called once at shell startup to:
69 /// 1. Put the shell in its own process group
70 /// 2. Take control of the terminal
71 /// 3. Ignore job control signals
72 pub fn setup_shell_terminal() -> Result<Pid, nix::Error> {
73 use nix::sys::signal::{signal, SigHandler, Signal};
74 use nix::unistd::{getpid, setpgid};
75
76 // Put the shell in its own process group
77 let shell_pid = getpid();
78 setpgid(shell_pid, shell_pid)?;
79
80 // Take control of the terminal
81 let stdin = io::stdin();
82 tcsetpgrp(&stdin, shell_pid)?;
83
84 // Ignore job control signals (shell should handle them explicitly)
85 unsafe {
86 signal(Signal::SIGTTOU, SigHandler::SigIgn)?;
87 signal(Signal::SIGTTIN, SigHandler::SigIgn)?;
88 signal(Signal::SIGTSTP, SigHandler::SigIgn)?;
89 }
90
91 Ok(shell_pid)
92 }
93
94 #[cfg(test)]
95 mod tests {
96 use super::*;
97 use std::io::IsTerminal;
98
99 #[test]
100 fn test_get_terminal_pgid() {
101 // Skip if stdin is not a terminal (e.g., in CI)
102 if !std::io::stdin().is_terminal() {
103 return;
104 }
105
106 // This test just verifies the function works without error
107 // The actual pgid will vary
108 let result = get_terminal_pgid();
109 assert!(result.is_ok());
110 }
111 }
112