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