use std::io;
use std::str::FromStr;
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::System::Console::{
AllocConsole, AttachConsole, FlushConsoleInputBuffer, FreeConsole, GetConsoleCP,
GetConsoleMode, GetStdHandle, ReadConsoleW, SetConsoleMode, SetStdHandle,
ATTACH_PARENT_PROCESS, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, STD_ERROR_HANDLE,
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Auto,
Show,
Hide,
Keep,
}
pub fn has_console() -> bool {
unsafe { GetConsoleCP() != 0 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attached {
Existed,
Created,
}
pub fn attach() -> Result<Attached> {
if has_console() {
return Ok(Attached::Existed);
}
if unsafe { AttachConsole(ATTACH_PARENT_PROCESS) } != 0 {
set_std_handles();
return Ok(Attached::Existed);
}
if unsafe { AllocConsole() } == 0 {
return Err(Error::Alloc(io::Error::last_os_error()));
}
set_std_handles();
Ok(Attached::Created)
}
pub fn show() -> Result<Attached> {
if has_console() {
detach()?;
}
if unsafe { AllocConsole() } == 0 {
return Err(Error::Alloc(io::Error::last_os_error()));
}
set_std_handles();
Ok(Attached::Created)
}
fn set_std_handles() {
use windows_sys::Win32::Foundation::GENERIC_READ;
use windows_sys::Win32::Foundation::GENERIC_WRITE;
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
unsafe {
let mut conin = "CONIN$\0".encode_utf16().collect::<Vec<u16>>();
let mut conout = "CONOUT$\0".encode_utf16().collect::<Vec<u16>>();
let input = CreateFileW(
conin.as_mut_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
std::ptr::null_mut(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
);
let output = CreateFileW(
conout.as_mut_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
std::ptr::null_mut(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
);
if input != INVALID_HANDLE_VALUE {
SetStdHandle(STD_INPUT_HANDLE, input);
}
if output != INVALID_HANDLE_VALUE {
SetStdHandle(STD_OUTPUT_HANDLE, output);
SetStdHandle(STD_ERROR_HANDLE, output);
}
}
}
pub fn detach() -> Result<()> {
if !has_console() {
return Ok(());
}
if unsafe { FreeConsole() } == 0 {
return Err(Error::Free(io::Error::last_os_error()));
}
Ok(())
}
pub fn set_mode(mode: Mode) -> Result<()> {
match mode {
Mode::Auto => attach().map(|_| ()),
Mode::Show => show().map(|_| ()),
Mode::Hide => detach(),
Mode::Keep => Ok(()),
}
}
pub fn init(mode: Mode) -> Result<bool> {
match mode {
Mode::Auto => Ok(attach()? == Attached::Created),
Mode::Show => {
show()?;
Ok(true)
}
Mode::Hide => {
detach()?;
Ok(false)
}
Mode::Keep => Ok(false),
}
}
impl FromStr for Mode {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_ascii_lowercase().as_str() {
"auto" => Ok(Mode::Auto),
"show" => Ok(Mode::Show),
"hide" => Ok(Mode::Hide),
"keep" => Ok(Mode::Keep),
_ => Err(Error::Parse(s.to_owned())),
}
}
}
impl<S: AsRef<std::ffi::OsStr>> FromIterator<S> for Mode {
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
let mut mode = Mode::Auto;
for arg in iter {
match arg.as_ref().to_str() {
Some("--show") => mode = Mode::Show,
Some("--hide") => mode = Mode::Hide,
_ => {}
}
}
mode
}
}
pub fn wait_key() -> Result<()> {
let h = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
if h.is_null() || h == INVALID_HANDLE_VALUE {
return Ok(());
}
unsafe { FlushConsoleInputBuffer(h) };
let mut mode = 0u32;
if unsafe { GetConsoleMode(h, &mut mode) } == 0 {
return Ok(());
}
let raw = mode & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
let _ = unsafe { SetConsoleMode(h, raw) };
let mut buf = [0u16; 1];
let mut read = 0u32;
let ok =
unsafe { ReadConsoleW(h, buf.as_mut_ptr() as *mut _, 1, &mut read, std::ptr::null()) };
let _ = unsafe { SetConsoleMode(h, mode) };
if ok == 0 {
return Err(Error::Input(io::Error::last_os_error()));
}
Ok(())
}