use std::io;
use windows::Win32::UI::Input::KeyboardAndMouse::{
HOT_KEY_MODIFIERS, MOD_ALT, MOD_CONTROL, MOD_NOREPEAT, MOD_SHIFT, MOD_WIN, RegisterHotKey,
UnregisterHotKey,
};
use super::config::{allocation_error, windows_error};
use super::event::ModifierState;
use super::native::NativeWindow;
pub const MAX_GLOBAL_HOTKEYS: usize = 32;
pub const MAX_PENDING_HOTKEY_PRESSES: usize = 64;
pub const MAX_HOTKEY_ID: u16 = 0xBFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct HotkeyId(u16);
impl HotkeyId {
pub fn new(id: u16) -> io::Result<Self> {
if id > MAX_HOTKEY_ID {
return Err(invalid("hotkey identifier is in the shared-library range"));
}
Ok(Self(id))
}
#[must_use]
pub const fn get(self) -> u16 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlobalHotkey {
modifiers: ModifierState,
virtual_key: u32,
}
impl GlobalHotkey {
pub fn new(modifiers: ModifierState, virtual_key: u32) -> io::Result<Self> {
if modifiers == ModifierState::NONE {
return Err(invalid("global hotkey requires at least one modifier"));
}
let modifier_key = matches!(virtual_key, 0x10..=0x12 | 0x5B | 0x5C | 0xA0..=0xA5);
if !(0x01..=0xFE).contains(&virtual_key) || modifier_key {
return Err(invalid(
"global hotkey key must be a non-modifier virtual key",
));
}
Ok(Self {
modifiers,
virtual_key,
})
}
#[must_use]
pub const fn modifiers(self) -> ModifierState {
self.modifiers
}
#[must_use]
pub const fn virtual_key(self) -> u32 {
self.virtual_key
}
fn native_modifiers(self) -> HOT_KEY_MODIFIERS {
let mut flags = MOD_NOREPEAT;
for (held, flag) in [
(self.modifiers.ctrl(), MOD_CONTROL),
(self.modifiers.alt(), MOD_ALT),
(self.modifiers.shift(), MOD_SHIFT),
(self.modifiers.meta(), MOD_WIN),
] {
if held {
flags |= flag;
}
}
flags
}
}
impl NativeWindow {
pub fn register_hotkey(&mut self, id: HotkeyId, hotkey: GlobalHotkey) -> io::Result<()> {
if self.is_destroyed() {
return Err(invalid(
"cannot register a hotkey on a destroyed native window",
));
}
if self.hotkeys.contains(&id) {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"hotkey identifier is already registered on this window",
));
}
if self.hotkeys.len() >= MAX_GLOBAL_HOTKEYS {
return Err(io::Error::new(
io::ErrorKind::OutOfMemory,
"native window hotkey capacity exceeded",
));
}
self.hotkeys
.try_reserve(1)
.map_err(|_| allocation_error())?;
unsafe {
RegisterHotKey(
Some(self.hwnd),
i32::from(id.0),
hotkey.native_modifiers(),
hotkey.virtual_key,
)
}
.map_err(windows_error)?;
self.hotkeys.push(id);
Ok(())
}
pub fn unregister_hotkey(&mut self, id: HotkeyId) -> io::Result<bool> {
let Some(index) = self.hotkeys.iter().position(|held| *held == id) else {
return Ok(false);
};
unsafe { UnregisterHotKey(Some(self.hwnd), i32::from(id.0)) }.map_err(windows_error)?;
self.hotkeys.swap_remove(index);
self.state
.with(|state| state.hotkey_presses.retain(|pressed| *pressed != id.0));
Ok(true)
}
pub fn take_hotkey_presses(&mut self) -> Vec<HotkeyId> {
let hotkeys = &self.hotkeys;
self.state.with(|state| {
state
.hotkey_presses
.drain(..)
.map(HotkeyId)
.filter(|id| hotkeys.contains(id))
.collect()
})
}
pub(super) fn release_hotkeys(&mut self) {
for id in self.hotkeys.drain(..) {
let _ = unsafe { UnregisterHotKey(Some(self.hwnd), i32::from(id.0)) };
}
self.state.with(|state| state.hotkey_presses.clear());
}
}
fn invalid(message: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, message)
}