From 7627f70a9229bf73b582ba8874caa950ad3633fc Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 22 Oct 2021 21:15:17 +0200 Subject: [PATCH] X11: Rewrite input handling in terms of XInput and XKB This patch completes the port of the X11 backend from core input handling to XInput/XKB input handling. In this context the word 'core' refers to the core X11 protocol in contrast to protocol extensions such as XInput and XKB. XInput and XKB are very large protocols that extend X11 with features expected from modern desktop environments such as - Support for a rich set of input devices such as touchpads. - Support for multiple attached keyboards, mice, touchpads, tablets, etc. - Support for rich and interactive keyboard layouts. # Breaking Changes - This patch removes all processing of core input events in favor of XInput events. The legacy XIM input method protocol is based on filtering and injecting core input events. Therefore, this patch also removes support for XIM input methods. Applications are encouraged to switch to more modern IM protocols such as [IBus]. These protocols can be implemented in application space outside of winit. Note that modern toolskits such as QT5 and chromium do not support XIM. [IBus]: https://en.wikipedia.org/wiki/Intelligent_Input_Bus - This patch removes support for synthetic keyboard events. This feature cannot be implemented correctly: - XKB is a complex state machine where key presses and releases can perform a rich set of actions. For example: - Switching modifiers on and off. - Switching between keyboard layouts. - Moving the mouse cursor. These actions depend on the order the key are pressed and released. For example, if a key that switches layouts is released before a regular key, then the release of the regular key will produce different events than it would otherwise. - The winit API does not permit synthetic `ModifierChanged` events. As such, an application cannot distinguish between the user deliberately changing the active modifiers and synthetic changes. For example, consider an application that performs a drag-and-drop operation as long as the Shift modifier is active. Applications are encouraged to track the state of keys manually in a way that is suitable for their application. # New and Changed Features - Winit no longer tracks keyboard events if no winit window has the focus except that: - Raw keyboard events are still being tracked. A future patch might make this behavior optional. See #1634. - Changes to the keyboard layout are being tracked at all times. - The backend now has complete support for multiple seats. For each seat it tracks the modifier state and the focused window. In the case of `KeyboardInput` events, applications can distinguish multiple seats by tracking the value of the `device_id` field. In the case of `ModifierChanged` events, applications cannot distinguish different seats. A future patch might add a `device_id` field to `ModifierChanged` events. The following sequence of events is possible: 1. Key Press: Seat 1, Left Shift 2. Modifiers Changed: Shift 3. Key Press: Seat 2, Left Ctrl 4. Modifiers Changed: Ctrl 5. Key Press: Seat 1, KeyA, Text: "A" (due to active Shift) 6. Key Release: Seat 1, Left Shift 7. Modifiers Changed: None 8. Key Release: Seat 2, Left Ctrl 9. Modifiers Changed: None - Keyboard state and window events are now completely independent of device events. Applications can disable device events by modifying the winit source code (or in the future with a supported toggle) without incurring regressions in other areas. - Key release events no longer contain a value in the `text` and `text_with_all_modifiers` fields. - Key presses that are part of a compose sequence no longer contain a value in the `text` and `text_with_all_modifiers`. Applications that simply want to handle text input can therefore listen for key events and append the values of the `text` field to the input buffer without having to track any state. - The `logical_key` field of key events is no longer affected by compose sequences. This is in line with how browsers handle compose sequences. - Aborted compose sequences no longer produce any `text`. An aborted compose sequence is a sequence that was not completed correctly. For example, consider the following sequence of keysyms: 1. Multi_key 2. ( 3. c 4. ( `(` is not a valid continuation of the compose sequence starting with `[Multi_key, (, c]`. Therefore it aborts the sequence and no `text` is produced (not even for the final `(`). This is in line with existing practice on linux. - The `Dead` `Key` is now used exclusively for those keysyms that have `_dead_` in their name. This appears to be in line with how browsers handle dead keys. - The value of a `Dead` `Key` is in one of three categories: - If the dead key does not correspond to any particular diacritical mark, the value is `None`. For example, `dead_greek` (used to input Greek characters on a Latin keyboard). - If the dead key has a freestanding variant in unicode, the value is `Some(c)` with `c` being the freestanding character. For example, `dead_circumflex` has the value `Some('^')`. - Otherwise the value is `None`. For example, `dead_belowdot`. - `key_without_modifiers` now respects the effective XKB group. It only discards the state of modifiers. This is essential to correctly handle keyboard layouts in the GNOME desktop environment which uses XKB groups to switch between layouts. # Implementation Details - `EventProcessor` no longer uses any interior mutability. In cases where there were conflicting borrows, the code has been rewritten to use freestanding functions. - Keyboard state is now tracked exclusively by xkbcommon. The code that manually tracked some of this state has been removed. - The `xkb_state` module has been significantly simplified. The `process_key_event` function now computes all effects produced by a key press/release ahead of time. - Almost all XInput events also carry the current XKB state of its seat. We use this to track the state of modifiers eagerly and independently of keyboard events. --- Cargo.toml | 4 +- src/platform_impl/linux/common/keymap.rs | 51 ++ src/platform_impl/linux/common/xkb_state.rs | 670 +++++++--------- .../linux/wayland/seat/keyboard/handlers.rs | 58 +- .../linux/wayland/seat/keyboard/mod.rs | 12 - .../linux/x11/event_processor.rs | 747 +++++++++--------- src/platform_impl/linux/x11/ime/mod.rs | 11 - src/platform_impl/linux/x11/mod.rs | 61 +- src/platform_impl/linux/x11/util/input.rs | 99 +-- src/platform_impl/linux/x11/util/keys.rs | 92 --- src/platform_impl/linux/x11/util/mod.rs | 2 - src/platform_impl/linux/x11/util/modifiers.rs | 190 ----- 12 files changed, 783 insertions(+), 1214 deletions(-) delete mode 100644 src/platform_impl/linux/x11/util/keys.rs delete mode 100644 src/platform_impl/linux/x11/util/modifiers.rs diff --git a/Cargo.toml b/Cargo.toml index d2f3c9026bc..cc1654f6bca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,11 +93,11 @@ wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true } mio = { version = "0.7", features = ["os-ext"], optional = true } mio-misc = { version = "1.0", optional = true } -x11-dl = { version = "2.18.5", optional = true } +x11-dl = { version = "2.19.1", optional = true } percent-encoding = { version = "2.0", optional = true } parking_lot = { version = "0.11.0", optional = true } memmap2 = { version = "0.2.1", optional = true } -xkbcommon-dl = { git = "https://github.com/maroider/xkbcommon-dl", rev = "900832888ad6f11011d1369befb344a9aa8a9610" } +xkbcommon-dl = { git = "https://github.com/mahkoh/xkbcommon-dl", rev = "3f92ba445a590a2928942fd1ce24fa6965f76bca" } [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" diff --git a/src/platform_impl/linux/common/keymap.rs b/src/platform_impl/linux/common/keymap.rs index 8e2e6c2458f..51083d6d386 100644 --- a/src/platform_impl/linux/common/keymap.rs +++ b/src/platform_impl/linux/common/keymap.rs @@ -826,6 +826,57 @@ pub fn keysym_to_key(keysym: u32) -> Key<'static> { keysyms::XKB_KEY_SunVideoRaiseBrightness => Key::BrightnessUp, // XKB_KEY_SunPowerSwitchShift // + // Dead keys + keysyms::XKB_KEY_dead_greek => Key::Dead(None), + keysyms::XKB_KEY_dead_currency => Key::Dead(None), + keysyms::XKB_KEY_dead_stroke => Key::Dead(None), + keysyms::XKB_KEY_dead_voiced_sound => Key::Dead(None), + keysyms::XKB_KEY_dead_semivoiced_sound => Key::Dead(None), + keysyms::XKB_KEY_dead_lowline => Key::Dead(None), + keysyms::XKB_KEY_dead_aboveverticalline => Key::Dead(None), + keysyms::XKB_KEY_dead_belowverticalline => Key::Dead(None), + keysyms::XKB_KEY_dead_longsolidusoverlay => Key::Dead(None), + keysyms::XKB_KEY_dead_grave => Key::Dead(Some('`')), + keysyms::XKB_KEY_dead_acute => Key::Dead(Some('´')), + keysyms::XKB_KEY_dead_circumflex => Key::Dead(Some('^')), + keysyms::XKB_KEY_dead_tilde => Key::Dead(Some('~')), + keysyms::XKB_KEY_dead_macron => Key::Dead(Some('¯')), + keysyms::XKB_KEY_dead_breve => Key::Dead(Some('˘')), + keysyms::XKB_KEY_dead_abovedot => Key::Dead(Some('˙')), + keysyms::XKB_KEY_dead_diaeresis => Key::Dead(Some('¨')), + keysyms::XKB_KEY_dead_abovering => Key::Dead(Some('°')), + keysyms::XKB_KEY_dead_doubleacute => Key::Dead(Some('˝')), + keysyms::XKB_KEY_dead_caron => Key::Dead(Some('ˇ')), + keysyms::XKB_KEY_dead_cedilla => Key::Dead(Some('¸')), + keysyms::XKB_KEY_dead_ogonek => Key::Dead(Some('˛')), + keysyms::XKB_KEY_dead_iota => Key::Dead(Some('ͺ')), + keysyms::XKB_KEY_dead_belowdot => Key::Dead(None), + keysyms::XKB_KEY_dead_hook => Key::Dead(None), + keysyms::XKB_KEY_dead_horn => Key::Dead(None), + keysyms::XKB_KEY_dead_abovecomma => Key::Dead(None), + keysyms::XKB_KEY_dead_abovereversedcomma => Key::Dead(None), + keysyms::XKB_KEY_dead_doublegrave => Key::Dead(None), + keysyms::XKB_KEY_dead_belowring => Key::Dead(Some('˳')), + keysyms::XKB_KEY_dead_belowmacron => Key::Dead(Some('ˍ')), + keysyms::XKB_KEY_dead_belowcircumflex => Key::Dead(None), + keysyms::XKB_KEY_dead_belowtilde => Key::Dead(Some('˷')), + keysyms::XKB_KEY_dead_belowbreve => Key::Dead(None), + keysyms::XKB_KEY_dead_belowdiaeresis => Key::Dead(None), + keysyms::XKB_KEY_dead_invertedbreve => Key::Dead(None), + keysyms::XKB_KEY_dead_belowcomma => Key::Dead(None), + keysyms::XKB_KEY_dead_a => Key::Dead(Some('a')), + keysyms::XKB_KEY_dead_A => Key::Dead(Some('A')), + keysyms::XKB_KEY_dead_e => Key::Dead(Some('e')), + keysyms::XKB_KEY_dead_E => Key::Dead(Some('E')), + keysyms::XKB_KEY_dead_i => Key::Dead(Some('i')), + keysyms::XKB_KEY_dead_I => Key::Dead(Some('I')), + keysyms::XKB_KEY_dead_o => Key::Dead(Some('o')), + keysyms::XKB_KEY_dead_O => Key::Dead(Some('O')), + keysyms::XKB_KEY_dead_u => Key::Dead(Some('u')), + keysyms::XKB_KEY_dead_U => Key::Dead(Some('U')), + keysyms::XKB_KEY_dead_small_schwa => Key::Dead(Some('ə')), + keysyms::XKB_KEY_dead_capital_schwa => Key::Dead(Some('Ə')), + 0 => Key::Unidentified(NativeKeyCode::Unidentified), _ => Key::Unidentified(NativeKeyCode::XkbSym(keysym)), } diff --git a/src/platform_impl/linux/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs index b1ff556db40..c6826f99afe 100644 --- a/src/platform_impl/linux/common/xkb_state.rs +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -1,12 +1,8 @@ -use std::convert::TryInto; -use std::env; use std::ffi::CString; -use std::fs::File; use std::os::raw::c_char; use std::os::unix::ffi::OsStringExt; -use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; +use std::{char, env, ptr, slice, str}; #[cfg(feature = "wayland")] use memmap2::MmapOptions; @@ -19,20 +15,15 @@ use x11_dl::xlib_xcb::xcb_connection_t; use xkbcommon_dl::x11::XKBCOMMON_X11_HANDLE as XKBXH; use xkbcommon_dl::{ - self as ffi, xkb_state_component, XKBCOMMON_COMPOSE_HANDLE as XKBCH, XKBCOMMON_HANDLE as XKBH, + self as ffi, xkb_compose_status, xkb_state_component, XKBCOMMON_COMPOSE_HANDLE as XKBCH, + XKBCOMMON_HANDLE as XKBH, }; use crate::{ event::ElementState, - keyboard::{Key, KeyCode, KeyLocation}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, }; -// TODO: Wire this up without using a static `Mutex>`. -#[cfg(feature = "x11")] -lazy_static! { - pub(crate) static ref X11_EVPROC_NEXT_COMPOSE: Mutex> = Mutex::new(None); -} - // TODO: Wire this up without using a static `AtomicBool`. static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); @@ -49,89 +40,83 @@ pub(crate) struct KbState { 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, + mod_indices: ModIndices, mods_state: ModifiersState, + #[cfg(feature = "wayland")] locked: bool, scratch_buffer: Vec, } -/// 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)] -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, +#[derive(Default)] +struct ModIndices { + ctrl: u32, + alt: u32, + shift: u32, + logo: u32, } -impl ModifiersState { - fn new() -> Self { - Self::default() +impl ModIndices { + unsafe fn from_keymap(xkb_keymap: *mut ffi::xkb_keymap) -> Self { + let ctrl = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char, + ); + let alt = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char, + ); + let shift = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char, + ); + let logo = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char, + ); + Self { + ctrl, + alt, + shift, + logo, + } } +} - fn update_with(&mut self, state: *mut ffi::xkb_state) { - self.ctrl = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.alt = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.shift = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.caps_lock = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_CAPS.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.logo = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.num_lock = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_NUM.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - } +unsafe fn xkb_state_to_modifiers( + state: *mut ffi::xkb_state, + indices: &ModIndices, +) -> ModifiersState { + let ctrl = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.ctrl, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let alt = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.alt, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let shift = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.shift, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let logo = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.logo, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, shift); + mods.set(ModifiersState::CONTROL, ctrl); + mods.set(ModifiersState::ALT, alt); + mods.set(ModifiersState::SUPER, logo); + mods } impl KbState { - pub(crate) fn update_modifiers( + pub(crate) fn update_state( &mut self, mods_depressed: u32, mods_latched: u32, @@ -156,7 +141,7 @@ impl KbState { }; 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); + self.mods_state = unsafe { xkb_state_to_modifiers(self.xkb_state, &self.mod_indices) }; } } @@ -171,105 +156,51 @@ impl KbState { if !self.ready() { return None; } - let size = - unsafe { (XKBH.xkb_state_key_get_utf8)(self.xkb_state, keycode, ptr::null_mut(), 0) } - + 1; - if size <= 1 { - return None; - }; - self.scratch_buffer.clear(); - let size = size.try_into().unwrap(); - self.scratch_buffer.reserve(size); - unsafe { - self.scratch_buffer.set_len(size); - (XKBH.xkb_state_key_get_utf8)( - self.xkb_state, - keycode, - self.scratch_buffer.as_mut_ptr() as *mut _, - size, - ); - }; - // remove the final `\0` - self.scratch_buffer.pop(); - Some(byte_slice_to_cached_string(&self.scratch_buffer)) - } - - 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) + let utf32 = unsafe { (XKBH.xkb_state_key_get_utf32)(self.xkb_state, keycode) }; + char_to_str(utf32) } - fn compose_feed( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - keysym: u32, - ) -> Option { + fn compose_feed(&mut self, 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) - } - - #[allow(dead_code)] - fn compose_status_2(&mut self) -> Option { - self.compose_status(self.xkb_compose_state_2) + Some(unsafe { (XKBCH.xkb_compose_state_feed)(self.xkb_compose_state, keysym) }) } - fn compose_status( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - ) -> Option { - if !self.ready() || xkb_compose_state.is_null() { + fn compose_status(&mut self) -> Option { + if !self.ready() || self.xkb_compose_state.is_null() { return None; } - Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) }) + Some(unsafe { (XKBCH.xkb_compose_state_get_status)(self.xkb_compose_state) }) } - fn compose_get_utf8_normal(&mut self) -> Option<&'static str> { - self.compose_get_utf8(self.xkb_compose_state) - } - - fn compose_get_utf8_2(&mut self) -> Option<&'static str> { - self.compose_get_utf8(self.xkb_compose_state_2) - } - - fn compose_get_utf8( - &mut self, - xkb_compose_state: *mut ffi::xkb_compose_state, - ) -> Option<&'static str> { - if !self.ready() || xkb_compose_state.is_null() { + fn compose_get_utf8(&mut self) -> Option<&'static str> { + if !self.ready() || self.xkb_compose_state.is_null() { return None; } - let size = - unsafe { (XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr::null_mut(), 0) } - + 1; - if size <= 1 { - return None; - }; - self.scratch_buffer.clear(); - let size = size.try_into().unwrap(); - self.scratch_buffer.reserve(size); - unsafe { - self.scratch_buffer.set_len(size); - (XKBCH.xkb_compose_state_get_utf8)( - xkb_compose_state, - self.scratch_buffer.as_mut_ptr() as *mut _, - size as usize, - ); - }; - // remove the final `\0` - self.scratch_buffer.pop(); - Some(byte_slice_to_cached_string(&self.scratch_buffer)) + self.scratch_buffer.truncate(0); + loop { + unsafe { + let size = (XKBCH.xkb_compose_state_get_utf8)( + self.xkb_compose_state, + self.scratch_buffer.as_mut_ptr() as *mut _, + self.scratch_buffer.capacity(), + ); + if size < 0 { + return None; + } + let size = size as usize; + if size >= self.scratch_buffer.capacity() { + self.scratch_buffer.reserve(size + 1); + continue; + } + self.scratch_buffer.set_len(size); + return Some(byte_slice_to_cached_string(&self.scratch_buffer)); + } + } } pub(crate) fn new() -> Result { @@ -283,6 +214,21 @@ impl KbState { return Err(Error::XKBNotFound); } + // let level = if log::log_enabled!(log::Level::Debug) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_DEBUG + // } else if log::log_enabled!(log::Level::Info) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_INFO + // } else if log::log_enabled!(log::Level::Warn) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_WARNING + // } else if log::log_enabled!(log::Level::Error) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_ERROR + // } else { + // ffi::xkb_log_level::XKB_LOG_LEVEL_CRITICAL + // }; + // unsafe { + // (XKBH.xkb_context_set_log_level)(context, level); + // } + let mut me = Self { #[cfg(feature = "x11")] xcb_connection: ptr::null_mut(), @@ -291,10 +237,11 @@ impl KbState { 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(), + mod_indices: Default::default(), + mods_state: ModifiersState::empty(), + #[cfg(feature = "wayland")] locked: false, - scratch_buffer: Vec::new(), + scratch_buffer: Vec::with_capacity(5), }; unsafe { me.init_compose() }; @@ -305,25 +252,14 @@ impl KbState { impl KbState { #[cfg(feature = "x11")] - pub(crate) fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result { + pub(crate) fn from_x11_xkb( + connection: *mut xcb_connection_t, + device_id: std::os::raw::c_int, + ) -> 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() }; + unsafe { me.init_with_x11_keymap(device_id) }; Ok(me) } @@ -385,33 +321,21 @@ impl KbState { ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, ); - if compose_table.is_null() { - // init of compose state failed, continue without compose - (XKBCH.xkb_compose_table_unref)(compose_table); - return; - } - - let compose_state_2 = (XKBCH.xkb_compose_state_new)( - compose_table, - ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, - ); - - if compose_state_2.is_null() { + if compose_state.is_null() { // init of compose state failed, continue without compose (XKBCH.xkb_compose_table_unref)(compose_table); - (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.mod_indices = ModIndices::from_keymap(keymap); self.xkb_state = state; - self.mods_state.update_with(state); + self.mods_state = xkb_state_to_modifiers(state, &self.mod_indices); } unsafe fn de_init(&mut self) { @@ -422,28 +346,25 @@ impl KbState { } #[cfg(feature = "x11")] - pub(crate) unsafe fn init_with_x11_keymap(&mut self) { + pub(crate) unsafe fn init_with_x11_keymap(&mut self, device_id: std::os::raw::c_int) { if !self.xkb_keymap.is_null() { self.de_init(); } - // TODO: Support keyboards other than the "virtual core keyboard device". - let core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection); let keymap = (XKBXH.xkb_x11_keymap_new_from_device)( self.xkb_context, self.xcb_connection, - core_keyboard_id, + device_id, xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ); assert_ne!(keymap, ptr::null_mut()); - let state = - (XKBXH.xkb_x11_state_new_from_device)(keymap, self.xcb_connection, core_keyboard_id); + let state = (XKBXH.xkb_x11_state_new_from_device)(keymap, self.xcb_connection, device_id); self.post_init(state, keymap); } #[cfg(feature = "wayland")] - pub(crate) unsafe fn init_with_fd(&mut self, fd: File, size: usize) { + pub(crate) unsafe fn init_with_fd(&mut self, fd: std::fs::File, size: usize) { if !self.xkb_keymap.is_null() { self.de_init(); } @@ -492,6 +413,7 @@ impl KbState { } impl KbState { + #[cfg(feature = "wayland")] pub(crate) unsafe fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 } @@ -502,6 +424,7 @@ impl KbState { } #[inline] + #[cfg(feature = "wayland")] pub(crate) fn locked(&self) -> bool { self.locked } @@ -521,9 +444,6 @@ impl Drop for KbState { 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); } @@ -543,211 +463,177 @@ pub enum Error { /// libxkbcommon is not available XKBNotFound, /// Provided RMLVO specified a keymap that would not be loaded + #[cfg(feature = "wayland")] BadNames, } impl KbState { - pub fn process_key_event(&mut self, keycode: u32, state: ElementState) -> KeyEventResults<'_> { - KeyEventResults::new(self, keycode, state == ElementState::Pressed) - } - - pub fn process_key_repeat_event(&mut self, keycode: u32) -> KeyEventResults<'_> { - KeyEventResults::new(self, keycode, false) - } - - fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<&'static str> { - 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 final `\0` - self.scratch_buffer.pop(); - Some(byte_slice_to_cached_string(&self.scratch_buffer)) - } -} - -#[derive(Copy, Clone, Debug)] -enum XkbCompose { - Accepted(ffi::xkb_compose_status), - Ignored, - Uninitialized, -} - -pub(crate) struct KeyEventResults<'a> { - state: &'a mut KbState, - keycode: u32, - keysym: u32, - compose: Option, -} + pub fn process_key_event( + &mut self, + keycode: u32, + group: u32, + state: ElementState, + ) -> KeyEventResults { + let keysym = self.get_one_sym_raw(keycode); -impl<'a> KeyEventResults<'a> { - fn new(state: &'a mut KbState, keycode: u32, compose: bool) -> Self { - let keysym = state.get_one_sym_raw(keycode); + let (text, text_with_all_modifiers); - let compose = if compose { - Some(match state.compose_feed_normal(keysym) { + if state == ElementState::Pressed { + // This is a press or repeat event. Feed the keysym to the compose engine. + match self.compose_feed(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()) + // The keysym potentially affected the compose state. Check it. + match self.compose_status().unwrap() { + xkb_compose_status::XKB_COMPOSE_NOTHING => { + // There is no ongoing composing. Use the keysym on its own. + text = keysym_to_utf8_raw(keysym); + text_with_all_modifiers = self.get_utf8_raw(keycode); + } + xkb_compose_status::XKB_COMPOSE_COMPOSING => { + // Composing is ongoing and not yet completed. No text is produced. + text = None; + text_with_all_modifiers = None; + } + xkb_compose_status::XKB_COMPOSE_COMPOSED => { + // This keysym completed the sequence. The text is the result. + text = self.compose_get_utf8(); + // 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 opposite behaviour. + text_with_all_modifiers = text; + } + xkb_compose_status::XKB_COMPOSE_CANCELLED => { + // Before this keysym, composing was ongoing. This keysym was not a possible + // continuation of the sequence and thus aborted composing. The standard + // behavior on linux in this case is to ignore both the sequence and this keysym. + text = None; + text_with_all_modifiers = None; + } + } + } + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => { + // This keysym is a modifier and thus has no effect on the engine. Nor does it produce + // text. + text = None; + text_with_all_modifiers = None; + } + _ => { + // The compose engine is disabled. Use the keysym on its own. + text = keysym_to_utf8_raw(keysym); + text_with_all_modifiers = self.get_utf8_raw(keycode); } - Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored, - None => XkbCompose::Uninitialized, - }) + } } else { - None + // This is a key release. No text is produced. + text = None; + text_with_all_modifiers = None; + } + + let key_without_modifiers = { + // 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 { + (XKBH.xkb_keymap_key_get_syms_by_level)( + self.xkb_keymap, + keycode, + group, + 0, + &mut keysyms, + ) + }; + let keysym = if keysym_count == 1 { + unsafe { *keysyms } + } else { + 0 + }; + keysym_to_key(keysym) }; - // let key_text = state.keysym_to_utf8_raw(keysym); - // unsafe { - // let layout_id = (XKBH.xkb_state_serialize_layout)(state.xkb_state, xkb_state_component::XKB_STATE_LAYOUT_EFFECTIVE); - // let layout_name_cstr = (XKBH.xkb_keymap_layout_get_name)(state.xkb_keymap, layout_id); - // let layout_name = std::ffi::CStr::from_ptr(layout_name_cstr as *mut _); - // debug!("KeyEventResults::new {:?}, {:?}", key_text, layout_name); - // } + let res = KeyEventResults { + keycode: super::keymap::raw_keycode_to_keycode(keycode), + location: super::keymap::keysym_location(keysym), + key: keysym_to_key(keysym), + key_without_modifiers, + text, + text_with_all_modifiers, + }; - KeyEventResults { - state, - keycode, - keysym, - compose, - } - } + // log::trace!("{:?}", res); - pub fn keycode(&mut self) -> KeyCode { - super::keymap::raw_keycode_to_keycode(self.keycode) + res } +} - pub fn key(&mut self) -> (Key<'static>, 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().nth(0).unwrap()), - ), - location, - ), - _ => (key, location), - } - } - _ => { - let composed_text = self.composed_text(); - - #[cfg(feature = "x11")] - if let Ok(Some(composed_text)) = composed_text { - *X11_EVPROC_NEXT_COMPOSE.lock().unwrap() = Some(composed_text); - } +#[derive(Debug)] +pub(crate) struct KeyEventResults { + pub keycode: KeyCode, + pub location: KeyLocation, + pub key: Key<'static>, + pub key_without_modifiers: Key<'static>, + pub text: Option<&'static str>, + pub text_with_all_modifiers: Option<&'static str>, +} - ( - composed_text - .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) - .map(Key::Character) - .unwrap_or(key), - location, - ) - } - }) +fn keysym_to_key(keysym: u32) -> Key<'static> { + let key = super::keymap::keysym_to_key(keysym); + if let Key::Unidentified(_) = key { + keysym_to_utf8_raw(keysym) + .map(Key::Character) + .unwrap_or(key) + } else { + key } +} - pub fn key_without_modifiers(&mut self) -> (Key<'static>, 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 { - (XKBH.xkb_keymap_key_get_syms_by_level)( - self.state.xkb_keymap, - self.keycode, - 0, - 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_utf8_raw(keysym: u32) -> Option<&'static str> { + let utf32 = unsafe { (XKBH.xkb_keysym_to_utf32)(keysym) }; + char_to_str(utf32) +} - fn keysym_to_key( - &mut self, - keysym: u32, - ) -> Result<(Key<'static>, KeyLocation), (Key<'static>, 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)) +fn char_to_str(utf32: u32) -> Option<&'static str> { + use std::cell::RefCell; + use std::collections::HashMap; + + if utf32 == 0 { + return None; + } + + if utf32 < 128 { + static ASCII: [u8; 128] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 127, + ]; + unsafe { + debug_assert_eq!(ASCII[utf32 as usize], utf32 as u8); + return Some(str::from_utf8_unchecked(slice::from_raw_parts( + &ASCII[utf32 as usize], + 1, + ))); } } - pub fn text(&mut self) -> Option<&'static str> { - self.composed_text() - .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) - } - - pub fn text_with_all_modifiers(&mut self) -> Option<&'static str> { - // 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)) + thread_local! { + static STRING_CACHE: RefCell> = RefCell::new(HashMap::new()); } - 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(()), - } + return STRING_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + if let Some(string) = cache.get(&utf32) { + Some(*string) } else { - Err(()) + let mut buf = [0; 4]; + let char = char::from_u32(utf32).unwrap(); + let string: &'static str = + Box::leak(char.encode_utf8(&mut buf).to_string().into_boxed_str()); + cache.insert(utf32, string); + Some(string) } - } + }); } fn byte_slice_to_cached_string(bytes: &[u8]) -> &'static str { @@ -758,7 +644,7 @@ fn byte_slice_to_cached_string(bytes: &[u8]) -> &'static str { static STRING_CACHE: RefCell> = RefCell::new(HashSet::new()); } - let string = std::str::from_utf8(bytes).unwrap(); + let string = str::from_utf8(bytes).unwrap(); STRING_CACHE.with(|cache| { let mut cache = cache.borrow_mut(); diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs index 6452ae65707..e06e0c82cbd 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -219,7 +219,7 @@ pub enum Event<'a> { /// The key modifiers have changed state Modifiers { /// current state of the modifiers - modifiers: xkb_state::ModifiersState, + modifiers: ModifiersState, }, /// A key event occurred Key { @@ -319,6 +319,7 @@ where current_repeat, details: repeat, }), + group: 0, }; (handler, source) }; @@ -368,6 +369,7 @@ struct RepeatDetails { struct KbdHandler { state: Rc>, + group: u32, callback: Rc>, repeat: Option, } @@ -379,7 +381,7 @@ struct KbdRepeat { } impl KbdRepeat { - fn start_repeat(&self, key: u32, keyboard: wl_keyboard::WlKeyboard, time: u32) { + fn start_repeat(&self, key: u32, group: u32, keyboard: wl_keyboard::WlKeyboard, time: u32) { // Start a new repetition, overwriting the previous ones self.timer_handle.cancel_all_timeouts(); @@ -391,6 +393,7 @@ impl KbdRepeat { *self.current_repeat.borrow_mut() = Some(RepeatData { keyboard, + group, keycode: key, gap, time: (time + self.details.delay) as u64 * 1000, @@ -548,24 +551,17 @@ impl KbdHandler { _ => unreachable!(), }; - let mut ker = state.process_key_event(key + 8, key_state); - - let physical_key = ker.keycode(); - let (logical_key, location) = ker.key(); - let text = ker.text(); - let (key_without_modifiers, _) = ker.key_without_modifiers(); - let text_with_all_modifiers = ker.text_with_all_modifiers(); - + let ker = state.process_key_event(key + 8, self.group, key_state); let repeats = unsafe { state.key_repeats(key) }; ( - physical_key, - logical_key, - text, - location, + ker.keycode, + ker.key, + ker.text, + ker.location, key_state, - key_without_modifiers, - text_with_all_modifiers, + ker.key_without_modifiers, + ker.text_with_all_modifiers, repeats, ) }; @@ -574,7 +570,7 @@ impl KbdHandler { if let Some(ref mut repeat_handle) = self.repeat { if repeats { if state == ElementState::Pressed { - repeat_handle.start_repeat(key, object.clone(), time); + repeat_handle.start_repeat(key, self.group, object.clone(), time); } else { repeat_handle.stop_repeat(key); } @@ -609,8 +605,9 @@ impl KbdHandler { dispatch_data: client::DispatchData<'_>, ) { { + self.group = group; let mut state = self.state.borrow_mut(); - state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group); + state.update_state(mods_depressed, mods_latched, mods_locked, 0, 0, group); (&mut *self.callback.borrow_mut())( Event::Modifiers { modifiers: state.mods_state(), @@ -639,6 +636,7 @@ impl KbdHandler { struct RepeatData { keyboard: wl_keyboard::WlKeyboard, + group: u32, keycode: u32, /// Gap between key presses in microseconds. gap: u64, @@ -683,25 +681,23 @@ impl calloop::EventSource for RepeatSource { if let Some(ref mut data) = *current_repeat.borrow_mut() { // there is something to repeat let mut state = state.borrow_mut(); - let mut ker = state.process_key_repeat_event(data.keycode + 8); - - let physical_key = ker.keycode(); - let (logical_key, location) = ker.key(); - let text = ker.text(); - let (key_without_modifiers, _) = ker.key_without_modifiers(); - let text_with_all_modifiers = ker.text_with_all_modifiers(); + let ker = state.process_key_event( + data.keycode + 8, + data.group, + ElementState::Pressed, + ); let new_time = data.gap + data.time; // Notify the callback. callback( Event::Repeat { time: (new_time / 1000) as u32, - physical_key, - logical_key, - text, - location, - key_without_modifiers, - text_with_all_modifiers, + physical_key: ker.keycode, + logical_key: ker.key, + text: ker.text, + location: ker.location, + key_without_modifiers: ker.key_without_modifiers, + text_with_all_modifiers: ker.text_with_all_modifiers, }, &mut data.keyboard, ); diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index 1a57b441b02..d2fd21daf7e 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -9,7 +9,6 @@ use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::Attached; use crate::keyboard::ModifiersState; -use crate::platform_impl::platform::common::xkb_state; use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::WindowId; @@ -89,14 +88,3 @@ impl KeyboardInner { } } } - -impl From for ModifiersState { - fn from(mods: xkb_state::ModifiersState) -> ModifiersState { - let mut wl_mods = ModifiersState::empty(); - wl_mods.set(ModifiersState::SHIFT, mods.shift); - wl_mods.set(ModifiersState::CONTROL, mods.ctrl); - wl_mods.set(ModifiersState::ALT, mods.alt); - wl_mods.set(ModifiersState::SUPER, mods.logo); - wl_mods - } -} diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 9dd110d9dd1..125a717baf6 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,6 +1,6 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; +use std::{collections::HashMap, rc::Rc, slice, sync::Arc}; -use libc::{c_char, c_int, c_long, c_ulong}; +use libc::{c_char, c_int, c_long, c_uint, c_ulong}; use parking_lot::MutexGuard; @@ -9,13 +9,12 @@ use super::{ GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension, }; -use util::modifiers::{ModifierKeyState, ModifierKeymap}; - use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, KeyEvent, RawKeyEvent, TouchPhase, WindowEvent}, event_loop::EventLoopWindowTarget as RootELW, keyboard::ModifiersState, + platform::unix::x11::EventLoopWindowTarget, platform_impl::platform::{ common::{keymap, xkb_state::KbState}, KeyEventExtra, @@ -25,41 +24,77 @@ use crate::{ /// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]". const KEYCODE_OFFSET: u8 = 8; +pub(super) struct Seat { + kb_state: KbState, + /// The master keyboard of this seat + keyboard: c_int, + /// The master pointer of this seat + pointer: c_int, + /// The window that has this seats keyboard focus + focus: Option, + /// The latest modifiers state + current_modifiers: ModifiersState, + /// `Some` iff `current_modifiers` changed while `focus` was `None`. If so, it has the same + /// value as `current_modifiers`. + pending_modifiers: Option, +} + pub(super) struct EventProcessor { pub(super) dnd: Dnd, pub(super) ime_receiver: ImeReceiver, pub(super) randr_event_offset: c_int, - pub(super) devices: RefCell>, + pub(super) devices: HashMap, pub(super) xi2ext: XExtension, + pub(super) xkb_base_event: c_int, pub(super) target: Rc>, - pub(super) kb_state: KbState, - pub(super) mod_keymap: ModifierKeymap, - pub(super) device_mod_state: ModifierKeyState, + pub(super) seats: Vec, // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, - // Currently focused window belonging to this process - pub(super) active_window: Option, } impl EventProcessor { - pub(super) fn init_device(&self, device: c_int) { - let wt = get_xtarget(&self.target); - let mut devices = self.devices.borrow_mut(); + pub(super) fn init_device( + target: &RootELW, + devices: &mut HashMap, + seats: &mut Vec, + device: c_int, + ) { + let wt = get_xtarget(target); if let Some(info) = DeviceInfo::get(&wt.xconn, device) { for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + let device_id = DeviceId(info.deviceid); + if info._use == ffi::XIMasterKeyboard { + if devices.contains_key(&device_id) { + seats.retain(|s| s.keyboard != info.deviceid); + } + let xconn = &wt.xconn; + let connection = unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }; + let kb_state = KbState::from_x11_xkb(connection, info.deviceid).unwrap(); + seats.push(Seat { + kb_state, + keyboard: info.deviceid, + pointer: info.attachment, + focus: None, + current_modifiers: ModifiersState::empty(), + pending_modifiers: None, + }); + } + devices.insert(device_id, Device::new(wt, info)); } } } - fn with_window(&self, window_id: ffi::Window, callback: F) -> Option + fn with_window( + wt: &EventLoopWindowTarget, + window_id: ffi::Window, + callback: F, + ) -> Option where F: Fn(&Arc) -> Ret, { let mut deleted = false; let window_id = WindowId(window_id); - let wt = get_xtarget(&self.target); let result = wt .windows .borrow() @@ -77,8 +112,8 @@ impl EventProcessor { result } - fn window_exists(&self, window_id: ffi::Window) -> bool { - self.with_window(window_id, |_| ()).is_some() + fn window_exists(wt: &EventLoopWindowTarget, window_id: ffi::Window) -> bool { + Self::with_window(wt, window_id, |_| ()).is_some() } pub(super) fn poll(&self) -> bool { @@ -135,27 +170,6 @@ impl EventProcessor { return; } - // We can't call a `&mut self` method because of the above borrow, - // so we use this macro for repeated modifier state updates. - macro_rules! update_modifiers { - ( $state:expr , $modifier:expr ) => {{ - match ($state, $modifier) { - (state, modifier) => { - if let Some(modifiers) = - self.device_mod_state.update_state(&state, modifier) - { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(modifiers), - }); - } - } - } - } - }}; - } - let event_type = xev.get_type(); match event_type { ffi::ClientMessage => { @@ -318,7 +332,7 @@ impl EventProcessor { let xwindow = xev.window; let window_id = mkwid(xwindow); - if let Some(window) = self.with_window(xwindow, Arc::clone) { + if let Some(window) = Self::with_window(wt, xwindow, Arc::clone) { // So apparently... // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent @@ -481,7 +495,7 @@ impl EventProcessor { // effect is that we waste some time trying to query unsupported properties. wt.xconn.update_cached_wm_info(wt.root); - self.with_window(xev.window, |window| { + Self::with_window(wt, xev.window, |window| { window.invalidate_cached_frame_extents(); }); } @@ -513,7 +527,7 @@ impl EventProcessor { let xev: &ffi::XVisibilityEvent = xev.as_ref(); let xwindow = xev.window; - self.with_window(xwindow, |window| window.visibility_notify()); + Self::with_window(wt, xwindow, |window| window.visibility_notify()); } ffi::Expose => { @@ -529,39 +543,6 @@ impl EventProcessor { } } - ffi::KeyPress => { - // TODO: Is it possible to exclusively use XInput2 events here? - let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - - let window = xkev.window; - let window_id = mkwid(window); - - let keycode = xkev.keycode; - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if keycode == 0 { - let written = if let Some(ic) = wt.ime.borrow().get_context(window) { - wt.xconn.lookup_utf8(ic, xkev) - } else { - return; - }; - if super::super::common::xkb_state::X11_EVPROC_NEXT_COMPOSE - .lock() - .unwrap() - .take() - .map(|composed| composed == written) - .unwrap_or(false) - { - return; - } - let event = Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedImeText(written), - }; - callback(event); - } - } - ffi::GenericEvent => { let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) { e @@ -579,8 +560,7 @@ impl EventProcessor { MouseScrollDelta::LineDelta, Touch, WindowEvent::{ - AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, - MouseWheel, + AxisMotion, CursorEntered, CursorLeft, CursorMoved, MouseInput, MouseWheel, }, }; @@ -594,8 +574,11 @@ impl EventProcessor { return; } - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); let state = if xev.evtype == ffi::XI_ButtonPress { Pressed @@ -603,33 +586,23 @@ impl EventProcessor { Released }; match xev.detail as u32 { - ffi::Button1 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Left, - modifiers, - }, - }), - ffi::Button2 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Middle, - modifiers, - }, - }), - ffi::Button3 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Right, - modifiers, - }, - }), + ffi::Button1 | ffi::Button2 | ffi::Button3 => { + let button = match xev.detail as u32 { + ffi::Button1 => Left, + ffi::Button2 => Middle, + ffi::Button3 => Right, + _ => unreachable!(), + }; + callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button, + modifiers: seat.current_modifiers, + }, + }) + } // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in @@ -648,7 +621,7 @@ impl EventProcessor { _ => unreachable!(), }, phase: TouchPhase::Moved, - modifiers, + modifiers: seat.current_modifiers, }, }); } @@ -660,7 +633,7 @@ impl EventProcessor { device_id, state, button: Other(x as u16), - modifiers, + modifiers: seat.current_modifiers, }, }), } @@ -671,10 +644,13 @@ impl EventProcessor { let window_id = mkwid(xev.event); let new_cursor_pos = (xev.event_x, xev.event_y); - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); - let cursor_moved = self.with_window(xev.event, |window| { + let cursor_moved = Self::with_window(wt, xev.event, |window| { let mut shared_state_lock = window.shared_state.lock(); util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); @@ -686,7 +662,7 @@ impl EventProcessor { event: CursorMoved { device_id, position, - modifiers, + modifiers: seat.current_modifiers, }, }); } else if cursor_moved.is_none() { @@ -702,11 +678,11 @@ impl EventProcessor { xev.valuators.mask_len as usize, ) }; - let mut devices = self.devices.borrow_mut(); - let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { - Some(device) => device, - None => return, - }; + let physical_device = + match self.devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; let mut value = xev.valuators.values; for i in 0..xev.valuators.mask_len * 8 { @@ -733,7 +709,7 @@ impl EventProcessor { } }, phase: TouchPhase::Moved, - modifiers, + modifiers: seat.current_modifiers, }, }); } else { @@ -761,8 +737,13 @@ impl EventProcessor { let window_id = mkwid(xev.event); let device_id = mkdid(xev.deviceid); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) { - let mut devices = self.devices.borrow_mut(); for device_info in all_info.iter() { if device_info.deviceid == xev.sourceid // This is needed for resetting to work correctly on i3, and @@ -772,14 +753,14 @@ impl EventProcessor { || device_info.attachment == xev.sourceid { let device_id = DeviceId(device_info.deviceid); - if let Some(device) = devices.get_mut(&device_id) { + if let Some(device) = self.devices.get_mut(&device_id) { device.reset_scroll_position(device_info); } } } } - if self.window_exists(xev.event) { + if Self::window_exists(wt, xev.event) { callback(Event::WindowEvent { window_id, event: CursorEntered { device_id }, @@ -787,25 +768,12 @@ impl EventProcessor { let position = PhysicalPosition::new(xev.event_x, xev.event_y); - // The mods field on this event isn't actually populated, so query the - // pointer device. In the future, we can likely remove this round-trip by - // relying on `Xkb` for modifier values. - // - // This needs to only be done after confirming the window still exists, - // since otherwise we risk getting a `BadWindow` error if the window was - // dropped with queued events. - let modifiers = wt - .xconn - .query_pointer(xev.event, xev.deviceid) - .expect("Failed to query pointer device") - .get_modifier_state(); - callback(Event::WindowEvent { window_id, event: CursorMoved { device_id, position, - modifiers, + modifiers: seat.current_modifiers, }, }); } @@ -815,7 +783,7 @@ impl EventProcessor { // Leave, FocusIn, and FocusOut can be received by a window that's already // been destroyed, which the user presumably doesn't want to deal with. - let window_closed = !self.window_exists(xev.event); + let window_closed = !Self::window_exists(wt, xev.event); if !window_closed { callback(Event::WindowEvent { window_id: mkwid(xev.event), @@ -833,43 +801,21 @@ impl EventProcessor { .focus(xev.event) .expect("Failed to focus input context"); - if self.active_window != Some(xev.event) { - self.active_window = Some(xev.event); + let seat = match find_seat(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, wt, Some(xev.event), &mut callback); - let window_id = mkwid(xev.event); + if let Some(focus) = seat.focus { let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); - - // Issue key press events for all pressed keys - Self::handle_pressed_keys( - &wt, - window_id, - ElementState::Pressed, - &mut self.kb_state, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); - - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self - .devices - .borrow() - .get(&DeviceId(xev.deviceid)) - .map(|device| device.attachment) - .unwrap_or(2); - callback(Event::WindowEvent { - window_id, + window_id: mkwid(focus), event: CursorMoved { - device_id: mkdid(pointer_id), + device_id: mkdid(seat.pointer), position, - modifiers: self.device_mod_state.modifiers(), + modifiers: seat.current_modifiers, }, }); } @@ -877,33 +823,18 @@ impl EventProcessor { ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - if !self.window_exists(xev.event) { - return; - } + wt.ime .borrow_mut() .unfocus(xev.event) .expect("Failed to unfocus input context"); - if self.active_window.take() == Some(xev.event) { - let window_id = mkwid(xev.event); - - // Issue key release events for all pressed keys - Self::handle_pressed_keys( - &wt, - window_id, - ElementState::Released, - &mut self.kb_state, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); - - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) - } + let seat = match find_seat(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, wt, None, &mut callback); } ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { @@ -915,9 +846,14 @@ impl EventProcessor { ffi::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!(), }; - if self.window_exists(xev.event) { + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + + if Self::window_exists(wt, xev.event) { let id = xev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); let location = PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); @@ -928,9 +864,9 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { - device_id: mkdid(util::VIRTUAL_CORE_POINTER), + device_id: mkdid(seat.pointer), position: location.cast(), - modifiers, + modifiers: seat.current_modifiers, }, }); } @@ -1019,65 +955,46 @@ impl EventProcessor { // The regular KeyPress event has a problem where if you press a dead key, a KeyPress // event won't be emitted. XInput 2 does not have this problem. ffi::XI_KeyPress | ffi::XI_KeyRelease => { - if let Some(active_window) = self.active_window { + let xkev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + + let seat = match find_seat(&mut self.seats, xkev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xkev.mods, &xkev.group, &mut callback); + Self::update_seat_focus(seat, wt, Some(xkev.event), &mut callback); + + if let Some(focus) = seat.focus { let state = if xev.evtype == ffi::XI_KeyPress { Pressed } else { Released }; - let xkev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - - // We use `self.active_window` here as `xkev.event` has a completely different - // value for some reason. - let window_id = mkwid(active_window); - let device_id = mkdid(xkev.deviceid); let keycode = xkev.detail as u32; - let mut ker = self.kb_state.process_key_event(keycode, state); - let physical_key = ker.keycode(); - let (logical_key, location) = ker.key(); - let text = ker.text(); - let (key_without_modifiers, _) = ker.key_without_modifiers(); - let text_with_all_modifiers = ker.text_with_all_modifiers(); + let ker = seat.kb_state.process_key_event( + keycode, + xkev.group.effective as u32, + state, + ); let repeat = xkev.flags & ffi::XIKeyRepeat == ffi::XIKeyRepeat; - if let Some(modifier) = - self.mod_keymap.get_modifier(keycode as ffi::KeyCode) - { - let old_modifiers = self.device_mod_state.modifiers(); - - self.device_mod_state.key_event( - state, - keycode as ffi::KeyCode, - modifier, - ); - - if old_modifiers != self.device_mod_state.modifiers() { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged( - self.device_mod_state.modifiers(), - ), - }); - } - } - callback(Event::WindowEvent { - window_id, + window_id: mkwid(focus), event: WindowEvent::KeyboardInput { device_id, event: KeyEvent { - physical_key, - logical_key, - text, - location, + physical_key: ker.keycode, + logical_key: ker.key, + text: ker.text, + location: ker.location, state, repeat, platform_specific: KeyEventExtra { - key_without_modifiers, - text_with_all_modifiers, + key_without_modifiers: ker.key_without_modifiers, + text_with_all_modifiers: ker.text_with_all_modifiers, }, }, is_synthetic: false, @@ -1087,44 +1004,6 @@ impl EventProcessor { } ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - // This is horrible, but I couldn't manage to respect keyboard layout changes - // in any other way. In fact, getting this to work at all proved so frustrating - // that I (@maroider) lost motivation to work on the keyboard event rework for - // some months. Thankfully, @ArturKovacs offered to help debug the problem - // over discord, and the following is the result of that debugging session. - // - // Without the XKB extension, the X.Org server sends us the `MappingNotify` - // event when there's been a change in the keyboard layout. This stops - // being the case when we select ourselves some XKB events with `XkbSelectEvents` - // and the "core keyboard device (0x100)" (we haven't tried with any other - // devices). We managed to reproduce this on both our machines. - // - // With the XKB extension active, it would seem like we're supposed to use the - // `XkbStateNotify` event to detect keyboard layout changes, but the `group` - // never changes value (it is always `0`). This worked for @ArturKovacs, but - // not for me. We also tried to use the `group` given to us in keypress events, - // but it remained constant there, too. - // - // We also tried to see if there was some other event that got fired when the - // keyboard layout changed, and we found a mysterious event with the value - // `85` (`0x55`). We couldn't find any reference to it in the X11 headers or - // in the X.Org server source. - // - // `KeymapNotify` did briefly look interesting based purely on the name, but - // it is only useful for checking what keys are pressed when we receive the - // event. - // - // So instead of any vaguely reasonable approach, we get this: reloading the - // keymap on *every* keypress. That's peak efficiency right there! - // - // FIXME: Someone please save our souls! Or at least our wasted CPU cycles. - // - // If you do manage to find a solution, remember to re-enable (and handle) the - // `XkbStateNotify` event with `XkbSelectEventDetails` with a mask of - // `XkbAllStateComponentsMask & !XkbPointerButtonMask` like in - // . - unsafe { self.kb_state.init_with_x11_keymap() }; - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; let state = match xev.evtype { @@ -1155,7 +1034,12 @@ impl EventProcessor { unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } { if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid); + Self::init_device( + &self.target, + &mut self.devices, + &mut self.seats, + info.deviceid, + ); callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added, @@ -1166,8 +1050,10 @@ impl EventProcessor { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed, }); - let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid)); + self.devices.remove(&DeviceId(info.deviceid)); + if info._use == ffi::XIMasterKeyboard { + self.seats.retain(|s| s.keyboard != info.deviceid); + } } } } @@ -1175,68 +1061,95 @@ impl EventProcessor { _ => {} } } - _ => { - if event_type == self.randr_event_offset { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt.xconn.available_monitors(); - for new_monitor in new_list { - prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| { - if new_monitor.scale_factor != prev_monitor.scale_factor { - for (window_id, window) in wt.windows.borrow().iter() { - if let Some(window) = window.upgrade() { - // Check if the window is on this monitor - let monitor = window.current_monitor(); - if monitor.name == new_monitor.name { - let (width, height) = - window.inner_size_physical(); - let (new_width, new_height) = window - .adjust_for_dpi( - prev_monitor.scale_factor, - new_monitor.scale_factor, - width, - height, - &*window.shared_state.lock(), - ); - - let window_id = crate::window::WindowId( - crate::platform_impl::platform::WindowId::X( - *window_id, - ), + _ if event_type == self.randr_event_offset => { + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = monitor::invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = wt.xconn.available_monitors(); + for new_monitor in new_list { + prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| { + if new_monitor.scale_factor != prev_monitor.scale_factor { + for (window_id, window) in wt.windows.borrow().iter() { + if let Some(window) = window.upgrade() { + // Check if the window is on this monitor + let monitor = window.current_monitor(); + if monitor.name == new_monitor.name { + let (width, height) = window.inner_size_physical(); + let (new_width, new_height) = window + .adjust_for_dpi( + prev_monitor.scale_factor, + new_monitor.scale_factor, + width, + height, + &*window.shared_state.lock(), + ); + + let window_id = crate::window::WindowId( + crate::platform_impl::platform::WindowId::X( + *window_id, + ), + ); + let old_inner_size = + PhysicalSize::new(width, height); + let mut new_inner_size = + PhysicalSize::new(new_width, new_height); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = + new_inner_size.into(); + window.set_inner_size_physical( + new_width, new_height, ); - let old_inner_size = - PhysicalSize::new(width, height); - let mut new_inner_size = - PhysicalSize::new(new_width, new_height); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = - new_inner_size.into(); - window.set_inner_size_physical( - new_width, new_height, - ); - } } } } } - }); + } + }); + } + } + } + _ if event_type == self.xkb_base_event => { + let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) }; + let seat = match find_seat(&mut self.seats, xev.device as c_int) { + Some(state) => state, + _ => return, + }; + match xev.xkb_type { + ffi::XkbMapNotify | ffi::XkbNewKeyboardNotify => { + unsafe { + seat.kb_state.init_with_x11_keymap(xev.device as c_int); } + Self::modifiers_changed(seat, &mut callback); + } + ffi::XkbStateNotify => { + let xev = unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) }; + Self::update_seat_kb( + seat, + xev.base_mods as u32, + xev.latched_mods as u32, + xev.locked_mods as u32, + xev.base_group as u32, + xev.latched_group as u32, + xev.locked_group as u32, + &mut callback, + ); } + _ => {} } } + _ => {} } match self.ime_receiver.try_recv() { @@ -1247,68 +1160,128 @@ impl EventProcessor { } } - fn handle_pressed_keys( - wt: &super::EventLoopWindowTarget, - window_id: crate::window::WindowId, - state: ElementState, - kb_state: &mut KbState, - mod_keymap: &ModifierKeymap, - device_mod_state: &mut ModifierKeyState, + /// Updates the window that has this seat's keyboard focus. + /// + /// This function must be called whenever an event occurs that could potentially imply a + /// change of the focus. Some non-multi-seat-aware window managers treat all focus changes + /// as changes of the core-seat focus. In these cases the X server will not send FocusIn/Out + /// events for additional seats. By calling this function on every keyboard input, we update + /// the focus as necessary. + fn update_seat_focus( + seat: &mut Seat, + wt: &EventLoopWindowTarget, + mut focus: Option, callback: &mut F, ) where F: FnMut(Event<'_, T>), { - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - - // Update modifiers state and emit key events based on which keys are currently pressed. - for keycode in wt - .xconn - .query_keymap() - .into_iter() - .filter(|k| *k >= KEYCODE_OFFSET) - { - let keycode = keycode as u32; - - let mut ker = kb_state.process_key_event(keycode, state); - let physical_key = ker.keycode(); - let (logical_key, location) = ker.key(); - let text = ker.text(); - let (key_without_modifiers, _) = ker.key_without_modifiers(); - let text_with_all_modifiers = ker.text_with_all_modifiers(); - - if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) { - let old_modifiers = device_mod_state.modifiers(); - - device_mod_state.key_event(state, keycode as ffi::KeyCode, modifier); - - if old_modifiers != device_mod_state.modifiers() { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(device_mod_state.modifiers()), - }); + if seat.focus == focus { + return; + } + if let Some(new) = focus { + if !Self::window_exists(wt, new) { + if seat.focus.is_none() { + return; } + focus = None; } - + } + if seat.focus.is_some() != focus.is_some() { + let mask = if focus.is_none() { + 0 + } else { + ffi::XkbModifierStateMask + }; + wt.xconn + .select_xkb_event_details( + seat.keyboard as c_uint, + ffi::XkbStateNotify as c_uint, + mask, + ) + .unwrap() + .queue(); + } + if let Some(focus) = seat.focus { callback(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id, - event: KeyEvent { - physical_key, - logical_key, - text, - location, - state, - repeat: false, - platform_specific: KeyEventExtra { - key_without_modifiers, - text_with_all_modifiers, - }, - }, - is_synthetic: true, - }, + window_id: mkwid(focus), + event: WindowEvent::Focused(false), }); } + seat.focus = focus; + if let Some(focus) = seat.focus { + callback(Event::WindowEvent { + window_id: mkwid(focus), + event: WindowEvent::Focused(true), + }); + if let Some(mods) = seat.pending_modifiers.take() { + callback(Event::WindowEvent { + window_id: mkwid(focus), + event: WindowEvent::ModifiersChanged(mods), + }); + } + } + } + + fn update_seat_kb_xi( + seat: &mut Seat, + mods: &ffi::XIModifierState, + group: &ffi::XIGroupState, + callback: &mut F, + ) where + F: FnMut(Event<'_, T>), + { + Self::update_seat_kb( + seat, + mods.base as u32, + mods.latched as u32, + mods.locked as u32, + group.base as u32, + group.latched as u32, + group.locked as u32, + callback, + ); + } + + fn update_seat_kb( + seat: &mut Seat, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + depressed_group: u32, + latched_group: u32, + locked_group: u32, + callback: &mut F, + ) where + F: FnMut(Event<'_, T>), + { + seat.kb_state.update_state( + mods_depressed, + mods_latched, + mods_locked, + depressed_group, + latched_group, + locked_group, + ); + Self::modifiers_changed(seat, callback); + } + + fn modifiers_changed(seat: &mut Seat, callback: &mut F) + where + F: FnMut(Event<'_, T>), + { + let new = seat.kb_state.mods_state(); + if seat.current_modifiers != new { + seat.current_modifiers = new; + if let Some(window) = seat.focus { + seat.pending_modifiers = None; + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::ModifiersChanged(new), + }); + } else { + seat.pending_modifiers = Some(new); + } + } } } @@ -1331,3 +1304,11 @@ fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchP *first == Some(id) } + +fn find_seat(seats: &mut [Seat], kb: c_int) -> Option<&mut Seat> { + seats.iter_mut().find(|s| s.keyboard == kb) +} + +fn find_seat_by_pointer(seats: &mut [Seat], pointer: c_int) -> Option<&mut Seat> { + seats.iter_mut().find(|s| s.pointer == pointer) +} diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs index b95da711010..4e2db1ef5e0 100644 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ b/src/platform_impl/linux/x11/ime/mod.rs @@ -99,17 +99,6 @@ impl Ime { Ok(!self.is_destroyed()) } - pub fn get_context(&self, window: ffi::Window) -> Option { - if self.is_destroyed() { - return None; - } - if let Some(&Some(ref context)) = self.inner.contexts.get(&window) { - Some(context.ic) - } else { - None - } - } - pub fn remove_context(&mut self, window: ffi::Window) -> Result { if let Some(Some(context)) = self.inner.contexts.remove(&window) { unsafe { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 6aa493e215a..3e905e62785 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -50,9 +50,7 @@ use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeSender}, - util::modifiers::ModifierKeymap, }; -use super::common::xkb_state::KbState; use crate::{ error::OsError as RootOsError, event::{Event, StartCause}, @@ -61,6 +59,11 @@ use crate::{ window::WindowAttributes, }; +use xkbcommon_dl::x11::{ + xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + XKBCOMMON_X11_HANDLE as XKBXH, +}; + const X_TOKEN: Token = Token(0); const USER_REDRAW_TOKEN: Token = Token(1); @@ -162,6 +165,26 @@ impl EventLoop { ext }; + let xkb_base_event = { + let mut base_event = 0; + + let result = unsafe { + (XKBXH.xkb_x11_setup_xkb_extension)( + (xconn.xlib_xcb.XGetXCBConnection)(xconn.display), + 1, // major + 0, // minor + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + ptr::null_mut(), // major out + ptr::null_mut(), // minor out + &mut base_event, + ptr::null_mut(), // base error + ) + }; + assert_eq!(result, 1, "Failed to initialize libxkbcommon"); + + base_event + }; + unsafe { let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; @@ -180,9 +203,6 @@ impl EventLoop { xconn.update_cached_wm_info(root); - let mut mod_keymap = ModifierKeymap::new(); - mod_keymap.reset_from_x_connection(&xconn); - let poll = Poll::new().unwrap(); let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); let queue = Arc::new(NotificationQueue::new(waker)); @@ -195,10 +215,6 @@ impl EventLoop { let (redraw_sender, redraw_channel) = channel(queue, NotificationId::gen_next()); - let kb_state = - KbState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }) - .unwrap(); - let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { ime, @@ -214,19 +230,17 @@ impl EventLoop { _marker: ::std::marker::PhantomData, }); - let event_processor = EventProcessor { + let mut event_processor = EventProcessor { target: target.clone(), dnd, devices: Default::default(), randr_event_offset, ime_receiver, xi2ext, - kb_state, - mod_keymap, - device_mod_state: Default::default(), + xkb_base_event: xkb_base_event as c_int, num_touch: 0, first_touch: None, - active_window: None, + seats: Default::default(), }; // Register for device hotplug events @@ -236,7 +250,12 @@ impl EventLoop { .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) .queue(); - event_processor.init_device(ffi::XIAllDevices); + EventProcessor::init_device( + &target, + &mut event_processor.devices, + &mut event_processor.seats, + ffi::XIAllDevices, + ); let result = EventLoop { poll, @@ -614,11 +633,19 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { + fn new(wt: &EventLoopWindowTarget, info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); - let wt = get_xtarget(&el.target); + if info._use == ffi::XIMasterKeyboard { + wt.xconn + .select_xkb_events( + info.deviceid as c_uint, + ffi::XkbNewKeyboardNotifyMask | ffi::XkbMapNotifyMask, + ) + .unwrap() + .queue(); + } if Device::physical_device(info) { // Register for global raw events diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 24aad57937a..daef20b69ef 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,30 +1,6 @@ -use std::{slice, str}; - use super::*; -use crate::keyboard::ModifiersState; pub const VIRTUAL_CORE_POINTER: c_int = 2; -pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; - -// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to -// re-allocate (and make another round-trip) in the *vast* majority of cases. -// To test if `lookup_utf8` works correctly, set this to 1. -const TEXT_BUFFER_SIZE: usize = 1024; - -impl ModifiersState { - pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self { - ModifiersState::from_x11_mask(state.effective as c_uint) - } - - pub(crate) fn from_x11_mask(mask: c_uint) -> Self { - let mut m = ModifiersState::empty(); - m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0); - m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0); - m.set(ModifiersState::CONTROL, mask & ffi::ControlMask != 0); - m.set(ModifiersState::SUPER, mask & ffi::Mod4Mask != 0); - m - } -} // NOTE: Some of these fields are not used, but may be of use in the future. pub struct PointerState<'a> { @@ -36,17 +12,9 @@ pub struct PointerState<'a> { pub win_x: c_double, pub win_y: c_double, buttons: ffi::XIButtonState, - modifiers: ffi::XIModifierState, - pub group: ffi::XIGroupState, pub relative_to_window: bool, } -impl<'a> PointerState<'a> { - pub fn get_modifier_state(&self) -> ModifiersState { - ModifiersState::from_x11(&self.modifiers) - } -} - impl<'a> Drop for PointerState<'a> { fn drop(&mut self) { if !self.buttons.mask.is_null() { @@ -91,6 +59,23 @@ impl XConnection { } } + pub fn select_xkb_event_details( + &self, + device_id: c_uint, + event: c_uint, + mask: c_ulong, + ) -> Option> { + let status = unsafe { + (self.xlib.XkbSelectEventDetails)(self.display, device_id, event, mask, mask) + }; + if status == ffi::True { + Some(Flusher::new(self)) + } else { + error!("Could not select XKB events: The XKB extension is not initialized!"); + None + } + } + pub fn query_pointer( &self, window: ffi::Window, @@ -133,58 +118,8 @@ impl XConnection { win_x, win_y, buttons, - modifiers, - group, relative_to_window, }) } } - - fn lookup_utf8_inner( - &self, - ic: ffi::XIC, - key_event: &mut ffi::XKeyEvent, - buffer: *mut u8, - size: usize, - ) -> (ffi::KeySym, ffi::Status, c_int) { - let mut keysym: ffi::KeySym = 0; - let mut status: ffi::Status = 0; - let count = unsafe { - (self.xlib.Xutf8LookupString)( - ic, - key_event, - buffer as *mut c_char, - size as c_int, - &mut keysym, - &mut status, - ) - }; - (keysym, status, count) - } - - pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { - // `assume_init` is safe here because the array consists of `MaybeUninit` values, - // which do not require initialization. - let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = - unsafe { MaybeUninit::uninit().assume_init() }; - // If the buffer overflows, we'll make a new one on the heap. - let mut vec; - - let (_, status, count) = - self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); - - let bytes = if status == ffi::XBufferOverflow { - vec = Vec::with_capacity(count as usize); - let (_, _, new_count) = - self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); - debug_assert_eq!(count, new_count); - - unsafe { vec.set_len(count as usize) }; - &vec[..count as usize] - } else { - unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } - }; - - str::from_utf8(bytes).unwrap_or("").to_string() - } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs deleted file mode 100644 index fc0c9d90620..00000000000 --- a/src/platform_impl/linux/x11/util/keys.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{iter::Enumerate, ptr, slice::Iter}; - -use super::*; - -pub struct Keymap { - keys: [u8; 32], -} - -pub struct KeymapIter<'a> { - iter: Enumerate>, - index: usize, - item: Option, -} - -impl Keymap { - pub fn iter(&self) -> KeymapIter<'_> { - KeymapIter { - iter: self.keys.iter().enumerate(), - index: 0, - item: None, - } - } -} - -impl<'a> IntoIterator for &'a Keymap { - type Item = ffi::KeyCode; - type IntoIter = KeymapIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl Iterator for KeymapIter<'_> { - type Item = ffi::KeyCode; - - fn next(&mut self) -> Option { - if self.item.is_none() { - while let Some((index, &item)) = self.iter.next() { - if item != 0 { - self.index = index; - self.item = Some(item); - break; - } - } - } - - self.item.take().map(|item| { - debug_assert!(item != 0); - - let bit = first_bit(item); - - if item != bit { - // Remove the first bit; save the rest for further iterations - self.item = Some(item ^ bit); - } - - let shift = bit.trailing_zeros() + (self.index * 8) as u32; - shift as ffi::KeyCode - }) - } -} - -impl XConnection { - pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym { - unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) } - } - - pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym { - let mut keysym = 0; - - unsafe { - (self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); - } - - keysym - } - - pub fn query_keymap(&self) -> Keymap { - let mut keys = [0; 32]; - - unsafe { - (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); - } - - Keymap { keys } - } -} - -fn first_bit(b: u8) -> u8 { - 1 << b.trailing_zeros() -} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index df3d89138ab..b7ea4e76904 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -9,9 +9,7 @@ mod geometry; mod hint; mod icon; mod input; -pub mod keys; mod memory; -pub mod modifiers; mod randr; mod window_property; mod wm; diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs deleted file mode 100644 index d7e10fa0b27..00000000000 --- a/src/platform_impl/linux/x11/util/modifiers.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::{collections::HashMap, slice}; - -use super::*; - -use crate::event::ElementState; -use crate::keyboard::ModifiersState; - -// Offsets within XModifierKeymap to each set of keycodes. -// We are only interested in Shift, Control, Alt, and Logo. -// -// There are 8 sets total. The order of keycode sets is: -// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 -// -// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html -const SHIFT_OFFSET: usize = 0; -const CONTROL_OFFSET: usize = 2; -const ALT_OFFSET: usize = 3; -const LOGO_OFFSET: usize = 6; -const NUM_MODS: usize = 8; - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Modifier { - Alt, - Ctrl, - Shift, - Logo, -} - -#[derive(Debug, Default)] -pub struct ModifierKeymap { - // Maps keycodes to modifiers - keys: HashMap, -} - -#[derive(Clone, Debug, Default)] -pub struct ModifierKeyState { - // Contains currently pressed modifier keys and their corresponding modifiers - keys: HashMap, - state: ModifiersState, -} - -impl ModifierKeymap { - pub fn new() -> ModifierKeymap { - ModifierKeymap::default() - } - - pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { - self.keys.get(&keycode).cloned() - } - - pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { - unsafe { - let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); - - if keymap.is_null() { - panic!("failed to allocate XModifierKeymap"); - } - - self.reset_from_x_keymap(&*keymap); - - (xconn.xlib.XFreeModifiermap)(keymap); - } - } - - pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { - let keys_per_mod = keymap.max_keypermod as usize; - - let keys = unsafe { - slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) - }; - - self.keys.clear(); - - self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); - self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); - self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); - self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); - } - - fn read_x_keys( - &mut self, - keys: &[ffi::KeyCode], - offset: usize, - keys_per_mod: usize, - modifier: Modifier, - ) { - let start = offset * keys_per_mod; - let end = start + keys_per_mod; - - for &keycode in &keys[start..end] { - if keycode != 0 { - self.keys.insert(keycode, modifier); - } - } - } -} - -impl ModifierKeyState { - pub fn update_keymap(&mut self, mods: &ModifierKeymap) { - self.keys.retain(|k, v| { - if let Some(m) = mods.get_modifier(*k) { - *v = m; - true - } else { - false - } - }); - - self.reset_state(); - } - - pub fn update_state( - &mut self, - state: &ModifiersState, - except: Option, - ) -> Option { - let mut new_state = *state; - - match except { - Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt_key()), - Some(Modifier::Ctrl) => { - new_state.set(ModifiersState::CONTROL, self.state.control_key()) - } - Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift_key()), - Some(Modifier::Logo) => new_state.set(ModifiersState::SUPER, self.state.super_key()), - None => (), - } - - if self.state == new_state { - None - } else { - self.keys.retain(|_k, v| get_modifier(&new_state, *v)); - self.state = new_state; - Some(new_state) - } - } - - pub fn modifiers(&self) -> ModifiersState { - self.state - } - - pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { - match state { - ElementState::Pressed => self.key_press(keycode, modifier), - ElementState::Released => self.key_release(keycode), - } - } - - pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { - self.keys.insert(keycode, modifier); - - set_modifier(&mut self.state, modifier, true); - } - - pub fn key_release(&mut self, keycode: ffi::KeyCode) { - if let Some(modifier) = self.keys.remove(&keycode) { - if self.keys.values().find(|&&m| m == modifier).is_none() { - set_modifier(&mut self.state, modifier, false); - } - } - } - - fn reset_state(&mut self) { - let mut new_state = ModifiersState::default(); - - for &m in self.keys.values() { - set_modifier(&mut new_state, m, true); - } - - self.state = new_state; - } -} - -fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool { - match modifier { - Modifier::Alt => state.alt_key(), - Modifier::Ctrl => state.control_key(), - Modifier::Shift => state.shift_key(), - Modifier::Logo => state.super_key(), - } -} - -fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) { - match modifier { - Modifier::Alt => state.set(ModifiersState::ALT, value), - Modifier::Ctrl => state.set(ModifiersState::CONTROL, value), - Modifier::Shift => state.set(ModifiersState::SHIFT, value), - Modifier::Logo => state.set(ModifiersState::SUPER, value), - } -}