hookmap-core 0.2.1

Global hooks and input simulation fo keyboard and mouse.
Documentation
mod hook;
mod input;
mod vkcode;

use hook::HookHandler;
use input::Input;
use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::HHOOK;

use crate::button::{Button, ButtonAction};
use crate::event::{self, EventReceiver, NativeEventOperation};

use std::sync::atomic::{AtomicBool, Ordering};

use once_cell::sync::Lazy;
use windows::Win32::UI::{HiDpi, WindowsAndMessaging};

const SHOULD_BE_IGNORED_FLAG: usize = 0x1;
const INJECTED_FLAG: usize = 0x2;

#[derive(Debug)]
struct ButtonState([AtomicBool; Button::VARIANT_COUNT]);

impl ButtonState {
    const fn new() -> Self {
        let inner = unsafe {
            // AtomicBool has the same in-memory representation as a bool.
            // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html
            std::mem::transmute([false; Button::VARIANT_COUNT])
        };
        ButtonState(inner)
    }

    #[inline]
    fn press(&self, button: Button, order: Ordering) {
        self.0[button as usize].store(true, order);
    }

    #[inline]
    fn release(&self, button: Button, order: Ordering) {
        self.0[button as usize].store(false, order)
    }

    #[inline]
    fn is_pressed(&self, button: Button, order: Ordering) -> bool {
        self.0[button as usize].load(order)
    }

    #[inline]
    fn is_released(&self, button: Button, order: Ordering) -> bool {
        !self.0[button as usize].load(order)
    }
}

static BUTTON_STATE: ButtonState = ButtonState::new();

static INPUT: Lazy<Input> = Lazy::new(Input::new);

#[inline]
fn send_input(button: Button, action: ButtonAction, recursive: bool, assume: fn(Button)) {
    let left_and_right_modifier = match button {
        Button::Shift => Some((Button::LShift, Button::RShift)),
        Button::Ctrl => Some((Button::LCtrl, Button::RCtrl)),
        Button::Alt => Some((Button::LAlt, Button::RAlt)),
        Button::Super => Some((Button::LSuper, Button::RSuper)),
        _ => None,
    };
    if let Some((left, right)) = left_and_right_modifier {
        assume(left);
        assume(right);
        assume(button);
        INPUT.button_input(left, action, recursive);
        INPUT.button_input(right, action, recursive);
    } else {
        assume(button);
        INPUT.button_input(button, action, recursive);
    }
}

impl Button {
    /// Simulates a button presses.
    #[inline]
    pub fn press(self) {
        send_input(self, ButtonAction::Press, false, Button::assume_pressed);
    }

    /// Simulates a button presses.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn press_recursive(self) {
        send_input(self, ButtonAction::Press, true, Button::assume_pressed);
    }

    /// Simulates a button releases.
    #[inline]
    pub fn release(self) {
        send_input(self, ButtonAction::Release, false, Button::assume_released);
    }

    /// Simulates a button releases.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn release_recursive(self) {
        send_input(self, ButtonAction::Release, true, Button::assume_released);
    }

    /// Simulates a button click.
    #[inline]
    pub fn click(self) {
        self.press();
        self.release();
    }

    /// Simulates a button click.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn click_recursive(self) {
        self.press_recursive();
        self.release_recursive();
    }

    /// Returns `true` if the button is pressed.
    #[inline]
    pub fn is_pressed(self) -> bool {
        BUTTON_STATE.is_pressed(self, Ordering::SeqCst)
    }

    /// Returns `true` if the button is released.
    #[inline]
    pub fn is_released(self) -> bool {
        BUTTON_STATE.is_released(self, Ordering::SeqCst)
    }

    #[inline]
    fn assume_pressed(self) {
        BUTTON_STATE.press(self, Ordering::SeqCst);
    }

    #[inline]
    fn assume_released(self) {
        BUTTON_STATE.release(self, Ordering::SeqCst);
    }
}

pub mod mouse {
    //! Functions for mouse operations

    use super::INPUT;

    /// Gets the position of the mouse cursor. `(x, y)`
    #[inline]
    pub fn get_position() -> (i32, i32) {
        INPUT.cursor_position()
    }

    /// Moves the mouse cursor to the specified coordinates.
    #[inline]
    pub fn move_absolute(x: i32, y: i32) {
        INPUT.move_absolute(x, y, false);
    }

    /// Moves the mouse cursor to the specified coordinates.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn move_absolute_recursive(x: i32, y: i32) {
        INPUT.move_absolute(x, y, true);
    }

    /// Moves the mouse cursor a specified distance.
    #[inline]
    pub fn move_relative(dx: i32, dy: i32) {
        INPUT.move_relative(dx, dy, false);
    }

    /// Moves the mouse cursor a specified distance.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn move_relative_recursive(dx: i32, dy: i32) {
        INPUT.move_relative(dx, dy, true);
    }

    /// Rotates the mouse wheel.
    #[inline]
    pub fn rotate(speed: i32) {
        INPUT.rotate_wheel(speed, false);
    }

    /// Rotates the mouse wheel.
    /// Events generated by this method can be hooked.
    #[inline]
    pub fn rotate_recursive(speed: i32) {
        INPUT.rotate_wheel(speed, true);
    }
}

static HOOK_HANDLER: Lazy<HookHandler> = Lazy::new(HookHandler::new);

extern "system" fn keyboard_hook_proc(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match hook::keyboard_hook_proc_inner(&HOOK_HANDLER, n_code, l_param) {
        NativeEventOperation::Block => LRESULT(1),
        NativeEventOperation::Dispatch => unsafe {
            WindowsAndMessaging::CallNextHookEx(HHOOK(0), n_code, w_param, l_param)
        },
    }
}

extern "system" fn mouse_hook_proc(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match hook::mouse_hook_proc_inner(&HOOK_HANDLER, &INPUT, n_code, w_param, l_param) {
        NativeEventOperation::Block => LRESULT(1),
        NativeEventOperation::Dispatch => unsafe {
            WindowsAndMessaging::CallNextHookEx(HHOOK(0), n_code, w_param, l_param)
        },
    }
}

/// Installs a hook and returns a receiver to receive the generated event.
///
/// # Panics
///
/// Panics if other hooks are already installed.
///
/// # Example
///
/// ```no_run
/// let rx = hookmap_core::install_hook();
/// ```
///
pub fn install_hook() -> EventReceiver {
    unsafe {
        // If this is not executed, the GetCursorPos function returns an invalid cursor position.
        HiDpi::SetProcessDpiAwarenessContext(HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
    }

    INPUT.update_cursor_position();

    let (tx, rx) = event::channel();
    HOOK_HANDLER.install(tx, keyboard_hook_proc, mouse_hook_proc);

    rx
}

/// Uninstalls a hook.
/// After this call, [`install_hook`] can be called again.
///
/// # Panics
///
/// Panics if the hook is not installed.
///
/// # Example
///
/// ```no_run
/// let rx = hookmap_core::install_hook();
/// hookmap_core::uninstall_hook();
///
/// assert!(rx.recv().is_err());
///
/// let rx = hookmap_core::install_hook();
/// ```
///
pub fn uninstall_hook() {
    HOOK_HANDLER.uninstall();
}