Skip to main content

inset_embedder/
key.rs

1//! Flutter counterpart: `engine/src/flutter/lib/ui/key.dart`.
2
3use std::fmt::{self, Display};
4use std::time::Duration;
5
6/// The type of a key event.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum KeyEventType {
10    /// The key is pressed.
11    Down,
12
13    /// The key is released.
14    Up,
15
16    /// The key is held, causing a repeated key input.
17    Repeat,
18}
19
20impl KeyEventType {
21    /// A human readable name, used by [`KeyData`]'s textual descriptions.
22    pub fn label(&self) -> &'static str {
23        match self {
24            KeyEventType::Down => "Key Down",
25            KeyEventType::Up => "Key Up",
26            KeyEventType::Repeat => "Key Repeat",
27        }
28    }
29}
30
31/// The source device for the key event.
32///
33/// Not all platforms supply an accurate type.
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum KeyEventDeviceType {
37    /// The device is a keyboard.
38    #[default]
39    Keyboard,
40
41    /// The device is a directional pad on something like a television remote
42    /// control or similar.
43    DirectionalPad,
44
45    /// The device is a gamepad button
46    Gamepad,
47
48    /// The device is a joystick button
49    Joystick,
50
51    /// The device is a device connected to an HDMI bus.
52    Hdmi,
53}
54
55impl KeyEventDeviceType {
56    /// A human readable name, used by [`KeyData`]'s textual descriptions.
57    pub fn label(&self) -> &'static str {
58        match self {
59            KeyEventDeviceType::Keyboard => "Keyboard",
60            KeyEventDeviceType::DirectionalPad => "Directional Pad",
61            KeyEventDeviceType::Gamepad => "Gamepad",
62            KeyEventDeviceType::Joystick => "Joystick",
63            KeyEventDeviceType::Hdmi => "HDMI",
64        }
65    }
66}
67
68/// Information about a key event.
69#[derive(Clone, Debug, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub struct KeyData {
72    /// Time of event dispatch, relative to an arbitrary timeline.
73    ///
74    /// For synthesized events, the [`time_stamp`](Self::time_stamp) might not be
75    /// the actual time that the key press or release happens.
76    pub time_stamp: Duration,
77
78    /// The type of the event.
79    pub event_type: KeyEventType,
80
81    /// Describes what type of device (keyboard, directional pad, etc.) this event
82    /// originated from.
83    pub device_type: KeyEventDeviceType,
84
85    /// The key code for the physical key that has changed.
86    pub physical: u64,
87
88    /// The key code for the logical key that has changed.
89    pub logical: u64,
90
91    /// Character input from the event.
92    ///
93    /// Ignored for up events.
94    pub character: Option<String>,
95
96    /// If [`synthesized`](Self::synthesized) is true, this event does not
97    /// correspond to a native event.
98    ///
99    /// Although most of Flutter's keyboard events are transformed from native
100    /// events, some events are not based on native events, and are synthesized
101    /// only to conform Flutter's key event model (as documented in the
102    /// `HardwareKeyboard` class in the framework).
103    ///
104    /// For example, some key downs or ups might be lost when the window loses
105    /// focus. Some platforms provide ways to query whether a key is being held.
106    /// If the embedder detects an inconsistency between its internal record and
107    /// the state returned by the system, the embedder will synthesize a
108    /// corresponding event to synchronize the state without breaking the event
109    /// model.
110    ///
111    /// As another example, macOS treats CapsLock in a special way by sending down
112    /// and up events at the down of alternate presses to indicate the direction
113    /// in which the lock is toggled instead of that the physical key is going. A
114    /// macOS embedder should normalize the behavior by converting a native down
115    /// event into a down event followed immediately by a synthesized up event,
116    /// and the native up event also into a down event followed immediately by a
117    /// synthesized up event.
118    ///
119    /// Synthesized events do not have a trustworthy
120    /// [`time_stamp`](Self::time_stamp), and should not be processed as if the
121    /// key actually went down or up at the time of the callback.
122    ///
123    /// A key repeat event is never synthesized.
124    pub synthesized: bool,
125}
126
127impl Default for KeyData {
128    fn default() -> KeyData {
129        KeyData {
130            time_stamp: Duration::ZERO,
131            event_type: KeyEventType::Down,
132            device_type: KeyEventDeviceType::Keyboard,
133            physical: 0,
134            logical: 0,
135            character: None,
136            synthesized: false,
137        }
138    }
139}
140
141impl KeyData {
142    /// Returns the bits that are not included in the value mask, shifted to the
143    /// right.
144    ///
145    /// For example, if the input is 0x12abcdabcd, then the result is 0x12.
146    fn non_value_bits(n: u64) -> u64 {
147        const VALUE_MASK_WIDTH: u32 = 32;
148        // Dart limits the non-value bits to what a JavaScript number can hold.
149        const MAX_SAFE_INTEGER_WIDTH: u32 = 52;
150        const NON_VALUE_MASK: u64 = (1 << (MAX_SAFE_INTEGER_WIDTH - VALUE_MASK_WIDTH)) - 1;
151        (n >> VALUE_MASK_WIDTH) & NON_VALUE_MASK
152    }
153
154    fn logical_to_string(&self) -> String {
155        let plane_description = match KeyData::non_value_bits(self.logical) & 0x0ff {
156            0x000 => " (Unicode)",
157            0x001 => " (Unprintable)",
158            0x002 => " (Flutter)",
159            0x011 => " (Android)",
160            0x012 => " (Fuchsia)",
161            0x013 => " (iOS)",
162            0x014 => " (macOS)",
163            0x015 => " (GTK)",
164            0x016 => " (Windows)",
165            0x017 => " (Web)",
166            0x018 => " (GLFW)",
167            _ => "",
168        };
169        format!("0x{:x}{}", self.logical, plane_description)
170    }
171
172    fn escape_character(&self) -> String {
173        let Some(character) = &self.character else {
174            return "<none>".to_owned();
175        };
176        match character.as_str() {
177            "\n" => r#""\n""#.to_owned(),
178            "\t" => r#""\t""#.to_owned(),
179            "\r" => r#""\r""#.to_owned(),
180            "\u{8}" => r#""\b""#.to_owned(),
181            "\u{c}" => r#""\f""#.to_owned(),
182            _ => format!("\"{character}\""),
183        }
184    }
185
186    fn quoted_char_code(&self) -> String {
187        let Some(character) = &self.character else {
188            return String::new();
189        };
190        let hex_chars: Vec<String> = character
191            .encode_utf16()
192            .map(|code| format!("{code:02x}"))
193            .collect();
194        format!(" (0x{})", hex_chars.join(" "))
195    }
196
197    /// Returns a complete textual description of the information in this object.
198    pub fn to_string_full(&self) -> String {
199        format!(
200            "KeyData(type: {}, \
201             deviceType: {}, \
202             timeStamp: {:?}, \
203             physical: 0x{:x}, \
204             logical: 0x{:x}, \
205             character: {}, \
206             synthesized: {}\
207             )",
208            self.event_type.label(),
209            self.device_type.label(),
210            self.time_stamp,
211            self.physical,
212            self.logical,
213            self.escape_character(),
214            self.synthesized,
215        )
216    }
217}
218
219impl Display for KeyData {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(
222            f,
223            "KeyData({}, physical: 0x{:x}, logical: {}, character: {}{}{}",
224            self.event_type.label(),
225            self.physical,
226            self.logical_to_string(),
227            self.escape_character(),
228            self.quoted_char_code(),
229            if self.synthesized {
230                ", synthesized"
231            } else {
232                ""
233            },
234        )
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use std::time::Duration;
241
242    use super::{KeyData, KeyEventDeviceType, KeyEventType};
243
244    #[test]
245    fn to_string_names_the_plane_and_quotes_the_character() {
246        let data = KeyData {
247            time_stamp: Duration::from_millis(5),
248            event_type: KeyEventType::Down,
249            device_type: KeyEventDeviceType::Keyboard,
250            physical: 0x00070004,
251            logical: 0x00000000061,
252            character: Some("a".to_owned()),
253            synthesized: false,
254        };
255        assert_eq!(
256            data.to_string(),
257            "KeyData(Key Down, physical: 0x70004, logical: 0x61 (Unicode), character: \"a\" (0x61)"
258        );
259    }
260
261    #[test]
262    fn to_string_full_reports_every_field() {
263        let data = KeyData {
264            time_stamp: Duration::from_millis(5),
265            event_type: KeyEventType::Up,
266            device_type: KeyEventDeviceType::Gamepad,
267            physical: 0x00070004,
268            logical: 0x00100000301,
269            character: None,
270            synthesized: true,
271        };
272        assert_eq!(
273            data.to_string_full(),
274            "KeyData(type: Key Up, deviceType: Gamepad, timeStamp: 5ms, physical: 0x70004, \
275             logical: 0x100000301, character: <none>, synthesized: true)"
276        );
277    }
278}