use std::{
mem::zeroed,
sync::{
atomic::{AtomicBool, Ordering},
LazyLock,
},
thread::JoinHandle,
};
use libc::isatty;
use windows::{
core::BOOL,
Win32::{
System::Console::{
FlushConsoleInputBuffer, GetConsoleMode,
GetConsoleScreenBufferInfo, GetStdHandle, SetConsoleMode,
WriteConsoleInputW, BACKGROUND_BLUE, BACKGROUND_GREEN,
BACKGROUND_RED, CONSOLE_CHARACTER_ATTRIBUTES, CONSOLE_MODE,
ENABLE_LVB_GRID_WORLDWIDE, ENABLE_PROCESSED_OUTPUT,
ENABLE_QUICK_EDIT_MODE, ENABLE_VIRTUAL_TERMINAL_INPUT,
ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_WINDOW_INPUT,
FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED, 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)
}
pub struct WindowsInfo {
pub ansi_support: bool,
pub default_fg: CONSOLE_CHARACTER_ATTRIBUTES,
pub default_bg: CONSOLE_CHARACTER_ATTRIBUTES,
}
fn get_windows_info() -> Result<WindowsInfo, crate::DummyError> {
unsafe {
let stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE)?;
let mut ansi_support = false;
if std::env::var("LISO_FORCE_WINDOWS_CONSOLE").is_err() {
let mut mode = zeroed();
GetConsoleMode(stdout_handle, &raw mut mode)?;
ansi_support = mode.contains(ENABLE_VIRTUAL_TERMINAL_PROCESSING);
if !ansi_support {
let test_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if SetConsoleMode(stdout_handle, test_mode).is_ok() {
SetConsoleMode(stdout_handle, mode).unwrap();
ansi_support = true;
}
}
}
let mut csbi = zeroed();
GetConsoleScreenBufferInfo(stdout_handle, &raw mut csbi)?;
let default_fg = csbi.wAttributes
& (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
let default_bg = csbi.wAttributes
& (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
Ok(WindowsInfo {
ansi_support,
default_fg,
default_bg,
})
}
}
pub static WINDOWS_INFO: LazyLock<WindowsInfo> = LazyLock::new(|| {
get_windows_info().unwrap_or_else(|_| WindowsInfo {
ansi_support: false,
default_fg: FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
default_bg: CONSOLE_CHARACTER_ATTRIBUTES(0),
})
});