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