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