blob: 3952e875eb64c2cb83a9121701866e639e345c7d [file] [log] [blame]
// Copyright 2019 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.
use std::collections::HashMap;
use fidl_fuchsia_ui_input::KeyboardReport;
use crate::usages::Usages;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Shift {
No,
Yes,
DontCare,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct KeyStroke {
usage: u32,
shift: Shift,
}
/// Lightweight utility for basic keymap conversion of chars to keystrokes.
///
/// This is intended for end-to-end and input testing only; for production use cases and general
/// testing, IME injection should be used instead. Generally a mapping exists only for printable
/// ASCII characters; in particular neither `'\t'` nor `'\n'` is mapped in either of the standard
/// zircon keymaps. Furthermore, IME implementations may themselves override the keymap in a way
/// that invalidates this translation.
///
/// This is an inverse of [hid/hid.h:hid_map_key].
///
/// [hid/hid.h:hid_map_key]: https://fuchsia.googlesource.com/fuchsia/+/ef9c451ba83a3ece22cad66b9dcfb446be291966/zircon/system/ulib/hid/include/hid/hid.h#30
#[derive(Debug)]
pub struct InverseKeymap {
map: HashMap<char, KeyStroke>,
}
impl InverseKeymap {
/// Creates an inverse keymap from a specialized `keymap` array. The value of this array at
/// index `u`, where `u` is the usage, can be:
///
/// * `None` if the key maps to no `char` (Esc key)
/// * `Some((c, None))` if the key maps to `c`, but does not map to any `char` when shift is pressed
/// * `Some((c, Some(cs)))` if the key maps to `c` when shift is not pressed and to `cs` when it is
/// pressed
///
/// # Examples
///
/// ```
/// # use crate::inverse_keymap::InverseKeymap;
/// let qwerty_map = &[
/// // ...
/// Some(('a', Some('A'))),
/// Some(('b', Some('B'))),
/// Some(('c', Some('C'))),
/// // ...
/// ];
///
/// let _keymap = InverseKeymap::new(&qwerty_map);
/// ```
pub fn new(keymap: &[Option<(char, Option<char>)>]) -> Self {
let mut map = HashMap::new();
for (usage, entry) in keymap.iter().enumerate() {
match entry {
Some((ch, Some(shift_ch))) if ch == shift_ch => {
map.insert(*ch, KeyStroke { usage: usage as u32, shift: Shift::DontCare });
}
Some((ch, Some(shift_ch))) => {
map.insert(*ch, KeyStroke { usage: usage as u32, shift: Shift::No });
map.insert(*shift_ch, KeyStroke { usage: usage as u32, shift: Shift::Yes });
}
Some((ch, None)) => {
map.insert(*ch, KeyStroke { usage: usage as u32, shift: Shift::No });
}
_ => (),
}
}
Self { map }
}
/// Converts the `input` string into a key sequence under the provided `InverseKeymap`.
///
/// This is intended for end-to-end and input testing only; for production use cases and general
/// testing, IME injection should be used instead.
///
/// A translation from `input` to a sequence of keystrokes is not guaranteed to exist. If a
/// translation does not exist, `None` is returned.
///
/// The sequence does not contain pauses except between repeated keys or to clear a shift state,
/// though the sequence does terminate with an empty report (no keys pressed). A shift key
/// transition is sent in advance of each series of keys that needs it.
///
/// Note that there is currently no way to distinguish between particular key releases. As such,
/// only one key release report is generated even in combinations, e.g. Shift + A.
///
/// # Examples
///
/// ```
/// # use crate::{keymaps::QWERTY_MAP, inverse_keymap::InverseKeymap};
/// let keymap = InverseKeymap::new(&QWERTY_MAP);
/// let key_sequence = keymap.derive_key_sequence("A").unwrap();
///
/// // [shift, A, clear]
/// assert_eq!(key_sequence.len(), 3);
/// ```
pub fn derive_key_sequence(&self, input: &str) -> Option<Vec<KeyboardReport>> {
let mut reports = vec![];
let mut shift_pressed = false;
let mut last_usage = None;
for ch in input.chars() {
let key_stroke = self.map.get(&ch)?;
match key_stroke.shift {
Shift::Yes if !shift_pressed => {
shift_pressed = true;
last_usage = Some(0);
}
Shift::No if shift_pressed => {
shift_pressed = false;
last_usage = Some(0);
}
_ => {
if last_usage == Some(key_stroke.usage) {
last_usage = Some(0);
}
}
}
if let Some(0) = last_usage {
reports.push(KeyboardReport {
pressed_keys: if shift_pressed {
vec![Usages::HidUsageKeyLeftShift as u32]
} else {
vec![]
},
});
}
last_usage = Some(key_stroke.usage);
reports.push(KeyboardReport {
pressed_keys: if shift_pressed {
vec![key_stroke.usage, Usages::HidUsageKeyLeftShift as u32]
} else {
vec![key_stroke.usage]
},
});
}
// TODO: In the future, we might want to distinguish between different key releases, instead
// of sending one single release report even in the case of key combinations.
reports.push(KeyboardReport { pressed_keys: vec![] });
Some(reports)
}
}
#[cfg(test)]
mod tests {
use crate::keymaps::QWERTY_MAP;
use super::*;
macro_rules! reports {
( $( [ $( $usages:expr ),* ] ),* $( , )? ) => {
Some(vec![
$(
KeyboardReport {
pressed_keys: vec![$($usages as u32),*]
}
),*
])
}
}
#[test]
fn shift_map() {
let keymap = InverseKeymap::new(QWERTY_MAP);
assert_eq!(keymap.map[&'a'].shift, Shift::No);
assert_eq!(keymap.map[&'A'].shift, Shift::Yes);
}
#[test]
fn lowercase() {
let keymap = InverseKeymap::new(QWERTY_MAP);
assert_eq!(
keymap.derive_key_sequence("lowercase"),
reports![
[Usages::HidUsageKeyL],
[Usages::HidUsageKeyO],
[Usages::HidUsageKeyW],
[Usages::HidUsageKeyE],
[Usages::HidUsageKeyR],
[Usages::HidUsageKeyC],
[Usages::HidUsageKeyA],
[Usages::HidUsageKeyS],
[Usages::HidUsageKeyE],
[],
]
);
}
#[test]
fn sentence() {
let keymap = InverseKeymap::new(QWERTY_MAP);
assert_eq!(
keymap.derive_key_sequence("Hello, world!"),
reports![
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyH, Usages::HidUsageKeyLeftShift],
[],
[Usages::HidUsageKeyE],
[Usages::HidUsageKeyL],
[],
[Usages::HidUsageKeyL],
[Usages::HidUsageKeyO],
[Usages::HidUsageKeyComma],
[Usages::HidUsageKeySpace],
[Usages::HidUsageKeyW],
[Usages::HidUsageKeyO],
[Usages::HidUsageKeyR],
[Usages::HidUsageKeyL],
[Usages::HidUsageKeyD],
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKey1, Usages::HidUsageKeyLeftShift],
[],
]
);
}
#[test]
fn hold_shift() {
let keymap = InverseKeymap::new(QWERTY_MAP);
assert_eq!(
keymap.derive_key_sequence("ALL'S WELL!"),
reports![
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyA, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift],
[],
[Usages::HidUsageKeyApostrophe],
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyS, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeySpace, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyW, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyE, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift],
[Usages::HidUsageKey1, Usages::HidUsageKeyLeftShift],
[],
]
);
}
}