Skip to main content

kbd_tao/
lib.rs

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