Skip to main content

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}