Skip to main content

smithay_client_toolkit/shell/xdg/
mod.rs

1//! ## Cross desktop group (XDG) shell
2// TODO: Examples
3
4use std::os::unix::io::OwnedFd;
5use std::sync::{Arc, Mutex};
6
7use wayland_protocols::xdg::dialog::v1::client::xdg_wm_dialog_v1;
8
9use crate::reexports::client::globals::{BindError, GlobalList};
10use crate::reexports::client::Connection;
11use crate::reexports::client::{protocol::wl_surface, Dispatch, Proxy, QueueHandle};
12use crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
13use crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::Mode;
14use crate::reexports::protocols::xdg::decoration::zv1::client::{
15    zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
16};
17use crate::reexports::protocols::xdg::dialog::v1::client::xdg_dialog_v1;
18use crate::reexports::protocols::xdg::shell::client::{
19    xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
20};
21
22use crate::compositor::Surface;
23use crate::dispatch2::Dispatch2;
24use crate::error::GlobalError;
25use crate::globals::{GlobalData, ProvidesBoundGlobal};
26use crate::registry::GlobalProxy;
27use crate::shell::xdg::dialog::{Dialog, DialogData, DialogHandler};
28
29use self::window::inner::WindowInner;
30use self::window::{Window, WindowData, WindowDecorations, WindowHandler};
31
32use super::WaylandSurface;
33
34pub mod dialog;
35pub mod fallback_frame;
36pub mod popup;
37pub mod window;
38
39/// The xdg shell globals.
40#[derive(Debug)]
41pub struct XdgShell {
42    xdg_wm_dialog_v1: Option<xdg_wm_dialog_v1::XdgWmDialogV1>,
43    xdg_wm_base: xdg_wm_base::XdgWmBase,
44    xdg_decoration_manager: GlobalProxy<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
45}
46
47impl XdgShell {
48    /// The maximum API version for XdgWmBase that this object will bind.
49    // Note: if bumping this version number, check if the changes to the wayland XML cause an API
50    // break in the rust interfaces.  If it does, be sure to remove other ProvidesBoundGlobal
51    // impls; if it does not, consider adding one for the previous (compatible) version.
52    pub const API_VERSION_MAX: u32 = 6;
53
54    /// Binds the xdg shell global, `xdg_wm_base`.
55    ///
56    /// If available, the `zxdg_decoration_manager_v1` global will be bound to allow server side decorations
57    /// for windows.
58    ///
59    /// # Errors
60    ///
61    /// This function will return [`Err`] if the `xdg_wm_base` global is not available.
62    pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
63    where
64        State: Dispatch<xdg_wm_base::XdgWmBase, GlobalData, State>
65            + Dispatch<xdg_wm_dialog_v1::XdgWmDialogV1, GlobalData, State>
66            + Dispatch<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, GlobalData, State>
67            + 'static,
68    {
69        let xdg_wm_base = globals.bind(qh, 1..=Self::API_VERSION_MAX, GlobalData)?;
70        let xdg_wm_dialog_v1 = globals.bind(qh, 1..=1, GlobalData).ok();
71        let xdg_decoration_manager = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData));
72        Ok(Self { xdg_wm_base, xdg_wm_dialog_v1, xdg_decoration_manager })
73    }
74
75    pub(crate) fn toplevel_decoration<State, D>(
76        decoration_manager: Option<&ZxdgDecorationManagerV1>,
77        xdg_toplevel: &xdg_toplevel::XdgToplevel,
78        decorations: WindowDecorations,
79        data: D,
80        qh: &QueueHandle<State>,
81    ) -> Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>
82    where
83        D: Send + Sync + 'static,
84        State: Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, D> + 'static,
85    {
86        // If server side decorations are available, create the toplevel decoration.
87        decoration_manager.and_then(|decoration_manager| {
88            match decorations {
89                // Window does not want any server side decorations.
90                WindowDecorations::ClientOnly | WindowDecorations::None => None,
91
92                _ => {
93                    // Create the toplevel decoration.
94                    let toplevel_decoration =
95                        decoration_manager.get_toplevel_decoration(xdg_toplevel, qh, data);
96
97                    // Tell the compositor we would like a specific mode.
98                    let mode = match decorations {
99                        WindowDecorations::RequestServer => Some(Mode::ServerSide),
100                        WindowDecorations::RequestClient => Some(Mode::ClientSide),
101                        _ => None,
102                    };
103
104                    if let Some(mode) = mode {
105                        toplevel_decoration.set_mode(mode);
106                    }
107
108                    Some(toplevel_decoration)
109                }
110            }
111        })
112    }
113
114    /// Creates a new, unmapped window.
115    ///
116    /// # Protocol errors
117    ///
118    /// If the surface already has a role object, the compositor will raise a protocol error.
119    ///
120    /// A surface is considered to have a role object if some other type of surface was created using the
121    /// surface. For example, creating a window, popup, layer or subsurface all assign a role object to a
122    /// surface.
123    ///
124    /// This function takes ownership of the surface.
125    ///
126    /// For more info related to creating windows, see [`the module documentation`](self).
127    #[must_use = "Dropping all window handles will destroy the window"]
128    pub fn create_window<State>(
129        &self,
130        surface: impl Into<Surface>,
131        decorations: WindowDecorations,
132        qh: &QueueHandle<State>,
133    ) -> Window
134    where
135        State: Dispatch<xdg_surface::XdgSurface, WindowData>
136            + Dispatch<xdg_toplevel::XdgToplevel, WindowData>
137            + Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, WindowData>
138            + WindowHandler
139            + 'static,
140    {
141        let decoration_manager = self.xdg_decoration_manager.get().ok();
142        let surface = surface.into();
143
144        // Freeze the queue during the creation of the Arc to avoid a race between events on the
145        // new objects being processed and the Weak in the WindowData becoming usable.
146        let freeze = qh.freeze();
147
148        let inner = Arc::new_cyclic(|weak| {
149            let xdg_surface = self.xdg_wm_base.get_xdg_surface(
150                surface.wl_surface(),
151                qh,
152                WindowData(weak.clone()),
153            );
154            let xdg_surface = XdgShellSurface { surface, xdg_surface };
155            let xdg_toplevel = xdg_surface.xdg_surface().get_toplevel(qh, WindowData(weak.clone()));
156
157            let toplevel_decoration = Self::toplevel_decoration(
158                decoration_manager,
159                &xdg_toplevel,
160                decorations,
161                WindowData(weak.clone()),
162                qh,
163            );
164
165            WindowInner {
166                xdg_surface,
167                xdg_toplevel,
168                toplevel_decoration,
169                pending_configure: Mutex::new(Default::default()),
170            }
171        });
172
173        // Explicitly drop the queue freeze to allow the queue to resume work.
174        drop(freeze);
175
176        Window(inner)
177    }
178
179    #[must_use = "Dropping all dialog handles will destroy the dialog"]
180    pub fn create_dialog<State>(
181        &self,
182        surface: impl Into<Surface>,
183        decorations: WindowDecorations,
184        qh: &QueueHandle<State>,
185        parent: &xdg_toplevel::XdgToplevel,
186    ) -> Result<Dialog, GlobalError>
187    where
188        State: Dispatch<xdg_surface::XdgSurface, DialogData>
189            + Dispatch<xdg_toplevel::XdgToplevel, DialogData>
190            + Dispatch<xdg_dialog_v1::XdgDialogV1, DialogData>
191            + Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, DialogData>
192            + DialogHandler
193            + 'static,
194    {
195        let decoration_manager = self.xdg_decoration_manager.get().ok();
196        Dialog::from_surface(surface, parent, qh, self, decoration_manager, decorations)
197    }
198
199    pub fn xdg_wm_base(&self) -> &xdg_wm_base::XdgWmBase {
200        &self.xdg_wm_base
201    }
202}
203
204/// A trivial wrapper for an [`xdg_positioner::XdgPositioner`].
205///
206/// This wrapper calls [`destroy`][xdg_positioner::XdgPositioner::destroy] on the contained
207/// positioner when it is dropped.
208#[derive(Debug)]
209pub struct XdgPositioner(xdg_positioner::XdgPositioner);
210
211impl XdgPositioner {
212    pub fn new(
213        wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, { XdgShell::API_VERSION_MAX }>,
214    ) -> Result<Self, GlobalError> {
215        wm_base
216            .bound_global()
217            .map(|wm_base| {
218                wm_base
219                    .send_constructor(
220                        xdg_wm_base::Request::CreatePositioner {},
221                        Arc::new(PositionerData),
222                    )
223                    .unwrap_or_else(|_| Proxy::inert(wm_base.backend().clone()))
224            })
225            .map(XdgPositioner)
226    }
227}
228
229impl std::ops::Deref for XdgPositioner {
230    type Target = xdg_positioner::XdgPositioner;
231
232    fn deref(&self) -> &Self::Target {
233        &self.0
234    }
235}
236
237impl Drop for XdgPositioner {
238    fn drop(&mut self) {
239        self.0.destroy()
240    }
241}
242
243struct PositionerData;
244
245impl wayland_client::backend::ObjectData for PositionerData {
246    fn event(
247        self: Arc<Self>,
248        _: &wayland_client::backend::Backend,
249        _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>,
250    ) -> Option<Arc<dyn wayland_client::backend::ObjectData + 'static>> {
251        unreachable!("xdg_positioner has no events");
252    }
253    fn destroyed(&self, _: wayland_client::backend::ObjectId) {}
254}
255
256/// A surface role for functionality common in desktop-like surfaces.
257#[derive(Debug)]
258pub struct XdgShellSurface {
259    xdg_surface: xdg_surface::XdgSurface,
260    surface: Surface,
261}
262
263impl XdgShellSurface {
264    /// Creates an [`XdgShellSurface`].
265    ///
266    /// This function is generally intended to be called in a higher level abstraction, such as
267    /// [`XdgShell::create_window`].
268    ///
269    /// The created [`XdgShellSurface`] will destroy the underlying [`XdgSurface`] or [`WlSurface`] when
270    /// dropped. Higher level abstractions are responsible for ensuring the destruction order of protocol
271    /// objects is correct. Since this function consumes the [`WlSurface`], it may be accessed using
272    /// [`XdgShellSurface::wl_surface`].
273    ///
274    /// # Protocol errors
275    ///
276    /// If the surface already has a role object, the compositor will raise a protocol error.
277    ///
278    /// A surface is considered to have a role object if some other type of surface was created using the
279    /// surface. For example, creating a window, popup, layer, subsurface or some other type of surface object
280    /// all assign a role object to a surface.
281    ///
282    /// [`XdgSurface`]: xdg_surface::XdgSurface
283    /// [`WlSurface`]: wl_surface::WlSurface
284    pub fn new<U, D>(
285        wm_base: &impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, { XdgShell::API_VERSION_MAX }>,
286        qh: &QueueHandle<D>,
287        surface: impl Into<Surface>,
288        udata: U,
289    ) -> Result<XdgShellSurface, GlobalError>
290    where
291        D: Dispatch<xdg_surface::XdgSurface, U> + 'static,
292        U: Send + Sync + 'static,
293    {
294        let surface = surface.into();
295        let xdg_surface = wm_base.bound_global()?.get_xdg_surface(surface.wl_surface(), qh, udata);
296
297        Ok(XdgShellSurface { xdg_surface, surface })
298    }
299
300    pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface {
301        &self.xdg_surface
302    }
303
304    pub fn wl_surface(&self) -> &wl_surface::WlSurface {
305        self.surface.wl_surface()
306    }
307}
308
309pub trait XdgSurface: WaylandSurface + Sized {
310    /// The underlying [`XdgSurface`](xdg_surface::XdgSurface).
311    fn xdg_surface(&self) -> &xdg_surface::XdgSurface;
312
313    fn set_window_geometry(&self, x: u32, y: u32, width: u32, height: u32) {
314        self.xdg_surface().set_window_geometry(x as i32, y as i32, width as i32, height as i32);
315    }
316}
317
318impl WaylandSurface for XdgShellSurface {
319    fn wl_surface(&self) -> &wl_surface::WlSurface {
320        self.wl_surface()
321    }
322}
323
324impl XdgSurface for XdgShellSurface {
325    fn xdg_surface(&self) -> &xdg_surface::XdgSurface {
326        &self.xdg_surface
327    }
328}
329
330impl Drop for XdgShellSurface {
331    fn drop(&mut self) {
332        // Surface role must be destroyed before the wl_surface
333        self.xdg_surface.destroy();
334    }
335}
336
337// Version 5 adds the wm_capabilities event, which is a break
338impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 5> for XdgShell {
339    fn bound_global(&self) -> Result<xdg_wm_base::XdgWmBase, GlobalError> {
340        <Self as ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, 6>>::bound_global(self)
341    }
342}
343
344impl ProvidesBoundGlobal<xdg_wm_base::XdgWmBase, { XdgShell::API_VERSION_MAX }> for XdgShell {
345    fn bound_global(&self) -> Result<xdg_wm_base::XdgWmBase, GlobalError> {
346        Ok(self.xdg_wm_base.clone())
347    }
348}
349
350/// Dialog
351impl ProvidesBoundGlobal<xdg_wm_dialog_v1::XdgWmDialogV1, 1> for XdgShell {
352    fn bound_global(&self) -> Result<xdg_wm_dialog_v1::XdgWmDialogV1, GlobalError> {
353        self.xdg_wm_dialog_v1
354            .clone()
355            .ok_or(GlobalError::MissingGlobal("Dialog v1 is not available"))
356    }
357}
358
359/// Dialog
360impl<D> Dispatch2<xdg_wm_dialog_v1::XdgWmDialogV1, D> for GlobalData {
361    fn event(
362        &self,
363        _: &mut D,
364        _: &xdg_wm_dialog_v1::XdgWmDialogV1,
365        _: xdg_wm_dialog_v1::Event,
366        _: &Connection,
367        _: &QueueHandle<D>,
368    ) {
369        unreachable!("xdg_wm_dialog_v1 has no events")
370    }
371}
372
373/// Dialog
374impl<D> Dispatch2<xdg_wm_base::XdgWmBase, D> for GlobalData {
375    fn event(
376        &self,
377        _state: &mut D,
378        xdg_wm_base: &xdg_wm_base::XdgWmBase,
379        event: xdg_wm_base::Event,
380        _conn: &Connection,
381        _qh: &QueueHandle<D>,
382    ) {
383        match event {
384            xdg_wm_base::Event::Ping { serial } => {
385                xdg_wm_base.pong(serial);
386            }
387
388            _ => unreachable!(),
389        }
390    }
391}