1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
//! Utilities for handling shell surfaces with the `wl_shell` protocol //! //! This module provides automatic handling of shell surfaces objects, by being registered //! as a global handler for `wl_shell`. This protocol is deprecated in favor of `xdg_shell`, //! thus this module is provided as a compatibility layer with older clients. As a consequence, //! you can as a compositor-writer decide to only support its functionality in a best-effort //! manner: as this global is part of the core protocol, you are still required to provide //! some support for it. //! //! ## Why use this implementation //! //! This implementation can track for you the various shell surfaces defined by the //! clients by handling the `wl_shell` protocol. //! //! It allows you to easily access a list of all shell surfaces defined by your clients //! access their associated metadata and underlying `wl_surface`s. //! //! This handler only handles the protocol exchanges with the client to present you the //! information in a coherent and relatively easy to use manner. All the actual drawing //! and positioning logic of windows is out of its scope. //! //! ## How to use it //! //! ### Initialization //! //! To initialize this handler, simple use the [`wl_shell_init`] function provided in this module. //! You need to provide a closure that will be invoked whenever some action is required from you, //! are represented by the [`ShellRequest`] enum. //! //! ```no_run //! # extern crate wayland_server; //! # //! use smithay::wayland::shell::legacy::{wl_shell_init, ShellRequest}; //! //! # let mut display = wayland_server::Display::new(); //! let (shell_state, _) = wl_shell_init( //! &mut display, //! // your implementation //! |event: ShellRequest, dispatch_data| { /* handle the shell requests here */ }, //! None // put a logger if you want //! ); //! //! // You're now ready to go! //! ``` use std::{ cell::RefCell, rc::Rc, sync::{Arc, Mutex}, }; use crate::{ utils::{Logical, Point, Size}, wayland::{compositor, Serial}, }; use wayland_server::{ protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}, DispatchData, Display, Filter, Global, }; use super::PingError; mod wl_handlers; /// Metadata associated with the `wl_surface` role #[derive(Debug)] pub struct ShellSurfaceAttributes { /// Title of the surface pub title: String, /// Class of the surface pub class: String, pending_ping: Option<Serial>, } /// A handle to a shell surface #[derive(Debug, Clone)] pub struct ShellSurface { wl_surface: wl_surface::WlSurface, shell_surface: wl_shell_surface::WlShellSurface, } impl std::cmp::PartialEq for ShellSurface { fn eq(&self, other: &Self) -> bool { self.shell_surface == other.shell_surface } } impl ShellSurface { /// Is the shell surface referred by this handle still alive? pub fn alive(&self) -> bool { self.shell_surface.as_ref().is_alive() && self.wl_surface.as_ref().is_alive() } /// Access the underlying `wl_surface` of this toplevel surface /// /// Returns `None` if the toplevel surface actually no longer exists. pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { if self.alive() { Some(&self.wl_surface) } else { None } } /// Send a ping request to this shell surface /// /// You'll receive the reply as a [`ShellRequest::Pong`] request /// /// A typical use is to start a timer at the same time you send this ping /// request, and cancel it when you receive the pong. If the timer runs /// down to 0 before a pong is received, mark the client as unresponsive. /// /// Fails if this shell client already has a pending ping or is already dead. pub fn send_ping(&self, serial: Serial) -> Result<(), PingError> { if !self.alive() { return Err(PingError::DeadSurface); } compositor::with_states(&self.wl_surface, |states| { let mut data = states .data_map .get::<Mutex<ShellSurfaceAttributes>>() .unwrap() .lock() .unwrap(); if let Some(pending_ping) = data.pending_ping { return Err(PingError::PingAlreadyPending(pending_ping)); } data.pending_ping = Some(serial); Ok(()) }) .unwrap()?; self.shell_surface.ping(serial.into()); Ok(()) } /// Send a configure event to this toplevel surface to suggest it a new configuration pub fn send_configure(&self, size: Size<i32, Logical>, edges: wl_shell_surface::Resize) { self.shell_surface.configure(edges, size.w, size.h) } /// Signal a popup surface that it has lost focus pub fn send_popup_done(&self) { self.shell_surface.popup_done() } } /// Possible kinds of shell surface of the `wl_shell` protocol #[derive(Debug)] pub enum ShellSurfaceKind { /// Toplevel, a regular window displayed somewhere in the compositor space Toplevel, /// Transient, this surface has a parent surface /// /// These are sub-windows of an application (for example a configuration window), /// and as such should only be visible in their parent window is, and on top of it. Transient { /// The surface considered as parent parent: wl_surface::WlSurface, /// Location relative to the parent location: Point<i32, Logical>, /// Weather this window should be marked as inactive inactive: bool, }, /// Fullscreen surface, covering an entire output Fullscreen { /// Method used for fullscreen method: wl_shell_surface::FullscreenMethod, /// Framerate (relevant only for driver fullscreen) framerate: u32, /// Requested output if any output: Option<wl_output::WlOutput>, }, /// A popup surface /// /// Short-lived surface, typically referred as "tooltips" in many /// contexts. Popup { /// The parent surface of this popup parent: wl_surface::WlSurface, /// The serial of the input event triggering the creation of this /// popup serial: Serial, /// Weather this popup should be marked as inactive inactive: bool, /// Location of the popup relative to its parent location: Point<i32, Logical>, /// Seat associated this the input that triggered the creation of the /// popup. Used to define when the "popup done" event is sent. seat: wl_seat::WlSeat, }, /// A maximized surface /// /// Like a toplevel surface, but as big as possible on a single output /// while keeping any relevant desktop-environment interface visible. Maximized { /// Requested output for maximization output: Option<wl_output::WlOutput>, }, } /// A request triggered by a `wl_shell_surface` #[derive(Debug)] pub enum ShellRequest { /// A new shell surface was created /// /// by default it has no kind and this should not be displayed NewShellSurface { /// The created surface surface: ShellSurface, }, /// A pong event /// /// The surface responded to its pending ping. If you receive this /// event, smithay has already checked that the responded serial was valid. Pong { /// The surface that sent the pong surface: ShellSurface, }, /// Start of an interactive move /// /// The surface requests that an interactive move is started on it Move { /// The surface requesting the move surface: ShellSurface, /// Serial of the implicit grab that initiated the move serial: Serial, /// Seat associated with the move seat: wl_seat::WlSeat, }, /// Start of an interactive resize /// /// The surface requests that an interactive resize is started on it Resize { /// The surface requesting the resize surface: ShellSurface, /// Serial of the implicit grab that initiated the resize serial: Serial, /// Seat associated with the resize seat: wl_seat::WlSeat, /// Direction of the resize edges: wl_shell_surface::Resize, }, /// The surface changed its kind SetKind { /// The surface surface: ShellSurface, /// Its new kind kind: ShellSurfaceKind, }, } /// Shell global state /// /// This state allows you to retrieve a list of surfaces /// currently known to the shell global. #[derive(Debug)] pub struct ShellState { known_surfaces: Vec<ShellSurface>, } impl ShellState { /// Cleans the internal surface storage by removing all dead surfaces pub(crate) fn cleanup_surfaces(&mut self) { self.known_surfaces.retain(|s| s.alive()); } /// Access all the shell surfaces known by this handler pub fn surfaces(&self) -> &[ShellSurface] { &self.known_surfaces[..] } } /// Create a new `wl_shell` global pub fn wl_shell_init<L, Impl>( display: &mut Display, implementation: Impl, logger: L, ) -> (Arc<Mutex<ShellState>>, Global<wl_shell::WlShell>) where L: Into<Option<::slog::Logger>>, Impl: FnMut(ShellRequest, DispatchData<'_>) + 'static, { let _log = crate::slog_or_fallback(logger); let implementation = Rc::new(RefCell::new(implementation)); let state = Arc::new(Mutex::new(ShellState { known_surfaces: Vec::new(), })); let state2 = state.clone(); let global = display.create_global( 1, Filter::new(move |(shell, _version), _, _data| { self::wl_handlers::implement_shell(shell, implementation.clone(), state2.clone()); }), ); (state, global) }