//! Mouse input simulation using Windows SendInput API use super::MouseButton; use anyhow::Result; #[cfg(windows)] use windows::Win32::UI::Input::KeyboardAndMouse::{ SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK, MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, }; // X button constants (not exported in windows crate 0.58+) #[cfg(windows)] const XBUTTON1: u32 = 0x0001; #[cfg(windows)] const XBUTTON2: u32 = 0x0002; #[cfg(windows)] use windows::Win32::UI::WindowsAndMessaging::{ GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, }; /// Mouse input controller pub struct MouseController { /// Virtual screen dimensions for coordinate translation #[cfg(windows)] virtual_screen: VirtualScreen, } #[cfg(windows)] struct VirtualScreen { x: i32, y: i32, width: i32, height: i32, } impl MouseController { /// Create a new mouse controller pub fn new() -> Result { #[cfg(windows)] { let virtual_screen = unsafe { VirtualScreen { x: GetSystemMetrics(SM_XVIRTUALSCREEN), y: GetSystemMetrics(SM_YVIRTUALSCREEN), width: GetSystemMetrics(SM_CXVIRTUALSCREEN), height: GetSystemMetrics(SM_CYVIRTUALSCREEN), } }; Ok(Self { virtual_screen }) } #[cfg(not(windows))] { anyhow::bail!("Mouse input only supported on Windows") } } /// Move mouse to absolute screen coordinates #[cfg(windows)] pub fn move_to(&mut self, x: i32, y: i32) -> Result<()> { // Convert screen coordinates to normalized absolute coordinates (0-65535) let norm_x = ((x - self.virtual_screen.x) * 65535) / self.virtual_screen.width; let norm_y = ((y - self.virtual_screen.y) * 65535) / self.virtual_screen.height; let input = INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx: norm_x, dy: norm_y, mouseData: 0, dwFlags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK, time: 0, dwExtraInfo: 0, }, }, }; self.send_input(&[input]) } /// Press mouse button down #[cfg(windows)] pub fn button_down(&mut self, button: MouseButton) -> Result<()> { let (flags, data) = match button { MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, 0), MouseButton::Right => (MOUSEEVENTF_RIGHTDOWN, 0), MouseButton::Middle => (MOUSEEVENTF_MIDDLEDOWN, 0), MouseButton::X1 => (MOUSEEVENTF_XDOWN, XBUTTON1), MouseButton::X2 => (MOUSEEVENTF_XDOWN, XBUTTON2), }; let input = INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx: 0, dy: 0, mouseData: data, dwFlags: flags, time: 0, dwExtraInfo: 0, }, }, }; self.send_input(&[input]) } /// Release mouse button #[cfg(windows)] pub fn button_up(&mut self, button: MouseButton) -> Result<()> { let (flags, data) = match button { MouseButton::Left => (MOUSEEVENTF_LEFTUP, 0), MouseButton::Right => (MOUSEEVENTF_RIGHTUP, 0), MouseButton::Middle => (MOUSEEVENTF_MIDDLEUP, 0), MouseButton::X1 => (MOUSEEVENTF_XUP, XBUTTON1), MouseButton::X2 => (MOUSEEVENTF_XUP, XBUTTON2), }; let input = INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx: 0, dy: 0, mouseData: data, dwFlags: flags, time: 0, dwExtraInfo: 0, }, }, }; self.send_input(&[input]) } /// Scroll mouse wheel #[cfg(windows)] pub fn scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> { let mut inputs = Vec::new(); // Vertical scroll if delta_y != 0 { inputs.push(INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx: 0, dy: 0, mouseData: delta_y as u32, dwFlags: MOUSEEVENTF_WHEEL, time: 0, dwExtraInfo: 0, }, }, }); } // Horizontal scroll if delta_x != 0 { inputs.push(INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi: MOUSEINPUT { dx: 0, dy: 0, mouseData: delta_x as u32, dwFlags: MOUSEEVENTF_HWHEEL, time: 0, dwExtraInfo: 0, }, }, }); } if !inputs.is_empty() { self.send_input(&inputs)?; } Ok(()) } /// 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 move_to(&mut self, _x: i32, _y: i32) -> Result<()> { anyhow::bail!("Mouse input only supported on Windows") } #[cfg(not(windows))] pub fn button_down(&mut self, _button: MouseButton) -> Result<()> { anyhow::bail!("Mouse input only supported on Windows") } #[cfg(not(windows))] pub fn button_up(&mut self, _button: MouseButton) -> Result<()> { anyhow::bail!("Mouse input only supported on Windows") } #[cfg(not(windows))] pub fn scroll(&mut self, _delta_x: i32, _delta_y: i32) -> Result<()> { anyhow::bail!("Mouse input only supported on Windows") } }