Skip to content

Commit 871f2bd

Browse files
Use the user's keyboard layout from the x11 system
This change uses xkbcommon to decode the key utf8 value based on the user's current layout. The implementation is adapted from the x11rb's xkbcommon-example.
1 parent 9424f0b commit 871f2bd

7 files changed

Lines changed: 173 additions & 60 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ raw-window-handle = "0.5"
3737

3838
[target.'cfg(target_os="linux")'.dependencies]
3939
x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false }
40+
xkbcommon-dl = { version = "0.4", features = ["x11"] }
4041
x11-dl = { version = "2.21" }
4142
polling = "3.11.0"
4243
percent-encoding = "2.3.1"

src/wrappers.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#[cfg(target_os = "linux")]
1616
pub mod xlib;
1717

18+
/// Wrappers and utilities around xkbcommon. (provided by xkbcommon_dl)
19+
#[cfg(target_os = "linux")]
20+
pub mod xkbcommon;
21+
1822
/// Wrappers and utilities around GLX
1923
#[cfg(all(target_os = "linux", feature = "opengl"))]
2024
pub mod glx;

src/wrappers/xkbcommon.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use xkbcommon_dl as xkbc;
2+
3+
pub(crate) type Keycode = xkbcommon_dl::xkb_keycode_t;
4+
/// A xkbcommon state object
5+
pub struct XkbcommonState {
6+
state: *mut xkbc::xkb_state,
7+
xkb_common: &'static xkbc::XkbCommon,
8+
}
9+
10+
impl XkbcommonState {
11+
pub fn new(xcb_connection: &mut crate::x11::XcbConnection) -> Self {
12+
let xkb_common = xkbc::xkbcommon_handle();
13+
let context =
14+
unsafe { (xkb_common.xkb_context_new)(xkbc::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
15+
16+
let conn_ptr: *mut x11rb::xcb_ffi::XCBConnection =
17+
&mut *xcb_connection.conn.xcb_connection_mut();
18+
let conn: *mut xkbc::x11::xcb_connection_t = conn_ptr as *mut std::ffi::c_void;
19+
20+
let xkb_x11 = xkbc::x11::xkbcommon_x11_handle();
21+
22+
let state = unsafe {
23+
let device_id = (xkb_x11.xkb_x11_get_core_keyboard_device_id)(conn);
24+
assert!(device_id >= 0);
25+
let keymap = (xkb_x11.xkb_x11_keymap_new_from_device)(
26+
context,
27+
conn,
28+
device_id,
29+
xkbc::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
30+
);
31+
(xkb_x11.xkb_x11_state_new_from_device)(keymap, conn, device_id)
32+
};
33+
XkbcommonState { state, xkb_common }
34+
}
35+
36+
pub fn key_get_utf8(&self, code: Keycode) -> String {
37+
// A buffer to store the cstr
38+
let buffer_size = 32;
39+
let mut buffer = vec![0; buffer_size];
40+
let result = unsafe {
41+
(self.xkb_common.xkb_state_key_get_utf8)(
42+
self.state,
43+
code,
44+
buffer.as_mut_ptr(),
45+
buffer_size,
46+
)
47+
};
48+
49+
// Convert back to String
50+
if result < 0 {
51+
"".to_string()
52+
} else {
53+
let c_str = unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) };
54+
match c_str.to_str() {
55+
Ok(s) => s.to_string(),
56+
Err(_) => "".to_string(),
57+
}
58+
}
59+
}
60+
61+
pub fn update_key(&mut self, code: Keycode, dir: xkbc::xkb_key_direction) {
62+
unsafe {
63+
(self.xkb_common.xkb_state_update_key)(self.state, code, dir);
64+
}
65+
}
66+
67+
pub fn update_key_down(&mut self, code: Keycode) {
68+
self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_DOWN)
69+
}
70+
71+
pub fn update_key_up(&mut self, code: Keycode) {
72+
self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_UP)
73+
}
74+
}

src/wrappers/xlib/xlib_xcb.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ impl XlibXcbConnection {
5959
&self.xcb_connection
6060
}
6161

62+
pub fn xcb_connection_mut(&mut self) -> &mut XCBConnection {
63+
&mut self.xcb_connection
64+
}
65+
6266
#[cfg(feature = "opengl")]
6367
pub fn xlib_connection(&self) -> &XlibConnection {
6468
&self.xlib_connection

src/x11/event_loop.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus};
2+
use crate::wrappers::xkbcommon::XkbcommonState;
23
use crate::x11::drag_n_drop::DragNDropState;
34
use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods};
45
use crate::x11::{ParentHandle, Window, WindowInner};
@@ -22,12 +23,14 @@ pub(super) struct EventLoop {
2223
event_loop_running: bool,
2324

2425
drag_n_drop: DragNDropState,
26+
27+
xkb_state: XkbcommonState,
2528
}
2629

2730
impl EventLoop {
2831
pub fn new(
2932
window: WindowInner, handler: impl WindowHandler + 'static,
30-
parent_handle: Option<ParentHandle>,
33+
parent_handle: Option<ParentHandle>, xkb_state: XkbcommonState,
3134
) -> Self {
3235
Self {
3336
window,
@@ -37,6 +40,7 @@ impl EventLoop {
3740
event_loop_running: false,
3841
new_physical_size: None,
3942
drag_n_drop: DragNDropState::NoCurrentSession,
43+
xkb_state,
4044
}
4145
}
4246

@@ -264,11 +268,13 @@ impl EventLoop {
264268
// keys
265269
////
266270
XEvent::KeyPress(event) => {
267-
self.handle_event(Event::Keyboard(convert_key_press_event(&event)));
271+
let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state));
272+
self.handle_event(ev);
268273
}
269274

270275
XEvent::KeyRelease(event) => {
271-
self.handle_event(Event::Keyboard(convert_key_release_event(&event)));
276+
let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state));
277+
self.handle_event(ev);
272278
}
273279

274280
XEvent::FocusIn(_) => {

src/x11/keyboard.rs

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ use x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent};
2323
use keyboard_types::*;
2424

2525
use crate::keyboard::code_to_location;
26+
use crate::wrappers::xkbcommon::{Keycode, XkbcommonState};
2627

2728
/// Convert a hardware scan code to a key.
28-
///
29-
/// Note: this is a hardcoded layout. We need to detect the user's
30-
/// layout from the system and apply it.
31-
fn code_to_key(code: Code, m: Modifiers) -> Key {
29+
fn code_to_key(code: Code, m: Modifiers, hw_code: Keycode, xkb_state: &XkbcommonState) -> Key {
3230
fn a(s: &str) -> Key {
3331
Key::Character(s.into())
3432
}
@@ -39,6 +37,15 @@ fn code_to_key(code: Code, m: Modifiers) -> Key {
3937
Key::Character(base.into())
4038
}
4139
}
40+
fn k(mods: Modifiers, base: &str, code: Keycode, state: &XkbcommonState) -> Key {
41+
if mods.contains(Modifiers::CONTROL) {
42+
// When ctrl is set, then state.key_get_utf8 return control sequence like \x1e.
43+
// TODO: handle this better?
44+
Key::Character(base.into())
45+
} else {
46+
Key::Character(state.key_get_utf8(code))
47+
}
48+
}
4249
fn n(mods: Modifiers, base: Key, num: &str) -> Key {
4350
if mods.contains(Modifiers::NUM_LOCK) != mods.contains(Modifiers::SHIFT) {
4451
Key::Character(num.into())
@@ -47,55 +54,55 @@ fn code_to_key(code: Code, m: Modifiers) -> Key {
4754
}
4855
}
4956
match code {
50-
Code::KeyA => s(m, "a", "A"),
51-
Code::KeyB => s(m, "b", "B"),
52-
Code::KeyC => s(m, "c", "C"),
53-
Code::KeyD => s(m, "d", "D"),
54-
Code::KeyE => s(m, "e", "E"),
55-
Code::KeyF => s(m, "f", "F"),
56-
Code::KeyG => s(m, "g", "G"),
57-
Code::KeyH => s(m, "h", "H"),
58-
Code::KeyI => s(m, "i", "I"),
59-
Code::KeyJ => s(m, "j", "J"),
60-
Code::KeyK => s(m, "k", "K"),
61-
Code::KeyL => s(m, "l", "L"),
62-
Code::KeyM => s(m, "m", "M"),
63-
Code::KeyN => s(m, "n", "N"),
64-
Code::KeyO => s(m, "o", "O"),
65-
Code::KeyP => s(m, "p", "P"),
66-
Code::KeyQ => s(m, "q", "Q"),
67-
Code::KeyR => s(m, "r", "R"),
68-
Code::KeyS => s(m, "s", "S"),
69-
Code::KeyT => s(m, "t", "T"),
70-
Code::KeyU => s(m, "u", "U"),
71-
Code::KeyV => s(m, "v", "V"),
72-
Code::KeyW => s(m, "w", "W"),
73-
Code::KeyX => s(m, "x", "X"),
74-
Code::KeyY => s(m, "y", "Y"),
75-
Code::KeyZ => s(m, "z", "Z"),
57+
Code::KeyA => k(m, "a", hw_code, xkb_state),
58+
Code::KeyB => k(m, "b", hw_code, xkb_state),
59+
Code::KeyC => k(m, "c", hw_code, xkb_state),
60+
Code::KeyD => k(m, "d", hw_code, xkb_state),
61+
Code::KeyE => k(m, "e", hw_code, xkb_state),
62+
Code::KeyF => k(m, "f", hw_code, xkb_state),
63+
Code::KeyG => k(m, "g", hw_code, xkb_state),
64+
Code::KeyH => k(m, "h", hw_code, xkb_state),
65+
Code::KeyI => k(m, "i", hw_code, xkb_state),
66+
Code::KeyJ => k(m, "j", hw_code, xkb_state),
67+
Code::KeyK => k(m, "k", hw_code, xkb_state),
68+
Code::KeyL => k(m, "l", hw_code, xkb_state),
69+
Code::KeyM => k(m, "m", hw_code, xkb_state),
70+
Code::KeyN => k(m, "n", hw_code, xkb_state),
71+
Code::KeyO => k(m, "o", hw_code, xkb_state),
72+
Code::KeyP => k(m, "p", hw_code, xkb_state),
73+
Code::KeyQ => k(m, "q", hw_code, xkb_state),
74+
Code::KeyR => k(m, "r", hw_code, xkb_state),
75+
Code::KeyS => k(m, "s", hw_code, xkb_state),
76+
Code::KeyT => k(m, "t", hw_code, xkb_state),
77+
Code::KeyU => k(m, "u", hw_code, xkb_state),
78+
Code::KeyV => k(m, "v", hw_code, xkb_state),
79+
Code::KeyW => k(m, "w", hw_code, xkb_state),
80+
Code::KeyX => k(m, "x", hw_code, xkb_state),
81+
Code::KeyY => k(m, "y", hw_code, xkb_state),
82+
Code::KeyZ => k(m, "z", hw_code, xkb_state),
7683

77-
Code::Digit0 => s(m, "0", ")"),
78-
Code::Digit1 => s(m, "1", "!"),
79-
Code::Digit2 => s(m, "2", "@"),
80-
Code::Digit3 => s(m, "3", "#"),
81-
Code::Digit4 => s(m, "4", "$"),
82-
Code::Digit5 => s(m, "5", "%"),
83-
Code::Digit6 => s(m, "6", "^"),
84-
Code::Digit7 => s(m, "7", "&"),
85-
Code::Digit8 => s(m, "8", "*"),
86-
Code::Digit9 => s(m, "9", "("),
84+
Code::Digit0 => k(m, "0", hw_code, xkb_state),
85+
Code::Digit1 => k(m, "1", hw_code, xkb_state),
86+
Code::Digit2 => k(m, "2", hw_code, xkb_state),
87+
Code::Digit3 => k(m, "3", hw_code, xkb_state),
88+
Code::Digit4 => k(m, "4", hw_code, xkb_state),
89+
Code::Digit5 => k(m, "5", hw_code, xkb_state),
90+
Code::Digit6 => k(m, "6", hw_code, xkb_state),
91+
Code::Digit7 => k(m, "7", hw_code, xkb_state),
92+
Code::Digit8 => k(m, "8", hw_code, xkb_state),
93+
Code::Digit9 => k(m, "9", hw_code, xkb_state),
8794

88-
Code::Backquote => s(m, "`", "~"),
89-
Code::Minus => s(m, "-", "_"),
90-
Code::Equal => s(m, "=", "+"),
91-
Code::BracketLeft => s(m, "[", "{"),
92-
Code::BracketRight => s(m, "]", "}"),
93-
Code::Backslash => s(m, "\\", "|"),
94-
Code::Semicolon => s(m, ";", ":"),
95-
Code::Quote => s(m, "'", "\""),
96-
Code::Comma => s(m, ",", "<"),
97-
Code::Period => s(m, ".", ">"),
98-
Code::Slash => s(m, "/", "?"),
95+
Code::Backquote => k(m, "`", hw_code, xkb_state),
96+
Code::Minus => k(m, "-", hw_code, xkb_state),
97+
Code::Equal => k(m, "=", hw_code, xkb_state),
98+
Code::BracketLeft => k(m, "[", hw_code, xkb_state),
99+
Code::BracketRight => k(m, "]", hw_code, xkb_state),
100+
Code::Backslash => k(m, "\\", hw_code, xkb_state),
101+
Code::Semicolon => k(m, ";", hw_code, xkb_state),
102+
Code::Quote => k(m, "'", hw_code, xkb_state),
103+
Code::Comma => k(m, ",", hw_code, xkb_state),
104+
Code::Period => k(m, ".", hw_code, xkb_state),
105+
Code::Slash => k(m, "/", hw_code, xkb_state),
99106

100107
Code::Space => a(" "),
101108

@@ -383,22 +390,36 @@ pub(super) fn key_mods(mods: KeyButMask) -> Modifiers {
383390
ret
384391
}
385392

386-
pub(super) fn convert_key_press_event(key_press: &KeyPressEvent) -> KeyboardEvent {
393+
pub(super) fn convert_key_press_event(
394+
key_press: &KeyPressEvent, state: &mut XkbcommonState,
395+
) -> KeyboardEvent {
387396
let hw_keycode = key_press.detail;
397+
398+
// Update the xkbc state
399+
let hw_code = hw_keycode.into();
400+
state.update_key_down(hw_code);
401+
388402
let code = hardware_keycode_to_code(hw_keycode.into());
389403
let modifiers = key_mods(key_press.state);
390-
let key = code_to_key(code, modifiers);
404+
let key = code_to_key(code, modifiers, hw_code, state);
391405
let location = code_to_location(code);
392406
let state = KeyState::Down;
393407

394408
KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false }
395409
}
396410

397-
pub(super) fn convert_key_release_event(key_release: &KeyReleaseEvent) -> KeyboardEvent {
411+
pub(super) fn convert_key_release_event(
412+
key_release: &KeyReleaseEvent, state: &mut XkbcommonState,
413+
) -> KeyboardEvent {
398414
let hw_keycode = key_release.detail;
415+
416+
// Update the xkbc state
417+
let hw_code = hw_keycode.into();
418+
state.update_key_up(hw_code);
419+
399420
let code = hardware_keycode_to_code(hw_keycode.into());
400421
let modifiers = key_mods(key_release.state);
401-
let key = code_to_key(code, modifiers);
422+
let key = code_to_key(code, modifiers, hw_code, state);
402423
let location = code_to_location(code);
403424
let state = KeyState::Up;
404425

src/x11/window.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ impl<'a> Window<'a> {
175175
{
176176
// Connect to the X server
177177
// FIXME: baseview error type instead of unwrap()
178-
let xcb_connection = XcbConnection::new()?;
178+
let mut xcb_connection = XcbConnection::new()?;
179+
180+
// Setup xkbcommon
181+
let xkb_state = crate::wrappers::xkbcommon::XkbcommonState::new(&mut xcb_connection);
179182

180183
// Get screen information
181184
let screen = xcb_connection.screen();
@@ -303,7 +306,7 @@ impl<'a> Window<'a> {
303306

304307
let _ = tx.send(Ok(SendableRwh(window.raw_window_handle())));
305308

306-
EventLoop::new(inner, handler, parent_handle).run()?;
309+
EventLoop::new(inner, handler, parent_handle, xkb_state).run()?;
307310

308311
Ok(())
309312
}

0 commit comments

Comments
 (0)