Skip to main content

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