use std::{
sync::atomic::{AtomicBool, Ordering},
thread::JoinHandle,
};
use libc::isatty;
use windows::{
core::BOOL,
Win32::{
System::Console::{
FlushConsoleInputBuffer, GetConsoleMode, GetStdHandle,
SetConsoleMode, WriteConsoleInputW, CONSOLE_MODE,
ENABLE_LVB_GRID_WORLDWIDE, ENABLE_PROCESSED_OUTPUT,
ENABLE_QUICK_EDIT_MODE, ENABLE_VIRTUAL_TERMINAL_INPUT,
ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_WINDOW_INPUT,
INPUT_RECORD, INPUT_RECORD_0, KEY_EVENT, KEY_EVENT_RECORD,
KEY_EVENT_RECORD_0, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
},
UI::Input::KeyboardAndMouse::VK_RETURN,
},
};
#[cfg(debug_assertions)]
static IS_RAW: AtomicBool = AtomicBool::new(false);
static STDIN_BEING_INTERRUPTED: AtomicBool = AtomicBool::new(false);
pub(crate) struct InterruptibleStdinThread {
join_handle: Option<JoinHandle<()>>,
}
impl InterruptibleStdinThread {
pub fn new(join_handle: JoinHandle<()>) -> InterruptibleStdinThread {
InterruptibleStdinThread {
join_handle: Some(join_handle),
}
}
pub fn interrupt(&mut self) {
let Some(join_handle) = self.join_handle.take() else {
return;
};
if join_handle.is_finished() {
return;
}
unsafe {
STDIN_BEING_INTERRUPTED.store(true, Ordering::SeqCst);
let buf = [
INPUT_RECORD {
EventType: KEY_EVENT as u16,
Event: INPUT_RECORD_0 {
KeyEvent: KEY_EVENT_RECORD {
bKeyDown: BOOL(1),
wRepeatCount: 1,
wVirtualKeyCode: VK_RETURN.0,
wVirtualScanCode: 0x0A,
uChar: KEY_EVENT_RECORD_0 { UnicodeChar: 0x1B },
dwControlKeyState: 0,
},
},
},
INPUT_RECORD {
EventType: KEY_EVENT as u16,
Event: INPUT_RECORD_0 {
KeyEvent: KEY_EVENT_RECORD {
bKeyDown: BOOL(0),
wRepeatCount: 0,
wVirtualKeyCode: VK_RETURN.0,
wVirtualScanCode: 0x0A,
uChar: KEY_EVENT_RECORD_0 { UnicodeChar: 0x1B },
dwControlKeyState: 0,
},
},
},
];
let Ok(stdin_handle) = GetStdHandle(STD_INPUT_HANDLE) else {
return;
};
loop {
let _ = FlushConsoleInputBuffer(stdin_handle);
let mut wrote = 0;
let _ = WriteConsoleInputW(stdin_handle, &buf, &raw mut wrote);
if wrote == 2 {
break;
} else {
std::thread::yield_now();
}
}
join_handle.join().expect("unable to join stdin thread");
STDIN_BEING_INTERRUPTED.store(false, Ordering::SeqCst);
let _ = FlushConsoleInputBuffer(stdin_handle);
}
}
pub fn placebo_check() {
}
}
static mut OLD_STDIN_MODE: CONSOLE_MODE = CONSOLE_MODE(0);
static mut OLD_STDOUT_MODE: CONSOLE_MODE = CONSOLE_MODE(0);
pub fn enter_raw_mode(ansi: bool) -> bool {
unsafe {
let Ok(stdin_handle) = GetStdHandle(STD_INPUT_HANDLE) else {
return false;
};
let Ok(stdout_handle) = GetStdHandle(STD_OUTPUT_HANDLE) else {
return false;
};
#[cfg(debug_assertions)]
loop {
match IS_RAW.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => break,
Err(true) => panic!("BUG IN LISO: enter_raw_mode() called twice without exit_raw_mode() in between!"),
Err(false) => continue,
}
}
let Ok(_) = GetConsoleMode(stdin_handle, &raw mut OLD_STDIN_MODE)
else {
#[cfg(debug_assertions)]
IS_RAW.store(false, Ordering::Release);
return false;
};
let Ok(_) = GetConsoleMode(stdout_handle, &raw mut OLD_STDOUT_MODE)
else {
#[cfg(debug_assertions)]
IS_RAW.store(false, Ordering::Release);
return false;
};
let mut in_mode = ENABLE_QUICK_EDIT_MODE | ENABLE_WINDOW_INPUT;
if ansi {
in_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
}
let mut out_mode = ENABLE_PROCESSED_OUTPUT | ENABLE_LVB_GRID_WORLDWIDE;
if ansi {
out_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
}
let Ok(_) = SetConsoleMode(stdin_handle, in_mode) else {
#[cfg(debug_assertions)]
IS_RAW.store(false, Ordering::Release);
return false;
};
let Ok(_) = SetConsoleMode(stdout_handle, out_mode) else {
panic!("we were able to set the console input mode, but not the console output mode");
};
true
}
}
pub fn exit_raw_mode() {
#[cfg(debug_assertions)]
if !IS_RAW.load(Ordering::Relaxed) {
panic!("BUG IN LISO: exit_raw_mode() called without preceding enter_raw_mode()!")
} else {
IS_RAW.store(false, Ordering::Release);
}
unsafe {
let stdin_handle = GetStdHandle(STD_INPUT_HANDLE).unwrap();
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
let _ = SetConsoleMode(stdin_handle, OLD_STDIN_MODE);
let _ = SetConsoleMode(stdout_handle, OLD_STDOUT_MODE);
}
}
pub fn stdin_and_stdout_are_tty() -> bool {
unsafe { isatty(0) != 0 && isatty(1) != 0 }
}
pub fn stdin_being_interrupted() -> bool {
STDIN_BEING_INTERRUPTED.load(Ordering::Relaxed)
}