Skip to content

Commit 91c1d36

Browse files
YaLTeRDrakulix
authored andcommitted
wayland/shell/xdg: Delay grab handler until pre-commit
Qt layer-shell calls grab() first, only then sets popup parent via zwlr_layer_surface_v1::get_popup(). Before this commit, grab() would call the handler immediately, which would see that the popup has no parent, and fail. However, it works on other compositors. The xdg_popup docs don't mention the desired behavior explicitly, though this line could be interpreted in favor of "applying" the grab request at commit time: > The client must call wl_surface.commit on the corresponding wl_surface for > the xdg_popup state to take effect. Fixes grabs from first-level popups on layer-shell Qt clients such as pcmanfm-qt --desktop and lxqt-panel.
1 parent 1c1a96a commit 91c1d36

File tree

4 files changed

+54
-45
lines changed

4 files changed

+54
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct XdgPopupSurfaceRoleAttributes {
6565
- configure_serial: Option<Serial>,
6666
- current: PopupState,
6767
- current_serial: Option<Serial>,
68+
- committed: bool,
6869
- last_acked: Option<PopupState>,
6970
+ last_acked: Option<PopupConfigure>,
7071
// ...

src/desktop/wayland/popup/manager.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
};
1010
use std::sync::{Arc, Mutex};
1111
use tracing::trace;
12-
use wayland_protocols::xdg::shell::server::{xdg_popup, xdg_wm_base};
12+
use wayland_protocols::xdg::shell::server::xdg_wm_base;
1313
use wayland_server::{protocol::wl_surface::WlSurface, Resource};
1414

1515
use super::{PopupGrab, PopupGrabError, PopupGrabInner, PopupKind};
@@ -74,23 +74,7 @@ impl PopupManager {
7474
);
7575

7676
match popup {
77-
PopupKind::Xdg(ref xdg) => {
78-
let surface = xdg.wl_surface();
79-
let committed = with_states(surface, |states| {
80-
states
81-
.data_map
82-
.get::<XdgPopupSurfaceData>()
83-
.unwrap()
84-
.lock()
85-
.unwrap()
86-
.committed
87-
});
88-
89-
if committed {
90-
surface.post_error(xdg_popup::Error::InvalidGrab, "xdg_popup already is mapped");
91-
return Err(PopupGrabError::InvalidGrab);
92-
}
93-
}
77+
PopupKind::Xdg(ref _xdg) => (),
9478
PopupKind::InputMethod(ref _input_method) => {
9579
return Err(PopupGrabError::InvalidGrab);
9680
}

src/wayland/shell/xdg/handlers/surface/popup.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
input::SeatHandler,
55
utils::Serial,
66
wayland::{
7-
compositor,
7+
compositor::{self, with_states},
88
shell::xdg::{PopupCachedState, SurfaceCachedState, XdgPopupSurfaceData, XdgPositionerUserData},
99
},
1010
};
@@ -45,7 +45,15 @@ where
4545

4646
let serial = Serial::from(serial);
4747

48-
XdgShellHandler::grab(state, handle, seat, serial);
48+
with_states(handle.wl_surface(), |states| {
49+
states
50+
.data_map
51+
.get::<XdgPopupSurfaceData>()
52+
.unwrap()
53+
.lock()
54+
.unwrap()
55+
.requested_grab = Some((seat, serial));
56+
});
4957
}
5058
xdg_popup::Request::Reposition { positioner, token } => {
5159
let handle = crate::wayland::shell::xdg::PopupSurface {

src/wayland/shell/xdg/mod.rs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,6 @@ xdg_role!(
426426
/// for the xdg_popup state to take effect.
427427
#[derive(Debug)]
428428
XdgPopupSurfaceRoleAttributes {
429-
// FIXME: "parent" should move into PopupCachedState, and "committed" should be removed in
430-
// favor of something like PopupCachedState::last_acked.is_some(). Along the way, popup
431-
// grabs should be made to apply at commit time, rather than immediately upon
432-
// xdg_popup.grab(). This is required to fix Qt layer-shell popup grabs for example.
433-
434429
/// Holds the parent for the xdg_popup.
435430
///
436431
/// The parent is allowed to remain unset as long
@@ -443,12 +438,8 @@ xdg_role!(
443438
/// the xdg_popup role when no parent is set.
444439
pub parent: Option<wl_surface::WlSurface>,
445440

446-
/// Defines if the surface has received at least one commit
447-
///
448-
/// This can be used to check for protocol errors, like
449-
/// checking if a popup requested a grab after it has been
450-
/// mapped.
451-
pub committed: bool
441+
/// If the popup requested a grab, this is the seat and serial.
442+
requested_grab: Option<(wl_seat::WlSeat, Serial)>
452443
},
453444
/// Represents the xdg_popup pending state
454445
#[derive(Debug, Default, Clone)]
@@ -1969,15 +1960,15 @@ impl PopupSurface {
19691960
) {
19701961
let _span = trace_span!("xdg-popup pre-commit", surface = %surface.id()).entered();
19711962

1972-
let error = compositor::with_states(surface, |states| {
1963+
let res = compositor::with_states(surface, |states| {
19731964
let mut role = states
19741965
.data_map
19751966
.get::<XdgPopupSurfaceData>()
19761967
.unwrap()
19771968
.lock()
19781969
.unwrap();
19791970
if role.parent.is_none() {
1980-
return Some((
1971+
return Err((
19811972
xdg_surface::Error::NotConstructed,
19821973
"xdg_popup must have parent before mapping",
19831974
));
@@ -1990,6 +1981,19 @@ impl PopupSurface {
19901981
// surface is not allowed to attach a buffer without acking the initial configure.
19911982
let had_buffer_before = pending.last_acked.is_some();
19921983

1984+
let grab = role.requested_grab.take();
1985+
if grab.is_some() && had_buffer_before {
1986+
let popups = &state.xdg_shell_state().known_popups;
1987+
if let Some(popup) = popups.iter().find(|handle| handle.wl_surface == *surface) {
1988+
popup
1989+
.shell_surface
1990+
.post_error(xdg_popup::Error::InvalidGrab, "tried to grab after being mapped");
1991+
} else {
1992+
error!("surface missing from known popups");
1993+
}
1994+
return Ok(None);
1995+
}
1996+
19931997
let mut guard_surface = states.cached_state.get::<SurfaceAttributes>();
19941998
let has_buffer = match &guard_surface.pending().buffer {
19951999
Some(BufferAssignment::NewBuffer(_)) => true,
@@ -2002,7 +2006,7 @@ impl PopupSurface {
20022006

20032007
if has_buffer {
20042008
let Some(last_acked) = role.last_acked else {
2005-
return Some((
2009+
return Err((
20062010
xdg_surface::Error::UnconfiguredBuffer,
20072011
"must ack the initial configure before attaching buffer",
20082012
));
@@ -2031,19 +2035,31 @@ impl PopupSurface {
20312035
*guard_popup.pending() = Default::default();
20322036
}
20332037

2034-
None
2038+
Ok(grab)
20352039
});
20362040

2037-
if let Some((error, msg)) = error {
2038-
let popups = &state.xdg_shell_state().known_popups;
2039-
if let Some(popup) = popups.iter().find(|handle| handle.wl_surface == *surface) {
2040-
let data = popup
2041-
.shell_surface
2042-
.data::<self::handlers::XdgShellSurfaceUserData>()
2043-
.unwrap();
2044-
data.xdg_surface.post_error(error, msg);
2045-
} else {
2046-
error!("surface missing from known popups");
2041+
match res {
2042+
Ok(Some((seat, serial))) => {
2043+
let popups = &state.xdg_shell_state().known_popups;
2044+
if let Some(popup) = popups.iter().find(|handle| handle.wl_surface == *surface) {
2045+
let popup = popup.clone();
2046+
XdgShellHandler::grab(state, popup, seat, serial);
2047+
} else {
2048+
error!("surface missing from known popups");
2049+
}
2050+
}
2051+
Ok(None) => (),
2052+
Err((error, msg)) => {
2053+
let popups = &state.xdg_shell_state().known_popups;
2054+
if let Some(popup) = popups.iter().find(|handle| handle.wl_surface == *surface) {
2055+
let data = popup
2056+
.shell_surface
2057+
.data::<self::handlers::XdgShellSurfaceUserData>()
2058+
.unwrap();
2059+
data.xdg_surface.post_error(error, msg);
2060+
} else {
2061+
error!("surface missing from known popups");
2062+
}
20472063
}
20482064
}
20492065
}

0 commit comments

Comments
 (0)