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