use crate::base::{Error, Result};
use crate::term::options::EnterOptions;
use std::sync::Mutex;
use windows_sys::Win32::Foundation::{
CloseHandle, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows_sys::Win32::System::Console::{
GetConsoleMode, GetStdHandle, SetConsoleMode, SetConsoleOutputCP, WriteConsoleA, CONSOLE_MODE,
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
pub(crate) fn win_err(ctx: &str) -> Error {
let code = unsafe { GetLastError() };
Error::Term(format!("{ctx}: windows error {code}"))
}
pub(crate) fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub(crate) struct EmergencySlot {
pub(crate) hin: isize,
pub(crate) hout: isize,
pub(crate) in_mode: CONSOLE_MODE,
pub(crate) out_mode: CONSOLE_MODE,
pub(crate) out_cp: u32,
pub(crate) leave_bytes: Vec<u8>,
}
pub(crate) static EMERGENCY: Mutex<Option<EmergencySlot>> = Mutex::new(None);
pub fn emergency_restore() {
let slot = match EMERGENCY.lock() {
Ok(mut g) => g.take(),
Err(_) => return,
};
if let Some(s) = slot {
let hout = s.hout as HANDLE;
let hin = s.hin as HANDLE;
let _ = write_all_handle(hout, &s.leave_bytes);
unsafe {
SetConsoleMode(hin, s.in_mode);
SetConsoleMode(hout, s.out_mode);
SetConsoleOutputCP(s.out_cp);
}
}
}
pub(crate) fn write_all_handle(h: HANDLE, mut bytes: &[u8]) -> Result<()> {
while !bytes.is_empty() {
let mut written: u32 = 0;
let chunk = bytes.len().min(u32::MAX as usize) as u32;
let ok = unsafe { WriteConsoleA(h, bytes.as_ptr(), chunk, &mut written, std::ptr::null()) };
if ok == 0 {
return Err(win_err("WriteConsoleA"));
}
if written == 0 {
return Err(Error::Term("console write made no progress".into()));
}
bytes = &bytes[written as usize..];
}
Ok(())
}
pub(crate) struct SavedModes {
pub(crate) in_mode: CONSOLE_MODE,
pub(crate) out_mode: CONSOLE_MODE,
pub(crate) out_cp: u32,
pub(crate) opts: EnterOptions,
}
pub(crate) struct WakeEvent(pub(crate) HANDLE);
unsafe impl Send for WakeEvent {}
unsafe impl Sync for WakeEvent {}
impl Drop for WakeEvent {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
pub fn have_tty() -> bool {
fn console_alive(name: &str, std_handle: u32) -> bool {
match open_console(name, std_handle) {
Ok((h, owned)) => {
let mut mode: CONSOLE_MODE = 0;
let ok = unsafe { GetConsoleMode(h, &mut mode) } != 0;
if owned {
unsafe { CloseHandle(h) };
}
ok
}
Err(_) => false,
}
}
console_alive("CONIN$", STD_INPUT_HANDLE) && console_alive("CONOUT$", STD_OUTPUT_HANDLE)
}
pub(crate) fn open_console(name: &str, std_handle: u32) -> Result<(HANDLE, bool)> {
let path = wide(name);
let h = unsafe {
CreateFileW(
path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
std::ptr::null(),
OPEN_EXISTING,
0,
std::ptr::null_mut(),
)
};
if h != INVALID_HANDLE_VALUE && !h.is_null() {
return Ok((h, true));
}
let h = unsafe { GetStdHandle(std_handle) };
if h == INVALID_HANDLE_VALUE || h.is_null() {
return Err(Error::Term(format!(
"no console: {name} and the std handle are both unavailable — run \
inside Windows Terminal/conhost, or use testing::CaptureTerm for \
headless runs"
)));
}
Ok((h, false))
}