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