breadx_keysyms/
lib.rs

1//               Copyright John Nunley, 2022.
2// Distributed under the Boost Software License, Version 1.0.
3//       (See accompanying file LICENSE or copy at
4//         https://www.boost.org/LICENSE_1_0.txt)
5
6//! A library for converting keycodes to key symbols.
7
8#![allow(non_upper_case_globals)]
9#![deprecated = "the `xkeysym` crate implements this crate without any cruft"]
10#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
11#![no_std]
12
13use breadx::{
14    display::Cookie,
15    prelude::*,
16    protocol::xproto::{GetKeyboardMappingReply, Keycode, Keysym, Setup},
17    Error, Result,
18};
19use keysyms::*;
20
21const NO_SYMBOL: Keysym = 0;
22
23#[path = "automatically_generated.rs"]
24pub mod keysyms;
25
26/// Keeps track of the keyboard state for the X11 display.
27pub struct KeyboardState {
28    innards: Innards,
29}
30
31enum Innards {
32    /// The keyboard state hasn't been resolved.
33    Unresolved(Cookie<GetKeyboardMappingReply>),
34    /// The keyboard state has been resolved.
35    Resolved(GetKeyboardMappingReply),
36}
37
38impl KeyboardState {
39    /// Create a new `KeyboardState` associated with the given connection.
40    pub fn new(dpy: &mut impl Display) -> Result<Self> {
41        // open up the cookie
42        let min_keycode = dpy.setup().min_keycode;
43        let max_keycode = dpy.setup().max_keycode;
44        let cookie = dpy.get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)?;
45
46        Ok(Self {
47            innards: Innards::Unresolved(cookie),
48        })
49    }
50
51    /// Create a new `KeyboardState`, async redox.
52    #[cfg(feature = "async")]
53    pub async fn new_async(dpy: &mut impl AsyncDisplay) -> Result<Self> {
54        let min_keycode = dpy.setup().min_keycode;
55        let max_keycode = dpy.setup().max_keycode;
56        let cookie = dpy
57            .get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)
58            .await?;
59
60        Ok(Self {
61            innards: Innards::Unresolved(cookie),
62        })
63    }
64
65    /// Get the resolved keyboard mapping.
66    fn resolve(&mut self, dpy: &mut impl Display) -> Result<&mut GetKeyboardMappingReply> {
67        match self.innards {
68            Innards::Unresolved(ref cookie) => {
69                let reply = dpy.wait_for_reply(*cookie)?;
70                self.innards = Innards::Resolved(reply);
71                match &mut self.innards {
72                    Innards::Resolved(reply) => Ok(reply),
73                    _ => unreachable!(),
74                }
75            }
76            Innards::Resolved(ref mut reply) => Ok(reply),
77        }
78    }
79
80    #[cfg(feature = "async")]
81    async fn resolve_async(
82        &mut self,
83        dpy: &mut impl AsyncDisplay,
84    ) -> Result<&mut GetKeyboardMappingReply> {
85        match self.innards {
86            Innards::Unresolved(ref cookie) => {
87                let reply = dpy.wait_for_reply(*cookie).await?;
88                self.innards = Innards::Resolved(reply);
89                match &mut self.innards {
90                    Innards::Resolved(reply) => Ok(reply),
91                    _ => unreachable!(),
92                }
93            }
94            Innards::Resolved(ref mut reply) => Ok(reply),
95        }
96    }
97
98    /// Refresh the keyboard mapping associated with this type.
99    pub fn refresh(&mut self, dpy: &mut impl Display) -> Result<()> {
100        let min_keycode = dpy.setup().min_keycode;
101        let max_keycode = dpy.setup().max_keycode;
102
103        // open up the cookie
104        let cookie = dpy.get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)?;
105
106        self.innards = Innards::Unresolved(cookie);
107        Ok(())
108    }
109
110    /// Refresh the keyboard mapping associated with this type, async redox.
111    #[cfg(feature = "async")]
112    pub async fn refresh_async(&mut self, dpy: &mut impl AsyncDisplay) -> Result<()> {
113        let min_keycode = dpy.setup().min_keycode;
114        let max_keycode = dpy.setup().max_keycode;
115
116        // open up the cookie
117        let cookie = dpy
118            .get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)
119            .await?;
120
121        self.innards = Innards::Unresolved(cookie);
122        Ok(())
123    }
124
125    /// Get the keyboard symbol associated with the keycode and the
126    /// column.
127    pub fn symbol(
128        &mut self,
129        dpy: &mut impl Display,
130        keycode: Keycode,
131        column: u8,
132    ) -> Result<Keysym> {
133        let reply = self.resolve(dpy)?;
134        get_symbol(dpy.setup(), reply, keycode, column)
135    }
136
137    /// Get the keyboard symbol associated with the keycode and the
138    /// column, async redox.
139    #[cfg(feature = "async")]
140    pub async fn symbol_async(
141        &mut self,
142        dpy: &mut impl AsyncDisplay,
143        keycode: Keycode,
144        column: u8,
145    ) -> Result<Keysym> {
146        let reply = self.resolve_async(dpy).await?;
147        get_symbol(dpy.setup(), reply, keycode, column)
148    }
149}
150
151fn get_symbol(
152    setup: &Setup,
153    mapping: &GetKeyboardMappingReply,
154    keycode: Keycode,
155    mut column: u8,
156) -> Result<Keysym> {
157    // this is mostly a port of the logic from xcb keysyms
158    let mut per = mapping.keysyms_per_keycode;
159    if column >= per && column > 3 {
160        return Err(Error::make_msg("Invalid column"));
161    }
162
163    // get the array of keysyms
164    let start = (keycode - setup.min_keycode) as usize * per as usize;
165    let end = start + per as usize;
166    let keysyms = &mapping.keysyms[start..end];
167
168    // get the alternate keysym if needed
169    if column < 4 {
170        if column > 1 {
171            while per > 2 && keysyms[per as usize - 1] == NO_SYMBOL {
172                per -= 1;
173            }
174
175            if per < 3 {
176                column -= 2;
177            }
178        }
179
180        if per <= column | 1 || keysyms[column as usize | 1] == NO_SYMBOL {
181            // convert to upper/lower case
182            let (upper, lower) = convert_case(keysyms[column as usize & !1]);
183            if column & 1 == 0 {
184                return Ok(lower);
185            } else {
186                return Ok(upper);
187            }
188        }
189    }
190
191    Ok(keysyms[column as usize])
192}
193
194/// Tell whether a keysym is a keypad key.
195pub fn is_keypad_key(keysym: Keysym) -> bool {
196    matches!(keysym, KEY_KP_Space..=KEY_KP_Equal)
197}
198
199/// Tell whether a keysym is a private keypad key.
200pub fn is_private_keypad_key(keysym: Keysym) -> bool {
201    matches!(keysym, 0x11000000..=0x1100FFFF)
202}
203
204/// Tell whether a keysym is a cursor key.
205pub fn is_cursor_key(keysym: Keysym) -> bool {
206    matches!(keysym, KEY_Home..=KEY_Select)
207}
208
209/// Tell whether a keysym is a PF key.
210pub fn is_pf_key(keysym: Keysym) -> bool {
211    matches!(keysym, KEY_KP_F1..=KEY_KP_F4)
212}
213
214/// Tell whether a keysym is a function key.
215pub fn is_function_key(keysym: Keysym) -> bool {
216    matches!(keysym, KEY_F1..=KEY_F35)
217}
218
219/// Tell whether a key is a miscellaneous function key.
220pub fn is_misc_function_key(keysym: Keysym) -> bool {
221    matches!(keysym, KEY_Select..=KEY_Break)
222}
223
224/// Tell whether a key is a modifier key.
225pub fn is_modifier_key(keysym: Keysym) -> bool {
226    matches!(
227        keysym,
228        KEY_Shift_L..=KEY_Hyper_R
229         | KEY_ISO_Lock..=KEY_ISO_Level5_Lock
230         | KEY_Mode_switch
231         | KEY_Num_Lock
232    )
233}
234
235/// Convert a keysym to its uppercase/lowercase equivalents.
236fn convert_case(keysym: Keysym) -> (Keysym, Keysym) {
237    // by default, they're both the regular keysym
238    let (mut upper, mut lower) = (keysym, keysym);
239
240    // tell which language it belongs to
241    #[allow(non_upper_case_globals)]
242    match keysym {
243        KEY_A..=KEY_Z => lower += KEY_a - KEY_A,
244        KEY_a..=KEY_z => upper -= KEY_a - KEY_A,
245        KEY_Agrave..=KEY_Odiaeresis => lower += KEY_agrave - KEY_Agrave,
246        KEY_agrave..=KEY_odiaeresis => upper -= KEY_agrave - KEY_Agrave,
247        KEY_Ooblique..=KEY_Thorn => lower += KEY_oslash - KEY_Ooblique,
248        KEY_oslash..=KEY_thorn => upper -= KEY_oslash - KEY_Ooblique,
249        KEY_Aogonek => lower = KEY_aogonek,
250        KEY_aogonek => upper = KEY_Aogonek,
251        KEY_Lstroke..=KEY_Sacute => lower += KEY_lstroke - KEY_Lstroke,
252        KEY_lstroke..=KEY_sacute => upper -= KEY_lstroke - KEY_Lstroke,
253        KEY_Scaron..=KEY_Zacute => lower += KEY_scaron - KEY_Scaron,
254        KEY_scaron..=KEY_zacute => upper -= KEY_scaron - KEY_Scaron,
255        KEY_Zcaron..=KEY_Zabovedot => lower += KEY_zcaron - KEY_Zcaron,
256        KEY_zcaron..=KEY_zabovedot => upper -= KEY_zcaron - KEY_Zcaron,
257        KEY_Racute..=KEY_Tcedilla => lower += KEY_racute - KEY_Racute,
258        KEY_racute..=KEY_tcedilla => upper -= KEY_racute - KEY_Racute,
259        KEY_Hstroke..=KEY_Hcircumflex => lower += KEY_hstroke - KEY_Hstroke,
260        KEY_hstroke..=KEY_hcircumflex => upper -= KEY_hstroke - KEY_Hstroke,
261        KEY_Gbreve..=KEY_Jcircumflex => lower += KEY_gbreve - KEY_Gbreve,
262        KEY_gbreve..=KEY_jcircumflex => upper -= KEY_gbreve - KEY_Gbreve,
263        KEY_Cabovedot..=KEY_Scircumflex => lower += KEY_cabovedot - KEY_Cabovedot,
264        KEY_cabovedot..=KEY_scircumflex => upper -= KEY_cabovedot - KEY_Cabovedot,
265        KEY_Rcedilla..=KEY_Tslash => lower += KEY_rcedilla - KEY_Rcedilla,
266        KEY_rcedilla..=KEY_tslash => upper -= KEY_rcedilla - KEY_Rcedilla,
267        KEY_ENG => lower = KEY_eng,
268        KEY_eng => upper = KEY_ENG,
269        KEY_Amacron..=KEY_Umacron => lower += KEY_amacron - KEY_Amacron,
270        KEY_amacron..=KEY_umacron => upper -= KEY_amacron - KEY_Amacron,
271        KEY_Serbian_DJE..=KEY_Serbian_DZE => lower -= KEY_Serbian_DJE - KEY_Serbian_dje,
272        KEY_Serbian_dje..=KEY_Serbian_dze => upper += KEY_Serbian_DJE - KEY_Serbian_dje,
273        KEY_Cyrillic_YU..=KEY_Cyrillic_HARDSIGN => lower -= KEY_Cyrillic_YU - KEY_Cyrillic_yu,
274        KEY_Cyrillic_yu..=KEY_Cyrillic_hardsign => upper += KEY_Cyrillic_YU - KEY_Cyrillic_yu,
275        KEY_Greek_ALPHAaccent..=KEY_Greek_OMEGAaccent => {
276            lower += KEY_Greek_alphaaccent - KEY_Greek_ALPHAaccent
277        }
278        KEY_Greek_alphaaccent..=KEY_Greek_omegaaccent
279            if !matches!(
280                keysym,
281                KEY_Greek_iotaaccentdieresis | KEY_Greek_upsilonaccentdieresis
282            ) =>
283        {
284            upper -= KEY_Greek_alphaaccent - KEY_Greek_ALPHAaccent
285        }
286        KEY_Greek_ALPHA..=KEY_Greek_OMEGA => lower += KEY_Greek_alpha - KEY_Greek_ALPHA,
287        KEY_Greek_alpha..=KEY_Greek_omega if !matches!(keysym, KEY_Greek_finalsmallsigma) => {
288            upper -= KEY_Greek_alpha - KEY_Greek_ALPHA
289        }
290        KEY_Armenian_AYB..=KEY_Armenian_fe => {
291            lower |= 1;
292            upper &= !1;
293        }
294        _ => {}
295    }
296
297    (upper, lower)
298}