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();
}
}