winapi_easy/
input.rs

1//! Keyboard and hotkeys.
2
3use std::ffi::c_void;
4use std::{
5    io,
6    mem,
7};
8
9use num_enum::{
10    FromPrimitive,
11    IntoPrimitive,
12};
13use windows::Win32::UI::Input::KeyboardAndMouse::{
14    GetAsyncKeyState,
15    GetKeyState,
16    INPUT,
17    INPUT_0,
18    INPUT_KEYBOARD,
19    INPUT_MOUSE,
20    KEYBDINPUT,
21    KEYEVENTF_KEYUP,
22    MOUSEEVENTF_LEFTDOWN,
23    MOUSEEVENTF_LEFTUP,
24    MOUSEEVENTF_MIDDLEDOWN,
25    MOUSEEVENTF_MIDDLEUP,
26    MOUSEEVENTF_RIGHTDOWN,
27    MOUSEEVENTF_RIGHTUP,
28    MOUSEEVENTF_WHEEL,
29    MOUSEEVENTF_XDOWN,
30    MOUSEEVENTF_XUP,
31    MOUSEINPUT,
32    SendInput,
33    VIRTUAL_KEY,
34    VK_0,
35    VK_1,
36    VK_2,
37    VK_3,
38    VK_4,
39    VK_5,
40    VK_6,
41    VK_7,
42    VK_8,
43    VK_9,
44    VK_A,
45    VK_ADD,
46    VK_APPS,
47    VK_B,
48    VK_BACK,
49    VK_C,
50    VK_CAPITAL,
51    VK_D,
52    VK_DECIMAL,
53    VK_DELETE,
54    VK_DIVIDE,
55    VK_DOWN,
56    VK_E,
57    VK_END,
58    VK_ESCAPE,
59    VK_F,
60    VK_F1,
61    VK_F2,
62    VK_F3,
63    VK_F4,
64    VK_F5,
65    VK_F6,
66    VK_F7,
67    VK_F8,
68    VK_F9,
69    VK_F10,
70    VK_F11,
71    VK_F12,
72    VK_G,
73    VK_H,
74    VK_HOME,
75    VK_I,
76    VK_INSERT,
77    VK_J,
78    VK_K,
79    VK_L,
80    VK_LBUTTON,
81    VK_LCONTROL,
82    VK_LEFT,
83    VK_LMENU,
84    VK_LSHIFT,
85    VK_LWIN,
86    VK_M,
87    VK_MBUTTON,
88    VK_MEDIA_NEXT_TRACK,
89    VK_MEDIA_PLAY_PAUSE,
90    VK_MEDIA_PREV_TRACK,
91    VK_MEDIA_STOP,
92    VK_MULTIPLY,
93    VK_N,
94    VK_NEXT,
95    VK_NUMLOCK,
96    VK_NUMPAD0,
97    VK_NUMPAD1,
98    VK_NUMPAD2,
99    VK_NUMPAD3,
100    VK_NUMPAD4,
101    VK_NUMPAD5,
102    VK_NUMPAD6,
103    VK_NUMPAD7,
104    VK_NUMPAD8,
105    VK_NUMPAD9,
106    VK_O,
107    VK_OEM_1,
108    VK_OEM_2,
109    VK_OEM_3,
110    VK_OEM_4,
111    VK_OEM_5,
112    VK_OEM_6,
113    VK_OEM_7,
114    VK_OEM_8,
115    VK_OEM_102,
116    VK_OEM_COMMA,
117    VK_OEM_MINUS,
118    VK_OEM_PERIOD,
119    VK_OEM_PLUS,
120    VK_P,
121    VK_PAUSE,
122    VK_PRIOR,
123    VK_Q,
124    VK_R,
125    VK_RBUTTON,
126    VK_RCONTROL,
127    VK_RETURN,
128    VK_RIGHT,
129    VK_RMENU,
130    VK_RSHIFT,
131    VK_RWIN,
132    VK_S,
133    VK_SCROLL,
134    VK_SNAPSHOT,
135    VK_SPACE,
136    VK_SUBTRACT,
137    VK_T,
138    VK_TAB,
139    VK_U,
140    VK_UP,
141    VK_V,
142    VK_VOLUME_DOWN,
143    VK_VOLUME_MUTE,
144    VK_VOLUME_UP,
145    VK_W,
146    VK_X,
147    VK_XBUTTON1,
148    VK_XBUTTON2,
149    VK_Y,
150    VK_Z,
151};
152use windows::Win32::UI::WindowsAndMessaging::{
153    SPI_GETMOUSESPEED,
154    SPI_SETMOUSESPEED,
155    SPIF_SENDCHANGE,
156    SPIF_UPDATEINIFILE,
157    SystemParametersInfoW,
158    WHEEL_DELTA,
159    XBUTTON1,
160    XBUTTON2,
161};
162
163#[expect(clippy::wildcard_imports)]
164use self::private::*;
165use crate::internal::ReturnValue;
166
167pub mod hotkey;
168
169/// A [`VirtualKey`] or a [`MouseButton`].
170pub trait GenericKey: GenericKeyInternal {
171    fn is_pressed(self) -> io::Result<bool> {
172        let result = unsafe {
173            GetAsyncKeyState(self.into())
174                .if_null_to_error(|| io::ErrorKind::PermissionDenied.into())?
175        };
176        Ok(result.cast_unsigned() >> (u16::BITS - 1) == 1)
177    }
178
179    /// Globally sends a 'press' event (without a corresponding 'release').
180    ///
181    /// This can conflict with existing user key presses. Use [`Self::is_pressed`] to avoid this.
182    fn press(self) -> io::Result<()> {
183        self.send_input(false)
184    }
185
186    /// Globally sends a 'release' event.
187    fn release(self) -> io::Result<()> {
188        self.send_input(true)
189    }
190
191    /// Globally sends a key (or mouse button) combination as if the user had performed it.
192    ///
193    /// This will cause a 'press' event for each key in the list (in the given order),
194    /// followed by a sequence of 'release' events (in the inverse order).
195    fn send_combination(keys: &[Self]) -> io::Result<()> {
196        let raw_input_pairs: Vec<_> = keys
197            .iter()
198            .copied()
199            .map(|key: Self| {
200                let raw_input = key.get_press_raw_input(false);
201                let raw_input_release = key.get_press_raw_input(true);
202                (raw_input, raw_input_release)
203            })
204            .collect();
205        let raw_inputs: Vec<_> = raw_input_pairs
206            .iter()
207            .map(|x| x.0)
208            .chain(raw_input_pairs.iter().rev().map(|x| x.1))
209            .collect();
210        send_raw_inputs(raw_inputs.as_slice())
211    }
212}
213
214// No generic impl to generate better docs
215impl GenericKey for VirtualKey {}
216impl GenericKey for MouseButton {}
217
218mod private {
219    #[expect(clippy::wildcard_imports)]
220    use super::*;
221
222    pub trait GenericKeyInternal: Copy + Into<i32> {
223        fn send_input(self, is_release: bool) -> io::Result<()> {
224            let raw_input = self.get_press_raw_input(is_release);
225            send_raw_inputs(&[raw_input])
226        }
227        fn get_press_raw_input(self, is_release: bool) -> INPUT;
228    }
229
230    impl GenericKeyInternal for VirtualKey {
231        fn get_press_raw_input(self, is_release: bool) -> INPUT {
232            let raw_key: u16 = self.into();
233            let raw_keybdinput = KEYBDINPUT {
234                wVk: VIRTUAL_KEY(raw_key),
235                dwFlags: if is_release {
236                    KEYEVENTF_KEYUP
237                } else {
238                    Default::default()
239                },
240                ..Default::default()
241            };
242            INPUT {
243                r#type: INPUT_KEYBOARD,
244                Anonymous: INPUT_0 { ki: raw_keybdinput },
245            }
246        }
247    }
248
249    impl GenericKeyInternal for MouseButton {
250        fn get_press_raw_input(self, is_release: bool) -> INPUT {
251            let (flags, mouse_data) = match (self, is_release) {
252                (MouseButton::Left, false) => (MOUSEEVENTF_LEFTDOWN, 0),
253                (MouseButton::Left, true) => (MOUSEEVENTF_LEFTUP, 0),
254                (MouseButton::Right, false) => (MOUSEEVENTF_RIGHTDOWN, 0),
255                (MouseButton::Right, true) => (MOUSEEVENTF_RIGHTUP, 0),
256                (MouseButton::Middle, false) => (MOUSEEVENTF_MIDDLEDOWN, 0),
257                (MouseButton::Middle, true) => (MOUSEEVENTF_MIDDLEUP, 0),
258                (MouseButton::X1, false) => (MOUSEEVENTF_XDOWN, XBUTTON1),
259                (MouseButton::X1, true) => (MOUSEEVENTF_XUP, XBUTTON1),
260                (MouseButton::X2, false) => (MOUSEEVENTF_XDOWN, XBUTTON2),
261                (MouseButton::X2, true) => (MOUSEEVENTF_XUP, XBUTTON2),
262            };
263            INPUT {
264                r#type: INPUT_MOUSE,
265                Anonymous: INPUT_0 {
266                    mi: MOUSEINPUT {
267                        mouseData: mouse_data.into(),
268                        dwFlags: flags,
269                        ..Default::default()
270                    },
271                },
272            }
273        }
274    }
275}
276
277/// Key with a virtual key code, usable for hotkeys.
278///
279/// # Related docs
280///
281/// [Microsoft docs for virtual key codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes)
282#[derive(FromPrimitive, IntoPrimitive, Copy, Clone, Eq, PartialEq, Hash, Debug)]
283#[repr(u16)]
284pub enum VirtualKey {
285    Backspace = VK_BACK.0,
286    Tab = VK_TAB.0,
287    Return = VK_RETURN.0,
288    Pause = VK_PAUSE.0,
289    CapsLock = VK_CAPITAL.0,
290    Esc = VK_ESCAPE.0,
291    Space = VK_SPACE.0,
292    PgUp = VK_PRIOR.0,
293    PgDown = VK_NEXT.0,
294    End = VK_END.0,
295    Home = VK_HOME.0,
296    LeftArrow = VK_LEFT.0,
297    UpArrow = VK_UP.0,
298    RightArrow = VK_RIGHT.0,
299    DownArrow = VK_DOWN.0,
300    PrintScreen = VK_SNAPSHOT.0,
301    Insert = VK_INSERT.0,
302    Delete = VK_DELETE.0,
303    Number0 = VK_0.0,
304    Number1 = VK_1.0,
305    Number2 = VK_2.0,
306    Number3 = VK_3.0,
307    Number4 = VK_4.0,
308    Number5 = VK_5.0,
309    Number6 = VK_6.0,
310    Number7 = VK_7.0,
311    Number8 = VK_8.0,
312    Number9 = VK_9.0,
313    A = VK_A.0,
314    B = VK_B.0,
315    C = VK_C.0,
316    D = VK_D.0,
317    E = VK_E.0,
318    F = VK_F.0,
319    G = VK_G.0,
320    H = VK_H.0,
321    I = VK_I.0,
322    J = VK_J.0,
323    K = VK_K.0,
324    L = VK_L.0,
325    M = VK_M.0,
326    N = VK_N.0,
327    O = VK_O.0,
328    P = VK_P.0,
329    Q = VK_Q.0,
330    R = VK_R.0,
331    S = VK_S.0,
332    T = VK_T.0,
333    U = VK_U.0,
334    V = VK_V.0,
335    W = VK_W.0,
336    X = VK_X.0,
337    Y = VK_Y.0,
338    Z = VK_Z.0,
339    LeftWindows = VK_LWIN.0,
340    RightWindows = VK_RWIN.0,
341    Menu = VK_APPS.0,
342    Numpad0 = VK_NUMPAD0.0,
343    Numpad1 = VK_NUMPAD1.0,
344    Numpad2 = VK_NUMPAD2.0,
345    Numpad3 = VK_NUMPAD3.0,
346    Numpad4 = VK_NUMPAD4.0,
347    Numpad5 = VK_NUMPAD5.0,
348    Numpad6 = VK_NUMPAD6.0,
349    Numpad7 = VK_NUMPAD7.0,
350    Numpad8 = VK_NUMPAD8.0,
351    Numpad9 = VK_NUMPAD9.0,
352    Multiply = VK_MULTIPLY.0,
353    Add = VK_ADD.0,
354    Subtract = VK_SUBTRACT.0,
355    Decimal = VK_DECIMAL.0,
356    Divide = VK_DIVIDE.0,
357    F1 = VK_F1.0,
358    F2 = VK_F2.0,
359    F3 = VK_F3.0,
360    F4 = VK_F4.0,
361    F5 = VK_F5.0,
362    F6 = VK_F6.0,
363    F7 = VK_F7.0,
364    F8 = VK_F8.0,
365    F9 = VK_F9.0,
366    F10 = VK_F10.0,
367    F11 = VK_F11.0,
368    F12 = VK_F12.0,
369    NumLock = VK_NUMLOCK.0,
370    ScrollLock = VK_SCROLL.0,
371    LeftShift = VK_LSHIFT.0,
372    RightShift = VK_RSHIFT.0,
373    LeftCtrl = VK_LCONTROL.0,
374    RightCtrl = VK_RCONTROL.0,
375    LeftAlt = VK_LMENU.0,
376    RightAlt = VK_RMENU.0,
377    VolumeMute = VK_VOLUME_MUTE.0,
378    VolumeDown = VK_VOLUME_DOWN.0,
379    VolumeUp = VK_VOLUME_UP.0,
380    MediaNextTrack = VK_MEDIA_NEXT_TRACK.0,
381    MediaPrevTrack = VK_MEDIA_PREV_TRACK.0,
382    MediaStop = VK_MEDIA_STOP.0,
383    MediaPlayPause = VK_MEDIA_PLAY_PAUSE.0,
384    /// Used for miscellaneous characters; it can vary by keyboard.
385    ///
386    /// * For the US standard keyboard, the ';:' key
387    /// * For the German keyboard, the 'ü' key
388    Oem1 = VK_OEM_1.0,
389    /// For any country/region, the '+' key
390    OemPlus = VK_OEM_PLUS.0,
391    /// For any country/region, the ',' key
392    OemComma = VK_OEM_COMMA.0,
393    /// For any country/region, the '-' key
394    OemMinus = VK_OEM_MINUS.0,
395    /// For any country/region, the '.' key
396    OemPeriod = VK_OEM_PERIOD.0,
397    /// Used for miscellaneous characters; it can vary by keyboard.
398    ///
399    /// * For the US standard keyboard, the '/?' key
400    /// * For the German keyboard, the '#'' key
401    Oem2 = VK_OEM_2.0,
402    /// Used for miscellaneous characters; it can vary by keyboard.
403    ///
404    /// * For the US standard keyboard, the '\`~' key
405    /// * For the German keyboard, the 'ö' key
406    Oem3 = VK_OEM_3.0,
407    /// Used for miscellaneous characters; it can vary by keyboard.
408    ///
409    /// * For the US standard keyboard, the '[{' key
410    /// * For the German keyboard, the 'ß?' key
411    Oem4 = VK_OEM_4.0,
412    /// Used for miscellaneous characters; it can vary by keyboard.
413    ///
414    /// * For the US standard keyboard, the '\|' key besides 'Enter'
415    /// * For the German keyboard, the '^°' key
416    Oem5 = VK_OEM_5.0,
417    /// Used for miscellaneous characters; it can vary by keyboard.
418    ///
419    /// * For the US standard keyboard, the ']}' key
420    /// * For the German keyboard, the '´\`' key
421    Oem6 = VK_OEM_6.0,
422    /// Used for miscellaneous characters; it can vary by keyboard.
423    ///
424    /// * For the US standard keyboard, the 'single-quote/double-quote' key
425    /// * For the German keyboard, the 'ä' key
426    Oem7 = VK_OEM_7.0,
427    Oem8 = VK_OEM_8.0,
428    /// Used for miscellaneous characters; it can vary by keyboard.
429    ///
430    /// * For the US standard keyboard, the '\|' key besides the 'z' key
431    /// * For the German keyboard, the '<>' key
432    Oem102 = VK_OEM_102.0,
433    /// Other virtual key code.
434    #[num_enum(catch_all)]
435    Other(u16),
436}
437
438impl VirtualKey {
439    /// Returns true if the key has lock functionality (e.g. Caps Lock) and the lock is toggled.
440    pub fn is_lock_toggled(self) -> bool {
441        let result = unsafe { GetKeyState(self.into()).cast_unsigned() };
442        result & 1 == 1
443    }
444}
445
446impl From<VirtualKey> for u32 {
447    fn from(value: VirtualKey) -> Self {
448        Self::from(u16::from(value))
449    }
450}
451
452impl From<VirtualKey> for i32 {
453    fn from(value: VirtualKey) -> Self {
454        u16::from(value).into()
455    }
456}
457
458fn send_raw_inputs(raw_inputs: &[INPUT]) -> io::Result<()> {
459    let raw_input_size = mem::size_of::<INPUT>()
460        .try_into()
461        .expect("Struct size conversion failed");
462
463    let expected_sent_size =
464        u32::try_from(raw_inputs.len()).expect("Inputs length conversion failed");
465    unsafe {
466        SendInput(raw_inputs, raw_input_size)
467            .if_null_get_last_error()?
468            .if_not_eq_to_error(expected_sent_size, || {
469                io::Error::from(io::ErrorKind::Interrupted)
470            })?;
471    }
472    Ok(())
473}
474
475/// Mouse button.
476///
477/// Note that X-Buttons above #2 are only handled by the mouse driver.
478#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Hash, Debug)]
479#[repr(u16)]
480pub enum MouseButton {
481    Left = VK_LBUTTON.0,
482    Right = VK_RBUTTON.0,
483    Middle = VK_MBUTTON.0,
484    X1 = VK_XBUTTON1.0,
485    X2 = VK_XBUTTON2.0,
486}
487
488impl From<MouseButton> for i32 {
489    fn from(value: MouseButton) -> Self {
490        u16::from(value).into()
491    }
492}
493
494/// Mouse scroll wheel 'up' or 'down' event, possibly continuous.
495#[derive(Copy, Clone, Eq, PartialEq, Debug)]
496pub enum MouseScrollEvent {
497    /// One or more full up-scroll events.
498    ///
499    /// Equivalent to [`Self::Continuous`] with a multiple of [`Self::WHEEL_DELTA`].
500    Up(u16),
501    /// One or more full down-scroll events.
502    ///
503    /// Equivalent to [`Self::Continuous`] with a multiple of -[`Self::WHEEL_DELTA`].
504    Down(u16),
505    /// Continuous 'up' (positive value) or 'down' (negative value) scroll event.
506    ///
507    /// Values other than multiples of positive or negative [`Self::WHEEL_DELTA`] are used for mouses
508    /// with continuous scroll wheels.
509    Continuous(i16),
510}
511
512impl MouseScrollEvent {
513    #[expect(clippy::cast_possible_truncation)]
514    pub const WHEEL_DELTA: i16 = WHEEL_DELTA as _;
515
516    /// Globally sends a single scroll event.
517    pub fn send(self) -> io::Result<()> {
518        // Should never overflow due to data types
519        let mouse_data: i32 = match self {
520            MouseScrollEvent::Up(amount) => i32::from(Self::WHEEL_DELTA) * i32::from(amount),
521            MouseScrollEvent::Down(amount) => -i32::from(Self::WHEEL_DELTA) * i32::from(amount),
522            MouseScrollEvent::Continuous(delta) => i32::from(delta),
523        };
524        let raw_input = INPUT {
525            r#type: INPUT_MOUSE,
526            Anonymous: INPUT_0 {
527                mi: MOUSEINPUT {
528                    // bit-cast semantics necessary here because negative values should be allowed
529                    mouseData: mouse_data.cast_unsigned(),
530                    dwFlags: MOUSEEVENTF_WHEEL,
531                    ..Default::default()
532                },
533            },
534        };
535        send_raw_inputs(&[raw_input])
536    }
537
538    #[cfg(feature = "hooking")]
539    pub(crate) fn from_raw_movement(raw_movement: i16) -> Self {
540        if raw_movement % Self::WHEEL_DELTA != 0 {
541            MouseScrollEvent::Continuous(raw_movement)
542        } else if raw_movement > 0 {
543            MouseScrollEvent::Up((raw_movement / Self::WHEEL_DELTA).cast_unsigned())
544        } else {
545            MouseScrollEvent::Down((-raw_movement / Self::WHEEL_DELTA).cast_unsigned())
546        }
547    }
548}
549
550/// Returns the global mouse speed.
551pub fn get_mouse_speed() -> io::Result<u32> {
552    let mut speed: u32 = 0;
553    unsafe {
554        SystemParametersInfoW(
555            SPI_GETMOUSESPEED,
556            0,
557            Some((&raw mut speed).cast::<c_void>()),
558            Default::default(),
559        )?;
560    }
561    Ok(speed)
562}
563
564/// Globally sets the mouse speed.
565///
566/// Valid values are `1` to `20` inclusive. The change can be persisted between login sessions.
567pub fn set_mouse_speed(speed: u32, persist: bool) -> io::Result<()> {
568    let flags = if persist {
569        SPIF_UPDATEINIFILE | SPIF_SENDCHANGE
570    } else {
571        SPIF_SENDCHANGE
572    };
573    unsafe {
574        SystemParametersInfoW(
575            SPI_SETMOUSESPEED,
576            0,
577            Some(std::ptr::with_exposed_provenance_mut(
578                usize::try_from(speed).unwrap_or_else(|_| unreachable!()),
579            )),
580            flags,
581        )?;
582    }
583    Ok(())
584}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589
590    #[test]
591    fn check_get_mouse_speed() -> io::Result<()> {
592        let speed = get_mouse_speed()?;
593        dbg!(speed);
594        assert!((1..=20).contains(&speed));
595        Ok(())
596    }
597}