diff --git a/CHANGELOG.md b/CHANGELOG.md index 110b268..b853f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - `PointerButtonEvent::button` now returns an `Option`. - `MouseButton` is now non-exhaustive. - Remove `Other` and add `Forward` and `Back` variants to `MouseButton`. Use the new `PointerButtonEvent::button_code` in place of `Other`. +- `GrabStartData` has been renamed to `PointerGrabStartData` #### Backends @@ -49,6 +50,7 @@ - Support for `xdg_wm_base` protocol version 3 - Added the option to initialize the dmabuf global with a client filter - `wayland::output::Output` now has user data attached to it and more functions to query its properties +- Added a `KeyboardGrab` similar to the existing `PointerGrab` #### Backends diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index 51c2d0d..bb0ee55 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -19,7 +19,7 @@ use smithay::{ compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment, SurfaceAttributes, TraversalAction, }, - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, shell::{ legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind}, wlr_layer::{LayerShellRequest, LayerSurfaceAttributes}, @@ -39,7 +39,7 @@ use crate::{ }; struct MoveSurfaceGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, window_map: Rc>, toplevel: SurfaceKind, initial_window_location: Point, @@ -82,7 +82,7 @@ impl PointerGrab for MoveSurfaceGrab { handle.axis(details) } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } @@ -130,7 +130,7 @@ impl From for xdg_toplevel::ResizeEdge { } struct ResizeSurfaceGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, toplevel: SurfaceKind, edges: ResizeEdge, initial_window_size: Size, @@ -280,7 +280,7 @@ impl PointerGrab for ResizeSurfaceGrab { handle.axis(details) } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index d8e02be..c15daff 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -8,7 +8,7 @@ use wayland_server::{ use crate::{ utils::{Logical, Point}, wayland::{ - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, Serial, }, }; @@ -16,7 +16,7 @@ use crate::{ use super::{with_source_metadata, DataDeviceData, SeatData}; pub(crate) struct DnDGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, data_source: Option, current_focus: Option, pending_offers: Vec, @@ -29,7 +29,7 @@ pub(crate) struct DnDGrab { impl DnDGrab { pub(crate) fn new( - start_data: GrabStartData, + start_data: PointerGrabStartData, source: Option, origin: wl_surface::WlSurface, seat: Seat, @@ -222,7 +222,7 @@ impl PointerGrab for DnDGrab { handle.axis(details); } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 125a840..d74d504 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -60,7 +60,7 @@ use slog::{debug, error, o}; use crate::wayland::{ compositor, - seat::{GrabStartData, Seat}, + seat::{PointerGrabStartData, Seat}, Serial, }; @@ -335,7 +335,7 @@ pub fn set_data_device_selection(seat: &Seat, mime_types: Vec) { pub fn start_dnd( seat: &Seat, serial: Serial, - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: SourceMetadata, callback: C, ) where diff --git a/src/wayland/data_device/server_dnd_grab.rs b/src/wayland/data_device/server_dnd_grab.rs index a1a554b..2f32c5d 100644 --- a/src/wayland/data_device/server_dnd_grab.rs +++ b/src/wayland/data_device/server_dnd_grab.rs @@ -8,7 +8,7 @@ use wayland_server::{ use crate::{ utils::{Logical, Point}, wayland::{ - seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, PointerGrab, PointerGrabStartData, PointerInnerHandle, Seat}, Serial, }, }; @@ -42,7 +42,7 @@ pub enum ServerDndEvent { } pub(crate) struct ServerDnDGrab { - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: super::SourceMetadata, current_focus: Option, pending_offers: Vec, @@ -53,7 +53,7 @@ pub(crate) struct ServerDnDGrab { impl ServerDnDGrab { pub(crate) fn new( - start_data: GrabStartData, + start_data: PointerGrabStartData, metadata: super::SourceMetadata, seat: Seat, callback: Rc>, @@ -222,7 +222,7 @@ where handle.axis(details); } - fn start_data(&self) -> &GrabStartData { + fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index 0872ce8..f64c1b1 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -86,9 +86,16 @@ pub struct XkbConfig<'a> { pub options: Option, } +enum GrabStatus { + None, + Active(Serial, Box), + Borrowed, +} + struct KbdInternal { known_kbds: Vec, focus: Option, + pending_focus: Option, pressed_keys: Vec, mods_state: ModifiersState, keymap: xkb::Keymap, @@ -96,6 +103,7 @@ struct KbdInternal { repeat_rate: i32, repeat_delay: i32, focus_hook: Box)>, + grab: GrabStatus, } // focus_hook does not implement debug, so we have to impl Debug manually @@ -147,6 +155,7 @@ impl KbdInternal { Ok(KbdInternal { known_kbds: Vec::new(), focus: None, + pending_focus: None, pressed_keys: Vec::new(), mods_state: ModifiersState::default(), keymap, @@ -154,6 +163,7 @@ impl KbdInternal { repeat_rate, repeat_delay, focus_hook, + grab: GrabStatus::None, }) } @@ -215,6 +225,35 @@ impl KbdInternal { } } } + + fn with_grab(&mut self, f: F, logger: ::slog::Logger) + where + F: FnOnce(KeyboardInnerHandle<'_>, &mut dyn KeyboardGrab), + { + let mut grab = ::std::mem::replace(&mut self.grab, GrabStatus::Borrowed); + match grab { + GrabStatus::Borrowed => panic!("Accessed a keyboard grab from within a keyboard grab access."), + GrabStatus::Active(_, ref mut handler) => { + // If this grab is associated with a surface that is no longer alive, discard it + if let Some(ref surface) = handler.start_data().focus { + if !surface.as_ref().is_alive() { + self.grab = GrabStatus::None; + f(KeyboardInnerHandle { inner: self, logger }, &mut DefaultGrab); + return; + } + } + f(KeyboardInnerHandle { inner: self, logger }, &mut **handler); + } + GrabStatus::None => { + f(KeyboardInnerHandle { inner: self, logger }, &mut DefaultGrab); + } + } + + if let GrabStatus::Borrowed = self.grab { + // the grab has not been ended nor replaced, put it back in place + self.grab = grab; + } + } } /// Errors that can be encountered when creating a keyboard handler @@ -320,6 +359,47 @@ pub enum FilterResult { Intercept(T), } +/// Data about the event that started the grab. +#[derive(Debug, Clone)] +pub struct GrabStartData { + /// The focused surface, if any, at the start of the grab. + pub focus: Option, +} + +/// A trait to implement a keyboard grab +/// +/// In some context, it is necessary to temporarily change the behavior of the keyboard. This is +/// typically known as a keyboard grab. A example would be, during a popup grab the keyboard focus +/// will not be changed and stay on the grabbed popup. +/// +/// This trait is the interface to intercept regular keyboard events and change them as needed, its +/// interface mimics the [`KeyboardHandle`] interface. +/// +/// If your logic decides that the grab should end, both [`KeyboardInnerHandle`] and [`KeyboardHandle`] have +/// a method to change it. +/// +/// When your grab ends (either as you requested it or if it was forcefully cancelled by the server), +/// the struct implementing this trait will be dropped. As such you should put clean-up logic in the destructor, +/// rather than trying to guess when the grab will end. +pub trait KeyboardGrab { + /// An input was reported + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ); + + /// A focus change was requested + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial); + + /// The data about the event that started the grab. + fn start_data(&self) -> &GrabStartData; +} + /// An handle to a keyboard handler /// /// It can be cloned and all clones manipulate the same internal state. @@ -337,6 +417,42 @@ pub struct KeyboardHandle { } impl KeyboardHandle { + /// Change the current grab on this keyboard to the provided grab + /// + /// Overwrites any current grab. + pub fn set_grab(&self, grab: G, serial: Serial) { + self.arc.internal.borrow_mut().grab = GrabStatus::Active(serial, Box::new(grab)); + } + + /// Remove any current grab on this keyboard, resetting it to the default behavior + pub fn unset_grab(&self) { + self.arc.internal.borrow_mut().grab = GrabStatus::None; + } + + /// Check if this keyboard is currently grabbed with this serial + pub fn has_grab(&self, serial: Serial) -> bool { + let guard = self.arc.internal.borrow_mut(); + match guard.grab { + GrabStatus::Active(s, _) => s == serial, + _ => false, + } + } + + /// Check if this keyboard is currently being grabbed + pub fn is_grabbed(&self) -> bool { + let guard = self.arc.internal.borrow_mut(); + !matches!(guard.grab, GrabStatus::None) + } + + /// Returns the start data for the grab, if any. + pub fn grab_start_data(&self) -> Option { + let guard = self.arc.internal.borrow(); + match &guard.grab { + GrabStatus::Active(_, g) => Some(g.start_data().clone()), + _ => None, + } + } + /// Handle a keystroke /// /// All keystrokes from the input backend should be fed _in order_ to this method of the @@ -392,14 +508,12 @@ impl KeyboardHandle { KeyState::Pressed => WlKeyState::Pressed, KeyState::Released => WlKeyState::Released, }; - guard.with_focused_kbds(|kbd, _| { - // key event must be sent before modifers event for libxkbcommon - // to process them correctly - kbd.key(serial.into(), time, keycode, wl_state); - if let Some((dep, la, lo, gr)) = modifiers { - kbd.modifiers(serial.into(), dep, la, lo, gr); - } - }); + guard.with_grab( + move |mut handle, grab| { + grab.input(&mut handle, keycode, wl_state, modifiers, serial, time); + }, + self.arc.logger.clone(), + ); if guard.focus.is_some() { trace!(self.arc.logger, "Input forwarded to client"); } else { @@ -417,44 +531,13 @@ impl KeyboardHandle { /// a [`wl_keyboard::Event::Enter`](wayland_server::protocol::wl_keyboard::Event::Enter) event will be sent. pub fn set_focus(&self, focus: Option<&WlSurface>, serial: Serial) { let mut guard = self.arc.internal.borrow_mut(); - - let same = guard - .focus - .as_ref() - .and_then(|f| focus.map(|s| s.as_ref().equals(f.as_ref()))) - .unwrap_or(false); - - if !same { - // unset old focus - guard.with_focused_kbds(|kbd, s| { - kbd.leave(serial.into(), s); - }); - - // set new focus - guard.focus = focus.cloned(); - let (dep, la, lo, gr) = guard.serialize_modifiers(); - let keys = guard.serialize_pressed_keys(); - guard.with_focused_kbds(|kbd, surface| { - kbd.enter(serial.into(), surface, keys.clone()); - // Modifiers must be send after enter event. - kbd.modifiers(serial.into(), dep, la, lo, gr); - }); - { - let KbdInternal { - ref focus, - ref mut focus_hook, - .. - } = *guard; - focus_hook(focus.as_ref()); - } - if guard.focus.is_some() { - trace!(self.arc.logger, "Focus set to new surface"); - } else { - trace!(self.arc.logger, "Focus unset"); - } - } else { - trace!(self.arc.logger, "Focus unchanged"); - } + guard.pending_focus = focus.cloned(); + guard.with_grab( + move |mut handle, grab| { + grab.set_focus(&mut handle, focus, serial); + }, + self.arc.logger.clone(), + ); } /// Check if given client currently has keyboard focus @@ -543,3 +626,129 @@ pub(crate) fn implement_keyboard(keyboard: Main, handle: Option<&Key keyboard.deref().clone() } + +/// This inner handle is accessed from inside a keyboard grab logic, and directly +/// sends event to the client +#[derive(Debug)] +pub struct KeyboardInnerHandle<'a> { + inner: &'a mut KbdInternal, + logger: ::slog::Logger, +} + +impl<'a> KeyboardInnerHandle<'a> { + /// Change the current grab on this keyboard to the provided grab + /// + /// Overwrites any current grab. + pub fn set_grab(&mut self, serial: Serial, grab: G) { + self.inner.grab = GrabStatus::Active(serial, Box::new(grab)); + } + + /// Remove any current grab on this keyboard, resetting it to the default behavior + /// + /// This will also restore the focus of the underlying keyboard if restore_focus + /// is [`true`] + pub fn unset_grab(&mut self, serial: Serial, restore_focus: bool) { + self.inner.grab = GrabStatus::None; + // restore the focus + if restore_focus { + let focus = self.inner.pending_focus.clone(); + self.set_focus(focus.as_ref(), serial); + } + } + + /// Access the current focus of this keyboard + pub fn current_focus(&self) -> Option<&WlSurface> { + self.inner.focus.as_ref() + } + + /// Send the input to the focused keyboards + pub fn input( + &mut self, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ) { + self.inner.with_focused_kbds(|kbd, _| { + // key event must be sent before modifers event for libxkbcommon + // to process them correctly + kbd.key(serial.into(), time, keycode, key_state); + if let Some((dep, la, lo, gr)) = modifiers { + kbd.modifiers(serial.into(), dep, la, lo, gr); + } + }); + } + + /// Set the current focus of this keyboard + /// + /// If the new focus is different from the previous one, any previous focus + /// will be sent a [`wl_keyboard::Event::Leave`](wayland_server::protocol::wl_keyboard::Event::Leave) + /// event, and if the new focus is not `None`, + /// a [`wl_keyboard::Event::Enter`](wayland_server::protocol::wl_keyboard::Event::Enter) event will be sent. + pub fn set_focus(&mut self, focus: Option<&WlSurface>, serial: Serial) { + let same = self + .inner + .focus + .as_ref() + .and_then(|f| focus.map(|s| s.as_ref().equals(f.as_ref()))) + .unwrap_or(false); + + if !same { + // unset old focus + self.inner.with_focused_kbds(|kbd, s| { + kbd.leave(serial.into(), s); + }); + + // set new focus + self.inner.focus = focus.cloned(); + let (dep, la, lo, gr) = self.inner.serialize_modifiers(); + let keys = self.inner.serialize_pressed_keys(); + self.inner.with_focused_kbds(|kbd, surface| { + kbd.enter(serial.into(), surface, keys.clone()); + // Modifiers must be send after enter event. + kbd.modifiers(serial.into(), dep, la, lo, gr); + }); + { + let KbdInternal { + ref focus, + ref mut focus_hook, + .. + } = *self.inner; + focus_hook(focus.as_ref()); + } + if self.inner.focus.is_some() { + trace!(self.logger, "Focus set to new surface"); + } else { + trace!(self.logger, "Focus unset"); + } + } else { + trace!(self.logger, "Focus unchanged"); + } + } +} + +// The default grab, the behavior when no particular grab is in progress +struct DefaultGrab; + +impl KeyboardGrab for DefaultGrab { + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: WlKeyState, + modifiers: Option<(u32, u32, u32, u32)>, + serial: Serial, + time: u32, + ) { + handle.input(keycode, key_state, modifiers, serial, time) + } + + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial) { + handle.set_focus(focus, serial) + } + + fn start_data(&self) -> &GrabStartData { + unreachable!() + } +} diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 7055106..754dad5 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -41,12 +41,12 @@ mod pointer; pub use self::{ keyboard::{ - keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, KeysymHandle, ModifiersState, - XkbConfig, + keysyms, Error as KeyboardError, FilterResult, GrabStartData as KeyboardGrabStartData, KeyboardGrab, + KeyboardHandle, KeyboardInnerHandle, Keysym, KeysymHandle, ModifiersState, XkbConfig, }, pointer::{ - AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, - PointerInnerHandle, + AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData as PointerGrabStartData, + PointerGrab, PointerHandle, PointerInnerHandle, }, };