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