#[cfg(not(unix))]
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::Once;
const SHOW_CURSOR: &[u8] = b"\x1b[?25h";
const LEAVE_ALT: &[u8] = b"\x1b[?1049l";
const RESET_SGR: &[u8] = b"\x1b[0m";
const SYNC_END: &[u8] = b"\x1b[?2026l";
static CURSOR_HIDDEN: AtomicBool = AtomicBool::new(false);
static ALT_SCREEN: AtomicBool = AtomicBool::new(false);
static ARMED: Once = Once::new();
pub(crate) fn set_cursor_hidden(hidden: bool) {
CURSOR_HIDDEN.store(hidden, SeqCst);
}
pub(crate) fn set_alt_screen(active: bool) {
ALT_SCREEN.store(active, SeqCst);
}
pub(crate) fn arm() {
ARMED.call_once(|| {
enable_virtual_terminal();
install_panic_hook();
install_signal_handler();
});
}
pub(crate) fn restore() {
let mut buf = Vec::with_capacity(32);
buf.extend_from_slice(SYNC_END);
buf.extend_from_slice(RESET_SGR);
if CURSOR_HIDDEN.swap(false, SeqCst) {
buf.extend_from_slice(SHOW_CURSOR);
}
if ALT_SCREEN.swap(false, SeqCst) {
buf.extend_from_slice(LEAVE_ALT);
}
write_stdout(&buf);
}
fn install_panic_hook() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
restore();
previous(info);
}));
}
#[cfg(unix)]
fn write_stdout(buf: &[u8]) {
unsafe {
let _ = libc::write(libc::STDOUT_FILENO, buf.as_ptr().cast(), buf.len());
}
}
#[cfg(unix)]
fn install_signal_handler() {
for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
unsafe {
libc::signal(signal, handler as *const () as libc::sighandler_t);
}
}
}
#[cfg(unix)]
extern "C" fn handler(signal: libc::c_int) {
restore();
unsafe {
libc::signal(signal, libc::SIG_DFL);
libc::raise(signal);
}
}
#[cfg(windows)]
fn write_stdout(buf: &[u8]) {
let mut out = std::io::stdout();
let _ = out.write_all(buf);
let _ = out.flush();
}
#[cfg(windows)]
mod win {
pub type Bool = i32;
pub type Dword = u32;
pub const CTRL_C_EVENT: Dword = 0;
pub const CTRL_BREAK_EVENT: Dword = 1;
pub const CTRL_CLOSE_EVENT: Dword = 2;
pub const STD_OUTPUT_HANDLE: Dword = -11i32 as Dword;
pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: Dword = 0x0004;
pub type Handle = *mut core::ffi::c_void;
#[link(name = "kernel32")]
extern "system" {
pub fn SetConsoleCtrlHandler(
handler: Option<unsafe extern "system" fn(Dword) -> Bool>,
add: Bool,
) -> Bool;
pub fn GetStdHandle(which: Dword) -> Handle;
pub fn GetConsoleMode(handle: Handle, mode: *mut Dword) -> Bool;
pub fn SetConsoleMode(handle: Handle, mode: Dword) -> Bool;
}
}
#[cfg(windows)]
fn enable_virtual_terminal() {
unsafe {
let handle = win::GetStdHandle(win::STD_OUTPUT_HANDLE);
let mut mode: win::Dword = 0;
if win::GetConsoleMode(handle, &mut mode) != 0 {
win::SetConsoleMode(handle, mode | win::ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
#[cfg(not(windows))]
fn enable_virtual_terminal() {}
#[cfg(windows)]
fn install_signal_handler() {
unsafe {
win::SetConsoleCtrlHandler(Some(handler), 1);
}
}
#[cfg(windows)]
unsafe extern "system" fn handler(event: win::Dword) -> win::Bool {
match event {
win::CTRL_C_EVENT | win::CTRL_BREAK_EVENT | win::CTRL_CLOSE_EVENT => {
restore();
0
}
_ => 0,
}
}
#[cfg(not(any(unix, windows)))]
fn write_stdout(buf: &[u8]) {
let mut out = std::io::stdout();
let _ = out.write_all(buf);
let _ = out.flush();
}
#[cfg(not(any(unix, windows)))]
fn install_signal_handler() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn restore_is_idempotent() {
set_cursor_hidden(true);
set_alt_screen(true);
restore();
assert!(!CURSOR_HIDDEN.load(SeqCst));
assert!(!ALT_SCREEN.load(SeqCst));
restore(); restore();
}
#[test]
fn arming_twice_is_harmless() {
arm();
arm();
}
}