Skip to main content

winit_core/
keyboard.rs

1//! Types related to the keyboard.
2
3use bitflags::bitflags;
4pub use keyboard_types::{Code as KeyCode, Location as KeyLocation, NamedKey};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7pub use smol_str::SmolStr;
8
9/// Contains the platform-native physical key identifier
10///
11/// The exact values vary from platform to platform (which is part of why this is a per-platform
12/// enum), but the values are primarily tied to the key's physical location on the keyboard.
13///
14/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native
15/// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we
16/// haven't mapped for you yet, this lets you use use [`KeyCode`] to:
17///
18/// - Correctly match key press and release events.
19/// - On non-Web platforms, support assigning keybinds to virtually any key through a UI.
20#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[non_exhaustive]
23pub enum NativeKeyCode {
24    Unidentified,
25    /// An Android "scancode".
26    Android(u32),
27    /// A macOS "scancode".
28    MacOS(u16),
29    /// A Windows "scancode".
30    Windows(u16),
31    /// An XKB "keycode".
32    Xkb(u32),
33}
34
35impl std::fmt::Debug for NativeKeyCode {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb};
38        let mut debug_tuple;
39        match self {
40            Unidentified => {
41                debug_tuple = f.debug_tuple("Unidentified");
42            },
43            Android(code) => {
44                debug_tuple = f.debug_tuple("Android");
45                debug_tuple.field(&format_args!("0x{code:04X}"));
46            },
47            MacOS(code) => {
48                debug_tuple = f.debug_tuple("MacOS");
49                debug_tuple.field(&format_args!("0x{code:04X}"));
50            },
51            Windows(code) => {
52                debug_tuple = f.debug_tuple("Windows");
53                debug_tuple.field(&format_args!("0x{code:04X}"));
54            },
55            Xkb(code) => {
56                debug_tuple = f.debug_tuple("Xkb");
57                debug_tuple.field(&format_args!("0x{code:04X}"));
58            },
59        }
60        debug_tuple.finish()
61    }
62}
63
64/// Contains the platform-native logical key identifier
65///
66/// Exactly what that means differs from platform to platform, but the values are to some degree
67/// tied to the currently active keyboard layout. The same key on the same keyboard may also report
68/// different values on different platforms, which is one of the reasons this is a per-platform
69/// enum.
70///
71/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
72/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
73/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
74#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
75#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
76#[non_exhaustive]
77pub enum NativeKey {
78    Unidentified,
79    /// An Android "keycode", which is similar to a "virtual-key code" on Windows.
80    Android(u32),
81    /// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or
82    /// "virtual-key" codes in macOS, so we report the scancode instead.
83    MacOS(u16),
84    /// A Windows "virtual-key code".
85    Windows(u16),
86    /// An XKB "keysym".
87    Xkb(u32),
88    /// A "key value string".
89    Web(SmolStr),
90}
91
92impl std::fmt::Debug for NativeKey {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb};
95        let mut debug_tuple;
96        match self {
97            Unidentified => {
98                debug_tuple = f.debug_tuple("Unidentified");
99            },
100            Android(code) => {
101                debug_tuple = f.debug_tuple("Android");
102                debug_tuple.field(&format_args!("0x{code:04X}"));
103            },
104            MacOS(code) => {
105                debug_tuple = f.debug_tuple("MacOS");
106                debug_tuple.field(&format_args!("0x{code:04X}"));
107            },
108            Windows(code) => {
109                debug_tuple = f.debug_tuple("Windows");
110                debug_tuple.field(&format_args!("0x{code:04X}"));
111            },
112            Xkb(code) => {
113                debug_tuple = f.debug_tuple("Xkb");
114                debug_tuple.field(&format_args!("0x{code:04X}"));
115            },
116            Web(code) => {
117                debug_tuple = f.debug_tuple("Web");
118                debug_tuple.field(code);
119            },
120        }
121        debug_tuple.finish()
122    }
123}
124
125impl From<NativeKeyCode> for NativeKey {
126    #[inline]
127    fn from(code: NativeKeyCode) -> Self {
128        match code {
129            NativeKeyCode::Unidentified => NativeKey::Unidentified,
130            NativeKeyCode::Android(x) => NativeKey::Android(x),
131            NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
132            NativeKeyCode::Windows(x) => NativeKey::Windows(x),
133            NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
134        }
135    }
136}
137
138impl PartialEq<NativeKey> for NativeKeyCode {
139    #[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
140    #[inline]
141    fn eq(&self, rhs: &NativeKey) -> bool {
142        NativeKey::from(*self) == *rhs
143    }
144}
145
146impl PartialEq<NativeKeyCode> for NativeKey {
147    #[inline]
148    fn eq(&self, rhs: &NativeKeyCode) -> bool {
149        rhs == self
150    }
151}
152
153/// Represents the location of a physical key.
154///
155/// Winit will not emit [`KeyCode::Unidentified`] when it cannot recognize the key, instead it will
156/// emit [`PhysicalKey::Unidentified`] with additional data about the key.
157#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
158#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
159#[allow(clippy::exhaustive_enums)]
160pub enum PhysicalKey {
161    /// A known key code
162    Code(KeyCode),
163    /// This variant is used when the key cannot be translated to a [`KeyCode`]
164    ///
165    /// The native keycode is provided (if available) so you're able to more reliably match
166    /// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
167    /// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
168    Unidentified(NativeKeyCode),
169}
170
171impl From<KeyCode> for PhysicalKey {
172    #[inline]
173    fn from(code: KeyCode) -> Self {
174        PhysicalKey::Code(code)
175    }
176}
177
178impl From<PhysicalKey> for KeyCode {
179    #[inline]
180    fn from(key: PhysicalKey) -> Self {
181        match key {
182            PhysicalKey::Code(code) => code,
183            PhysicalKey::Unidentified(_) => KeyCode::Unidentified,
184        }
185    }
186}
187
188impl From<NativeKeyCode> for PhysicalKey {
189    #[inline]
190    fn from(code: NativeKeyCode) -> Self {
191        PhysicalKey::Unidentified(code)
192    }
193}
194
195impl PartialEq<KeyCode> for PhysicalKey {
196    #[inline]
197    fn eq(&self, rhs: &KeyCode) -> bool {
198        match self {
199            PhysicalKey::Code(code) => code == rhs,
200            _ => false,
201        }
202    }
203}
204
205impl PartialEq<PhysicalKey> for KeyCode {
206    #[inline]
207    fn eq(&self, rhs: &PhysicalKey) -> bool {
208        rhs == self
209    }
210}
211
212impl PartialEq<NativeKeyCode> for PhysicalKey {
213    #[inline]
214    fn eq(&self, rhs: &NativeKeyCode) -> bool {
215        match self {
216            PhysicalKey::Unidentified(code) => code == rhs,
217            _ => false,
218        }
219    }
220}
221
222impl PartialEq<PhysicalKey> for NativeKeyCode {
223    #[inline]
224    fn eq(&self, rhs: &PhysicalKey) -> bool {
225        rhs == self
226    }
227}
228
229/// Key represents the meaning of a keypress.
230///
231/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
232/// additions:
233/// - All simple variants are wrapped under the `Named` variant
234/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
235/// - The `Dead` variant here, can specify the character which is inserted when pressing the
236///   dead-key twice.
237///
238/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
239#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241#[allow(clippy::exhaustive_enums)]
242pub enum Key<Str = SmolStr> {
243    /// A simple (unparameterised) action
244    Named(NamedKey),
245
246    /// A key string that corresponds to the character typed by the user, taking into account the
247    /// user’s current locale setting, and any system-level keyboard mapping overrides that are in
248    /// effect.
249    Character(Str),
250
251    /// This variant is used when the key cannot be translated to any other variant.
252    ///
253    /// The native key is provided (if available) in order to allow the user to specify keybindings
254    /// for keys which are not defined by this API, mainly through some sort of UI.
255    Unidentified(NativeKey),
256
257    /// Contains the text representation of the dead-key when available.
258    ///
259    /// ## Platform-specific
260    /// - **Web:** Always contains `None`
261    Dead(Option<char>),
262}
263
264impl From<NamedKey> for Key {
265    #[inline]
266    fn from(action: NamedKey) -> Self {
267        Key::Named(action)
268    }
269}
270
271impl From<NativeKey> for Key {
272    #[inline]
273    fn from(code: NativeKey) -> Self {
274        Key::Unidentified(code)
275    }
276}
277
278impl<Str> PartialEq<NamedKey> for Key<Str> {
279    #[inline]
280    fn eq(&self, rhs: &NamedKey) -> bool {
281        match self {
282            Key::Named(a) => a == rhs,
283            _ => false,
284        }
285    }
286}
287
288impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
289    #[inline]
290    fn eq(&self, rhs: &str) -> bool {
291        match self {
292            Key::Character(s) => s == rhs,
293            _ => false,
294        }
295    }
296}
297
298impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
299    #[inline]
300    fn eq(&self, rhs: &&str) -> bool {
301        self == *rhs
302    }
303}
304
305impl<Str> PartialEq<NativeKey> for Key<Str> {
306    #[inline]
307    fn eq(&self, rhs: &NativeKey) -> bool {
308        match self {
309            Key::Unidentified(code) => code == rhs,
310            _ => false,
311        }
312    }
313}
314
315impl<Str> PartialEq<Key<Str>> for NativeKey {
316    #[inline]
317    fn eq(&self, rhs: &Key<Str>) -> bool {
318        rhs == self
319    }
320}
321
322impl Key<SmolStr> {
323    /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
324    /// `Key`. All other variants remain unchanged.
325    pub fn as_ref(&self) -> Key<&str> {
326        match self {
327            Key::Named(a) => Key::Named(*a),
328            Key::Character(ch) => Key::Character(ch.as_str()),
329            Key::Dead(d) => Key::Dead(*d),
330            Key::Unidentified(u) => Key::Unidentified(u.clone()),
331        }
332    }
333}
334
335impl Key {
336    /// Convert a key to its approximate textual equivalent.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// # #[cfg(target_family = "wasm")]
342    /// # wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
343    /// # #[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
344    /// # fn main() {
345    /// use winit_core::keyboard::{Key, NamedKey};
346    ///
347    /// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
348    /// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
349    /// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
350    /// # }
351    /// ```
352    pub fn to_text(&self) -> Option<&str> {
353        match self {
354            Key::Named(action) => match action {
355                NamedKey::Enter => Some("\r"),
356                NamedKey::Backspace => Some("\x08"),
357                NamedKey::Tab => Some("\t"),
358                NamedKey::Escape => Some("\x1b"),
359                _ => None,
360            },
361            Key::Character(ch) => Some(ch.as_str()),
362            _ => None,
363        }
364    }
365}
366
367bitflags! {
368    /// Represents the current logical state of the keyboard modifiers
369    ///
370    /// Each flag represents a modifier and is set if this modifier is active.
371    ///
372    /// Note that the modifier key can be physically released with the modifier
373    /// still being marked as active, as in the case of sticky modifiers.
374    /// See [`ModifiersKeyState`] for more details on what "sticky" means.
375    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
376    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
377    pub struct ModifiersState: u32 {
378        /// The "shift" key.
379        const SHIFT = 0b100;
380        /// The "control" key.
381        const CONTROL = 0b100 << 3;
382        /// The "alt" key.
383        const ALT = 0b100 << 6;
384        /// This is the "windows" key on PC and "command" key on Mac.
385        const META = 0b100 << 9;
386        #[deprecated = "use META instead"]
387        const SUPER = Self::META.bits();
388    }
389}
390
391impl ModifiersState {
392    /// Returns whether the shift modifier is active.
393    pub fn shift_key(&self) -> bool {
394        self.intersects(Self::SHIFT)
395    }
396
397    /// Returns whether the control modifier is active.
398    pub fn control_key(&self) -> bool {
399        self.intersects(Self::CONTROL)
400    }
401
402    /// Returns whether the alt modifier is active.
403    pub fn alt_key(&self) -> bool {
404        self.intersects(Self::ALT)
405    }
406
407    /// Returns whether the meta modifier is active.
408    pub fn meta_key(&self) -> bool {
409        self.intersects(Self::META)
410    }
411}
412
413/// The logical state of the particular modifiers key.
414///
415/// NOTE: while the modifier can only be in a binary active/inactive state, it might be helpful to
416/// note the context re. how its state changes by physical key events.
417///
418/// `↓` / `↑` denote physical press/release[^1]:
419///
420/// | Type              | Activated           | Deactivated | Comment |
421/// | ----------------- | :-----------------: | :---------: | ------- |
422/// | __Regular__       | `↓`                 | `↑`         | Active while being held |
423/// | __Sticky__        | `↓`                 | `↓` unless lock is enabled<br>`↓`/`↑`[^2] __non__-sticky key | Temporarily "stuck"; other `Sticky` keys have no effect |
424/// | __Sticky Locked__ | `↓` <br>if `Sticky` | `↓`         | Similar to `Toggle`, but deactivating `↓` turns on `Regular` effect |
425/// | __Toggle__        | `↓`                 | `↓`         | `↑` from the activating `↓` has no effect |
426///
427/// `Sticky` effect avoids the need to press and hold multiple modifiers for a single shortcut and
428/// is usually a platform-wide option that affects modifiers _commonly_ used in shortcuts:
429/// <kbd>Shift</kbd>, <kbd>Control</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>.
430///
431/// `Toggle` type is typically a property of a modifier, for example, <kbd>Caps Lock</kbd>.
432///
433/// These active states are __not__ differentiated here.
434///
435/// [^1]: For virtual/on-screen keyboards physical press/release can be a mouse click or a finger tap or a voice command.
436/// [^2]: platform-dependent
437#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
438#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
439#[allow(clippy::exhaustive_enums)]
440pub enum ModifiersKeyState {
441    /// The particular modifier is active or logically, but not necessarily physically, pressed.
442    Pressed,
443    /// The state of the key is unknown.
444    ///
445    /// Can include cases when the key is active or logically pressed, for example, when a sticky
446    /// **Shift** is active, the OS might not preserve information that it was activated by
447    /// RightShift, so the state of [`ModifiersKeys::RSHIFT`] will be unknown while the state
448    /// of [`ModifiersState::SHIFT`] will be active.
449    #[default]
450    Unknown,
451}
452
453// NOTE: the exact modifier key is not used to represent modifiers state in the
454// first place due to a fact that modifiers state could be changed without any
455// key being pressed and on some platforms like Wayland/X11 which key resulted
456// in modifiers change is hidden, also, not that it really matters.
457//
458// The reason this API is even exposed is mostly to provide a way for users
459// to treat modifiers differently based on their position, which is required
460// on macOS due to their AltGr/Option situation.
461bitflags! {
462    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
463    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
464    pub struct ModifiersKeys: u8 {
465        const LSHIFT   = 0b0000_0001;
466        const RSHIFT   = 0b0000_0010;
467        const LCONTROL = 0b0000_0100;
468        const RCONTROL = 0b0000_1000;
469        const LALT     = 0b0001_0000;
470        const RALT     = 0b0010_0000;
471        const LMETA    = 0b0100_0000;
472        const RMETA    = 0b1000_0000;
473        #[deprecated = "use LMETA instead"]
474        const LSUPER   = Self::LMETA.bits();
475        #[deprecated = "use RMETA instead"]
476        const RSUPER   = Self::RMETA.bits();
477    }
478}