use {
crate::{scan_osc, OscScan, XQError},
std::io::{self, Write},
windows_sys::Win32::{
Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE},
Storage::FileSystem::{CreateFileW, ReadFile},
System::{
Console::{GetConsoleMode, SetConsoleMode},
Threading::WaitForSingleObject,
},
},
};
const GENERIC_READ: u32 = 0x8000_0000;
const GENERIC_WRITE: u32 = 0x4000_0000;
const FILE_SHARE_READ: u32 = 0x0000_0001;
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
const OPEN_EXISTING: u32 = 3;
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
const ENABLE_LINE_INPUT: u32 = 0x0002;
const ENABLE_ECHO_INPUT: u32 = 0x0004;
const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
const WAIT_OBJECT_0: u32 = 0x0000_0000;
const WAIT_TIMEOUT: u32 = 0x0000_0102;
struct ConsoleInput {
handle: HANDLE,
prev_mode: u32,
}
impl ConsoleInput {
fn open() -> Result<Self, XQError> {
let name: Vec<u16> = "CONIN$\0".encode_utf16().collect();
let handle = unsafe {
CreateFileW(
name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
0,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE || handle.is_null() {
return Err(XQError::IO(io::Error::last_os_error()));
}
let mut prev_mode: u32 = 0;
if unsafe { GetConsoleMode(handle, &mut prev_mode) } == 0 {
let err = io::Error::last_os_error();
unsafe { CloseHandle(handle) };
return Err(XQError::IO(err));
}
let new_mode = (prev_mode | ENABLE_VIRTUAL_TERMINAL_INPUT)
& !ENABLE_LINE_INPUT
& !ENABLE_ECHO_INPUT
& !ENABLE_PROCESSED_INPUT;
if unsafe { SetConsoleMode(handle, new_mode) } == 0 {
let err = io::Error::last_os_error();
unsafe { CloseHandle(handle) };
return Err(XQError::IO(err));
}
Ok(Self { handle, prev_mode })
}
fn wait(&self, timeout_ms: u64) -> Result<bool, XQError> {
let ms = u32::try_from(timeout_ms).unwrap_or(u32::MAX);
match unsafe { WaitForSingleObject(self.handle, ms) } {
WAIT_OBJECT_0 => Ok(true),
WAIT_TIMEOUT => Ok(false),
_ => Err(XQError::IO(io::Error::last_os_error())),
}
}
fn read(&self, buffer: &mut [u8]) -> Result<usize, XQError> {
let mut read: u32 = 0;
let ok = unsafe {
ReadFile(
self.handle,
buffer.as_mut_ptr().cast(),
u32::try_from(buffer.len()).unwrap_or(u32::MAX),
&mut read,
core::ptr::null_mut(),
)
};
if ok == 0 {
return Err(XQError::IO(io::Error::last_os_error()));
}
Ok(read as usize)
}
}
impl Drop for ConsoleInput {
fn drop(&mut self) {
unsafe {
SetConsoleMode(self.handle, self.prev_mode);
CloseHandle(self.handle);
}
}
}
fn write_query(query: &str) -> Result<(), XQError> {
let stdout = io::stdout();
let mut stdout = stdout.lock();
write!(stdout, "{}", query)?;
stdout.flush()?;
Ok(())
}
pub fn query_buffer<MS: Into<u64>>(
query: &str,
buffer: &mut [u8],
timeout_ms: MS,
) -> Result<usize, XQError> {
let con = ConsoleInput::open()?;
write_query(query)?;
if con.wait(timeout_ms.into())? {
con.read(buffer)
} else {
Err(XQError::Timeout)
}
}
pub fn query_osc_buffer<'b, MS: Into<u64> + Copy>(
query: &str,
buffer: &'b mut [u8],
timeout_ms: MS,
) -> Result<&'b [u8], XQError> {
const ESC: char = '\x1b';
let con = ConsoleInput::open()?;
{
let stdout = io::stdout();
let mut stdout = stdout.lock();
write!(stdout, "{}", query)?;
write!(stdout, "{ESC}[5n")?;
stdout.flush()?;
}
let mut filled = 0;
while filled < buffer.len() {
if !con.wait(timeout_ms.into())? {
return Err(XQError::Timeout);
}
let bytes_read = con.read(&mut buffer[filled..])?;
if bytes_read == 0 {
return Err(XQError::NotAnOSCResponse);
}
filled += bytes_read;
match scan_osc(&buffer[..filled]) {
OscScan::Found { start, end } => return Ok(&buffer[start..=end]),
OscScan::NotOsc => return Err(XQError::NotAnOSCResponse),
OscScan::NeedMore => {}
}
}
Err(XQError::BufferOverflow)
}