keyflow 0.1.0

Cross-platform input simulation library for keyboard, mouse and hotkeys.
Documentation
use crate::error::*;
use crate::hotkey::HotkeyListener;
use crate::platform::windows::mapping::platform_to_key;
use crate::platform::{BackendListener, Listener};
use crate::types::KeyState;
use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock};
use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::{
    CallNextHookEx, DispatchMessageW, GetMessageW, KBDLLHOOKSTRUCT, MSG, SetWindowsHookExW,
    TranslateMessage, UnhookWindowsHookEx, WH_KEYBOARD_LL, WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN,
    WM_SYSKEYUP,
};

// need a static listener.
// there is no possibility to send data to the hook???
static LISTENER: OnceLock<Arc<HotkeyListener>> = OnceLock::new();

/// keyboard hook callback
unsafe extern "system" fn keyboard_hook_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    unsafe {
        if code < 0 {
            return CallNextHookEx(None, code, wparam, lparam);
        }

        let kb = &*(lparam.0 as *const KBDLLHOOKSTRUCT);

        let scancode = kb.scanCode as u16;
        let extended = (kb.flags.0 & 0x01) != 0;

        let key = match platform_to_key(scancode, extended) {
            Some(k) => k,
            None => return CallNextHookEx(None, code, wparam, lparam),
        };

        let state = match wparam.0 as u32 {
            WM_KEYDOWN | WM_SYSKEYDOWN => KeyState::Pressed,
            WM_KEYUP | WM_SYSKEYUP => KeyState::Released,
            _ => return CallNextHookEx(None, code, wparam, lparam),
        };

        if let Some(listener) = LISTENER.get() {
            listener.on_key_event(key, state);
        }

        CallNextHookEx(None, code, wparam, lparam)
    }
}

impl Drop for BackendListener {
    fn drop(&mut self) {
        self.stop();
    }
}

impl Listener for BackendListener {
    fn start(&self) -> Result<()> {
        LISTENER.set(Arc::clone(&self.listener)).map_err(|_| {
            KeyflowError::PlatformError("Windows Keyboard hook is already running".to_string())
        })?;

        // install the hook and run the message loop as long as listener.running is true
        std::thread::spawn(move || unsafe {
            let hook = SetWindowsHookExW(WH_KEYBOARD_LL, Some(keyboard_hook_proc), None, 0)
                .expect("SetWindowsHookExW failed");

            let mut msg = MSG::default();

            if let Some(listener) = LISTENER.get() {
                while listener.running.load(Ordering::SeqCst) {
                    if GetMessageW(&mut msg, None, 0, 0).as_bool() {
                        let _ = TranslateMessage(&msg);
                        DispatchMessageW(&msg);
                    }
                }
            }

            UnhookWindowsHookEx(hook).expect("UnhookWindowsHookEx failed");
        });

        Ok(())
    }

    fn stop(&self) {
        self.listener.running.store(false, Ordering::Relaxed);
    }
}