1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 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 }));
    }
}