//! Keyboard input simulation using Windows SendInput API use anyhow::Result; #[cfg(windows)] use windows::Win32::UI::Input::KeyboardAndMouse::{ SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBD_EVENT_FLAGS, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, KEYEVENTF_UNICODE, KEYBDINPUT, MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, }; /// Keyboard input controller pub struct KeyboardController { // Track modifier states for proper handling #[allow(dead_code)] modifiers: ModifierState, } #[derive(Default)] struct ModifierState { ctrl: bool, alt: bool, shift: bool, meta: bool, } impl KeyboardController { /// Create a new keyboard controller pub fn new() -> Result { Ok(Self { modifiers: ModifierState::default(), }) } /// Press a key down by virtual key code #[cfg(windows)] pub fn key_down(&mut self, vk_code: u16) -> Result<()> { self.send_key(vk_code, true) } /// Release a key by virtual key code #[cfg(windows)] pub fn key_up(&mut self, vk_code: u16) -> Result<()> { self.send_key(vk_code, false) } /// Send a key event #[cfg(windows)] fn send_key(&mut self, vk_code: u16, down: bool) -> Result<()> { // Get scan code from virtual key let scan_code = unsafe { MapVirtualKeyW(vk_code as u32, MAPVK_VK_TO_VSC_EX) as u16 }; let mut flags = KEYBD_EVENT_FLAGS::default(); // Add extended key flag for certain keys if Self::is_extended_key(vk_code) || (scan_code >> 8) == 0xE0 { flags |= KEYEVENTF_EXTENDEDKEY; } if !down { flags |= KEYEVENTF_KEYUP; } let input = INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(vk_code), wScan: scan_code, dwFlags: flags, time: 0, dwExtraInfo: 0, }, }, }; self.send_input(&[input]) } /// Type a unicode character #[cfg(windows)] pub fn type_char(&mut self, ch: char) -> Result<()> { let mut inputs = Vec::new(); let mut buf = [0u16; 2]; let encoded = ch.encode_utf16(&mut buf); // For characters that fit in a single u16 for &code_unit in encoded.iter() { // Key down inputs.push(INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0), wScan: code_unit, dwFlags: KEYEVENTF_UNICODE, time: 0, dwExtraInfo: 0, }, }, }); // Key up inputs.push(INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0), wScan: code_unit, dwFlags: KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, time: 0, dwExtraInfo: 0, }, }, }); } self.send_input(&inputs) } /// Type a string of text #[cfg(windows)] pub fn type_string(&mut self, text: &str) -> Result<()> { for ch in text.chars() { self.type_char(ch)?; } Ok(()) } /// Send Secure Attention Sequence (Ctrl+Alt+Delete) /// /// This uses a multi-tier approach: /// 1. Try the GuruConnect SAS Service (runs as SYSTEM, handles via named pipe) /// 2. Try the sas.dll directly (requires SYSTEM privileges) /// 3. Fallback to key simulation (won't work on secure desktop) #[cfg(windows)] pub fn send_sas(&mut self) -> Result<()> { // Tier 1: Try the SAS service (named pipe IPC to SYSTEM service) if let Ok(()) = crate::sas_client::request_sas() { tracing::info!("SAS sent via GuruConnect SAS Service"); return Ok(()); } tracing::info!("SAS service not available, trying direct sas.dll..."); // Tier 2: Try using the sas.dll directly (requires SYSTEM privileges) use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW}; use windows::core::PCWSTR; unsafe { let dll_name: Vec = "sas.dll\0".encode_utf16().collect(); let lib = LoadLibraryW(PCWSTR(dll_name.as_ptr())); if let Ok(lib) = lib { let proc_name = b"SendSAS\0"; if let Some(proc) = GetProcAddress(lib, windows::core::PCSTR(proc_name.as_ptr())) { // SendSAS takes a BOOL parameter: FALSE for Ctrl+Alt+Del let send_sas: extern "system" fn(i32) = std::mem::transmute(proc); send_sas(0); // FALSE = Ctrl+Alt+Del tracing::info!("SAS sent via direct sas.dll call"); return Ok(()); } } } // Tier 3: Fallback - try sending the keys (won't work on secure desktop) tracing::warn!("SAS service and sas.dll not available, Ctrl+Alt+Del may not work"); // VK codes const VK_CONTROL: u16 = 0x11; const VK_MENU: u16 = 0x12; // Alt const VK_DELETE: u16 = 0x2E; // Press keys self.key_down(VK_CONTROL)?; self.key_down(VK_MENU)?; self.key_down(VK_DELETE)?; // Release keys self.key_up(VK_DELETE)?; self.key_up(VK_MENU)?; self.key_up(VK_CONTROL)?; Ok(()) } /// Check if a virtual key code is an extended key #[cfg(windows)] fn is_extended_key(vk: u16) -> bool { matches!( vk, 0x21..=0x28 | // Page Up, Page Down, End, Home, Arrow keys 0x2D | 0x2E | // Insert, Delete 0x5B | 0x5C | // Left/Right Windows keys 0x5D | // Applications key 0x6F | // Numpad Divide 0x90 | // Num Lock 0x91 // Scroll Lock ) } /// Send input events #[cfg(windows)] fn send_input(&self, inputs: &[INPUT]) -> Result<()> { let sent = unsafe { SendInput(inputs, std::mem::size_of::() as i32) }; if sent as usize != inputs.len() { anyhow::bail!( "SendInput failed: sent {} of {} inputs", sent, inputs.len() ); } Ok(()) } #[cfg(not(windows))] pub fn key_down(&mut self, _vk_code: u16) -> Result<()> { anyhow::bail!("Keyboard input only supported on Windows") } #[cfg(not(windows))] pub fn key_up(&mut self, _vk_code: u16) -> Result<()> { anyhow::bail!("Keyboard input only supported on Windows") } #[cfg(not(windows))] pub fn type_char(&mut self, _ch: char) -> Result<()> { anyhow::bail!("Keyboard input only supported on Windows") } #[cfg(not(windows))] pub fn send_sas(&mut self) -> Result<()> { anyhow::bail!("SAS only supported on Windows") } } /// Common Windows virtual key codes #[allow(dead_code)] pub mod vk { pub const BACK: u16 = 0x08; pub const TAB: u16 = 0x09; pub const RETURN: u16 = 0x0D; pub const SHIFT: u16 = 0x10; pub const CONTROL: u16 = 0x11; pub const MENU: u16 = 0x12; // Alt pub const PAUSE: u16 = 0x13; pub const CAPITAL: u16 = 0x14; // Caps Lock pub const ESCAPE: u16 = 0x1B; pub const SPACE: u16 = 0x20; pub const PRIOR: u16 = 0x21; // Page Up pub const NEXT: u16 = 0x22; // Page Down pub const END: u16 = 0x23; pub const HOME: u16 = 0x24; pub const LEFT: u16 = 0x25; pub const UP: u16 = 0x26; pub const RIGHT: u16 = 0x27; pub const DOWN: u16 = 0x28; pub const INSERT: u16 = 0x2D; pub const DELETE: u16 = 0x2E; // 0-9 keys pub const KEY_0: u16 = 0x30; pub const KEY_9: u16 = 0x39; // A-Z keys pub const KEY_A: u16 = 0x41; pub const KEY_Z: u16 = 0x5A; // Windows keys pub const LWIN: u16 = 0x5B; pub const RWIN: u16 = 0x5C; // Function keys pub const F1: u16 = 0x70; pub const F2: u16 = 0x71; pub const F3: u16 = 0x72; pub const F4: u16 = 0x73; pub const F5: u16 = 0x74; pub const F6: u16 = 0x75; pub const F7: u16 = 0x76; pub const F8: u16 = 0x77; pub const F9: u16 = 0x78; pub const F10: u16 = 0x79; pub const F11: u16 = 0x7A; pub const F12: u16 = 0x7B; // Modifier keys pub const LSHIFT: u16 = 0xA0; pub const RSHIFT: u16 = 0xA1; pub const LCONTROL: u16 = 0xA2; pub const RCONTROL: u16 = 0xA3; pub const LMENU: u16 = 0xA4; // Left Alt pub const RMENU: u16 = 0xA5; // Right Alt }