1use crate::{
2 compositor::{Surface, SurfaceData},
3 error::GlobalError,
4 globals::ProvidesBoundGlobal,
5 shell::xdg::XdgShellSurface,
6};
7use std::sync::{
8 atomic::{AtomicI32, AtomicU32, Ordering::Relaxed},
9 Arc, Weak,
10};
11use wayland_client::{
12 protocol::{wl_compositor::WlCompositor, wl_surface},
13 Connection, Dispatch, QueueHandle,
14};
15use wayland_protocols::xdg::shell::client::{xdg_popup, xdg_positioner, xdg_surface, xdg_wm_base};
16
17#[derive(Debug, Clone)]
18pub struct Popup {
19 inner: Arc<PopupInner>,
20}
21
22impl Eq for Popup {}
23impl PartialEq for Popup {
24 fn eq(&self, other: &Popup) -> bool {
25 Arc::ptr_eq(&self.inner, &other.inner)
26 }
27}
28
29#[derive(Debug)]
30pub struct PopupData {
31 inner: Weak<PopupInner>,
32}
33
34#[derive(Debug)]
35struct PopupInner {
36 surface: XdgShellSurface,
37 xdg_popup: xdg_popup::XdgPopup,
38 pending_position: (AtomicI32, AtomicI32),
39 pending_dimensions: (AtomicI32, AtomicI32),
40 pending_token: AtomicU32,
41 configure_state: AtomicU32,
42}
43
44impl Popup {
45 /// Create a new popup.
46 ///
47 /// This creates the popup and sends the initial commit. You must wait for
48 /// [`PopupHandler::configure`] to commit contents to the surface.
49 pub fn new<D>(
50 parent: &xdg_surface::XdgSurface,
51 position: &xdg_positioner::XdgPositioner,
52 qh: &QueueHandle<D>,
53 compositor: &impl ProvidesBoundGlobal<WlCompositor, 6>,
54 wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
55 ) -> Result<Popup, GlobalError>
56 where
57 D: Dispatch<wl_surface::WlSurface, SurfaceData>
58 + Dispatch<xdg_surface::XdgSurface, PopupData>
59 + Dispatch<xdg_popup::XdgPopup, PopupData>
60 + PopupHandler
61 + 'static,
62 {
63 let surface = Surface::new(compositor, qh)?;
64 let popup = Self::from_surface(Some(parent), position, qh, surface, wm_base)?;
65 popup.wl_surface().commit();
66 Ok(popup)
67 }
68
69 /// Create a new popup from an existing surface.
70 ///
71 /// If you do not specify a parent surface, you must configure the parent using an alternate
72 /// function such as [`LayerSurface::get_popup`] prior to committing the surface, or you will
73 /// get an `invalid_popup_parent` protocol error.
74 ///
75 /// [`LayerSurface::get_popup`]: wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1::get_popup
76 pub fn from_surface<D>(
77 parent: Option<&xdg_surface::XdgSurface>,
78 position: &xdg_positioner::XdgPositioner,
79 qh: &QueueHandle<D>,
80 surface: impl Into<Surface>,
81 wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
82 ) -> Result<Popup, GlobalError>
83 where
84 D: Dispatch<xdg_surface::XdgSurface, PopupData>
85 + Dispatch<xdg_popup::XdgPopup, PopupData>
86 + 'static,
87 {
88 let surface = surface.into();
89 let wm_base = wm_base.bound_global()?;
90 // Freeze the queue during the creation of the Arc to avoid a race between events on the
91 // new objects being processed and the Weak in the PopupData becoming usable.
92 let freeze = qh.freeze();
93 let inner = Arc::new_cyclic(|weak| {
94 let xdg_surface = wm_base.get_xdg_surface(
95 surface.wl_surface(),
96 qh,
97 PopupData { inner: weak.clone() },
98 );
99 let surface = XdgShellSurface { surface, xdg_surface };
100 let xdg_popup = surface.xdg_surface().get_popup(
101 parent,
102 position,
103 qh,
104 PopupData { inner: weak.clone() },
105 );
106
107 PopupInner {
108 surface,
109 xdg_popup,
110 pending_position: (AtomicI32::new(0), AtomicI32::new(0)),
111 pending_dimensions: (AtomicI32::new(-1), AtomicI32::new(-1)),
112 pending_token: AtomicU32::new(0),
113 configure_state: AtomicU32::new(PopupConfigure::STATE_NEW),
114 }
115 });
116 drop(freeze);
117 Ok(Popup { inner })
118 }
119
120 pub fn xdg_popup(&self) -> &xdg_popup::XdgPopup {
121 &self.inner.xdg_popup
122 }
123
124 pub fn xdg_shell_surface(&self) -> &XdgShellSurface {
125 &self.inner.surface
126 }
127
128 pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface {
129 self.inner.surface.xdg_surface()
130 }
131
132 pub fn wl_surface(&self) -> &wl_surface::WlSurface {
133 self.inner.surface.wl_surface()
134 }
135
136 pub fn reposition(&self, position: &xdg_positioner::XdgPositioner, token: u32) {
137 self.xdg_popup().reposition(position, token);
138 }
139}
140
141impl PopupData {
142 /// Get a new handle to the Popup
143 ///
144 /// This returns `None` if the popup has been destroyed.
145 pub fn popup(&self) -> Option<Popup> {
146 let inner: Arc = self.inner.upgrade()?;
147 Some(Popup { inner })
148 }
149}
150
151impl Drop for PopupInner {
152 fn drop(&mut self) {
153 self.xdg_popup.destroy();
154 }
155}
156
157#[derive(Debug, Clone)]
158#[non_exhaustive]
159pub struct PopupConfigure {
160 /// (x,y) relative to parent surface window geometry
161 pub position: (i32, i32),
162 pub width: i32,
163 pub height: i32,
164 pub serial: u32,
165 pub kind: ConfigureKind,
166}
167
168#[derive(Debug, Clone)]
169#[non_exhaustive]
170pub enum ConfigureKind {
171 /// Initial configure for this popup
172 Initial,
173 /// The configure is due to an xdg_positioner with set_reactive requested
174 Reactive,
175 /// The configure is due to a reposition request with this token
176 Reposition { token: u32 },
177}
178
179impl PopupConfigure {
180 const STATE_NEW: u32 = 0;
181 const STATE_CONFIGURED: u32 = 1;
182 const STATE_REPOSITION_ACK: u32 = 2;
183}
184
185pub trait PopupHandler: Sized {
186 /// The popup has been configured.
187 fn configure(
188 &mut self,
189 conn: &Connection,
190 qh: &QueueHandle<Self>,
191 popup: &Popup,
192 config: PopupConfigure,
193 );
194
195 /// The popup was dismissed by the compositor and should be destroyed.
196 fn done(&mut self, conn: &Connection, qh: &QueueHandle<Self>, popup: &Popup);
197}
198
199impl<D> Dispatch<xdg_surface::XdgSurface, PopupData, D> for PopupData
200where
201 D: Dispatch<xdg_surface::XdgSurface, PopupData> + PopupHandler,
202{
203 fn event(
204 data: &mut D,
205 xdg_surface: &xdg_surface::XdgSurface,
206 event: xdg_surface::Event,
207 pdata: &PopupData,
208 conn: &Connection,
209 qh: &QueueHandle<D>,
210 ) {
211 let popup = match pdata.popup() {
212 Some(popup) => popup,
213 None => return,
214 };
215 let inner = &popup.inner;
216 match event {
217 xdg_surface::Event::Configure { serial } => {
218 xdg_surface.ack_configure(serial);
219 let x = inner.pending_position.0.load(Relaxed);
220 let y = inner.pending_position.1.load(Relaxed);
221 let width = inner.pending_dimensions.0.load(Relaxed);
222 let height = inner.pending_dimensions.1.load(Relaxed);
223 let kind =
224 match inner.configure_state.swap(PopupConfigure::STATE_CONFIGURED, Relaxed) {
225 PopupConfigure::STATE_NEW => ConfigureKind::Initial,
226 PopupConfigure::STATE_CONFIGURED => ConfigureKind::Reactive,
227 PopupConfigure::STATE_REPOSITION_ACK => {
228 ConfigureKind::Reposition { token: inner.pending_token.load(Relaxed) }
229 }
230 _ => unreachable!(),
231 };
232
233 let config = PopupConfigure { position: (x, y), width, height, serial, kind };
234
235 data.configure(conn, qh, &popup, config);
236 }
237 _ => unreachable!(),
238 }
239 }
240}
241
242impl<D> Dispatch<xdg_popup::XdgPopup, PopupData, D> for PopupData
243where
244 D: Dispatch<xdg_popup::XdgPopup, PopupData> + PopupHandler,
245{
246 fn event(
247 data: &mut D,
248 _: &xdg_popup::XdgPopup,
249 event: xdg_popup::Event,
250 pdata: &PopupData,
251 conn: &Connection,
252 qh: &QueueHandle<D>,
253 ) {
254 let popup = match pdata.popup() {
255 Some(popup) => popup,
256 None => return,
257 };
258 let inner = &popup.inner;
259 match event {
260 xdg_popup::Event::Configure { x, y, width, height } => {
261 inner.pending_position.0.store(x, Relaxed);
262 inner.pending_position.1.store(y, Relaxed);
263 inner.pending_dimensions.0.store(width, Relaxed);
264 inner.pending_dimensions.1.store(height, Relaxed);
265 }
266 xdg_popup::Event::PopupDone => {
267 data.done(conn, qh, &popup);
268 }
269 xdg_popup::Event::Repositioned { token } => {
270 inner.pending_token.store(token, Relaxed);
271 inner.configure_state.store(PopupConfigure::STATE_REPOSITION_ACK, Relaxed);
272 }
273 _ => unreachable!(),
274 }
275 }
276}
277
278#[macro_export]
279macro_rules! delegate_xdg_popup {
280 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
281 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
282 $crate::reexports::protocols::xdg::shell::client::xdg_popup::XdgPopup: $crate::shell::xdg::popup::PopupData
283 ] => $crate::shell::xdg::popup::PopupData);
284 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
285 $crate::reexports::protocols::xdg::shell::client::xdg_surface::XdgSurface: $crate::shell::xdg::popup::PopupData
286 ] => $crate::shell::xdg::popup::PopupData);
287 };
288}
289