prevent_alt_win_menu/event_handler.rs
1//! Process keyboard events and suppress menu activation, providing interfaces for customization.
2//!
3//! This module provides mechanisms to process keyboard events and suppress menu activation,
4//! along with interfaces to customize this behavior.
5//!
6//! If you only want to suppress the menu activation triggered by Alt or Win keys,
7//! using `prevent_alt_win_menu::start` is simpler.
8//! However, if you already obtain keyboard events through other means,
9//! you can implement the [`MenuTriggerEvent`] trait for those event types
10//! and pass them to `start_event_handler` to create a custom menu suppression mechanism.
11//!
12//! In other words, this module offers a flexible way to integrate with existing keyboard event sources
13//! and suppress menu activation accordingly.
14
15use std::{fmt::Display, thread, time::Duration};
16
17use windows::Win32::{
18 Foundation::{LPARAM, WPARAM},
19 UI::{
20 Input::KeyboardAndMouse::{
21 INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, SendInput, VIRTUAL_KEY,
22 VK__none_, VK_LMENU, VK_LWIN, VK_MENU, VK_RMENU, VK_RWIN,
23 },
24 WindowsAndMessaging::{WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP},
25 },
26};
27
28pub use windows::Win32::UI::WindowsAndMessaging::KBDLLHOOKSTRUCT;
29
30pub use windows::Win32::UI::Input::KeyboardAndMouse;
31
32/// Starts an event-handling thread that processes each received event in a loop.
33///
34/// # Arguments
35/// - `rx`: The source of incoming events. Must be an `IntoIterator` whose items implement [`MenuTriggerEvent`].
36/// - `config`: Configuration used for event handling, such as the `on_released` callback.
37///
38/// # Returns
39/// A [`std::thread::JoinHandle`] that represents the running event-handling thread.
40///
41/// This function directly spawns a thread to process events in the background.
42/// It does not perform asynchronous operations.
43pub fn start_event_handler<
44 T: MenuTriggerEvent + Clone + Send + 'static,
45 I: IntoIterator<Item = T> + Send + 'static,
46>(
47 rx: I,
48 config: Config<T>,
49) -> thread::JoinHandle<()> {
50 let mut handler = Handler {
51 config,
52 state: Default::default(),
53 };
54
55 thread::spawn(move || {
56 #[cfg(feature = "log")]
57 log::debug!("started event handler");
58
59 for event in rx {
60 handler.handle_keyboard_event(&event);
61 }
62 })
63}
64
65/// A trait that abstracts keyboard events related to menu triggering.
66///
67/// By implementing this trait, you can consistently determine which key
68/// triggered a menu (e.g., Alt or Win) and whether the key was pressed or released.
69pub trait MenuTriggerEvent {
70 /// Returns the corresponding [`MenuTrigger`] for the key event.
71 ///
72 /// For example, return `Some(MenuTrigger::Alt)` for `LAlt` or `RAlt`,
73 /// and `Some(MenuTrigger::Win)` for `LWin` or `RWin`.
74 fn menu_trigger(&self) -> Option<MenuTrigger>;
75
76 /// Returns the current state of the key (pressed or released).
77 fn key_state(&self) -> KeyState;
78
79 /// Returns `true` if the key is currently pressed. (Default implementation provided.)
80 fn is_key_down(&self) -> bool {
81 matches!(self.key_state(), KeyState::Down)
82 }
83
84 /// Returns `true` if the key is currently released. (Default implementation provided.)
85 fn is_key_up(&self) -> bool {
86 !self.is_key_down()
87 }
88}
89
90/// Indicates which modifier key was used to trigger a menu.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum MenuTrigger {
93 /// The Windows key (either left or right).
94 Win,
95 /// The Alt key (either left or right).
96 Alt,
97}
98
99impl Display for MenuTrigger {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 let s = match self {
102 MenuTrigger::Win => "WIN",
103 MenuTrigger::Alt => "Alt",
104 };
105 write!(f, "{}", s)
106 }
107}
108
109/// Represents the state of a key: pressed or released.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum KeyState {
112 /// The key is currently pressed.
113 Down,
114 /// The key is currently released.
115 Up,
116}
117
118struct Handler<T = KeyboardEvent> {
119 config: Config<T>,
120 state: HoldStates<T>,
121}
122
123impl<T: MenuTriggerEvent + Clone> Handler<T> {
124 fn handle_keyboard_event(&mut self, event: &T) {
125 if let Some((_trigger, hold)) = self.state.update(event.clone()) {
126 if let Some(dummy_key) = (self.config.on_released)(hold) {
127 if let Err(_e) = send_keyup(dummy_key) {
128 #[cfg(feature = "log")]
129 log::error!("failed to prevent {} menu: {:?}", _trigger, _e);
130 } else {
131 #[cfg(feature = "log")]
132 log::info!("prevented {} menu by sending {:?}", _trigger, dummy_key);
133 }
134 } else {
135 #[cfg(feature = "log")]
136 log::info!("{} key released, but did not prevent menu", _trigger);
137 }
138 }
139 }
140}
141
142/// Represents a sequence of events where a modifier key is pressed and then released.
143///
144/// Typically passed to callbacks like `on_released` to determine how to handle
145/// modifier key interactions.
146///
147/// Note: The key pressed and the key released may differ.
148/// For example, consider the following sequence:
149///
150/// 1. `LAlt` is pressed
151/// 2. `RAlt` is pressed
152/// 3. `LAlt` is released
153/// 4. `RAlt` is released
154///
155/// In this case, `press` may be `LAlt` and `release` may be `RAlt`.
156#[derive(Debug, Clone, Copy, PartialEq)]
157pub struct HoldEvent<T = KeyboardEvent> {
158 /// The event when the key was pressed.
159 pub press: T,
160 /// The event when the key was released.
161 pub release: T,
162}
163
164#[derive(Debug)]
165struct HoldStates<T = KeyboardEvent> {
166 win: HoldState<T>,
167 alt: HoldState<T>,
168}
169
170impl<T> HoldStates<T> {
171 fn get_mut(&mut self, trigger: MenuTrigger) -> &mut HoldState<T> {
172 match trigger {
173 MenuTrigger::Win => &mut self.win,
174 MenuTrigger::Alt => &mut self.alt,
175 }
176 }
177
178 fn reset(&mut self) {
179 self.win.reset();
180 self.alt.reset();
181 }
182}
183
184impl<T: MenuTriggerEvent> HoldStates<T> {
185 fn update(&mut self, event: T) -> Option<(MenuTrigger, HoldEvent<T>)> {
186 if let Some(trigger) = event.menu_trigger() {
187 self.get_mut(trigger)
188 .update(event)
189 .map(|hold| (trigger, hold))
190 } else {
191 self.reset();
192 None
193 }
194 }
195}
196
197impl<T> Default for HoldStates<T> {
198 fn default() -> Self {
199 Self {
200 win: Default::default(),
201 alt: Default::default(),
202 }
203 }
204}
205
206#[derive(Debug)]
207struct HoldState<T = KeyboardEvent>(Option<T>);
208
209impl<T> HoldState<T> {
210 fn reset(&mut self) {
211 self.0 = None;
212 }
213}
214
215impl<T: MenuTriggerEvent> HoldState<T> {
216 fn update(&mut self, event: T) -> Option<HoldEvent<T>> {
217 match event.key_state() {
218 KeyState::Down => {
219 self.0.get_or_insert(event);
220 None
221 }
222 KeyState::Up => self.0.take().map(|hold_start_event| HoldEvent {
223 press: hold_start_event,
224 release: event,
225 }),
226 }
227 }
228}
229
230impl<T> Default for HoldState<T> {
231 fn default() -> Self {
232 Self(Default::default())
233 }
234}
235
236/// A callback type invoked when a key is released.
237///
238/// Receives a [`HoldEvent`] and returns a virtual key code (dummy key) to send,
239/// or `None` if no key should be sent.
240///
241/// Sending a virtual key allows Windows to treat it as a hotkey input,
242/// which prevents the default menu from being displayed when Alt or Win is released.
243pub type OnReleasedFn<T = KeyboardEvent> =
244 dyn Fn(HoldEvent<T>) -> Option<VIRTUAL_KEY> + Send + Sync + 'static;
245
246/// Configuration for the event handler's behavior.
247///
248/// Used to define how to handle a modifier key after it has been pressed and released.
249/// For example, you can specify a callback to send a dummy key to prevent menu activation.
250///
251/// By default, it returns `Some(VK__none_)` to always suppress menu activation.
252pub struct Config<T = KeyboardEvent> {
253 /// A callback invoked when a key is released after being pressed.
254 pub on_released: Box<OnReleasedFn<T>>,
255}
256
257impl<T> Config<T> {
258 /// Sets the callback function to be invoked when a key is released.
259 ///
260 /// This method updates the `on_released` field with the provided function,
261 /// which takes a [`HoldEvent`] representing the press and release of a modifier key.
262 /// The callback should return a dummy [`VIRTUAL_KEY`] to send, or `None` if no key should be sent.
263 ///
264 /// # Arguments
265 /// - `f`: A closure or function of type `Fn(HoldEvent<T>) -> Option<VIRTUAL_KEY>`.
266 ///
267 /// # Returns
268 /// A modified [`Config`] instance with the new callback set (builder pattern).
269 pub fn set_on_released<F: Fn(HoldEvent<T>) -> Option<VIRTUAL_KEY> + Send + Sync + 'static>(
270 mut self,
271 f: F,
272 ) -> Self {
273 self.on_released = Box::new(f);
274 self
275 }
276}
277
278impl<T> Default for Config<T> {
279 fn default() -> Self {
280 Self {
281 on_released: Box::new(|_| Some(VK__none_)),
282 }
283 }
284}
285
286/// Sends a key-up event for the specified virtual key code.
287///
288/// This function uses the Windows `SendInput` API to emit a `KEYEVENTF_KEYUP`
289/// event for the given key. It is typically used to suppress system behavior
290/// such as menu activation after pressing modifier keys like Alt or Win.
291///
292/// # Arguments
293/// - `dummy_key`: The virtual key code for which to send a key-up event.
294///
295/// # Returns
296/// Returns `Ok(())` if the event was successfully sent, or an `std::io::Error` if it failed.
297pub fn send_keyup(dummy_key: VIRTUAL_KEY) -> std::io::Result<()> {
298 send_input(&[INPUT {
299 r#type: INPUT_KEYBOARD,
300 Anonymous: INPUT_0 {
301 ki: KEYBDINPUT {
302 wVk: dummy_key,
303 dwFlags: KEYEVENTF_KEYUP,
304 ..Default::default()
305 },
306 },
307 }])
308}
309
310fn send_input(inputs: &[INPUT]) -> std::io::Result<()> {
311 let result = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };
312
313 if result as usize != inputs.len() {
314 Err(std::io::Error::last_os_error())
315 } else {
316 #[cfg(feature = "log")]
317 log::trace!(
318 "SendInput: {:?}",
319 inputs
320 .iter()
321 .map(|i| unsafe { i.Anonymous.ki.wVk })
322 .collect::<Vec<_>>()
323 );
324 Ok(())
325 }
326}
327
328/// Represents a single keyboard event received via a Windows low-level keyboard hook.
329///
330/// Internally contains the raw Windows [`KBDLLHOOKSTRUCT`] and the associated event type
331/// (e.g., key down or key up).
332#[derive(Debug, Clone, Copy, PartialEq)]
333pub struct KeyboardEvent {
334 /// The raw Windows keyboard event structure.
335 pub kbd: KBDLLHOOKSTRUCT,
336 /// The raw Windows keyboard event structure.
337 pub wm_key_state: WmKeyState,
338}
339
340impl KeyboardEvent {
341 /// Constructs a `KeyboardEvent` from `l_param` and `w_param` inside a Windows hook procedure.
342 ///
343 /// # Safety
344 /// `l_param` must be a valid pointer to a `KBDLLHOOKSTRUCT`.
345 pub(crate) unsafe fn from_params(l_param: LPARAM, w_param: WPARAM) -> KeyboardEvent {
346 let kbd = unsafe { *(l_param.0 as *const KBDLLHOOKSTRUCT) };
347 let key_state = WmKeyState::from_w_param(w_param).unwrap();
348 Self {
349 kbd,
350 wm_key_state: key_state,
351 }
352 }
353
354 /// Returns the virtual key code of the event.
355 pub fn virtual_key(&self) -> VIRTUAL_KEY {
356 VIRTUAL_KEY(self.kbd.vkCode as _)
357 }
358
359 /// Returns the duration elapsed since the given earlier event.
360 pub fn duration_since(&self, earlier: &Self) -> Duration {
361 let millis = self.kbd.time.wrapping_sub(earlier.kbd.time);
362 Duration::from_millis(millis as u64)
363 }
364}
365
366impl MenuTriggerEvent for KeyboardEvent {
367 fn menu_trigger(&self) -> Option<crate::event_handler::MenuTrigger> {
368 match self.virtual_key() {
369 VK_LWIN | VK_RWIN => Some(MenuTrigger::Win),
370 VK_MENU | VK_LMENU | VK_RMENU => Some(MenuTrigger::Alt),
371 _ => None,
372 }
373 }
374
375 fn key_state(&self) -> KeyState {
376 self.wm_key_state.into()
377 }
378}
379
380impl HoldEvent<KeyboardEvent> {
381 /// Returns the duration between the key press and release.
382 pub fn duration(&self) -> Duration {
383 self.release.duration_since(&self.press)
384 }
385}
386
387/// Represents the type of Windows message related to a keyboard event.
388///
389/// See also: [Keyboard Input](https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-messages)
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub enum WmKeyState {
392 /// [`WM_KEYDOWN`](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keydown)
393 KeyDown,
394 /// [`WM_KEYUP`](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keyup)
395 KeyUp,
396 /// [`WM_SYSKEYDOWN`](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-syskeydown)
397 SysKeyDown,
398 /// [`WM_SYSKEYUP`](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-syskeyup)
399 SysKeyUp,
400}
401
402impl WmKeyState {
403 /// Converts a `w_param` to the corresponding `WmKeyState`, if applicable.
404 ///
405 /// Returns `None` if the value does not match a known key message.
406 fn from_w_param(w_param: WPARAM) -> Option<WmKeyState> {
407 if w_param.0 == WM_KEYDOWN as usize {
408 Some(WmKeyState::KeyDown)
409 } else if w_param.0 == WM_KEYUP as usize {
410 Some(WmKeyState::KeyUp)
411 } else if w_param.0 == WM_SYSKEYDOWN as usize {
412 Some(WmKeyState::SysKeyDown)
413 } else if w_param.0 == WM_SYSKEYUP as usize {
414 Some(WmKeyState::SysKeyUp)
415 } else {
416 None
417 }
418 }
419
420 /// Returns `true` if this is a key-down event.
421 pub fn is_key_down(&self) -> bool {
422 KeyState::from(*self) == KeyState::Down
423 }
424
425 /// Returns `true` if this is a key-up event.
426 pub fn is_key_up(&self) -> bool {
427 !self.is_key_down()
428 }
429}
430
431impl From<WmKeyState> for KeyState {
432 fn from(value: WmKeyState) -> Self {
433 match value {
434 WmKeyState::KeyDown | WmKeyState::SysKeyDown => KeyState::Down,
435 WmKeyState::KeyUp | WmKeyState::SysKeyUp => KeyState::Up,
436 }
437 }
438}