Skip to main content

smithay_client_toolkit/shell/xdg/
dialog.rs

1use crate::reexports::client::{protocol::wl_compositor::WlCompositor, Proxy, QueueHandle};
2use crate::reexports::client::{protocol::wl_surface, Connection, Dispatch};
3use crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
4use crate::shell::xdg::window::inner::{
5    determine_decoration_mode, determine_window_state, determine_wm_capabilities, WindowInner,
6};
7use crate::shell::xdg::window::{DecorationMode, WindowConfigure};
8use crate::shell::xdg::Dispatch2;
9use crate::shell::xdg::WindowDecorations;
10use crate::shell::WaylandSurface;
11use crate::{
12    compositor::{Surface, SurfaceData},
13    globals::ProvidesBoundGlobal,
14};
15use crate::{error::GlobalError, shell::xdg::XdgShellSurface};
16use std::num::NonZeroU32;
17use std::sync::{Arc, Mutex, Weak};
18use wayland_protocols::xdg::{
19    decoration::zv1::client::zxdg_toplevel_decoration_v1,
20    dialog::v1::client::xdg_dialog_v1::XdgDialogV1, shell::client::xdg_wm_base,
21};
22use wayland_protocols::xdg::{dialog::v1::client::xdg_dialog_v1, shell::client::xdg_surface};
23use wayland_protocols::xdg::{dialog::v1::client::xdg_wm_dialog_v1, shell::client::xdg_toplevel};
24
25/// Handler for toplevel operations on a [`Dialog`]
26pub trait DialogHandler: Sized {
27    /// Request to close a dialog.
28    ///
29    /// This request does not destroy the dialog. You must drop all [`Dialog`] handles to destroy the dialog.
30    /// This request may be sent either by the compositor or by some other mechanism (such as client side decorations).
31    fn request_close(&mut self, conn: &Connection, qh: &QueueHandle<Self>, window: &Dialog);
32
33    /// Apply a suggested surface change.
34    ///
35    /// When this function is called, the compositor is requesting the window's size or state to change.
36    ///
37    /// Internally this function is called when the underlying `xdg_surface` is configured. Any extension
38    /// protocols that interface with xdg-shell are able to be notified that the surface's configure sequence
39    /// is complete by using this function.
40    ///
41    /// # Double buffering
42    ///
43    /// Configure events in Wayland are considered to be double buffered and the state of the window does not
44    /// change until committed.
45    fn configure(
46        &mut self,
47        conn: &Connection,
48        qh: &QueueHandle<Self>,
49        window: &Dialog,
50        configure: WindowConfigure,
51        serial: u32,
52    );
53}
54
55#[derive(Debug, Clone)]
56pub struct Dialog {
57    inner: Arc<DialogInner>,
58}
59
60#[derive(Debug)]
61pub struct DialogData(pub(crate) Weak<DialogInner>);
62
63#[derive(Debug)]
64pub(crate) struct DialogInner {
65    pub xdg_dialog: XdgDialogV1,
66    pub window: WindowInner,
67}
68
69impl Dialog {
70    pub fn new<D, GLOBAL>(
71        parent: &xdg_toplevel::XdgToplevel,
72        qh: &QueueHandle<D>,
73        // TODO: is 6 correct?
74        compositor: &impl ProvidesBoundGlobal<WlCompositor, 6>,
75        wm: &GLOBAL,
76        decoration_manager: Option<&ZxdgDecorationManagerV1>,
77        decorations: WindowDecorations,
78    ) -> Result<Self, GlobalError>
79    where
80        D: Dispatch<wl_surface::WlSurface, SurfaceData<()>>
81            + Dispatch<xdg_surface::XdgSurface, DialogData>
82            + Dispatch<xdg_dialog_v1::XdgDialogV1, DialogData>
83            + Dispatch<xdg_toplevel::XdgToplevel, DialogData>
84            + Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, DialogData>
85            + 'static,
86        GLOBAL: ProvidesBoundGlobal<xdg_wm_dialog_v1::XdgWmDialogV1, 1>
87            + ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
88    {
89        let surface = Surface::new(compositor, qh)?;
90        let dialog = Self::from_surface(surface, parent, qh, wm, decoration_manager, decorations)?;
91        dialog.wl_surface().commit();
92        Ok(dialog)
93    }
94
95    pub fn from_surface<D, GLOBAL>(
96        surface: impl Into<Surface>,
97        parent: &xdg_toplevel::XdgToplevel,
98        qh: &QueueHandle<D>,
99        wm_base: &GLOBAL,
100        decoration_manager: Option<&ZxdgDecorationManagerV1>,
101        decorations: WindowDecorations,
102    ) -> Result<Self, GlobalError>
103    where
104        D: Dispatch<xdg_surface::XdgSurface, DialogData>
105            + Dispatch<xdg_dialog_v1::XdgDialogV1, DialogData>
106            + Dispatch<xdg_toplevel::XdgToplevel, DialogData>
107            + Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, DialogData>
108            + 'static,
109        GLOBAL: ProvidesBoundGlobal<xdg_wm_dialog_v1::XdgWmDialogV1, 1>
110            + ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5>,
111    {
112        let surface = surface.into();
113        let wm_dialog: xdg_wm_dialog_v1::XdgWmDialogV1 = wm_base.bound_global()?;
114        let wm_base: xdg_wm_base::XdgWmBase = wm_base.bound_global()?;
115
116        // Freeze the queue during the creation of the Arc to avoid a race between events on the
117        // new objects being processed and the Weak in the DialogData becoming usable.
118        let freeze = qh.freeze();
119
120        let inner = Arc::new_cyclic(|weak| {
121            let xdg_surface =
122                wm_base.get_xdg_surface(surface.wl_surface(), qh, DialogData(weak.clone()));
123            let surface = XdgShellSurface { surface, xdg_surface };
124            let xdg_toplevel = surface.xdg_surface.get_toplevel(qh, DialogData(weak.clone()));
125            xdg_toplevel.set_parent(Some(parent));
126            let xdg_dialog = wm_dialog.get_xdg_dialog(&xdg_toplevel, qh, DialogData(weak.clone()));
127
128            let toplevel_decoration = crate::shell::xdg::XdgShell::toplevel_decoration(
129                decoration_manager,
130                &xdg_toplevel,
131                decorations,
132                DialogData(weak.clone()),
133                qh,
134            );
135
136            DialogInner {
137                xdg_dialog,
138                window: WindowInner {
139                    xdg_surface: surface,
140                    xdg_toplevel,
141                    toplevel_decoration,
142                    pending_configure: Mutex::new(Default::default()),
143                },
144            }
145        });
146        drop(freeze);
147        let dialog = Dialog { inner };
148        Ok(dialog)
149    }
150
151    pub fn from_xdg_toplevel(toplevel: &xdg_toplevel::XdgToplevel) -> Option<Dialog> {
152        toplevel.data::<DialogData>().and_then(|data| data.dialog())
153    }
154
155    pub fn from_xdg_surface(surface: &xdg_surface::XdgSurface) -> Option<Dialog> {
156        surface.data::<DialogData>().and_then(|data| data.dialog())
157    }
158
159    pub fn from_toplevel_decoration(
160        decoration: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
161    ) -> Option<Dialog> {
162        decoration.data::<DialogData>().and_then(|data| data.dialog())
163    }
164
165    pub fn xdg_dialog(&self) -> &XdgDialogV1 {
166        &self.inner.xdg_dialog
167    }
168
169    pub fn xdg_shell_surface(&self) -> &XdgShellSurface {
170        &self.inner.window.xdg_surface
171    }
172
173    pub fn xdg_toplevel(&self) -> &xdg_toplevel::XdgToplevel {
174        &self.inner.window.xdg_toplevel
175    }
176
177    pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface {
178        self.inner.window.xdg_surface.xdg_surface()
179    }
180
181    pub fn wl_surface(&self) -> &wl_surface::WlSurface {
182        self.inner.window.xdg_surface.wl_surface()
183    }
184
185    pub fn set_modal(&self, modal: bool) {
186        if modal {
187            self.inner.xdg_dialog.set_modal();
188        } else {
189            self.inner.xdg_dialog.unset_modal();
190        }
191    }
192
193    /// Requests the dialog should use the specified decoration mode.
194    ///
195    /// A mode of [`None`] indicates that the dialog does not care what type of decorations are
196    /// used.
197    ///
198    /// The compositor will respond with a [`configure`](DialogHandler::configure). The configure
199    /// will indicate whether the dialog's decoration mode has changed.
200    ///
201    /// # Configure loops
202    ///
203    /// You should avoid sending multiple decoration mode requests to ensure you do not enter a
204    /// configure loop.
205    pub fn request_decoration_mode(&self, mode: Option<DecorationMode>) {
206        self.inner.window.request_decoration_mode(mode)
207    }
208}
209
210impl WaylandSurface for Dialog {
211    fn wl_surface(&self) -> &wl_surface::WlSurface {
212        self.wl_surface()
213    }
214}
215
216impl PartialEq for Dialog {
217    fn eq(&self, other: &Self) -> bool {
218        Arc::ptr_eq(&self.inner, &other.inner)
219    }
220}
221
222impl DialogData {
223    /// Get a new handle to the Dialog
224    ///
225    /// This returns `None` if the dialog has been destroyed.
226    pub fn dialog(&self) -> Option<Dialog> {
227        let inner = self.0.upgrade()?;
228        Some(Dialog { inner })
229    }
230}
231
232impl Drop for DialogInner {
233    fn drop(&mut self) {
234        self.xdg_dialog.destroy();
235    }
236}
237
238impl<D: DialogHandler> Dispatch2<xdg_surface::XdgSurface, D> for DialogData {
239    fn event(
240        &self,
241        data: &mut D,
242        xdg_surface: &xdg_surface::XdgSurface,
243        event: <xdg_surface::XdgSurface as wayland_client::Proxy>::Event,
244        conn: &Connection,
245        qhandle: &QueueHandle<D>,
246    ) {
247        if let Some(dialog) = Dialog::from_xdg_surface(xdg_surface) {
248            match event {
249                xdg_surface::Event::Configure { serial } => {
250                    xdg_surface.ack_configure(serial);
251
252                    let configure = dialog.inner.window.pending_configure.lock().unwrap().clone();
253                    DialogHandler::configure(data, conn, qhandle, &dialog, configure, serial)
254                }
255                _ => unreachable!(),
256            }
257        }
258    }
259}
260
261impl<D> Dispatch2<XdgDialogV1, D> for DialogData {
262    fn event(
263        &self,
264        _state: &mut D,
265        _proxy: &XdgDialogV1,
266        _event: <XdgDialogV1 as wayland_client::Proxy>::Event,
267        _conn: &Connection,
268        _qhandle: &QueueHandle<D>,
269    ) {
270    }
271}
272
273impl<D: DialogHandler> Dispatch2<xdg_toplevel::XdgToplevel, D> for DialogData {
274    fn event(
275        &self,
276        data: &mut D,
277        toplevel: &xdg_toplevel::XdgToplevel,
278        event: <xdg_toplevel::XdgToplevel as wayland_client::Proxy>::Event,
279        conn: &Connection,
280        qhandle: &QueueHandle<D>,
281    ) {
282        let Some(dialog) = Dialog::from_xdg_toplevel(toplevel) else {
283            return;
284        };
285
286        match event {
287            xdg_toplevel::Event::Configure { width, height, states } => {
288                let new_state = determine_window_state(&states);
289
290                // XXX we do explicit convertion and sanity checking because compositor
291                // could pass negative values which we should ignore all together.
292                let width = u32::try_from(width).ok().and_then(NonZeroU32::new);
293                let height = u32::try_from(height).ok().and_then(NonZeroU32::new);
294
295                let pending_configure = &mut dialog.inner.window.pending_configure.lock().unwrap();
296                pending_configure.new_size = (width, height);
297                pending_configure.state = new_state;
298            }
299            xdg_toplevel::Event::Close => {
300                data.request_close(conn, qhandle, &dialog);
301            }
302
303            xdg_toplevel::Event::ConfigureBounds { width, height } => {
304                let pending_configure = &mut dialog.inner.window.pending_configure.lock().unwrap();
305                if width == 0 && height == 0 {
306                    pending_configure.suggested_bounds = None;
307                } else {
308                    pending_configure.suggested_bounds = Some((width as u32, height as u32));
309                }
310            }
311            xdg_toplevel::Event::WmCapabilities { capabilities } => {
312                let pending_configure = &mut dialog.inner.window.pending_configure.lock().unwrap();
313                pending_configure.capabilities = determine_wm_capabilities(&capabilities)
314            }
315            _ => unreachable!(),
316        }
317    }
318}
319
320impl<D> Dispatch2<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, D> for DialogData
321where
322    D: DialogHandler,
323{
324    fn event(
325        &self,
326        _: &mut D,
327        decoration: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
328        event: zxdg_toplevel_decoration_v1::Event,
329        _: &Connection,
330        _: &QueueHandle<D>,
331    ) {
332        if let Some(dialog) = Dialog::from_toplevel_decoration(decoration) {
333            match event {
334                zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
335                    wayland_client::WEnum::Value(mode) => {
336                        let mode = determine_decoration_mode(mode);
337                        dialog.inner.window.pending_configure.lock().unwrap().decoration_mode =
338                            mode;
339                    }
340
341                    wayland_client::WEnum::Unknown(unknown) => {
342                        log::error!(target: "sctk", "unknown decoration mode 0x{:x}", unknown);
343                    }
344                },
345
346                _ => unreachable!(),
347            }
348        }
349    }
350}