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}