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