use crate::errors::LisaError;
use std::{mem::transmute_copy, sync::OnceLock};
use widestring::U16String;
use windows::{
Win32::{
Foundation::{GetLastError, HANDLE},
System::{
Console::{
CONSOLE_MODE, CTRL_C_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT,
ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, GenerateConsoleCtrlEvent,
GetConsoleMode, GetStdHandle, INPUT_RECORD, KEY_EVENT, STD_INPUT_HANDLE,
SetConsoleMode,
},
LibraryLoader::{GetModuleHandleW, GetProcAddress},
},
},
core::{BOOL as WindowsBool, PCWSTR, s, w},
};
type ReadConsoleInputExW = unsafe extern "system" fn(
h_console_input: HANDLE,
lp_buffer: *mut INPUT_RECORD,
n_length: u32,
lp_number_of_events_read: *mut u32,
w_flags: u16,
) -> WindowsBool;
static LOADED_READ_CONSOLE_INPUT: OnceLock<ReadConsoleInputExW> = OnceLock::new();
const CONSOLE_READ_NOWAIT: u16 = 0x0002;
pub type CachedModeType = CONSOLE_MODE;
#[must_use]
pub fn default_cached_mode() -> CachedModeType {
CONSOLE_MODE(0)
}
pub fn enable_raw_mode() -> Result<CachedModeType, LisaError> {
let mut current_console_mode = CONSOLE_MODE(0);
let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) }?;
unsafe { GetConsoleMode(stdin, &raw mut current_console_mode) }?;
let mut cloned_old_mode = current_console_mode.clone();
current_console_mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
current_console_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
if current_console_mode != cloned_old_mode {
unsafe { SetConsoleMode(stdin, current_console_mode) }?;
} else {
cloned_old_mode = CONSOLE_MODE(0);
}
Ok(cloned_old_mode)
}
pub fn disable_raw_mode(cached: CachedModeType) -> Result<CachedModeType, LisaError> {
if cached.0 != 0 {
let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) }?;
unsafe { SetConsoleMode(stdin, cached) }?;
}
return Ok(CONSOLE_MODE(0));
}
pub fn os_pre_reqs() -> Result<(), LisaError> {
ensure_load_read_console()?;
Ok(())
}
pub fn raise_sigint() {
unsafe {
_ = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
}
}
pub fn read_non_blocking_stdin() -> Result<String, LisaError> {
let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE) }?;
let mut uninitialized_input_events: Vec<INPUT_RECORD> = vec![INPUT_RECORD::default(); 4096];
let mut read = 0;
let read_console_input_ex = unsafe {
let Some(fn_ref) = LOADED_READ_CONSOLE_INPUT.get() else {
return Err(LisaError::Win32(GetLastError()));
};
let fn_ptr = *fn_ref;
fn_ptr(
stdin,
&raw mut uninitialized_input_events[0],
4096,
&mut read,
CONSOLE_READ_NOWAIT,
)
};
if read_console_input_ex.0 == 0 || read == 0 {
return Ok(String::with_capacity(0));
}
let input_events = uninitialized_input_events
.drain(..read as usize)
.collect::<Vec<_>>();
let wide_bytes = input_events
.into_iter()
.filter_map(|record| {
if u32::from(record.EventType) != KEY_EVENT {
None
} else {
let key_evt_data = unsafe { record.Event.KeyEvent };
Some(unsafe { key_evt_data.uChar.UnicodeChar })
}
})
.collect::<Vec<u16>>();
Ok(U16String::from_vec(wide_bytes).to_string()?)
}
fn ensure_load_read_console() -> Result<(), LisaError> {
if LOADED_READ_CONSOLE_INPUT.get().is_some() {
return Ok(());
}
let fn_ptr = unsafe { load_read_func(w!("kernel32.dll")) }
.or_else(|_| unsafe { load_read_func(w!("kernelbase.dll")) })?;
_ = LOADED_READ_CONSOLE_INPUT.set(fn_ptr);
Ok(())
}
unsafe fn load_read_func(module: PCWSTR) -> Result<ReadConsoleInputExW, LisaError> {
let module_ptr = unsafe { GetModuleHandleW(module) }?;
unsafe { GetProcAddress(module_ptr, s!("ReadConsoleInputExW")) }
.ok_or_else(|| LisaError::Win32(unsafe { GetLastError() }))
.map(|value| unsafe { transmute_copy(&value) })
}