blob: f803f86584e926a91e9a45dbade1cd7af6de7b54 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implements dead key handling.
//!
//! Dead key is a character composition approach where an accented character,
//! typically from a Western European alphabet, is composed by actuating two
//! keys on the keyboard:
//!
//! 1. A "dead key" which determines which diacritic is to be placed on the
//! character, and which produces no immediate output; and
//! 2. The character onto which the diacritic is to be placed.
//!
//! The resulting two successive key actuations produce an effect of single
//! accented character being emitted.
//!
//! The dead key handler relies on keymap already having been applied, and the
//! use of key meanings.
//!
//! This means that the dead key handler must be added to the input pipeline
//! after the keymap handler in the input pipeline.
//!
//! The dead key handler can delay or modify the key meanings, but it never delays nor
//! modifies key events. This ensures that clients which require key events see the
//! key events as they come in. The key meanings may be delayed because of the delayed
//! effect of composition.
//!
//! The state machine of the dead key handler is watching for dead key and "live" key
//! combinations, and handles all their possible interleaving. The event sequences
//! vary from the "obvious" ones such as "dead key press and release followed
//! by a live key press and release", to not so obvious ones such as: "dead key
//! press and hold, shift press, live key press and hold followed by another
//! live key press, followed by arbitrary sequence of key releases".
//!
//! See the documentation for [Handler] for some more detail.
use crate::input_device::{
Handled, InputDeviceDescriptor, InputDeviceEvent, InputEvent, UnhandledInputEvent,
};
use crate::input_handler::UnhandledInputHandler;
use crate::keyboard_binding::KeyboardEvent;
use async_trait::async_trait;
use core::fmt;
use fidl_fuchsia_ui_input3::{KeyEventType, KeyMeaning};
use fuchsia_syslog::fx_log_debug;
use fuchsia_zircon as zx;
use rust_icu_sys as usys;
use rust_icu_unorm2 as unorm;
use std::cell::RefCell;
use std::rc::Rc;
// There probably is a more general method of determining whether the characters
// are combining characters. But somehow it escapes me now.
const GRAVE: u32 = 0x300;
const ACUTE: u32 = 0x301;
const CIRCUMFLEX: u32 = 0x302;
const TILDE: u32 = 0x303;
/// Returns true if `c` is one of the dead keys we support.
///
/// This should likely be some ICU library function, but I'm not sure which one.
fn is_dead_key(c: u32) -> bool {
match c {
GRAVE | ACUTE | CIRCUMFLEX | TILDE => true,
_ => false,
}
}
/// Removes the combining effect from a combining code point, leaving only
/// the diacritic.
///
/// This should likely be some ICU library function, but I'm not sure which one.
fn remove_combination(c: u32) -> u32 {
match c {
GRAVE => '`' as u32,
ACUTE => '\'' as u32,
CIRCUMFLEX => '^' as u32,
TILDE => '~' as u32,
_ => c,
}
}
/// StoredEvent is an InputEvent which is known to be a keyboard event.
#[derive(Debug, Clone)]
struct StoredEvent {
event: KeyboardEvent,
device_descriptor: InputDeviceDescriptor,
event_time: zx::Time,
}
impl fmt::Display for StoredEvent {
// Implement a compact [Display], as the device descriptor is not
// normally very interesting to see.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "event: {:?}, event_time: {:?}", &self.event, &self.event_time)
}
}
impl Into<InputEvent> for StoredEvent {
/// Converts [StoredEvent] into [InputEvent].
fn into(self) -> InputEvent {
InputEvent {
device_event: InputDeviceEvent::Keyboard(self.event),
device_descriptor: self.device_descriptor,
event_time: self.event_time,
handled: Handled::No,
trace_id: None,
}
}
}
impl Into<Vec<InputEvent>> for StoredEvent {
fn into(self) -> Vec<InputEvent> {
vec![self.into()]
}
}
/// Whether a [StoredEvent] corresponds to a live key or a dead key.
enum Liveness {
/// The key is dead.
Dead,
/// The key is live.
Live,
}
/// Whether two events are the same or different by key.
enum Sameness {
/// Two events are the same by key.
Same,
/// Two events are different.
Other,
}
impl StoredEvent {
/// Repackages self into a new [StoredEvent], with `event` replaced as supplied.
fn into_with_event(self, event: KeyboardEvent) -> Self {
StoredEvent {
event,
device_descriptor: self.device_descriptor,
event_time: self.event_time,
}
}
/// Returns the code point contained in this [StoredEvent].
fn code_point(&self) -> u32 {
match self.event.get_key_meaning() {
Some(KeyMeaning::Codepoint(c)) => c,
_ => panic!("programming error: requested code point for an event that has none"),
}
}
/// Modifies this [StoredEvent] to contain a new code point instead of whatever was there.
fn into_with_code_point(self, code_point: u32) -> Self {
let new_event =
self.event.clone().into_with_key_meaning(Some(KeyMeaning::Codepoint(code_point)));
self.into_with_event(new_event)
}
/// Returns true if [StoredEvent] contains a valid code point.
fn is_code_point(&self) -> bool {
match self.event.get_key_meaning() {
// Some nonprintable keys have the code point value set to 0.
Some(KeyMeaning::Codepoint(c)) => c != 0,
_ => false,
}
}
/// Returns whether the key is a dead key or not. The return value is an enum
/// to make the state machine match arms more readable.
fn key_liveness(&self) -> Liveness {
match self.event.get_key_meaning() {
Some(KeyMeaning::Codepoint(c)) if is_dead_key(c) => Liveness::Dead,
_ => Liveness::Live,
}
}
/// Returns the key event type (pressed, released, or something else)
fn e_type(&self) -> KeyEventType {
self.event.get_event_type_folded()
}
/// Returns a new [StoredEvent] based on `Self`, but with the combining effect removed.
fn into_base_character(self) -> Self {
let key_meaning = self.event.get_key_meaning();
match key_meaning {
Some(KeyMeaning::Codepoint(c)) => {
let new_event = self
.event
.clone()
.into_with_key_meaning(Some(KeyMeaning::Codepoint(remove_combination(c))));
self.into_with_event(new_event)
}
_ => self,
}
}
/// Returns a new [StoredEvent], but with key meaning removed.
fn remove_key_meaning(self) -> Self {
let mut event = self.event.clone();
// A zero code point means a KeyEvent for which its edit effect should
// be ignored. In contrast, an event with an unset code point has by
// definition the same effect as if the US QWERTY keymap were applied.
// See discussion at:
// https://groups.google.com/a/fuchsia.dev/g/ui-input-dev/c/ITYKvbJS6_o/m/8kK0DRccDAAJ
event = event.into_with_key_meaning(Some(KeyMeaning::Codepoint(0)));
self.into_with_event(event)
}
/// Returns whether the two keys `this` and `that` are in fact the same key
/// as per the USB HID usage reported. The return value is an enum to make
/// the state machine match arms more readable.
fn key_sameness(this: &StoredEvent, that: &StoredEvent) -> Sameness {
match this.event.get_key() == that.event.get_key() {
true => Sameness::Same,
false => Sameness::Other,
}
}
}
/// State contains the current observed state of the dead key state machine.
///
/// The dead key composition is started by observing a key press that amounts
/// to a dead key. The first non-dead key that gets actuated thereafter becomes
/// the "live" key that we will attempt to add a diacritic to. When such a live
/// key is actuated, we will emit a key meaning equivalent to producing an
/// accented character.
///
/// A complication here is that composition can unfold in any number of ways.
/// The user could press and release the dead key, then press and release
/// the live key. The user could, also, press and hold the dead key, then
/// press any number of live or dead keys in an arbitrary order.
///
/// Another complication is that the user could press the dead key twice, which
/// should also be handled correctly. In this case, "correct" handling implies
/// emitting the dead key as an accented character. Similarly, two different
/// dead keys pressed in succession are handled by (1) emitting the first as
/// an accented character, and restarting composition with the second. It is
/// worth noting that the key press and key release events could be arbitrarily
/// interleaved for the two dead keys, and that should be handled correctly too.
///
/// A third complication is that, while all the composition is taking place,
/// the pipeline must emit the `KeyEvent`s consistent with the key event protocol,
/// but keep key meanings suppressed until the time that the key meanings have
/// been resolved by the combination.
///
/// The elements of state are as follows:
///
/// * Did we see a dead key press event? (bit `a`)
/// * Did we see a dead key release event? (bit `b`)
/// * Did we see a live key press event? (bit `c`)
/// * Did we see a live key release event? (bit `d`)
///
/// Almost any variation of the above elements is possible and allowed. Even
/// the states that ostensibly shouldn't be possible (e.g. observed a release
/// event before a press) should be accounted for in order to implement
/// self-correcting behavior if needed. The [State] enum below encodes each
/// state as a name `Sdcba`, where each of `a..d` are booleans, encoded
/// as characters `0` and `1` as conventional. So for example, `S0101`
/// is a state where we observed a dead key press event, and a live key press
/// event. I made an experiment where I tried to use more illustrative state
/// names, but the number of variations didn't make the resulting names any more
/// meaningful compared to the current state name encoding scheme. So compact
/// naming it is.
#[derive(Debug, Clone)]
enum State {
/// We have yet to see a key to act on.
S0000,
/// We saw an actuation of a dead key.
S0001 { dead_key_down: StoredEvent },
/// A dead key was pressed and released.
S0011 { dead_key_down: StoredEvent, dead_key_up: StoredEvent },
/// A dead key was pressed and released, followed by a live key press.
S0111 { dead_key_down: StoredEvent, dead_key_up: StoredEvent, live_key_down: StoredEvent },
/// A dead key was pressed, followed by a live key press.
S0101 { dead_key_down: StoredEvent, live_key_down: StoredEvent },
/// A dead key was pressed, then a live key was pressed and released.
S1101 { dead_key_down: StoredEvent },
}
#[derive(Debug)]
pub struct Handler {
/// Tracks the current state of the dead key composition.
state: RefCell<State>,
/// The unicode normalizer used for composition.
normalizer: unorm::UNormalizer,
/// This handler requires ICU data to be live. This is ensured by holding
/// a reference to an ICU data loader.
_data: icu_data::Loader,
}
/// This trait implementation allows the [Handler] to be hooked up into the input
/// pipeline.
#[async_trait(?Send)]
impl UnhandledInputHandler for Handler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
unhandled_input_event: UnhandledInputEvent,
) -> Vec<InputEvent> {
self.handle_unhandled_input_event_internal(unhandled_input_event)
}
}
impl Handler {
/// Creates a new instance of the dead keys handler.
pub fn new(icu_data: icu_data::Loader) -> Rc<Self> {
let handler = Handler {
state: RefCell::new(State::S0000),
// The NFC normalizer performs the needed composition and is not
// lossy.
normalizer: unorm::UNormalizer::new_nfc().unwrap(),
_data: icu_data,
};
Rc::new(handler)
}
fn handle_unhandled_input_event_internal(
self: Rc<Self>,
unhandled_input_event: UnhandledInputEvent,
) -> Vec<InputEvent> {
match unhandled_input_event {
UnhandledInputEvent {
device_event: InputDeviceEvent::Keyboard(event),
device_descriptor,
event_time,
trace_id: _,
} => {
let event = StoredEvent { event, device_descriptor, event_time };
// Separated into two statements to ensure the logs are not truncated.
fx_log_debug!("state: {:?}", self.state.borrow());
fx_log_debug!("event: {}", &event);
let result = self.process_keyboard_event(event);
fx_log_debug!("result: {:?}", &result);
result
}
// Pass other events unchanged.
_ => vec![InputEvent::from(unhandled_input_event)],
}
}
/// Sets the internal handler state to `new_state`.
fn set_state(self: &Rc<Self>, new_state: State) {
*(self.state.borrow_mut()) = new_state;
}
/// Attaches a key meaning to each passing keyboard event.
///
/// Underlying this function is a state machine which registers the flow of dead and live keys
/// after each reported event, and modifies the input event stream accordingly. For example,
/// a sequence of events where a dead key is pressed and released, followed by a live key
/// press and release, results in a composed character being emitted. The state machine
/// takese care of this sequence, but also of other less obvious sequences and their effects.
fn process_keyboard_event(self: &Rc<Self>, event: StoredEvent) -> Vec<InputEvent> {
if !event.is_code_point() {
// Pass through any non-codepoint events.
return event.into();
}
let old_state = self.state.borrow().clone();
match old_state {
// We are waiting for the composition to begin.
State::S0000 => match (event.key_liveness(), event.e_type()) {
// A dead key press starts composition. We advance to the next
// state machine state, and eliminate any key meaning from the
// key event, since we anticipate its use in composition.
(Liveness::Dead, KeyEventType::Pressed) => {
self.set_state(State::S0001 { dead_key_down: event.clone() });
event.remove_key_meaning().into()
}
// A dead key release while we're waiting for a dead key press,
// this is probably a remnant of an earlier double press, remove the
// combining from it and forward. Keep waiting for composition
// to begin.
(Liveness::Dead, KeyEventType::Released) => event.into_base_character().into(),
// Any other events can be forwarded unmodified.
_ => event.into(),
},
// We have seen a dead key press, but not release.
State::S0001 { dead_key_down } => {
match (
event.key_liveness(),
StoredEvent::key_sameness(&event, &dead_key_down),
event.e_type(),
) {
// The same dead key that was pressed the other time was released.
// Emit a stripped version, and start waiting for a live key.
(Liveness::Dead, Sameness::Same, KeyEventType::Released) => {
self.set_state(State::S0011 { dead_key_down, dead_key_up: event.clone() });
event.remove_key_meaning().into()
}
// Another dead key was released at this point. Since
// we can not start a new combination here, we must forward
// it with meaning stripped.
(Liveness::Dead, Sameness::Other, KeyEventType::Released) => {
event.remove_key_meaning().into()
}
// The same dead key was pressed again, while we have seen
// it pressed before. This can happen when autorepeat kicks
// in. We treat this the same as two successive actuations
// i.e. we send a stripped version of the character, and
// go back to waiting.
(Liveness::Dead, Sameness::Same, KeyEventType::Pressed) => {
self.set_state(State::S0000);
event.into_base_character().into()
}
// A different dead key was pressed. This stops the ongoing
// composition, and starts a new one with a new dead key. However,
// what we emit is a bit subtle: we emit a key press event
// for the *new* key, but with a key meaning of the stripped
// version of the current key.
(Liveness::Dead, Sameness::Other, KeyEventType::Pressed) => {
let current_removed = dead_key_down.clone().into_base_character();
self.set_state(State::S0001 { dead_key_down: event.clone() });
event.into_with_code_point(current_removed.code_point()).into()
}
// A live key was pressed while the dead key is held down. Yay!
//
// Compose and ship out the live key with attached new meaning.
//
// A very similar piece of code happens in the state `State::S0011`,
// except we get there through a different sequence of events.
// Please refer to that code for the details about composition.
(Liveness::Live, _, KeyEventType::Pressed) => {
let maybe_composed = self.normalizer.compose_pair(
event.code_point() as usys::UChar32,
dead_key_down.code_point() as usys::UChar32,
);
if maybe_composed >= 0 {
// Composition was a success.
let composed_event = event.into_with_code_point(maybe_composed as u32);
self.set_state(State::S0101 {
dead_key_down,
live_key_down: composed_event.clone(),
});
return composed_event.into();
} else {
// FAIL!
self.set_state(State::S0101 {
dead_key_down,
live_key_down: event.clone(),
});
return event.into();
}
}
// All other key events are forwarded unmodified.
_ => event.into(),
}
}
// The dead key was pressed and released, the first live key that
// gets pressed after that now will be used for the composition.
State::S0011 { dead_key_down, dead_key_up } => {
match (event.key_liveness(), event.e_type()) {
// We observed a dead key actuation.
(Liveness::Dead, KeyEventType::Pressed) => {
match StoredEvent::key_sameness(&dead_key_down, &event) {
// The user pressed the same dead key again. Let's "compose" it by
// stripping its diacritic and making that a compose key.
Sameness::Same => {
let event = event.into_base_character();
self.set_state(State::S0111 {
dead_key_down,
dead_key_up,
live_key_down: event.clone(),
});
event.into()
}
// The user pressed a different dead key. It would have been nice
// to start a new composition, but we can not express that with the
// KeyEvent API, since that would require emitting spurious press and
// release key events for the dead key press and release.
//
// Instead, forward the key unmodified and cancel
// the composition. We may revisit this if the KeyEvent API is
// changed to allow decoupling key events from key meanings.
Sameness::Other => {
self.set_state(State::S0000);
event.into_base_character().into()
}
}
}
// We observed a dead key release. This is likely a dead key
// from the *previous* composition attempt. Nothing to do here,
// except forward it stripped of key meaning.
(Liveness::Dead, KeyEventType::Released) => event.remove_key_meaning().into(),
// Oh, frabjous day! Someone pressed a live key that may be
// possible to combine! Let's try it out! If composition is
// a success, emit the current key with the meaning set to
// the composed character.
(Liveness::Live, KeyEventType::Pressed) => {
let maybe_composed = self.normalizer.compose_pair(
event.code_point() as usys::UChar32,
dead_key_down.code_point() as usys::UChar32,
);
if maybe_composed >= 0 {
// Composition was a success.
// Emit the composed event, remember it also when
// transitioning to S0111, so we can recover the key meaning
// when the live key is released.
let composed_event = event.into_with_code_point(maybe_composed as u32);
self.set_state(State::S0111 {
dead_key_down,
dead_key_up,
live_key_down: composed_event.clone(),
});
return composed_event.into();
} else {
fx_log_debug!("compose failed for: {}\n", &event);
// FAIL!
// Composition failed, what now? We would need to
// emit TWO characters - one for the now-defunct
// dead key, and another for the current live key.
// But this is not possible, since we may not emit
// more combining key events, but must always emit
// both the key and the key meaning since that is
// how our protocol works. Well, we reached the
// limit of what key event composition may do, so
// let's simply agree to emit the current event
// unmodified and forget we had the dead key.
self.set_state(State::S0111 {
dead_key_down,
dead_key_up,
live_key_down: event.clone(),
});
return event.into();
}
}
// All other key events are forwarded unmodified.
_ => event.into(),
}
}
// We already combined the live key with the dead key, and are
// now waiting for the live key to be released.
State::S0111 { dead_key_down, dead_key_up, live_key_down } => {
match (
event.key_liveness(),
// Here we compare the current key with the live key down,
// unlike in prior states.
StoredEvent::key_sameness(&event, &live_key_down),
event.e_type(),
) {
// This is what we've been waiting for: the live key is now
// lifted. Emit the live key release using the same code point
// as we used when the key went down, and we're done.
(Liveness::Live, Sameness::Same, KeyEventType::Released) => {
self.set_state(State::S0000);
event.into_with_code_point(live_key_down.code_point()).into()
}
// A second press of the live key we're combining. This is
// probably a consequence of autorepeat. The effect should
// be to complete the composition and continue emitting the
// "base" key meaning for any further repeats; but also
// continue waiting for a key release.
(Liveness::Live, Sameness::Same, KeyEventType::Pressed) => {
let base_codepoint = event.code_point();
let combined_event =
event.clone().into_with_code_point(live_key_down.code_point());
// We emit a combined key, but further repeats will use the
// base code point and not combine.
self.set_state(State::S0111 {
dead_key_down,
dead_key_up,
live_key_down: event.into_with_code_point(base_codepoint),
});
combined_event.into()
}
// If another live key event comes in, just forward it, and
// continue waiting for the last live key release.
(Liveness::Live, Sameness::Other, _) => event.into(),
// Another dead key has been pressed in addition to what
// had been pressed before. So now, we are waiting for the
// user to release the live key we already composed, but the
// user is again pressing a compose key instead.
//
// Ideally, we'd want to start new composition with the
// new dead key. But, there's still the issue with the
// live key that is still being pressed: when it is eventually
// released, we want to have it have exactly the same key
// meaning as what we emitted for when it was pressed. But,
// that may happen arbitrarily late afterwards, and we'd
// prefer not to keep any composition state for that long.
//
// That suggests that we must not honor this new dead key
// as composition. But, also, we must not drop the key
// event on the floor, since the clients that read key
// events must receive it. So, we just *turn* off
// the combining effect on this key, forward it like that,
// and continue waiting for the key release.
(Liveness::Dead, _, KeyEventType::Pressed) => event.remove_key_meaning().into(),
(Liveness::Dead, _, KeyEventType::Released) => {
match StoredEvent::key_sameness(&event, &live_key_down) {
// Special: if the released key a dead key and the same as the
// "live" composing key, then we're seeing a release of a doubly-
// pressed dead key. This one needs to be emitted as a diacritic.
Sameness::Same => {
self.set_state(State::S0000);
event.into_base_character().into()
}
// All other dead keys are forwarded with stripped key meanings.
// We have no way to handle them further.
Sameness::Other => event.remove_key_meaning().into(),
}
}
// Forward any other events unmodified.
_ => event.into(),
}
}
// The user pressed and is holding the dead key; and pressed and
// is holding a live key.
State::S0101 { dead_key_down, live_key_down } => {
match (event.key_liveness(), event.e_type()) {
// The same dead key we're already holding is pressed. Just forward
// the key event, but not meaning.
(Liveness::Dead, KeyEventType::Pressed) => event.remove_key_meaning().into(),
(Liveness::Dead, KeyEventType::Released) => {
// The dead key that we are using for combining is released.
// Emit its release event without a key meaning and go to a
// state that expects a release of the live key.
match StoredEvent::key_sameness(&dead_key_down, &event) {
Sameness::Same => {
self.set_state(State::S0111 {
dead_key_down,
dead_key_up: event.clone(),
live_key_down,
});
event.remove_key_meaning().into()
}
// Other dead key is released. Remove its key meaning, but forward.
Sameness::Other => event.remove_key_meaning().into(),
}
}
(Liveness::Live, KeyEventType::Pressed) => {
match StoredEvent::key_sameness(&live_key_down, &event) {
// The currently pressed live key is pressed again.
// This is autorepeat. We emit one composed key, but any
// further emitted keys will not compose. This
// should be similar to `State::S0111`, except the
// transition is back to *this* state.
Sameness::Same => {
let base_codepoint = event.code_point();
let combined_event =
event.clone().into_with_code_point(live_key_down.code_point());
self.set_state(State::S0101 {
dead_key_down,
live_key_down: event.into_with_code_point(base_codepoint),
});
combined_event.into()
}
Sameness::Other => event.into(),
}
}
(Liveness::Live, KeyEventType::Released) => {
match StoredEvent::key_sameness(&live_key_down, &event) {
Sameness::Same => {
self.set_state(State::S1101 { dead_key_down });
event.into_with_code_point(live_key_down.code_point()).into()
}
// Any other release just gets forwarded.
Sameness::Other => event.into(),
}
}
// Forward any other events unmodified
_ => event.into(),
}
}
// The dead key is still actuated, but we already sent out the
// combined versions of the live key.
State::S1101 { dead_key_down } => {
match (event.key_liveness(), event.e_type()) {
(Liveness::Dead, KeyEventType::Pressed) => {
// Two possible cases here, but the outcome is the
// same:
//
// The same dead key is pressed again. Let's not
// do any more compositions here.
//
// A different dead key has been pressed. We can
// not start a new composition while we have not
// closed out the current composition. For this
// reason we ignore the other key.
//
// A real compositioning API would perhaps allow us
// to stack compositions on top of each other, but
// we will require any such consumers to go talk to
// the text editing API instead.
event.remove_key_meaning().into()
}
(Liveness::Dead, KeyEventType::Released) => {
match StoredEvent::key_sameness(&dead_key_down, &event) {
// The dead key is released, the composition is
// done, let's close up shop.
Sameness::Same => {
self.set_state(State::S0000);
event.remove_key_meaning().into()
}
// A dead key was released, but not the one that we
// are combining by. Forward with the combining
// effect stripped.
Sameness::Other => event.remove_key_meaning().into(),
}
}
// Any additional live keys, no matter if they are the same
// as the one currently being composed, will *not* be composed,
// we forward them unmodified as we wait to close off this
// composition.
//
// Forward any other events unmodified.
_ => event.into(),
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing_utilities;
use fidl_fuchsia_input::Key;
use fidl_fuchsia_ui_input3::{KeyEventType, KeyMeaning};
use fuchsia_zircon as zx;
use pretty_assertions::assert_eq;
use std::convert::TryFrom as _;
// Creates a new keyboard event for testing.
fn new_event(
key: Key,
event_type: KeyEventType,
key_meaning: Option<KeyMeaning>,
) -> UnhandledInputEvent {
UnhandledInputEvent::try_from(testing_utilities::create_keyboard_event_with_handled(
key,
event_type,
/*modifiers=*/ None,
/*event_time*/ zx::Time::ZERO,
&InputDeviceDescriptor::Fake,
/*keymap=*/ None,
key_meaning,
/*handled=*/ Handled::No,
))
.unwrap()
}
// Tests some common keyboard input use cases with dead keys actuation.
#[test]
fn test_input_processing() {
// A zero codepoint is a way to let the consumers know that this key
// event should have no effect on the edited text; even though its
// key event may have other effects, such as moving the hero across
// the screen in a game.
const ZERO_CP: Option<KeyMeaning> = Some(KeyMeaning::Codepoint(0));
#[derive(Debug)]
struct TestCase {
name: &'static str,
// The sequence of input events at the input of the dead keys
// handler.
inputs: Vec<UnhandledInputEvent>,
// The expected sequence of input events, after being transformed
// by the dead keys handler.
expected: Vec<UnhandledInputEvent>,
}
let tests: Vec<TestCase> = vec![
TestCase {
name: "passthrough",
inputs: vec![
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
],
expected: vec![
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
],
},
TestCase {
name: "A circumflex - dead key first, then live key",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
],
},
TestCase {
name: "A circumflex - dead key held all the way through composition",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
],
},
TestCase {
name: "A circumflex - dead key held until the live key was down",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
],
},
TestCase {
name: "Combining character pressed twice - results in a single diacritic",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('^' as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('^' as u32)),
),
],
},
TestCase {
name: "A circumflex - dead key spans live key",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
],
},
TestCase {
name: "Only the first key after the dead key actuation is composed",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::E,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('E' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::E,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('E' as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::E,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('E' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::E,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('E' as u32)),
),
],
},
TestCase {
name: "Modifier keys are not affected",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(Key::LeftShift, KeyEventType::Pressed, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(Key::LeftShift, KeyEventType::Released, ZERO_CP),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(Key::LeftShift, KeyEventType::Pressed, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(Key::LeftShift, KeyEventType::Released, ZERO_CP),
],
},
TestCase {
name: "Two dead keys in succession - no compose",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(GRAVE as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(GRAVE as u32)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('`' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('`' as u32)),
),
],
},
TestCase {
name: "Compose with capital letter",
inputs: vec![
new_event(
Key::Key5,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::Key5,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(CIRCUMFLEX as u32)),
),
new_event(
Key::LeftShift,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(0)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('A' as u32)),
),
new_event(
Key::LeftShift,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(0)),
),
],
expected: vec![
new_event(Key::Key5, KeyEventType::Pressed, ZERO_CP),
new_event(Key::Key5, KeyEventType::Released, ZERO_CP),
new_event(
Key::LeftShift,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint(0)),
),
new_event(
Key::A,
KeyEventType::Pressed,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::A,
KeyEventType::Released,
Some(KeyMeaning::Codepoint('Â' as u32)),
),
new_event(
Key::LeftShift,
KeyEventType::Released,
Some(KeyMeaning::Codepoint(0)),
),
],
},
];
let loader = icu_data::Loader::new().unwrap();
let handler = super::Handler::new(loader);
for test in tests {
let actuals: Vec<InputEvent> = test
.inputs
.into_iter()
.map(|event| handler.clone().handle_unhandled_input_event_internal(event))
.flatten()
.collect();
assert_eq!(
test.expected.into_iter().map(InputEvent::from).collect::<Vec<_>>(),
actuals,
"in test: {}",
test.name
);
}
}
}