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

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