1#[cfg(any(feature = "wayland", feature = "x11"))]
2use collections::{HashMap, HashSet};
3#[cfg(any(feature = "wayland", feature = "x11"))]
4use xkbcommon::xkb::Keycode;
5
6use crate::{PlatformKeyboardLayout, SharedString};
7
8#[derive(Clone)]
9pub(crate) struct LinuxKeyboardLayout {
10 name: SharedString,
11}
12
13impl PlatformKeyboardLayout for LinuxKeyboardLayout {
14 fn id(&self) -> &str {
15 &self.name
16 }
17
18 fn name(&self) -> &str {
19 &self.name
20 }
21}
22
23impl LinuxKeyboardLayout {
24 pub(crate) fn new(name: SharedString) -> Self {
25 Self { name }
26 }
27}
28
29#[cfg(any(feature = "wayland", feature = "x11"))]
30pub(crate) struct LinuxKeyboardMapper {
31 code_to_key: HashMap<Keycode, String>,
32 code_to_shifted_key: HashMap<Keycode, String>,
33}
34
35#[cfg(any(feature = "wayland", feature = "x11"))]
36impl LinuxKeyboardMapper {
37 pub(crate) fn new(xkb_state: &xkbcommon::xkb::State) -> Self {
38 let mut code_to_key = HashMap::default();
39 let mut code_to_shifted_key = HashMap::default();
40 let mut inserted = HashSet::default();
41
42 let keymap = xkb_state.get_keymap();
43 let mut shifted_state = xkbcommon::xkb::State::new(&keymap);
44 let shift_mod = keymap.mod_get_index(xkbcommon::xkb::MOD_NAME_SHIFT);
45 let shift_mask = 1 << shift_mod;
46 shifted_state.update_mask(shift_mask, 0, 0, 0, 0, 0);
47
48 for &scan_code in TYPEABLE_CODES {
49 let keycode = Keycode::new(scan_code);
50 let key = xkb_state.key_get_utf8(keycode);
51 code_to_key.insert(keycode, key.clone());
52 inserted.insert(key);
53
54 let shifted_key = shifted_state.key_get_utf8(keycode);
55 code_to_shifted_key.insert(keycode, shifted_key);
56 }
57 insert_letters_if_missing(&inserted, &mut code_to_key);
58
59 Self {
60 code_to_key,
61 code_to_shifted_key,
62 }
63 }
64
65 pub(crate) fn get_key(
66 &self,
67 keycode: Keycode,
68 modifiers: &mut crate::Modifiers,
69 ) -> Option<String> {
70 if is_alphabetic_key(keycode) || !modifiers.shift {
71 self.code_to_key.get(&keycode).cloned()
72 } else {
73 modifiers.shift = false;
74 self.code_to_shifted_key.get(&keycode).cloned()
75 }
76 }
77}
78
79#[cfg(any(feature = "wayland", feature = "x11"))]
80fn is_alphabetic_key(keycode: Keycode) -> bool {
81 matches!(
82 keycode.raw(),
83 0x0026 // a
84 | 0x0038 // b
85 | 0x0036 // c
86 | 0x0028 // d
87 | 0x001a // e
88 | 0x0029 // f
89 | 0x002a // g
90 | 0x002b // h
91 | 0x001f // i
92 | 0x002c // j
93 | 0x002d // k
94 | 0x002e // l
95 | 0x003a // m
96 | 0x0039 // n
97 | 0x0020 // o
98 | 0x0021 // p
99 | 0x0018 // q
100 | 0x001b // r
101 | 0x0027 // s
102 | 0x001c // t
103 | 0x001e // u
104 | 0x0037 // v
105 | 0x0019 // w
106 | 0x0035 // x
107 | 0x001d // y
108 | 0x0034 // z
109 )
110}
111
112#[cfg(any(feature = "wayland", feature = "x11"))]
113macro_rules! insert_letters_if_missing_internal {
114 ($inserted:expr, $code_to_key:expr, $code:expr, $key:literal) => {
115 if !$inserted.contains($key) {
116 $code_to_key.insert($code, $key.to_string());
117 }
118 };
119}
120
121#[cfg(any(feature = "wayland", feature = "x11"))]
122fn insert_letters_if_missing(
123 inserted: &HashSet<String>,
124 code_to_key: &mut HashMap<Keycode, String>,
125) {
126 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0026), "a");
127 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0038), "b");
128 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0036), "c");
129 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0028), "d");
130 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001a), "e");
131 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0029), "f");
132 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002a), "g");
133 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002b), "h");
134 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001f), "i");
135 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002c), "j");
136 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002d), "k");
137 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002e), "l");
138 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x003a), "m");
139 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0039), "n");
140 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0020), "o");
141 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0021), "p");
142 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0018), "q");
143 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001b), "r");
144 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0027), "s");
145 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001c), "t");
146 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001e), "u");
147 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0037), "v");
148 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0019), "w");
149 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0035), "x");
150 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001d), "y");
151 insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0034), "z");
152}
153
154// All typeable scan codes for the standard US keyboard layout, ANSI104
155#[cfg(any(feature = "wayland", feature = "x11"))]
156const TYPEABLE_CODES: &[u32] = &[
157 0x0026, // a
158 0x0038, // b
159 0x0036, // c
160 0x0028, // d
161 0x001a, // e
162 0x0029, // f
163 0x002a, // g
164 0x002b, // h
165 0x001f, // i
166 0x002c, // j
167 0x002d, // k
168 0x002e, // l
169 0x003a, // m
170 0x0039, // n
171 0x0020, // o
172 0x0021, // p
173 0x0018, // q
174 0x001b, // r
175 0x0027, // s
176 0x001c, // t
177 0x001e, // u
178 0x0037, // v
179 0x0019, // w
180 0x0035, // x
181 0x001d, // y
182 0x0034, // z
183 0x0013, // Digit 0
184 0x000a, // Digit 1
185 0x000b, // Digit 2
186 0x000c, // Digit 3
187 0x000d, // Digit 4
188 0x000e, // Digit 5
189 0x000f, // Digit 6
190 0x0010, // Digit 7
191 0x0011, // Digit 8
192 0x0012, // Digit 9
193 0x0031, // ` Backquote
194 0x0014, // - Minus
195 0x0015, // = Equal
196 0x0022, // [ Left bracket
197 0x0023, // ] Right bracket
198 0x0033, // \ Backslash
199 0x002f, // ; Semicolon
200 0x0030, // ' Quote
201 0x003b, // , Comma
202 0x003c, // . Period
203 0x003d, // / Slash
204];