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,
};
static LISTENER: OnceLock<Arc<HotkeyListener>> = OnceLock::new();
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())
})?;
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);
}
}