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