win_hotkeys/
hook.rs

1//! Provides a low-level implementation of a keyboard hook
2//! using the Windows API. It captures keyboard events such as key presses
3//! and releases, tracks the state of modifier keys, and communicates events
4//! via channels to the rest of the application.
5
6use crate::state::KeyboardState;
7use crossbeam_channel::{unbounded, Receiver, RecvError, Sender};
8use std::sync::{Mutex, OnceLock, RwLock};
9use std::thread;
10use std::time::Duration;
11use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM};
12use windows::Win32::UI::Input::KeyboardAndMouse::{
13    SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYBD_EVENT_FLAGS, KEYEVENTF_KEYUP,
14    VIRTUAL_KEY,
15};
16use windows::Win32::UI::WindowsAndMessaging::{
17    CallNextHookEx, DispatchMessageW, GetMessageW, SetWindowsHookExW, TranslateMessage,
18    UnhookWindowsHookEx, KBDLLHOOKSTRUCT, MSG, WH_KEYBOARD_LL, WM_KEYDOWN, WM_SYSKEYDOWN,
19};
20
21/// Timeout for blocking key events, measured in milliseconds.
22const TIMEOUT: Duration = Duration::from_millis(250);
23
24/// Unassigned Virtual Key code used to suppress Windows Key events.
25const SILENT_KEY: VIRTUAL_KEY = VIRTUAL_KEY(0xE8);
26
27/// Channel sender used by hook proc to send keyboard events.
28pub static HOOK_EVENT_TX: RwLock<Option<Sender<KeyboardEvent>>> = RwLock::new(None);
29
30/// Channel receiver used to notify the hook on how to handle keyboard events.
31static HOOK_RESPONSE_RX: RwLock<Option<Receiver<KeyAction>>> = RwLock::new(None);
32
33/// Channel receiver used to notify hook proc to exit.
34static HOOK_CONTROL_RX: RwLock<Option<Receiver<ControlFlow>>> = RwLock::new(None);
35
36/// Bitmask object representing all pressed keys on keyboard.
37static KEYBOARD_STATE: OnceLock<Mutex<KeyboardState>> = OnceLock::new();
38
39/// Enum representing how to handle keypress.
40#[derive(Debug, Copy, Clone, PartialEq)]
41pub enum KeyAction {
42    Allow,
43    Block,
44    Replace,
45}
46
47/// Enum representing control flow signals for the hook thread.
48#[derive(Debug, Copy, Clone, PartialEq)]
49enum ControlFlow {
50    Exit,
51}
52
53/// Enum representing keyboard events.
54#[derive(Debug, Copy, Clone, PartialEq)]
55pub enum KeyboardEvent {
56    KeyDown {
57        /// The virtual key code of the key.
58        vk_code: u16,
59        /// The updated keyboard state due to this event.
60        keyboard_state: KeyboardState,
61    },
62    KeyUp {
63        /// The virtual key code of the key.
64        key_code: u16,
65        /// The updated keyboard state due to this event.
66        keyboard_state: KeyboardState,
67    },
68}
69
70/// Struct representing the keyboard hook interface
71pub struct KeyboardHook {
72    ke_rx: Receiver<KeyboardEvent>,
73    action_tx: Sender<KeyAction>,
74    cf_tx: Sender<ControlFlow>,
75}
76
77impl KeyboardHook {
78    /// Receives a keyboard event from the hook.
79    pub fn recv(&self) -> Result<KeyboardEvent, RecvError> {
80        self.ke_rx.recv()
81    }
82
83    /// Blocks or unblocks the propagation of the key event.
84    pub fn key_action(&self, value: KeyAction) {
85        self.action_tx.send(value).unwrap();
86    }
87
88    /// Signals the hook thread to exit.
89    pub fn exit(&self) {
90        self.cf_tx.send(ControlFlow::Exit).unwrap();
91    }
92}
93
94/// Starts the keyboard hook in a separate thread.
95///
96/// # Returns
97/// A `KeyboardHook` instance to interact with the hook (e.g., receiving events, blocking keys).
98pub fn start() -> KeyboardHook {
99    // Create channels
100    let (ke_tx, ke_rx) = unbounded();
101    let (action_tx, action_rx) = unbounded();
102    let (cf_tx, cf_rx) = unbounded();
103
104    // Set static channel variables
105    let mut hook_event_tx = HOOK_EVENT_TX.write().unwrap();
106    *hook_event_tx = Some(ke_tx);
107    let mut hook_response_rx = HOOK_RESPONSE_RX.write().unwrap();
108    *hook_response_rx = Some(action_rx);
109    let mut hook_control_tx = HOOK_CONTROL_RX.write().unwrap();
110    *hook_control_tx = Some(cf_rx);
111
112    // Create/clear keyboard state
113    let mutex = KEYBOARD_STATE.get_or_init(|| Mutex::new(KeyboardState::new()));
114    let mut state = mutex.lock().unwrap();
115    state.clear();
116
117    unsafe {
118        thread::spawn(|| {
119            let hhook = SetWindowsHookExW(WH_KEYBOARD_LL, Some(hook_proc), None, 0).unwrap();
120            if let Some(cf_rx) = &*HOOK_CONTROL_RX.read().unwrap() {
121                loop {
122                    let mut msg = MSG::default();
123                    if GetMessageW(&mut msg, None, 0, 0).into() {
124                        let _ = TranslateMessage(&msg);
125                        DispatchMessageW(&msg);
126                    }
127
128                    if let Ok(cf) = cf_rx.try_recv() {
129                        match cf {
130                            ControlFlow::Exit => {
131                                let mut hook_event_tx = HOOK_EVENT_TX.write().unwrap();
132                                *hook_event_tx = None;
133                                let mut hook_response_rx = HOOK_RESPONSE_RX.write().unwrap();
134                                *hook_response_rx = None;
135                                let mut hook_control_tx = HOOK_CONTROL_RX.write().unwrap();
136                                *hook_control_tx = None;
137                                UnhookWindowsHookEx(hhook).unwrap();
138                                break;
139                            }
140                        }
141                    }
142                }
143            }
144        });
145    }
146
147    KeyboardHook {
148        ke_rx,
149        action_tx,
150        cf_tx,
151    }
152}
153
154/// Updates global keyboard state for given virtual key code.
155fn update_keyboard_state(vk_code: u16) {
156    let mutex = KEYBOARD_STATE.get();
157    let mut keyboard = mutex.unwrap().lock().unwrap();
158    keyboard.sync();
159    keyboard.keydown(vk_code);
160}
161
162/// Sends a keydown and keyup event for Unassigned Virtual Key 0xE8.
163unsafe fn send_silent_key() {
164    let inputs = [
165        INPUT {
166            r#type: INPUT_KEYBOARD,
167            Anonymous: INPUT_0 {
168                ki: KEYBDINPUT {
169                    wVk: SILENT_KEY,
170                    wScan: 0,
171                    dwFlags: KEYBD_EVENT_FLAGS(0),
172                    time: 0,
173                    dwExtraInfo: 0,
174                },
175            },
176        },
177        INPUT {
178            r#type: INPUT_KEYBOARD,
179            Anonymous: INPUT_0 {
180                ki: KEYBDINPUT {
181                    wVk: SILENT_KEY,
182                    wScan: 0,
183                    dwFlags: KEYEVENTF_KEYUP,
184                    time: 0,
185                    dwExtraInfo: 0,
186                },
187            },
188        },
189    ];
190    SendInput(&inputs, size_of::<INPUT>() as i32);
191}
192
193/// Hook procedure for handling keyboard events.
194unsafe extern "system" fn hook_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
195    if code >= 0 {
196        let event_guard = HOOK_EVENT_TX.read().unwrap();
197        let event_tx = event_guard.as_ref().unwrap();
198        let response_guard = HOOK_RESPONSE_RX.read().unwrap();
199        let response_rx = response_guard.as_ref().unwrap();
200
201        let event_type = wparam.0 as u32;
202        let vk_code = (*(lparam.0 as *const KBDLLHOOKSTRUCT)).vkCode as u16;
203        if vk_code == SILENT_KEY.0 {
204            return CallNextHookEx(None, code, wparam, lparam);
205        }
206
207        match event_type {
208            // We only care about key down events
209            WM_KEYDOWN | WM_SYSKEYDOWN => {
210                // Clear the actions channel of any previous action
211                while let Ok(_) = response_rx.try_recv() {}
212                update_keyboard_state(vk_code);
213                event_tx
214                    .send(KeyboardEvent::KeyDown {
215                        vk_code,
216                        keyboard_state: *KEYBOARD_STATE.get().unwrap().lock().unwrap(),
217                    })
218                    .unwrap();
219
220                // Wait for response on how to handle event
221                if let Ok(action) = response_rx.recv_timeout(TIMEOUT) {
222                    match action {
223                        KeyAction::Block => {
224                            return LRESULT(1);
225                        }
226                        KeyAction::Replace => {
227                            send_silent_key();
228                            return LRESULT(1);
229                        }
230                        KeyAction::Allow => {}
231                    }
232                }
233            }
234            _ => {}
235        };
236    }
237    CallNextHookEx(None, code, wparam, lparam)
238}