diff --git a/Cargo.lock b/Cargo.lock index 941c6f8..151058b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,73 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + [[package]] name = "wash" version = "0.1.0" dependencies = [ + "anyhow", + "thiserror", "windows", ] diff --git a/Cargo.toml b/Cargo.toml index b0c20cb..37c32f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" +thiserror = "1.0" [dependencies.windows] version = "0.44.0" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..56c1f11 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,22 @@ +use thiserror::Error; +use windows::Win32::Foundation::{GetLastError, BOOL}; + +#[derive(Error, Debug)] +pub enum Error { + #[error("windows error: {0}")] + WindowsError(String), +} + +impl Error { + pub fn last_error() -> Self { + unsafe { Error::WindowsError(GetLastError().to_hresult().message().to_string()) } + } + + pub fn check(b: BOOL) -> Result<(), Self> { + if b.as_bool() { + Ok(()) + } else { + Err(Error::last_error()) + } + } +} diff --git a/src/main.rs b/src/main.rs index 96f963f..d83452f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,11 @@ -use std::io; -use std::io::Write; -use windows::Win32::{Foundation::GetLastError, System::Console}; +mod error; + +use std::io::{self, Write}; +use windows::Win32::{Foundation::HANDLE, System::Console}; + +use anyhow::{Context, Result}; + +use crate::error::Error; fn print_input_mode(mode: Console::CONSOLE_MODE) { // Characters read by the ReadFile or ReadConsole function are written to the active screen @@ -171,56 +176,130 @@ fn print_output_mode(mode: Console::CONSOLE_MODE) { } } -fn main() { - let mut stdout = io::stdout(); +fn stdin_handle() -> Result { + unsafe { + let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE) + .context("unable to get stdin handle")?; + Ok(handle) + } +} +fn stdout_handle() -> Result { + unsafe { + let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE) + .context("unable to get stdin handle")?; + Ok(handle) + } +} + +fn setup_stdin() -> Result<()> { let mut mode = Console::CONSOLE_MODE(0); unsafe { - let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE).unwrap(); - if Console::GetConsoleMode(handle, &mut mode).as_bool() { - println!("Stdin details:"); - print_input_mode(mode); - } else { - let err = GetLastError(); - println!( - "Unable to get console mode: {:?}", - err.to_hresult().message() - ); - } + let handle = stdin_handle()?; + Error::check(Console::GetConsoleMode(handle, &mut mode))?; + println!("Stdin details:"); + print_input_mode(mode); + + // allow terminal input characters + mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT; + + // disable automatic processing of CTRL+C, we'll handle it ourselves + mode &= !Console::ENABLE_PROCESSED_INPUT; - let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE).unwrap(); + // disable line mode to get every input as its pressed + mode &= !Console::ENABLE_LINE_INPUT; + + // disable automatic echoing of inputs + mode &= !Console::ENABLE_ECHO_INPUT; + + // enable mouse input + mode |= Console::ENABLE_MOUSE_INPUT; + + Error::check(Console::SetConsoleMode(handle, mode))?; + } + Ok(()) +} + +fn setup_stdout() -> Result<()> { + let mut mode = Console::CONSOLE_MODE(0); + unsafe { + let handle = stdout_handle()?; if Console::GetConsoleMode(handle, &mut mode).as_bool() { println!("Stdout details:"); print_output_mode(mode); } else { - let err = GetLastError(); - println!( - "Unable to get console mode: {:?}", - err.to_hresult().message() - ); + return Err(Error::last_error().into()); } } + Ok(()) +} + +fn main() -> Result<()> { + setup_stdin()?; + setup_stdout()?; - let stdin = io::stdin(); - let mut line = String::new(); + let stdin = stdin_handle()?; + let mut buf = [Console::INPUT_RECORD::default(); 100]; loop { - if let Err(_) = stdout.write(b"> ") { - break; + let mut n: u32 = 0; + unsafe { + Error::check(Console::ReadConsoleInputA(stdin, &mut buf, &mut n))?; } - if let Err(_) = stdout.flush() { - break; - } - match stdin.read_line(&mut line) { - Ok(_) => { - if let Err(_) = stdout.write( - String::from("\u{001b}[31m ok \u{001b}[0m\n") - .as_str() - .as_bytes(), - ) { - break; + let n = n as usize; + println!("read: {:?}", n); + for rec in &buf[0..n] { + println!("Event Type: {}", rec.EventType); + match rec.EventType as u32 { + Console::FOCUS_EVENT => { + // The Event member contains a FOCUS_EVENT_RECORD structure. These events are + // used internally and should be ignored. + unsafe { + let event = rec.Event.FocusEvent; + println!("Focus Event: {:?}", event); + } + } + Console::MENU_EVENT => { + // The Event member contains a MENU_EVENT_RECORD structure. These events are + // used internally and should be ignored. + unsafe { + let event = rec.Event.MenuEvent; + println!("Menu Event: {:?}", event); + } + } + Console::KEY_EVENT => { + // The Event member contains a KEY_EVENT_RECORD structure with information + // about a keyboard event. + unsafe { + let event = rec.Event.KeyEvent; + let down = event.bKeyDown; + let repeats = event.wRepeatCount; + let key_code = event.wVirtualKeyCode; + let scan_code = event.wVirtualScanCode; + let c = event.uChar.UnicodeChar; + let modifiers = event.dwControlKeyState; + println!( + "Key Event: down: {down:?} repeats: {repeats} key-code: {key_code} scan-code: {scan_code} char: {c} modifiers: {modifiers}" + ); + } + } + Console::MOUSE_EVENT => { + // The Event member contains a MOUSE_EVENT_RECORD structure with information + // about a mouse movement or button press event. + unsafe { + let event = rec.Event.MouseEvent; + println!("Mouse Event: {:?}", event); + } + } + Console::WINDOW_BUFFER_SIZE_EVENT => { + // The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with + // information about the new size of the console screen buffer. + unsafe { + let event = rec.Event.WindowBufferSizeEvent; + println!("Window Buffer Event: {:?}", event); + } } + _ => {} } - Err(_) => break, } } }