use std::io::{self, Write};
use std::os::unix::io::AsRawFd;
use std::sync::{atomic::AtomicBool, atomic::Ordering, OnceLock};
static RAW_MODE_ACTIVE: AtomicBool = AtomicBool::new(false);
static ORIGINAL_TERMIOS: OnceLock<libc::termios> = OnceLock::new();
#[must_use = "raw mode is exited when the guard is dropped"]
pub struct RawModeGuard {
fd: i32,
original: libc::termios,
}
impl RawModeGuard {
pub fn enter() -> io::Result<Self> {
let fd = io::stdin().as_raw_fd();
let mut original: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(fd, &mut original) } != 0 {
return Err(io::Error::last_os_error());
}
ORIGINAL_TERMIOS.set(original).ok();
let mut raw = original;
unsafe { libc::cfmakeraw(&mut raw) };
raw.c_cc[libc::VMIN] = 0;
raw.c_cc[libc::VTIME] = 1;
if unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &raw) } != 0 {
return Err(io::Error::last_os_error());
}
RAW_MODE_ACTIVE.store(true, Ordering::SeqCst);
Ok(Self { fd, original })
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
let ret = unsafe { libc::tcsetattr(self.fd, libc::TCSAFLUSH, &self.original) };
if ret == 0 {
RAW_MODE_ACTIVE.store(false, Ordering::SeqCst);
}
}
}
#[must_use = "alternate screen is exited when the guard is dropped"]
pub struct AltScreenGuard;
impl AltScreenGuard {
pub fn enter() -> io::Result<Self> {
let mut out = io::stdout();
out.write_all(b"\x1b[?1049h")?;
out.flush()?;
Ok(Self)
}
}
impl Drop for AltScreenGuard {
fn drop(&mut self) {
let mut out = io::stdout();
let _ = out.write_all(b"\x1b[?1049l");
let _ = out.flush();
}
}
pub fn install_panic_hook() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let mut out = io::stdout();
let _ = out.write_all(b"\x1b[?1049l\x1b[?25h\x1b[0m");
let _ = out.flush();
if RAW_MODE_ACTIVE.load(Ordering::SeqCst) {
if let Some(original) = ORIGINAL_TERMIOS.get() {
let fd = io::stdin().as_raw_fd();
unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, original) };
}
}
default_hook(info);
}));
}
pub fn terminal_size() -> io::Result<(u16, u16)> {
let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
let fd = io::stdout().as_raw_fd();
if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) } != 0 {
return Err(io::Error::last_os_error());
}
if ws.ws_col == 0 || ws.ws_row == 0 {
return Err(io::Error::other("terminal reported 0x0 dimensions"));
}
Ok((ws.ws_col, ws.ws_row))
}
pub fn hide_cursor() -> io::Result<()> {
let mut out = io::stdout();
out.write_all(b"\x1b[?25l")?;
out.flush()
}
pub fn show_cursor() -> io::Result<()> {
let mut out = io::stdout();
out.write_all(b"\x1b[?25h")?;
out.flush()
}