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