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