blob: 890f713d31f0607ea820c8e6e4999040151d9a4e [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 crate as keymaps;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Shift {
No,
Yes,
DontCare,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct KeyStroke {
pub usage: u32,
pub 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/src/ui/input/lib/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;
/// # use crate::US_QWERTY;
///
/// let _keymap = InverseKeymap::new(&US_QWERTY);
/// ```
pub fn new(keymap: &keymaps::Keymap<'_>) -> Self {
let mut map = HashMap::new();
// A real keymap is not invertible, so multiple keys can produce the same effect. For
// example, a key `1` and a numeric keypad `1` will end up inserting the same element in
// the inverse keymap. By iterating over the forward keymap in reverse, we give priority
// to more "conventional" keys, i.e. a key `1` will always be used instead of numeric
// keypad `1`. A similar idea is applied in all match arms below.
for (usage, key_levels) in keymap.as_ref().iter().enumerate().rev() {
let entry = key_levels.as_ref().map(|kl| (kl.ch, kl.shift_ch));
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 });
}
_ => (),
}
}
// Additionally, insert some convenience keystrokes. This is not intended to be
// complete, but to allow simple keyboard navigation such as confirming text input
// or focus change to the next element.
// Based on //sdk/fidl/fuchsia.input/key.fidl.
// Assumes the usage page is 0x7.
map.insert('\n', KeyStroke { usage: 0x28, shift: Shift::No });
map.insert('\t', KeyStroke { usage: 0x2b, shift: Shift::No });
Self { map }
}
pub fn get(&self, c: &char) -> Option<&KeyStroke> {
self.map.get(c)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn returns_correct_shift_level() {
let keymap = InverseKeymap::new(&keymaps::US_QWERTY);
assert_matches!(keymap.get(&'a'), Some(KeyStroke { shift: Shift::No, .. }));
assert_matches!(keymap.get(&'A'), Some(KeyStroke { shift: Shift::Yes, .. }));
}
#[test]
fn returns_expected_usage() {
let keymap = InverseKeymap::new(&keymaps::US_QWERTY);
assert_matches!(keymap.get(&'a'), Some(KeyStroke { usage: 0x04, .. }));
// Numeric character: maps to main keyboard, not keypad.
assert_matches!(keymap.get(&'1'), Some(KeyStroke { usage: 0x1e, .. }));
}
#[test]
fn returns_special_keys() {
let keymap = InverseKeymap::new(&keymaps::US_QWERTY);
assert_matches!(keymap.get(&'\n'), Some(KeyStroke { usage: 0x28, shift: Shift::No }));
assert_matches!(keymap.get(&'\t'), Some(KeyStroke { usage: 0x2b, shift: Shift::No }));
}
}