util.rs

 1use xkbcommon::xkb::{self, Keycode, Keysym, State};
 2
 3use crate::{Keystroke, Modifiers};
 4
 5impl Keystroke {
 6    pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
 7        let mut modifiers = modifiers;
 8
 9        let key_utf32 = state.key_get_utf32(keycode);
10        let key_utf8 = state.key_get_utf8(keycode);
11        let key_sym = state.key_get_one_sym(keycode);
12
13        // The logic here tries to replicate the logic in `../mac/events.rs`
14        // "Consumed" modifiers are modifiers that have been used to translate a key, for example
15        // pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
16        // Notes:
17        //  - macOS gets the key character directly ("."), xkb gives us the key name ("period")
18        //  - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
19        //  - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
20
21        let mut handle_consumed_modifiers = true;
22        let key = match key_sym {
23            Keysym::Return => "enter".to_owned(),
24            Keysym::Prior => "pageup".to_owned(),
25            Keysym::Next => "pagedown".to_owned(),
26
27            Keysym::comma => ",".to_owned(),
28            Keysym::period => ".".to_owned(),
29            Keysym::less => "<".to_owned(),
30            Keysym::greater => ">".to_owned(),
31            Keysym::slash => "/".to_owned(),
32            Keysym::question => "?".to_owned(),
33
34            Keysym::semicolon => ";".to_owned(),
35            Keysym::colon => ":".to_owned(),
36            Keysym::apostrophe => "'".to_owned(),
37            Keysym::quotedbl => "\"".to_owned(),
38
39            Keysym::bracketleft => "[".to_owned(),
40            Keysym::braceleft => "{".to_owned(),
41            Keysym::bracketright => "]".to_owned(),
42            Keysym::braceright => "}".to_owned(),
43            Keysym::backslash => "\\".to_owned(),
44            Keysym::bar => "|".to_owned(),
45
46            Keysym::grave => "`".to_owned(),
47            Keysym::asciitilde => "~".to_owned(),
48            Keysym::exclam => "!".to_owned(),
49            Keysym::at => "@".to_owned(),
50            Keysym::numbersign => "#".to_owned(),
51            Keysym::dollar => "$".to_owned(),
52            Keysym::percent => "%".to_owned(),
53            Keysym::asciicircum => "^".to_owned(),
54            Keysym::ampersand => "&".to_owned(),
55            Keysym::asterisk => "*".to_owned(),
56            Keysym::parenleft => "(".to_owned(),
57            Keysym::parenright => ")".to_owned(),
58            Keysym::minus => "-".to_owned(),
59            Keysym::underscore => "_".to_owned(),
60            Keysym::equal => "=".to_owned(),
61            Keysym::plus => "+".to_owned(),
62
63            Keysym::ISO_Left_Tab => {
64                handle_consumed_modifiers = false;
65                "tab".to_owned()
66            }
67
68            _ => {
69                handle_consumed_modifiers = false;
70                xkb::keysym_get_name(key_sym).to_lowercase()
71            }
72        };
73
74        // Ignore control characters (and DEL) for the purposes of ime_key,
75        // but if key_utf32 is 0 then assume it isn't one
76        let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
77            && !key_utf8.is_empty())
78        .then_some(key_utf8);
79
80        if handle_consumed_modifiers {
81            let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
82            let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
83
84            if modifiers.shift && is_shift_consumed {
85                modifiers.shift = false;
86            }
87        }
88
89        Keystroke {
90            modifiers,
91            key,
92            ime_key,
93        }
94    }
95}