/// 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())
}