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 _ => {
64 handle_consumed_modifiers = false;
65 xkb::keysym_get_name(key_sym).to_lowercase()
66 }
67 };
68
69 // Ignore control characters (and DEL) for the purposes of ime_key,
70 // but if key_utf32 is 0 then assume it isn't one
71 let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
72 && !key_utf8.is_empty())
73 .then_some(key_utf8);
74
75 if handle_consumed_modifiers {
76 let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
77 let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
78
79 if modifiers.shift && is_shift_consumed {
80 modifiers.shift = false;
81 }
82 }
83
84 Keystroke {
85 modifiers,
86 key,
87 ime_key,
88 }
89 }
90}