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