Skip to main content

kbd_winit/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Winit key event conversions for `kbd`.
4//!
5//! This crate converts winit's key events into `kbd`'s unified types so
6//! that window-focused key events (from winit) and global hotkey events
7//! (from [`kbd-global`](https://docs.rs/kbd-global)) can feed into the
8//! same [`Dispatcher`](kbd::dispatcher::Dispatcher). This is useful in
9//! applications where you want both in-window shortcuts and system-wide
10//! hotkeys handled through a single hotkey registry.
11//!
12//! Both winit and `kbd` derive from the W3C UI Events specification, so
13//! the variant names are nearly identical — the mapping is mechanical.
14//! Winit wraps key codes in a [`PhysicalKey`] type and tracks modifiers
15//! separately via `WindowEvent::ModifiersChanged`. The [`WinitEventExt`]
16//! trait therefore takes `ModifiersState` as a parameter.
17//!
18//! # Extension traits
19//!
20//! - [`WinitKeyExt`] — converts a winit [`PhysicalKey`] or [`KeyCode`] to
21//!   a [`kbd::key::Key`].
22//! - [`WinitModifiersExt`] — converts winit [`ModifiersState`] to a
23//!   `Vec<Modifier>`.
24//! - [`WinitEventExt`] — converts a winit [`KeyEvent`] plus
25//!   [`ModifiersState`] to a [`kbd::hotkey::Hotkey`].
26//!
27//! # Key mapping
28//!
29//! | winit | kbd | Notes |
30//! |---|---|---|
31//! | `KeyCode::KeyA` – `KeyCode::KeyZ` | [`Key::A`] – [`Key::Z`] | Letters |
32//! | `KeyCode::Digit0` – `KeyCode::Digit9` | [`Key::DIGIT0`] – [`Key::DIGIT9`] | Digits |
33//! | `KeyCode::F1` – `KeyCode::F35` | [`Key::F1`] – [`Key::F35`] | Function keys |
34//! | `KeyCode::Numpad0` – `KeyCode::Numpad9` | [`Key::NUMPAD0`] – [`Key::NUMPAD9`] | Numpad |
35//! | `KeyCode::Enter`, `KeyCode::Escape`, … | [`Key::ENTER`], [`Key::ESCAPE`], … | Navigation / editing |
36//! | `KeyCode::ControlLeft`, … | [`Key::CONTROL_LEFT`], … | Modifier keys as triggers |
37//! | `KeyCode::SuperLeft` / `KeyCode::Meta` | [`Key::META_LEFT`] | winit's Super = kbd's Meta |
38//! | `KeyCode::SuperRight` | [`Key::META_RIGHT`] | |
39//! | `KeyCode::MediaPlayPause`, … | [`Key::MEDIA_PLAY_PAUSE`], … | Media keys |
40//! | `KeyCode::BrowserBack`, … | [`Key::BROWSER_BACK`], … | Browser keys |
41//! | `KeyCode::Convert`, `KeyCode::Lang1`, … | [`Key::CONVERT`], [`Key::LANG1`], … | CJK / international |
42//! | `PhysicalKey::Unidentified(_)` | `None` | No mapping possible |
43//!
44//! # Modifier mapping
45//!
46//! | winit | kbd |
47//! |---|---|
48//! | `CONTROL` | [`Modifier::Ctrl`] |
49//! | `SHIFT` | [`Modifier::Shift`] |
50//! | `ALT` | [`Modifier::Alt`] |
51//! | `SUPER` | [`Modifier::Super`] |
52//!
53//! # Usage
54//!
55//! Inside winit's event loop, use [`WinitEventExt`] to convert key
56//! events directly:
57//!
58//! ```no_run
59//! use kbd::prelude::*;
60//! use kbd_winit::WinitEventExt;
61//! use winit::application::ApplicationHandler;
62//! use winit::event::WindowEvent;
63//! use winit::event_loop::{ActiveEventLoop, EventLoop};
64//! use winit::keyboard::ModifiersState;
65//! use winit::window::{Window, WindowId};
66//!
67//! struct App {
68//!     modifiers: ModifiersState,
69//!     window: Option<Window>,
70//! }
71//!
72//! impl ApplicationHandler for App {
73//!     fn resumed(&mut self, event_loop: &ActiveEventLoop) {
74//!         if self.window.is_none() {
75//!             let attrs = Window::default_attributes().with_title("kbd-winit example");
76//!             self.window = Some(event_loop.create_window(attrs).unwrap());
77//!         }
78//!     }
79//!
80//!     fn window_event(&mut self, _event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
81//!         match event {
82//!             WindowEvent::ModifiersChanged(mods) => {
83//!                 self.modifiers = mods.state();
84//!             }
85//!             WindowEvent::KeyboardInput { event, .. } => {
86//!                 if let Some(hotkey) = event.to_hotkey(self.modifiers) {
87//!                     println!("{hotkey}");
88//!                 }
89//!             }
90//!             _ => {}
91//!         }
92//!     }
93//! }
94//!
95//! let event_loop = EventLoop::new().unwrap();
96//! let mut app = App { modifiers: ModifiersState::empty(), window: None };
97//! event_loop.run_app(&mut app).unwrap();
98//! ```
99//!
100//! The individual conversion traits can also be used separately:
101//!
102//! ```
103//! use kbd::prelude::*;
104//! use kbd_winit::{WinitKeyExt, WinitModifiersExt};
105//! use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey};
106//!
107//! // KeyCode conversion
108//! let key = KeyCode::KeyA.to_key();
109//! assert_eq!(key, Some(Key::A));
110//!
111//! // PhysicalKey conversion
112//! let key = PhysicalKey::Code(KeyCode::KeyA).to_key();
113//! assert_eq!(key, Some(Key::A));
114//!
115//! // Modifier conversion
116//! let mods = ModifiersState::CONTROL.to_modifiers();
117//! assert_eq!(mods, vec![Modifier::Ctrl]);
118//! ```
119
120use kbd::hotkey::Hotkey;
121use kbd::hotkey::Modifier;
122use kbd::key::Key;
123use winit::event::KeyEvent;
124use winit::keyboard::KeyCode;
125use winit::keyboard::ModifiersState;
126use winit::keyboard::PhysicalKey;
127
128mod private {
129    pub trait Sealed {}
130    impl Sealed for winit::keyboard::KeyCode {}
131    impl Sealed for winit::keyboard::PhysicalKey {}
132    impl Sealed for winit::keyboard::ModifiersState {}
133    impl Sealed for winit::event::KeyEvent {}
134}
135
136/// Convert a winit key type to a `kbd` [`Key`].
137///
138/// Returns `None` for keys that have no `kbd` equivalent (e.g.,
139/// `Unidentified`, keys beyond F24, TV remote keys).
140///
141/// This trait is sealed and cannot be implemented outside this crate.
142pub trait WinitKeyExt: private::Sealed {
143    /// Convert this winit key to a `kbd` [`Key`], or `None` if unmappable.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use kbd::prelude::*;
149    /// use kbd_winit::WinitKeyExt;
150    /// use winit::keyboard::{KeyCode, PhysicalKey};
151    ///
152    /// assert_eq!(KeyCode::KeyA.to_key(), Some(Key::A));
153    /// assert_eq!(PhysicalKey::Code(KeyCode::Enter).to_key(), Some(Key::ENTER));
154    /// assert_eq!(KeyCode::SuperLeft.to_key(), Some(Key::META_LEFT));
155    /// ```
156    #[must_use]
157    fn to_key(&self) -> Option<Key>;
158}
159
160impl WinitKeyExt for KeyCode {
161    #[allow(clippy::too_many_lines)]
162    fn to_key(&self) -> Option<Key> {
163        match self {
164            // Letters
165            KeyCode::KeyA => Some(Key::A),
166            KeyCode::KeyB => Some(Key::B),
167            KeyCode::KeyC => Some(Key::C),
168            KeyCode::KeyD => Some(Key::D),
169            KeyCode::KeyE => Some(Key::E),
170            KeyCode::KeyF => Some(Key::F),
171            KeyCode::KeyG => Some(Key::G),
172            KeyCode::KeyH => Some(Key::H),
173            KeyCode::KeyI => Some(Key::I),
174            KeyCode::KeyJ => Some(Key::J),
175            KeyCode::KeyK => Some(Key::K),
176            KeyCode::KeyL => Some(Key::L),
177            KeyCode::KeyM => Some(Key::M),
178            KeyCode::KeyN => Some(Key::N),
179            KeyCode::KeyO => Some(Key::O),
180            KeyCode::KeyP => Some(Key::P),
181            KeyCode::KeyQ => Some(Key::Q),
182            KeyCode::KeyR => Some(Key::R),
183            KeyCode::KeyS => Some(Key::S),
184            KeyCode::KeyT => Some(Key::T),
185            KeyCode::KeyU => Some(Key::U),
186            KeyCode::KeyV => Some(Key::V),
187            KeyCode::KeyW => Some(Key::W),
188            KeyCode::KeyX => Some(Key::X),
189            KeyCode::KeyY => Some(Key::Y),
190            KeyCode::KeyZ => Some(Key::Z),
191
192            // Digits
193            KeyCode::Digit0 => Some(Key::DIGIT0),
194            KeyCode::Digit1 => Some(Key::DIGIT1),
195            KeyCode::Digit2 => Some(Key::DIGIT2),
196            KeyCode::Digit3 => Some(Key::DIGIT3),
197            KeyCode::Digit4 => Some(Key::DIGIT4),
198            KeyCode::Digit5 => Some(Key::DIGIT5),
199            KeyCode::Digit6 => Some(Key::DIGIT6),
200            KeyCode::Digit7 => Some(Key::DIGIT7),
201            KeyCode::Digit8 => Some(Key::DIGIT8),
202            KeyCode::Digit9 => Some(Key::DIGIT9),
203
204            // Function keys
205            KeyCode::F1 => Some(Key::F1),
206            KeyCode::F2 => Some(Key::F2),
207            KeyCode::F3 => Some(Key::F3),
208            KeyCode::F4 => Some(Key::F4),
209            KeyCode::F5 => Some(Key::F5),
210            KeyCode::F6 => Some(Key::F6),
211            KeyCode::F7 => Some(Key::F7),
212            KeyCode::F8 => Some(Key::F8),
213            KeyCode::F9 => Some(Key::F9),
214            KeyCode::F10 => Some(Key::F10),
215            KeyCode::F11 => Some(Key::F11),
216            KeyCode::F12 => Some(Key::F12),
217            KeyCode::F13 => Some(Key::F13),
218            KeyCode::F14 => Some(Key::F14),
219            KeyCode::F15 => Some(Key::F15),
220            KeyCode::F16 => Some(Key::F16),
221            KeyCode::F17 => Some(Key::F17),
222            KeyCode::F18 => Some(Key::F18),
223            KeyCode::F19 => Some(Key::F19),
224            KeyCode::F20 => Some(Key::F20),
225            KeyCode::F21 => Some(Key::F21),
226            KeyCode::F22 => Some(Key::F22),
227            KeyCode::F23 => Some(Key::F23),
228            KeyCode::F24 => Some(Key::F24),
229            KeyCode::F25 => Some(Key::F25),
230            KeyCode::F26 => Some(Key::F26),
231            KeyCode::F27 => Some(Key::F27),
232            KeyCode::F28 => Some(Key::F28),
233            KeyCode::F29 => Some(Key::F29),
234            KeyCode::F30 => Some(Key::F30),
235            KeyCode::F31 => Some(Key::F31),
236            KeyCode::F32 => Some(Key::F32),
237            KeyCode::F33 => Some(Key::F33),
238            KeyCode::F34 => Some(Key::F34),
239            KeyCode::F35 => Some(Key::F35),
240
241            // Navigation and editing
242            KeyCode::Enter => Some(Key::ENTER),
243            KeyCode::Escape => Some(Key::ESCAPE),
244            KeyCode::Space => Some(Key::SPACE),
245            KeyCode::Tab => Some(Key::TAB),
246            KeyCode::Delete => Some(Key::DELETE),
247            KeyCode::Backspace => Some(Key::BACKSPACE),
248            KeyCode::Insert => Some(Key::INSERT),
249            KeyCode::CapsLock => Some(Key::CAPS_LOCK),
250            KeyCode::Home => Some(Key::HOME),
251            KeyCode::End => Some(Key::END),
252            KeyCode::PageUp => Some(Key::PAGE_UP),
253            KeyCode::PageDown => Some(Key::PAGE_DOWN),
254            KeyCode::ArrowUp => Some(Key::ARROW_UP),
255            KeyCode::ArrowDown => Some(Key::ARROW_DOWN),
256            KeyCode::ArrowLeft => Some(Key::ARROW_LEFT),
257            KeyCode::ArrowRight => Some(Key::ARROW_RIGHT),
258
259            // Punctuation
260            KeyCode::Minus => Some(Key::MINUS),
261            KeyCode::Equal => Some(Key::EQUAL),
262            KeyCode::BracketLeft => Some(Key::BRACKET_LEFT),
263            KeyCode::BracketRight => Some(Key::BRACKET_RIGHT),
264            KeyCode::Backslash => Some(Key::BACKSLASH),
265            KeyCode::Semicolon => Some(Key::SEMICOLON),
266            KeyCode::Quote => Some(Key::QUOTE),
267            KeyCode::Backquote => Some(Key::BACKQUOTE),
268            KeyCode::Comma => Some(Key::COMMA),
269            KeyCode::Period => Some(Key::PERIOD),
270            KeyCode::Slash => Some(Key::SLASH),
271
272            // Numpad
273            KeyCode::Numpad0 => Some(Key::NUMPAD0),
274            KeyCode::Numpad1 => Some(Key::NUMPAD1),
275            KeyCode::Numpad2 => Some(Key::NUMPAD2),
276            KeyCode::Numpad3 => Some(Key::NUMPAD3),
277            KeyCode::Numpad4 => Some(Key::NUMPAD4),
278            KeyCode::Numpad5 => Some(Key::NUMPAD5),
279            KeyCode::Numpad6 => Some(Key::NUMPAD6),
280            KeyCode::Numpad7 => Some(Key::NUMPAD7),
281            KeyCode::Numpad8 => Some(Key::NUMPAD8),
282            KeyCode::Numpad9 => Some(Key::NUMPAD9),
283            KeyCode::NumpadDecimal => Some(Key::NUMPAD_DECIMAL),
284            KeyCode::NumpadAdd => Some(Key::NUMPAD_ADD),
285            KeyCode::NumpadSubtract => Some(Key::NUMPAD_SUBTRACT),
286            KeyCode::NumpadMultiply => Some(Key::NUMPAD_MULTIPLY),
287            KeyCode::NumpadDivide => Some(Key::NUMPAD_DIVIDE),
288            KeyCode::NumpadEnter => Some(Key::NUMPAD_ENTER),
289            KeyCode::NumpadEqual => Some(Key::NUMPAD_EQUAL),
290            KeyCode::NumpadComma => Some(Key::NUMPAD_COMMA),
291            KeyCode::NumpadBackspace => Some(Key::NUMPAD_BACKSPACE),
292            KeyCode::NumpadClear => Some(Key::NUMPAD_CLEAR),
293            KeyCode::NumpadClearEntry => Some(Key::NUMPAD_CLEAR_ENTRY),
294            KeyCode::NumpadHash => Some(Key::NUMPAD_HASH),
295            KeyCode::NumpadMemoryAdd => Some(Key::NUMPAD_MEMORY_ADD),
296            KeyCode::NumpadMemoryClear => Some(Key::NUMPAD_MEMORY_CLEAR),
297            KeyCode::NumpadMemoryRecall => Some(Key::NUMPAD_MEMORY_RECALL),
298            KeyCode::NumpadMemoryStore => Some(Key::NUMPAD_MEMORY_STORE),
299            KeyCode::NumpadMemorySubtract => Some(Key::NUMPAD_MEMORY_SUBTRACT),
300            KeyCode::NumpadParenLeft => Some(Key::NUMPAD_PAREN_LEFT),
301            KeyCode::NumpadParenRight => Some(Key::NUMPAD_PAREN_RIGHT),
302            KeyCode::NumpadStar => Some(Key::NUMPAD_STAR),
303
304            // Modifiers — winit uses SuperLeft/SuperRight where W3C uses MetaLeft/MetaRight.
305            // Meta is winit's legacy alias for the Super key (no left/right distinction).
306            KeyCode::ControlLeft => Some(Key::CONTROL_LEFT),
307            KeyCode::ControlRight => Some(Key::CONTROL_RIGHT),
308            KeyCode::ShiftLeft => Some(Key::SHIFT_LEFT),
309            KeyCode::ShiftRight => Some(Key::SHIFT_RIGHT),
310            KeyCode::AltLeft => Some(Key::ALT_LEFT),
311            KeyCode::AltRight => Some(Key::ALT_RIGHT),
312            KeyCode::SuperLeft | KeyCode::Meta => Some(Key::META_LEFT),
313            KeyCode::SuperRight => Some(Key::META_RIGHT),
314
315            // Media keys
316            KeyCode::AudioVolumeUp => Some(Key::AUDIO_VOLUME_UP),
317            KeyCode::AudioVolumeDown => Some(Key::AUDIO_VOLUME_DOWN),
318            KeyCode::AudioVolumeMute => Some(Key::AUDIO_VOLUME_MUTE),
319            KeyCode::MediaPlayPause => Some(Key::MEDIA_PLAY_PAUSE),
320            KeyCode::MediaStop => Some(Key::MEDIA_STOP),
321            KeyCode::MediaTrackNext => Some(Key::MEDIA_TRACK_NEXT),
322            KeyCode::MediaTrackPrevious => Some(Key::MEDIA_TRACK_PREVIOUS),
323            KeyCode::MediaSelect => Some(Key::MEDIA_SELECT),
324
325            // Browser keys
326            KeyCode::BrowserBack => Some(Key::BROWSER_BACK),
327            KeyCode::BrowserFavorites => Some(Key::BROWSER_FAVORITES),
328            KeyCode::BrowserForward => Some(Key::BROWSER_FORWARD),
329            KeyCode::BrowserHome => Some(Key::BROWSER_HOME),
330            KeyCode::BrowserRefresh => Some(Key::BROWSER_REFRESH),
331            KeyCode::BrowserSearch => Some(Key::BROWSER_SEARCH),
332            KeyCode::BrowserStop => Some(Key::BROWSER_STOP),
333
334            // System keys
335            KeyCode::PrintScreen => Some(Key::PRINT_SCREEN),
336            KeyCode::ScrollLock => Some(Key::SCROLL_LOCK),
337            KeyCode::Pause => Some(Key::PAUSE),
338            KeyCode::NumLock => Some(Key::NUM_LOCK),
339            KeyCode::ContextMenu => Some(Key::CONTEXT_MENU),
340            KeyCode::Power => Some(Key::POWER),
341            KeyCode::Sleep => Some(Key::SLEEP),
342            KeyCode::WakeUp => Some(Key::WAKE_UP),
343            KeyCode::Eject => Some(Key::EJECT),
344
345            // Clipboard / editing keys
346            KeyCode::Copy => Some(Key::COPY),
347            KeyCode::Cut => Some(Key::CUT),
348            KeyCode::Paste => Some(Key::PASTE),
349            KeyCode::Undo => Some(Key::UNDO),
350            KeyCode::Find => Some(Key::FIND),
351            KeyCode::Help => Some(Key::HELP),
352            KeyCode::Open => Some(Key::OPEN),
353            KeyCode::Select => Some(Key::SELECT),
354            KeyCode::Again => Some(Key::AGAIN),
355            KeyCode::Props => Some(Key::PROPS),
356            KeyCode::Abort => Some(Key::ABORT),
357            KeyCode::Resume => Some(Key::RESUME),
358            KeyCode::Suspend => Some(Key::SUSPEND),
359
360            // Fn and legacy
361            KeyCode::Fn => Some(Key::FN),
362            KeyCode::FnLock => Some(Key::FN_LOCK),
363            KeyCode::Hyper => Some(Key::HYPER),
364            KeyCode::Turbo => Some(Key::TURBO),
365
366            // CJK / international
367            KeyCode::Convert => Some(Key::CONVERT),
368            KeyCode::NonConvert => Some(Key::NON_CONVERT),
369            KeyCode::KanaMode => Some(Key::KANA_MODE),
370            KeyCode::Hiragana => Some(Key::HIRAGANA),
371            KeyCode::Katakana => Some(Key::KATAKANA),
372            KeyCode::Lang1 => Some(Key::LANG1),
373            KeyCode::Lang2 => Some(Key::LANG2),
374            KeyCode::Lang3 => Some(Key::LANG3),
375            KeyCode::Lang4 => Some(Key::LANG4),
376            KeyCode::Lang5 => Some(Key::LANG5),
377            KeyCode::IntlBackslash => Some(Key::INTL_BACKSLASH),
378            KeyCode::IntlRo => Some(Key::INTL_RO),
379            KeyCode::IntlYen => Some(Key::INTL_YEN),
380
381            // App launch keys
382            KeyCode::LaunchApp1 => Some(Key::LAUNCH_APP1),
383            KeyCode::LaunchApp2 => Some(Key::LAUNCH_APP2),
384            KeyCode::LaunchMail => Some(Key::LAUNCH_MAIL),
385
386            _ => None,
387        }
388    }
389}
390
391impl WinitKeyExt for PhysicalKey {
392    fn to_key(&self) -> Option<Key> {
393        match self {
394            PhysicalKey::Code(code) => code.to_key(),
395            PhysicalKey::Unidentified(_) => None,
396        }
397    }
398}
399
400/// Convert winit [`ModifiersState`] bitflags to a sorted `Vec<Modifier>`.
401///
402/// This trait is sealed and cannot be implemented outside this crate.
403pub trait WinitModifiersExt: private::Sealed {
404    /// Convert these winit modifier flags to a `Vec<Modifier>`.
405    ///
406    /// # Examples
407    ///
408    /// ```
409    /// use kbd::prelude::*;
410    /// use kbd_winit::WinitModifiersExt;
411    /// use winit::keyboard::ModifiersState;
412    ///
413    /// let mods = (ModifiersState::CONTROL | ModifiersState::SHIFT).to_modifiers();
414    /// assert_eq!(mods, vec![Modifier::Ctrl, Modifier::Shift]);
415    /// ```
416    #[must_use]
417    fn to_modifiers(&self) -> Vec<Modifier>;
418}
419
420impl WinitModifiersExt for ModifiersState {
421    fn to_modifiers(&self) -> Vec<Modifier> {
422        Modifier::collect_active([
423            (self.control_key(), Modifier::Ctrl),
424            (self.shift_key(), Modifier::Shift),
425            (self.alt_key(), Modifier::Alt),
426            (self.super_key(), Modifier::Super),
427        ])
428    }
429}
430
431/// Build a [`Hotkey`] from a physical key and modifier state.
432///
433/// This is the same conversion performed by [`WinitEventExt::to_hotkey`],
434/// exposed as a standalone function for use when you have a raw
435/// [`PhysicalKey`] and [`ModifiersState`] but no [`KeyEvent`]. If you do
436/// have a `KeyEvent`, prefer calling [`event.to_hotkey(modifiers)`](WinitEventExt::to_hotkey)
437/// instead.
438///
439/// When the key is itself a modifier (e.g., `ControlLeft`), the
440/// corresponding modifier flag is stripped — winit includes the pressed
441/// modifier key in its own state, but `kbd` treats the key as the
442/// trigger, not as a modifier of itself.
443///
444/// Returns `None` if the physical key has no `kbd` equivalent.
445#[must_use]
446pub fn winit_key_to_hotkey(physical_key: PhysicalKey, modifiers: ModifiersState) -> Option<Hotkey> {
447    let key = physical_key.to_key()?;
448
449    let mut mods = modifiers.to_modifiers();
450    if let Some(self_modifier) = Modifier::from_key(key) {
451        mods.retain(|m| *m != self_modifier);
452    }
453
454    Some(Hotkey::with_modifiers(key, mods))
455}
456
457/// Convert a winit [`KeyEvent`] (plus modifier state) to a `kbd`
458/// [`Hotkey`].
459///
460/// Winit's `KeyEvent` does not include modifier state — modifiers are
461/// tracked via `WindowEvent::ModifiersChanged`. Pass the current
462/// `ModifiersState` alongside the event.
463///
464/// Returns `None` if the physical key has no `kbd` equivalent.
465///
466/// When the key is itself a modifier (e.g., `ControlLeft`), the
467/// corresponding modifier flag is stripped from the modifiers — winit
468/// includes the pressed modifier key in its own state, but `kbd`
469/// treats the key as the trigger, not as a modifier of itself.
470///
471/// This trait is sealed and cannot be implemented outside this crate.
472pub trait WinitEventExt: private::Sealed {
473    /// Convert this key event to a [`Hotkey`], or `None` if the key is unmappable.
474    ///
475    /// Pass the current [`ModifiersState`] from
476    /// `WindowEvent::ModifiersChanged`.
477    ///
478    /// # Examples
479    ///
480    /// Winit's [`KeyEvent`] cannot be easily constructed in doctests,
481    /// so this trait is used inside winit's event loop where the
482    /// framework provides the event. For standalone conversion use
483    /// [`winit_key_to_hotkey`]:
484    ///
485    /// ```
486    /// use kbd::prelude::*;
487    /// use kbd_winit::winit_key_to_hotkey;
488    /// use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey};
489    ///
490    /// let hotkey = winit_key_to_hotkey(
491    ///     PhysicalKey::Code(KeyCode::KeyS),
492    ///     ModifiersState::CONTROL,
493    /// );
494    /// assert_eq!(
495    ///     hotkey,
496    ///     Some(Hotkey::new(Key::S).modifier(Modifier::Ctrl)),
497    /// );
498    /// ```
499    #[must_use]
500    fn to_hotkey(&self, modifiers: ModifiersState) -> Option<Hotkey>;
501}
502
503impl WinitEventExt for KeyEvent {
504    fn to_hotkey(&self, modifiers: ModifiersState) -> Option<Hotkey> {
505        winit_key_to_hotkey(self.physical_key, modifiers)
506    }
507}
508
509#[cfg(test)]
510mod tests {
511    use kbd::hotkey::Hotkey;
512    use kbd::hotkey::Modifier;
513    use kbd::key::Key;
514    use winit::keyboard::KeyCode;
515    use winit::keyboard::ModifiersState;
516    use winit::keyboard::NativeKeyCode;
517    use winit::keyboard::PhysicalKey;
518
519    use super::*;
520
521    // WinitKeyExt — KeyCode
522
523    #[test]
524    fn keycode_letters() {
525        assert_eq!(KeyCode::KeyA.to_key(), Some(Key::A));
526        assert_eq!(KeyCode::KeyZ.to_key(), Some(Key::Z));
527    }
528
529    #[test]
530    fn keycode_digits() {
531        assert_eq!(KeyCode::Digit0.to_key(), Some(Key::DIGIT0));
532        assert_eq!(KeyCode::Digit9.to_key(), Some(Key::DIGIT9));
533    }
534
535    #[test]
536    fn keycode_function_keys() {
537        assert_eq!(KeyCode::F1.to_key(), Some(Key::F1));
538        assert_eq!(KeyCode::F12.to_key(), Some(Key::F12));
539        assert_eq!(KeyCode::F24.to_key(), Some(Key::F24));
540        assert_eq!(KeyCode::F25.to_key(), Some(Key::F25));
541    }
542
543    #[test]
544    fn keycode_navigation() {
545        assert_eq!(KeyCode::Enter.to_key(), Some(Key::ENTER));
546        assert_eq!(KeyCode::Escape.to_key(), Some(Key::ESCAPE));
547        assert_eq!(KeyCode::Backspace.to_key(), Some(Key::BACKSPACE));
548        assert_eq!(KeyCode::Tab.to_key(), Some(Key::TAB));
549        assert_eq!(KeyCode::Space.to_key(), Some(Key::SPACE));
550        assert_eq!(KeyCode::Delete.to_key(), Some(Key::DELETE));
551        assert_eq!(KeyCode::Insert.to_key(), Some(Key::INSERT));
552        assert_eq!(KeyCode::Home.to_key(), Some(Key::HOME));
553        assert_eq!(KeyCode::End.to_key(), Some(Key::END));
554        assert_eq!(KeyCode::PageUp.to_key(), Some(Key::PAGE_UP));
555        assert_eq!(KeyCode::PageDown.to_key(), Some(Key::PAGE_DOWN));
556        assert_eq!(KeyCode::ArrowUp.to_key(), Some(Key::ARROW_UP));
557        assert_eq!(KeyCode::ArrowDown.to_key(), Some(Key::ARROW_DOWN));
558        assert_eq!(KeyCode::ArrowLeft.to_key(), Some(Key::ARROW_LEFT));
559        assert_eq!(KeyCode::ArrowRight.to_key(), Some(Key::ARROW_RIGHT));
560    }
561
562    #[test]
563    fn keycode_modifiers() {
564        assert_eq!(KeyCode::ControlLeft.to_key(), Some(Key::CONTROL_LEFT));
565        assert_eq!(KeyCode::ControlRight.to_key(), Some(Key::CONTROL_RIGHT));
566        assert_eq!(KeyCode::ShiftLeft.to_key(), Some(Key::SHIFT_LEFT));
567        assert_eq!(KeyCode::ShiftRight.to_key(), Some(Key::SHIFT_RIGHT));
568        assert_eq!(KeyCode::AltLeft.to_key(), Some(Key::ALT_LEFT));
569        assert_eq!(KeyCode::AltRight.to_key(), Some(Key::ALT_RIGHT));
570        // winit's SuperLeft/Right → kbd's MetaLeft/Right
571        assert_eq!(KeyCode::SuperLeft.to_key(), Some(Key::META_LEFT));
572        assert_eq!(KeyCode::SuperRight.to_key(), Some(Key::META_RIGHT));
573        // winit's legacy Meta (no left/right) → defaults to MetaLeft
574        assert_eq!(KeyCode::Meta.to_key(), Some(Key::META_LEFT));
575    }
576
577    #[test]
578    fn keycode_punctuation() {
579        assert_eq!(KeyCode::Minus.to_key(), Some(Key::MINUS));
580        assert_eq!(KeyCode::Equal.to_key(), Some(Key::EQUAL));
581        assert_eq!(KeyCode::BracketLeft.to_key(), Some(Key::BRACKET_LEFT));
582        assert_eq!(KeyCode::BracketRight.to_key(), Some(Key::BRACKET_RIGHT));
583        assert_eq!(KeyCode::Backslash.to_key(), Some(Key::BACKSLASH));
584        assert_eq!(KeyCode::Semicolon.to_key(), Some(Key::SEMICOLON));
585        assert_eq!(KeyCode::Quote.to_key(), Some(Key::QUOTE));
586        assert_eq!(KeyCode::Backquote.to_key(), Some(Key::BACKQUOTE));
587        assert_eq!(KeyCode::Comma.to_key(), Some(Key::COMMA));
588        assert_eq!(KeyCode::Period.to_key(), Some(Key::PERIOD));
589        assert_eq!(KeyCode::Slash.to_key(), Some(Key::SLASH));
590    }
591
592    #[test]
593    fn keycode_numpad() {
594        assert_eq!(KeyCode::Numpad0.to_key(), Some(Key::NUMPAD0));
595        assert_eq!(KeyCode::Numpad9.to_key(), Some(Key::NUMPAD9));
596        assert_eq!(KeyCode::NumpadDecimal.to_key(), Some(Key::NUMPAD_DECIMAL));
597        assert_eq!(KeyCode::NumpadAdd.to_key(), Some(Key::NUMPAD_ADD));
598        assert_eq!(KeyCode::NumpadSubtract.to_key(), Some(Key::NUMPAD_SUBTRACT));
599        assert_eq!(KeyCode::NumpadMultiply.to_key(), Some(Key::NUMPAD_MULTIPLY));
600        assert_eq!(KeyCode::NumpadDivide.to_key(), Some(Key::NUMPAD_DIVIDE));
601        assert_eq!(KeyCode::NumpadEnter.to_key(), Some(Key::NUMPAD_ENTER));
602    }
603
604    #[test]
605    fn keycode_media() {
606        assert_eq!(
607            KeyCode::MediaPlayPause.to_key(),
608            Some(Key::MEDIA_PLAY_PAUSE)
609        );
610        assert_eq!(KeyCode::MediaStop.to_key(), Some(Key::MEDIA_STOP));
611        assert_eq!(
612            KeyCode::MediaTrackNext.to_key(),
613            Some(Key::MEDIA_TRACK_NEXT)
614        );
615        assert_eq!(
616            KeyCode::MediaTrackPrevious.to_key(),
617            Some(Key::MEDIA_TRACK_PREVIOUS)
618        );
619        assert_eq!(KeyCode::AudioVolumeUp.to_key(), Some(Key::AUDIO_VOLUME_UP));
620        assert_eq!(
621            KeyCode::AudioVolumeDown.to_key(),
622            Some(Key::AUDIO_VOLUME_DOWN)
623        );
624        assert_eq!(
625            KeyCode::AudioVolumeMute.to_key(),
626            Some(Key::AUDIO_VOLUME_MUTE)
627        );
628    }
629
630    #[test]
631    fn keycode_system() {
632        assert_eq!(KeyCode::PrintScreen.to_key(), Some(Key::PRINT_SCREEN));
633        assert_eq!(KeyCode::ScrollLock.to_key(), Some(Key::SCROLL_LOCK));
634        assert_eq!(KeyCode::Pause.to_key(), Some(Key::PAUSE));
635        assert_eq!(KeyCode::NumLock.to_key(), Some(Key::NUM_LOCK));
636        assert_eq!(KeyCode::ContextMenu.to_key(), Some(Key::CONTEXT_MENU));
637        assert_eq!(KeyCode::Power.to_key(), Some(Key::POWER));
638    }
639
640    #[test]
641    fn keycode_extended_keys() {
642        assert_eq!(KeyCode::F35.to_key(), Some(Key::F35));
643        assert_eq!(KeyCode::BrowserBack.to_key(), Some(Key::BROWSER_BACK));
644        assert_eq!(KeyCode::Copy.to_key(), Some(Key::COPY));
645        assert_eq!(KeyCode::Sleep.to_key(), Some(Key::SLEEP));
646        assert_eq!(KeyCode::IntlBackslash.to_key(), Some(Key::INTL_BACKSLASH));
647        assert_eq!(KeyCode::NumpadEqual.to_key(), Some(Key::NUMPAD_EQUAL));
648        assert_eq!(KeyCode::Fn.to_key(), Some(Key::FN));
649        assert_eq!(KeyCode::LaunchMail.to_key(), Some(Key::LAUNCH_MAIL));
650        assert_eq!(KeyCode::Convert.to_key(), Some(Key::CONVERT));
651        assert_eq!(KeyCode::Lang1.to_key(), Some(Key::LANG1));
652    }
653
654    // WinitKeyExt — PhysicalKey
655
656    #[test]
657    fn physical_key_code_to_key() {
658        let physical = PhysicalKey::Code(KeyCode::KeyA);
659        assert_eq!(physical.to_key(), Some(Key::A));
660    }
661
662    #[test]
663    fn physical_key_unidentified_returns_none() {
664        let physical = PhysicalKey::Unidentified(NativeKeyCode::Unidentified);
665        assert_eq!(physical.to_key(), None);
666    }
667
668    // WinitModifiersExt
669
670    #[test]
671    fn empty_modifiers() {
672        assert_eq!(
673            ModifiersState::empty().to_modifiers(),
674            Vec::<Modifier>::new()
675        );
676    }
677
678    #[test]
679    fn single_modifiers() {
680        assert_eq!(ModifiersState::CONTROL.to_modifiers(), vec![Modifier::Ctrl]);
681        assert_eq!(ModifiersState::SHIFT.to_modifiers(), vec![Modifier::Shift]);
682        assert_eq!(ModifiersState::ALT.to_modifiers(), vec![Modifier::Alt]);
683        assert_eq!(ModifiersState::SUPER.to_modifiers(), vec![Modifier::Super]);
684    }
685
686    #[test]
687    fn combined_modifiers() {
688        let mods = ModifiersState::CONTROL | ModifiersState::SHIFT;
689        assert_eq!(mods.to_modifiers(), vec![Modifier::Ctrl, Modifier::Shift]);
690    }
691
692    #[test]
693    fn all_modifiers() {
694        let mods = ModifiersState::CONTROL
695            | ModifiersState::SHIFT
696            | ModifiersState::ALT
697            | ModifiersState::SUPER;
698        assert_eq!(
699            mods.to_modifiers(),
700            vec![
701                Modifier::Ctrl,
702                Modifier::Shift,
703                Modifier::Alt,
704                Modifier::Super,
705            ]
706        );
707    }
708
709    // winit_key_to_hotkey (exercises the same logic as WinitEventExt)
710
711    #[test]
712    fn simple_key_to_hotkey() {
713        let hotkey = winit_key_to_hotkey(PhysicalKey::Code(KeyCode::KeyC), ModifiersState::empty());
714        assert_eq!(hotkey, Some(Hotkey::new(Key::C)));
715    }
716
717    #[test]
718    fn key_with_ctrl_to_hotkey() {
719        let hotkey = winit_key_to_hotkey(PhysicalKey::Code(KeyCode::KeyC), ModifiersState::CONTROL);
720        assert_eq!(hotkey, Some(Hotkey::new(Key::C).modifier(Modifier::Ctrl)));
721    }
722
723    #[test]
724    fn key_with_multiple_modifiers_to_hotkey() {
725        let mods = ModifiersState::CONTROL | ModifiersState::SHIFT;
726        let hotkey = winit_key_to_hotkey(PhysicalKey::Code(KeyCode::KeyA), mods);
727        assert_eq!(
728            hotkey,
729            Some(
730                Hotkey::new(Key::A)
731                    .modifier(Modifier::Ctrl)
732                    .modifier(Modifier::Shift)
733            )
734        );
735    }
736
737    #[test]
738    fn unidentified_key_to_hotkey_returns_none() {
739        let hotkey = winit_key_to_hotkey(
740            PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
741            ModifiersState::empty(),
742        );
743        assert_eq!(hotkey, None);
744    }
745
746    #[test]
747    fn modifier_key_strips_self() {
748        // Pressing ShiftLeft — winit reports SHIFT in ModifiersState.
749        // Hotkey should be just "ShiftLeft", not "Shift+ShiftLeft".
750        let hotkey =
751            winit_key_to_hotkey(PhysicalKey::Code(KeyCode::ShiftLeft), ModifiersState::SHIFT);
752        assert_eq!(hotkey, Some(Hotkey::new(Key::SHIFT_LEFT)));
753    }
754
755    #[test]
756    fn modifier_key_keeps_other_modifiers() {
757        // Pressing ControlLeft while Shift is already held
758        let mods = ModifiersState::SHIFT | ModifiersState::CONTROL;
759        let hotkey = winit_key_to_hotkey(PhysicalKey::Code(KeyCode::ControlLeft), mods);
760        assert_eq!(
761            hotkey,
762            Some(Hotkey::new(Key::CONTROL_LEFT).modifier(Modifier::Shift))
763        );
764    }
765
766    #[test]
767    fn ctrl_shift_f5_to_hotkey() {
768        let mods = ModifiersState::CONTROL | ModifiersState::SHIFT;
769        let hotkey = winit_key_to_hotkey(PhysicalKey::Code(KeyCode::F5), mods);
770        assert_eq!(
771            hotkey,
772            Some(
773                Hotkey::new(Key::F5)
774                    .modifier(Modifier::Ctrl)
775                    .modifier(Modifier::Shift)
776            )
777        );
778    }
779
780    #[test]
781    fn space_to_hotkey() {
782        let hotkey =
783            winit_key_to_hotkey(PhysicalKey::Code(KeyCode::Space), ModifiersState::empty());
784        assert_eq!(hotkey, Some(Hotkey::new(Key::SPACE)));
785    }
786
787    #[test]
788    fn super_key_strips_self() {
789        // Pressing SuperLeft — winit reports SUPER in ModifiersState.
790        let hotkey =
791            winit_key_to_hotkey(PhysicalKey::Code(KeyCode::SuperLeft), ModifiersState::SUPER);
792        assert_eq!(hotkey, Some(Hotkey::new(Key::META_LEFT)));
793    }
794}