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}