From 97ff56615665ddaf85a566ff8a5a0cb57371dc8a Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 30 Oct 2023 01:59:58 +0400 Subject: [PATCH 1/2] Add API to manipulate XKB layouts While this is not ideal it does the job to implement basic layouts functionality like switching layout on compositor and propagating the change to client, resolving latin character for Keysym handles and set/restore layouts. --- src/input/keyboard/mod.rs | 194 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/src/input/keyboard/mod.rs b/src/input/keyboard/mod.rs index 08f125eb148a..f9949f8caefb 100644 --- a/src/input/keyboard/mod.rs +++ b/src/input/keyboard/mod.rs @@ -11,9 +11,11 @@ use std::{ use thiserror::Error; use tracing::{debug, error, info, info_span, instrument, trace}; +use xkbcommon::xkb::ffi::XKB_STATE_LAYOUT_EFFECTIVE; pub use xkbcommon::xkb::{self, keysyms, Keycode, Keysym}; use super::{Seat, SeatHandler}; +use private::XkbTypesHandle; #[cfg(feature = "wayland_frontend")] mod keymap_file; @@ -249,12 +251,166 @@ impl<'a> KeysymHandle<'a> { .key_get_syms_by_level(self.keycode, self.state.key_get_layout(self.keycode), 0) } + /// Get the raw latin keysym or fallback to current raw keysym. + /// + /// This method is handy to implement layout agnostic bindings. Keep in mind that + /// it could be not-ideal to use just this function, since some layouts utilize non-standard + /// shift levels and you should look into [`Self::modified_sym`] first. + /// + /// The `None` is returned when the underlying keycode doesn't produce a valid keysym. + pub fn raw_latin_sym_or_raw_current_sym(&self) -> Option { + let effective_layout = Layout(self.state.key_get_layout(self.keycode)); + // NOTE: There's always a keysym in the current layout given that we have modified_sym. + let base_sym = *self.raw_syms().first()?; + + // If the character is ascii or non-printable, return it. + if base_sym.key_char().map(|ch| ch.is_ascii()).unwrap_or(true) { + return Some(base_sym); + }; + + // Try to look other layouts and find the one with ascii character. + for layout in self.layouts() { + if layout == effective_layout { + continue; + } + + for keysym in self.raw_syms_for_key_in_layout(self.keycode, layout) { + // NOTE: Only check for ascii alphabetic since the rest of the punctuation should + // be uniform across layouts. + if keysym + .key_char() + .map(|key| key.is_ascii_alphabetic()) + .unwrap_or(false) + { + return Some(*keysym); + } + } + } + + Some(base_sym) + } + /// Returns the raw code in X keycode system (shifted by 8) pub fn raw_code(&'a self) -> Keycode { self.keycode } } +impl<'a> XkbTypesHandle for KeysymHandle<'a> { + fn keymap(&self) -> &xkb::Keymap { + self.keymap + } + + fn state(&self) -> &xkb::State { + self.state + } +} + +/// The currently active state of the Xkb. +pub struct XkbContext<'a> { + state: &'a mut xkb::State, + keymap: &'a mut xkb::Keymap, + mods_state: &'a mut ModifiersState, + mods_changed: &'a mut bool, +} + +impl<'a> XkbContext<'a> { + /// Set layout of the keyboard to the given index. + /// + /// Returns `true` when the layout got applied. + pub fn set_layout(&mut self, layout: Layout) { + let state = self.state.update_mask( + self.mods_state.serialized.depressed, + self.mods_state.serialized.latched, + self.mods_state.serialized.locked, + 0, + 0, + layout.0, + ); + + if state != 0 { + self.mods_state.update_with(self.state); + *self.mods_changed = true; + } + } + + /// Switches layout forward cycling when cyclying when it reaches the end. + pub fn cycle_next_layout(&mut self) { + let next_layout = (self.active_layout().0 + 1) % self.keymap.num_layouts(); + self.set_layout(Layout(next_layout)); + } + + /// Switches layout backward cycling when cyclying when it reaches the start. + pub fn cycle_prev_layout(&mut self) { + let num_layouts = self.keymap.num_layouts(); + let next_layout = (num_layouts + self.active_layout().0 - 1) % num_layouts; + self.set_layout(Layout(next_layout)); + } +} + +impl<'a> fmt::Debug for XkbContext<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("XkbContext") + .field("mods_state", &self.mods_state) + .field("mods_changed", &self.mods_changed) + .finish() + } +} + +impl<'a> XkbTypesHandle for XkbContext<'a> { + fn keymap(&self) -> &xkb::Keymap { + self.keymap + } + + fn state(&self) -> &xkb::State { + self.state + } +} + +/// Query and manipulate Xkb layouts. +pub trait XkbContextHandler: XkbTypesHandle { + /// Get the active layout of the keyboard. + fn active_layout(&self) -> Layout { + (0..self.keymap().num_layouts()) + .find(|&idx| { + self.state() + .layout_index_is_active(idx, XKB_STATE_LAYOUT_EFFECTIVE) + }) + .map(Layout) + .unwrap_or_default() + } + + /// Get the human readablle name for the layout. + fn layout_name(&self, layout: Layout) -> &str { + self.keymap().layout_get_name(layout.0) + } + + /// Get the number of layouts in the keymap. + fn num_layouts(&self) -> usize { + self.keymap().num_layouts() as usize + } + + /// Iterate over layouts present in the keymap. + fn layouts(&self) -> Box> { + Box::new((0..self.keymap().num_layouts()).map(Layout)) + } + + /// Returns the syms for the underlying keycode without any modifications by the current keymap + /// state applied. + fn raw_syms_for_key_in_layout(&self, keycode: Keycode, layout: Layout) -> &[Keysym] { + self.keymap().key_get_syms_by_level(keycode, layout.0, 0) + } +} + +impl<'a> XkbContextHandler for XkbContext<'a> {} +impl<'a> XkbContextHandler for KeysymHandle<'a> {} + +/// Reference to the XkbLayout in the active keymap. +/// +/// The layout may become invalid after calling [`KeyboardHandle::set_xkb_config`] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub struct Layout(xkb::LayoutIndex); + /// Result for key input filtering (see [`KeyboardHandle::input`]) #[derive(Debug)] pub enum FilterResult { @@ -448,6 +604,32 @@ impl KeyboardHandle { Ok(()) } + /// Access the underlying Xkb state and perform mutable operations on it, like + /// changing layouts. + /// + /// The changes to the state are automatically broadcasted to the focused client on exit. + pub fn with_kkb_state) -> T, T>(&self, data: &mut D, mut callback: F) -> T { + let internal = &mut *self.arc.internal.lock().unwrap(); + let mut mods_changed = false; + let state = XkbContext { + mods_state: &mut internal.mods_state, + state: &mut internal.state, + keymap: &mut internal.keymap, + mods_changed: &mut mods_changed, + }; + + let result = callback(state); + + if mods_changed { + if let Some((focus, _)) = internal.focus.as_mut() { + let seat = self.get_seat(data); + focus.modifiers(&seat, data, internal.mods_state, SERIAL_COUNTER.next_serial()); + }; + } + + result + } + /// Change the current grab on this keyboard to the provided grab /// /// Overwrites any current grab. @@ -838,3 +1020,15 @@ impl KeyboardGrab for DefaultGrab { unreachable!() } } + +mod private { + use super::xkb; + // Glue trait to avoid duplicated code. + pub trait XkbTypesHandle { + /// Get the reference to the xkb keymap. + fn keymap(&self) -> &xkb::Keymap; + + /// Get the reference to the xkb state. + fn state(&self) -> &xkb::State; + } +} From 73bd4ff5d25a1d348776fdb3aa468c9534678655 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 2 Nov 2023 14:09:12 +0400 Subject: [PATCH 2/2] v2 --- src/input/keyboard/mod.rs | 86 ++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/src/input/keyboard/mod.rs b/src/input/keyboard/mod.rs index f9949f8caefb..703e24f97736 100644 --- a/src/input/keyboard/mod.rs +++ b/src/input/keyboard/mod.rs @@ -15,7 +15,6 @@ use xkbcommon::xkb::ffi::XKB_STATE_LAYOUT_EFFECTIVE; pub use xkbcommon::xkb::{self, keysyms, Keycode, Keysym}; use super::{Seat, SeatHandler}; -use private::XkbTypesHandle; #[cfg(feature = "wayland_frontend")] mod keymap_file; @@ -236,17 +235,17 @@ impl<'a> KeysymHandle<'a> { /// does not want to or cannot handle multiple keysyms. /// /// If the key does not have exactly one keysym, returns [`keysyms::KEY_NoSymbol`]. - pub fn modified_sym(&'a self) -> Keysym { + pub fn modified_sym(&self) -> Keysym { self.state.key_get_one_sym(self.keycode) } /// Returns the syms for the underlying keycode with all modifications by the current keymap state applied. - pub fn modified_syms(&'a self) -> &'a [Keysym] { + pub fn modified_syms(&self) -> &[Keysym] { self.state.key_get_syms(self.keycode) } /// Returns the syms for the underlying keycode without any modifications by the current keymap state applied. - pub fn raw_syms(&'a self) -> &'a [Keysym] { + pub fn raw_syms(&self) -> &[Keysym] { self.keymap .key_get_syms_by_level(self.keycode, self.state.key_get_layout(self.keycode), 0) } @@ -274,12 +273,12 @@ impl<'a> KeysymHandle<'a> { continue; } - for keysym in self.raw_syms_for_key_in_layout(self.keycode, layout) { - // NOTE: Only check for ascii alphabetic since the rest of the punctuation should - // be uniform across layouts. + if let Some(keysym) = self.raw_syms_for_key_in_layout(self.keycode, layout).first() { + // NOTE: Only check for ascii non-control characters, since control ones are + // layout agnostic. if keysym .key_char() - .map(|key| key.is_ascii_alphabetic()) + .map(|key| key.is_ascii() && !key.is_ascii_control()) .unwrap_or(false) { return Some(*keysym); @@ -296,16 +295,6 @@ impl<'a> KeysymHandle<'a> { } } -impl<'a> XkbTypesHandle for KeysymHandle<'a> { - fn keymap(&self) -> &xkb::Keymap { - self.keymap - } - - fn state(&self) -> &xkb::State { - self.state - } -} - /// The currently active state of the Xkb. pub struct XkbContext<'a> { state: &'a mut xkb::State, @@ -316,8 +305,6 @@ pub struct XkbContext<'a> { impl<'a> XkbContext<'a> { /// Set layout of the keyboard to the given index. - /// - /// Returns `true` when the layout got applied. pub fn set_layout(&mut self, layout: Layout) { let state = self.state.update_mask( self.mods_state.serialized.depressed, @@ -334,13 +321,13 @@ impl<'a> XkbContext<'a> { } } - /// Switches layout forward cycling when cyclying when it reaches the end. + /// Switches layout forward cycling when it reaches the end. pub fn cycle_next_layout(&mut self) { let next_layout = (self.active_layout().0 + 1) % self.keymap.num_layouts(); self.set_layout(Layout(next_layout)); } - /// Switches layout backward cycling when cyclying when it reaches the start. + /// Switches layout backward cycling when it reaches the start. pub fn cycle_prev_layout(&mut self) { let num_layouts = self.keymap.num_layouts(); let next_layout = (num_layouts + self.active_layout().0 - 1) % num_layouts; @@ -357,18 +344,14 @@ impl<'a> fmt::Debug for XkbContext<'a> { } } -impl<'a> XkbTypesHandle for XkbContext<'a> { - fn keymap(&self) -> &xkb::Keymap { - self.keymap - } +/// Query and manipulate Xkb layouts. +pub trait XkbContextHandler { + /// Get the reference to the xkb keymap. + fn keymap(&self) -> &xkb::Keymap; - fn state(&self) -> &xkb::State { - self.state - } -} + /// Get the reference to the xkb state. + fn state(&self) -> &xkb::State; -/// Query and manipulate Xkb layouts. -pub trait XkbContextHandler: XkbTypesHandle { /// Get the active layout of the keyboard. fn active_layout(&self) -> Layout { (0..self.keymap().num_layouts()) @@ -380,16 +363,11 @@ pub trait XkbContextHandler: XkbTypesHandle { .unwrap_or_default() } - /// Get the human readablle name for the layout. + /// Get the human readable name for the layout. fn layout_name(&self, layout: Layout) -> &str { self.keymap().layout_get_name(layout.0) } - /// Get the number of layouts in the keymap. - fn num_layouts(&self) -> usize { - self.keymap().num_layouts() as usize - } - /// Iterate over layouts present in the keymap. fn layouts(&self) -> Box> { Box::new((0..self.keymap().num_layouts()).map(Layout)) @@ -402,8 +380,24 @@ pub trait XkbContextHandler: XkbTypesHandle { } } -impl<'a> XkbContextHandler for XkbContext<'a> {} -impl<'a> XkbContextHandler for KeysymHandle<'a> {} +impl<'a> XkbContextHandler for XkbContext<'a> { + fn keymap(&self) -> &xkb::Keymap { + self.keymap + } + + fn state(&self) -> &xkb::State { + self.state + } +} +impl<'a> XkbContextHandler for KeysymHandle<'a> { + fn keymap(&self) -> &xkb::Keymap { + self.keymap + } + + fn state(&self) -> &xkb::State { + self.state + } +} /// Reference to the XkbLayout in the active keymap. /// @@ -1020,15 +1014,3 @@ impl KeyboardGrab for DefaultGrab { unreachable!() } } - -mod private { - use super::xkb; - // Glue trait to avoid duplicated code. - pub trait XkbTypesHandle { - /// Get the reference to the xkb keymap. - fn keymap(&self) -> &xkb::Keymap; - - /// Get the reference to the xkb state. - fn state(&self) -> &xkb::State; - } -}