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