diff --git a/CHANGELOG.md b/CHANGELOG.md index f66ba677c0..7769640216 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 X11, fix swapped instance and general class names. - **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`. diff --git a/Cargo.toml b/Cargo.toml index 9be0076827..3b8ea5447f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,7 +189,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.47", default-features = false } diff --git a/src/platform_impl/linux/common/mod.rs b/src/platform_impl/linux/common/mod.rs index fa23919676..ed7f82cde9 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 0000000000..65b6514ba9 --- /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 2fc3979bcd..26f1179256 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 0000000000..00696c3f7b --- /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_physicalkey}; +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 0000000000..27c055aa20 --- /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/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs deleted file mode 100644 index 0d9be37054..0000000000 --- a/src/platform_impl/linux/common/xkb_state.rs +++ /dev/null @@ -1,692 +0,0 @@ -use std::convert::TryInto; -use std::env; -use std::ffi::CString; -use std::os::raw::c_char; -use std::os::unix::ffi::OsStringExt; -use std::ptr; -use std::sync::atomic::{AtomicBool, Ordering}; - -use once_cell::sync::Lazy; -use smol_str::SmolStr; -use xkbcommon_dl::{ - self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon, - XkbCommonCompose, -}; -#[cfg(feature = "wayland")] -use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; -#[cfg(feature = "x11")] -use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; - -use crate::event::KeyEvent; -use crate::platform_impl::common::keymap; -use crate::platform_impl::KeyEventExtra; -use crate::{ - event::ElementState, - keyboard::{Key, KeyLocation, PhysicalKey}, -}; - -// TODO: Wire this up without using a static `AtomicBool`. -static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); - -#[inline(always)] -pub fn reset_dead_keys() { - RESET_DEAD_KEYS.store(true, Ordering::SeqCst); -} - -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 ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); - -#[derive(Debug)] -pub struct KbdState { - #[cfg(feature = "x11")] - xcb_connection: *mut xcb_connection_t, - xkb_context: *mut ffi::xkb_context, - xkb_keymap: *mut ffi::xkb_keymap, - xkb_state: *mut ffi::xkb_state, - xkb_compose_table: *mut ffi::xkb_compose_table, - xkb_compose_state: *mut ffi::xkb_compose_state, - xkb_compose_state_2: *mut ffi::xkb_compose_state, - mods_state: ModifiersState, - #[cfg(feature = "x11")] - pub core_keyboard_id: i32, - scratch_buffer: Vec, -} - -impl KbdState { - pub fn update_modifiers( - &mut self, - mods_depressed: u32, - mods_latched: u32, - mods_locked: u32, - depressed_group: u32, - latched_group: u32, - locked_group: u32, - ) { - if !self.ready() { - return; - } - let mask = unsafe { - (XKBH.xkb_state_update_mask)( - self.xkb_state, - 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.mods_state.update_with(self.xkb_state); - } - } - - pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { - if !self.ready() { - return 0; - } - unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) } - } - - pub fn get_utf8_raw(&mut self, keycode: u32) -> Option { - if !self.ready() { - return None; - } - let xkb_state = self.xkb_state; - self.make_string_with({ - |ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) } - }) - } - - fn compose_feed_normal(&mut self, keysym: u32) -> Option { - self.compose_feed(self.xkb_compose_state, keysym) - } - - fn compose_feed_2(&mut self, keysym: u32) -> Option { - self.compose_feed(self.xkb_compose_state_2, keysym) - } - - fn compose_feed( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - keysym: u32, - ) -> Option { - if !self.ready() || self.xkb_compose_state.is_null() { - return None; - } - if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { - unsafe { self.init_compose() }; - } - Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) }) - } - - fn compose_status_normal(&mut self) -> Option { - self.compose_status(self.xkb_compose_state) - } - - fn compose_status( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - ) -> Option { - if !self.ready() || xkb_compose_state.is_null() { - return None; - } - Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) }) - } - - fn compose_get_utf8_normal(&mut self) -> Option { - self.compose_get_utf8(self.xkb_compose_state) - } - - fn compose_get_utf8_2(&mut self) -> Option { - self.compose_get_utf8(self.xkb_compose_state_2) - } - - fn compose_get_utf8( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - ) -> Option { - if !self.ready() || xkb_compose_state.is_null() { - return None; - } - self.make_string_with(|ptr, len| unsafe { - (XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len) - }) - } - - /// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and - /// `xkb_state_key_get_utf8`. - fn make_string_with(&mut self, 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(); - self.scratch_buffer.clear(); - // The allocated buffer must include space for the null-terminator - self.scratch_buffer.reserve(size + 1); - unsafe { - let written = f( - self.scratch_buffer.as_mut_ptr().cast(), - self.scratch_buffer.capacity(), - ); - if usize::try_from(written).unwrap() != size { - // This will likely never happen - return None; - } - self.scratch_buffer.set_len(size); - }; - byte_slice_to_smol_str(&self.scratch_buffer) - } - - pub fn new() -> Result { - if ffi::xkbcommon_option().is_none() { - return Err(Error::XKBNotFound); - } - - let context = - unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; - if context.is_null() { - return Err(Error::XKBNotFound); - } - - let mut me = Self { - #[cfg(feature = "x11")] - xcb_connection: ptr::null_mut(), - xkb_context: context, - xkb_keymap: ptr::null_mut(), - xkb_state: ptr::null_mut(), - xkb_compose_table: ptr::null_mut(), - xkb_compose_state: ptr::null_mut(), - xkb_compose_state_2: ptr::null_mut(), - mods_state: ModifiersState::new(), - #[cfg(feature = "x11")] - core_keyboard_id: 0, - scratch_buffer: Vec::new(), - }; - - unsafe { me.init_compose() }; - - Ok(me) - } - - #[cfg(feature = "x11")] - pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result { - let mut me = Self::new()?; - me.xcb_connection = connection; - - let result = unsafe { - (XKBXH.xkb_x11_setup_xkb_extension)( - connection, - 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(), - ) - }; - assert_eq!(result, 1, "Failed to initialize libxkbcommon"); - - unsafe { me.init_with_x11_keymap() }; - - Ok(me) - } - - unsafe fn init_compose(&mut self) { - 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 compose_table = unsafe { - (XKBCH.xkb_compose_table_new_from_locale)( - self.xkb_context, - locale.as_ptr(), - ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, - ) - }; - - if compose_table.is_null() { - // init of compose table failed, continue without compose - return; - } - - let compose_state = unsafe { - (XKBCH.xkb_compose_state_new)( - compose_table, - ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, - ) - }; - - if compose_state.is_null() { - // init of compose state failed, continue without compose - unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) }; - return; - } - - let compose_state_2 = unsafe { - (XKBCH.xkb_compose_state_new)( - compose_table, - ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, - ) - }; - - if compose_state_2.is_null() { - // init of compose state failed, continue without compose - unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) }; - unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) }; - return; - } - - self.xkb_compose_table = compose_table; - self.xkb_compose_state = compose_state; - self.xkb_compose_state_2 = compose_state_2; - } - - unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) { - self.xkb_keymap = keymap; - self.xkb_state = state; - self.mods_state.update_with(state); - } - - unsafe fn de_init(&mut self) { - unsafe { (XKBH.xkb_state_unref)(self.xkb_state) }; - self.xkb_state = ptr::null_mut(); - unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) }; - self.xkb_keymap = ptr::null_mut(); - } - - #[cfg(feature = "x11")] - pub unsafe fn init_with_x11_keymap(&mut self) { - if !self.xkb_keymap.is_null() { - unsafe { self.de_init() }; - } - - // TODO: Support keyboards other than the "virtual core keyboard device". - self.core_keyboard_id = - unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) }; - let keymap = unsafe { - (XKBXH.xkb_x11_keymap_new_from_device)( - self.xkb_context, - self.xcb_connection, - self.core_keyboard_id, - xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, - ) - }; - if keymap.is_null() { - panic!("Failed to get keymap from X11 server."); - } - - let state = unsafe { - (XKBXH.xkb_x11_state_new_from_device)( - keymap, - self.xcb_connection, - self.core_keyboard_id, - ) - }; - unsafe { self.post_init(state, keymap) }; - } - - #[cfg(feature = "wayland")] - pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) { - if !self.xkb_keymap.is_null() { - unsafe { self.de_init() }; - } - - let map = unsafe { - MmapOptions::new() - .len(size) - .map_copy_read_only(&fd) - .unwrap() - }; - - let keymap = unsafe { - (XKBH.xkb_keymap_new_from_string)( - self.xkb_context, - map.as_ptr() as *const _, - ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, - ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, - ) - }; - - if keymap.is_null() { - panic!("Received invalid keymap from compositor."); - } - - let state = unsafe { (XKBH.xkb_state_new)(keymap) }; - unsafe { self.post_init(state, keymap) }; - } - - pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { - unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 } - } - - #[inline] - pub fn ready(&self) -> bool { - !self.xkb_state.is_null() - } - - #[inline] - pub fn mods_state(&self) -> ModifiersState { - self.mods_state - } - - 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 = event.physical_key(); - 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) - } -} - -impl Drop for KbdState { - fn drop(&mut self) { - unsafe { - if !self.xkb_compose_state.is_null() { - (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state); - } - if !self.xkb_compose_state_2.is_null() { - (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2); - } - if !self.xkb_compose_table.is_null() { - (XKBCH.xkb_compose_table_unref)(self.xkb_compose_table); - } - if !self.xkb_state.is_null() { - (XKBH.xkb_state_unref)(self.xkb_state); - } - if !self.xkb_keymap.is_null() { - (XKBH.xkb_keymap_unref)(self.xkb_keymap); - } - (XKBH.xkb_context_unref)(self.xkb_context); - } - } -} - -struct KeyEventResults<'a> { - state: &'a mut KbdState, - keycode: u32, - keysym: u32, - compose: Option, -} - -impl<'a> KeyEventResults<'a> { - fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self { - let keysym = state.get_one_sym_raw(keycode); - - let compose = if compose { - Some(match state.compose_feed_normal(keysym) { - Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { - // Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized. - XkbCompose::Accepted(state.compose_status_normal().unwrap()) - } - Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored, - None => XkbCompose::Uninitialized, - }) - } else { - None - }; - - KeyEventResults { - state, - keycode, - keysym, - compose, - } - } - - fn physical_key(&self) -> PhysicalKey { - keymap::raw_keycode_to_physicalkey(self.keycode) - } - - pub fn key(&mut self) -> (Key, KeyLocation) { - self.keysym_to_key(self.keysym) - .unwrap_or_else(|(key, location)| match self.compose { - Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => { - // 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. - self.state.compose_feed_2(self.keysym); - match self.state.compose_feed_2(self.keysym) { - Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_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`. - Key::Dead( - self.state - .compose_get_utf8_2() - .map(|s| s.chars().next().unwrap()), - ), - location, - ), - _ => (key, location), - } - } - _ => ( - self.composed_text() - .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) - .map(Key::Character) - .unwrap_or(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 mut keysyms = ptr::null(); - let keysym_count = unsafe { - let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode); - (XKBH.xkb_keymap_key_get_syms_by_level)( - self.state.xkb_keymap, - self.keycode, - layout, - // NOTE: The level should be zero to ignore modifiers. - 0, - &mut keysyms, - ) - }; - let keysym = if keysym_count == 1 { - unsafe { *keysyms } - } else { - 0 - }; - - self.keysym_to_key(keysym) - .unwrap_or_else(|(key, location)| { - ( - self.state - .keysym_to_utf8_raw(keysym) - .map(Key::Character) - .unwrap_or(key), - location, - ) - }) - } - - fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { - let location = super::keymap::keysym_location(keysym); - let key = super::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.state.keysym_to_utf8_raw(self.keysym)) - } - - pub fn text_with_all_modifiers(&mut self) -> Option { - // 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. - self.composed_text() - .unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode)) - } - - fn composed_text(&mut self) -> Result, ()> { - if let Some(compose) = &self.compose { - match compose { - XkbCompose::Accepted(status) => match status { - ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => { - Ok(self.state.compose_get_utf8_normal()) - } - ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()), - _ => Ok(None), - }, - XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()), - } - } else { - Err(()) - } - } -} - -/// 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 ModifiersState { - fn new() -> Self { - Self::default() - } - - fn update_with(&mut self, state: *mut ffi::xkb_state) { - let mod_name_is_active = |mod_name: &[u8]| unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - mod_name.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL); - self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT); - self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT); - self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS); - self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO); - self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM); - } -} - -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 - } -} - -#[derive(Debug)] -pub enum Error { - /// libxkbcommon is not available - XKBNotFound, -} - -#[derive(Copy, Clone, Debug)] -enum XkbCompose { - Accepted(ffi::xkb_compose_status), - Ignored, - Uninitialized, -} - -// 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/mod.rs b/src/platform_impl/linux/mod.rs index d57a3ca64e..2754360979 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -34,7 +34,7 @@ use crate::{ }, }; -pub(crate) use self::common::keymap::{physicalkey_to_scancode, scancode_to_physicalkey}; +pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey}; pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -516,7 +516,7 @@ impl Window { #[inline] pub fn reset_dead_keys(&self) { - common::xkb_state::reset_dead_keys() + common::xkb::reset_dead_keys() } #[inline] diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index cf1ef21851..e3e28666ab 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 9e88f1d4ee..2f01b93b96 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 => { @@ -895,7 +900,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 { @@ -914,20 +924,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)) @@ -1227,8 +1246,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. @@ -1247,15 +1277,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) @@ -1283,12 +1304,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, ); @@ -1296,8 +1319,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; } @@ -1462,7 +1483,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, @@ -1527,17 +1548,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) }; @@ -1545,24 +1574,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. @@ -1591,7 +1718,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), @@ -1600,14 +1727,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 8f1db698ca..18e4e78602 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -28,12 +28,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}, window::WindowAttributes, }; @@ -281,8 +282,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, @@ -322,7 +323,7 @@ impl EventLoop { ime_event_receiver, xi2ext, xkbext, - kb_state, + xkb_context, num_touch: 0, held_key_press: None, first_touch: None,