smithay_client_toolkit/shell/xdg/
popup.rs

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 = 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}