From 90dd28c910c0d2eab0796e873ca021708966202b Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Fri, 17 Sep 2021 16:55:01 +0200 Subject: [PATCH 1/2] wayland/seat: Add `KeysymHandle` to allow for keycode conversions. Currently keycodes are always converted using xkbcommons `State::key_get_one_sym` function. This may be not what the compositor wants, e.g. if it represents keybindings with explicit modifiers. Applying Shift in this case changes the sym, making it necessary for the compositor to *undo* this transformation, which is hard or even impossible and very unnecessary, when we have all the necessary information in smithay. Therefor this commit replaces the `Keysym` argument of the filter closure with a `KeysymHandle`, which allows for different variants of keysyms to be received. Modified (as previously), unmodified or even as a raw keycode. --- CHANGELOG.md | 1 + anvil/src/input_handler.rs | 4 ++- src/wayland/seat/keyboard.rs | 59 +++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2e085..c1cbe4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `XdgPositionerState` moved to `XdgPopupState` and added to `XdgRequest::NewPopup` - `PopupSurface::send_configure` now checks the protocol version and returns an `Result` +- `KeyboardHandle::input` filter closure now receives a `KeysymHandle` instead of a `Keysym`. ### Additions diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index e2f06d2..38925b9 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -45,7 +45,9 @@ impl AnvilState { let mut action = KeyAction::None; let suppressed_keys = &mut self.suppressed_keys; self.keyboard - .input(keycode, state, serial, time, |modifiers, keysym| { + .input(keycode, state, serial, time, |modifiers, handle| { + let keysym = handle.modified_sym(); + debug!(log, "keysym"; "state" => format!("{:?}", state), "mods" => format!("{:?}", modifiers), diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 99fc320..b2f6e8c 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -282,6 +282,47 @@ struct KbdRc { logger: ::slog::Logger, } +/// Handle to the underlying keycode to allow for different conversions +pub struct KeysymHandle<'a> { + keycode: u32, + keymap: &'a xkb::Keymap, + state: &'a xkb::State, +} + +impl<'a> fmt::Debug for KeysymHandle<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.keycode) + } +} + +impl<'a> KeysymHandle<'a> { + /// Returns the sym for the underlying keycode with all modifications by the current keymap state applied. + /// + /// This function is similar to [`KeysymHandle::modified_syms`], but is intended for cases where the user + /// 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 { + 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] { + 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] { + self.keymap + .key_get_syms_by_level(self.keycode, self.state.key_get_layout(self.keycode), 0) + } + + /// Returns the raw code in X keycode system (shifted by 8) + pub fn raw_code(&'a self) -> u32 { + self.keycode + } +} + /// An handle to a keyboard handler /// /// It can be cloned and all clones manipulate the same internal state. @@ -313,22 +354,24 @@ impl KeyboardHandle { /// to be compared against. This includes non-character keysyms, such as XF86 special keys. pub fn input(&self, keycode: u32, state: KeyState, serial: Serial, time: u32, filter: F) where - F: FnOnce(&ModifiersState, Keysym) -> bool, + F: FnOnce(&ModifiersState, KeysymHandle<'_>) -> bool, { trace!(self.arc.logger, "Handling keystroke"; "keycode" => keycode, "state" => format_args!("{:?}", state)); let mut guard = self.arc.internal.borrow_mut(); - - // Offset the keycode by 8, as the evdev XKB rules reflect X's - // broken keycode system, which starts at 8. - let sym = guard.state.key_get_one_sym(keycode + 8); - let mods_changed = guard.key_input(keycode, state); + let handle = KeysymHandle { + // Offset the keycode by 8, as the evdev XKB rules reflect X's + // broken keycode system, which starts at 8. + keycode: keycode + 8, + state: &guard.state, + keymap: &guard.keymap, + }; trace!(self.arc.logger, "Calling input filter"; - "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(sym) + "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(handle.modified_sym()) ); - if !filter(&guard.mods_state, sym) { + if !filter(&guard.mods_state, handle) { // the filter returned false, we do not forward to client trace!(self.arc.logger, "Input was intercepted by filter"); return; From 55eb73cb9e6ed4aaeab89528377a326370448a9c Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 19 Sep 2021 17:10:08 +0200 Subject: [PATCH 2/2] wayland/seat: add return value to `KeyboardHandle::input` --- CHANGELOG.md | 2 +- anvil/src/input_handler.rs | 48 +++++++++++++++++------------------- src/wayland/seat/keyboard.rs | 31 ++++++++++++++++++----- src/wayland/seat/mod.rs | 4 ++- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1cbe4b..b6b413c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - `XdgPositionerState` moved to `XdgPopupState` and added to `XdgRequest::NewPopup` - `PopupSurface::send_configure` now checks the protocol version and returns an `Result` -- `KeyboardHandle::input` filter closure now receives a `KeysymHandle` instead of a `Keysym`. +- `KeyboardHandle::input` filter closure now receives a `KeysymHandle` instead of a `Keysym` and returns a `FilterResult`. ### Additions diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 38925b9..1313bd1 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -13,7 +13,7 @@ use smithay::{ }, reexports::wayland_server::protocol::wl_pointer, wayland::{ - seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState}, + seat::{keysyms as xkb, AxisFrame, FilterResult, Keysym, ModifiersState}, SERIAL_COUNTER as SCOUNTER, }, }; @@ -42,7 +42,6 @@ impl AnvilState { let serial = SCOUNTER.next_serial(); let log = &self.log; let time = Event::time(&evt); - let mut action = KeyAction::None; let suppressed_keys = &mut self.suppressed_keys; self.keyboard .input(keycode, state, serial, time, |modifiers, handle| { @@ -60,27 +59,26 @@ impl AnvilState { // so that we can decide on a release if the key // should be forwarded to the client or not. if let KeyState::Pressed = state { - action = process_keyboard_shortcut(*modifiers, keysym); + let action = process_keyboard_shortcut(*modifiers, keysym); - // forward to client only if action == KeyAction::Forward - let forward = matches!(action, KeyAction::Forward); - - if !forward { + if action.is_some() { suppressed_keys.push(keysym); } - forward + action + .map(FilterResult::Intercept) + .unwrap_or(FilterResult::Forward) } else { let suppressed = suppressed_keys.contains(&keysym); - if suppressed { suppressed_keys.retain(|k| *k != keysym); + FilterResult::Intercept(KeyAction::None) + } else { + FilterResult::Forward } - - !suppressed } - }); - action + }) + .unwrap_or(KeyAction::None) } fn on_pointer_button(&mut self, evt: B::PointerButtonEvent) { @@ -157,7 +155,7 @@ impl AnvilState { match event { InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::(event) { - KeyAction::None | KeyAction::Forward => {} + KeyAction::None => {} KeyAction::Quit => { info!(self.log, "Quitting."); self.running.store(false, Ordering::SeqCst); @@ -245,7 +243,7 @@ impl AnvilState { pub fn process_input_event(&mut self, event: InputEvent) { match event { InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::(event) { - KeyAction::None | KeyAction::Forward => {} + KeyAction::None => {} KeyAction::Quit => { info!(self.log, "Quitting."); self.running.store(false, Ordering::SeqCst); @@ -522,32 +520,32 @@ enum KeyAction { Screen(usize), ScaleUp, ScaleDown, - /// Forward the key to the client - Forward, /// Do nothing more None, } -fn process_keyboard_shortcut(modifiers: ModifiersState, keysym: Keysym) -> KeyAction { +fn process_keyboard_shortcut(modifiers: ModifiersState, keysym: Keysym) -> Option { if modifiers.ctrl && modifiers.alt && keysym == xkb::KEY_BackSpace || modifiers.logo && keysym == xkb::KEY_q { // ctrl+alt+backspace = quit // logo + q = quit - KeyAction::Quit + Some(KeyAction::Quit) } else if (xkb::KEY_XF86Switch_VT_1..=xkb::KEY_XF86Switch_VT_12).contains(&keysym) { // VTSwicth - KeyAction::VtSwitch((keysym - xkb::KEY_XF86Switch_VT_1 + 1) as i32) + Some(KeyAction::VtSwitch( + (keysym - xkb::KEY_XF86Switch_VT_1 + 1) as i32, + )) } else if modifiers.logo && keysym == xkb::KEY_Return { // run terminal - KeyAction::Run("weston-terminal".into()) + Some(KeyAction::Run("weston-terminal".into())) } else if modifiers.logo && keysym >= xkb::KEY_1 && keysym <= xkb::KEY_9 { - KeyAction::Screen((keysym - xkb::KEY_1) as usize) + Some(KeyAction::Screen((keysym - xkb::KEY_1) as usize)) } else if modifiers.logo && modifiers.shift && keysym == xkb::KEY_M { - KeyAction::ScaleDown + Some(KeyAction::ScaleDown) } else if modifiers.logo && modifiers.shift && keysym == xkb::KEY_P { - KeyAction::ScaleUp + Some(KeyAction::ScaleUp) } else { - KeyAction::Forward + None } } diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index b2f6e8c..e5a1d57 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -323,6 +323,15 @@ impl<'a> KeysymHandle<'a> { } } +/// Result for key input filtering (see [`KeyboardHandle::input`]) +#[derive(Debug)] +pub enum FilterResult { + /// Forward the given keycode to the client + Forward, + /// Do not forward and return value + Intercept(T), +} + /// An handle to a keyboard handler /// /// It can be cloned and all clones manipulate the same internal state. @@ -347,14 +356,22 @@ impl KeyboardHandle { /// /// The `filter` argument is expected to be a closure which will peek at the generated input /// as interpreted by the keymap before it is forwarded to the focused client. If this closure - /// returns false, the input will not be sent to the client. This mechanism can be used to - /// implement compositor-level key bindings for example. + /// returns [`FilterResult::Forward`], the input will not be sent to the client. If it returns + /// [`FilterResult::Intercept`] a value can be passed to be returned by the whole function. + /// This mechanism can be used to implement compositor-level key bindings for example. /// /// The module [`crate::wayland::seat::keysyms`] exposes definitions of all possible keysyms /// to be compared against. This includes non-character keysyms, such as XF86 special keys. - pub fn input(&self, keycode: u32, state: KeyState, serial: Serial, time: u32, filter: F) + pub fn input( + &self, + keycode: u32, + state: KeyState, + serial: Serial, + time: u32, + filter: F, + ) -> Option where - F: FnOnce(&ModifiersState, KeysymHandle<'_>) -> bool, + F: FnOnce(&ModifiersState, KeysymHandle<'_>) -> FilterResult, { trace!(self.arc.logger, "Handling keystroke"; "keycode" => keycode, "state" => format_args!("{:?}", state)); let mut guard = self.arc.internal.borrow_mut(); @@ -371,10 +388,10 @@ impl KeyboardHandle { "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(handle.modified_sym()) ); - if !filter(&guard.mods_state, handle) { + if let FilterResult::Intercept(val) = filter(&guard.mods_state, handle) { // the filter returned false, we do not forward to client trace!(self.arc.logger, "Input was intercepted by filter"); - return; + return Some(val); } // forward to client if no keybinding is triggered @@ -400,6 +417,8 @@ impl KeyboardHandle { } else { trace!(self.arc.logger, "No client currently focused"); } + + None } /// Set the current focus of this keyboard diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 8e9dd5f..b8279a6 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -40,7 +40,9 @@ mod keyboard; mod pointer; pub use self::{ - keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig}, + keyboard::{ + keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, ModifiersState, XkbConfig, + }, pointer::{ AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, PointerInnerHandle,