use std::io;
pub(crate) struct TerminalSnapshot {
#[cfg(unix)]
states: Vec<(libc::c_int, libc::termios)>,
#[cfg(windows)]
states: Vec<(windows_sys::Win32::Foundation::HANDLE, u32)>,
restored: bool,
}
impl TerminalSnapshot {
pub(crate) fn capture() -> io::Result<Self> {
#[cfg(unix)]
{
Self::capture_unix(&[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO])
}
#[cfg(windows)]
{
use windows_sys::Win32::System::Console::{GetConsoleMode, GetStdHandle};
use windows_sys::Win32::System::Console::{
STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
let mut states = Vec::new();
for id in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
let handle = unsafe { GetStdHandle(id) };
let mut mode = 0;
if !handle.is_null() && unsafe { GetConsoleMode(handle, &mut mode) } != 0 {
states.push((handle, mode));
}
}
Ok(Self {
states,
restored: false,
})
}
#[cfg(not(any(unix, windows)))]
{
Ok(Self { restored: false })
}
}
#[cfg(unix)]
fn capture_unix(fds: &[libc::c_int]) -> io::Result<Self> {
let mut states = Vec::new();
for &fd in fds {
if unsafe { libc::isatty(fd) } != 1 {
continue;
}
let mut state = unsafe { std::mem::zeroed::<libc::termios>() };
if unsafe { libc::tcgetattr(fd, &mut state) } != 0 {
return Err(io::Error::last_os_error());
}
states.push((fd, state));
}
Ok(Self {
states,
restored: false,
})
}
pub(crate) fn restore(&mut self) -> io::Result<()> {
if self.restored {
return Ok(());
}
#[cfg(unix)]
for (fd, state) in &self.states {
if unsafe { libc::tcsetattr(*fd, libc::TCSANOW, state) } != 0 {
return Err(io::Error::last_os_error());
}
}
#[cfg(windows)]
for (handle, mode) in &self.states {
if unsafe { windows_sys::Win32::System::Console::SetConsoleMode(*handle, *mode) } == 0 {
return Err(io::Error::last_os_error());
}
}
self.restored = true;
Ok(())
}
pub(crate) fn restore_after_abnormal_exit(&mut self) -> io::Result<()> {
use std::io::Write;
if !self.restored {
let mut output = std::io::stdout().lock();
output.write_all(b"\x1b[?25h\x1b[?1049l\x1b[?2004l\x1b[?1004l")?;
output.flush()?;
}
self.restore()
}
}
impl Drop for TerminalSnapshot {
fn drop(&mut self) {
let _ = self.restore();
}
}
pub(crate) struct ForegroundProcessGroup {
#[cfg(unix)]
fd: libc::c_int,
#[cfg(unix)]
original_group: libc::pid_t,
#[cfg(unix)]
original_sigttou: libc::sighandler_t,
active: bool,
}
impl ForegroundProcessGroup {
pub(crate) fn transfer_to(child_pid: Option<u32>) -> io::Result<Self> {
#[cfg(unix)]
{
let fd = libc::STDIN_FILENO;
let Some(child_pid) = child_pid else {
return Ok(Self {
fd,
original_group: 0,
original_sigttou: libc::SIG_DFL,
active: false,
});
};
if unsafe { libc::isatty(fd) } != 1 {
return Ok(Self {
fd,
original_group: 0,
original_sigttou: libc::SIG_DFL,
active: false,
});
}
let original_group = unsafe { libc::tcgetpgrp(fd) };
if original_group < 0 {
return Err(io::Error::last_os_error());
}
let original_sigttou = unsafe { libc::signal(libc::SIGTTOU, libc::SIG_IGN) };
if unsafe { libc::tcsetpgrp(fd, child_pid as libc::pid_t) } != 0 {
unsafe { libc::signal(libc::SIGTTOU, original_sigttou) };
return Err(io::Error::last_os_error());
}
Ok(Self {
fd,
original_group,
original_sigttou,
active: true,
})
}
#[cfg(not(unix))]
{
let _ = child_pid;
Ok(Self { active: false })
}
}
pub(crate) fn restore(&mut self) -> io::Result<()> {
if !self.active {
return Ok(());
}
#[cfg(unix)]
{
if unsafe { libc::tcsetpgrp(self.fd, self.original_group) } != 0 {
return Err(io::Error::last_os_error());
}
unsafe { libc::signal(libc::SIGTTOU, self.original_sigttou) };
}
self.active = false;
Ok(())
}
}
impl Drop for ForegroundProcessGroup {
fn drop(&mut self) {
let _ = self.restore();
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn restores_real_pty_flags_exactly_after_child_style_mutation() {
let mut master = -1;
let mut slave = -1;
let opened = unsafe {
libc::openpty(
&mut master,
&mut slave,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(opened, 0, "openpty: {}", io::Error::last_os_error());
let mut before = unsafe { std::mem::zeroed::<libc::termios>() };
assert_eq!(unsafe { libc::tcgetattr(slave, &mut before) }, 0);
let mut snapshot = TerminalSnapshot::capture_unix(&[slave]).unwrap();
let mut changed = before;
changed.c_lflag ^= libc::ECHO;
assert_eq!(
unsafe { libc::tcsetattr(slave, libc::TCSANOW, &changed) },
0
);
snapshot.restore().unwrap();
let mut after = unsafe { std::mem::zeroed::<libc::termios>() };
assert_eq!(unsafe { libc::tcgetattr(slave, &mut after) }, 0);
assert_eq!(before.c_iflag, after.c_iflag);
assert_eq!(before.c_oflag, after.c_oflag);
assert_eq!(before.c_cflag, after.c_cflag);
assert_eq!(before.c_lflag, after.c_lflag);
assert_eq!(before.c_cc, after.c_cc);
unsafe {
libc::close(master);
libc::close(slave);
}
}
}