winit/platform_impl/linux/common/xkb/
mod.rs

1use std::ops::Deref;
2use std::os::raw::c_char;
3use std::ptr::{self, NonNull};
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use crate::utils::Lazy;
7use smol_str::SmolStr;
8#[cfg(wayland_platform)]
9use std::os::unix::io::OwnedFd;
10use tracing::warn;
11use xkbcommon_dl::{
12    self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
13    xkbcommon_handle, XkbCommon, XkbCommonCompose,
14};
15#[cfg(x11_platform)]
16use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
17
18use crate::event::{ElementState, KeyEvent};
19use crate::keyboard::{Key, KeyLocation};
20use crate::platform_impl::KeyEventExtra;
21
22mod compose;
23mod keymap;
24mod state;
25
26use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
27use keymap::XkbKeymap;
28
29#[cfg(x11_platform)]
30pub use keymap::raw_keycode_to_physicalkey;
31pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
32pub use state::XkbState;
33
34// TODO: Wire this up without using a static `AtomicBool`.
35static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
36
37static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
38static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
39#[cfg(feature = "x11")]
40static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
41
42#[inline(always)]
43pub fn reset_dead_keys() {
44    RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
45}
46
47#[derive(Debug)]
48pub enum Error {
49    /// libxkbcommon is not available
50    XKBNotFound,
51}
52
53#[derive(Debug)]
54pub struct Context {
55    // NOTE: field order matters.
56    #[cfg(x11_platform)]
57    pub core_keyboard_id: i32,
58    state: Option<XkbState>,
59    keymap: Option<XkbKeymap>,
60    compose_state1: Option<XkbComposeState>,
61    compose_state2: Option<XkbComposeState>,
62    _compose_table: Option<XkbComposeTable>,
63    context: XkbContext,
64    scratch_buffer: Vec<u8>,
65}
66
67impl Context {
68    pub fn new() -> Result<Self, Error> {
69        if xkb::xkbcommon_option().is_none() {
70            return Err(Error::XKBNotFound);
71        }
72
73        let context = XkbContext::new()?;
74        let mut compose_table = XkbComposeTable::new(&context);
75        let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
76        let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
77
78        // Disable compose if anything compose related failed to initialize.
79        if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
80            compose_state2 = None;
81            compose_state1 = None;
82            compose_table = None;
83        }
84
85        Ok(Self {
86            state: None,
87            keymap: None,
88            compose_state1,
89            compose_state2,
90            #[cfg(x11_platform)]
91            core_keyboard_id: 0,
92            _compose_table: compose_table,
93            context,
94            scratch_buffer: Vec::with_capacity(8),
95        })
96    }
97
98    #[cfg(feature = "x11")]
99    pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
100        let result = unsafe {
101            (XKBXH.xkb_x11_setup_xkb_extension)(
102                xcb,
103                1,
104                2,
105                xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
106                ptr::null_mut(),
107                ptr::null_mut(),
108                ptr::null_mut(),
109                ptr::null_mut(),
110            )
111        };
112
113        if result != 1 {
114            return Err(Error::XKBNotFound);
115        }
116
117        let mut this = Self::new()?;
118        this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
119        this.set_keymap_from_x11(xcb);
120        Ok(this)
121    }
122
123    pub fn state_mut(&mut self) -> Option<&mut XkbState> {
124        self.state.as_mut()
125    }
126
127    pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
128        self.keymap.as_mut()
129    }
130
131    #[cfg(wayland_platform)]
132    pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
133        let keymap = XkbKeymap::from_fd(&self.context, fd, size);
134        let state = keymap.as_ref().and_then(XkbState::new_wayland);
135        if keymap.is_none() || state.is_none() {
136            warn!("failed to update xkb keymap");
137        }
138        self.state = state;
139        self.keymap = keymap;
140    }
141
142    #[cfg(x11_platform)]
143    pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
144        let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
145        let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap));
146        if keymap.is_none() || state.is_none() {
147            warn!("failed to update xkb keymap");
148        }
149        self.state = state;
150        self.keymap = keymap;
151    }
152
153    /// Key builder context with the user provided xkb state.
154    pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
155        let state = self.state.as_mut()?;
156        let keymap = self.keymap.as_mut()?;
157        let compose_state1 = self.compose_state1.as_mut();
158        let compose_state2 = self.compose_state2.as_mut();
159        let scratch_buffer = &mut self.scratch_buffer;
160        Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
161    }
162
163    /// Key builder context with the user provided xkb state.
164    ///
165    /// Should be used when the original context must not be altered.
166    #[cfg(x11_platform)]
167    pub fn key_context_with_state<'a>(
168        &'a mut self,
169        state: &'a mut XkbState,
170    ) -> Option<KeyContext<'a>> {
171        let keymap = self.keymap.as_mut()?;
172        let compose_state1 = self.compose_state1.as_mut();
173        let compose_state2 = self.compose_state2.as_mut();
174        let scratch_buffer = &mut self.scratch_buffer;
175        Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
176    }
177}
178
179pub struct KeyContext<'a> {
180    pub state: &'a mut XkbState,
181    pub keymap: &'a mut XkbKeymap,
182    compose_state1: Option<&'a mut XkbComposeState>,
183    compose_state2: Option<&'a mut XkbComposeState>,
184    scratch_buffer: &'a mut Vec<u8>,
185}
186
187impl KeyContext<'_> {
188    pub fn process_key_event(
189        &mut self,
190        keycode: u32,
191        state: ElementState,
192        repeat: bool,
193    ) -> KeyEvent {
194        let mut event =
195            KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
196        let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
197        let (logical_key, location) = event.key();
198        let text = event.text();
199        let (key_without_modifiers, _) = event.key_without_modifiers();
200        let text_with_all_modifiers = event.text_with_all_modifiers();
201
202        let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
203
204        KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
205    }
206
207    fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
208        self.scratch_buffer.clear();
209        self.scratch_buffer.reserve(8);
210        loop {
211            let bytes_written = unsafe {
212                (XKBH.xkb_keysym_to_utf8)(
213                    keysym,
214                    self.scratch_buffer.as_mut_ptr().cast(),
215                    self.scratch_buffer.capacity(),
216                )
217            };
218            if bytes_written == 0 {
219                return None;
220            } else if bytes_written == -1 {
221                self.scratch_buffer.reserve(8);
222            } else {
223                unsafe { self.scratch_buffer.set_len(bytes_written.try_into().unwrap()) };
224                break;
225            }
226        }
227
228        // Remove the null-terminator
229        self.scratch_buffer.pop();
230        byte_slice_to_smol_str(self.scratch_buffer)
231    }
232}
233
234struct KeyEventResults<'a, 'b> {
235    context: &'a mut KeyContext<'b>,
236    keycode: u32,
237    keysym: u32,
238    compose: ComposeStatus,
239}
240
241impl<'a, 'b> KeyEventResults<'a, 'b> {
242    fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
243        let keysym = context.state.get_one_sym_raw(keycode);
244
245        let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
246            if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
247                state.reset();
248                context.compose_state2.as_mut().unwrap().reset();
249            }
250            state.feed(keysym)
251        } else {
252            ComposeStatus::None
253        };
254
255        KeyEventResults { context, keycode, keysym, compose }
256    }
257
258    pub fn key(&mut self) -> (Key, KeyLocation) {
259        let (key, location) = match self.keysym_to_key(self.keysym) {
260            Ok(known) => return known,
261            Err(undefined) => undefined,
262        };
263
264        if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
265            let compose_state = self.context.compose_state2.as_mut().unwrap();
266            // When pressing a dead key twice, the non-combining variant of that character will
267            // be produced. Since this function only concerns itself with a single keypress, we
268            // simulate this double press here by feeding the keysym to the compose state
269            // twice.
270
271            compose_state.feed(self.keysym);
272            if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
273                // Extracting only a single `char` here *should* be fine, assuming that no
274                // dead key's non-combining variant ever occupies more than one `char`.
275                let text = compose_state.get_string(self.context.scratch_buffer);
276                let key = Key::Dead(text.and_then(|s| s.chars().next()));
277                (key, location)
278            } else {
279                (key, location)
280            }
281        } else {
282            let key = self
283                .composed_text()
284                .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
285                .map(Key::Character)
286                .unwrap_or(key);
287            (key, location)
288        }
289    }
290
291    pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
292        // This will become a pointer to an array which libxkbcommon owns, so we don't need to
293        // deallocate it.
294        let layout = self.context.state.layout(self.keycode);
295        let keysym = self.context.keymap.first_keysym_by_level(layout, self.keycode);
296
297        match self.keysym_to_key(keysym) {
298            Ok((key, location)) => (key, location),
299            Err((key, location)) => {
300                let key =
301                    self.context.keysym_to_utf8_raw(keysym).map(Key::Character).unwrap_or(key);
302                (key, location)
303            },
304        }
305    }
306
307    fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
308        let location = keymap::keysym_location(keysym);
309        let key = keymap::keysym_to_key(keysym);
310        if matches!(key, Key::Unidentified(_)) {
311            Err((key, location))
312        } else {
313            Ok((key, location))
314        }
315    }
316
317    pub fn text(&mut self) -> Option<SmolStr> {
318        self.composed_text().unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
319    }
320
321    // The current behaviour makes it so composing a character overrides attempts to input a
322    // control character with the `Ctrl` key. We can potentially add a configuration option
323    // if someone specifically wants the oppsite behaviour.
324    pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
325        match self.composed_text() {
326            Ok(text) => text,
327            Err(_) => self.context.state.get_utf8_raw(self.keycode, self.context.scratch_buffer),
328        }
329    }
330
331    fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
332        match self.compose {
333            ComposeStatus::Accepted(status) => match status {
334                xkb_compose_status::XKB_COMPOSE_COMPOSED => {
335                    let state = self.context.compose_state1.as_mut().unwrap();
336                    Ok(state.get_string(self.context.scratch_buffer))
337                },
338                xkb_compose_status::XKB_COMPOSE_COMPOSING
339                | xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
340                xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
341            },
342            _ => Err(()),
343        }
344    }
345}
346
347#[derive(Debug)]
348pub struct XkbContext {
349    context: NonNull<xkb_context>,
350}
351
352impl XkbContext {
353    pub fn new() -> Result<Self, Error> {
354        let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
355
356        let context = match NonNull::new(context) {
357            Some(context) => context,
358            None => return Err(Error::XKBNotFound),
359        };
360
361        Ok(Self { context })
362    }
363}
364
365impl Drop for XkbContext {
366    fn drop(&mut self) {
367        unsafe {
368            (XKBH.xkb_context_unref)(self.context.as_ptr());
369        }
370    }
371}
372
373impl Deref for XkbContext {
374    type Target = NonNull<xkb_context>;
375
376    fn deref(&self) -> &Self::Target {
377        &self.context
378    }
379}
380
381/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
382/// `xkb_state_key_get_utf8`.
383fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
384where
385    F: FnMut(*mut c_char, usize) -> i32,
386{
387    let size = f(ptr::null_mut(), 0);
388    if size == 0 {
389        return None;
390    }
391    let size = usize::try_from(size).unwrap();
392    scratch_buffer.clear();
393    // The allocated buffer must include space for the null-terminator.
394    scratch_buffer.reserve(size + 1);
395    unsafe {
396        let written = f(scratch_buffer.as_mut_ptr().cast(), scratch_buffer.capacity());
397        if usize::try_from(written).unwrap() != size {
398            // This will likely never happen.
399            return None;
400        }
401        scratch_buffer.set_len(size);
402    };
403
404    byte_slice_to_smol_str(scratch_buffer)
405}
406
407// NOTE: This is track_caller so we can have more informative line numbers when logging
408#[track_caller]
409fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
410    std::str::from_utf8(bytes)
411        .map(SmolStr::new)
412        .map_err(|e| {
413            tracing::warn!("UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes)
414        })
415        .ok()
416}