diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2e085..b6b413c 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` and returns a `FilterResult`. ### Additions diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index e2f06d2..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,10 +42,11 @@ 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, keysym| { + .input(keycode, state, serial, time, |modifiers, handle| { + let keysym = handle.modified_sym(); + debug!(log, "keysym"; "state" => format!("{:?}", state), "mods" => format!("{:?}", modifiers), @@ -58,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) { @@ -155,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); @@ -243,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); @@ -520,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 99fc320..e5a1d57 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -282,6 +282,56 @@ 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 + } +} + +/// 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. @@ -306,32 +356,42 @@ 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, Keysym) -> 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(); - - // 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 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 @@ -357,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,