diff --git a/src/desktop/popup/grab.rs b/src/desktop/popup/grab.rs new file mode 100644 index 0000000..25f3c65 --- /dev/null +++ b/src/desktop/popup/grab.rs @@ -0,0 +1,501 @@ +use std::sync::{Arc, Mutex}; + +use wayland_server::protocol::{wl_keyboard::KeyState, wl_pointer::ButtonState, wl_surface::WlSurface}; + +use crate::{ + utils::{DeadResource, Logical, Point}, + wayland::{ + compositor::get_role, + seat::{ + AxisFrame, KeyboardGrab, KeyboardGrabStartData, KeyboardHandle, KeyboardInnerHandle, PointerGrab, + PointerGrabStartData, PointerInnerHandle, + }, + shell::xdg::XDG_POPUP_ROLE, + Serial, + }, +}; + +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, + active_grabs: Vec<(WlSurface, PopupKind)>, + dismissed_grabs: Vec<(WlSurface, PopupKind)>, +} + +impl PopupGrabInternal { + fn alive(&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(|(s, _)| s) + } + + fn is_dismissed(&self, surface: &WlSurface) -> bool { + self.dismissed_grabs.iter().any(|(s, _)| s == surface) + } + + fn append_grab(&mut self, popup: &PopupKind) -> Result<(), DeadResource> { + let surface = popup.get_surface().ok_or(DeadResource)?; + self.active_grabs.push((surface.clone(), popup.clone())); + Ok(()) + } + + fn cleanup(&mut self) { + let mut i = 0; + while i < self.active_grabs.len() { + if !self.active_grabs[i].1.alive() { + let grab = self.active_grabs.remove(i); + self.dismissed_grabs.push(grab); + } else { + i += 1; + } + } + + self.dismissed_grabs.retain(|(s, _)| s.as_ref().is_alive()); + } +} + +#[derive(Debug, Clone, Default)] +pub(super) struct PopupGrabInner { + internal: Arc>, +} + +impl PopupGrabInner { + pub(super) fn alive(&self) -> bool { + let guard = self.internal.lock().unwrap(); + guard.alive() + } + + fn current_grab(&self) -> Option { + let guard = self.internal.lock().unwrap(); + guard + .active_grabs + .iter() + .rev() + .find(|(_, p)| p.alive()) + .map(|(s, _)| s) + .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, PopupGrabError> { + let parent = popup.parent().ok_or(DeadResource)?; + let parent_role = get_role(&parent); + + self.cleanup(); + + let mut guard = self.internal.lock().unwrap(); + + match guard.current_grab() { + Some(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); + } + } + None => { + if parent_role == Some(XDG_POPUP_ROLE) { + return Err(PopupGrabError::NotTheTopmostPopup); + } + } + } + + guard.append_grab(popup)?; + + Ok(guard.serial.replace(serial)) + } + + fn ungrab(&self, root: &WlSurface, strategy: PopupUngrabStrategy) -> Option { + 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.1); + + if dismissed.is_ok() { + guard.dismissed_grabs.push(grab); + } + + dismissed + } else { + Ok(()) + } + } + PopupUngrabStrategy::All => { + let grabs = guard.active_grabs.drain(..).collect::>(); + + if let Some(grab) = grabs.first() { + let dismissed = PopupManager::dismiss_popup(root, &grab.1); + + 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). +#[derive(Debug, Clone)] +pub struct PopupGrab { + root: WlSurface, + serial: Serial, + previous_serial: Option, + toplevel_grab: PopupGrabInner, + keyboard_handle: Option, + keyboard_grab_start_data: KeyboardGrabStartData, + pointer_grab_start_data: PointerGrabStartData, +} + +impl PopupGrab { + pub(super) fn new( + toplevel_popups: PopupGrabInner, + root: WlSurface, + serial: Serial, + previous_serial: Option, + keyboard_handle: Option, + ) -> 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, (0, 0).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 { + 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.as_ref().is_alive() || !self.toplevel_grab.alive() + } + + /// 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 { + self.toplevel_grab + .current_grab() + .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 { + self.toplevel_grab + .ungrab(&self.root, strategy) + .or_else(|| Some(self.root.clone())) + } + + /// 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 { + &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 { + &self.pointer_grab_start_data + } + + fn unset_keyboard_grab(&self, 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(); + keyboard.set_focus(Some(&self.root), 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`] +#[derive(Debug)] +pub struct PopupKeyboardGrab { + popup_grab: PopupGrab, +} + +impl PopupKeyboardGrab { + /// Create a [`PopupKeyboardGrab`] for the provided [`PopupGrab`] + pub fn new(popup_grab: &PopupGrab) -> Self { + PopupKeyboardGrab { + popup_grab: popup_grab.clone(), + } + } +} + +impl KeyboardGrab for PopupKeyboardGrab { + fn input( + &mut self, + handle: &mut KeyboardInnerHandle<'_>, + keycode: u32, + key_state: KeyState, + modifiers: Option<(u32, u32, u32, u32)>, + 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(surface) = self.popup_grab.current_grab() { + handle.set_focus(Some(&surface), serial); + } + + if self.popup_grab.has_ended() { + handle.unset_grab(serial, false); + } + + handle.input(keycode, key_state, modifiers, serial, time) + } + + fn set_focus(&mut self, handle: &mut KeyboardInnerHandle<'_>, focus: Option<&WlSurface>, serial: Serial) { + // Ignore focus changes unless the grab has ended + if self.popup_grab.has_ended() { + handle.set_focus(focus, serial); + handle.unset_grab(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().as_ref() == focus { + handle.set_focus(focus, serial); + } + } + + fn start_data(&self) -> &KeyboardGrabStartData { + self.popup_grab.keyboard_grab_start_data() + } +} + +/// 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`] +#[derive(Debug)] +pub struct PopupPointerGrab { + popup_grab: PopupGrab, +} + +impl PopupPointerGrab { + /// Create a [`PopupPointerGrab`] for the provided [`PopupGrab`] + pub fn new(popup_grab: &PopupGrab) -> Self { + PopupPointerGrab { + popup_grab: popup_grab.clone(), + } + } +} + +impl PointerGrab for PopupPointerGrab { + fn motion( + &mut self, + handle: &mut PointerInnerHandle<'_>, + location: Point, + focus: Option<(WlSurface, Point)>, + serial: Serial, + time: u32, + ) { + if self.popup_grab.has_ended() { + handle.unset_grab(serial, time); + self.popup_grab.unset_keyboard_grab(serial); + return; + } + + // Check that the focus is of the same client as the grab + // If yes allow it, if not unset the focus. + let same_client = focus.as_ref().map(|(surface, _)| surface.as_ref().client()) + == self + .popup_grab + .current_grab() + .map(|surface| surface.as_ref().client()); + + if same_client { + handle.motion(location, focus, serial, time); + } else { + handle.motion(location, None, serial, time); + } + } + + fn button( + &mut self, + handle: &mut PointerInnerHandle<'_>, + button: u32, + state: ButtonState, + serial: Serial, + time: u32, + ) { + if self.popup_grab.has_ended() { + handle.unset_grab(serial, time); + handle.button(button, state, serial, time); + self.popup_grab.unset_keyboard_grab(serial); + return; + } + + // Check if the the focused surface is still on the current grabbed surface, + // if not the popup will be dismissed + if state == ButtonState::Pressed + && handle.current_focus().map(|(surface, _)| surface) != self.popup_grab.current_grab().as_ref() + { + let _ = self.popup_grab.ungrab(PopupUngrabStrategy::All); + handle.unset_grab(serial, time); + handle.button(button, state, serial, time); + self.popup_grab.unset_keyboard_grab(serial); + } + + handle.button(button, state, serial, time); + } + + fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) { + handle.axis(details); + } + + fn start_data(&self) -> &PointerGrabStartData { + self.popup_grab.pointer_grab_start_data() + } +} diff --git a/src/desktop/popup.rs b/src/desktop/popup/manager.rs similarity index 54% rename from src/desktop/popup.rs rename to src/desktop/popup/manager.rs index 2dd69c5..ab743f2 100644 --- a/src/desktop/popup.rs +++ b/src/desktop/popup/manager.rs @@ -1,18 +1,23 @@ use crate::{ - utils::{DeadResource, Logical, Point, Rectangle}, + utils::{DeadResource, Logical, Point}, wayland::{ compositor::{get_role, with_states}, - shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + seat::Seat, + shell::xdg::{XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + Serial, }, }; use std::sync::{Arc, Mutex}; use wayland_server::protocol::wl_surface::WlSurface; +use super::{PopupGrab, PopupGrabError, PopupGrabInner, PopupKind}; + /// Helper to track popups. #[derive(Debug)] pub struct PopupManager { unmapped_popups: Vec, popup_trees: Vec, + popup_grabs: Vec, logger: ::slog::Logger, } @@ -22,6 +27,7 @@ impl PopupManager { PopupManager { unmapped_popups: Vec::new(), popup_trees: Vec::new(), + popup_grabs: Vec::new(), logger: crate::slog_or_fallback(logger), } } @@ -54,24 +60,89 @@ impl PopupManager { } } - fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { - let mut parent = popup.parent().unwrap(); - while get_role(&parent) == Some(XDG_POPUP_ROLE) { - parent = with_states(&parent, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .parent - .as_ref() - .cloned() - .unwrap() - })?; + /// Take an explicit grab for the provided [`PopupKind`] + /// + /// Returns a [`PopupGrab`] on success or an [`PopupGrabError`] + /// if the grab has been denied. + pub fn grab_popup( + &mut self, + popup: PopupKind, + seat: &Seat, + serial: Serial, + ) -> Result { + let surface = popup.get_surface().ok_or(DeadResource)?; + let root = find_popup_root_surface(&popup)?; + + match popup { + PopupKind::Xdg(ref xdg) => { + let surface = xdg.get_surface().ok_or(DeadResource)?; + let committed = with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .committed + })?; + + if committed { + surface.as_ref().post_error( + wayland_protocols::xdg_shell::server::xdg_popup::Error::InvalidGrab as u32, + "xdg_popup already is mapped".to_string(), + ); + return Err(PopupGrabError::InvalidGrab); + } + } } - with_states(&parent, |states| { + // The primary store for the grab is the seat, additional we store it + // in the popupmanager for active cleanup + seat.user_data().insert_if_missing(PopupGrabInner::default); + let toplevel_popups = seat.user_data().get::().unwrap().clone(); + + // It the popup grab is not alive it is likely + // that it either is new and have never been + // added to the popupmanager or that it has been + // cleaned up. + if !toplevel_popups.alive() { + self.popup_grabs.push(toplevel_popups.clone()); + } + + let previous_serial = match toplevel_popups.grab(&popup, serial) { + Ok(serial) => serial, + Err(err) => { + match err { + PopupGrabError::ParentDismissed => { + let _ = PopupManager::dismiss_popup(&root, &popup); + } + PopupGrabError::NotTheTopmostPopup => { + surface.as_ref().post_error( + wayland_protocols::xdg_shell::server::xdg_wm_base::Error::NotTheTopmostPopup + as u32, + "xdg_popup was not created on the topmost popup".to_string(), + ); + } + _ => {} + } + + return Err(err); + } + }; + + Ok(PopupGrab::new( + toplevel_popups, + root, + serial, + previous_serial, + seat.get_keyboard(), + )) + } + + fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { + let root = find_popup_root_surface(&popup)?; + + with_states(&root, |states| { let tree = PopupTree::default(); if states.data_map.insert_if_missing(|| tree.clone()) { self.popup_trees.push(tree); @@ -81,7 +152,7 @@ impl PopupManager { // if it previously had no popups, we likely removed it from our list already self.popup_trees.push(tree.clone()); } - slog::trace!(self.logger, "Adding popup {:?} to parent {:?}", popup, parent); + slog::trace!(self.logger, "Adding popup {:?} to root {:?}", popup, root); tree.insert(popup); }) } @@ -116,16 +187,47 @@ impl PopupManager { }) } + pub(crate) fn dismiss_popup(surface: &WlSurface, popup: &PopupKind) -> Result<(), DeadResource> { + with_states(surface, |states| { + let tree = states.data_map.get::(); + + if let Some(tree) = tree { + tree.dismiss_popup(popup); + } + }) + } + /// Needs to be called periodically (but not necessarily frequently) /// to cleanup internal resources. pub fn cleanup(&mut self) { // retain_mut is sadly still unstable + self.popup_grabs.iter_mut().for_each(|grabs| grabs.cleanup()); + self.popup_grabs.retain(|grabs| grabs.alive()); self.popup_trees.iter_mut().for_each(|tree| tree.cleanup()); self.popup_trees.retain(|tree| tree.alive()); self.unmapped_popups.retain(|surf| surf.alive()); } } +fn find_popup_root_surface(popup: &PopupKind) -> Result { + let mut parent = popup.parent().ok_or(DeadResource)?; + while get_role(&parent) == Some(XDG_POPUP_ROLE) { + parent = with_states(&parent, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .parent + .as_ref() + .cloned() + .unwrap() + })?; + } + Ok(parent) +} + #[derive(Debug, Default, Clone)] struct PopupTree(Arc>>); @@ -157,6 +259,22 @@ impl PopupTree { children.push(PopupNode::new(popup)); } + fn dismiss_popup(&self, popup: &PopupKind) { + let mut children = self.0.lock().unwrap(); + + let mut i = 0; + while i < children.len() { + let child = &mut children[i]; + + if child.dismiss_popup(popup) { + let _ = children.remove(i); + break; + } else { + i += 1; + } + } + } + fn cleanup(&mut self) { let mut children = self.0.lock().unwrap(); for child in children.iter_mut() { @@ -209,80 +327,48 @@ impl PopupNode { } } + fn send_done(&self) { + for child in self.children.iter().rev() { + child.send_done(); + } + + self.surface.send_done(); + } + + fn dismiss_popup(&mut self, popup: &PopupKind) -> bool { + if self.surface.get_surface() == popup.get_surface() { + self.send_done(); + return true; + } + + let mut i = 0; + while i < self.children.len() { + let child = &mut self.children[i]; + + if child.dismiss_popup(popup) { + let _ = self.children.remove(i); + return false; + } else { + i += 1; + } + } + + false + } + fn cleanup(&mut self) { for child in &mut self.children { child.cleanup(); } + + if !self.surface.alive() && !self.children.is_empty() { + // TODO: The client destroyed a popup before + // destroying all children, this is a protocol + // error. As the surface is no longer alive we + // can not retrieve the client here to send + // the error. + } + self.children.retain(|n| n.surface.alive()); } } - -/// Represents a popup surface -#[derive(Debug, Clone)] -pub enum PopupKind { - /// xdg-shell [`PopupSurface`] - Xdg(PopupSurface), -} - -impl PopupKind { - fn alive(&self) -> bool { - match *self { - PopupKind::Xdg(ref t) => t.alive(), - } - } - - /// Retrieves the underlying [`WlSurface`] - pub fn get_surface(&self) -> Option<&WlSurface> { - match *self { - PopupKind::Xdg(ref t) => t.get_surface(), - } - } - - fn parent(&self) -> Option { - match *self { - PopupKind::Xdg(ref t) => t.get_parent_surface(), - } - } - - /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` - pub fn geometry(&self) -> Rectangle { - let wl_surface = match self.get_surface() { - Some(s) => s, - None => return Rectangle::from_loc_and_size((0, 0), (0, 0)), - }; - - with_states(wl_surface, |states| { - states - .cached_state - .current::() - .geometry - .unwrap_or_default() - }) - .unwrap() - } - - fn location(&self) -> Point { - let wl_surface = match self.get_surface() { - Some(s) => s, - None => return (0, 0).into(), - }; - with_states(wl_surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .current - .geometry - }) - .unwrap_or_default() - .loc - } -} - -impl From for PopupKind { - fn from(p: PopupSurface) -> PopupKind { - PopupKind::Xdg(p) - } -} diff --git a/src/desktop/popup/mod.rs b/src/desktop/popup/mod.rs new file mode 100644 index 0000000..c5ec0b4 --- /dev/null +++ b/src/desktop/popup/mod.rs @@ -0,0 +1,96 @@ +mod grab; +mod manager; + +use std::sync::Mutex; + +pub use grab::*; +pub use manager::*; +use wayland_server::protocol::wl_surface::WlSurface; + +use crate::{ + utils::{Logical, Point, Rectangle}, + wayland::{ + compositor::with_states, + shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes}, + }, +}; + +/// Represents a popup surface +#[derive(Debug, Clone)] +pub enum PopupKind { + /// xdg-shell [`PopupSurface`] + Xdg(PopupSurface), +} + +impl PopupKind { + fn alive(&self) -> bool { + match *self { + PopupKind::Xdg(ref t) => t.alive(), + } + } + + /// Retrieves the underlying [`WlSurface`] + pub fn get_surface(&self) -> Option<&WlSurface> { + match *self { + PopupKind::Xdg(ref t) => t.get_surface(), + } + } + + fn parent(&self) -> Option { + match *self { + PopupKind::Xdg(ref t) => t.get_parent_surface(), + } + } + + /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` + pub fn geometry(&self) -> Rectangle { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return Rectangle::from_loc_and_size((0, 0), (0, 0)), + }; + + with_states(wl_surface, |states| { + states + .cached_state + .current::() + .geometry + .unwrap_or_default() + }) + .unwrap() + } + + fn send_done(&self) { + if !self.alive() { + return; + } + + match *self { + PopupKind::Xdg(ref t) => t.send_popup_done(), + } + } + + fn location(&self) -> Point { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return (0, 0).into(), + }; + with_states(wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .current + .geometry + }) + .unwrap_or_default() + .loc + } +} + +impl From for PopupKind { + fn from(p: PopupSurface) -> PopupKind { + PopupKind::Xdg(p) + } +} diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index f7274eb..8d604b5 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -677,16 +677,18 @@ impl PointerGrab for DefaultGrab { time: u32, ) { handle.button(button, state, serial, time); - handle.set_grab( - serial, - ClickGrab { - start_data: GrabStartData { - focus: handle.current_focus().cloned(), - button, - location: handle.current_location(), + if state == ButtonState::Pressed { + handle.set_grab( + serial, + ClickGrab { + start_data: GrabStartData { + focus: handle.current_focus().cloned(), + button, + location: handle.current_location(), + }, }, - }, - ); + ); + } } fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) { handle.axis(details);