use crate::stack::{Stack, pop, push};
use crate::value::Value;
use std::sync::atomic::{AtomicBool, Ordering};
static RAW_MODE_ENABLED: AtomicBool = AtomicBool::new(false);
static mut SAVED_TERMIOS: Option<libc::termios> = None;
static mut SAVED_SIGINT_ACTION: Option<libc::sigaction> = None;
static mut SAVED_SIGTERM_ACTION: Option<libc::sigaction> = None;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_raw_mode(stack: Stack) -> Stack {
assert!(!stack.is_null(), "terminal_raw_mode: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::Bool(enable) => {
if enable {
enable_raw_mode();
} else {
disable_raw_mode();
}
rest
}
_ => panic!("terminal_raw_mode: expected Bool on stack, got {:?}", value),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_read_char(stack: Stack) -> Stack {
let mut buf = [0u8; 1];
let result =
unsafe { libc::read(libc::STDIN_FILENO, buf.as_mut_ptr() as *mut libc::c_void, 1) };
let char_value = if result == 1 {
buf[0] as i64
} else {
-1 };
unsafe { push(stack, Value::Int(char_value)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_read_char_nonblock(stack: Stack) -> Stack {
let flags = unsafe { libc::fcntl(libc::STDIN_FILENO, libc::F_GETFL) };
if flags < 0 {
return unsafe { push(stack, Value::Int(-1)) };
}
unsafe { libc::fcntl(libc::STDIN_FILENO, libc::F_SETFL, flags | libc::O_NONBLOCK) };
let mut buf = [0u8; 1];
let result =
unsafe { libc::read(libc::STDIN_FILENO, buf.as_mut_ptr() as *mut libc::c_void, 1) };
unsafe { libc::fcntl(libc::STDIN_FILENO, libc::F_SETFL, flags) };
let char_value = if result == 1 {
buf[0] as i64
} else {
-1 };
unsafe { push(stack, Value::Int(char_value)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_width(stack: Stack) -> Stack {
let width = get_terminal_size().0;
unsafe { push(stack, Value::Int(width)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_height(stack: Stack) -> Stack {
let height = get_terminal_size().1;
unsafe { push(stack, Value::Int(height)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_terminal_flush(stack: Stack) -> Stack {
use std::io::Write;
let _ = std::io::stdout().flush();
stack
}
extern "C" fn signal_handler(sig: libc::c_int) {
unsafe {
if let Some(ref saved) = SAVED_TERMIOS {
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, saved);
}
}
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
}
fn install_signal_handlers() {
unsafe {
let mut new_action: libc::sigaction = std::mem::zeroed();
new_action.sa_sigaction = signal_handler as *const () as usize;
libc::sigemptyset(&mut new_action.sa_mask);
new_action.sa_flags = 0;
let mut old_sigint: libc::sigaction = std::mem::zeroed();
if libc::sigaction(libc::SIGINT, &new_action, &mut old_sigint) == 0 {
SAVED_SIGINT_ACTION = Some(old_sigint);
}
let mut old_sigterm: libc::sigaction = std::mem::zeroed();
if libc::sigaction(libc::SIGTERM, &new_action, &mut old_sigterm) == 0 {
SAVED_SIGTERM_ACTION = Some(old_sigterm);
}
}
}
fn restore_signal_handlers() {
unsafe {
if let Some(ref action) = SAVED_SIGINT_ACTION {
libc::sigaction(libc::SIGINT, action, std::ptr::null_mut());
}
SAVED_SIGINT_ACTION = None;
if let Some(ref action) = SAVED_SIGTERM_ACTION {
libc::sigaction(libc::SIGTERM, action, std::ptr::null_mut());
}
SAVED_SIGTERM_ACTION = None;
}
}
fn enable_raw_mode() {
if RAW_MODE_ENABLED.load(Ordering::SeqCst) {
return; }
unsafe {
if libc::isatty(libc::STDIN_FILENO) != 1 {
return; }
let mut termios: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) != 0 {
return; }
SAVED_TERMIOS = Some(termios);
termios.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG | libc::IEXTEN);
termios.c_iflag &= !(libc::IXON | libc::ICRNL);
termios.c_oflag &= !libc::OPOST;
termios.c_cc[libc::VMIN] = 1;
termios.c_cc[libc::VTIME] = 0;
if libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios) == 0 {
RAW_MODE_ENABLED.store(true, Ordering::SeqCst);
install_signal_handlers();
}
}
}
fn disable_raw_mode() {
if !RAW_MODE_ENABLED.load(Ordering::SeqCst) {
return; }
restore_signal_handlers();
unsafe {
if let Some(ref saved) = SAVED_TERMIOS {
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, saved);
}
SAVED_TERMIOS = None;
RAW_MODE_ENABLED.store(false, Ordering::SeqCst);
}
}
fn get_terminal_size() -> (i64, i64) {
unsafe {
if libc::isatty(libc::STDOUT_FILENO) != 1 {
return (80, 24); }
let mut winsize: libc::winsize = std::mem::zeroed();
if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut winsize) == 0 {
let cols = if winsize.ws_col > 0 {
winsize.ws_col as i64
} else {
80
};
let rows = if winsize.ws_row > 0 {
winsize.ws_row as i64
} else {
24
};
(cols, rows)
} else {
(80, 24) }
}
}
pub use patch_seq_terminal_flush as terminal_flush;
pub use patch_seq_terminal_height as terminal_height;
pub use patch_seq_terminal_raw_mode as terminal_raw_mode;
pub use patch_seq_terminal_read_char as terminal_read_char;
pub use patch_seq_terminal_read_char_nonblock as terminal_read_char_nonblock;
pub use patch_seq_terminal_width as terminal_width;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_terminal_size() {
let (width, height) = get_terminal_size();
assert!(width > 0);
assert!(height > 0);
}
#[test]
fn test_terminal_width_stack() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = terminal_width(stack);
let (_, value) = pop(stack);
match value {
Value::Int(w) => assert!(w > 0),
_ => panic!("expected Int"),
}
}
}
#[test]
fn test_terminal_height_stack() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = terminal_height(stack);
let (_, value) = pop(stack);
match value {
Value::Int(h) => assert!(h > 0),
_ => panic!("expected Int"),
}
}
}
#[test]
fn test_raw_mode_toggle() {
enable_raw_mode();
disable_raw_mode();
assert!(!RAW_MODE_ENABLED.load(Ordering::SeqCst));
}
}