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