Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ raw-window-handle = "0.5"

[target.'cfg(target_os="linux")'.dependencies]
x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false }
xkbcommon-dl = { version = "0.4", features = ["x11"] }
x11-dl = { version = "2.21" }
polling = "3.11.0"
percent-encoding = "2.3.1"
Expand Down
4 changes: 4 additions & 0 deletions src/wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#[cfg(target_os = "linux")]
pub mod xlib;

/// Wrappers and utilities around xkbcommon. (provided by xkbcommon_dl)
#[cfg(target_os = "linux")]
pub mod xkbcommon;

/// Wrappers and utilities around GLX
#[cfg(all(target_os = "linux", feature = "opengl"))]
pub mod glx;
Expand Down
86 changes: 86 additions & 0 deletions src/wrappers/xkbcommon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use xkbcommon_dl as xkbc;

pub(crate) type Keycode = xkbcommon_dl::xkb_keycode_t;
/// A xkbcommon state object
pub struct XkbcommonState {
state: *mut xkbc::xkb_state,
xkb_common: &'static xkbc::XkbCommon,
}

impl XkbcommonState {
pub fn new(xcb_connection: &crate::x11::XcbConnection) -> Option<Self> {
let xkb_common = xkbc::xkbcommon_option()?;
let xkb_x11 = xkbc::x11::xkbcommon_x11_option()?;

// libxkbcommon is avail, let's allocate a new context
let context =
unsafe { (xkb_common.xkb_context_new)(xkbc::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };

let conn: *mut xkbc::x11::xcb_connection_t =
xcb_connection.conn.xcb_connection().get_raw_xcb_connection();

let state = unsafe {
let device_id = (xkb_x11.xkb_x11_get_core_keyboard_device_id)(conn);
assert!(device_id >= 0);
// keymap should be unref when XkbcommonState drop
let keymap = (xkb_x11.xkb_x11_keymap_new_from_device)(
context,
conn,
device_id,
xkbc::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
// state will be unref on drop
(xkb_x11.xkb_x11_state_new_from_device)(keymap, conn, device_id)
};

// free the context, it's no longer needed
unsafe { (xkb_common.xkb_context_unref)(context) };

Some(XkbcommonState { state, xkb_common })
}
Comment thread
prokopyl marked this conversation as resolved.

pub fn key_get_utf8(&self, code: Keycode) -> String {
// A buffer to store the cstr
let buffer_size = 32;
let mut buffer = vec![0; buffer_size];
let result = unsafe {
(self.xkb_common.xkb_state_key_get_utf8)(
self.state,
code,
buffer.as_mut_ptr(),
buffer_size,
)
};

// Convert back to String
if result < 0 {
"".to_string()
} else {
let c_str = unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) };
match c_str.to_str() {
Ok(s) => s.to_string(),
Err(_) => "".to_string(),
}
}
}

pub fn update_key(&mut self, code: Keycode, dir: xkbc::xkb_key_direction) {
unsafe {
(self.xkb_common.xkb_state_update_key)(self.state, code, dir);
}
}

pub fn update_key_down(&mut self, code: Keycode) {
self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_DOWN)
}

pub fn update_key_up(&mut self, code: Keycode) {
self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_UP)
}
}

impl Drop for XkbcommonState {
fn drop(&mut self) {
unsafe { (self.xkb_common.xkb_state_unref)(self.state) };
}
}
12 changes: 9 additions & 3 deletions src/x11/event_loop.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus};
use crate::wrappers::xkbcommon::XkbcommonState;
use crate::x11::drag_n_drop::DragNDropState;
use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods};
use crate::x11::{ParentHandle, Window, WindowInner};
Expand All @@ -22,12 +23,14 @@ pub(super) struct EventLoop {
event_loop_running: bool,

drag_n_drop: DragNDropState,

xkb_state: Option<XkbcommonState>,
}

impl EventLoop {
pub fn new(
window: WindowInner, handler: impl WindowHandler + 'static,
parent_handle: Option<ParentHandle>,
parent_handle: Option<ParentHandle>, xkb_state: Option<XkbcommonState>,
) -> Self {
Self {
window,
Expand All @@ -37,6 +40,7 @@ impl EventLoop {
event_loop_running: false,
new_physical_size: None,
drag_n_drop: DragNDropState::NoCurrentSession,
xkb_state,
}
}

Expand Down Expand Up @@ -264,11 +268,13 @@ impl EventLoop {
// keys
////
XEvent::KeyPress(event) => {
self.handle_event(Event::Keyboard(convert_key_press_event(&event)));
let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state));
self.handle_event(ev);
}

XEvent::KeyRelease(event) => {
self.handle_event(Event::Keyboard(convert_key_release_event(&event)));
let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state));
self.handle_event(ev);
}

XEvent::FocusIn(_) => {
Expand Down
143 changes: 88 additions & 55 deletions src/x11/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ use x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent};
use keyboard_types::*;

use crate::keyboard::code_to_location;
use crate::wrappers::xkbcommon::{Keycode, XkbcommonState};

/// Convert a hardware scan code to a key.
///
/// Note: this is a hardcoded layout. We need to detect the user's
/// layout from the system and apply it.
fn code_to_key(code: Code, m: Modifiers) -> Key {
fn code_to_key(
code: Code, m: Modifiers, hw_code: Keycode, xkb_state: &Option<XkbcommonState>,
) -> Key {
fn a(s: &str) -> Key {
Key::Character(s.into())
}
Expand All @@ -39,6 +39,21 @@ fn code_to_key(code: Code, m: Modifiers) -> Key {
Key::Character(base.into())
}
}
fn k(
mods: Modifiers, base: &str, shifted: &str, code: Keycode, state: &Option<XkbcommonState>,
) -> Key {
if let Some(state) = state {
if mods.contains(Modifiers::CONTROL) {
// When ctrl is set, then state.key_get_utf8 return control sequence like \x1e.
// TODO: handle this better?
Key::Character(base.into())
} else {
Key::Character(state.key_get_utf8(code))
}
} else {
s(mods, base, shifted)
}
}
fn n(mods: Modifiers, base: Key, num: &str) -> Key {
if mods.contains(Modifiers::NUM_LOCK) != mods.contains(Modifiers::SHIFT) {
Key::Character(num.into())
Expand All @@ -47,55 +62,55 @@ fn code_to_key(code: Code, m: Modifiers) -> Key {
}
}
match code {
Code::KeyA => s(m, "a", "A"),
Code::KeyB => s(m, "b", "B"),
Code::KeyC => s(m, "c", "C"),
Code::KeyD => s(m, "d", "D"),
Code::KeyE => s(m, "e", "E"),
Code::KeyF => s(m, "f", "F"),
Code::KeyG => s(m, "g", "G"),
Code::KeyH => s(m, "h", "H"),
Code::KeyI => s(m, "i", "I"),
Code::KeyJ => s(m, "j", "J"),
Code::KeyK => s(m, "k", "K"),
Code::KeyL => s(m, "l", "L"),
Code::KeyM => s(m, "m", "M"),
Code::KeyN => s(m, "n", "N"),
Code::KeyO => s(m, "o", "O"),
Code::KeyP => s(m, "p", "P"),
Code::KeyQ => s(m, "q", "Q"),
Code::KeyR => s(m, "r", "R"),
Code::KeyS => s(m, "s", "S"),
Code::KeyT => s(m, "t", "T"),
Code::KeyU => s(m, "u", "U"),
Code::KeyV => s(m, "v", "V"),
Code::KeyW => s(m, "w", "W"),
Code::KeyX => s(m, "x", "X"),
Code::KeyY => s(m, "y", "Y"),
Code::KeyZ => s(m, "z", "Z"),
Code::KeyA => k(m, "a", "A", hw_code, xkb_state),
Code::KeyB => k(m, "b", "B", hw_code, xkb_state),
Code::KeyC => k(m, "c", "C", hw_code, xkb_state),
Code::KeyD => k(m, "d", "D", hw_code, xkb_state),
Code::KeyE => k(m, "e", "E", hw_code, xkb_state),
Code::KeyF => k(m, "f", "F", hw_code, xkb_state),
Code::KeyG => k(m, "g", "G", hw_code, xkb_state),
Code::KeyH => k(m, "h", "H", hw_code, xkb_state),
Code::KeyI => k(m, "i", "I", hw_code, xkb_state),
Code::KeyJ => k(m, "j", "J", hw_code, xkb_state),
Code::KeyK => k(m, "k", "K", hw_code, xkb_state),
Code::KeyL => k(m, "l", "L", hw_code, xkb_state),
Code::KeyM => k(m, "m", "M", hw_code, xkb_state),
Code::KeyN => k(m, "n", "N", hw_code, xkb_state),
Code::KeyO => k(m, "o", "O", hw_code, xkb_state),
Code::KeyP => k(m, "p", "P", hw_code, xkb_state),
Code::KeyQ => k(m, "q", "Q", hw_code, xkb_state),
Code::KeyR => k(m, "r", "R", hw_code, xkb_state),
Code::KeyS => k(m, "s", "S", hw_code, xkb_state),
Code::KeyT => k(m, "t", "T", hw_code, xkb_state),
Code::KeyU => k(m, "u", "U", hw_code, xkb_state),
Code::KeyV => k(m, "v", "V", hw_code, xkb_state),
Code::KeyW => k(m, "w", "W", hw_code, xkb_state),
Code::KeyX => k(m, "x", "X", hw_code, xkb_state),
Code::KeyY => k(m, "y", "Y", hw_code, xkb_state),
Code::KeyZ => k(m, "z", "Z", hw_code, xkb_state),

Code::Digit0 => s(m, "0", ")"),
Code::Digit1 => s(m, "1", "!"),
Code::Digit2 => s(m, "2", "@"),
Code::Digit3 => s(m, "3", "#"),
Code::Digit4 => s(m, "4", "$"),
Code::Digit5 => s(m, "5", "%"),
Code::Digit6 => s(m, "6", "^"),
Code::Digit7 => s(m, "7", "&"),
Code::Digit8 => s(m, "8", "*"),
Code::Digit9 => s(m, "9", "("),
Code::Digit0 => k(m, "0", ")", hw_code, xkb_state),
Code::Digit1 => k(m, "1", "!", hw_code, xkb_state),
Code::Digit2 => k(m, "2", "@", hw_code, xkb_state),
Code::Digit3 => k(m, "3", "#", hw_code, xkb_state),
Code::Digit4 => k(m, "4", "$", hw_code, xkb_state),
Code::Digit5 => k(m, "5", "%", hw_code, xkb_state),
Code::Digit6 => k(m, "6", "^", hw_code, xkb_state),
Code::Digit7 => k(m, "7", "&", hw_code, xkb_state),
Code::Digit8 => k(m, "8", "*", hw_code, xkb_state),
Code::Digit9 => k(m, "9", "(", hw_code, xkb_state),

Code::Backquote => s(m, "`", "~"),
Code::Minus => s(m, "-", "_"),
Code::Equal => s(m, "=", "+"),
Code::BracketLeft => s(m, "[", "{"),
Code::BracketRight => s(m, "]", "}"),
Code::Backslash => s(m, "\\", "|"),
Code::Semicolon => s(m, ";", ":"),
Code::Quote => s(m, "'", "\""),
Code::Comma => s(m, ",", "<"),
Code::Period => s(m, ".", ">"),
Code::Slash => s(m, "/", "?"),
Code::Backquote => k(m, "`", "~", hw_code, xkb_state),
Code::Minus => k(m, "-", "_", hw_code, xkb_state),
Code::Equal => k(m, "=", "+", hw_code, xkb_state),
Code::BracketLeft => k(m, "[", "{", hw_code, xkb_state),
Code::BracketRight => k(m, "]", "}", hw_code, xkb_state),
Code::Backslash => k(m, "\\", "|", hw_code, xkb_state),
Code::Semicolon => k(m, ";", ":", hw_code, xkb_state),
Code::Quote => k(m, "'", "\"", hw_code, xkb_state),
Code::Comma => k(m, ",", "<", hw_code, xkb_state),
Code::Period => k(m, ".", ">", hw_code, xkb_state),
Code::Slash => k(m, "/", "?", hw_code, xkb_state),

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

Expand Down Expand Up @@ -383,22 +398,40 @@ pub(super) fn key_mods(mods: KeyButMask) -> Modifiers {
ret
}

pub(super) fn convert_key_press_event(key_press: &KeyPressEvent) -> KeyboardEvent {
pub(super) fn convert_key_press_event(
key_press: &KeyPressEvent, state: &mut Option<XkbcommonState>,
) -> KeyboardEvent {
let hw_keycode = key_press.detail;

// Update the xkbc state
let hw_code = hw_keycode.into();
if let Some(state) = state {
state.update_key_down(hw_code);
}

let code = hardware_keycode_to_code(hw_keycode.into());
let modifiers = key_mods(key_press.state);
let key = code_to_key(code, modifiers);
let key = code_to_key(code, modifiers, hw_code, state);
let location = code_to_location(code);
let state = KeyState::Down;

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

pub(super) fn convert_key_release_event(key_release: &KeyReleaseEvent) -> KeyboardEvent {
pub(super) fn convert_key_release_event(
key_release: &KeyReleaseEvent, state: &mut Option<XkbcommonState>,
) -> KeyboardEvent {
let hw_keycode = key_release.detail;

// Update the xkbc state
let hw_code = hw_keycode.into();
if let Some(state) = state {
state.update_key_up(hw_code);
}

let code = hardware_keycode_to_code(hw_keycode.into());
let modifiers = key_mods(key_release.state);
let key = code_to_key(code, modifiers);
let key = code_to_key(code, modifiers, hw_code, state);
let location = code_to_location(code);
let state = KeyState::Up;

Expand Down
5 changes: 4 additions & 1 deletion src/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ impl<'a> Window<'a> {
// FIXME: baseview error type instead of unwrap()
let xcb_connection = XcbConnection::new()?;

// Setup xkbcommon
let xkb_state = crate::wrappers::xkbcommon::XkbcommonState::new(&xcb_connection);

// Get screen information
let screen = xcb_connection.screen();
let parent_id = parent.unwrap_or(screen.root);
Expand Down Expand Up @@ -303,7 +306,7 @@ impl<'a> Window<'a> {

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

EventLoop::new(inner, handler, parent_handle).run()?;
EventLoop::new(inner, handler, parent_handle, xkb_state).run()?;

Ok(())
}
Expand Down
Loading