diff --git a/src/job_control.rs b/src/job_control.rs index e001e63..faaadb2 100644 --- a/src/job_control.rs +++ b/src/job_control.rs @@ -10,6 +10,8 @@ use std::os::unix::process::CommandExt; use libc; +use crate::tty; + static FG_PGID: Lazy = Lazy::new(|| AtomicI32::new(0)); pub fn init_signal_handlers() { @@ -21,6 +23,9 @@ pub fn init_signal_handlers() { libc::SIGCHLD, libc::SIGINT, libc::SIGTSTP, + libc::SIGTERM, + libc::SIGQUIT, + libc::SIGHUP, ]) { Ok(s) => s, Err(e) => { @@ -53,6 +58,13 @@ pub fn init_signal_handlers() { unsafe { libc::kill(-(pgid as libc::pid_t), libc::SIGTSTP) }; } } + libc::SIGTERM | libc::SIGQUIT | libc::SIGHUP => { + // Best-effort: restore terminal state before exiting + crate::tty::restore_original_termios(); + // Exit with a code indicating signal termination. + // Use 128 + signo as conventional shell exit code for signals. + std::process::exit(128 + signal); + } _ => {} } } diff --git a/src/main.rs b/src/main.rs index f4d61ae..59b7e99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod parser; mod job_control; mod terminal; mod wasm_host; +mod tty; use crate::dotfiles::import_dotfiles; use crate::wasm_host::{load_plugin_manifest, WasmPlugin}; @@ -16,6 +17,21 @@ fn main() { // Initialize job control and signal handlers job_control::init_signal_handlers(); + + // Save the current terminal attributes so we can restore them on panic/abnormal exit. + if let Err(e) = tty::save_original_termios() { + eprintln!("warning: failed to save terminal settings: {}", e); + } + + // Install a panic hook that restores the terminal before printing panic info / exiting. + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + // best-effort restore + crate::tty::restore_original_termios(); + // call the default hook so panic info still appears + default_hook(info); + })); + if let Ok(home) = std::env::var("HOME") { match import_dotfiles(std::path::Path::new(&home)) { Ok(imports) => { diff --git a/src/tty.rs b/src/tty.rs new file mode 100644 index 0000000..544eab4 --- /dev/null +++ b/src/tty.rs @@ -0,0 +1,35 @@ +use once_cell::sync::Lazy; +use std::sync::Mutex; +use std::io; + +/// Stores the original terminal attributes so we can restore them on panic/signals. +/// +/// This is intentionally best-effort: saving/restoring termios can fail (e.g. stdin closed), +/// but restoring when possible prevents leaving the user's terminal in an unusable state. +static ORIGINAL_TERMIOS: Lazy>> = Lazy::new(|| Mutex::new(None)); + +/// Save the current terminal attributes once. Safe to call multiple times. +pub fn save_original_termios() -> io::Result<()> { + let mut guard = ORIGINAL_TERMIOS.lock().unwrap(); + if guard.is_some() { + return Ok(()); + } + + let mut t: libc::termios = unsafe { std::mem::zeroed() }; + let r = unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut t as *mut libc::termios) }; + if r != 0 { + return Err(io::Error::last_os_error()); + } + + *guard = Some(t); + Ok(()) +} + +/// Attempt to restore the saved termios. Best-effort; ignore errors. +pub fn restore_original_termios() { + let guard = ORIGINAL_TERMIOS.lock().unwrap(); + if let Some(orig) = guard.as_ref() { + // use TCSANOW to apply immediately; ignore return value (best-effort) + let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, orig as *const libc::termios) }; + } +}