add support for popup grabs
This commit is contained in:
parent
9cb64b9a7f
commit
444a7f2be1
|
@ -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<Serial>,
|
||||
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<Mutex<PopupGrabInternal>>,
|
||||
}
|
||||
|
||||
impl PopupGrabInner {
|
||||
pub(super) fn alive(&self) -> bool {
|
||||
let guard = self.internal.lock().unwrap();
|
||||
guard.alive()
|
||||
}
|
||||
|
||||
fn current_grab(&self) -> Option<WlSurface> {
|
||||
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<Option<Serial>, 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<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.1);
|
||||
|
||||
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.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<Serial>,
|
||||
toplevel_grab: PopupGrabInner,
|
||||
keyboard_handle: Option<KeyboardHandle>,
|
||||
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<Serial>,
|
||||
keyboard_handle: Option<KeyboardHandle>,
|
||||
) -> 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<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.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<WlSurface> {
|
||||
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<WlSurface> {
|
||||
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<f64, Logical>,
|
||||
focus: Option<(WlSurface, Point<i32, Logical>)>,
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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<PopupKind>,
|
||||
popup_trees: Vec<PopupTree>,
|
||||
popup_grabs: Vec<PopupGrabInner>,
|
||||
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| {
|
||||
/// 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<PopupGrab, PopupGrabError> {
|
||||
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::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.parent
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.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::<PopupGrabInner>().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::<PopupTree>();
|
||||
|
||||
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<WlSurface, DeadResource> {
|
||||
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::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.parent
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
})?;
|
||||
}
|
||||
Ok(parent)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct PopupTree(Arc<Mutex<Vec<PopupNode>>>);
|
||||
|
||||
|
@ -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<WlSurface> {
|
||||
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<i32, Logical> {
|
||||
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::<SurfaceCachedState>()
|
||||
.geometry
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn location(&self) -> Point<i32, Logical> {
|
||||
let wl_surface = match self.get_surface() {
|
||||
Some(s) => s,
|
||||
None => return (0, 0).into(),
|
||||
};
|
||||
with_states(wl_surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.current
|
||||
.geometry
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.loc
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupSurface> for PopupKind {
|
||||
fn from(p: PopupSurface) -> PopupKind {
|
||||
PopupKind::Xdg(p)
|
||||
}
|
||||
}
|
|
@ -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<WlSurface> {
|
||||
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<i32, Logical> {
|
||||
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::<SurfaceCachedState>()
|
||||
.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<i32, Logical> {
|
||||
let wl_surface = match self.get_surface() {
|
||||
Some(s) => s,
|
||||
None => return (0, 0).into(),
|
||||
};
|
||||
with_states(wl_surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.current
|
||||
.geometry
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.loc
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupSurface> for PopupKind {
|
||||
fn from(p: PopupSurface) -> PopupKind {
|
||||
PopupKind::Xdg(p)
|
||||
}
|
||||
}
|
|
@ -677,6 +677,7 @@ impl PointerGrab for DefaultGrab {
|
|||
time: u32,
|
||||
) {
|
||||
handle.button(button, state, serial, time);
|
||||
if state == ButtonState::Pressed {
|
||||
handle.set_grab(
|
||||
serial,
|
||||
ClickGrab {
|
||||
|
@ -688,6 +689,7 @@ impl PointerGrab for DefaultGrab {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame) {
|
||||
handle.axis(details);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue