use crate::RuntimeErrorKind;
pub trait TerminalOps {
fn enable_raw_mode(&self) -> Result<(), RuntimeErrorKind>;
fn disable_raw_mode(&self) -> Result<(), RuntimeErrorKind>;
#[cfg(windows)]
fn handle_ctrl_c(&self, running: Arc<AtomicBool>) -> Result<(), RuntimeErrorKind>;
fn get_terminal_size(&self) -> (u16, u16);
}
#[cfg(unix)]
pub struct UnixTerminal;
#[cfg(unix)]
impl TerminalOps for UnixTerminal {
fn enable_raw_mode(&self) -> Result<(), RuntimeErrorKind> {
crossterm::terminal::enable_raw_mode()
.map_err(|e| RuntimeErrorKind::CustomError(e.to_string().into()))
}
fn disable_raw_mode(&self) -> Result<(), RuntimeErrorKind> {
crossterm::terminal::disable_raw_mode()
.map_err(|e| RuntimeErrorKind::CustomError(e.to_string().into()))
}
fn get_terminal_size(&self) -> (u16, u16) {
crossterm::terminal::size().unwrap_or((24, 80))
}
}
#[cfg(windows)]
pub struct WindowsTerminal;
#[cfg(windows)]
use std::io::{stdin, stdout};
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(windows)]
use std::sync::{Arc, LazyLock, Mutex};
#[cfg(windows)]
use winapi::ctypes::c_void as WinapiCVoid;
#[cfg(windows)]
use winapi::shared::minwindef::BOOL;
#[cfg(windows)]
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleCtrlHandler, SetConsoleMode};
#[cfg(windows)]
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
#[cfg(windows)]
use winapi::um::wincon::{
CONSOLE_SCREEN_BUFFER_INFO, CTRL_BREAK_EVENT, CTRL_C_EVENT, CTRL_CLOSE_EVENT,
ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT, GetConsoleScreenBufferInfo,
};
#[cfg(windows)]
static RUNNING_FLAG: LazyLock<Mutex<Option<Arc<AtomicBool>>>> = LazyLock::new(|| Mutex::new(None));
#[cfg(windows)]
impl TerminalOps for WindowsTerminal {
fn enable_raw_mode(&self) -> Result<(), RuntimeErrorKind> {
unsafe {
let handle: *mut WinapiCVoid = stdin().as_raw_handle() as *mut WinapiCVoid;
if handle == INVALID_HANDLE_VALUE {
return Err(RuntimeErrorKind::CustomError(
"Failed to get stdin handle".into(),
));
}
let mut mode: u32 = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return Err(RuntimeErrorKind::CustomError(
"Failed to get console mode".into(),
));
}
mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
if SetConsoleMode(handle, mode) == 0 {
return Err(RuntimeErrorKind::CustomError(
"Failed to set raw mode".into(),
));
}
}
Ok(())
}
fn disable_raw_mode(&self) -> Result<(), RuntimeErrorKind> {
unsafe {
let handle: *mut WinapiCVoid = stdin().as_raw_handle() as *mut WinapiCVoid;
if handle == INVALID_HANDLE_VALUE {
return Err(RuntimeErrorKind::CustomError(
"Failed to get stdin handle".into(),
));
}
let mut mode: u32 = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return Err(RuntimeErrorKind::CustomError(
"Failed to get console mode".into(),
));
}
mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
if SetConsoleMode(handle, mode) == 0 {
return Err(RuntimeErrorKind::CustomError(
"Failed to restore console mode".into(),
));
}
}
Ok(())
}
fn handle_ctrl_c(&self, running: Arc<AtomicBool>) -> Result<(), RuntimeErrorKind> {
*RUNNING_FLAG.lock().unwrap() = Some(running);
unsafe extern "system" fn handler(ctrl_type: u32) -> BOOL {
match ctrl_type {
CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT => {
if let Some(flag) = RUNNING_FLAG.lock().unwrap().as_ref() {
flag.store(false, Ordering::SeqCst);
}
1 }
_ => 0, }
}
unsafe {
if SetConsoleCtrlHandler(Some(handler), 1) == 0 {
return Err(RuntimeErrorKind::CustomError(
"Failed to set Ctrl+C handler".into(),
));
}
}
Ok(())
}
fn get_terminal_size(&self) -> (u16, u16) {
unsafe {
let handle: *mut WinapiCVoid = stdout().as_raw_handle() as *mut WinapiCVoid;
if handle == INVALID_HANDLE_VALUE {
return (80, 24); }
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = std::mem::zeroed();
if GetConsoleScreenBufferInfo(handle, &mut csbi) == 0 {
return (80, 24); }
let width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
let height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
(width as u16, height as u16)
}
}
}
pub fn get_terminal_impl() -> Box<dyn TerminalOps> {
#[cfg(unix)]
return Box::new(UnixTerminal);
#[cfg(windows)]
return Box::new(WindowsTerminal);
}