keyboard_types/
lib.rs

1//! Contains types to define keyboard related events.
2//!
3//! The naming and conventions follow the UI Events specification
4//! but this crate should be useful for anyone implementing keyboard
5//! input in a cross-platform way.
6
7#![warn(clippy::doc_markdown)]
8#![cfg_attr(docsrs, feature(doc_auto_cfg))]
9#![no_std]
10
11extern crate alloc;
12
13#[cfg(feature = "std")]
14extern crate std;
15
16use alloc::string::{String, ToString};
17use core::fmt;
18use core::str::FromStr;
19
20pub use crate::code::{Code, UnrecognizedCodeError};
21pub use crate::location::Location;
22pub use crate::modifiers::Modifiers;
23pub use crate::named_key::{NamedKey, UnrecognizedNamedKeyError};
24pub use crate::shortcuts::ShortcutMatcher;
25
26mod code;
27mod location;
28mod modifiers;
29mod named_key;
30mod shortcuts;
31#[cfg(feature = "webdriver")]
32pub mod webdriver;
33
34#[cfg(feature = "serde")]
35use serde::{Deserialize, Serialize};
36
37/// Describes the state a key is in.
38#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub enum KeyState {
41    /// The key is pressed down.
42    ///
43    /// Often emitted in a [keydown] event, see also [the MDN documentation][mdn] on that.
44    ///
45    /// [keydown]: https://w3c.github.io/uievents/#event-type-keydown
46    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
47    Down,
48    /// The key is not pressed / was just released.
49    ///
50    /// Often emitted in a [keyup] event, see also [the MDN documentation][mdn] on that.
51    ///
52    /// [keyup]: https://w3c.github.io/uievents/#event-type-keyup
53    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event
54    Up,
55}
56
57impl KeyState {
58    /// The [type] name of the corresponding key event.
59    ///
60    /// This is either `"keydown"` or `"keyup"`.
61    ///
62    /// [type]: https://w3c.github.io/uievents/#events-keyboard-types
63    pub const fn event_type(self) -> &'static str {
64        match self {
65            Self::Down => "keydown",
66            Self::Up => "keyup",
67        }
68    }
69
70    /// True if the key is pressed down.
71    pub const fn is_down(self) -> bool {
72        matches!(self, Self::Down)
73    }
74
75    /// True if the key is released.
76    pub const fn is_up(self) -> bool {
77        matches!(self, Self::Up)
78    }
79}
80
81/// Keyboard events are issued for all pressed and released keys.
82#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
83#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84pub struct KeyboardEvent {
85    /// Whether the key is pressed or released.
86    pub state: KeyState,
87    /// Logical key value.
88    pub key: Key,
89    /// Physical key position.
90    pub code: Code,
91    /// Location for keys with multiple instances on common keyboards.
92    pub location: Location,
93    /// Flags for pressed modifier keys.
94    pub modifiers: Modifiers,
95    /// True if the key is currently auto-repeated.
96    pub repeat: bool,
97    /// Events with this flag should be ignored in a text editor
98    /// and instead [composition events](CompositionEvent) should be used.
99    pub is_composing: bool,
100}
101
102/// Describes the state of a composition session.
103#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105pub enum CompositionState {
106    /// The [compositionstart] event.
107    ///
108    /// See also [the MDN documentation][mdn].
109    ///
110    /// [compositionstart]: https://w3c.github.io/uievents/#event-type-compositionstart
111    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event
112    Start,
113    /// The [compositionupdate] event.
114    ///
115    /// See also [the MDN documentation][mdn].
116    ///
117    /// [compositionupdate]: https://w3c.github.io/uievents/#event-type-compositionupdate
118    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionupdate_event
119    Update,
120    /// The [compositionend] event.
121    ///
122    /// In a text editor, in this state the data should be added to the input.
123    ///
124    /// See also [the MDN documentation][mdn].
125    ///
126    /// [compositionend]: https://w3c.github.io/uievents/#event-type-compositionend
127    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event
128    End,
129}
130
131impl CompositionState {
132    /// The [type] name of the corresponding composition event.
133    ///
134    /// This is either `"compositionstart"`, `"compositionupdate"` or `"compositionend"`.
135    ///
136    /// [type]: https://w3c.github.io/uievents/#events-composition-types
137    pub const fn event_type(self) -> &'static str {
138        match self {
139            Self::Start => "compositionstart",
140            Self::Update => "compositionupdate",
141            Self::End => "compositionend",
142        }
143    }
144}
145
146/// Event to expose input methods to program logic.
147///
148/// Provides information about entered sequences from
149/// dead key combinations and IMEs.
150///
151/// A composition session is always started by a [`CompositionState::Start`]
152/// event followed by zero or more [`CompositionState::Update`] events
153/// and terminated by a single [`CompositionState::End`] event.
154#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
156pub struct CompositionEvent {
157    /// Describes the event kind.
158    pub state: CompositionState,
159    /// Current composition data. May be empty.
160    pub data: String,
161}
162
163/// The value received from the keypress.
164#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub enum Key {
167    /// A key string that corresponds to the character typed by the user,
168    /// taking into account the user’s current locale setting, modifier state,
169    /// and any system-level keyboard mapping overrides that are in effect.
170    Character(String),
171    Named(NamedKey),
172}
173
174/// Parse from string error, returned when string does not match to any [`Key`] variant.
175#[derive(Clone, Debug)]
176pub struct UnrecognizedKeyError;
177
178impl fmt::Display for Key {
179    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180        match self {
181            Self::Character(s) => f.write_str(s),
182            Self::Named(k) => k.fmt(f),
183        }
184    }
185}
186
187impl FromStr for Key {
188    type Err = UnrecognizedKeyError;
189
190    fn from_str(s: &str) -> Result<Self, Self::Err> {
191        if is_key_string(s) {
192            Ok(Self::Character(s.to_string()))
193        } else {
194            Ok(Self::Named(
195                NamedKey::from_str(s).map_err(|_| UnrecognizedKeyError)?,
196            ))
197        }
198    }
199}
200
201impl From<NamedKey> for Key {
202    fn from(value: NamedKey) -> Self {
203        Self::Named(value)
204    }
205}
206
207impl Key {
208    /// Determine a *charCode* value for a key with a character value.
209    ///
210    /// For all other keys the value is zero.
211    /// The *charCode* is an implementation specific legacy property of DOM keyboard events.
212    ///
213    /// Specification: <https://w3c.github.io/uievents/#dom-keyboardevent-charcode>
214    pub fn legacy_charcode(&self) -> u32 {
215        // Spec: event.charCode = event.key.charCodeAt(0)
216        // otherwise 0
217        match self {
218            Key::Character(ref c) => c.chars().next().unwrap_or('\0') as u32,
219            Key::Named(_) => 0,
220        }
221    }
222
223    /// Determine a *keyCode* value for a key.
224    ///
225    /// The *keyCode* is an implementation specific legacy property of DOM keyboard events.
226    ///
227    /// Specification: <https://w3c.github.io/uievents/#dom-keyboardevent-keycode>
228    pub fn legacy_keycode(&self) -> u32 {
229        match self {
230            // See: https://w3c.github.io/uievents/#fixed-virtual-key-codes
231            Key::Named(NamedKey::Backspace) => 8,
232            Key::Named(NamedKey::Tab) => 9,
233            Key::Named(NamedKey::Enter) => 13,
234            Key::Named(NamedKey::Shift) => 16,
235            Key::Named(NamedKey::Control) => 17,
236            Key::Named(NamedKey::Alt) => 18,
237            Key::Named(NamedKey::CapsLock) => 20,
238            Key::Named(NamedKey::Escape) => 27,
239            Key::Named(NamedKey::PageUp) => 33,
240            Key::Named(NamedKey::PageDown) => 34,
241            Key::Named(NamedKey::End) => 35,
242            Key::Named(NamedKey::Home) => 36,
243            Key::Named(NamedKey::ArrowLeft) => 37,
244            Key::Named(NamedKey::ArrowUp) => 38,
245            Key::Named(NamedKey::ArrowRight) => 39,
246            Key::Named(NamedKey::ArrowDown) => 40,
247            Key::Named(NamedKey::Delete) => 46,
248            Key::Character(ref c) if c.len() == 1 => match first_char(c) {
249                ' ' => 32,
250                x @ '0'..='9' => x as u32,
251                x @ 'a'..='z' => x.to_ascii_uppercase() as u32,
252                x @ 'A'..='Z' => x as u32,
253                // See: https://w3c.github.io/uievents/#optionally-fixed-virtual-key-codes
254                ';' | ':' => 186,
255                '=' | '+' => 187,
256                ',' | '<' => 188,
257                '-' | '_' => 189,
258                '.' | '>' => 190,
259                '/' | '?' => 191,
260                '`' | '~' => 192,
261                '[' | '{' => 219,
262                '\\' | '|' => 220,
263                ']' | '}' => 221,
264                '\'' | '\"' => 222,
265                _ => 0,
266            },
267            _ => 0,
268        }
269    }
270}
271
272impl Default for KeyState {
273    fn default() -> KeyState {
274        KeyState::Down
275    }
276}
277
278impl Default for Key {
279    fn default() -> Self {
280        Self::Named(NamedKey::default())
281    }
282}
283
284impl Default for NamedKey {
285    fn default() -> Self {
286        Self::Unidentified
287    }
288}
289
290impl Default for Code {
291    fn default() -> Code {
292        Code::Unidentified
293    }
294}
295
296impl Default for Location {
297    fn default() -> Location {
298        Location::Standard
299    }
300}
301
302/// Return the first codepoint of a string.
303///
304/// # Panics
305/// Panics if the string is empty.
306fn first_char(s: &str) -> char {
307    s.chars().next().expect("empty string")
308}
309
310/// Check if string can be used as a `Key::Character` _keystring_.
311///
312/// This check is simple and is meant to prevents common mistakes like mistyped keynames
313/// (e.g. `Ennter`) from being recognized as characters.
314fn is_key_string(s: &str) -> bool {
315    s.chars().all(|c| !c.is_control()) && s.chars().skip(1).all(|c| !c.is_ascii())
316}
317
318#[cfg(test)]
319mod test {
320    use super::*;
321
322    #[test]
323    fn test_is_key_string() {
324        assert!(is_key_string("A"));
325        assert!(!is_key_string("AA"));
326        assert!(!is_key_string("	"));
327    }
328
329    #[test]
330    fn into() {
331        assert_eq!(Key::Named(NamedKey::Enter), NamedKey::Enter.into());
332    }
333}