1 | //! ## Cross desktop group (XDG) shell |
2 | // TODO: Examples |
3 | |
4 | use std::os::unix::io::OwnedFd; |
5 | use std::sync::{Arc, Mutex}; |
6 | |
7 | use crate::reexports::client::globals::{BindError, GlobalList}; |
8 | use crate::reexports::client::Connection; |
9 | use crate::reexports::client::{protocol::wl_surface, Dispatch, Proxy, QueueHandle}; |
10 | use crate::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; |
11 | use crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::Mode; |
12 | use crate::reexports::protocols::xdg::decoration::zv1::client::{ |
13 | zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1, |
14 | }; |
15 | use crate::reexports::protocols::xdg::shell::client::{ |
16 | xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, |
17 | }; |
18 | |
19 | use crate::compositor::Surface; |
20 | use crate::error::GlobalError; |
21 | use crate::globals::{GlobalData, ProvidesBoundGlobal}; |
22 | use crate::registry::GlobalProxy; |
23 | |
24 | use self::window::inner::WindowInner; |
25 | use self::window::{ |
26 | DecorationMode, Window, WindowConfigure, WindowData, WindowDecorations, WindowHandler, |
27 | }; |
28 | |
29 | use super::WaylandSurface; |
30 | |
31 | pub mod fallback_frame; |
32 | pub mod popup; |
33 | pub mod window; |
34 | |
35 | /// The xdg shell globals. |
36 | #[derive (Debug)] |
37 | pub struct XdgShell { |
38 | xdg_wm_base: xdg_wm_base::XdgWmBase, |
39 | xdg_decoration_manager: GlobalProxy<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>, |
40 | } |
41 | |
42 | impl 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)] |
173 | pub struct XdgPositioner(xdg_positioner::XdgPositioner); |
174 | |
175 | impl 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(op:XdgPositioner) |
190 | } |
191 | } |
192 | |
193 | impl std::ops::Deref for XdgPositioner { |
194 | type Target = xdg_positioner::XdgPositioner; |
195 | |
196 | fn deref(&self) -> &Self::Target { |
197 | &self.0 |
198 | } |
199 | } |
200 | |
201 | impl Drop for XdgPositioner { |
202 | fn drop(&mut self) { |
203 | self.0.destroy() |
204 | } |
205 | } |
206 | |
207 | struct PositionerData; |
208 | |
209 | impl 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)] |
222 | pub struct XdgShellSurface { |
223 | xdg_surface: xdg_surface::XdgSurface, |
224 | surface: Surface, |
225 | } |
226 | |
227 | impl 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 | |
273 | pub 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 | |
282 | impl WaylandSurface for XdgShellSurface { |
283 | fn wl_surface(&self) -> &wl_surface::WlSurface { |
284 | self.wl_surface() |
285 | } |
286 | } |
287 | |
288 | impl XdgSurface for XdgShellSurface { |
289 | fn xdg_surface(&self) -> &xdg_surface::XdgSurface { |
290 | &self.xdg_surface |
291 | } |
292 | } |
293 | |
294 | #[macro_export ] |
295 | macro_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 | |
309 | impl 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 |
317 | impl 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 | |
323 | impl 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 | |
329 | impl<D> Dispatch<xdg_wm_base::XdgWmBase, GlobalData, D> for XdgShell |
330 | where |
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 | } |
350 | |