#[cfg(not(target_os = "windows"))]
compile_error!("Only supported on windows");
use std::collections::HashMap;
use std::marker::PhantomData;
use winapi::shared::windef::HWND;
use winapi::um::libloaderapi::GetModuleHandleA;
use winapi::um::winuser::{
CreateWindowExA, DestroyWindow, GetMessageW, RegisterHotKey, UnregisterHotKey, HWND_MESSAGE,
MSG, WM_HOTKEY, WM_NULL, WS_DISABLED, WS_EX_NOACTIVATE,
};
use crate::{
error::HkError, get_global_keystate, keys::*, HotkeyCallback, HotkeyId, HotkeyManagerImpl,
InterruptHandle,
};
pub struct HotkeyManager<T> {
hwnd: HwndDropper,
id_offset: i32,
handlers: HashMap<HotkeyId, HotkeyCallback<T>>,
no_repeat: bool,
_unimpl_send_sync: PhantomData<*const u8>,
}
impl<T> Default for HotkeyManager<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> HotkeyManager<T> {
pub fn set_no_repeat(&mut self, no_repeat: bool) {
self.no_repeat = no_repeat;
}
}
impl<T> HotkeyManagerImpl<T> for HotkeyManager<T> {
fn new() -> HotkeyManager<T> {
let hwnd = create_hidden_window().unwrap_or(HwndDropper(std::ptr::null_mut()));
HotkeyManager {
hwnd,
id_offset: 0,
handlers: HashMap::new(),
no_repeat: true,
_unimpl_send_sync: PhantomData,
}
}
fn register_extrakeys(
&mut self,
key: VKey,
key_modifiers: &[ModKey],
extra_keys: &[VKey],
callback: impl Fn() -> T + Send + 'static,
) -> Result<HotkeyId, HkError> {
let register_id = HotkeyId(self.id_offset);
self.id_offset += 1;
let mut modifiers = ModKey::combine(key_modifiers);
if self.no_repeat {
modifiers |= ModKey::NoRepeat.to_mod_code();
}
let reg_ok = unsafe {
RegisterHotKey(
self.hwnd.0,
register_id.0,
modifiers,
key.to_vk_code() as u32,
)
};
if reg_ok == 0 {
Err(HkError::RegistrationFailed)
} else {
self.handlers.insert(
register_id,
HotkeyCallback {
callback: Box::new(callback),
extra_keys: extra_keys.to_owned(),
},
);
Ok(register_id)
}
}
fn register(
&mut self,
key: VKey,
key_modifiers: &[ModKey],
callback: impl Fn() -> T + Send + 'static,
) -> Result<HotkeyId, HkError> {
self.register_extrakeys(key, key_modifiers, &[], callback)
}
fn unregister(&mut self, id: HotkeyId) -> Result<(), HkError> {
let ok = unsafe { UnregisterHotKey(self.hwnd.0, id.0) };
match ok {
0 => Err(HkError::UnregistrationFailed),
_ => {
self.handlers.remove(&id);
Ok(())
}
}
}
fn unregister_all(&mut self) -> Result<(), HkError> {
let ids: Vec<_> = self.handlers.keys().copied().collect();
for id in ids {
self.unregister(id)?;
}
Ok(())
}
fn handle_hotkey(&self) -> Option<T> {
loop {
let mut msg = std::mem::MaybeUninit::<MSG>::uninit();
let ok = unsafe { GetMessageW(msg.as_mut_ptr(), self.hwnd.0, WM_NULL, WM_HOTKEY) };
if ok != 0 {
let msg = unsafe { msg.assume_init() };
if WM_HOTKEY == msg.message {
let hk_id = HotkeyId(msg.wParam as i32);
if let Some(handler) = self.handlers.get(&hk_id) {
if !handler
.extra_keys
.iter()
.any(|vk| !get_global_keystate(*vk))
{
return Some((handler.callback)());
}
}
} else if WM_NULL == msg.message {
return None;
}
}
}
}
fn event_loop(&self) {
while self.handle_hotkey().is_some() {}
}
fn interrupt_handle(&self) -> InterruptHandle {
InterruptHandle(self.hwnd.0)
}
}
impl<T> Drop for HotkeyManager<T> {
fn drop(&mut self) {
let _ = self.unregister_all();
}
}
struct HwndDropper(HWND);
impl Drop for HwndDropper {
fn drop(&mut self) {
if !self.0.is_null() {
let _ = unsafe { DestroyWindow(self.0) };
}
}
}
fn create_hidden_window() -> Result<HwndDropper, ()> {
let hwnd = unsafe {
let hinstance = GetModuleHandleA(std::ptr::null_mut());
CreateWindowExA(
WS_EX_NOACTIVATE,
b"Static\0".as_ptr() as *const i8,
b"\0".as_ptr() as *const i8,
WS_DISABLED,
0,
0,
0,
0,
HWND_MESSAGE,
std::ptr::null_mut(),
hinstance,
std::ptr::null_mut(),
)
};
if hwnd.is_null() {
Err(())
} else {
Ok(HwndDropper(hwnd))
}
}