🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
use std::{
fmt,
sync::{Arc, Mutex},
};
use wayland_server::{protocol::wl_surface::WlSurface, Resource};
use crate::{
backend::input::{ButtonState, KeyState, Keycode},
input::{
keyboard::{
GrabStartData as KeyboardGrabStartData, KeyboardGrab, KeyboardHandle, KeyboardInnerHandle,
ModifiersState,
},
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle, RelativeMotionEvent,
},
SeatHandler,
},
utils::{DeadResource, IsAlive, Logical, Point, Serial, SERIAL_COUNTER},
wayland::seat::WaylandFocus,
};
use thiserror::Error;
use super::{PopupKind, PopupManager};
/// Defines the possible errors that
/// can be returned from [`PopupManager::grab_popup`]
#[derive(Debug, Error)]
pub enum PopupGrabError {
/// This resource has been destroyed and can no longer be used.
#[error(transparent)]
DeadResource(#[from] DeadResource),
/// The client tried to grab a popup after it's parent has been dismissed
#[error("the parent of the popup has been already dismissed")]
ParentDismissed,
/// The client tried to grab a popup after it has been mapped
#[error("tried to grab after being mapped")]
InvalidGrab,
/// The client tried to grab a popup which is not the topmost
#[error("popup was not created on the topmost popup")]
NotTheTopmostPopup,
}
/// Defines the possibly strategies
/// for the [`PopupGrab::ungrab`] operation
#[derive(Debug, Copy, Clone)]
pub enum PopupUngrabStrategy {
/// Only ungrab the topmost popup
Topmost,
/// Ungrab all active popups
All,
}
#[derive(Debug, Default)]
struct PopupGrabInternal {
serial: Option<Serial>,
active_grabs: Vec<PopupKind>,
dismissed_grabs: Vec<PopupKind>,
}
impl PopupGrabInternal {
fn active(&self) -> bool {
!self.active_grabs.is_empty() || !self.dismissed_grabs.is_empty()
}
fn current_grab(&self) -> Option<&WlSurface> {
self.active_grabs
.iter()
.rev()
.find(|p| p.alive())
.map(|p| p.wl_surface())
}
fn is_dismissed(&self, surface: &WlSurface) -> bool {
self.dismissed_grabs.iter().any(|p| p.wl_surface() == surface)
}
fn append_grab(&mut self, popup: &PopupKind) {
self.active_grabs.push(popup.clone());
}
fn cleanup(&mut self) {
let mut i = 0;
while i < self.active_grabs.len() {
if !self.active_grabs[i].alive() {
let grab = self.active_grabs.remove(i);
self.dismissed_grabs.push(grab);
} else {
i += 1;
}
}
self.dismissed_grabs.retain(|p| p.alive());
}
}
#[derive(Debug, Clone, Default)]
pub(super) struct PopupGrabInner {
internal: Arc<Mutex<PopupGrabInternal>>,
}
impl PopupGrabInner {
pub(super) fn active(&self) -> bool {
let guard = self.internal.lock().unwrap();
guard.active()
}
fn current_grab(&self) -> Option<PopupKind> {
let guard = self.internal.lock().unwrap();
guard.active_grabs.iter().rev().find(|p| p.alive()).cloned()
}
pub(super) fn cleanup(&self) {
let mut guard = self.internal.lock().unwrap();
guard.cleanup();
}
pub(super) fn grab(&self, popup: &PopupKind, serial: Serial) -> Result<Option<Serial>, PopupGrabError> {
let parent = popup.parent().ok_or(DeadResource)?;
self.cleanup();
let mut guard = self.internal.lock().unwrap();
if let Some(grab) = guard.current_grab() {
if grab != &parent {
// If the parent is a grabbing popup which has already been dismissed, this popup will be immediately dismissed.
if guard.is_dismissed(&parent) {
return Err(PopupGrabError::ParentDismissed);
}
// If the parent is a popup that did not take an explicit grab, an error will be raised.
return Err(PopupGrabError::NotTheTopmostPopup);
}
}
guard.append_grab(popup);
Ok(guard.serial.replace(serial))
}
fn ungrab(&self, root: &WlSurface, strategy: PopupUngrabStrategy) -> Option<WlSurface> {
let mut guard = self.internal.lock().unwrap();
let dismissed = match strategy {
PopupUngrabStrategy::Topmost => {
if let Some(grab) = guard.active_grabs.pop() {
let dismissed = PopupManager::dismiss_popup(root, &grab);
if dismissed.is_ok() {
guard.dismissed_grabs.push(grab);
}
dismissed
} else {
Ok(())
}
}
PopupUngrabStrategy::All => {
let grabs = guard.active_grabs.drain(..).collect::<Vec<_>>();
if let Some(grab) = grabs.first() {
let dismissed = PopupManager::dismiss_popup(root, grab);
if dismissed.is_ok() {
guard.dismissed_grabs.push(grab.clone());
guard.dismissed_grabs.extend(grabs);
}
dismissed
} else {
Ok(())
}
}
};
if dismissed.is_err() {
// If dismiss_popup returns Err(DeadResource) there is not much what
// can do about it here, we just remove all our grabs as they are dead now
// anyway. The pointer/keyboard grab will be unset automatically so we
// should be fine.
guard.active_grabs.drain(..);
}
guard.current_grab().cloned()
}
}
/// Represents the explicit grab a client requested for a popup
///
/// An explicit grab can be used by a client to redirect all keyboard
/// input to a single popup. The focus of the keyboard will stay on
/// the popup for as long as the grab is valid, that is as long as the
/// compositor did not call [`ungrab`](PopupGrab::ungrab) or the client
/// did not destroy the popup. A grab can be nested by requesting a grab
/// on a popup who's parent is the currently grabbed popup. The grab will
/// be returned to the parent after the popup has been dismissed.
///
/// This module also provides default implementations for [`KeyboardGrab`] and
/// [`PointerGrab`] that implement the behavior described in the [`xdg-shell`](https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab)
/// specification. See [`PopupKeyboardGrab`] and [`PopupPointerGrab`] for more
/// information on the default implementations.
///
/// In case the implemented behavior is not suited for your use-case the grab can be
/// either decorated or a custom [`KeyboardGrab`]/[`PointerGrab`] can use the methods
/// on the [`PopupGrab`] to implement a custom behavior.
///
/// One example would be to use a timer to automatically dismiss the popup after some
/// timeout.
///
/// The grab is obtained by calling [`PopupManager::grap_popup`](super::PopupManager::grab_popup).
pub struct PopupGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
root: <D as SeatHandler>::KeyboardFocus,
serial: Serial,
previous_serial: Option<Serial>,
toplevel_grab: PopupGrabInner,
keyboard_handle: Option<KeyboardHandle<D>>,
keyboard_grab_start_data: KeyboardGrabStartData<D>,
pointer_grab_start_data: PointerGrabStartData<D>,
}
impl<D> fmt::Debug for PopupGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PopupGrab")
.field("root", &self.root)
.field("serial", &self.serial)
.field("previous_serial", &self.previous_serial)
.field("keyboard_handle", &self.keyboard_handle)
.field("keyboard_grab_start_data", &self.keyboard_grab_start_data)
.field("pointer_grab_start_data", &self.pointer_grab_start_data)
.finish()
}
}
impl<D> Clone for PopupGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn clone(&self) -> Self {
PopupGrab {
root: self.root.clone(),
serial: self.serial,
previous_serial: self.previous_serial,
toplevel_grab: self.toplevel_grab.clone(),
keyboard_handle: self.keyboard_handle.clone(),
keyboard_grab_start_data: self.keyboard_grab_start_data.clone(),
pointer_grab_start_data: self.pointer_grab_start_data.clone(),
}
}
}
impl<D> PopupGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus + From<PopupKind>,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
pub(super) fn new(
toplevel_popups: PopupGrabInner,
root: <D as SeatHandler>::KeyboardFocus,
serial: Serial,
previous_serial: Option<Serial>,
keyboard_handle: Option<KeyboardHandle<D>>,
) -> Self {
PopupGrab {
root: root.clone(),
serial,
previous_serial,
toplevel_grab: toplevel_popups,
keyboard_handle,
keyboard_grab_start_data: KeyboardGrabStartData {
// We set the focus to root as this will make
// sure the grab will stay alive until the
// toplevel is destroyed or the grab is unset
focus: Some(root.clone()),
},
pointer_grab_start_data: PointerGrabStartData {
button: 0,
// We set the focus to root as this will make
// sure the grab will stay alive until the
// toplevel is destroyed or the grab is unset
focus: Some((root.into(), (0f64, 0f64).into())),
location: (0f64, 0f64).into(),
},
}
}
/// Returns the serial that was used to grab the popup
pub fn serial(&self) -> Serial {
self.serial
}
/// Returns the previous serial that was used to grab
/// the parent popup in case of nested grabs
pub fn previous_serial(&self) -> Option<Serial> {
self.previous_serial
}
/// Check if this grab has ended
///
/// A grab has ended if either all popups
/// associated with the grab have been dismissed
/// by the server with [`PopupGrab::ungrab`] or by the client
/// by destroying the popup.
///
/// This will also return [`false`] if the root
/// of the grab has been destroyed.
pub fn has_ended(&self) -> bool {
!self.root.alive() || !self.toplevel_grab.active()
}
/// Returns the current grabbed [`WlSurface`].
///
/// If the grab has ended this will return the root surface
/// so that the client expected focus can be restored
pub fn current_grab(&self) -> Option<<D as SeatHandler>::KeyboardFocus> {
self.toplevel_grab
.current_grab()
.map(From::from)
.or_else(|| Some(self.root.clone()))
}
/// Ungrab and dismiss a popup
///
/// This will dismiss either the topmost or all popups
/// according to the specified [`PopupUngrabStrategy`]
///
/// Returns the new topmost popup in case of nested popups
/// or if the grab has ended the root surface
pub fn ungrab(&mut self, strategy: PopupUngrabStrategy) -> Option<WlSurface> {
let root_surface = self.root.wl_surface()?;
self.toplevel_grab
.ungrab(&root_surface, strategy)
.or(Some(root_surface.into_owned()))
}
/// Convenience method for getting a [`KeyboardGrabStartData`] for this grab.
///
/// The focus of the [`KeyboardGrabStartData`] will always be the root
/// of the popup grab, e.g. the surface of the toplevel, to make sure
/// the grab is not automatically unset.
pub fn keyboard_grab_start_data(&self) -> &KeyboardGrabStartData<D> {
&self.keyboard_grab_start_data
}
/// Convenience method for getting a [`PointerGrabStartData`] for this grab.
///
/// The focus of the [`PointerGrabStartData`] will always be the root
/// of the popup grab, e.g. the surface of the toplevel, to make sure
/// the grab is not automatically unset.
pub fn pointer_grab_start_data(&self) -> &PointerGrabStartData<D> {
&self.pointer_grab_start_data
}
fn unset_keyboard_grab(&self, data: &mut D, serial: Serial) {
if let Some(keyboard) = self.keyboard_handle.as_ref() {
if keyboard.is_grabbed()
&& (keyboard.has_grab(self.serial)
|| keyboard.has_grab(self.previous_serial.unwrap_or(self.serial)))
{
keyboard.unset_grab(data);
keyboard.set_focus(data, Some(self.root.clone()), serial);
}
}
}
}
/// Default implementation of a [`KeyboardGrab`] for [`PopupGrab`]
///
/// The [`PopupKeyboardGrab`] will keep the focus of the keyboard
/// on the topmost popup until the grab has ended. If the
/// grab has ended it will restore the focus on the root of the grab
/// and unset the [`KeyboardGrab`]
pub struct PopupKeyboardGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
popup_grab: PopupGrab<D>,
}
impl<D> fmt::Debug for PopupKeyboardGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PopupKeyboardGrab")
.field("popup_grab", &self.popup_grab)
.finish()
}
}
impl<D> PopupKeyboardGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
/// Create a [`PopupKeyboardGrab`] for the provided [`PopupGrab`]
pub fn new(popup_grab: &PopupGrab<D>) -> Self {
PopupKeyboardGrab {
popup_grab: popup_grab.clone(),
}
}
}
impl<D> KeyboardGrab<D> for PopupKeyboardGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus + From<PopupKind>,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn input(
&mut self,
data: &mut D,
handle: &mut KeyboardInnerHandle<'_, D>,
keycode: Keycode,
state: KeyState,
modifiers: Option<ModifiersState>,
serial: Serial,
time: u32,
) {
// Check if the grab changed and update the focus
// If the grab has ended this will return the root
// surface to restore the client expected focus.
if let Some(focus) = self.popup_grab.current_grab() {
handle.set_focus(data, Some(focus), serial);
}
if self.popup_grab.has_ended() {
handle.unset_grab(self, data, serial, false);
}
handle.input(data, keycode, state, modifiers, serial, time)
}
fn set_focus(
&mut self,
data: &mut D,
handle: &mut KeyboardInnerHandle<'_, D>,
focus: Option<<D as SeatHandler>::KeyboardFocus>,
serial: Serial,
) {
// Ignore focus changes unless the grab has ended
if self.popup_grab.has_ended() {
handle.set_focus(data, focus, serial);
handle.unset_grab(self, data, serial, false);
return;
}
// Allow to set the focus to the current grab, this can
// happen if the user initially sets the focus to
// popup instead of relying on the grab behavior
if self.popup_grab.current_grab() == focus {
handle.set_focus(data, focus, serial);
}
}
fn start_data(&self) -> &KeyboardGrabStartData<D> {
self.popup_grab.keyboard_grab_start_data()
}
fn unset(&mut self, _data: &mut D) {}
}
/// Default implementation of a [`PointerGrab`] for [`PopupGrab`]
///
/// The [`PopupPointerGrab`] will make sure that the pointer focus
/// stays on the same client as the grabbed popup (similar to an
/// "owner-events" grab in X11 parlance). If an input event happens
/// outside of the grabbed [`WlSurface`] the popup will be dismissed
/// and the grab ends. In case of a nested grab all parent grabs will
/// also be dismissed.
///
/// If the grab has ended the pointer focus is restored and the
/// [`PointerGrab`] is unset. Additional it will unset an active
/// [`KeyboardGrab`] that matches the [`Serial`] of this grab and
/// restore the keyboard focus like described in [`PopupKeyboardGrab`]
pub struct PopupPointerGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
popup_grab: PopupGrab<D>,
}
impl<D> fmt::Debug for PopupPointerGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PopupPointerGrab")
.field("popup_grab", &self.popup_grab)
.finish()
}
}
impl<D> PopupPointerGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
/// Create a [`PopupPointerGrab`] for the provided [`PopupGrab`]
pub fn new(popup_grab: &PopupGrab<D>) -> Self {
PopupPointerGrab {
popup_grab: popup_grab.clone(),
}
}
}
impl<D> PointerGrab<D> for PopupPointerGrab<D>
where
D: SeatHandler + 'static,
<D as SeatHandler>::KeyboardFocus: WaylandFocus + From<PopupKind>,
<D as SeatHandler>::PointerFocus: From<<D as SeatHandler>::KeyboardFocus> + WaylandFocus,
{
fn motion(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
focus: Option<(<D as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
event: &MotionEvent,
) {
if self.popup_grab.has_ended() {
handle.unset_grab(self, data, event.serial, event.time, true);
return;
}
// Check that the focus is of the same client as the grab
// If yes allow it, if not unset the focus.
if focus
.as_ref()
.and_then(|f1| {
self.popup_grab
.current_grab()
.as_ref()
.and_then(|f2| f2.wl_surface())
.map(|s| f1.0.same_client_as(&s.id()))
})
.unwrap_or(false)
{
handle.motion(data, focus, event);
} else {
handle.motion(data, None, event);
}
}
fn relative_motion(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
focus: Option<(<D as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
event: &RelativeMotionEvent,
) {
handle.relative_motion(data, focus, event);
}
fn button(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, event: &ButtonEvent) {
let serial = event.serial;
let time = event.time;
let state = event.state;
if self.popup_grab.has_ended() {
handle.unset_grab(self, data, serial, time, true);
handle.button(data, event);
return;
}
// Check if the the client of the focused surface is still equal to the grabbed surface client
// if not the popup will be dismissed
if state == ButtonState::Pressed
&& !handle
.current_focus()
.and_then(|f| {
self.popup_grab
.current_grab()
.and_then(|f2| f.0.wl_surface().map(|s| f2.same_client_as(&s.id())))
})
.unwrap_or(false)
{
let _ = self.popup_grab.ungrab(PopupUngrabStrategy::All);
handle.unset_grab(self, data, serial, time, true);
handle.button(data, event);
return;
}
handle.button(data, event);
}
fn axis(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>, details: AxisFrame) {
handle.axis(data, details);
}
fn frame(&mut self, data: &mut D, handle: &mut PointerInnerHandle<'_, D>) {
handle.frame(data);
}
fn gesture_swipe_begin(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GestureSwipeBeginEvent,
) {
handle.gesture_swipe_begin(data, event);
}
fn gesture_swipe_update(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GestureSwipeUpdateEvent,
) {
handle.gesture_swipe_update(data, event);
}
fn gesture_swipe_end(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GestureSwipeEndEvent,
) {
handle.gesture_swipe_end(data, event);
}
fn gesture_pinch_begin(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GesturePinchBeginEvent,
) {
handle.gesture_pinch_begin(data, event);
}
fn gesture_pinch_update(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GesturePinchUpdateEvent,
) {
handle.gesture_pinch_update(data, event);
}
fn gesture_pinch_end(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GesturePinchEndEvent,
) {
handle.gesture_pinch_end(data, event);
}
fn gesture_hold_begin(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GestureHoldBeginEvent,
) {
handle.gesture_hold_begin(data, event);
}
fn gesture_hold_end(
&mut self,
data: &mut D,
handle: &mut PointerInnerHandle<'_, D>,
event: &GestureHoldEndEvent,
) {
handle.gesture_hold_end(data, event);
}
fn start_data(&self) -> &PointerGrabStartData<D> {
self.popup_grab.pointer_grab_start_data()
}
fn unset(&mut self, data: &mut D) {
let serial = SERIAL_COUNTER.next_serial();
self.popup_grab.unset_keyboard_grab(data, serial);
}
}