input_pipeline/
chromebook_keyboard_handler.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! # Fixups for the Chromebook keyboard
6//!
7//! Chromebook keyboards have a top row of "action" keys, which are usually
8//! reported as function keys.  The correct functionality would be to allow
9//! them to be used as either function or action keys, depending on whether
10//! the "search" key is being actuated alongside one of the keys.
11//!
12//! The "Search" key in Chromebooks is used to substitute for a range of keys
13//! that are not usually present on a Chromebook keyboard.  This Handler
14//! implements some of those.
15
16use crate::input_device::{
17    Handled, InputDeviceDescriptor, InputDeviceEvent, InputEvent, UnhandledInputEvent,
18};
19use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
20use crate::keyboard_binding::{KeyboardDeviceDescriptor, KeyboardEvent};
21use async_trait::async_trait;
22use fidl_fuchsia_input::Key;
23use fidl_fuchsia_ui_input3::KeyEventType;
24use fuchsia_inspect::health::Reporter;
25use fuchsia_trace as ftrace;
26use keymaps::KeyState;
27use maplit::hashmap;
28use std::cell::RefCell;
29use std::collections::HashMap;
30use std::rc::Rc;
31use std::sync::LazyLock;
32
33/// The vendor ID denoting the internal Chromebook keyboard.
34const VENDOR_ID: u32 = 0x18d1; // Google
35
36/// The product ID denoting the internal Chromebook keyboard.
37const PRODUCT_ID: u32 = 0x10003;
38
39//// The Chromebook "Search" key is reported as left meta.
40const SEARCH_KEY: Key = Key::LeftMeta;
41
42#[derive(Debug)]
43struct KeyPair {
44    /// The emitted key without actuated Search key.
45    without_search: Key,
46    /// The emitted key with actuated Search key.
47    with_search: Key,
48}
49
50// Map key is the original key code produced by the keyboard.  The map value
51// are the possible remapped keys, depending on whether Search key is
52// actuated.
53static REMAPPED_KEYS: LazyLock<HashMap<Key, KeyPair>> = LazyLock::new(|| {
54    hashmap! {
55        Key::F1 => KeyPair{ without_search: Key::AcBack, with_search: Key::F1 },
56        Key::F2 => KeyPair{ without_search: Key::AcRefresh, with_search: Key::F2},
57        Key::F3 => KeyPair{ without_search: Key::AcFullScreenView, with_search: Key::F3 },
58        Key::F4 => KeyPair{ without_search: Key::AcSelectTaskApplication, with_search:  Key::F4 },
59        Key::F5 => KeyPair{ without_search: Key::BrightnessDown, with_search: Key::F5 },
60        Key::F6 => KeyPair{ without_search: Key::BrightnessUp, with_search: Key::F6 },
61        Key::F7 => KeyPair{ without_search: Key::PlayPause, with_search: Key::F7 },
62        Key::F8 => KeyPair{ without_search: Key::Mute, with_search: Key::F8 },
63        Key::F9 => KeyPair{ without_search: Key::VolumeDown, with_search: Key::F9 },
64        Key::F10 => KeyPair{ without_search: Key::VolumeUp, with_search: Key::F10 },
65        Key::Left => KeyPair{ without_search: Key::Left, with_search: Key::Home },
66        Key::Right => KeyPair{ without_search: Key::Right, with_search: Key::End },
67        Key::Up => KeyPair{ without_search: Key::Up, with_search: Key::PageUp },
68        Key::Down => KeyPair{ without_search: Key::Down, with_search: Key::PageDown },
69        Key::Dot => KeyPair{ without_search: Key::Dot, with_search: Key::Insert },
70        Key::Backspace => KeyPair{ without_search: Key::Backspace, with_search: Key::Delete },
71    }
72});
73
74/// A Chromebook dedicated keyboard handler.
75///
76/// Create a new instance with [ChromebookKeyboardHandler::new].
77#[derive(Debug, Default)]
78pub struct ChromebookKeyboardHandler {
79    // Handler's mutable state must be accessed via RefCell.
80    state: RefCell<Inner>,
81
82    /// The inventory of this handler's Inspect status.
83    pub inspect_status: InputHandlerStatus,
84}
85
86#[derive(Debug, Default)]
87struct Inner {
88    /// The list of keys (using original key codes, not remapped ones) that are
89    /// currently actuated.
90    key_state: KeyState,
91    /// Is the search key currently activated.
92    is_search_key_actuated: bool,
93    /// Were there any new keyboard events generated by this handler, or
94    /// received by this handler, since the Search key was last pressed?
95    other_key_events: bool,
96    /// The minimum timestamp that is still larger than the last observed
97    /// timestamp (i.e. last + 1ns). Used to generate monotonically increasing
98    /// timestamps for generated events.
99    next_event_time: zx::MonotonicInstant,
100    /// Have any regular (non-remapped) keys been pressed since the actuation
101    /// of the Search key?
102    regular_keys_pressed: bool,
103}
104
105/// Returns true if the provided device info matches the Chromebook keyboard.
106fn is_chromebook_keyboard(device_info: &fidl_fuchsia_input_report::DeviceInformation) -> bool {
107    device_info.product_id.unwrap_or_default() == PRODUCT_ID
108        && device_info.vendor_id.unwrap_or_default() == VENDOR_ID
109}
110
111#[async_trait(?Send)]
112impl UnhandledInputHandler for ChromebookKeyboardHandler {
113    async fn handle_unhandled_input_event(
114        self: Rc<Self>,
115        input_event: UnhandledInputEvent,
116    ) -> Vec<InputEvent> {
117        fuchsia_trace::duration!(c"input", c"chromebook_keyboard_handler");
118        match input_event.clone() {
119            // Decorate a keyboard event with key meaning.
120            UnhandledInputEvent {
121                device_event: InputDeviceEvent::Keyboard(event),
122                device_descriptor: InputDeviceDescriptor::Keyboard(ref keyboard_descriptor),
123                event_time,
124                trace_id,
125            } if is_chromebook_keyboard(&keyboard_descriptor.device_information) => {
126                fuchsia_trace::duration!(c"input", c"chromebook_keyboard_handler[processing]");
127                self.inspect_status.count_received_event(&event_time);
128                self.process_keyboard_event(
129                    event,
130                    keyboard_descriptor.clone(),
131                    event_time,
132                    trace_id,
133                )
134            }
135            // Pass other events unchanged.
136            _ => vec![InputEvent::from(input_event)],
137        }
138    }
139
140    fn set_handler_healthy(self: std::rc::Rc<Self>) {
141        self.inspect_status.health_node.borrow_mut().set_ok();
142    }
143
144    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
145        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
146    }
147}
148
149impl ChromebookKeyboardHandler {
150    /// Creates a new instance of the handler.
151    pub fn new(input_handlers_node: &fuchsia_inspect::Node) -> Rc<Self> {
152        let inspect_status = InputHandlerStatus::new(
153            input_handlers_node,
154            "chromebook_keyboard_handler",
155            /* generates_events */ true,
156        );
157        Rc::new(Self { state: RefCell::new(Default::default()), inspect_status })
158    }
159
160    /// Gets the next event time that is at least as large as event_time, and
161    /// larger than last seen time.
162    fn next_event_time(self: &Rc<Self>, event_time: zx::MonotonicInstant) -> zx::MonotonicInstant {
163        let proposed = self.state.borrow().next_event_time;
164        let returned = if event_time < proposed { proposed } else { event_time };
165        self.state.borrow_mut().next_event_time = returned + zx::MonotonicDuration::from_nanos(1);
166        returned
167    }
168
169    /// Updates the internal key state, but only for remappable keys.
170    fn update_key_state(self: &Rc<Self>, event_type: KeyEventType, key: Key) {
171        if REMAPPED_KEYS.contains_key(&key) {
172            self.state.borrow_mut().key_state.update(event_type, key)
173        }
174    }
175
176    /// Gets the list of keys in the key state, in order they were pressed.
177    fn get_ordered_keys(self: &Rc<Self>) -> Vec<Key> {
178        self.state.borrow().key_state.get_ordered_keys()
179    }
180
181    fn is_search_key_actuated(self: &Rc<Self>) -> bool {
182        self.state.borrow().is_search_key_actuated
183    }
184
185    fn set_search_key_actuated(self: &Rc<Self>, value: bool) {
186        self.state.borrow_mut().is_search_key_actuated = value;
187    }
188
189    fn has_other_key_events(self: &Rc<Self>) -> bool {
190        self.state.borrow().other_key_events
191    }
192
193    fn set_other_key_events(self: &Rc<Self>, value: bool) {
194        self.state.borrow_mut().other_key_events = value;
195    }
196
197    fn is_regular_keys_pressed(self: &Rc<Self>) -> bool {
198        self.state.borrow().regular_keys_pressed
199    }
200
201    fn set_regular_keys_pressed(self: &Rc<Self>, value: bool) {
202        self.state.borrow_mut().regular_keys_pressed = value;
203    }
204
205    fn synthesize_input_events<'a, I: Iterator<Item = &'a Key>>(
206        self: &Rc<Self>,
207        event: KeyboardEvent,
208        event_type: KeyEventType,
209        descriptor: KeyboardDeviceDescriptor,
210        event_time: zx::MonotonicInstant,
211        trace_id: Option<ftrace::Id>,
212        keys: I,
213        mapfn: fn(&KeyPair) -> Key,
214    ) -> Vec<InputEvent> {
215        keys.map(|key| {
216            mapfn(REMAPPED_KEYS.get(key).expect("released_key must be in REMAPPED_KEYS"))
217        })
218        .map(|key| {
219            into_unhandled_input_event(
220                event.clone().into_with_key(key).into_with_event_type(event_type),
221                descriptor.clone(),
222                self.next_event_time(event_time),
223                trace_id,
224            )
225        })
226        .collect()
227    }
228
229    /// Remaps hardware events.
230    fn process_keyboard_event(
231        self: &Rc<Self>,
232        event: KeyboardEvent,
233        device_descriptor: KeyboardDeviceDescriptor,
234        event_time: zx::MonotonicInstant,
235        trace_id: Option<ftrace::Id>,
236    ) -> Vec<InputEvent> {
237        // Remapping happens when search key is *not* actuated. The keyboard
238        // sends the F1 key code, but we must convert it by default to AcBack,
239        // for example.
240        let is_search_key_actuated = self.is_search_key_actuated();
241
242        let key = event.get_key();
243        let event_type_folded = event.get_event_type_folded();
244        let event_type = event.get_event_type();
245
246        match is_search_key_actuated {
247            true => {
248                // If the meta key is released, turn the remapping off, but also flip remapping of
249                // any currently active keys.
250                if key == SEARCH_KEY && event_type_folded == KeyEventType::Released {
251                    // Used to synthesize new events.
252                    let keys_to_release = self.get_ordered_keys();
253
254                    // No more remapping: flip any active keys to non-remapped, and continue.
255                    let mut new_events = self.synthesize_input_events(
256                        event.clone(),
257                        KeyEventType::Released,
258                        device_descriptor.clone(),
259                        event_time,
260                        None,
261                        keys_to_release.iter().rev(),
262                        |kp: &KeyPair| kp.with_search,
263                    );
264                    new_events.append(&mut self.synthesize_input_events(
265                        event.clone(),
266                        KeyEventType::Pressed,
267                        device_descriptor.clone(),
268                        event_time,
269                        None,
270                        keys_to_release.iter(),
271                        |kp: &KeyPair| kp.without_search,
272                    ));
273
274                    // The Search key serves a dual purpose: it is a "silent" modifier for
275                    // action keys, and it also can serve as a left Meta key if pressed on
276                    // its own, or in combination with a key that does not normally get
277                    // remapped. Such would be the case of Meta+A, where we must synthesize
278                    // a Meta press, then press of A, then the respective releases. Contrast
279                    // to Search+AcBack, which only results in F1, without Meta.
280                    let search_key_only =
281                        !self.has_other_key_events() && event_type == KeyEventType::Released;
282                    // If there were no intervening events between a press and a release of the
283                    // Search key, then emulate a press.
284                    if search_key_only {
285                        new_events.push(into_unhandled_input_event(
286                            event.clone().into_with_event_type(KeyEventType::Pressed),
287                            device_descriptor.clone(),
288                            self.next_event_time(event_time),
289                            None,
290                        ));
291                    }
292                    // Similarly, emulate a release too, in two cases:
293                    //
294                    // 1) No intervening presses (like above); and
295                    // 2) There was a non-remapped key used with Search.
296                    if search_key_only || self.is_regular_keys_pressed() {
297                        new_events.push(into_unhandled_input_event(
298                            event.into_with_event_type(KeyEventType::Released),
299                            device_descriptor,
300                            self.next_event_time(event_time),
301                            None,
302                        ));
303                    }
304
305                    // Reset search key state tracking to initial values.
306                    self.set_search_key_actuated(false);
307                    self.set_other_key_events(false);
308                    self.set_regular_keys_pressed(false);
309
310                    return new_events;
311                } else {
312                    // Any other key press or release that is not the Search key.
313                }
314            }
315            false => {
316                if key == SEARCH_KEY && event_type == KeyEventType::Pressed {
317                    // Used to synthesize new events.
318                    let keys_to_release = self.get_ordered_keys();
319
320                    let mut new_events = self.synthesize_input_events(
321                        event.clone(),
322                        KeyEventType::Released,
323                        device_descriptor.clone(),
324                        event_time,
325                        None,
326                        keys_to_release.iter().rev(),
327                        |kp: &KeyPair| kp.without_search,
328                    );
329                    new_events.append(&mut self.synthesize_input_events(
330                        event,
331                        KeyEventType::Pressed,
332                        device_descriptor,
333                        event_time,
334                        None,
335                        keys_to_release.iter(),
336                        |kp: &KeyPair| kp.with_search,
337                    ));
338
339                    self.set_search_key_actuated(true);
340                    if !keys_to_release.is_empty() {
341                        self.set_other_key_events(true);
342                    }
343                    return new_events;
344                }
345            }
346        }
347
348        self.update_key_state(event_type, key);
349        let maybe_remapped_key = REMAPPED_KEYS.get(&key);
350        let return_events = if let Some(remapped_keypair) = maybe_remapped_key {
351            let key = if is_search_key_actuated {
352                remapped_keypair.with_search
353            } else {
354                remapped_keypair.without_search
355            };
356            vec![into_unhandled_input_event(
357                event.into_with_key(key),
358                device_descriptor,
359                self.next_event_time(event_time),
360                trace_id,
361            )]
362        } else {
363            let mut events = vec![];
364            // If this is the first non-remapped keypress after SEARCH_KEY actuation, we must emit
365            // the modifier before the key itself, because now we know that the user's intent was
366            // to use a modifier, not to remap action keys into function keys.
367            if self.is_search_key_actuated()
368                && !self.has_other_key_events()
369                && event_type == KeyEventType::Pressed
370            {
371                let new_event = event
372                    .clone()
373                    .into_with_key(SEARCH_KEY)
374                    .into_with_event_type(KeyEventType::Pressed);
375                events.push(into_unhandled_input_event(
376                    new_event,
377                    device_descriptor.clone(),
378                    self.next_event_time(event_time),
379                    None,
380                ));
381                self.set_regular_keys_pressed(true);
382            }
383            events.push(into_unhandled_input_event(
384                event,
385                device_descriptor,
386                self.next_event_time(event_time),
387                trace_id,
388            ));
389            //
390            // Set "non-remapped-key".
391            events
392        };
393
394        // Remember that there were keypresses other than SEARCH_KEY after
395        // SEARCH_KEY was actuated.
396        if event_type == KeyEventType::Pressed && key != SEARCH_KEY && is_search_key_actuated {
397            self.set_other_key_events(true);
398        }
399
400        return_events
401    }
402}
403
404fn into_unhandled_input_event(
405    event: KeyboardEvent,
406    device_descriptor: KeyboardDeviceDescriptor,
407    event_time: zx::MonotonicInstant,
408    trace_id: Option<ftrace::Id>,
409) -> InputEvent {
410    InputEvent {
411        device_event: InputDeviceEvent::Keyboard(event),
412        device_descriptor: device_descriptor.into(),
413        event_time,
414        handled: Handled::No,
415        trace_id,
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422    use crate::testing_utilities::create_input_event;
423    use std::sync::LazyLock;
424    use test_case::test_case;
425
426    static MATCHING_KEYBOARD_DESCRIPTOR: LazyLock<InputDeviceDescriptor> = LazyLock::new(|| {
427        InputDeviceDescriptor::Keyboard(KeyboardDeviceDescriptor {
428            keys: vec![],
429            device_information: fidl_fuchsia_input_report::DeviceInformation {
430                vendor_id: Some(VENDOR_ID),
431                product_id: Some(PRODUCT_ID),
432                version: Some(42),
433                polling_rate: Some(1000),
434                ..Default::default()
435            },
436            device_id: 43,
437        })
438    });
439    static MISMATCHING_KEYBOARD_DESCRIPTOR: LazyLock<InputDeviceDescriptor> = LazyLock::new(|| {
440        InputDeviceDescriptor::Keyboard(KeyboardDeviceDescriptor {
441            keys: vec![],
442            device_information: fidl_fuchsia_input_report::DeviceInformation {
443                vendor_id: Some(VENDOR_ID + 10),
444                product_id: Some(PRODUCT_ID),
445                version: Some(42),
446                polling_rate: Some(1000),
447                ..Default::default()
448            },
449            device_id: 43,
450        })
451    });
452
453    async fn run_all_events<T: UnhandledInputHandler>(
454        handler: &Rc<T>,
455        events: Vec<InputEvent>,
456    ) -> Vec<InputEvent> {
457        let handler_clone = || handler.clone();
458        let events_futs = events
459            .into_iter()
460            .map(|e| e.try_into().expect("events are always convertible in tests"))
461            .map(|e| handler_clone().handle_unhandled_input_event(e));
462        // Is there a good streaming way to achieve this?
463        let mut events_set = vec![];
464        for events_fut in events_futs.into_iter() {
465            events_set.push(events_fut.await);
466        }
467        events_set.into_iter().flatten().collect()
468    }
469
470    // A shorthand to create a sequence of keyboard events for testing.  All events
471    // created share a descriptor and a handled marker.  The event time is incremented
472    // for each event in turn.
473    fn new_key_sequence(
474        mut event_time: zx::MonotonicInstant,
475        descriptor: &InputDeviceDescriptor,
476        handled: Handled,
477        keys: Vec<(Key, KeyEventType)>,
478    ) -> Vec<InputEvent> {
479        let mut ret = vec![];
480        for (k, t) in keys {
481            ret.push(create_input_event(KeyboardEvent::new(k, t), descriptor, event_time, handled));
482            event_time = event_time + zx::MonotonicDuration::from_nanos(1);
483        }
484        ret
485    }
486
487    #[test]
488    fn next_event_time() {
489        let inspector = fuchsia_inspect::Inspector::default();
490        let test_node = inspector.root().create_child("test_node");
491        let handler = ChromebookKeyboardHandler::new(&test_node);
492        assert_eq!(
493            zx::MonotonicInstant::from_nanos(10),
494            handler.next_event_time(zx::MonotonicInstant::from_nanos(10))
495        );
496        assert_eq!(
497            zx::MonotonicInstant::from_nanos(11),
498            handler.next_event_time(zx::MonotonicInstant::from_nanos(10))
499        );
500        assert_eq!(
501            zx::MonotonicInstant::from_nanos(12),
502            handler.next_event_time(zx::MonotonicInstant::from_nanos(10))
503        );
504        assert_eq!(
505            zx::MonotonicInstant::from_nanos(13),
506            handler.next_event_time(zx::MonotonicInstant::from_nanos(13))
507        );
508        assert_eq!(
509            zx::MonotonicInstant::from_nanos(14),
510            handler.next_event_time(zx::MonotonicInstant::from_nanos(13))
511        );
512    }
513
514    // Basic tests: ensure that function key codes are normally converted into
515    // action key codes on the built-in keyboard. Other action keys are not.
516    #[test_case(Key::F1, Key::AcBack; "convert F1")]
517    #[test_case(Key::F2, Key::AcRefresh; "convert F2")]
518    #[test_case(Key::F3, Key::AcFullScreenView; "convert F3")]
519    #[test_case(Key::F4, Key::AcSelectTaskApplication; "convert F4")]
520    #[test_case(Key::F5, Key::BrightnessDown; "convert F5")]
521    #[test_case(Key::F6, Key::BrightnessUp; "convert F6")]
522    #[test_case(Key::F7, Key::PlayPause; "convert F7")]
523    #[test_case(Key::F8, Key::Mute; "convert F8")]
524    #[test_case(Key::F9, Key::VolumeDown; "convert F9")]
525    #[test_case(Key::F10, Key::VolumeUp; "convert F10")]
526    #[test_case(Key::A, Key::A; "do not convert A")]
527    #[test_case(Key::Up, Key::Up; "do not convert Up")]
528    #[test_case(Key::Down, Key::Down; "do not convert Down")]
529    #[test_case(Key::Left, Key::Left; "do not convert Left")]
530    #[test_case(Key::Right, Key::Right; "do not convert Right")]
531    #[test_case(Key::Dot, Key::Dot; "do not convert Dot")]
532    #[test_case(Key::Backspace, Key::Backspace; "do not convert Backspace")]
533    #[fuchsia::test]
534    async fn conversion_matching_keyboard(input_key: Key, output_key: Key) {
535        let inspector = fuchsia_inspect::Inspector::default();
536        let test_node = inspector.root().create_child("test_node");
537        let handler = ChromebookKeyboardHandler::new(&test_node);
538        let input = new_key_sequence(
539            zx::MonotonicInstant::from_nanos(42),
540            &MATCHING_KEYBOARD_DESCRIPTOR,
541            Handled::No,
542            vec![(input_key, KeyEventType::Pressed), (input_key, KeyEventType::Released)],
543        );
544        let actual = run_all_events(&handler, input).await;
545        let expected = new_key_sequence(
546            zx::MonotonicInstant::from_nanos(42),
547            &MATCHING_KEYBOARD_DESCRIPTOR,
548            Handled::No,
549            vec![(output_key, KeyEventType::Pressed), (output_key, KeyEventType::Released)],
550        );
551        pretty_assertions::assert_eq!(expected, actual);
552    }
553
554    // Basic tests: ensure that function key codes are NOT converted into
555    // action key codes on any other keyboard (e.g. external).
556    #[test_case(Key::F1, Key::F1; "do not convert F1")]
557    #[test_case(Key::F2, Key::F2; "do not convert F2")]
558    #[test_case(Key::F3, Key::F3; "do not convert F3")]
559    #[test_case(Key::F4, Key::F4; "do not convert F4")]
560    #[test_case(Key::F5, Key::F5; "do not convert F5")]
561    #[test_case(Key::F6, Key::F6; "do not convert F6")]
562    #[test_case(Key::F7, Key::F7; "do not convert F7")]
563    #[test_case(Key::F8, Key::F8; "do not convert F8")]
564    #[test_case(Key::F9, Key::F9; "do not convert F9")]
565    #[test_case(Key::F10, Key::F10; "do not convert F10")]
566    #[test_case(Key::A, Key::A; "do not convert A")]
567    #[fuchsia::test]
568    async fn conversion_mismatching_keyboard(input_key: Key, output_key: Key) {
569        let inspector = fuchsia_inspect::Inspector::default();
570        let test_node = inspector.root().create_child("test_node");
571        let handler = ChromebookKeyboardHandler::new(&test_node);
572        let input = new_key_sequence(
573            zx::MonotonicInstant::from_nanos(42),
574            &MISMATCHING_KEYBOARD_DESCRIPTOR,
575            Handled::No,
576            vec![(input_key, KeyEventType::Pressed), (input_key, KeyEventType::Released)],
577        );
578        let actual = run_all_events(&handler, input).await;
579        let expected = new_key_sequence(
580            zx::MonotonicInstant::from_nanos(42),
581            &MISMATCHING_KEYBOARD_DESCRIPTOR,
582            Handled::No,
583            vec![(output_key, KeyEventType::Pressed), (output_key, KeyEventType::Released)],
584        );
585        pretty_assertions::assert_eq!(expected, actual);
586    }
587
588    // If a Search key is pressed without intervening keypresses, it results in
589    // a delayed press and release.
590    //
591    // SEARCH_KEY[in]  ___/""""""""\___
592    //
593    // SEARCH_KEY[out] ____________/""\___
594    #[fuchsia::test]
595    async fn search_key_only() {
596        let inspector = fuchsia_inspect::Inspector::default();
597        let test_node = inspector.root().create_child("test_node");
598        let handler = ChromebookKeyboardHandler::new(&test_node);
599        let input = new_key_sequence(
600            zx::MonotonicInstant::from_nanos(42),
601            &MATCHING_KEYBOARD_DESCRIPTOR,
602            Handled::No,
603            vec![(SEARCH_KEY, KeyEventType::Pressed), (SEARCH_KEY, KeyEventType::Released)],
604        );
605        let actual = run_all_events(&handler, input).await;
606        let expected = new_key_sequence(
607            zx::MonotonicInstant::from_nanos(43),
608            &MATCHING_KEYBOARD_DESCRIPTOR,
609            Handled::No,
610            vec![(SEARCH_KEY, KeyEventType::Pressed), (SEARCH_KEY, KeyEventType::Released)],
611        );
612        pretty_assertions::assert_eq!(expected, actual);
613    }
614
615    // When a remappable key (e.g. F1) is pressed with the Search key, the effect
616    // is as if the action key only is pressed.
617    //
618    // SEARCH_KEY[in]  ___/"""""""""""\___
619    // F1[in]          ______/""""\_______
620    //
621    // SEARCH_KEY[out] ___________________
622    // F1[out]         ______/""""\_______
623    #[fuchsia::test]
624    async fn f1_conversion() {
625        let inspector = fuchsia_inspect::Inspector::default();
626        let test_node = inspector.root().create_child("test_node");
627        let handler = ChromebookKeyboardHandler::new(&test_node);
628        let input = new_key_sequence(
629            zx::MonotonicInstant::from_nanos(42),
630            &MATCHING_KEYBOARD_DESCRIPTOR,
631            Handled::No,
632            vec![
633                (SEARCH_KEY, KeyEventType::Pressed),
634                (Key::F1, KeyEventType::Pressed),
635                (Key::F1, KeyEventType::Released),
636                (SEARCH_KEY, KeyEventType::Released),
637            ],
638        );
639        let actual = run_all_events(&handler, input).await;
640        let expected = new_key_sequence(
641            zx::MonotonicInstant::from_nanos(43),
642            &MATCHING_KEYBOARD_DESCRIPTOR,
643            Handled::No,
644            vec![(Key::F1, KeyEventType::Pressed), (Key::F1, KeyEventType::Released)],
645        );
646        pretty_assertions::assert_eq!(expected, actual);
647    }
648
649    // When a remappable key (e.g. F1) is pressed with the Search key, the effect
650    // is as if the action key only is pressed.
651    //
652    // SEARCH_KEY[in]  ___/"""""""""""\___
653    // F1[in]          ______/""""\_______
654    //
655    // SEARCH_KEY[out] ___________________
656    // F1[out]         ______/""""\_______
657    //
658    // Similar tests are ran on all other convertible keys.
659    #[test_case(Key::F1, Key::F1; "do not convert F1")]
660    #[test_case(Key::F2, Key::F2; "do not convert F2")]
661    #[test_case(Key::F3, Key::F3; "do not convert F3")]
662    #[test_case(Key::F4, Key::F4; "do not convert F4")]
663    #[test_case(Key::F5, Key::F5; "do not convert F5")]
664    #[test_case(Key::F6, Key::F6; "do not convert F6")]
665    #[test_case(Key::F7, Key::F7; "do not convert F7")]
666    #[test_case(Key::F8, Key::F8; "do not convert F8")]
667    #[test_case(Key::F9, Key::F9; "do not convert F9")]
668    #[test_case(Key::F10, Key::F10; "do not convert F10")]
669    #[test_case(Key::Up, Key::PageUp; "convert Up")]
670    #[test_case(Key::Down, Key::PageDown; "convert Down")]
671    #[test_case(Key::Left, Key::Home; "convert Left")]
672    #[test_case(Key::Right, Key::End; "convert Right")]
673    #[test_case(Key::Dot, Key::Insert; "convert Dot")]
674    #[test_case(Key::Backspace, Key::Delete; "convert Backspace")]
675    #[fuchsia::test]
676    async fn with_search_key_pressed(input_key: Key, output_key: Key) {
677        let inspector = fuchsia_inspect::Inspector::default();
678        let test_node = inspector.root().create_child("test_node");
679        let handler = ChromebookKeyboardHandler::new(&test_node);
680        let input = new_key_sequence(
681            zx::MonotonicInstant::from_nanos(42),
682            &MATCHING_KEYBOARD_DESCRIPTOR,
683            Handled::No,
684            vec![
685                (SEARCH_KEY, KeyEventType::Pressed),
686                (input_key, KeyEventType::Pressed),
687                (input_key, KeyEventType::Released),
688                (SEARCH_KEY, KeyEventType::Released),
689            ],
690        );
691        let actual = run_all_events(&handler, input).await;
692        let expected = new_key_sequence(
693            zx::MonotonicInstant::from_nanos(43),
694            &MATCHING_KEYBOARD_DESCRIPTOR,
695            Handled::No,
696            vec![(output_key, KeyEventType::Pressed), (output_key, KeyEventType::Released)],
697        );
698        pretty_assertions::assert_eq!(expected, actual);
699    }
700
701    // SEARCH_KEY[in]  __/""""""\________
702    // F1[in]          _____/""""""""\___
703    //
704    // SEARCH_KEY[out] __________________
705    // F1[out]         _____/"""\________
706    // AcBack[out]     _________/""""\___
707    #[fuchsia::test]
708    async fn search_released_before_f1() {
709        let inspector = fuchsia_inspect::Inspector::default();
710        let test_node = inspector.root().create_child("test_node");
711        let handler = ChromebookKeyboardHandler::new(&test_node);
712        let input = new_key_sequence(
713            zx::MonotonicInstant::from_nanos(42),
714            &MATCHING_KEYBOARD_DESCRIPTOR,
715            Handled::No,
716            vec![
717                (SEARCH_KEY, KeyEventType::Pressed),
718                (Key::F1, KeyEventType::Pressed),
719                (SEARCH_KEY, KeyEventType::Released),
720                (Key::F1, KeyEventType::Released),
721            ],
722        );
723        let actual = run_all_events(&handler, input).await;
724        let expected = new_key_sequence(
725            zx::MonotonicInstant::from_nanos(43),
726            &MATCHING_KEYBOARD_DESCRIPTOR,
727            Handled::No,
728            vec![
729                (Key::F1, KeyEventType::Pressed),
730                (Key::F1, KeyEventType::Released),
731                (Key::AcBack, KeyEventType::Pressed),
732                (Key::AcBack, KeyEventType::Released),
733            ],
734        );
735        pretty_assertions::assert_eq!(expected, actual);
736    }
737
738    // When a "regular" key (e.g. "A") is pressed in chord with the Search key,
739    // the effect is as if LeftMeta+A was pressed.
740    //
741    // SEARCH_KEY[in]  ___/"""""""""""\__
742    // A[in]           _____/""""\_______
743    //
744    // SEARCH_KEY[out] _____/"""""""""\__
745    // A[out]          ______/""""\______
746    #[fuchsia::test]
747    async fn search_key_a_is_delayed_leftmeta_a() {
748        let inspector = fuchsia_inspect::Inspector::default();
749        let test_node = inspector.root().create_child("test_node");
750        let handler = ChromebookKeyboardHandler::new(&test_node);
751        let input = new_key_sequence(
752            zx::MonotonicInstant::from_nanos(42),
753            &MATCHING_KEYBOARD_DESCRIPTOR,
754            Handled::No,
755            vec![
756                (SEARCH_KEY, KeyEventType::Pressed),
757                (Key::A, KeyEventType::Pressed),
758                (Key::A, KeyEventType::Released),
759                (SEARCH_KEY, KeyEventType::Released),
760            ],
761        );
762        let actual = run_all_events(&handler, input).await;
763        let expected = new_key_sequence(
764            zx::MonotonicInstant::from_nanos(43),
765            &MATCHING_KEYBOARD_DESCRIPTOR,
766            Handled::No,
767            vec![
768                (Key::LeftMeta, KeyEventType::Pressed),
769                (Key::A, KeyEventType::Pressed),
770                (Key::A, KeyEventType::Released),
771                (Key::LeftMeta, KeyEventType::Released),
772            ],
773        );
774        pretty_assertions::assert_eq!(expected, actual);
775    }
776
777    // SEARCH_KEY[in]  ___/"""""""""""\___
778    // F1[in]          ______/""""\_______
779    // F2[in]          _________/""""\____
780    //
781    // SEARCH_KEY[out] ___________________
782    // F1[out]         ______/""""\_______
783    // F2[out]         _________/""""\____
784    #[fuchsia::test]
785    async fn f1_and_f2_interleaved_conversion() {
786        let inspector = fuchsia_inspect::Inspector::default();
787        let test_node = inspector.root().create_child("test_node");
788        let handler = ChromebookKeyboardHandler::new(&test_node);
789        let input = new_key_sequence(
790            zx::MonotonicInstant::from_nanos(42),
791            &MATCHING_KEYBOARD_DESCRIPTOR,
792            Handled::No,
793            vec![
794                (SEARCH_KEY, KeyEventType::Pressed),
795                (Key::F1, KeyEventType::Pressed),
796                (Key::F2, KeyEventType::Pressed),
797                (Key::F1, KeyEventType::Released),
798                (Key::F2, KeyEventType::Released),
799                (SEARCH_KEY, KeyEventType::Released),
800            ],
801        );
802        let actual = run_all_events(&handler, input).await;
803        let expected = new_key_sequence(
804            zx::MonotonicInstant::from_nanos(43),
805            &MATCHING_KEYBOARD_DESCRIPTOR,
806            Handled::No,
807            vec![
808                (Key::F1, KeyEventType::Pressed),
809                (Key::F2, KeyEventType::Pressed),
810                (Key::F1, KeyEventType::Released),
811                (Key::F2, KeyEventType::Released),
812            ],
813        );
814        pretty_assertions::assert_eq!(expected, actual);
815    }
816
817    // SEARCH_KEY[in]  _______/""""""\__
818    // F1[in]          ___/""""""""\____
819    //
820    // SEARCH_KEY[out] _________________
821    // F1[out]         _______/"""""\___
822    // AcBack[out]     __/""""\_________
823    #[fuchsia::test]
824    async fn search_pressed_before_f1_released() {
825        let inspector = fuchsia_inspect::Inspector::default();
826        let test_node = inspector.root().create_child("test_node");
827        let handler = ChromebookKeyboardHandler::new(&test_node);
828        let input = new_key_sequence(
829            zx::MonotonicInstant::from_nanos(42),
830            &MATCHING_KEYBOARD_DESCRIPTOR,
831            Handled::No,
832            vec![
833                (Key::F1, KeyEventType::Pressed),
834                (SEARCH_KEY, KeyEventType::Pressed),
835                (Key::F1, KeyEventType::Released),
836                (SEARCH_KEY, KeyEventType::Released),
837            ],
838        );
839        let actual = run_all_events(&handler, input).await;
840        let expected = new_key_sequence(
841            zx::MonotonicInstant::from_nanos(42),
842            &MATCHING_KEYBOARD_DESCRIPTOR,
843            Handled::No,
844            vec![
845                (Key::AcBack, KeyEventType::Pressed),
846                (Key::AcBack, KeyEventType::Released),
847                (Key::F1, KeyEventType::Pressed),
848                (Key::F1, KeyEventType::Released),
849            ],
850        );
851        pretty_assertions::assert_eq!(expected, actual);
852    }
853
854    // When the Search key gets actuated when there are already remappable keys
855    // actuated, we de-actuate the remapped versions, and actuate remapped ones.
856    // This causes the output to observe both F1, F2 and AcBack and AcRefresh.
857    //
858    // SEARCH_KEY[in]  _______/""""""""""""\___
859    // F1[in]          ___/"""""""""\__________
860    // F2[in]          _____/""""""""""\_______
861    //
862    // SEARCH_KEY[out] ________________________
863    // F1[out]         _______/"""""\__________
864    // AcBack[out]     ___/"""\________________
865    // F2[out]         _______/""""""""\_______
866    // AcRefresh[out]  _____/"\________________
867    #[fuchsia::test]
868    async fn search_pressed_while_f1_and_f2_pressed() {
869        let inspector = fuchsia_inspect::Inspector::default();
870        let test_node = inspector.root().create_child("test_node");
871        let handler = ChromebookKeyboardHandler::new(&test_node);
872        let input = new_key_sequence(
873            zx::MonotonicInstant::from_nanos(42),
874            &MATCHING_KEYBOARD_DESCRIPTOR,
875            Handled::No,
876            vec![
877                (Key::F1, KeyEventType::Pressed),
878                (Key::F2, KeyEventType::Pressed),
879                (SEARCH_KEY, KeyEventType::Pressed),
880                (Key::F1, KeyEventType::Released),
881                (Key::F2, KeyEventType::Released),
882                (SEARCH_KEY, KeyEventType::Released),
883            ],
884        );
885        let actual = run_all_events(&handler, input).await;
886        let expected = new_key_sequence(
887            zx::MonotonicInstant::from_nanos(42),
888            &MATCHING_KEYBOARD_DESCRIPTOR,
889            Handled::No,
890            vec![
891                (Key::AcBack, KeyEventType::Pressed),
892                (Key::AcRefresh, KeyEventType::Pressed),
893                (Key::AcRefresh, KeyEventType::Released),
894                (Key::AcBack, KeyEventType::Released),
895                (Key::F1, KeyEventType::Pressed),
896                (Key::F2, KeyEventType::Pressed),
897                (Key::F1, KeyEventType::Released),
898                (Key::F2, KeyEventType::Released),
899            ],
900        );
901        pretty_assertions::assert_eq!(expected, actual);
902    }
903
904    // An interleaving of remapped (F1, F2) and non-remapped keys (A). In this
905    // case, A is presented without a modifier.  This is not strictly correct,
906    // but is a simpler implementation for an unlikely key combination.
907    //
908    // SEARCH_KEY[in]  _______/"""""""""\______
909    // F1[in]          ___/""""""\_____________
910    // A[in]           _________/"""""\________
911    // F2[in]          ___________/""""""""\___
912    //
913    // SEARCH_KEY[out]  _______________________
914    // F1[out]          _______/""\_____________
915    // AcBack[out]      __/"""\________________
916    // A[out]           ________/"""""\________
917    // F2[out]          __________/"""""\______
918    // AcRefresh[out]  __________________/""\__
919    #[fuchsia::test]
920    async fn key_combination() {
921        let inspector = fuchsia_inspect::Inspector::default();
922        let test_node = inspector.root().create_child("test_node");
923        let handler = ChromebookKeyboardHandler::new(&test_node);
924        let input = new_key_sequence(
925            zx::MonotonicInstant::from_nanos(42),
926            &MATCHING_KEYBOARD_DESCRIPTOR,
927            Handled::No,
928            vec![
929                (Key::F1, KeyEventType::Pressed),
930                (SEARCH_KEY, KeyEventType::Pressed),
931                (Key::A, KeyEventType::Pressed),
932                (Key::F1, KeyEventType::Released),
933                (Key::F2, KeyEventType::Pressed),
934                (Key::A, KeyEventType::Released),
935                (SEARCH_KEY, KeyEventType::Released),
936                (Key::F2, KeyEventType::Released),
937            ],
938        );
939        let actual = run_all_events(&handler, input).await;
940        let expected = new_key_sequence(
941            zx::MonotonicInstant::from_nanos(42),
942            &MATCHING_KEYBOARD_DESCRIPTOR,
943            Handled::No,
944            vec![
945                (Key::AcBack, KeyEventType::Pressed),
946                (Key::AcBack, KeyEventType::Released),
947                (Key::F1, KeyEventType::Pressed),
948                (Key::A, KeyEventType::Pressed),
949                (Key::F1, KeyEventType::Released),
950                (Key::F2, KeyEventType::Pressed),
951                (Key::A, KeyEventType::Released),
952                (Key::F2, KeyEventType::Released),
953                (Key::AcRefresh, KeyEventType::Pressed),
954                (Key::AcRefresh, KeyEventType::Released),
955            ],
956        );
957        pretty_assertions::assert_eq!(expected, actual);
958    }
959
960    #[fuchsia::test]
961    async fn chromebook_keyboard_handler_initialized_with_inspect_node() {
962        let inspector = fuchsia_inspect::Inspector::default();
963        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
964        let _handler = ChromebookKeyboardHandler::new(&fake_handlers_node);
965        diagnostics_assertions::assert_data_tree!(inspector, root: {
966            input_handlers_node: {
967                chromebook_keyboard_handler: {
968                    events_received_count: 0u64,
969                    events_handled_count: 0u64,
970                    last_received_timestamp_ns: 0u64,
971                    "fuchsia.inspect.Health": {
972                        status: "STARTING_UP",
973                        // Timestamp value is unpredictable and not relevant in this context,
974                        // so we only assert that the property is present.
975                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
976                    },
977                }
978            }
979        });
980    }
981
982    #[fuchsia::test]
983    async fn chromebook_keyboard_handler_inspect_counts_events() {
984        let inspector = fuchsia_inspect::Inspector::default();
985        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
986        let handler = ChromebookKeyboardHandler::new(&fake_handlers_node);
987        let events = new_key_sequence(
988            zx::MonotonicInstant::from_nanos(42),
989            &MATCHING_KEYBOARD_DESCRIPTOR,
990            Handled::No,
991            vec![
992                (Key::F1, KeyEventType::Pressed),
993                (Key::F1, KeyEventType::Released),
994                (Key::Down, KeyEventType::Pressed),
995                (Key::Down, KeyEventType::Released),
996            ],
997        );
998        let _ = run_all_events(&handler, events).await;
999        diagnostics_assertions::assert_data_tree!(inspector, root: {
1000            input_handlers_node: {
1001                chromebook_keyboard_handler: {
1002                    events_received_count: 4u64,
1003                    events_handled_count: 0u64,
1004                    last_received_timestamp_ns: 45u64,
1005                    "fuchsia.inspect.Health": {
1006                        status: "STARTING_UP",
1007                        // Timestamp value is unpredictable and not relevant in this context,
1008                        // so we only assert that the property is present.
1009                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1010                    },
1011                }
1012            }
1013        });
1014    }
1015}