/// Mouse click and keyboard input via Windows SendInput (or stub on non-Windows). use helios_common::MouseButton; #[cfg(windows)] pub fn click(x: i32, y: i32, button: &MouseButton) -> Result<(), String> { use windows::Win32::UI::Input::KeyboardAndMouse::{ SendInput, INPUT, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEINPUT, }; use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN}; unsafe { let screen_w = GetSystemMetrics(SM_CXSCREEN) as i32; let screen_h = GetSystemMetrics(SM_CYSCREEN) as i32; if screen_w == 0 || screen_h == 0 { return Err(format!( "Could not get screen dimensions: {screen_w}x{screen_h}" )); } // Convert pixel coords to absolute 0-65535 range let abs_x = ((x * 65535) / screen_w) as i32; let abs_y = ((y * 65535) / screen_h) as i32; let (down_flag, up_flag) = match button { MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP), MouseButton::Right => (MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP), MouseButton::Middle => (MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP), }; // Move to position let move_input = INPUT { r#type: INPUT_MOUSE, Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 { mi: MOUSEINPUT { dx: abs_x, dy: abs_y, mouseData: 0, dwFlags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, time: 0, dwExtraInfo: 0, }, }, }; let down_input = INPUT { r#type: INPUT_MOUSE, Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 { mi: MOUSEINPUT { dx: abs_x, dy: abs_y, mouseData: 0, dwFlags: down_flag | MOUSEEVENTF_ABSOLUTE, time: 0, dwExtraInfo: 0, }, }, }; let up_input = INPUT { r#type: INPUT_MOUSE, Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 { mi: MOUSEINPUT { dx: abs_x, dy: abs_y, mouseData: 0, dwFlags: up_flag | MOUSEEVENTF_ABSOLUTE, time: 0, dwExtraInfo: 0, }, }, }; let inputs = [move_input, down_input, up_input]; let result = SendInput(&inputs, std::mem::size_of::() as i32); if result != inputs.len() as u32 { return Err(format!( "SendInput for click at ({x},{y}) sent {result}/{} events — some may have been blocked by UIPI", inputs.len() )); } Ok(()) } } #[cfg(windows)] pub fn type_text(text: &str) -> Result<(), String> { use windows::Win32::UI::Input::KeyboardAndMouse::{ SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_UNICODE, }; if text.is_empty() { return Ok(()); } unsafe { let mut inputs: Vec = Vec::with_capacity(text.len() * 2); for ch in text.encode_utf16() { // Key down inputs.push(INPUT { r#type: INPUT_KEYBOARD, Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 { ki: KEYBDINPUT { wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0), wScan: ch, dwFlags: KEYEVENTF_UNICODE, time: 0, dwExtraInfo: 0, }, }, }); // Key up inputs.push(INPUT { r#type: INPUT_KEYBOARD, Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 { ki: KEYBDINPUT { wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0), wScan: ch, dwFlags: KEYEVENTF_UNICODE | windows::Win32::UI::Input::KeyboardAndMouse::KEYEVENTF_KEYUP, time: 0, dwExtraInfo: 0, }, }, }); } let result = SendInput(&inputs, std::mem::size_of::() as i32); if result != inputs.len() as u32 { return Err(format!( "SendInput for type_text sent {result}/{} events — some may have been blocked (UIPI or secure desktop)", inputs.len() )); } Ok(()) } } #[cfg(not(windows))] pub fn click(_x: i32, _y: i32, _button: &MouseButton) -> Result<(), String> { Err("click() is only supported on Windows".to_string()) } #[cfg(not(windows))] pub fn type_text(_text: &str) -> Result<(), String> { Err("type_text() is only supported on Windows".to_string()) }