diff --git a/CHANGELOG.md b/CHANGELOG.md index 6176489f9..e84fb765a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Unreleased` header. # Unreleased - On X11, don't require XIM to run. +- On X11, fix xkb state not being updated correctly sometimes leading to wrong input. - Fix compatibility with 32-bit platforms without 64-bit atomics. - On macOS, fix incorrect IME cursor rect origin. - On X11, fix swapped instance and general class names. diff --git a/Cargo.toml b/Cargo.toml index 2c80b6fc1..147e2bda1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,7 +167,7 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } x11-dl = { version = "2.18.5", optional = true } x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } -xkbcommon-dl = "0.4.0" +xkbcommon-dl = "0.4.2" [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/src/platform_impl/linux/common/mod.rs b/src/platform_impl/linux/common/mod.rs index fa2391967..ed7f82cde 100644 --- a/src/platform_impl/linux/common/mod.rs +++ b/src/platform_impl/linux/common/mod.rs @@ -1,2 +1 @@ -pub mod keymap; -pub mod xkb_state; +pub mod xkb; diff --git a/src/platform_impl/linux/common/xkb/compose.rs b/src/platform_impl/linux/common/xkb/compose.rs new file mode 100644 index 000000000..65b6514ba --- /dev/null +++ b/src/platform_impl/linux/common/xkb/compose.rs @@ -0,0 +1,124 @@ +//! XKB compose handling. + +use std::env; +use std::ffi::CString; +use std::ops::Deref; +use std::os::unix::ffi::OsStringExt; +use std::ptr::NonNull; + +use super::XkbContext; +use super::XKBCH; +use smol_str::SmolStr; +use xkbcommon_dl::{ + xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags, + xkb_compose_status, xkb_compose_table, xkb_keysym_t, +}; + +#[derive(Debug)] +pub struct XkbComposeTable { + table: NonNull, +} + +impl XkbComposeTable { + pub fn new(context: &XkbContext) -> Option { + let locale = env::var_os("LC_ALL") + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LC_CTYPE")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LANG")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .unwrap_or_else(|| "C".into()); + let locale = CString::new(locale.into_vec()).unwrap(); + + let table = unsafe { + (XKBCH.xkb_compose_table_new_from_locale)( + context.as_ptr(), + locale.as_ptr(), + xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, + ) + }; + + let table = NonNull::new(table)?; + Some(Self { table }) + } + + /// Create new state with the given compose table. + pub fn new_state(&self) -> Option { + let state = unsafe { + (XKBCH.xkb_compose_state_new)( + self.table.as_ptr(), + xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, + ) + }; + + let state = NonNull::new(state)?; + Some(XkbComposeState { state }) + } +} + +impl Deref for XkbComposeTable { + type Target = NonNull; + + fn deref(&self) -> &Self::Target { + &self.table + } +} + +impl Drop for XkbComposeTable { + fn drop(&mut self) { + unsafe { + (XKBCH.xkb_compose_table_unref)(self.table.as_ptr()); + } + } +} + +#[derive(Debug)] +pub struct XkbComposeState { + state: NonNull, +} + +impl XkbComposeState { + pub fn get_string(&mut self, scratch_buffer: &mut Vec) -> Option { + super::make_string_with(scratch_buffer, |ptr, len| unsafe { + (XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len) + }) + } + + #[inline] + pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus { + let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) }; + match feed_result { + xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored, + xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => { + ComposeStatus::Accepted(self.status()) + } + } + } + + #[inline] + pub fn reset(&mut self) { + unsafe { + (XKBCH.xkb_compose_state_reset)(self.state.as_ptr()); + } + } + + #[inline] + pub fn status(&mut self) -> xkb_compose_status { + unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) } + } +} + +impl Drop for XkbComposeState { + fn drop(&mut self) { + unsafe { + (XKBCH.xkb_compose_state_unref)(self.state.as_ptr()); + }; + } +} + +#[derive(Copy, Clone, Debug)] +pub enum ComposeStatus { + Accepted(xkb_compose_status), + Ignored, + None, +} diff --git a/src/platform_impl/linux/common/keymap.rs b/src/platform_impl/linux/common/xkb/keymap.rs similarity index 88% rename from src/platform_impl/linux/common/keymap.rs rename to src/platform_impl/linux/common/xkb/keymap.rs index fa9c496e3..0fd15042a 100644 --- a/src/platform_impl/linux/common/keymap.rs +++ b/src/platform_impl/linux/common/xkb/keymap.rs @@ -1,6 +1,24 @@ -//! Convert XKB keys to Winit keys. +//! XKB keymap. + +use std::ffi::c_char; +use std::ops::Deref; +use std::ptr::{self, NonNull}; + +#[cfg(x11_platform)] +use x11_dl::xlib_xcb::xcb_connection_t; +#[cfg(wayland_platform)] +use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; + +use xkb::XKB_MOD_INVALID; +use xkbcommon_dl::{ + self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t, + xkb_layout_index_t, xkb_mod_index_t, +}; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; +#[cfg(x11_platform)] +use crate::platform_impl::common::xkb::XKBXH; +use crate::platform_impl::common::xkb::{XkbContext, XKBH}; /// Map the raw X11-style keycode to the `KeyCode` enum. /// @@ -894,3 +912,140 @@ pub fn keysym_location(keysym: u32) -> KeyLocation { _ => KeyLocation::Standard, } } + +#[derive(Debug)] +pub struct XkbKeymap { + keymap: NonNull, + _mods_indices: ModsIndices, + pub _core_keyboard_id: i32, +} + +impl XkbKeymap { + #[cfg(wayland_platform)] + pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option { + let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? }; + + let keymap = unsafe { + let keymap = (XKBH.xkb_keymap_new_from_string)( + (*context).as_ptr(), + map.as_ptr() as *const _, + xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, + xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + NonNull::new(keymap)? + }; + + Some(Self::new_inner(keymap, 0)) + } + + #[cfg(x11_platform)] + pub fn from_x11_keymap( + context: &XkbContext, + xcb: *mut xcb_connection_t, + core_keyboard_id: i32, + ) -> Option { + let keymap = unsafe { + (XKBXH.xkb_x11_keymap_new_from_device)( + context.as_ptr(), + xcb, + core_keyboard_id, + xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ) + }; + let keymap = NonNull::new(keymap)?; + Some(Self::new_inner(keymap, core_keyboard_id)) + } + + fn new_inner(keymap: NonNull, _core_keyboard_id: i32) -> Self { + let mods_indices = ModsIndices { + shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT), + caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS), + ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL), + alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT), + num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM), + mod3: mod_index_for_name(keymap, b"Mod3\0"), + logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO), + mod5: mod_index_for_name(keymap, b"Mod5\0"), + }; + + Self { + keymap, + _mods_indices: mods_indices, + _core_keyboard_id, + } + } + + #[cfg(x11_platform)] + pub fn mods_indices(&self) -> ModsIndices { + self._mods_indices + } + + pub fn first_keysym_by_level( + &mut self, + layout: xkb_layout_index_t, + keycode: xkb_keycode_t, + ) -> xkb_keysym_t { + unsafe { + let mut keysyms = ptr::null(); + let count = (XKBH.xkb_keymap_key_get_syms_by_level)( + self.keymap.as_ptr(), + keycode, + layout, + // NOTE: The level should be zero to ignore modifiers. + 0, + &mut keysyms, + ); + + if count == 1 { + *keysyms + } else { + 0 + } + } + } + + /// Check whether the given key repeats. + pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool { + unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 } + } +} + +impl Drop for XkbKeymap { + fn drop(&mut self) { + unsafe { + (XKBH.xkb_keymap_unref)(self.keymap.as_ptr()); + }; + } +} + +impl Deref for XkbKeymap { + type Target = NonNull; + fn deref(&self) -> &Self::Target { + &self.keymap + } +} + +/// Modifier index in the keymap. +#[derive(Default, Debug, Clone, Copy)] +pub struct ModsIndices { + pub shift: Option, + pub caps: Option, + pub ctrl: Option, + pub alt: Option, + pub num: Option, + pub mod3: Option, + pub logo: Option, + pub mod5: Option, +} + +fn mod_index_for_name(keymap: NonNull, name: &[u8]) -> Option { + unsafe { + let mod_index = + (XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char); + if mod_index == XKB_MOD_INVALID { + None + } else { + Some(mod_index) + } + } +} diff --git a/src/platform_impl/linux/common/xkb/mod.rs b/src/platform_impl/linux/common/xkb/mod.rs new file mode 100644 index 000000000..8ad99eed4 --- /dev/null +++ b/src/platform_impl/linux/common/xkb/mod.rs @@ -0,0 +1,461 @@ +use std::convert::TryInto; +use std::ops::Deref; +use std::os::raw::c_char; +use std::ptr::{self, NonNull}; +use std::sync::atomic::{AtomicBool, Ordering}; + +use log::warn; +use once_cell::sync::Lazy; +use smol_str::SmolStr; +#[cfg(wayland_platform)] +use std::os::unix::io::OwnedFd; +use xkbcommon_dl::{ + self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle, + xkbcommon_handle, XkbCommon, XkbCommonCompose, +}; +#[cfg(x11_platform)] +use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; + +use crate::event::ElementState; +use crate::event::KeyEvent; +use crate::keyboard::{Key, KeyLocation}; +use crate::platform_impl::KeyEventExtra; + +mod compose; +mod keymap; +mod state; + +use compose::{ComposeStatus, XkbComposeState, XkbComposeTable}; +use keymap::XkbKeymap; + +#[cfg(x11_platform)] +pub use keymap::raw_keycode_to_physicalkey; +pub use keymap::{physicalkey_to_scancode, scancode_to_keycode}; +pub use state::XkbState; + +// TODO: Wire this up without using a static `AtomicBool`. +static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); + +static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); +static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); +#[cfg(feature = "x11")] +static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); + +#[inline(always)] +pub fn reset_dead_keys() { + RESET_DEAD_KEYS.store(true, Ordering::SeqCst); +} + +#[derive(Debug)] +pub enum Error { + /// libxkbcommon is not available + XKBNotFound, +} + +#[derive(Debug)] +pub struct Context { + // NOTE: field order matters. + #[cfg(x11_platform)] + pub core_keyboard_id: i32, + state: Option, + keymap: Option, + compose_state1: Option, + compose_state2: Option, + _compose_table: Option, + context: XkbContext, + scratch_buffer: Vec, +} + +impl Context { + pub fn new() -> Result { + if xkb::xkbcommon_option().is_none() { + return Err(Error::XKBNotFound); + } + + let context = XkbContext::new()?; + let mut compose_table = XkbComposeTable::new(&context); + let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state()); + let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state()); + + // Disable compose if anything compose related failed to initialize. + if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() { + compose_state2 = None; + compose_state1 = None; + compose_table = None; + } + + Ok(Self { + state: None, + keymap: None, + compose_state1, + compose_state2, + #[cfg(x11_platform)] + core_keyboard_id: 0, + _compose_table: compose_table, + context, + scratch_buffer: Vec::with_capacity(8), + }) + } + + #[cfg(feature = "x11")] + pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result { + let result = unsafe { + (XKBXH.xkb_x11_setup_xkb_extension)( + xcb, + 1, + 2, + xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + }; + + if result != 1 { + return Err(Error::XKBNotFound); + } + + let mut this = Self::new()?; + this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) }; + this.set_keymap_from_x11(xcb); + Ok(this) + } + + pub fn state_mut(&mut self) -> Option<&mut XkbState> { + self.state.as_mut() + } + + pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> { + self.keymap.as_mut() + } + + #[cfg(wayland_platform)] + pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) { + let keymap = XkbKeymap::from_fd(&self.context, fd, size); + let state = keymap.as_ref().and_then(XkbState::new_wayland); + if keymap.is_none() || state.is_none() { + warn!("failed to update xkb keymap"); + } + self.state = state; + self.keymap = keymap; + } + + #[cfg(x11_platform)] + pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) { + let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id); + let state = keymap + .as_ref() + .and_then(|keymap| XkbState::new_x11(xcb, keymap)); + if keymap.is_none() || state.is_none() { + warn!("failed to update xkb keymap"); + } + self.state = state; + self.keymap = keymap; + } + + /// Key builder context with the user provided xkb state. + pub fn key_context(&mut self) -> Option> { + let state = self.state.as_mut()?; + let keymap = self.keymap.as_mut()?; + let compose_state1 = self.compose_state1.as_mut(); + let compose_state2 = self.compose_state2.as_mut(); + let scratch_buffer = &mut self.scratch_buffer; + Some(KeyContext { + state, + keymap, + compose_state1, + compose_state2, + scratch_buffer, + }) + } + + /// Key builder context with the user provided xkb state. + /// + /// Should be used when the original context must not be altered. + #[cfg(x11_platform)] + pub fn key_context_with_state<'a>( + &'a mut self, + state: &'a mut XkbState, + ) -> Option> { + let keymap = self.keymap.as_mut()?; + let compose_state1 = self.compose_state1.as_mut(); + let compose_state2 = self.compose_state2.as_mut(); + let scratch_buffer = &mut self.scratch_buffer; + Some(KeyContext { + state, + keymap, + compose_state1, + compose_state2, + scratch_buffer, + }) + } +} + +pub struct KeyContext<'a> { + pub state: &'a mut XkbState, + pub keymap: &'a mut XkbKeymap, + compose_state1: Option<&'a mut XkbComposeState>, + compose_state2: Option<&'a mut XkbComposeState>, + scratch_buffer: &'a mut Vec, +} + +impl<'a> KeyContext<'a> { + pub fn process_key_event( + &mut self, + keycode: u32, + state: ElementState, + repeat: bool, + ) -> KeyEvent { + let mut event = + KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed); + let physical_key = keymap::raw_keycode_to_physicalkey(keycode); + let (logical_key, location) = event.key(); + let text = event.text(); + let (key_without_modifiers, _) = event.key_without_modifiers(); + let text_with_all_modifiers = event.text_with_all_modifiers(); + + let platform_specific = KeyEventExtra { + text_with_all_modifiers, + key_without_modifiers, + }; + + KeyEvent { + physical_key, + logical_key, + text, + location, + state, + repeat, + platform_specific, + } + } + + fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option { + self.scratch_buffer.clear(); + self.scratch_buffer.reserve(8); + loop { + let bytes_written = unsafe { + (XKBH.xkb_keysym_to_utf8)( + keysym, + self.scratch_buffer.as_mut_ptr().cast(), + self.scratch_buffer.capacity(), + ) + }; + if bytes_written == 0 { + return None; + } else if bytes_written == -1 { + self.scratch_buffer.reserve(8); + } else { + unsafe { + self.scratch_buffer + .set_len(bytes_written.try_into().unwrap()) + }; + break; + } + } + + // Remove the null-terminator + self.scratch_buffer.pop(); + byte_slice_to_smol_str(self.scratch_buffer) + } +} + +struct KeyEventResults<'a, 'b> { + context: &'a mut KeyContext<'b>, + keycode: u32, + keysym: u32, + compose: ComposeStatus, +} + +impl<'a, 'b> KeyEventResults<'a, 'b> { + fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self { + let keysym = context.state.get_one_sym_raw(keycode); + + let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) { + if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { + state.reset(); + context.compose_state2.as_mut().unwrap().reset(); + } + state.feed(keysym) + } else { + ComposeStatus::None + }; + + KeyEventResults { + context, + keycode, + keysym, + compose, + } + } + + pub fn key(&mut self) -> (Key, KeyLocation) { + let (key, location) = match self.keysym_to_key(self.keysym) { + Ok(known) => return known, + Err(undefined) => undefined, + }; + + if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose { + let compose_state = self.context.compose_state2.as_mut().unwrap(); + // When pressing a dead key twice, the non-combining variant of that character will + // be produced. Since this function only concerns itself with a single keypress, we + // simulate this double press here by feeding the keysym to the compose state + // twice. + + compose_state.feed(self.keysym); + if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) { + // Extracting only a single `char` here *should* be fine, assuming that no + // dead key's non-combining variant ever occupies more than one `char`. + let text = compose_state.get_string(self.context.scratch_buffer); + let key = Key::Dead(text.and_then(|s| s.chars().next())); + (key, location) + } else { + (key, location) + } + } else { + let key = self + .composed_text() + .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) + .map(Key::Character) + .unwrap_or(key); + (key, location) + } + } + + pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) { + // This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it. + let layout = self.context.state.layout(self.keycode); + let keysym = self + .context + .keymap + .first_keysym_by_level(layout, self.keycode); + + match self.keysym_to_key(keysym) { + Ok((key, location)) => (key, location), + Err((key, location)) => { + let key = self + .context + .keysym_to_utf8_raw(keysym) + .map(Key::Character) + .unwrap_or(key); + (key, location) + } + } + } + + fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { + let location = keymap::keysym_location(keysym); + let key = keymap::keysym_to_key(keysym); + if matches!(key, Key::Unidentified(_)) { + Err((key, location)) + } else { + Ok((key, location)) + } + } + + pub fn text(&mut self) -> Option { + self.composed_text() + .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) + } + + // The current behaviour makes it so composing a character overrides attempts to input a + // control character with the `Ctrl` key. We can potentially add a configuration option + // if someone specifically wants the oppsite behaviour. + pub fn text_with_all_modifiers(&mut self) -> Option { + match self.composed_text() { + Ok(text) => text, + Err(_) => self + .context + .state + .get_utf8_raw(self.keycode, self.context.scratch_buffer), + } + } + + fn composed_text(&mut self) -> Result, ()> { + match self.compose { + ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSED) => { + let state = self.context.compose_state1.as_mut().unwrap(); + Ok(state.get_string(self.context.scratch_buffer)) + } + _ => Err(()), + } + } +} + +#[derive(Debug)] +pub struct XkbContext { + context: NonNull, +} + +impl XkbContext { + pub fn new() -> Result { + let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; + + let context = match NonNull::new(context) { + Some(context) => context, + None => return Err(Error::XKBNotFound), + }; + + Ok(Self { context }) + } +} + +impl Drop for XkbContext { + fn drop(&mut self) { + unsafe { + (XKBH.xkb_context_unref)(self.context.as_ptr()); + } + } +} + +impl Deref for XkbContext { + type Target = NonNull; + + fn deref(&self) -> &Self::Target { + &self.context + } +} + +/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and +/// `xkb_state_key_get_utf8`. +fn make_string_with(scratch_buffer: &mut Vec, mut f: F) -> Option +where + F: FnMut(*mut c_char, usize) -> i32, +{ + let size = f(ptr::null_mut(), 0); + if size == 0 { + return None; + } + let size = usize::try_from(size).unwrap(); + scratch_buffer.clear(); + // The allocated buffer must include space for the null-terminator. + scratch_buffer.reserve(size + 1); + unsafe { + let written = f( + scratch_buffer.as_mut_ptr().cast(), + scratch_buffer.capacity(), + ); + if usize::try_from(written).unwrap() != size { + // This will likely never happen. + return None; + } + scratch_buffer.set_len(size); + }; + + byte_slice_to_smol_str(scratch_buffer) +} + +// NOTE: This is track_caller so we can have more informative line numbers when logging +#[track_caller] +fn byte_slice_to_smol_str(bytes: &[u8]) -> Option { + std::str::from_utf8(bytes) + .map(SmolStr::new) + .map_err(|e| { + log::warn!( + "UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", + bytes + ) + }) + .ok() +} diff --git a/src/platform_impl/linux/common/xkb/state.rs b/src/platform_impl/linux/common/xkb/state.rs new file mode 100644 index 000000000..27c055aa2 --- /dev/null +++ b/src/platform_impl/linux/common/xkb/state.rs @@ -0,0 +1,189 @@ +//! XKB state. + +use std::os::raw::c_char; +use std::ptr::NonNull; + +use smol_str::SmolStr; +#[cfg(x11_platform)] +use x11_dl::xlib_xcb::xcb_connection_t; +use xkbcommon_dl::{ + self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component, +}; + +use crate::platform_impl::common::xkb::keymap::XkbKeymap; +#[cfg(x11_platform)] +use crate::platform_impl::common::xkb::XKBXH; +use crate::platform_impl::common::xkb::{make_string_with, XKBH}; + +#[derive(Debug)] +pub struct XkbState { + state: NonNull, + modifiers: ModifiersState, +} + +impl XkbState { + #[cfg(wayland_platform)] + pub fn new_wayland(keymap: &XkbKeymap) -> Option { + let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?; + Some(Self::new_inner(state)) + } + + #[cfg(x11_platform)] + pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option { + let state = unsafe { + (XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id) + }; + let state = NonNull::new(state)?; + Some(Self::new_inner(state)) + } + + fn new_inner(state: NonNull) -> Self { + let modifiers = ModifiersState::default(); + let mut this = Self { state, modifiers }; + this.reload_modifiers(); + this + } + + pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t { + unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) } + } + + pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t { + unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) } + } + + #[cfg(x11_platform)] + pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t { + unsafe { + (XKBH.xkb_state_serialize_mods)( + self.state.as_ptr(), + xkb_state_component::XKB_STATE_MODS_DEPRESSED, + ) + } + } + + #[cfg(x11_platform)] + pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t { + unsafe { + (XKBH.xkb_state_serialize_mods)( + self.state.as_ptr(), + xkb_state_component::XKB_STATE_MODS_LATCHED, + ) + } + } + + #[cfg(x11_platform)] + pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t { + unsafe { + (XKBH.xkb_state_serialize_mods)( + self.state.as_ptr(), + xkb_state_component::XKB_STATE_MODS_LOCKED, + ) + } + } + + pub fn get_utf8_raw( + &mut self, + keycode: xkb_keycode_t, + scratch_buffer: &mut Vec, + ) -> Option { + make_string_with(scratch_buffer, |ptr, len| unsafe { + (XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len) + }) + } + + pub fn modifiers(&self) -> ModifiersState { + self.modifiers + } + + pub fn update_modifiers( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + depressed_group: u32, + latched_group: u32, + locked_group: u32, + ) { + let mask = unsafe { + (XKBH.xkb_state_update_mask)( + self.state.as_ptr(), + mods_depressed, + mods_latched, + mods_locked, + depressed_group, + latched_group, + locked_group, + ) + }; + + if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { + // Effective value of mods have changed, we need to update our state. + self.reload_modifiers(); + } + } + + /// Reload the modifiers. + fn reload_modifiers(&mut self) { + self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL); + self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT); + self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT); + self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS); + self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO); + self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM); + } + + /// Check if the modifier is active within xkb. + fn mod_name_is_active(&mut self, name: &[u8]) -> bool { + unsafe { + (XKBH.xkb_state_mod_name_is_active)( + self.state.as_ptr(), + name.as_ptr() as *const c_char, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0 + } + } +} + +impl Drop for XkbState { + fn drop(&mut self) { + unsafe { + (XKBH.xkb_state_unref)(self.state.as_ptr()); + } + } +} + +/// Represents the current state of the keyboard modifiers +/// +/// Each field of this struct represents a modifier and is `true` if this modifier is active. +/// +/// For some modifiers, this means that the key is currently pressed, others are toggled +/// (like caps lock). +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct ModifiersState { + /// The "control" key + pub ctrl: bool, + /// The "alt" key + pub alt: bool, + /// The "shift" key + pub shift: bool, + /// The "Caps lock" key + pub caps_lock: bool, + /// The "logo" key + /// + /// Also known as the "windows" key on most keyboards + pub logo: bool, + /// The "Num lock" key + pub num_lock: bool, +} + +impl From for crate::keyboard::ModifiersState { + fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { + let mut to_mods = crate::keyboard::ModifiersState::empty(); + to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); + to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); + to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); + to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo); + to_mods + } +} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e10eafeff..321bc32dd 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -521,7 +521,7 @@ impl Window { #[inline] pub fn reset_dead_keys(&self) { - common::xkb_state::reset_dead_keys() + common::xkb::reset_dead_keys() } #[inline] @@ -659,11 +659,11 @@ impl KeyEventExtModifierSupplement for KeyEvent { impl PhysicalKeyExtScancode for PhysicalKey { fn from_scancode(scancode: u32) -> PhysicalKey { - common::keymap::scancode_to_keycode(scancode) + common::xkb::scancode_to_keycode(scancode) } fn to_scancode(self) -> Option { - common::keymap::physicalkey_to_scancode(self) + common::xkb::physicalkey_to_scancode(self) } } diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index cf1ef2185..e3e28666a 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -17,7 +17,7 @@ use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; use crate::event::{ElementState, WindowEvent}; use crate::keyboard::ModifiersState; -use crate::platform_impl::common::xkb_state::KbdState; +use crate::platform_impl::common::xkb::Context; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::seat::WinitSeatState; use crate::platform_impl::wayland::state::WinitState; @@ -43,14 +43,10 @@ impl Dispatch for WinitState { WlKeymapFormat::NoKeymap => { warn!("non-xkb compatible keymap") } - WlKeymapFormat::XkbV1 => unsafe { - seat_state - .keyboard_state - .as_mut() - .unwrap() - .xkb_state - .init_with_fd(fd, size as usize); - }, + WlKeymapFormat::XkbV1 => { + let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; + context.set_keymap_from_fd(fd, size as usize); + } _ => unreachable!(), }, WEnum::Unknown(value) => { @@ -155,7 +151,12 @@ impl Dispatch for WinitState { RepeatInfo::Disable => return, }; - if !keyboard_state.xkb_state.key_repeats(key) { + if !keyboard_state + .xkb_context + .keymap_mut() + .unwrap() + .key_repeats(key) + { return; } @@ -221,7 +222,11 @@ impl Dispatch for WinitState { let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); if keyboard_state.repeat_info != RepeatInfo::Disable - && keyboard_state.xkb_state.key_repeats(key) + && keyboard_state + .xkb_context + .keymap_mut() + .unwrap() + .key_repeats(key) && Some(key) == keyboard_state.current_repeat { keyboard_state.current_repeat = None; @@ -237,9 +242,14 @@ impl Dispatch for WinitState { group, .. } => { - let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state; + let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; + let xkb_state = match xkb_context.state_mut() { + Some(state) => state, + None => return, + }; + xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group); - seat_state.modifiers = xkb_state.mods_state().into(); + seat_state.modifiers = xkb_state.modifiers().into(); // HACK: part of the workaround from `WlKeyboardEvent::Enter`. let window_id = match *data.window_id.lock().unwrap() { @@ -285,7 +295,7 @@ pub struct KeyboardState { pub loop_handle: LoopHandle<'static, WinitState>, /// The state of the keyboard. - pub xkb_state: KbdState, + pub xkb_context: Context, /// The information about the repeat rate obtained from the compositor. pub repeat_info: RepeatInfo, @@ -302,7 +312,7 @@ impl KeyboardState { Self { keyboard, loop_handle, - xkb_state: KbdState::new().unwrap(), + xkb_context: Context::new().unwrap(), repeat_info: RepeatInfo::default(), repeat_token: None, current_repeat: None, @@ -385,16 +395,13 @@ fn key_input( let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); - let event = keyboard_state - .xkb_state - .process_key_event(keycode, state, repeat); - - event_sink.push_window_event( - WindowEvent::KeyboardInput { + if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { + let event = key_context.process_key_event(keycode, state, repeat); + let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false, - }, - window_id, - ); + }; + event_sink.push_window_event(event, window_id); + } } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index c92e902a9..357382194 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex}; use x11_dl::xinput2::{ self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent, - XILeaveEvent, XIRawEvent, + XILeaveEvent, XIModifierState, XIRawEvent, }; use x11_dl::xlib::{ self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent, @@ -14,9 +14,10 @@ use x11_dl::xlib::{ XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, }; use x11rb::protocol::xinput; -use x11rb::protocol::xproto::{self, ConnectionExt as _}; +use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask}; use x11rb::x11_utils::ExtensionInformation; use x11rb::x11_utils::Serialize; +use xkbcommon_dl::xkb_mod_mask_t; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::event::{ @@ -26,7 +27,8 @@ use crate::event::{ use crate::event::{InnerSizeWriter, MouseButton}; use crate::event_loop::EventLoopWindowTarget as RootELW; use crate::keyboard::ModifiersState; -use crate::platform_impl::platform::common::{keymap, xkb_state::KbdState}; +use crate::platform_impl::common::xkb::{self, XkbState}; +use crate::platform_impl::platform::common::xkb::Context; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest}; use crate::platform_impl::platform::x11::EventLoopWindowTarget; use crate::platform_impl::platform::EventLoopWindowTarget as PlatformEventLoopWindowTarget; @@ -47,7 +49,7 @@ pub struct EventProcessor { pub xi2ext: ExtensionInformation, pub xkbext: ExtensionInformation, pub target: RootELW, - pub kb_state: KbdState, + pub xkb_context: Context, // Number of touch events currently in progress pub num_touch: u32, // This is the last pressed key that is repeatable (if it hasn't been @@ -178,10 +180,12 @@ impl EventProcessor { }; let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback); self.xinput2_button_input(xev, state, &mut callback); } xinput2::XI_Motion => { let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback); self.xinput2_mouse_motion(xev, &mut callback); } xinput2::XI_Enter => { @@ -190,6 +194,7 @@ impl EventProcessor { } xinput2::XI_Leave => { let xev: &XILeaveEvent = unsafe { &*(xev.data as *const _) }; + self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback); self.xinput2_mouse_left(xev, &mut callback); } xinput2::XI_FocusIn => { @@ -891,7 +896,12 @@ impl EventProcessor { // Only keys that can repeat should change the held_key_press state since a // continuously held repeatable key may continue repeating after the press of a // non-repeatable key. - let repeat = if self.kb_state.key_repeats(keycode) { + let key_repeats = self + .xkb_context + .keymap_mut() + .map(|k| k.key_repeats(keycode)) + .unwrap_or(false); + let repeat = if key_repeats { let is_latest_held = self.held_key_press == Some(keycode); if state == ElementState::Pressed { @@ -910,20 +920,29 @@ impl EventProcessor { false }; + // Always update the modifiers. + self.udpate_mods_from_core_event(xev.state as u16, &mut callback); + if keycode != 0 && !self.is_composing { - let event = self.kb_state.process_key_event(keycode, state, repeat); - callback( - &self.target, - Event::WindowEvent { + if let Some(mut key_processor) = self.xkb_context.key_context() { + let event = key_processor.process_key_event(keycode, state, repeat); + let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: false, }, - }, - ); - } else if let Some(ic) = wt + }; + callback(&self.target, event); + } + + return; + } + + let wt = Self::window_target(&self.target); + + if let Some(ic) = wt .ime .as_ref() .and_then(|ime| ime.borrow().get_context(window as XWindow)) @@ -1219,8 +1238,19 @@ impl EventProcessor { }; callback(&self.target, event); - let modifiers: crate::keyboard::ModifiersState = self.kb_state.mods_state().into(); - self.send_modifiers(modifiers, &mut callback); + // Issue key press events for all pressed keys + Self::handle_pressed_keys( + &self.target, + window_id, + ElementState::Pressed, + &mut self.xkb_context, + &mut callback, + ); + + if let Some(state) = self.xkb_context.state_mut() { + let mods = state.modifiers().into(); + self.send_modifiers(mods, &mut callback); + } // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. @@ -1239,15 +1269,6 @@ impl EventProcessor { }, }; callback(&self.target, event); - - // Issue key press events for all pressed keys - Self::handle_pressed_keys( - &self.target, - window_id, - ElementState::Pressed, - &mut self.kb_state, - &mut callback, - ); } fn xinput2_unfocused(&mut self, xev: &XIFocusOutEvent, mut callback: F) @@ -1275,12 +1296,14 @@ impl EventProcessor { wt.update_listen_device_events(false); + self.send_modifiers(ModifiersState::empty(), &mut callback); + // Issue key release events for all pressed keys Self::handle_pressed_keys( &self.target, window_id, ElementState::Released, - &mut self.kb_state, + &mut self.xkb_context, &mut callback, ); @@ -1288,8 +1311,6 @@ impl EventProcessor { // window regains focus. self.held_key_press = None; - self.send_modifiers(ModifiersState::empty(), &mut callback); - if let Some(window) = self.with_window(window, Arc::clone) { window.shared_state_lock().has_focus = false; } @@ -1442,7 +1463,7 @@ impl EventProcessor { if keycode < KEYCODE_OFFSET as u32 { return; } - let physical_key = keymap::raw_keycode_to_physicalkey(keycode); + let physical_key = xkb::raw_keycode_to_physicalkey(keycode); callback( &self.target, @@ -1507,17 +1528,25 @@ impl EventProcessor { let keycodes_changed = util::has_flag(xev.changed, keycodes_changed_flag); let geometry_changed = util::has_flag(xev.changed, geometry_changed_flag); - if xev.device == self.kb_state.core_keyboard_id + if xev.device == self.xkb_context.core_keyboard_id && (keycodes_changed || geometry_changed) { - unsafe { self.kb_state.init_with_x11_keymap() }; - let modifiers = self.kb_state.mods_state(); - self.send_modifiers(modifiers.into(), &mut callback); + let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); + self.xkb_context.set_keymap_from_x11(xcb); + + if let Some(state) = self.xkb_context.state_mut() { + let mods = state.modifiers().into(); + self.send_modifiers(mods, &mut callback); + } } } xlib::XkbMapNotify => { - unsafe { self.kb_state.init_with_x11_keymap() }; - self.send_modifiers(self.kb_state.mods_state().into(), &mut callback); + let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); + self.xkb_context.set_keymap_from_x11(xcb); + if let Some(state) = self.xkb_context.state_mut() { + let mods = state.modifiers().into(); + self.send_modifiers(mods, &mut callback); + } } xlib::XkbStateNotify => { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) }; @@ -1525,24 +1554,122 @@ impl EventProcessor { // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - // NOTE: Modifiers could update without a prior event updating them, - // thus diffing the state before and after is not reliable. - - self.kb_state.update_modifiers( - xev.base_mods, - xev.latched_mods, - xev.locked_mods, - xev.base_group as u32, - xev.latched_group as u32, - xev.locked_group as u32, - ); - - self.send_modifiers(self.kb_state.mods_state().into(), &mut callback); + if let Some(state) = self.xkb_context.state_mut() { + state.update_modifiers( + xev.base_mods, + xev.latched_mods, + xev.locked_mods, + xev.base_group as u32, + xev.latched_group as u32, + xev.locked_group as u32, + ); + let mods = state.modifiers().into(); + self.send_modifiers(mods, &mut callback); + } } _ => {} } } + pub fn update_mods_from_xinput2_event( + &mut self, + mods: &XIModifierState, + group: &XIModifierState, + mut callback: F, + ) where + F: FnMut(&RootELW, Event), + { + if let Some(state) = self.xkb_context.state_mut() { + state.update_modifiers( + mods.base as u32, + mods.latched as u32, + mods.locked as u32, + group.base as u32, + group.latched as u32, + group.locked as u32, + ); + + let mods = state.modifiers(); + self.send_modifiers(mods.into(), &mut callback); + } + } + + pub fn udpate_mods_from_core_event(&mut self, state: u16, mut callback: F) + where + F: FnMut(&RootELW, Event), + { + let xkb_mask = self.xkb_mod_mask_from_core(state); + let xkb_state = match self.xkb_context.state_mut() { + Some(xkb_state) => xkb_state, + None => return, + }; + + // NOTE: this is inspired by Qt impl. + let mut depressed = xkb_state.depressed_modifiers() & xkb_mask; + let latched = xkb_state.latched_modifiers() & xkb_mask; + let locked = xkb_state.locked_modifiers() & xkb_mask; + // Set modifiers in depressed if they don't appear in any of the final masks. + depressed |= !(depressed | latched | locked) & xkb_mask; + + xkb_state.update_modifiers( + depressed, + latched, + locked, + 0, + 0, + // Bits 13 and 14 report the state keyboard group. + ((state >> 13) & 3) as u32, + ); + + let mods = xkb_state.modifiers(); + self.send_modifiers(mods.into(), &mut callback); + } + + pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t { + let mods_indices = match self.xkb_context.keymap_mut() { + Some(keymap) => keymap.mods_indices(), + None => return 0, + }; + + // Build the XKB modifiers from the regular state. + let mut depressed = 0u32; + if let Some(shift) = mods_indices + .shift + .filter(|_| ModMask::SHIFT.intersects(state)) + { + depressed |= 1 << shift; + } + if let Some(caps) = mods_indices + .caps + .filter(|_| ModMask::LOCK.intersects(state)) + { + depressed |= 1 << caps; + } + if let Some(ctrl) = mods_indices + .ctrl + .filter(|_| ModMask::CONTROL.intersects(state)) + { + depressed |= 1 << ctrl; + } + if let Some(alt) = mods_indices.alt.filter(|_| ModMask::M1.intersects(state)) { + depressed |= 1 << alt; + } + if let Some(num) = mods_indices.num.filter(|_| ModMask::M2.intersects(state)) { + depressed |= 1 << num; + } + if let Some(mod3) = mods_indices.mod3.filter(|_| ModMask::M3.intersects(state)) { + depressed |= 1 << mod3; + } + if let Some(logo) = mods_indices.logo.filter(|_| ModMask::M4.intersects(state)) { + depressed |= 1 << logo; + } + if let Some(mod5) = mods_indices.mod5.filter(|_| ModMask::M5.intersects(state)) { + depressed |= 1 << mod5; + } + + depressed + } + /// Send modifiers for the active window. /// /// The event won't be send when the `modifiers` match the previosly `sent` modifiers value. @@ -1571,7 +1698,7 @@ impl EventProcessor { target: &RootELW, window_id: crate::window::WindowId, state: ElementState, - kb_state: &mut KbdState, + xkb_context: &mut Context, callback: &mut F, ) where F: FnMut(&RootELW, Event), @@ -1580,14 +1707,33 @@ impl EventProcessor { // Update modifiers state and emit key events based on which keys are currently pressed. let window_target = Self::window_target(target); + let xcb = window_target + .xconn + .xcb_connection() + .get_raw_xcb_connection(); + + let keymap = match xkb_context.keymap_mut() { + Some(keymap) => keymap, + None => return, + }; + + // Send the keys using the sythetic state to not alter the main state. + let mut xkb_state = match XkbState::new_x11(xcb, keymap) { + Some(xkb_state) => xkb_state, + None => return, + }; + let mut key_processor = match xkb_context.key_context_with_state(&mut xkb_state) { + Some(key_processor) => key_processor, + None => return, + }; + for keycode in window_target .xconn .query_keymap() .into_iter() .filter(|k| *k >= KEYCODE_OFFSET) { - let keycode = keycode as u32; - let event = kb_state.process_key_event(keycode, state, false); + let event = key_processor.process_key_event(keycode as u32, state, false); let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 5d0ad53c8..4310db3a0 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -30,12 +30,13 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::x11_utils::X11Error as LogicalError; use x11rb::xcb_ffi::ReplyOrIdError; -use super::{common::xkb_state::KbdState, ControlFlow, OsError}; +use super::{ControlFlow, OsError}; use crate::{ error::{EventLoopError, OsError as RootOsError}, event::{Event, StartCause, WindowEvent}, event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform::pump_events::PumpStatus, + platform_impl::common::xkb::Context, platform_impl::{ platform::{min_timeout, WindowId}, PlatformSpecificWindowBuilderAttributes, @@ -285,8 +286,8 @@ impl EventLoop { // Create a channel for sending user events. let (user_sender, user_channel) = mpsc::channel(); - let kb_state = - KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); + let xkb_context = + Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); let window_target = EventLoopWindowTarget { ime, @@ -327,7 +328,7 @@ impl EventLoop { ime_event_receiver, xi2ext, xkbext, - kb_state, + xkb_context, num_touch: 0, held_key_press: None, first_touch: None,