use actl_core::CtlError;
use windows::Win32::UI::Input::KeyboardAndMouse::{
INPUT, INPUT_0, INPUT_MOUSE, MOUSE_EVENT_FLAGS, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTDOWN,
MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
MOUSEEVENTF_VIRTUALDESK, MOUSEEVENTF_WHEEL, MOUSEINPUT, SendInput,
};
use windows::Win32::UI::WindowsAndMessaging::{
GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
};
const WHEEL_DELTA: i32 = 120;
fn normalize(x: i32, y: i32) -> (i32, i32) {
unsafe {
let (vx, vy, vw, vh) = (
GetSystemMetrics(SM_XVIRTUALSCREEN),
GetSystemMetrics(SM_YVIRTUALSCREEN),
GetSystemMetrics(SM_CXVIRTUALSCREEN),
GetSystemMetrics(SM_CYVIRTUALSCREEN),
);
if vw <= 0 || vh <= 0 {
return (0, 0);
}
let nx = ((x - vx) as f64 * 65535.0 / vw as f64).round() as i32;
let ny = ((y - vy) as f64 * 65535.0 / vh as f64).round() as i32;
(nx.clamp(0, 65535), ny.clamp(0, 65535))
}
}
fn mouse_event(flags: MOUSE_EVENT_FLAGS, nx: i32, ny: i32, data: i32) -> INPUT {
INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: nx,
dy: ny,
mouseData: data as u32,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
}
}
fn send_all(inputs: &[INPUT]) -> Result<(), CtlError> {
let sent = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };
if sent == inputs.len() as u32 {
Ok(())
} else {
Err(CtlError::internal(format!(
"SendInput(mouse) delivered {sent}/{} events; pointer state is now unknown",
inputs.len()
)))
}
}
fn abs(flags: MOUSE_EVENT_FLAGS) -> MOUSE_EVENT_FLAGS {
flags | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
}
pub fn left_click_at(x: i32, y: i32) -> Result<(), CtlError> {
let (nx, ny) = normalize(x, y);
send_all(&[
mouse_event(abs(MOUSEEVENTF_MOVE), nx, ny, 0),
mouse_event(abs(MOUSEEVENTF_LEFTDOWN), nx, ny, 0),
mouse_event(MOUSEEVENTF_LEFTUP, nx, ny, 0),
])
}
pub fn right_click_at(x: i32, y: i32) -> Result<(), CtlError> {
let (nx, ny) = normalize(x, y);
send_all(&[
mouse_event(abs(MOUSEEVENTF_MOVE), nx, ny, 0),
mouse_event(abs(MOUSEEVENTF_RIGHTDOWN), nx, ny, 0),
mouse_event(MOUSEEVENTF_RIGHTUP, nx, ny, 0),
])
}
pub fn double_click_at(x: i32, y: i32) -> Result<(), CtlError> {
let (nx, ny) = normalize(x, y);
send_all(&[
mouse_event(abs(MOUSEEVENTF_MOVE), nx, ny, 0),
mouse_event(abs(MOUSEEVENTF_LEFTDOWN), nx, ny, 0),
mouse_event(MOUSEEVENTF_LEFTUP, nx, ny, 0),
mouse_event(abs(MOUSEEVENTF_LEFTDOWN), nx, ny, 0),
mouse_event(MOUSEEVENTF_LEFTUP, nx, ny, 0),
])
}
pub fn move_to(x: i32, y: i32) -> Result<(), CtlError> {
let (nx, ny) = normalize(x, y);
send_all(&[mouse_event(abs(MOUSEEVENTF_MOVE), nx, ny, 0)])
}
pub fn drag_to(x1: i32, y1: i32, x2: i32, y2: i32) -> Result<(), CtlError> {
let (ax, ay) = normalize(x1, y1);
let (bx, by) = normalize(x2, y2);
let result = send_all(&[
mouse_event(abs(MOUSEEVENTF_MOVE), ax, ay, 0),
mouse_event(abs(MOUSEEVENTF_LEFTDOWN), ax, ay, 0),
mouse_event(abs(MOUSEEVENTF_MOVE), bx, by, 0),
mouse_event(MOUSEEVENTF_LEFTUP, bx, by, 0),
]);
if result.is_err() {
let _ = send_all(&[mouse_event(MOUSEEVENTF_LEFTUP, bx, by, 0)]);
}
result
}
pub fn wheel_at(x: i32, y: i32, notches: i32) -> Result<(), CtlError> {
let (nx, ny) = normalize(x, y);
send_all(&[
mouse_event(abs(MOUSEEVENTF_MOVE), nx, ny, 0),
mouse_event(
MOUSEEVENTF_WHEEL,
nx,
ny,
notches.saturating_mul(WHEEL_DELTA),
),
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_maps_primary_origin_to_zero() {
unsafe {
let vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
let vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
let (nx, ny) = normalize(vx, vy);
assert_eq!((nx, ny), (0, 0));
let (fx, fy) = normalize(
vx + GetSystemMetrics(SM_CXVIRTUALSCREEN),
vy + GetSystemMetrics(SM_CYVIRTUALSCREEN),
);
assert!((fx - 65535).abs() <= 1 && (fy - 65535).abs() <= 1);
}
}
}