keyflow 0.1.0

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
use crate::error::*;
use crate::platform::Simulation;
use crate::platform::windows::mapping::{WindowsScancode, button_to_platform, key_to_platform};
use crate::types::*;
use smallvec::SmallVec;
use std::time::Duration;
use windows::Win32::UI::Input::KeyboardAndMouse::{
    INPUT, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP,
    KEYEVENTF_SCANCODE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL, MOUSEEVENTF_MOVE,
    MOUSEEVENTF_VIRTUALDESK, MOUSEEVENTF_WHEEL, MOUSEINPUT, SendInput,
};
use windows::Win32::UI::WindowsAndMessaging::{
    GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
};

pub struct Backend {
    events: SmallVec<[INPUT; 32]>,
    screen: Screen,
}

impl Backend {
    pub(crate) fn new() -> Result<Self> {
        Ok(Self {
            events: SmallVec::new(),
            screen: Screen {
                width: unsafe { GetSystemMetrics(SM_CXVIRTUALSCREEN) },
                height: unsafe { GetSystemMetrics(SM_CYVIRTUALSCREEN) },
                left: unsafe { GetSystemMetrics(SM_XVIRTUALSCREEN) },
                top: unsafe { GetSystemMetrics(SM_YVIRTUALSCREEN) },
            },
        })
    }

    /// Convert screen coordinates to Windows absolute coordinates (0-65535)
    /// accounting for virtual screen offset
    fn to_absolute_x(&self, x: i32) -> i32 {
        let normalized_x = x - self.screen.left;
        (normalized_x * 65536) / self.screen.width
    }

    fn to_absolute_y(&self, y: i32) -> i32 {
        let normalized_y = y - self.screen.top;
        (normalized_y * 65536) / self.screen.height
    }

    fn send_input(&mut self) -> Result<()> {
        unsafe {
            let result = SendInput(&self.events, size_of::<INPUT>() as i32);
            if result as usize != self.events.len() {
                return Err(KeyflowError::PlatformError(format!(
                    "SendInput failed: expected {}, sent {}",
                    self.events.len(),
                    result
                )));
            }
        }

        self.events.clear();
        Ok(())
    }

    fn add_key(&mut self, scancode: WindowsScancode, released: bool) {
        let mut flags = KEYEVENTF_SCANCODE;
        if scancode.extended {
            flags |= KEYEVENTF_EXTENDEDKEY;
        }
        if released {
            flags |= KEYEVENTF_KEYUP;
        }

        self.events.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: scancode.code,
                    dwFlags: flags,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        });
    }

    fn add_button(&mut self, button: Button, released: bool) {
        if let Some((flags, mouse_data)) = button_to_platform(button, released) {
            self.events.push(INPUT {
                r#type: INPUT_MOUSE,
                Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 {
                    mi: MOUSEINPUT {
                        dx: 0,
                        dy: 0,
                        mouseData: mouse_data,
                        dwFlags: flags,
                        time: 0,
                        dwExtraInfo: 0,
                    },
                },
            });
        }
    }

    fn add_movement(&mut self, movement: Movement) {
        let (dx, dy, flag) = match movement {
            Movement::Relative { dx, dy } => (dx, dy, MOUSEEVENTF_MOVE),
            Movement::Absolute { x, y } => {
                let x = x.clamp(self.screen.left, self.screen.left + self.screen.width);
                let y = y.clamp(self.screen.top, self.screen.top + self.screen.height);

                (
                    self.to_absolute_x(x),
                    self.to_absolute_y(y),
                    MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
                )
            }
        };

        self.events.push(INPUT {
            r#type: INPUT_MOUSE,
            Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 {
                mi: MOUSEINPUT {
                    dx,
                    dy,
                    mouseData: 0,
                    dwFlags: flag,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        });
    }

    fn add_scroll(&mut self, scroll: Scroll) {
        let (delta, flag) = match scroll {
            Scroll::Vertical(delta) => ((delta * 120) as u32, MOUSEEVENTF_WHEEL),
            Scroll::Horizontal(delta) => ((delta * 120) as u32, MOUSEEVENTF_HWHEEL),
        };

        self.events.push(INPUT {
            r#type: INPUT_MOUSE,
            Anonymous: windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0 {
                mi: MOUSEINPUT {
                    dx: 0,
                    dy: 0,
                    mouseData: delta,
                    dwFlags: flag,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        });
    }
}

impl Simulation for Backend {
    fn send_key(&mut self, key: Key, action: Action) -> Result<()> {
        if let Some(scancode) = key_to_platform(key) {
            match action {
                Action::Press => {
                    self.add_key(scancode, false);
                }
                Action::Release => {
                    self.add_key(scancode, true);
                }
                Action::Click => {
                    self.add_key(scancode, false);
                    self.send_input()?;

                    std::thread::sleep(Duration::from_millis(1));

                    self.add_key(scancode, true);
                }
            }
        }

        self.send_input()
    }

    fn send_button(&mut self, button: Button, action: Action) -> Result<()> {
        match action {
            Action::Press => {
                self.add_button(button, false);
            }
            Action::Release => {
                self.add_button(button, true);
            }
            Action::Click => {
                self.add_button(button, false);
                self.send_input()?;

                std::thread::sleep(Duration::from_millis(1));

                self.add_button(button, true);
            }
        }

        self.send_input()
    }

    fn send_movement(&mut self, movement: Movement) -> Result<()> {
        self.add_movement(movement);
        self.send_input()
    }

    fn send_scroll(&mut self, scroll: Scroll) -> Result<()> {
        self.add_scroll(scroll);
        self.send_input()
    }

    fn send_batch(&mut self, events: Vec<InputEvent>) -> Result<()> {
        for event in events {
            match event {
                InputEvent::KeyEvent { key, action } => {
                    if let Some(scancode) = key_to_platform(key) {
                        match action {
                            Action::Press => self.add_key(scancode, false),
                            Action::Release => self.add_key(scancode, true),
                            Action::Click => {
                                self.add_key(scancode, false);
                                self.send_input()?;

                                std::thread::sleep(Duration::from_millis(1));

                                self.add_key(scancode, true);
                            }
                        }
                    }
                }
                InputEvent::ButtonEvent { button, action } => match action {
                    Action::Press => self.add_button(button, false),
                    Action::Release => self.add_button(button, true),
                    Action::Click => {
                        self.add_button(button, false);
                        self.send_input()?;

                        std::thread::sleep(Duration::from_millis(1));

                        self.add_button(button, true);
                    }
                },
                InputEvent::MouseMove(movement) => {
                    self.add_movement(movement);
                }
                InputEvent::MouseScroll(scroll) => {
                    self.add_scroll(scroll);
                }
                InputEvent::Delay(duration) => {
                    if !self.events.is_empty() {
                        self.send_input()?;
                    }
                    std::thread::sleep(duration);
                }
            }
        }

        if !self.events.is_empty() {
            self.send_input()?
        }

        Ok(())
    }
}