Skip to main content

input_pipeline/
dead_keys_handler.rs

1// Copyright 2021 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//! Implements dead key handling.
6//!
7//! Dead key is a character composition approach where an accented character,
8//! typically from a Western European alphabet, is composed by actuating two
9//! keys on the keyboard:
10//!
11//! 1. A "dead key" which determines which diacritic is to be placed on the
12//!    character, and which produces no immediate output; and
13//! 2. The character onto which the diacritic is to be placed.
14//!
15//! The resulting two successive key actuations produce an effect of single
16//! accented character being emitted.
17//!
18//! The dead key handler relies on keymap already having been applied, and the
19//! use of key meanings.
20//!
21//! This means that the dead key handler must be added to the input pipeline
22//! after the keymap handler in the input pipeline.
23//!
24//! The dead key handler can delay or modify the key meanings, but it never delays nor
25//! modifies key events.  This ensures that clients which require key events see the
26//! key events as they come in.  The key meanings may be delayed because of the delayed
27//! effect of composition.
28//!
29//! The state machine of the dead key handler is watching for dead key and "live" key
30//! combinations, and handles all their possible interleaving. The event sequences
31//! vary from the "obvious" ones such as "dead key press and release followed
32//! by a live key press and release", to not so obvious ones such as: "dead key
33//! press and hold, shift press, live key press and hold followed by another
34//! live key press, followed by arbitrary sequence of key releases".
35//!
36//! See the documentation for [Handler] for some more detail.
37
38use crate::input_device::{
39    Handled, InputDeviceDescriptor, InputDeviceEvent, InputEvent, InputEventType,
40    UnhandledInputEvent,
41};
42use crate::input_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
43use crate::keyboard_binding::KeyboardEvent;
44use crate::metrics;
45use async_trait::async_trait;
46use core::fmt;
47use fidl_fuchsia_ui_input3::{KeyEventType, KeyMeaning};
48use fuchsia_inspect::health::Reporter;
49use metrics_registry::InputPipelineErrorMetricDimensionEvent;
50use std::cell::RefCell;
51use std::rc::Rc;
52use {rust_icu_sys as usys, rust_icu_unorm2 as unorm};
53
54// There probably is a more general method of determining whether the characters
55// are combining characters. But somehow it escapes me now.
56const GRAVE: u32 = 0x300;
57const ACUTE: u32 = 0x301;
58const CIRCUMFLEX: u32 = 0x302;
59const TILDE: u32 = 0x303;
60
61/// Returns true if `c` is one of the dead keys we support.
62///
63/// This should likely be some ICU library function, but I'm not sure which one.
64fn is_dead_key(c: u32) -> bool {
65    match c {
66        GRAVE | ACUTE | CIRCUMFLEX | TILDE => true,
67        _ => false,
68    }
69}
70
71/// Removes the combining effect from a combining code point, leaving only
72/// the diacritic.
73///
74/// This should likely be some ICU library function, but I'm not sure which one.
75fn remove_combination(c: u32) -> u32 {
76    match c {
77        GRAVE => '`' as u32,
78        ACUTE => '\'' as u32,
79        CIRCUMFLEX => '^' as u32,
80        TILDE => '~' as u32,
81        _ => c,
82    }
83}
84
85/// StoredEvent is an InputEvent which is known to be a keyboard event.
86#[derive(Debug, Clone)]
87struct StoredEvent {
88    event: KeyboardEvent,
89    device_descriptor: InputDeviceDescriptor,
90    event_time: zx::MonotonicInstant,
91    trace_id: Option<fuchsia_trace::Id>,
92}
93
94impl fmt::Display for StoredEvent {
95    // Implement a compact [Display], as the device descriptor is not
96    // normally very interesting to see.
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(f, "event: {:?}, event_time: {:?}", &self.event, &self.event_time)
99    }
100}
101
102impl Into<InputEvent> for StoredEvent {
103    /// Converts [StoredEvent] into [InputEvent].
104    fn into(self) -> InputEvent {
105        InputEvent {
106            device_event: InputDeviceEvent::Keyboard(self.event),
107            device_descriptor: self.device_descriptor,
108            event_time: self.event_time,
109            handled: Handled::No,
110            trace_id: self.trace_id,
111        }
112    }
113}
114
115impl Into<Vec<InputEvent>> for StoredEvent {
116    fn into(self) -> Vec<InputEvent> {
117        vec![self.into()]
118    }
119}
120
121/// Whether a [StoredEvent] corresponds to a live key or a dead key.
122enum Liveness {
123    /// The key is dead.
124    Dead,
125    /// The key is live.
126    Live,
127}
128
129/// Whether two events are the same or different by key.
130enum Sameness {
131    /// Two events are the same by key.
132    Same,
133    /// Two events are different.
134    Other,
135}
136
137impl StoredEvent {
138    /// Repackages self into a new [StoredEvent], with `event` replaced as supplied.
139    fn into_with_event(self, event: KeyboardEvent) -> Self {
140        StoredEvent {
141            event,
142            device_descriptor: self.device_descriptor,
143            event_time: self.event_time,
144            trace_id: self.trace_id,
145        }
146    }
147
148    /// Returns the code point contained in this [StoredEvent].
149    fn code_point(&self) -> u32 {
150        match self.event.get_key_meaning() {
151            Some(KeyMeaning::Codepoint(c)) => c,
152            _ => panic!("programming error: requested code point for an event that has none"),
153        }
154    }
155
156    /// Modifies this [StoredEvent] to contain a new code point instead of whatever was there.
157    fn into_with_code_point(self, code_point: u32) -> Self {
158        let new_event =
159            self.event.clone().into_with_key_meaning(Some(KeyMeaning::Codepoint(code_point)));
160        self.into_with_event(new_event)
161    }
162
163    /// Returns true if [StoredEvent] contains a valid code point.
164    fn is_code_point(&self) -> bool {
165        match self.event.get_key_meaning() {
166            // Some nonprintable keys have the code point value set to 0.
167            Some(KeyMeaning::Codepoint(c)) => c != 0,
168            _ => false,
169        }
170    }
171
172    /// Returns whether the key is a dead key or not.  The return value is an enum
173    /// to make the state machine match arms more readable.
174    fn key_liveness(&self) -> Liveness {
175        match self.event.get_key_meaning() {
176            Some(KeyMeaning::Codepoint(c)) if is_dead_key(c) => Liveness::Dead,
177            _ => Liveness::Live,
178        }
179    }
180
181    /// Returns the key event type (pressed, released, or something else)
182    fn e_type(&self) -> KeyEventType {
183        self.event.get_event_type_folded()
184    }
185
186    /// Returns a new [StoredEvent] based on `Self`, but with the combining effect removed.
187    fn into_base_character(self) -> Self {
188        let key_meaning = self.event.get_key_meaning();
189        match key_meaning {
190            Some(KeyMeaning::Codepoint(c)) => {
191                let new_event = self
192                    .event
193                    .clone()
194                    .into_with_key_meaning(Some(KeyMeaning::Codepoint(remove_combination(c))));
195                self.into_with_event(new_event)
196            }
197            _ => self,
198        }
199    }
200
201    /// Returns a new [StoredEvent], but with key meaning removed.
202    fn remove_key_meaning(self) -> Self {
203        let mut event = self.event.clone();
204        // A zero code point means a KeyEvent for which its edit effect should
205        // be ignored. In contrast, an event with an unset code point has by
206        // definition the same effect as if the US QWERTY keymap were applied.
207        // See discussion at:
208        // https://groups.google.com/a/fuchsia.dev/g/ui-input-dev/c/ITYKvbJS6_o/m/8kK0DRccDAAJ
209        event = event.into_with_key_meaning(Some(KeyMeaning::Codepoint(0)));
210        self.into_with_event(event)
211    }
212
213    /// Returns whether the two keys `this` and `that` are in fact the same key
214    /// as per the USB HID usage reported.  The return value is an enum to make
215    /// the state machine match arms more readable.
216    fn key_sameness(this: &StoredEvent, that: &StoredEvent) -> Sameness {
217        match this.event.get_key() == that.event.get_key() {
218            true => Sameness::Same,
219            false => Sameness::Other,
220        }
221    }
222}
223
224/// State contains the current observed state of the dead key state machine.
225///
226/// The dead key composition is started by observing a key press that amounts
227/// to a dead key.  The first non-dead key that gets actuated thereafter becomes
228/// the "live" key that we will attempt to add a diacritic to.  When such a live
229/// key is actuated, we will emit a key meaning equivalent to producing an
230/// accented character.
231///
232/// A complication here is that composition can unfold in any number of ways.
233/// The user could press and release the dead key, then press and release
234/// the live key.  The user could, also, press and hold the dead key, then
235/// press any number of live or dead keys in an arbitrary order.
236///
237/// Another complication is that the user could press the dead key twice, which
238/// should also be handled correctly. In this case, "correct" handling implies
239/// emitting the dead key as an accented character.  Similarly, two different
240/// dead keys pressed in succession are handled by (1) emitting the first as
241/// an accented character, and restarting composition with the second. It is
242/// worth noting that the key press and key release events could be arbitrarily
243/// interleaved for the two dead keys, and that should be handled correctly too.
244///
245/// A third complication is that, while all the composition is taking place,
246/// the pipeline must emit the `KeyEvent`s consistent with the key event protocol,
247/// but keep key meanings suppressed until the time that the key meanings have
248/// been resolved by the combination.
249///
250/// The elements of state are as follows:
251///
252///   * Did we see a dead key press event? (bit `a`)
253///   * Did we see a dead key release event? (bit `b`)
254///   * Did we see a live key press event? (bit `c`)
255///   * Did we see a live key release event? (bit `d`)
256///
257/// Almost any variation of the above elements is possible and allowed.  Even
258/// the states that ostensibly shouldn't be possible (e.g. observed a release
259/// event before a press) should be accounted for in order to implement
260/// self-correcting behavior if needed.  The [State] enum below encodes each
261/// state as a name `Sdcba`, where each of `a..d` are booleans, encoded
262/// as characters `0` and `1` as conventional. So for example, `S0101`
263/// is a state where we observed a dead key press event, and a live key press
264/// event.  I made an experiment where I tried to use more illustrative state
265/// names, but the number of variations didn't make the resulting names any more
266/// meaningful compared to the current state name encoding scheme. So compact
267/// naming it is.
268#[derive(Debug, Clone)]
269enum State {
270    /// We have yet to see a key to act on.
271    S0000,
272
273    /// We saw an actuation of a dead key.
274    S0001 { dead_key_down: Box<StoredEvent> },
275
276    /// A dead key was pressed and released.
277    S0011 { dead_key_down: Box<StoredEvent>, dead_key_up: Box<StoredEvent> },
278
279    /// A dead key was pressed and released, followed by a live key press.
280    S0111 {
281        dead_key_down: Box<StoredEvent>,
282        dead_key_up: Box<StoredEvent>,
283        live_key_down: Box<StoredEvent>,
284    },
285
286    /// A dead key was pressed, followed by a live key press.
287    S0101 { dead_key_down: Box<StoredEvent>, live_key_down: Box<StoredEvent> },
288
289    /// A dead key was pressed, then a live key was pressed and released.
290    S1101 { dead_key_down: Box<StoredEvent> },
291}
292
293#[derive(Debug)]
294pub struct DeadKeysHandler {
295    /// Tracks the current state of the dead key composition.
296    state: RefCell<State>,
297
298    /// The unicode normalizer used for composition.
299    normalizer: unorm::UNormalizer,
300
301    /// This handler requires ICU data to be live. This is ensured by holding
302    /// a reference to an ICU data loader.
303    _data: icu_data::Loader,
304
305    /// The metrics logger.
306    metrics_logger: metrics::MetricsLogger,
307
308    /// The inventory of this handler's Inspect status.
309    pub inspect_status: InputHandlerStatus,
310}
311
312/// This trait implementation allows the [Handler] to be hooked up into the input
313/// pipeline.
314impl Handler for DeadKeysHandler {
315    fn set_handler_healthy(self: std::rc::Rc<Self>) {
316        self.inspect_status.health_node.borrow_mut().set_ok();
317    }
318
319    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
320        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
321    }
322
323    fn get_name(&self) -> &'static str {
324        "DeadKeysHandler"
325    }
326
327    fn interest(&self) -> Vec<InputEventType> {
328        vec![InputEventType::Keyboard]
329    }
330}
331
332#[async_trait(?Send)]
333impl UnhandledInputHandler for DeadKeysHandler {
334    async fn handle_unhandled_input_event(
335        self: Rc<Self>,
336        unhandled_input_event: UnhandledInputEvent,
337    ) -> Vec<InputEvent> {
338        self.handle_unhandled_input_event_internal(unhandled_input_event)
339    }
340}
341
342impl DeadKeysHandler {
343    /// Creates a new instance of the dead keys handler.
344    pub fn new(
345        icu_data: icu_data::Loader,
346        input_handlers_node: &fuchsia_inspect::Node,
347        metrics_logger: metrics::MetricsLogger,
348    ) -> Rc<Self> {
349        let inspect_status = InputHandlerStatus::new(
350            input_handlers_node,
351            "dead_keys_handler",
352            /* generates_events */ false,
353        );
354        Rc::new(Self {
355            state: RefCell::new(State::S0000),
356            metrics_logger,
357            // The NFC normalizer performs the needed composition and is not
358            // lossy.
359            normalizer: unorm::UNormalizer::new_nfc().unwrap(),
360            _data: icu_data,
361            inspect_status,
362        })
363    }
364
365    fn handle_unhandled_input_event_internal(
366        self: Rc<Self>,
367        unhandled_input_event: UnhandledInputEvent,
368    ) -> Vec<InputEvent> {
369        fuchsia_trace::duration!("input", "dead_keys_handler");
370        match unhandled_input_event {
371            UnhandledInputEvent {
372                device_event: InputDeviceEvent::Keyboard(event),
373                device_descriptor,
374                event_time,
375                trace_id,
376            } => {
377                fuchsia_trace::duration!("input", "dead_keys_handler[processing]");
378                if let Some(trace_id) = trace_id {
379                    fuchsia_trace::flow_step!("input", "event_in_input_pipeline", trace_id.into());
380                }
381
382                self.inspect_status.count_received_event(&event_time);
383                let event = StoredEvent { event, device_descriptor, event_time, trace_id };
384                // Separated into two statements to ensure the logs are not truncated.
385                log::debug!("state: {:?}", self.state.borrow());
386                log::debug!("event: {}", &event);
387                let result = self.process_keyboard_event(event);
388                log::debug!("result: {:?}", &result);
389                result
390            }
391
392            // Pass other events unchanged.
393            _ => {
394                self.metrics_logger.log_error(
395                    InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
396                    std::format!(
397                        "uninterested input event: {:?}",
398                        unhandled_input_event.get_event_type()
399                    ),
400                );
401                vec![InputEvent::from(unhandled_input_event)]
402            }
403        }
404    }
405
406    /// Sets the internal handler state to `new_state`.
407    fn set_state(self: &Rc<Self>, new_state: State) {
408        *(self.state.borrow_mut()) = new_state;
409    }
410
411    /// Attaches a key meaning to each passing keyboard event.
412    ///
413    /// Underlying this function is a state machine which registers the flow of dead and live keys
414    /// after each reported event, and modifies the input event stream accordingly.  For example,
415    /// a sequence of events where a dead key is pressed and released, followed by a live key
416    /// press and release, results in a composed character being emitted.  The state machine
417    /// takese care of this sequence, but also of other less obvious sequences and their effects.
418    fn process_keyboard_event(self: &Rc<Self>, event: StoredEvent) -> Vec<InputEvent> {
419        if !event.is_code_point() {
420            // Pass through any non-codepoint events.
421            return event.into();
422        }
423        let old_state = self.state.borrow().clone();
424        match old_state {
425            // We are waiting for the composition to begin.
426            State::S0000 => match (event.key_liveness(), event.e_type()) {
427                // A dead key press starts composition.  We advance to the next
428                // state machine state, and eliminate any key meaning from the
429                // key event, since we anticipate its use in composition.
430                (Liveness::Dead, KeyEventType::Pressed) => {
431                    self.set_state(State::S0001 { dead_key_down: Box::new(event.clone()) });
432                    event.remove_key_meaning().into()
433                }
434
435                // A dead key release while we're waiting for a dead key press,
436                // this is probably a remnant of an earlier double press, remove the
437                // combining from it and forward.  Keep waiting for composition
438                // to begin.
439                (Liveness::Dead, KeyEventType::Released) => event.into_base_character().into(),
440
441                // Any other events can be forwarded unmodified.
442                _ => event.into(),
443            },
444
445            // We have seen a dead key press, but not release.
446            State::S0001 { dead_key_down } => {
447                match (
448                    event.key_liveness(),
449                    StoredEvent::key_sameness(&event, &dead_key_down),
450                    event.e_type(),
451                ) {
452                    // The same dead key that was pressed the other time was released.
453                    // Emit a stripped version, and start waiting for a live key.
454                    (Liveness::Dead, Sameness::Same, KeyEventType::Released) => {
455                        self.set_state(State::S0011 {
456                            dead_key_down,
457                            dead_key_up: Box::new(event.clone()),
458                        });
459                        event.remove_key_meaning().into()
460                    }
461
462                    // Another dead key was released at this point.  Since
463                    // we can not start a new combination here, we must forward
464                    // it with meaning stripped.
465                    (Liveness::Dead, Sameness::Other, KeyEventType::Released) => {
466                        event.remove_key_meaning().into()
467                    }
468
469                    // The same dead key was pressed again, while we have seen
470                    // it pressed before.  This can happen when autorepeat kicks
471                    // in.  We treat this the same as two successive actuations
472                    // i.e. we send a stripped version of the character, and
473                    // go back to waiting.
474                    (Liveness::Dead, Sameness::Same, KeyEventType::Pressed) => {
475                        self.set_state(State::S0000);
476                        event.into_base_character().into()
477                    }
478
479                    // A different dead key was pressed.  This stops the ongoing
480                    // composition, and starts a new one with a new dead key.  However,
481                    // what we emit is a bit subtle: we emit a key press event
482                    // for the *new* key, but with a key meaning of the stripped
483                    // version of the current key.
484                    (Liveness::Dead, Sameness::Other, KeyEventType::Pressed) => {
485                        let current_removed = dead_key_down.as_ref().clone().into_base_character();
486                        self.set_state(State::S0001 { dead_key_down: Box::new(event.clone()) });
487                        event.into_with_code_point(current_removed.code_point()).into()
488                    }
489
490                    // A live key was pressed while the dead key is held down. Yay!
491                    //
492                    // Compose and ship out the live key with attached new meaning.
493                    //
494                    // A very similar piece of code happens in the state `State::S0011`,
495                    // except we get there through a different sequence of events.
496                    // Please refer to that code for the details about composition.
497                    (Liveness::Live, _, KeyEventType::Pressed) => {
498                        let maybe_composed = self.normalizer.compose_pair(
499                            event.code_point() as usys::UChar32,
500                            dead_key_down.code_point() as usys::UChar32,
501                        );
502
503                        if maybe_composed >= 0 {
504                            // Composition was a success.
505                            let composed_event = event.into_with_code_point(maybe_composed as u32);
506                            self.set_state(State::S0101 {
507                                dead_key_down,
508                                live_key_down: Box::new(composed_event.clone()),
509                            });
510                            return composed_event.into();
511                        } else {
512                            // FAIL!
513                            self.set_state(State::S0101 {
514                                dead_key_down,
515                                live_key_down: Box::new(event.clone()),
516                            });
517                            return event.into();
518                        }
519                    }
520                    // All other key events are forwarded unmodified.
521                    _ => event.into(),
522                }
523            }
524
525            // The dead key was pressed and released, the first live key that
526            // gets pressed after that now will be used for the composition.
527            State::S0011 { dead_key_down, dead_key_up } => {
528                match (event.key_liveness(), event.e_type()) {
529                    // We observed a dead key actuation.
530                    (Liveness::Dead, KeyEventType::Pressed) => {
531                        match StoredEvent::key_sameness(&dead_key_down, &event) {
532                            // The user pressed the same dead key again.  Let's "compose" it by
533                            // stripping its diacritic and making that a compose key.
534                            Sameness::Same => {
535                                let event = event.into_base_character();
536                                self.set_state(State::S0111 {
537                                    dead_key_down,
538                                    dead_key_up,
539                                    live_key_down: Box::new(event.clone()),
540                                });
541                                event.into()
542                            }
543                            // The user pressed a different dead key. It would have been nice
544                            // to start a new composition, but we can not express that with the
545                            // KeyEvent API, since that would require emitting spurious press and
546                            // release key events for the dead key press and release.
547                            //
548                            // Instead, forward the key unmodified and cancel
549                            // the composition.  We may revisit this if the KeyEvent API is
550                            // changed to allow decoupling key events from key meanings.
551                            Sameness::Other => {
552                                self.set_state(State::S0000);
553                                event.into_base_character().into()
554                            }
555                        }
556                    }
557
558                    // We observed a dead key release.  This is likely a dead key
559                    // from the *previous* composition attempt.  Nothing to do here,
560                    // except forward it stripped of key meaning.
561                    (Liveness::Dead, KeyEventType::Released) => event.remove_key_meaning().into(),
562
563                    // Oh, frabjous day! Someone pressed a live key that may be
564                    // possible to combine!  Let's try it out!  If composition is
565                    // a success, emit the current key with the meaning set to
566                    // the composed character.
567                    (Liveness::Live, KeyEventType::Pressed) => {
568                        let maybe_composed = self.normalizer.compose_pair(
569                            event.code_point() as usys::UChar32,
570                            dead_key_down.code_point() as usys::UChar32,
571                        );
572
573                        if maybe_composed >= 0 {
574                            // Composition was a success.
575                            // Emit the composed event, remember it also when
576                            // transitioning to S0111, so we can recover the key meaning
577                            // when the live key is released.
578                            let composed_event = event.into_with_code_point(maybe_composed as u32);
579                            self.set_state(State::S0111 {
580                                dead_key_down,
581                                dead_key_up,
582                                live_key_down: Box::new(composed_event.clone()),
583                            });
584                            return composed_event.into();
585                        } else {
586                            log::debug!("compose failed for: {}\n", &event);
587                            // FAIL!
588                            // Composition failed, what now?  We would need to
589                            // emit TWO characters - one for the now-defunct
590                            // dead key, and another for the current live key.
591                            // But this is not possible, since we may not emit
592                            // more combining key events, but must always emit
593                            // both the key and the key meaning since that is
594                            // how our protocol works.  Well, we reached the
595                            // limit of what key event composition may do, so
596                            // let's simply agree to emit the current event
597                            // unmodified and forget we had the dead key.
598                            self.set_state(State::S0111 {
599                                dead_key_down,
600                                dead_key_up,
601                                live_key_down: Box::new(event.clone()),
602                            });
603                            return event.into();
604                        }
605                    }
606
607                    // All other key events are forwarded unmodified.
608                    _ => event.into(),
609                }
610            }
611
612            // We already combined the live key with the dead key, and are
613            // now waiting for the live key to be released.
614            State::S0111 { dead_key_down, dead_key_up, live_key_down } => {
615                match (
616                    event.key_liveness(),
617                    // Here we compare the current key with the live key down,
618                    // unlike in prior states.
619                    StoredEvent::key_sameness(&event, &live_key_down),
620                    event.e_type(),
621                ) {
622                    // This is what we've been waiting for: the live key is now
623                    // lifted.  Emit the live key release using the same code point
624                    // as we used when the key went down, and we're done.
625                    (Liveness::Live, Sameness::Same, KeyEventType::Released) => {
626                        self.set_state(State::S0000);
627                        event.into_with_code_point(live_key_down.code_point()).into()
628                    }
629
630                    // A second press of the live key we're combining.  This is
631                    // probably a consequence of autorepeat.  The effect should
632                    // be to complete the composition and continue emitting the
633                    // "base" key meaning for any further repeats; but also
634                    // continue waiting for a key release.
635                    (Liveness::Live, Sameness::Same, KeyEventType::Pressed) => {
636                        let base_codepoint = event.code_point();
637                        let combined_event =
638                            event.clone().into_with_code_point(live_key_down.code_point());
639                        // We emit a combined key, but further repeats will use the
640                        // base code point and not combine.
641                        self.set_state(State::S0111 {
642                            dead_key_down,
643                            dead_key_up,
644                            live_key_down: Box::new(event.into_with_code_point(base_codepoint)),
645                        });
646                        combined_event.into()
647                    }
648
649                    // If another live key event comes in, just forward it, and
650                    // continue waiting for the last live key release.
651                    (Liveness::Live, Sameness::Other, _) => event.into(),
652
653                    // Another dead key has been pressed in addition to what
654                    // had been pressed before. So now, we are waiting for the
655                    // user to release the live key we already composed, but the
656                    // user is again pressing a compose key instead.
657                    //
658                    // Ideally, we'd want to start new composition with the
659                    // new dead key.  But, there's still the issue with the
660                    // live key that is still being pressed: when it is eventually
661                    // released, we want to have it have exactly the same key
662                    // meaning as what we emitted for when it was pressed.  But,
663                    // that may happen arbitrarily late afterwards, and we'd
664                    // prefer not to keep any composition state for that long.
665                    //
666                    // That suggests that we must not honor this new dead key
667                    // as composition.  But, also, we must not drop the key
668                    // event on the floor, since the clients that read key
669                    // events must receive it.  So, we just *turn* off
670                    // the combining effect on this key, forward it like that,
671                    // and continue waiting for the key release.
672                    (Liveness::Dead, _, KeyEventType::Pressed) => event.remove_key_meaning().into(),
673
674                    (Liveness::Dead, _, KeyEventType::Released) => {
675                        match StoredEvent::key_sameness(&event, &live_key_down) {
676                            // Special: if the released key a dead key and the same as the
677                            // "live" composing key, then we're seeing a release of a doubly-
678                            // pressed dead key.  This one needs to be emitted as a diacritic.
679                            Sameness::Same => {
680                                self.set_state(State::S0000);
681                                event.into_base_character().into()
682                            }
683
684                            // All other dead keys are forwarded with stripped key meanings.
685                            // We have no way to handle them further.
686                            Sameness::Other => event.remove_key_meaning().into(),
687                        }
688                    }
689
690                    // Forward any other events unmodified.
691                    _ => event.into(),
692                }
693            }
694
695            // The user pressed and is holding the dead key; and pressed and
696            // is holding a live key.
697            State::S0101 { dead_key_down, live_key_down } => {
698                match (event.key_liveness(), event.e_type()) {
699                    // The same dead key we're already holding is pressed.  Just forward
700                    // the key event, but not meaning.
701                    (Liveness::Dead, KeyEventType::Pressed) => event.remove_key_meaning().into(),
702
703                    (Liveness::Dead, KeyEventType::Released) => {
704                        // The dead key that we are using for combining is released.
705                        // Emit its release event without a key meaning and go to a
706                        // state that expects a release of the live key.
707                        match StoredEvent::key_sameness(&dead_key_down, &event) {
708                            Sameness::Same => {
709                                self.set_state(State::S0111 {
710                                    dead_key_down,
711                                    dead_key_up: Box::new(event.clone()),
712                                    live_key_down,
713                                });
714                                event.remove_key_meaning().into()
715                            }
716
717                            // Other dead key is released.  Remove its key meaning, but forward.
718                            Sameness::Other => event.remove_key_meaning().into(),
719                        }
720                    }
721                    (Liveness::Live, KeyEventType::Pressed) => {
722                        match StoredEvent::key_sameness(&live_key_down, &event) {
723                            // The currently pressed live key is pressed again.
724                            // This is autorepeat.  We emit one composed key, but any
725                            // further emitted keys will not compose.  This
726                            // should be similar to `State::S0111`, except the
727                            // transition is back to *this* state.
728                            Sameness::Same => {
729                                let base_codepoint = event.code_point();
730                                let combined_event =
731                                    event.clone().into_with_code_point(live_key_down.code_point());
732                                self.set_state(State::S0101 {
733                                    dead_key_down,
734                                    live_key_down: Box::new(
735                                        event.into_with_code_point(base_codepoint),
736                                    ),
737                                });
738                                combined_event.into()
739                            }
740                            Sameness::Other => event.into(),
741                        }
742                    }
743                    (Liveness::Live, KeyEventType::Released) => {
744                        match StoredEvent::key_sameness(&live_key_down, &event) {
745                            Sameness::Same => {
746                                self.set_state(State::S1101 { dead_key_down });
747                                event.into_with_code_point(live_key_down.code_point()).into()
748                            }
749
750                            // Any other release just gets forwarded.
751                            Sameness::Other => event.into(),
752                        }
753                    }
754
755                    // Forward any other events unmodified
756                    _ => event.into(),
757                }
758            }
759
760            // The dead key is still actuated, but we already sent out the
761            // combined versions of the live key.
762            State::S1101 { dead_key_down } => {
763                match (event.key_liveness(), event.e_type()) {
764                    (Liveness::Dead, KeyEventType::Pressed) => {
765                        // Two possible cases here, but the outcome is the
766                        // same:
767                        //
768                        // The same dead key is pressed again.  Let's not
769                        // do any more compositions here.
770                        //
771                        // A different dead key has been pressed.  We can
772                        // not start a new composition while we have not
773                        // closed out the current composition.  For this
774                        // reason we ignore the other key.
775                        //
776                        // A real compositioning API would perhaps allow us
777                        // to stack compositions on top of each other, but
778                        // we will require any such consumers to go talk to
779                        // the text editing API instead.
780                        event.remove_key_meaning().into()
781                    }
782
783                    (Liveness::Dead, KeyEventType::Released) => {
784                        match StoredEvent::key_sameness(&dead_key_down, &event) {
785                            // The dead key is released, the composition is
786                            // done, let's close up shop.
787                            Sameness::Same => {
788                                self.set_state(State::S0000);
789                                event.remove_key_meaning().into()
790                            }
791                            // A dead key was released, but not the one that we
792                            // are combining by.  Forward with the combining
793                            // effect stripped.
794                            Sameness::Other => event.remove_key_meaning().into(),
795                        }
796                    }
797
798                    // Any additional live keys, no matter if they are the same
799                    // as the one currently being composed, will *not* be composed,
800                    // we forward them unmodified as we wait to close off this
801                    // composition.
802                    //
803                    // Forward any other events unmodified.
804                    _ => event.into(),
805                }
806            }
807        }
808    }
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814    use crate::testing_utilities;
815    use fidl_fuchsia_input::Key;
816    use fidl_fuchsia_input_report::ConsumerControlButton;
817
818    use pretty_assertions::assert_eq;
819    use std::convert::TryFrom as _;
820
821    // Creates a new keyboard event for testing.
822    fn new_event(
823        key: Key,
824        event_type: KeyEventType,
825        key_meaning: Option<KeyMeaning>,
826    ) -> UnhandledInputEvent {
827        UnhandledInputEvent::try_from(testing_utilities::create_keyboard_event_with_handled(
828            key,
829            event_type,
830            /*modifiers=*/ None,
831            /*event_time*/ zx::MonotonicInstant::ZERO,
832            &InputDeviceDescriptor::Fake,
833            /*keymap=*/ None,
834            key_meaning,
835            /*handled=*/ Handled::No,
836        ))
837        .unwrap()
838    }
839
840    // Tests some common keyboard input use cases with dead keys actuation.
841    #[test]
842    fn test_input_processing() {
843        // A zero codepoint is a way to let the consumers know that this key
844        // event should have no effect on the edited text; even though its
845        // key event may have other effects, such as moving the hero across
846        // the screen in a game.
847        const ZERO_CP: Option<KeyMeaning> = Some(KeyMeaning::Codepoint(0));
848
849        #[derive(Debug)]
850        struct TestCase {
851            name: &'static str,
852            // The sequence of input events at the input of the dead keys
853            // handler.
854            inputs: Vec<UnhandledInputEvent>,
855            // The expected sequence of input events, after being transformed
856            // by the dead keys handler.
857            expected: Vec<UnhandledInputEvent>,
858        }
859        let tests: Vec<TestCase> = vec![
860            TestCase {
861                name: "passthrough",
862                inputs: vec![
863                    new_event(
864                        Key::A,
865                        KeyEventType::Pressed,
866                        Some(KeyMeaning::Codepoint('A' as u32)),
867                    ),
868                    new_event(
869                        Key::A,
870                        KeyEventType::Released,
871                        Some(KeyMeaning::Codepoint('A' as u32)),
872                    ),
873                ],
874                expected: vec![
875                    new_event(
876                        Key::A,
877                        KeyEventType::Pressed,
878                        Some(KeyMeaning::Codepoint('A' as u32)),
879                    ),
880                    new_event(
881                        Key::A,
882                        KeyEventType::Released,
883                        Some(KeyMeaning::Codepoint('A' as u32)),
884                    ),
885                ],
886            },
887            TestCase {
888                name: "A circumflex - dead key first, then live key",
889                inputs: vec![
890                    new_event(
891                        Key::Key5,
892                        KeyEventType::Pressed,
893                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
894                    ),
895                    new_event(
896                        Key::Key5,
897                        KeyEventType::Released,
898                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
899                    ),
900                    new_event(
901                        Key::A,
902                        KeyEventType::Pressed,
903                        Some(KeyMeaning::Codepoint('A' as u32)),
904                    ),
905                    new_event(
906                        Key::A,
907                        KeyEventType::Released,
908                        Some(KeyMeaning::Codepoint('A' as u32)),
909                    ),
910                ],
911                expected: vec![
912                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
913                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
914                    new_event(
915                        Key::A,
916                        KeyEventType::Pressed,
917                        Some(KeyMeaning::Codepoint('Â' as u32)),
918                    ),
919                    new_event(
920                        Key::A,
921                        KeyEventType::Released,
922                        Some(KeyMeaning::Codepoint('Â' as u32)),
923                    ),
924                ],
925            },
926            TestCase {
927                name: "A circumflex - dead key held all the way through composition",
928                inputs: vec![
929                    new_event(
930                        Key::Key5,
931                        KeyEventType::Pressed,
932                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
933                    ),
934                    new_event(
935                        Key::A,
936                        KeyEventType::Pressed,
937                        Some(KeyMeaning::Codepoint('A' as u32)),
938                    ),
939                    new_event(
940                        Key::A,
941                        KeyEventType::Released,
942                        Some(KeyMeaning::Codepoint('A' as u32)),
943                    ),
944                    new_event(
945                        Key::Key5,
946                        KeyEventType::Released,
947                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
948                    ),
949                ],
950                expected: vec![
951                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
952                    new_event(
953                        Key::A,
954                        KeyEventType::Pressed,
955                        Some(KeyMeaning::Codepoint('Â' as u32)),
956                    ),
957                    new_event(
958                        Key::A,
959                        KeyEventType::Released,
960                        Some(KeyMeaning::Codepoint('Â' as u32)),
961                    ),
962                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
963                ],
964            },
965            TestCase {
966                name: "A circumflex - dead key held until the live key was down",
967                inputs: vec![
968                    new_event(
969                        Key::Key5,
970                        KeyEventType::Pressed,
971                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
972                    ),
973                    new_event(
974                        Key::A,
975                        KeyEventType::Pressed,
976                        Some(KeyMeaning::Codepoint('A' as u32)),
977                    ),
978                    new_event(
979                        Key::Key5,
980                        KeyEventType::Released,
981                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
982                    ),
983                    new_event(
984                        Key::A,
985                        KeyEventType::Released,
986                        Some(KeyMeaning::Codepoint('A' as u32)),
987                    ),
988                ],
989                expected: vec![
990                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
991                    new_event(
992                        Key::A,
993                        KeyEventType::Pressed,
994                        Some(KeyMeaning::Codepoint('Â' as u32)),
995                    ),
996                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
997                    new_event(
998                        Key::A,
999                        KeyEventType::Released,
1000                        Some(KeyMeaning::Codepoint('Â' as u32)),
1001                    ),
1002                ],
1003            },
1004            TestCase {
1005                name: "Combining character pressed twice - results in a single diacritic",
1006                inputs: vec![
1007                    new_event(
1008                        Key::Key5,
1009                        KeyEventType::Pressed,
1010                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1011                    ),
1012                    new_event(
1013                        Key::Key5,
1014                        KeyEventType::Released,
1015                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1016                    ),
1017                    new_event(
1018                        Key::Key5,
1019                        KeyEventType::Pressed,
1020                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1021                    ),
1022                    new_event(
1023                        Key::Key5,
1024                        KeyEventType::Released,
1025                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1026                    ),
1027                ],
1028                expected: vec![
1029                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1030                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1031                    new_event(
1032                        Key::Key5,
1033                        KeyEventType::Pressed,
1034                        Some(KeyMeaning::Codepoint('^' as u32)),
1035                    ),
1036                    new_event(
1037                        Key::Key5,
1038                        KeyEventType::Released,
1039                        Some(KeyMeaning::Codepoint('^' as u32)),
1040                    ),
1041                ],
1042            },
1043            TestCase {
1044                name: "A circumflex - dead key spans live key",
1045                inputs: vec![
1046                    new_event(
1047                        Key::Key5,
1048                        KeyEventType::Pressed,
1049                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1050                    ),
1051                    new_event(
1052                        Key::A,
1053                        KeyEventType::Pressed,
1054                        Some(KeyMeaning::Codepoint('A' as u32)),
1055                    ),
1056                    new_event(
1057                        Key::A,
1058                        KeyEventType::Released,
1059                        Some(KeyMeaning::Codepoint('A' as u32)),
1060                    ),
1061                    new_event(
1062                        Key::Key5,
1063                        KeyEventType::Released,
1064                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1065                    ),
1066                ],
1067                expected: vec![
1068                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1069                    new_event(
1070                        Key::A,
1071                        KeyEventType::Pressed,
1072                        Some(KeyMeaning::Codepoint('Â' as u32)),
1073                    ),
1074                    new_event(
1075                        Key::A,
1076                        KeyEventType::Released,
1077                        Some(KeyMeaning::Codepoint('Â' as u32)),
1078                    ),
1079                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1080                ],
1081            },
1082            TestCase {
1083                name: "Only the first key after the dead key actuation is composed",
1084                inputs: vec![
1085                    new_event(
1086                        Key::Key5,
1087                        KeyEventType::Pressed,
1088                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1089                    ),
1090                    new_event(
1091                        Key::Key5,
1092                        KeyEventType::Released,
1093                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1094                    ),
1095                    new_event(
1096                        Key::A,
1097                        KeyEventType::Pressed,
1098                        Some(KeyMeaning::Codepoint('A' as u32)),
1099                    ),
1100                    new_event(
1101                        Key::E,
1102                        KeyEventType::Pressed,
1103                        Some(KeyMeaning::Codepoint('E' as u32)),
1104                    ),
1105                    new_event(
1106                        Key::A,
1107                        KeyEventType::Released,
1108                        Some(KeyMeaning::Codepoint('A' as u32)),
1109                    ),
1110                    new_event(
1111                        Key::E,
1112                        KeyEventType::Released,
1113                        Some(KeyMeaning::Codepoint('E' as u32)),
1114                    ),
1115                ],
1116                expected: vec![
1117                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1118                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1119                    new_event(
1120                        Key::A,
1121                        KeyEventType::Pressed,
1122                        Some(KeyMeaning::Codepoint('Â' as u32)),
1123                    ),
1124                    new_event(
1125                        Key::E,
1126                        KeyEventType::Pressed,
1127                        Some(KeyMeaning::Codepoint('E' as u32)),
1128                    ),
1129                    new_event(
1130                        Key::A,
1131                        KeyEventType::Released,
1132                        Some(KeyMeaning::Codepoint('Â' as u32)),
1133                    ),
1134                    new_event(
1135                        Key::E,
1136                        KeyEventType::Released,
1137                        Some(KeyMeaning::Codepoint('E' as u32)),
1138                    ),
1139                ],
1140            },
1141            TestCase {
1142                name: "Modifier keys are not affected",
1143                inputs: vec![
1144                    new_event(
1145                        Key::Key5,
1146                        KeyEventType::Pressed,
1147                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1148                    ),
1149                    new_event(
1150                        Key::Key5,
1151                        KeyEventType::Released,
1152                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1153                    ),
1154                    new_event(Key::LeftShift, KeyEventType::Pressed, ZERO_CP),
1155                    new_event(
1156                        Key::A,
1157                        KeyEventType::Pressed,
1158                        Some(KeyMeaning::Codepoint('A' as u32)),
1159                    ),
1160                    new_event(
1161                        Key::A,
1162                        KeyEventType::Released,
1163                        Some(KeyMeaning::Codepoint('A' as u32)),
1164                    ),
1165                    new_event(Key::LeftShift, KeyEventType::Released, ZERO_CP),
1166                ],
1167                expected: vec![
1168                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1169                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1170                    new_event(Key::LeftShift, KeyEventType::Pressed, ZERO_CP),
1171                    new_event(
1172                        Key::A,
1173                        KeyEventType::Pressed,
1174                        Some(KeyMeaning::Codepoint('Â' as u32)),
1175                    ),
1176                    new_event(
1177                        Key::A,
1178                        KeyEventType::Released,
1179                        Some(KeyMeaning::Codepoint('Â' as u32)),
1180                    ),
1181                    new_event(Key::LeftShift, KeyEventType::Released, ZERO_CP),
1182                ],
1183            },
1184            TestCase {
1185                name: "Two dead keys in succession - no compose",
1186                inputs: vec![
1187                    new_event(
1188                        Key::Key5,
1189                        KeyEventType::Pressed,
1190                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1191                    ),
1192                    new_event(
1193                        Key::Key5,
1194                        KeyEventType::Released,
1195                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1196                    ),
1197                    new_event(
1198                        Key::A,
1199                        KeyEventType::Pressed,
1200                        Some(KeyMeaning::Codepoint(GRAVE as u32)),
1201                    ),
1202                    new_event(
1203                        Key::A,
1204                        KeyEventType::Released,
1205                        Some(KeyMeaning::Codepoint(GRAVE as u32)),
1206                    ),
1207                ],
1208                expected: vec![
1209                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1210                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1211                    new_event(
1212                        Key::A,
1213                        KeyEventType::Pressed,
1214                        Some(KeyMeaning::Codepoint('`' as u32)),
1215                    ),
1216                    new_event(
1217                        Key::A,
1218                        KeyEventType::Released,
1219                        Some(KeyMeaning::Codepoint('`' as u32)),
1220                    ),
1221                ],
1222            },
1223            TestCase {
1224                name: "Compose with capital letter",
1225                inputs: vec![
1226                    new_event(
1227                        Key::Key5,
1228                        KeyEventType::Pressed,
1229                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1230                    ),
1231                    new_event(
1232                        Key::Key5,
1233                        KeyEventType::Released,
1234                        Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
1235                    ),
1236                    new_event(
1237                        Key::LeftShift,
1238                        KeyEventType::Pressed,
1239                        Some(KeyMeaning::Codepoint(0)),
1240                    ),
1241                    new_event(
1242                        Key::A,
1243                        KeyEventType::Pressed,
1244                        Some(KeyMeaning::Codepoint('A' as u32)),
1245                    ),
1246                    new_event(
1247                        Key::A,
1248                        KeyEventType::Released,
1249                        Some(KeyMeaning::Codepoint('A' as u32)),
1250                    ),
1251                    new_event(
1252                        Key::LeftShift,
1253                        KeyEventType::Released,
1254                        Some(KeyMeaning::Codepoint(0)),
1255                    ),
1256                ],
1257                expected: vec![
1258                    new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
1259                    new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
1260                    new_event(
1261                        Key::LeftShift,
1262                        KeyEventType::Pressed,
1263                        Some(KeyMeaning::Codepoint(0)),
1264                    ),
1265                    new_event(
1266                        Key::A,
1267                        KeyEventType::Pressed,
1268                        Some(KeyMeaning::Codepoint('Â' as u32)),
1269                    ),
1270                    new_event(
1271                        Key::A,
1272                        KeyEventType::Released,
1273                        Some(KeyMeaning::Codepoint('Â' as u32)),
1274                    ),
1275                    new_event(
1276                        Key::LeftShift,
1277                        KeyEventType::Released,
1278                        Some(KeyMeaning::Codepoint(0)),
1279                    ),
1280                ],
1281            },
1282        ];
1283        let inspector = fuchsia_inspect::Inspector::default();
1284        let test_node = inspector.root().create_child("test_node");
1285        let loader = icu_data::Loader::new().unwrap();
1286        let handler =
1287            super::DeadKeysHandler::new(loader, &test_node, metrics::MetricsLogger::default());
1288        for test in tests {
1289            let actuals: Vec<InputEvent> = test
1290                .inputs
1291                .into_iter()
1292                .map(|event| handler.clone().handle_unhandled_input_event_internal(event))
1293                .flatten()
1294                .collect();
1295            assert_eq!(
1296                test.expected.into_iter().map(InputEvent::from).collect::<Vec<_>>(),
1297                actuals,
1298                "in test: {}",
1299                test.name
1300            );
1301        }
1302    }
1303
1304    #[fuchsia::test]
1305    async fn dead_keys_handler_initialized_with_inspect_node() {
1306        let loader = icu_data::Loader::new().unwrap();
1307        let inspector = fuchsia_inspect::Inspector::default();
1308        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
1309        let _handler =
1310            DeadKeysHandler::new(loader, &fake_handlers_node, metrics::MetricsLogger::default());
1311        diagnostics_assertions::assert_data_tree!(inspector, root: {
1312            input_handlers_node: {
1313                dead_keys_handler: {
1314                    events_received_count: 0u64,
1315                    events_handled_count: 0u64,
1316                    last_received_timestamp_ns: 0u64,
1317                    "fuchsia.inspect.Health": {
1318                        status: "STARTING_UP",
1319                        // Timestamp value is unpredictable and not relevant in this context,
1320                        // so we only assert that the property is present.
1321                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1322                    },
1323                }
1324            }
1325        });
1326    }
1327
1328    #[fuchsia::test]
1329    async fn dead_keys_handler_inspect_counts_events() {
1330        let loader = icu_data::Loader::new().unwrap();
1331        let inspector = fuchsia_inspect::Inspector::default();
1332        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
1333        let handler =
1334            DeadKeysHandler::new(loader, &fake_handlers_node, metrics::MetricsLogger::default());
1335
1336        // Inspect should count unhandled key events and ignore irrelevent InputEvent types.
1337        let events = vec![
1338            new_event(Key::A, KeyEventType::Pressed, Some(KeyMeaning::Codepoint('A' as u32))),
1339            UnhandledInputEvent::try_from(testing_utilities::create_consumer_controls_event(
1340                vec![ConsumerControlButton::VolumeUp],
1341                zx::MonotonicInstant::ZERO,
1342                &testing_utilities::consumer_controls_device_descriptor(),
1343            ))
1344            .unwrap(),
1345            new_event(Key::A, KeyEventType::Released, Some(KeyMeaning::Codepoint('A' as u32))),
1346        ];
1347        let _res: Vec<InputEvent> = events
1348            .into_iter()
1349            .map(|event| handler.clone().handle_unhandled_input_event_internal(event))
1350            .flatten()
1351            .collect();
1352        diagnostics_assertions::assert_data_tree!(inspector, root: {
1353            input_handlers_node: {
1354                dead_keys_handler: {
1355                    events_received_count: 2u64,
1356                    events_handled_count: 0u64,
1357                    last_received_timestamp_ns: 0u64,
1358                    "fuchsia.inspect.Health": {
1359                        status: "STARTING_UP",
1360                        // Timestamp value is unpredictable and not relevant in this context,
1361                        // so we only assert that the property is present.
1362                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
1363                    },
1364                }
1365            }
1366        });
1367    }
1368}