Skip to main content

smithay_client_toolkit/shell/xdg/
popup.rs

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