diff --git a/Cargo.toml b/Cargo.toml index 534fb563..54cbd5d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/wrappers.rs b/src/wrappers.rs index d06a6d8b..d5d30536 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -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; diff --git a/src/wrappers/xkbcommon.rs b/src/wrappers/xkbcommon.rs new file mode 100644 index 00000000..7c84c3d9 --- /dev/null +++ b/src/wrappers/xkbcommon.rs @@ -0,0 +1,75 @@ +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 { + xkbc::xkbcommon_option().and_then(|xkb_common| { + xkbc::x11::xkbcommon_x11_option().and_then(|xkb_x11| { + 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); + let keymap = (xkb_x11.xkb_x11_keymap_new_from_device)( + context, + conn, + device_id, + xkbc::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + (xkb_x11.xkb_x11_state_new_from_device)(keymap, conn, device_id) + }; + Some(XkbcommonState { state, xkb_common }) + }) + }) + } + + 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) + } +} diff --git a/src/x11/event_loop.rs b/src/x11/event_loop.rs index 21b50b1d..a0e9f6ce 100644 --- a/src/x11/event_loop.rs +++ b/src/x11/event_loop.rs @@ -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}; @@ -22,12 +23,14 @@ pub(super) struct EventLoop { event_loop_running: bool, drag_n_drop: DragNDropState, + + xkb_state: Option, } impl EventLoop { pub fn new( window: WindowInner, handler: impl WindowHandler + 'static, - parent_handle: Option, + parent_handle: Option, xkb_state: Option, ) -> Self { Self { window, @@ -37,6 +40,7 @@ impl EventLoop { event_loop_running: false, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, + xkb_state, } } @@ -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(_) => { diff --git a/src/x11/keyboard.rs b/src/x11/keyboard.rs index 634a205a..3af346dc 100644 --- a/src/x11/keyboard.rs +++ b/src/x11/keyboard.rs @@ -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, +) -> Key { fn a(s: &str) -> Key { Key::Character(s.into()) } @@ -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, + ) -> 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()) @@ -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(" "), @@ -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, +) -> 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, +) -> 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; diff --git a/src/x11/window.rs b/src/x11/window.rs index 448e648e..1b0b2c25 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -2,9 +2,9 @@ use std::cell::Cell; use std::error::Error; use std::ffi::c_void; use std::rc::Rc; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; -use std::sync::Arc; use std::thread::{self, JoinHandle}; use raw_window_handle::{ @@ -26,7 +26,7 @@ use crate::{ }; #[cfg(feature = "opengl")] -use crate::gl::{platform, GlContext}; +use crate::gl::{GlContext, platform}; use crate::x11::event_loop::EventLoop; use crate::x11::visual_info::WindowVisualConfig; @@ -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); @@ -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(()) }