keyboard.rs

  1#[cfg(any(feature = "wayland", feature = "x11"))]
  2use collections::{HashMap, HashSet};
  3#[cfg(any(feature = "wayland", feature = "x11"))]
  4use xkbcommon::xkb::{Keycode, Keysym};
  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
 52            if !key.is_empty() {
 53                code_to_key.insert(keycode, key.clone());
 54                inserted.insert(key);
 55
 56                let shifted_key = shifted_state.key_get_utf8(keycode);
 57                code_to_shifted_key.insert(keycode, shifted_key);
 58            } else {
 59                // keycode might be a dead key
 60                let keysym = xkb_state.key_get_one_sym(keycode);
 61                if let Some(key) = underlying_dead_key(keysym) {
 62                    code_to_key.insert(keycode, key.clone());
 63                    inserted.insert(key);
 64                }
 65
 66                let shifted_keysym = shifted_state.key_get_one_sym(keycode);
 67                if let Some(shifted_key) = underlying_dead_key(shifted_keysym) {
 68                    code_to_shifted_key.insert(keycode, shifted_key);
 69                }
 70            }
 71        }
 72        insert_letters_if_missing(&inserted, &mut code_to_key);
 73
 74        Self {
 75            code_to_key,
 76            code_to_shifted_key,
 77        }
 78    }
 79
 80    pub(crate) fn get_key(
 81        &self,
 82        keycode: Keycode,
 83        modifiers: &mut crate::Modifiers,
 84    ) -> Option<String> {
 85        if is_alphabetic_key(keycode) || !modifiers.shift {
 86            self.code_to_key.get(&keycode).cloned()
 87        } else {
 88            modifiers.shift = false;
 89            self.code_to_shifted_key.get(&keycode).cloned()
 90        }
 91    }
 92}
 93
 94#[cfg(any(feature = "wayland", feature = "x11"))]
 95fn is_alphabetic_key(keycode: Keycode) -> bool {
 96    matches!(
 97        keycode.raw(),
 98        0x0026 // a
 99        | 0x0038 // b
100        | 0x0036 // c
101        | 0x0028 // d
102        | 0x001a // e
103        | 0x0029 // f
104        | 0x002a // g
105        | 0x002b // h
106        | 0x001f // i
107        | 0x002c // j
108        | 0x002d // k
109        | 0x002e // l
110        | 0x003a // m
111        | 0x0039 // n
112        | 0x0020 // o
113        | 0x0021 // p
114        | 0x0018 // q
115        | 0x001b // r
116        | 0x0027 // s
117        | 0x001c // t
118        | 0x001e // u
119        | 0x0037 // v
120        | 0x0019 // w
121        | 0x0035 // x
122        | 0x001d // y
123        | 0x0034 // z
124    )
125}
126
127/**
128 * Returns which symbol the dead key represents
129 * <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
130 */
131#[cfg(any(feature = "wayland", feature = "x11"))]
132pub(crate) fn underlying_dead_key(keysym: Keysym) -> Option<String> {
133    match keysym {
134        Keysym::dead_grave => Some("`".to_owned()),
135        Keysym::dead_acute => Some("´".to_owned()),
136        Keysym::dead_circumflex => Some("^".to_owned()),
137        Keysym::dead_tilde => Some("~".to_owned()),
138        Keysym::dead_macron => Some("¯".to_owned()),
139        Keysym::dead_breve => Some("˘".to_owned()),
140        Keysym::dead_abovedot => Some("˙".to_owned()),
141        Keysym::dead_diaeresis => Some("¨".to_owned()),
142        Keysym::dead_abovering => Some("˚".to_owned()),
143        Keysym::dead_doubleacute => Some("˝".to_owned()),
144        Keysym::dead_caron => Some("ˇ".to_owned()),
145        Keysym::dead_cedilla => Some("¸".to_owned()),
146        Keysym::dead_ogonek => Some("˛".to_owned()),
147        Keysym::dead_iota => Some("ͅ".to_owned()),
148        Keysym::dead_voiced_sound => Some("".to_owned()),
149        Keysym::dead_semivoiced_sound => Some("".to_owned()),
150        Keysym::dead_belowdot => Some("̣̣".to_owned()),
151        Keysym::dead_hook => Some("̡".to_owned()),
152        Keysym::dead_horn => Some("̛".to_owned()),
153        Keysym::dead_stroke => Some("̶̶".to_owned()),
154        Keysym::dead_abovecomma => Some("̓̓".to_owned()),
155        Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
156        Keysym::dead_doublegrave => Some("̏".to_owned()),
157        Keysym::dead_belowring => Some("˳".to_owned()),
158        Keysym::dead_belowmacron => Some("̱".to_owned()),
159        Keysym::dead_belowcircumflex => Some("".to_owned()),
160        Keysym::dead_belowtilde => Some("̰".to_owned()),
161        Keysym::dead_belowbreve => Some("̮".to_owned()),
162        Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
163        Keysym::dead_invertedbreve => Some("̯".to_owned()),
164        Keysym::dead_belowcomma => Some("̦".to_owned()),
165        Keysym::dead_currency => None,
166        Keysym::dead_lowline => None,
167        Keysym::dead_aboveverticalline => None,
168        Keysym::dead_belowverticalline => None,
169        Keysym::dead_longsolidusoverlay => None,
170        Keysym::dead_a => None,
171        Keysym::dead_A => None,
172        Keysym::dead_e => None,
173        Keysym::dead_E => None,
174        Keysym::dead_i => None,
175        Keysym::dead_I => None,
176        Keysym::dead_o => None,
177        Keysym::dead_O => None,
178        Keysym::dead_u => None,
179        Keysym::dead_U => None,
180        Keysym::dead_small_schwa => Some("ə".to_owned()),
181        Keysym::dead_capital_schwa => Some("Ə".to_owned()),
182        Keysym::dead_greek => None,
183        _ => None,
184    }
185}
186
187#[cfg(any(feature = "wayland", feature = "x11"))]
188macro_rules! insert_letters_if_missing_internal {
189    ($inserted:expr, $code_to_key:expr, $code:expr, $key:literal) => {
190        if !$inserted.contains($key) {
191            $code_to_key.insert($code, $key.to_string());
192        }
193    };
194}
195
196#[cfg(any(feature = "wayland", feature = "x11"))]
197fn insert_letters_if_missing(
198    inserted: &HashSet<String>,
199    code_to_key: &mut HashMap<Keycode, String>,
200) {
201    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0026), "a");
202    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0038), "b");
203    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0036), "c");
204    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0028), "d");
205    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001a), "e");
206    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0029), "f");
207    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002a), "g");
208    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002b), "h");
209    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001f), "i");
210    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002c), "j");
211    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002d), "k");
212    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x002e), "l");
213    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x003a), "m");
214    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0039), "n");
215    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0020), "o");
216    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0021), "p");
217    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0018), "q");
218    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001b), "r");
219    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0027), "s");
220    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001c), "t");
221    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001e), "u");
222    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0037), "v");
223    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0019), "w");
224    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0035), "x");
225    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x001d), "y");
226    insert_letters_if_missing_internal!(inserted, code_to_key, Keycode::new(0x0034), "z");
227}
228
229// All typeable scan codes for the standard US keyboard layout, ANSI104
230#[cfg(any(feature = "wayland", feature = "x11"))]
231const TYPEABLE_CODES: &[u32] = &[
232    0x0026, // a
233    0x0038, // b
234    0x0036, // c
235    0x0028, // d
236    0x001a, // e
237    0x0029, // f
238    0x002a, // g
239    0x002b, // h
240    0x001f, // i
241    0x002c, // j
242    0x002d, // k
243    0x002e, // l
244    0x003a, // m
245    0x0039, // n
246    0x0020, // o
247    0x0021, // p
248    0x0018, // q
249    0x001b, // r
250    0x0027, // s
251    0x001c, // t
252    0x001e, // u
253    0x0037, // v
254    0x0019, // w
255    0x0035, // x
256    0x001d, // y
257    0x0034, // z
258    0x0013, // Digit 0
259    0x000a, // Digit 1
260    0x000b, // Digit 2
261    0x000c, // Digit 3
262    0x000d, // Digit 4
263    0x000e, // Digit 5
264    0x000f, // Digit 6
265    0x0010, // Digit 7
266    0x0011, // Digit 8
267    0x0012, // Digit 9
268    0x0031, // ` Backquote
269    0x0014, // - Minus
270    0x0015, // = Equal
271    0x0022, // [ Left bracket
272    0x0023, // ] Right bracket
273    0x0033, // \ Backslash
274    0x002f, // ; Semicolon
275    0x0030, // ' Quote
276    0x003b, // , Comma
277    0x003c, // . Period
278    0x003d, // / Slash
279];