on-screen keyboard
git clone https://git.lucas.co/cce-keyboard.git
src/keymap.rs (3.4K)
1 //! The keymap the virtual keyboard uploads, and the key labels read from it.
2 //!
3 //! Compiled from the same RMLVO the compositor's default keymap is: empty
4 //! names, which libxkbcommon fills from `XKB_DEFAULT_*` and then the system
5 //! defaults (`cce-compositor` `xkb_config.rs` passes NULL names). So the board
6 //! labels and types whatever layout the session's hardware keyboard has.
7
8 use std::collections::HashMap;
9
10 use xkbcommon::xkb;
11
12 use crate::layout::Modifier;
13
14 /// What a character key prints, plain and shifted. `None` for a key that
15 /// prints nothing printable (a control character, or nothing at all).
16 #[derive(Debug, Clone, Default, PartialEq)]
17 pub struct Label {
18 pub plain: Option<String>,
19 pub shifted: Option<String>,
20 }
21
22 pub struct Keymap {
23 /// The keymap in XKB text format, for `zwp_virtual_keyboard_v1.keymap`.
24 pub text: String,
25 /// Each [`Modifier`]'s mask, indexed by [`Modifier::index`].
26 masks: [u32; 4],
27 labels: HashMap<u32, Label>,
28 }
29
30 impl Keymap {
31 /// Compile the session's default keymap and label `codes` (evdev) from it.
32 pub fn compile(codes: &[u32]) -> Option<Self> {
33 let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
34 let keymap = xkb::Keymap::new_from_names(&context, "", "", "", "", None, xkb::KEYMAP_COMPILE_NO_FLAGS)?;
35 let mut masks = [0; 4];
36 for m in Modifier::ALL {
37 let index = keymap.mod_get_index(m.xkb_name());
38 if index != xkb::MOD_INVALID {
39 masks[m.index()] = 1 << index;
40 }
41 }
42 let shift = masks[Modifier::Shift.index()];
43 let plain = xkb::State::new(&keymap);
44 let mut shifted = xkb::State::new(&keymap);
45 shifted.update_mask(shift, 0, 0, 0, 0, 0);
46 let printable = |s: String| (!s.is_empty() && !s.chars().any(char::is_control)).then_some(s);
47 let labels = codes
48 .iter()
49 .map(|&code| {
50 // xkb keycodes are evdev codes offset by 8.
51 let kc = xkb::Keycode::new(code + 8);
52 (code, Label { plain: printable(plain.key_get_utf8(kc)), shifted: printable(shifted.key_get_utf8(kc)) })
53 })
54 .collect();
55 Some(Self { text: keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1), masks, labels })
56 }
57
58 pub fn label(&self, code: u32) -> Option<&Label> {
59 self.labels.get(&code)
60 }
61
62 /// The depressed-modifier mask for `mods` held.
63 pub fn mask(&self, mods: impl IntoIterator<Item = Modifier>) -> u32 {
64 mods.into_iter().fold(0, |acc, m| acc | self.masks[m.index()])
65 }
66 }
67
68 impl Label {
69 /// The small legend in a key's corner: the shifted symbol, when it is
70 /// not simply the capital of the plain one (a digit's `!`, not Q's `Q`).
71 pub fn corner(&self) -> Option<&str> {
72 let (plain, shifted) = (self.plain.as_deref()?, self.shifted.as_deref()?);
73 (shifted != plain && shifted != plain.to_uppercase()).then_some(shifted)
74 }
75 }
76
77 #[cfg(test)]
78 mod tests {
79 use super::*;
80
81 #[test]
82 fn a_letter_has_no_corner_legend_and_a_digit_does() {
83 let q = Label { plain: Some("q".into()), shifted: Some("Q".into()) };
84 assert_eq!(q.corner(), None);
85 let one = Label { plain: Some("1".into()), shifted: Some("!".into()) };
86 assert_eq!(one.corner(), Some("!"));
87 let dead = Label { plain: Some("1".into()), shifted: None };
88 assert_eq!(dead.corner(), None);
89 }
90 }