<NME> mod.rs <BEF> //! Utilities for handling shell surfaces with the `xdg_shell` protocol //! //! This module provides automatic handling of shell surfaces objects, by being registered //! as a global handler for `xdg_shell`. //! //! ## Why use this implementation //! //! This implementation can track for you the various shell surfaces defined by the //! clients by handling the `xdg_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 //! //! ### Initialization //! //! To initialize this handler, simple use the [`xdg_shell_init`] function provided in this //! module. You will need to provide it the [`CompositorToken`](crate::wayland::compositor::CompositorToken) //! you retrieved from an instantiation of the compositor global provided by smithay. //! //! ```no_run //! # extern crate wayland_server; //! # #[macro_use] extern crate smithay; //! # extern crate wayland_protocols; //! # //! use smithay::wayland::compositor::roles::*; //! use smithay::wayland::compositor::CompositorToken; //! use smithay::wayland::shell::xdg::{xdg_shell_init, XdgToplevelSurfaceRole, XdgPopupSurfaceRole, XdgRequest}; //! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6; //! # use wayland_server::protocol::{wl_seat, wl_output}; //! //! // define the roles type. You need to integrate the XdgSurface role: //! define_roles!(MyRoles => //! [XdgToplevelSurface, XdgToplevelSurfaceRole] //! [XdgPopupSurface, XdgPopupSurfaceRole] //! ); //! //! // define the metadata you want associated with the shell clients //! #[derive(Default)] //! # let mut display = wayland_server::Display::<State>::new().unwrap(); //! let xdg_shell_state = XdgShellState::new::<State, _>( //! } //! //! # let mut display = wayland_server::Display::new(); //! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<MyRoles, _, _>( //! # &mut display, //! # |_, _, _, _| {}, //! # None //! # ); //! let (shell_state, _, _) = xdg_shell_init( //! &mut display, //! // token from the compositor implementation //! compositor_token, //! // your implementation //! |event: XdgRequest<_>| { /* ... */ }, //! None // put a logger if you want //! ); //! //! &mut self.xdg_shell_state //! } //! //! // handle the shell requests here. //! // more optional methods can be used to further customized //! fn new_toplevel(&mut self, surface: ToplevelSurface) { //! // ... //! } //! fn new_popup( //! &mut self, //! surface: PopupSurface, //! positioner: PositionerState, //! ) { //! // ... //! } //! fn grab( //! &mut self, //! surface: PopupSurface, //! seat: wl_seat::WlSeat, //! serial: Serial, //! ) { //! the subhandler you provided, or via methods on the [`ShellState`] //! that you are given (in an `Arc<Mutex<_>>`) as return value of the `init` function. use crate::utils::Rectangle; use crate::wayland::compositor::roles::WrongRole; use crate::wayland::compositor::{roles::Role, CompositorToken}; use crate::wayland::{Serial, SERIAL_COUNTER}; use std::fmt::Debug; use std::{ //! There are mainly 3 kind of objects that you'll manipulate from this implementation: rc::Rc, sync::{Arc, Mutex}, }; use thiserror::Error; use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_surface_v6; use wayland_protocols::xdg_shell::server::xdg_surface; use wayland_protocols::{ //! retrieve a list of all currently alive toplevel surface from the //! [`XdgShellState`]. //! - [`PopupSurface`]: //! This is a handle representing a popup/tooltip surface. Similarly, //! you can get a list of all currently alive popup surface from the //! [`XdgShellState`]. //! //! You'll obtain these objects though two means: either via the callback methods of //! the [`XdgShellHandler`], or via methods on the [`XdgShellState`]. use crate::utils::alive_tracker::IsAlive; use crate::utils::{user_data::UserDataMap, Logical, Point, Rectangle, Size}; use crate::utils::{Serial, SERIAL_COUNTER}; mod zxdgv6_handlers; macro_rules! xdg_role { ($(#[$role_attr:meta])* $role:ident, $configure:ty, $(#[$attr:meta])* $element:ident {$($(#[$field_attr:meta])* $vis:vis$field:ident:$type:ty),*}, $role_ack_configure:expr) => { $(#[$role_attr])* pub type $role = Option<$element>; $(#[$attr])* pub struct $element { /// Defines the current window geometry of the surface if any. pub window_geometry: Option<Rectangle>, /// Holds the double-buffered geometry that may be specified /// by xdg_surface.set_window_geometry. This should be moved to the /// current configuration on a commit of the underlying /// wl_surface. pub client_pending_window_geometry: Option<Rectangle>, /// Defines if the surface has received at least one /// xdg_surface.ack_configure from the client pub configured: bool, protocol::{wl_output, wl_seat, wl_surface}, DisplayHandle, GlobalDispatch, Resource, }; use super::PingError; pub mod decoration; // handlers for the xdg_shell protocol pub(super) mod handlers; pub use handlers::{XdgPositionerUserData, XdgShellSurfaceUserData, XdgSurfaceUserData, XdgWmBaseUserData}; /// The role of an XDG toplevel surface. pub const XDG_TOPLEVEL_ROLE: &str = "xdg_toplevel"; /// The role of an XDG popup surface. pub const XDG_POPUP_ROLE: &str = "xdg_popup"; /// Constant for toplevel state version checking const XDG_TOPLEVEL_STATE_TILED_SINCE: u32 = 2; )* } impl XdgSurface for $element { fn set_window_geometry(&mut self, geometry: Rectangle) { self.client_pending_window_geometry = Some(geometry); } fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError> { let configure = match self .pending_configures .iter() /// The state associated with this configure { Some(configure) => (*configure).clone(), None => { return Err(ConfigureError::SerialNotFound(serial)); } }; $($( $(#[$configure_field_meta])* $configure_field_vis $configure_field_name: $configure_field_type, )*)* } $(#[$attributes_meta])* pub struct $attributes_name { /// Defines if the surface has received at least one // Clean old configures self.pending_configures.retain(|c| c.serial > serial); Ok(configure.into()) } } impl Default for $element { fn default() -> Self { Self { window_geometry: None, client_pending_window_geometry: None, configured: false, configure_serial: None, pending_configures: Vec::new(), /// processing xdg_surface.ack_configure. pending_configures: Vec<$configure_name>, /// Holds the pending state as set by the server. pub server_pending: Option<$state>, /// Holds the last server_pending state that has been acknowledged /// by the client. This state should be cloned to the current /// during a commit. pub last_acked: Option<$state>, }; } impl<R> CompositorToken<R> where R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, { fn with_xdg_role<F, T>(&self, surface: &wl_surface::WlSurface, f: F) -> Result<T, WrongRole> where F: FnOnce(&mut dyn XdgSurface) -> T, { if self.has_role::<XdgToplevelSurfaceRole>(surface) { self.with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| f(role)) } else { self.with_role_data(surface, |role: &mut XdgPopupSurfaceRole| f(role)) } } } trait XdgSurface { fn set_window_geometry(&mut self, geometry: Rectangle); fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError>; } #[derive(Debug, Error)] enum ConfigureError { #[error("the serial `{0:?}` was not found in the pending configure list")] SerialNotFound(Serial), } impl<T> XdgSurface for Option<T> where T: XdgSurface, { fn set_window_geometry(&mut self, geometry: Rectangle) { self.as_mut() .expect("xdg_surface exists but role has been destroyed?!") .set_window_geometry(geometry) } fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError> { self.as_mut() .expect("xdg_surface exists but role has been destroyed?!") .ack_configure(serial) } } xdg_role!( /// This interface defines an xdg_surface role which allows a surface to, /// among other things, set window-like properties such as maximize, /// fullscreen, and minimize, set application-specific metadata like title and fn ack_configure(&mut self, serial: Serial) -> Option<Configure> { let configure = match self .pending_configures .iter() .find(|configure| configure.serial == serial) { Some(configure) => (*configure).clone(), None => { return None; } }; /// xdg_surface description). /// /// Attaching a null buffer to a toplevel unmaps the surface. XdgToplevelSurfaceRole, ToplevelConfigure, /// Role specific attributes for xdg_toplevel XdgToplevelSurfaceRoleAttributes { /// The parent field of a toplevel should be used /// by the compositor to determine which toplevel // Save the last configure serial as a reference self.configure_serial = Some(Serial::from(serial)); // Clean old configures self.pending_configures.retain(|c| c.serial > serial); Some(configure.into()) } /// Gets the latest state that has been configured /// on the server and sent to the client. /// /// The state includes all changes that have been /// made on the server, including all not yet /// acked or committed changes, but excludes the /// current [`server_pending`](#structfield.server_pending) state. /// /// This can be used for example to check if the /// [`server_pending`](#structfield.server_pending) state /// is different from the last configured. pub fn current_server_state(&self) -> &$state { // We check if there is already a non-acked pending // configure and use its state or otherwise we could // loose some state that was previously configured // and sent, but not acked before calling with_pending_state /// /// A value of 0 on an axis means this axis is not constrained pub max_size: (i32, i32), /// Holds the pending state as set by the client. pub client_pending: Option<ToplevelClientPending>, /// Holds the pending state as set by the server. pub server_pending: Option<ToplevelState>, /// Holds the last server_pending state that has been acknowledged // In both cases the state already contains all previous // sent states. This way all pending state is accumulated // into the current state. self.pending_configures .last() .map(|c| &c.state) .or_else(|| self.last_acked.as_ref()) .unwrap_or(&self.current) } ); xdg_role!( /// A popup surface is a short-lived, temporary surface. It can be used to /// implement for example menus, popovers, tooltips and other similar user /// interface concepts. } impl Default for $attributes_name { fn default() -> Self { Self { configured: false, configure_serial: None, pending_configures: Vec::new(), initial_configure_sent: false, server_pending: None, last_acked: None, current: Default::default(), $( $attributes_field_name: Default::default(), )* } } /// /// The client must call wl_surface.commit on the corresponding wl_surface /// for the xdg_popup state to take effect. XdgPopupSurfaceRole, PopupConfigure, /// Popup Surface Role XdgPopupSurfaceRoleAttributes { /// Holds the parent for the xdg_popup. /// /// A configure message for toplevel surfaces #[derive(Debug, Clone)] ToplevelConfigure, /// Role specific attributes for xdg_toplevel /// /// This interface defines an xdg_surface role which allows a surface to, /// among other things, set window-like properties such as maximize, /// fullscreen, and minimize, set application-specific metadata like title and /// id, and well as trigger user interactive operations such as interactive /// resize and move. /// /// Unmapping an xdg_toplevel means that the surface cannot be shown /// by the compositor until it is explicitly mapped again. /// All active operations (e.g., move, resize) are cancelled and all /// attributes (e.g. title, state, stacking, ...) are discarded for /// an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to /// the state it had right after xdg_surface.get_toplevel. The client /// can re-map the toplevel by performing a commit without any buffer /// attached, waiting for a configure event and handling it as usual (see /// xdg_surface description). /// commit. pub current: PopupState, /// Holds the pending state as set by the server. pub server_pending: Option<PopupState> }, |attributes,configure| { attributes.last_acked = Some(configure.state); pub parent: Option<wl_surface::WlSurface>, /// Holds the optional title the client has set for /// this toplevel. For example a web-browser will most likely /// set this to include the current uri. /// /// This string may be used to identify the surface in a task bar, /// window list, or other user interface elements provided by the /// compositor. pub title: Option<String>, /// Holds the optional app ID the client has set for /// this toplevel. /// /// The app ID identifies the general class of applications to which /// the surface belongs. The compositor can use this to group multiple /// surfaces together, or to determine how to launch a new application. /// /// For D-Bus activatable applications, the app ID is used as the D-Bus /// service name. pub app_id: Option<String>, /// Minimum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained pub min_size: Size<i32, Logical>, /// Maximum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained pub max_size: Size<i32, Logical> } ); /// Data associated with XDG toplevel surface /// /// ```no_run /// use smithay::wayland::compositor; /// use smithay::wayland::shell::xdg::XdgToplevelSurfaceData; /// /// # let wl_surface = todo!(); /// compositor::with_states(&wl_surface, |states| { /// states.data_map.get::<XdgToplevelSurfaceData>(); /// }); /// ``` pub type XdgToplevelSurfaceData = Mutex<XdgToplevelSurfaceRoleAttributes>; xdg_role!( PopupState, /// A configure message for popup surface #[derive(Debug, Clone, Copy)] PopupConfigure { /// The token the client provided in the `xdg_popup::reposition` /// request. The token itself is opaque, and has no other special meaning. /// The token is sent in the corresponding `xdg_popup::repositioned` event. pub reposition_token: Option<u32> }, /// Role specific attributes for xdg_popup /// /// A popup surface is a short-lived, temporary surface. It can be used to /// implement for example menus, popovers, tooltips and other similar user /// interface concepts. /// /// A popup can be made to take an explicit grab. See xdg_popup.grab for /// details. /// /// When the popup is dismissed, a popup_done event will be sent out, and at /// the same time the surface will be unmapped. See the xdg_popup.popup_done /// event for details. /// /// Explicitly destroying the xdg_popup object will also dismiss the popup and /// unmap the surface. Clients that want to dismiss the popup when another /// surface of their own is clicked should dismiss the popup using the destroy /// request. /// /// A newly created xdg_popup will be stacked on top of all previously created /// xdg_popup surfaces associated with the same xdg_toplevel. /// /// The parent of an xdg_popup must be mapped (see the xdg_surface /// description) before the xdg_popup itself. /// /// The client must call wl_surface.commit on the corresponding wl_surface /// for the xdg_popup state to take effect. #[derive(Debug)] XdgPopupSurfaceRoleAttributes { /// Holds the parent for the xdg_popup. /// /// The parent is allowed to remain unset as long /// as no commit has been requested for the underlying /// wl_surface. The parent can either be directly /// specified during xdg_surface.get_popup or using /// another protocol extension, for example xdg_layer_shell. /// /// It is a protocol error to call commit on a wl_surface with /// the xdg_popup role when no parent is set. pub parent: Option<wl_surface::WlSurface>, /// Defines if the surface has received at least one commit /// /// This can be used to check for protocol errors, like /// checking if a popup requested a grab after it has been /// mapped. pub committed: bool, popup_handle: Option<xdg_popup::XdgPopup> } ); /// Data associated with XDG popup surface /// /// ```no_run /// use smithay::wayland::compositor; /// use smithay::wayland::shell::xdg::XdgPopupSurfaceData; /// /// # let wl_surface = todo!(); /// compositor::with_states(&wl_surface, |states| { /// states.data_map.get::<XdgPopupSurfaceData>(); /// }); /// ``` pub type XdgPopupSurfaceData = Mutex<XdgPopupSurfaceRoleAttributes>; /// Represents the state of the popup #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct PopupState { /// The positioner state can be used by the compositor /// to calculate the best placement for the popup. /// /// For example the compositor should prevent that a popup /// is placed outside the visible rectangle of a output. pub positioner: PositionerState, /// Holds the geometry of the popup as defined by the positioner. /// /// `Rectangle::width` and `Rectangle::height` holds the size of the /// of the popup in surface-local coordinates and corresponds to the /// window geometry /// /// `Rectangle::x` and `Rectangle::y` holds the position of the popup /// The position is relative to the window geometry as defined by /// xdg_surface.set_window_geometry of the parent surface. pub geometry: Rectangle<i32, Logical>, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] /// The state of a positioner, as set by the client pub struct PositionerState { /// Size of the rectangle that needs to be positioned pub rect_size: Size<i32, Logical>, /// Anchor rectangle in the parent surface coordinates /// relative to which the surface must be positioned pub anchor_rect: Rectangle<i32, Logical>, /// Edges defining the anchor point pub anchor_edges: xdg_positioner::Anchor, /// Gravity direction for positioning the child surface /// relative to its anchor point pub gravity: xdg_positioner::Gravity, /// Adjustments to do if previous criteria constrain the /// surface pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, /// Offset placement relative to the anchor point pub offset: Point<i32, Logical>, /// When set reactive, the surface is reconstrained if the conditions /// used for constraining changed, e.g. the parent window moved. /// /// If the conditions changed and the popup was reconstrained, /// an xdg_popup.configure event is sent with updated geometry, /// followed by an xdg_surface.configure event. pub reactive: bool, /// The parent window geometry the compositor should use when /// positioning the popup. The compositor may use this information /// to determine the future state the popup should be constrained using. /// If this doesn't match the dimension of the parent the popup is /// eventually positioned against, the behavior is undefined. /// /// The arguments are given in the surface-local coordinate space. pub parent_size: Option<Size<i32, Logical>>, /// The serial of an xdg_surface.configure event this positioner will /// be used in response to. The compositor may use this information /// together with set_parent_size to determine what future state the /// popup should be constrained using. pub parent_configure: Option<Serial>, } impl Default for PositionerState { fn default() -> Self { PositionerState { anchor_edges: xdg_positioner::Anchor::None, anchor_rect: Default::default(), constraint_adjustment: xdg_positioner::ConstraintAdjustment::empty(), gravity: xdg_positioner::Gravity::None, offset: Default::default(), rect_size: Default::default(), reactive: false, parent_size: None, parent_configure: None, } } } impl PositionerState { pub(crate) fn anchor_has_edge(&self, edge: xdg_positioner::Anchor) -> bool { match edge { xdg_positioner::Anchor::Top => { self.anchor_edges == xdg_positioner::Anchor::Top || self.anchor_edges == xdg_positioner::Anchor::TopLeft || self.anchor_edges == xdg_positioner::Anchor::TopRight } xdg_positioner::Anchor::Bottom => { self.anchor_edges == xdg_positioner::Anchor::Bottom || self.anchor_edges == xdg_positioner::Anchor::BottomLeft || self.anchor_edges == xdg_positioner::Anchor::BottomRight } xdg_positioner::Anchor::Left => { self.anchor_edges == xdg_positioner::Anchor::Left || self.anchor_edges == xdg_positioner::Anchor::TopLeft || self.anchor_edges == xdg_positioner::Anchor::BottomLeft } xdg_positioner::Anchor::Right => { self.anchor_edges == xdg_positioner::Anchor::Right || self.anchor_edges == xdg_positioner::Anchor::TopRight || self.anchor_edges == xdg_positioner::Anchor::BottomRight } _ => unreachable!(), } } pub(crate) fn gravity_has_edge(&self, edge: xdg_positioner::Gravity) -> bool { match edge { xdg_positioner::Gravity::Top => { self.gravity == xdg_positioner::Gravity::Top || self.gravity == xdg_positioner::Gravity::TopLeft || self.gravity == xdg_positioner::Gravity::TopRight } xdg_positioner::Gravity::Bottom => { self.gravity == xdg_positioner::Gravity::Bottom || self.gravity == xdg_positioner::Gravity::BottomLeft || self.gravity == xdg_positioner::Gravity::BottomRight } xdg_positioner::Gravity::Left => { self.gravity == xdg_positioner::Gravity::Left || self.gravity == xdg_positioner::Gravity::TopLeft || self.gravity == xdg_positioner::Gravity::BottomLeft } xdg_positioner::Gravity::Right => { self.gravity == xdg_positioner::Gravity::Right || self.gravity == xdg_positioner::Gravity::TopRight || self.gravity == xdg_positioner::Gravity::BottomRight } _ => unreachable!(), } } /// Get the geometry for a popup as defined by this positioner. /// /// `Rectangle::width` and `Rectangle::height` corresponds to the /// size set by `xdg_positioner.set_size`. /// /// `Rectangle::x` and `Rectangle::y` define the position of the /// popup relative to it's parent surface `window_geometry`. /// The position is calculated according to the rules defined /// in the `xdg_shell` protocol. /// The `constraint_adjustment` will not be considered by this /// implementation and the position and size should be re-calculated /// in the compositor if the compositor implements `constraint_adjustment` pub fn get_geometry(&self) -> Rectangle<i32, Logical> { // From the `xdg_shell` prococol specification: // // set_offset: // // Specify the surface position offset relative to the position of the // anchor on the anchor rectangle and the anchor on the surface. For // example if the anchor of the anchor rectangle is at (x, y), the surface // has the gravity bottom|right, and the offset is (ox, oy), the calculated // surface position will be (x + ox, y + oy) let mut geometry = Rectangle { loc: self.offset, size: self.rect_size, }; // Defines the anchor point for the anchor rectangle. The specified anchor // is used derive an anchor point that the child surface will be // positioned relative to. If a corner anchor is set (e.g. 'top_left' or // 'bottom_right'), the anchor point will be at the specified corner; // otherwise, the derived anchor point will be centered on the specified // edge, or in the center of the anchor rectangle if no edge is specified. if self.anchor_has_edge(xdg_positioner::Anchor::Top) { geometry.loc.y += self.anchor_rect.loc.y; } else if self.anchor_has_edge(xdg_positioner::Anchor::Bottom) { geometry.loc.y += self.anchor_rect.loc.y + self.anchor_rect.size.h; } else { geometry.loc.y += self.anchor_rect.loc.y + self.anchor_rect.size.h / 2; } if self.anchor_has_edge(xdg_positioner::Anchor::Left) { geometry.loc.x += self.anchor_rect.loc.x; } else if self.anchor_has_edge(xdg_positioner::Anchor::Right) { geometry.loc.x += self.anchor_rect.loc.x + self.anchor_rect.size.w; } else { geometry.loc.x += self.anchor_rect.loc.x + self.anchor_rect.size.w / 2; } // Defines in what direction a surface should be positioned, relative to /// Represents the client pending state #[derive(Debug, Clone, Copy)] pub struct ToplevelClientPending { /// Minimum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// /// A value of `None` represents the client has not defined /// a minimum size pub min_size: Option<(i32, i32)>, /// Maximum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// /// A value of `None` represents the client has not defined /// a maximum size pub max_size: Option<(i32, i32)>, } impl Default for ToplevelClientPending { fn default() -> Self { Self { min_size: None, max_size: None, } } } /// Represents the possible errors /// returned from `ToplevelSurface::with_pending_state` /// and `PopupSurface::with_pending_state` #[derive(Debug, Error)] pub enum PendingStateError { /// The operation failed because the underlying surface has been destroyed #[error("could not retrieve the pending state because the underlying surface has been destroyed")] DeadSurface, /// The role of the xdg_surface has been destroyed #[error("the role of the xdg_surface has been destroyed")] RoleDestroyed, } pub(crate) struct ShellData<R> { log: ::slog::Logger, compositor_token: CompositorToken<R>, user_impl: Rc<RefCell<dyn FnMut(XdgRequest<R>)>>, shell_state: Arc<Mutex<ShellState<R>>>, } impl<R> Clone for ShellData<R> { fn clone(&self) -> Self { ShellData { log: self.log.clone(), compositor_token: self.compositor_token, user_impl: self.user_impl.clone(), shell_state: self.shell_state.clone(), } pub struct ToplevelStateSet { } /// Create a new `xdg_shell` globals pub fn xdg_shell_init<R, L, Impl>( display: &mut Display, ctoken: CompositorToken<R>, implementation: Impl, logger: L, ) -> ( Arc<Mutex<ShellState<R>>>, Global<xdg_wm_base::XdgWmBase>, Global<zxdg_shell_v6::ZxdgShellV6>, ) where R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, L: Into<Option<::slog::Logger>>, Impl: FnMut(XdgRequest<R>) + 'static, { let log = crate::slog_or_fallback(logger); let shell_state = Arc::new(Mutex::new(ShellState { self.states.push(state); true } let shell_data = ShellData { log: log.new(o!("smithay_module" => "xdg_shell_handler")), compositor_token: ctoken, user_impl: Rc::new(RefCell::new(implementation)), shell_state: shell_state.clone(), }; false } else { self.states.retain(|s| *s != state); true } } /// Filter the states according to the provided version /// of the [`XdgToplevel`] pub(crate) fn into_filtered_states(self, version: u32) -> Vec<xdg_toplevel::State> { // If the client version supports the tiled states // we can directly return the states which will save // us from allocating another vector if version >= XDG_TOPLEVEL_STATE_TILED_SINCE { return self.states; } let is_tiled = |state: &xdg_toplevel::State| { matches!( state, xdg_toplevel::State::TiledTop | xdg_toplevel::State::TiledBottom /// This state allows you to retrieve a list of surfaces /// currently known to the shell global. #[derive(Debug)] pub struct ShellState<R> { known_toplevels: Vec<ToplevelSurface<R>>, known_popups: Vec<PopupSurface<R>>, } impl<R> ShellState<R> where R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, { /// Access all the shell surfaces known by this handler pub fn toplevel_surfaces(&self) -> &[ToplevelSurface<R>] { &self.known_toplevels[..] } /// Access all the popup surfaces known by this handler pub fn popup_surfaces(&self) -> &[PopupSurface<R>] { &self.known_popups[..] } } impl IntoIterator for ToplevelStateSet { type Item = xdg_toplevel::State; type IntoIter = std::vec::IntoIter<Self::Item>; fn into_iter(self) -> Self::IntoIter { self.states.into_iter() } } impl From<ToplevelStateSet> for Vec<xdg_toplevel::State> { fn from(states: ToplevelStateSet) -> Self { states.states } } /// Represents the client pending state #[derive(Debug, Default, Clone, Copy)] pub struct SurfaceCachedState { /// Holds the double-buffered geometry that may be specified /// by xdg_surface.set_window_geometry. pub geometry: Option<Rectangle<i32, Logical>>, /// Minimum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// /// This is only relevant for xdg_toplevel, and will always be /// `(0, 0)` for xdg_popup. pub min_size: Size<i32, Logical>, /// Maximum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// You can use this handle to access a storage for any /// client-specific data you wish to associate with it. #[derive(Debug)] pub struct ShellClient<R> { kind: ShellClientKind, _token: CompositorToken<R>, } impl<R> ShellClient<R> where R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, { /// Is the shell client represented by this handle still connected? pub fn alive(&self) -> bool { match self.kind { *into = self; } } /// Xdg Shell handler type #[allow(unused_variables)] pub trait XdgShellHandler { /// [XdgShellState] getter fn xdg_shell_state(&mut self) -> &mut XdgShellState; /// A new shell client was instantiated fn new_client(&mut self, client: ShellClient) {} /// The pong for a pending ping of this shell client was received /// /// The `ShellHandler` already checked for you that the serial matches the one /// from the pending ping. fn client_pong(&mut self, client: ShellClient) {} /// A new toplevel surface was created /// /// You likely need to send a [`ToplevelConfigure`] to the surface, to hint the /// client as to how its window should be sized. fn new_toplevel(&mut self, surface: ToplevelSurface); /// A new popup surface was created /// /// You likely need to send a [`PopupConfigure`] to the surface, to hint the /// client as to how its popup should be sized. /// /// ## Arguments /// let user_data = shell .as_ref() .user_data() .get::<self::xdg_handlers::ShellUserData<R>>() .unwrap(); let mut guard = user_data.client_data.lock().unwrap(); if let Some(pending_ping) = guard.pending_ping { /// The client requested the start of an interactive move for this surface fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {} /// The client requested the start of an interactive resize for this surface fn resize_request( &mut self, let user_data = shell .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellUserData<R>>() .unwrap(); let mut guard = user_data.client_data.lock().unwrap(); if let Some(pending_ping) = guard.pending_ping { /// This popup requests a grab of the pointer /// /// This means it requests to be sent a `popup_done` event when the pointer leaves /// the grab area. fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial); /// A toplevel surface requested to be maximized fn maximize_request(&mut self, surface: ToplevelSurface) {} /// A toplevel surface requested to stop being maximized fn unmaximize_request(&mut self, surface: ToplevelSurface) {} /// A toplevel surface requested to be set fullscreen fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option<wl_output::WlOutput>) {} /// A toplevel surface request to stop being fullscreen fn unfullscreen_request(&mut self, surface: ToplevelSurface) {} /// A toplevel surface requested to be minimized let data = shell .as_ref() .user_data() .get::<self::xdg_handlers::ShellUserData<R>>() .unwrap(); let mut guard = data.client_data.lock().unwrap(); Ok(f(&mut guard.data)) &mut self, surface: ToplevelSurface, let data = shell .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellUserData<R>>() .unwrap(); let mut guard = data.client_data.lock().unwrap(); Ok(f(&mut guard.data)) fn ack_configure(&mut self, surface: wl_surface::WlSurface, configure: Configure) {} /// A client requested a reposition, providing a new /// positioner, of a popup. /// /// ## Arguments /// /// - `positioner` - The state of the positioner at the time the reposition request was made. /// The positioner state can be used by the compositor } /// A handle to a toplevel surface #[derive(Debug)] pub struct ToplevelSurface<R> { wl_surface: wl_surface::WlSurface, shell_surface: ToplevelKind, token: CompositorToken<R>, } // We implement Clone manually because #[derive(..)] would require R: Clone. impl<R> Clone for ToplevelSurface<R> { fn clone(&self) -> Self { Self { wl_surface: self.wl_surface.clone(), shell_surface: self.shell_surface.clone(), token: self.token, } } } impl<R> ToplevelSurface<R> where R: Role<XdgToplevelSurfaceRole> + 'static, { /// Is the toplevel surface referred by this handle still alive? pub fn alive(&self) -> bool { let shell_alive = match self.shell_surface { fn popup_destroyed(&mut self, surface: PopupSurface) {} } #[derive(Debug)] pub(crate) struct InnerState { known_toplevels: Vec<ToplevelSurface>, known_popups: Vec<PopupSurface>, } /// Shell global state /// /// Retrieve the shell client owning this toplevel surface /// /// Returns `None` if the surface does actually no longer exist. pub fn client(&self) -> Option<ShellClient<R>> { if !self.alive() { return None; } } impl XdgShellState { let data = s .as_ref() .user_data() .get::<self::xdg_handlers::ShellSurfaceUserData<R>>() .unwrap(); ShellClientKind::Xdg(data.wm_base.clone()) } let global = display.create_global::<D, XdgWmBase, _>(3, ()); let data = s .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellSurfaceUserData<R>>() .unwrap(); ShellClientKind::ZxdgV6(data.shell.clone()) } }; Some(ShellClient { kind: shell, _token: self.token, }) } /// Gets the current pending state for a configure cb(&self.inner.lock().unwrap().known_toplevels) } /// Returns a [`ToplevelSurface`] from an underlying toplevel surface. pub fn get_toplevel(&self, toplevel: &xdg_toplevel::XdgToplevel) -> Option<ToplevelSurface> { self.inner .lock() .unwrap() .known_toplevels .iter() .find(|surface| surface.xdg_toplevel() == toplevel) .cloned() } /// Access all the popup surfaces known by this handler pub fn popup_surfaces<T, F: FnMut(&[PopupSurface]) -> T>(&self, mut cb: F) -> T { cb(&self.inner.lock().unwrap().known_popups) } /// Returns a [`PopupSurface`] from an underlying popup surface. pub fn get_popup(&self, popup: &xdg_popup::XdgPopup) -> Option<PopupSurface> { self.inner .lock() .unwrap() .known_popups .iter() .find(|surface| surface.xdg_popup() == popup) .cloned() } /// Returns the xdg shell global. pub fn global(&self) -> GlobalId { self.global.clone() } } #[derive(Default, Debug)] /// The serial of this configure will be tracked waiting for the client to ACK it. pub fn send_configure(&self) { if let Some(surface) = self.get_surface() { let configure = self .token .with_role_data(surface, |role| { if let Some(attributes) = role { if let Some(pending) = self.get_pending_state(attributes) { let configure = ToplevelConfigure { serial: SERIAL_COUNTER.next_serial(), state: pending, }; attributes.pending_configures.push(configure.clone()); Some((configure, !attributes.initial_configure_sent)) } else { None } } else { None } }) .unwrap_or(None); if let Some((configure, initial)) = configure { match self.shell_surface { ToplevelKind::Xdg(ref s) => { self::xdg_handlers::send_toplevel_configure::<R>(s, configure) } ToplevelKind::ZxdgV6(ref s) => { self::zxdgv6_handlers::send_toplevel_configure::<R>(s, configure) } } if initial { self.token .with_role_data(surface, |role| { if let XdgToplevelSurfaceRole::Some(attributes) = role { attributes.initial_configure_sent = true; } }) .unwrap(); } } } } self.kind.alive() } /// /// This should be called when the underlying WlSurface /// handles a wl_surface.commit request. pub fn commit(&self) { if let Some(surface) = self.get_surface() { let send_initial_configure = self .token .with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| { if let XdgToplevelSurfaceRole::Some(attributes) = role { if let Some(window_geometry) = attributes.client_pending_window_geometry.take() { attributes.window_geometry = Some(window_geometry); } if attributes.initial_configure_sent { if let Some(state) = attributes.last_acked.clone() { if state != attributes.current { attributes.current = state; } } if let Some(mut client_pending) = attributes.client_pending.take() { // update state from the client that doesn't need compositor approval if let Some(size) = client_pending.max_size.take() { attributes.max_size = size; } if let Some(size) = client_pending.min_size.take() { attributes.min_size = size; } } } !attributes.initial_configure_sent } else { false } }) .unwrap_or(false); if send_initial_configure { self.send_configure(); } } } /// Make sure this surface was configured guard.pending_ping = Some(serial); self.kind.ping(serial.into()); Ok(()) } /// Access the user data associated with this shell client pub fn with_data<F, T>(&self, f: F) -> Result<T, crate::utils::DeadResource> if !self.alive() { return false; } let configured = self .token .with_role_data::<XdgToplevelSurfaceRole, _, _>(&self.wl_surface, |data| { data.as_ref() .expect("xdg_toplevel exists but role has been destroyed?!") .configured }) .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); if !configured { match self.shell_surface { ToplevelKind::Xdg(ref s) => { let data = s .as_ref() .user_data() .get::<self::xdg_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, } } impl ToplevelSurface { let data = s .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, self.shell_surface.version() } /// Retrieve the shell client owning this toplevel surface pub fn client(&self) -> ShellClient { let shell = { let data = self .shell_surface .data::<self::handlers::XdgShellSurfaceUserData>() .unwrap(); data.wm_base.clone() }; ShellClient { kind: shell } } /// Gets the current pending state for a configure /// /// Returns `Some` if either no initial configure has been sent or /// the `server_pending` is `Some` and different from the last pending /// configure or `last_acked` if there is no pending /// /// Returns `None` if either no `server_pending` or the pending /// has already been sent to the client or the pending is equal /// to the `last_acked` fn get_pending_state(&self, attributes: &mut XdgToplevelSurfaceRoleAttributes) -> Option<ToplevelState> { if !attributes.initial_configure_sent { return Some(attributes.server_pending.take().unwrap_or_default()); } // Check if the state really changed, it is possible // that with_pending_state has been called without // modifying the state. if !attributes.has_pending_changes() { /// /// The state will be sent to the client when calling /// send_configure. pub fn with_pending_state<F, T>(&self, f: F) -> Result<T, PendingStateError> where F: FnOnce(&mut ToplevelState) -> T, { if !self.alive() { return Err(PendingStateError::DeadSurface); } self.token .with_role_data(&self.wl_surface, |role| { if let Some(attributes) = role { if attributes.server_pending.is_none() { attributes.server_pending = Some(attributes.current.clone()); } let server_pending = attributes.server_pending.as_mut().unwrap(); Ok(f(server_pending)) } else { Err(PendingStateError::RoleDestroyed) } }) .unwrap() } } serial: SERIAL_COUNTER.next_serial(), state: pending, }; attributes.pending_configures.push(configure.clone()); attributes.initial_configure_sent = true; /// /// This is an unified abstraction over the popup surfaces /// of both `wl_shell` and `xdg_shell`. #[derive(Debug)] pub struct PopupSurface<R> { wl_surface: wl_surface::WlSurface, shell_surface: PopupKind, token: CompositorToken<R>, } // We implement Clone manually because #[derive(..)] would require R: Clone. impl<R> Clone for PopupSurface<R> { fn clone(&self) -> Self { Self { wl_surface: self.wl_surface.clone(), shell_surface: self.shell_surface.clone(), token: self.token, } } } impl<R> PopupSurface<R> where R: Role<XdgPopupSurfaceRole> + 'static, { /// Is the popup surface referred by this handle still alive? pub fn alive(&self) -> bool { let shell_alive = match self.shell_surface { .decoration_mode .unwrap_or(zxdg_toplevel_decoration_v1::Mode::ClientSide), ); } } } self::handlers::send_toplevel_configure(&self.shell_surface, configure) } if !self.alive() { None } else { self.token .with_role_data(&self.wl_surface, |role: &mut XdgPopupSurfaceRole| { role.as_ref().and_then(|role| role.parent.clone()) }) .unwrap() } } }); } /// Make sure this surface was configured /// /// Retrieve the shell client owning this popup surface /// /// Returns `None` if the surface does actually no longer exist. pub fn client(&self) -> Option<ShellClient<R>> { if !self.alive() { return None; } let configured = compositor::with_states(&self.wl_surface, |states| { states .data_map let data = p .as_ref() .user_data() .get::<self::xdg_handlers::ShellSurfaceUserData<R>>() .unwrap(); ShellClientKind::Xdg(data.wm_base.clone()) } let data = self let data = p .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellSurfaceUserData<R>>() .unwrap(); ShellClientKind::ZxdgV6(data.shell.clone()) } }; Some(ShellClient { kind: shell, _token: self.token, }) } /// Send a configure event to this popup surface to suggest it a new configuration self.shell_surface.close() /// The serial of this configure will be tracked waiting for the client to ACK it. pub fn send_configure(&self) { if let Some(surface) = self.get_surface() { if let Some((configure, initial)) = self .token .with_role_data(surface, |role| { if let XdgPopupSurfaceRole::Some(attributes) = role { if !attributes.initial_configure_sent || attributes.server_pending.is_some() { let pending = attributes.server_pending.take().unwrap_or(attributes.current); let configure = PopupConfigure { state: pending, serial: SERIAL_COUNTER.next_serial(), }; attributes.pending_configures.push(configure); Some((configure, !attributes.initial_configure_sent)) } else { None } } else { None } }) .unwrap_or(None) { match self.shell_surface { PopupKind::Xdg(ref p) => { self::xdg_handlers::send_popup_configure::<R>(p, configure); } PopupKind::ZxdgV6(ref p) => { self::zxdgv6_handlers::send_popup_configure::<R>(p, configure); } } if initial { self.token .with_role_data(surface, |role| { if let XdgPopupSurfaceRole::Some(attributes) = role { attributes.initial_configure_sent = true; } }) .unwrap(); } } } } /// Gets a copy of the current state of this toplevel pub fn current_state(&self) -> ToplevelState { /// /// This should be called when the underlying WlSurface /// handles a wl_surface.commit request. pub fn commit(&self) { if let Some(surface) = self.get_surface() { let has_parent = self .token .with_role_data(surface, |role| { if let Some(attributes) = role { attributes.parent.is_some() } else { false } }) .unwrap_or(false); if !has_parent { match self.shell_surface { PopupKind::Xdg(ref s) => { let data = s .as_ref() .user_data() .get::<self::xdg_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, "Surface has not been configured yet.".into(), ); } PopupKind::ZxdgV6(ref s) => { let data = s .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, "Surface has not been configured yet.".into(), ); } } return; } let send_initial_configure = self .token .with_role_data(surface, |role: &mut XdgPopupSurfaceRole| { if let XdgPopupSurfaceRole::Some(attributes) = role { if let Some(window_geometry) = attributes.client_pending_window_geometry.take() { attributes.window_geometry = Some(window_geometry); } if attributes.initial_configure_sent { if let Some(state) = attributes.last_acked { if state != attributes.current { attributes.current = state; } } if attributes.window_geometry.is_none() { attributes.window_geometry = Some(Rectangle { width: attributes.current.geometry.width, height: attributes.current.geometry.height, ..Default::default() }) } } !attributes.initial_configure_sent } else { false } }) .unwrap_or(false); if send_initial_configure { self.send_configure(); } } } /// Make sure this surface was configured } impl PopupSurface { /// Is the toplevel surface referred by this handle still alive? pub fn alive(&self) -> bool { self.wl_surface.alive() && self.shell_surface.alive() } if !self.alive() { return false; } let configured = self .token .with_role_data::<XdgPopupSurfaceRole, _, _>(&self.wl_surface, |data| { data.as_ref() .expect("xdg_popup exists but role has been destroyed?!") .configured }) .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); if !configured { match self.shell_surface { PopupKind::Xdg(ref s) => { let data = s .as_ref() .user_data() .get::<self::xdg_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, }; ShellClient { kind: shell } } let data = s .as_ref() .user_data() .get::<self::zxdgv6_handlers::ShellSurfaceUserData<R>>() .unwrap(); data.xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, /// logic for both [`XdgRequest::send_configure`] and [`XdgRequest::send_repositioned`] fn send_configure_internal(&self, reposition_token: Option<u32>) { let next_configure = compositor::with_states(&self.wl_surface, |states| { let mut attributes = states .data_map .get::<XdgPopupSurfaceData>() .unwrap() .lock() .unwrap(); if !attributes.initial_configure_sent || attributes.has_pending_changes() || reposition_token.is_some() { let pending = attributes .server_pending .take() .unwrap_or_else(|| *attributes.current_server_state()); let configure = PopupConfigure { state: pending, serial: SERIAL_COUNTER.next_serial(), reposition_token, }; attributes.pending_configures.push(configure); attributes.initial_configure_sent = true; Some(configure) } else { None } }); if let Some(configure) = next_configure { self::handlers::send_popup_configure(&self.shell_surface, configure); } } /// /// The state will be sent to the client when calling /// send_configure. pub fn with_pending_state<F, T>(&self, f: F) -> Result<T, PendingStateError> where F: FnOnce(&mut PopupState) -> T, { if !self.alive() { return Err(PendingStateError::DeadSurface); } self.token .with_role_data(&self.wl_surface, |role| { if let Some(attributes) = role { if attributes.server_pending.is_none() { attributes.server_pending = Some(attributes.current); } let server_pending = attributes.server_pending.as_mut().unwrap(); Ok(f(server_pending)) } else { Err(PendingStateError::RoleDestroyed) } }) .unwrap() } } if attributes.initial_configure_sent && !is_reactive { // Return error, the positioner does not allow re-configure return Err(PopupConfigureError::NotReactive); } Ok(()) })?; self.send_configure_internal(None); Ok(()) } /// Send a configure event, including the `repositioned` event to the client /// in response to a `reposition` request. /// /// For further information see [`send_configure`](#method.send_configure) pub fn send_repositioned(&self, token: u32) { self.send_configure_internal(Some(token)) } /// Handles the role specific commit logic /// /// This should be called when the underlying WlSurface /// handles a wl_surface.commit request. pub(crate) fn commit_hook(_dh: &DisplayHandle, surface: &wl_surface::WlSurface) { let send_error_to = compositor::with_states(surface, |states| { let attributes = states .data_map .get::<XdgPopupSurfaceData>() .unwrap() .lock() .unwrap(); if attributes.parent.is_none() { attributes.popup_handle.clone() } else { None } }); if let Some(handle) = send_error_to { let data = handle.data::<self::handlers::XdgShellSurfaceUserData>().unwrap(); data.xdg_surface.post_error( xdg_surface::Error::NotConstructed, "Surface has not been configured yet.", ); return; } compositor::with_states(surface, |states| { let mut attributes = states .data_map .get::<XdgPopupSurfaceData>() .unwrap() .lock() .unwrap(); attributes.committed = true; /// /// Depending on what you want to do, you might ignore some of them #[derive(Debug)] pub enum XdgRequest<R> { /// A new shell client was instantiated NewClient { /// the client client: ShellClient<R>, }, /// The pong for a pending ping of this shell client was received /// /// Make sure this surface was configured /// from the pending ping. ClientPong { /// the client client: ShellClient<R>, }, /// A new toplevel surface was created /// pub fn ensure_configured(&self) -> bool { /// client as to how its window should be NewToplevel { /// the surface surface: ToplevelSurface<R>, }, /// A new popup surface was created /// .configured /// client as to how its popup should be NewPopup { /// the surface surface: PopupSurface<R>, }, /// The client requested the start of an interactive move for this surface Move { /// the surface surface: ToplevelSurface<R>, /// the seat associated to this move seat: wl_seat::WlSeat, /// the grab serial } /// The client requested the start of an interactive resize for this surface Resize { /// The surface surface: ToplevelSurface<R>, /// The seat associated with this resize seat: wl_seat::WlSeat, /// The grab serial /// Access the underlying `wl_surface` of this popup surface pub fn wl_surface(&self) -> &wl_surface::WlSurface { &self.wl_surface } /// Access the underlying `xdg_popup` of this popup surface /// the grab area. Grab { /// The surface surface: PopupSurface<R>, /// The seat to grab seat: wl_seat::WlSeat, /// The grab serial /// This should be used to inform the client about size and position changes, /// for example after a move of the parent toplevel. /// A toplevel surface requested to be maximized Maximize { /// The surface surface: ToplevelSurface<R>, }, /// A toplevel surface requested to stop being maximized UnMaximize { /// The surface surface: ToplevelSurface<R>, }, /// A toplevel surface requested to be set fullscreen Fullscreen { /// The surface surface: ToplevelSurface<R>, /// The output (if any) on which the fullscreen is requested output: Option<wl_output::WlOutput>, }, /// A toplevel surface request to stop being fullscreen UnFullscreen { /// The surface surface: ToplevelSurface<R>, }, /// A toplevel surface requested to be minimized Minimize { /// The surface surface: ToplevelSurface<R>, }, /// The client requests the window menu to be displayed on this surface at this location /// /// has processed an ack_configure request /// control of the window (maximize/minimize/close/move/etc...). ShowWindowMenu { /// The surface surface: ToplevelSurface<R>, /// The seat associated with this input grab seat: wl_seat::WlSeat, /// the grab serial fn from(configure: ToplevelConfigure) -> Self { Configure::Toplevel(configure) } } impl From<PopupConfigure> for Configure { fn from(configure: PopupConfigure) -> Self { Configure::Popup(configure) } } #[allow(missing_docs)] // TODO #[macro_export] macro_rules! delegate_xdg_shell { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_wm_base::XdgWmBase: () ] => $crate::wayland::shell::xdg::XdgShellState); $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_wm_base::XdgWmBase: $crate::wayland::shell::xdg::XdgWmBaseUserData ] => $crate::wayland::shell::xdg::XdgShellState); $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_positioner::XdgPositioner: $crate::wayland::shell::xdg::XdgPositionerUserData ] => $crate::wayland::shell::xdg::XdgShellState); $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup: $crate::wayland::shell::xdg::XdgShellSurfaceUserData ] => $crate::wayland::shell::xdg::XdgShellState); $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_surface::XdgSurface: $crate::wayland::shell::xdg::XdgSurfaceUserData ] => $crate::wayland::shell::xdg::XdgShellState); $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel: $crate::wayland::shell::xdg::XdgShellSurfaceUserData ] => $crate::wayland::shell::xdg::XdgShellState); }; } <MSG> compositor: Introduce transaction state tracking This changes the state handling logic of wl_surface to automatically track subsurface pending in the form of transactions. The role enum (and the associated type parameter) is no more, and replaced by a general-purpose typemap-like container on surfaces. The new logic is introduced in the files: - `src/wayland/compositor/cache.rs` - `src/wayland/compositor/transaction.rs` The rest of the PR is the fallout of these changes, as well as a few trivial clippy fixes. <DFF> @@ -21,26 +21,13 @@ //! //! ### Initialization //! -//! To initialize this handler, simple use the [`xdg_shell_init`] function provided in this -//! module. You will need to provide it the [`CompositorToken`](crate::wayland::compositor::CompositorToken) -//! you retrieved from an instantiation of the compositor global provided by smithay. +//! To initialize this handler, simple use the [`xdg_shell_init`] function provided in this module. //! //! ```no_run //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! # extern crate wayland_protocols; //! # -//! use smithay::wayland::compositor::roles::*; -//! use smithay::wayland::compositor::CompositorToken; -//! use smithay::wayland::shell::xdg::{xdg_shell_init, XdgToplevelSurfaceRole, XdgPopupSurfaceRole, XdgRequest}; -//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6; -//! # use wayland_server::protocol::{wl_seat, wl_output}; +//! use smithay::wayland::shell::xdg::{xdg_shell_init, XdgRequest}; //! -//! // define the roles type. You need to integrate the XdgSurface role: -//! define_roles!(MyRoles => -//! [XdgToplevelSurface, XdgToplevelSurfaceRole] -//! [XdgPopupSurface, XdgPopupSurfaceRole] -//! ); //! //! // define the metadata you want associated with the shell clients //! #[derive(Default)] @@ -49,17 +36,10 @@ //! } //! //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<MyRoles, _, _>( -//! # &mut display, -//! # |_, _, _, _| {}, -//! # None -//! # ); //! let (shell_state, _, _) = xdg_shell_init( //! &mut display, -//! // token from the compositor implementation -//! compositor_token, //! // your implementation -//! |event: XdgRequest<_>| { /* ... */ }, +//! |event: XdgRequest| { /* ... */ }, //! None // put a logger if you want //! ); //! @@ -87,9 +67,10 @@ //! the subhandler you provided, or via methods on the [`ShellState`] //! that you are given (in an `Arc<Mutex<_>>`) as return value of the `init` function. +use crate::utils::DeadResource; use crate::utils::Rectangle; -use crate::wayland::compositor::roles::WrongRole; -use crate::wayland::compositor::{roles::Role, CompositorToken}; +use crate::wayland::compositor; +use crate::wayland::compositor::Cacheable; use crate::wayland::{Serial, SERIAL_COUNTER}; use std::fmt::Debug; use std::{ @@ -97,7 +78,6 @@ use std::{ rc::Rc, sync::{Arc, Mutex}, }; -use thiserror::Error; use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_surface_v6; use wayland_protocols::xdg_shell::server::xdg_surface; use wayland_protocols::{ @@ -117,24 +97,12 @@ mod xdg_handlers; mod zxdgv6_handlers; macro_rules! xdg_role { - ($(#[$role_attr:meta])* $role:ident, - $configure:ty, + ($configure:ty, $(#[$attr:meta])* $element:ident {$($(#[$field_attr:meta])* $vis:vis$field:ident:$type:ty),*}, $role_ack_configure:expr) => { - $(#[$role_attr])* - pub type $role = Option<$element>; $(#[$attr])* pub struct $element { - /// Defines the current window geometry of the surface if any. - pub window_geometry: Option<Rectangle>, - - /// Holds the double-buffered geometry that may be specified - /// by xdg_surface.set_window_geometry. This should be moved to the - /// current configuration on a commit of the underlying - /// wl_surface. - pub client_pending_window_geometry: Option<Rectangle>, - /// Defines if the surface has received at least one /// xdg_surface.ack_configure from the client pub configured: bool, @@ -161,12 +129,8 @@ macro_rules! xdg_role { )* } - impl XdgSurface for $element { - fn set_window_geometry(&mut self, geometry: Rectangle) { - self.client_pending_window_geometry = Some(geometry); - } - - fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError> { + impl $element { + fn ack_configure(&mut self, serial: Serial) -> Option<Configure> { let configure = match self .pending_configures .iter() @@ -174,7 +138,7 @@ macro_rules! xdg_role { { Some(configure) => (*configure).clone(), None => { - return Err(ConfigureError::SerialNotFound(serial)); + return None; } }; @@ -191,15 +155,13 @@ macro_rules! xdg_role { // Clean old configures self.pending_configures.retain(|c| c.serial > serial); - Ok(configure.into()) + Some(configure.into()) } } impl Default for $element { fn default() -> Self { Self { - window_geometry: None, - client_pending_window_geometry: None, configured: false, configure_serial: None, pending_configures: Vec::new(), @@ -214,52 +176,10 @@ macro_rules! xdg_role { }; } -impl<R> CompositorToken<R> -where - R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, -{ - fn with_xdg_role<F, T>(&self, surface: &wl_surface::WlSurface, f: F) -> Result<T, WrongRole> - where - F: FnOnce(&mut dyn XdgSurface) -> T, - { - if self.has_role::<XdgToplevelSurfaceRole>(surface) { - self.with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| f(role)) - } else { - self.with_role_data(surface, |role: &mut XdgPopupSurfaceRole| f(role)) - } - } -} - -trait XdgSurface { - fn set_window_geometry(&mut self, geometry: Rectangle); - - fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError>; -} - -#[derive(Debug, Error)] -enum ConfigureError { - #[error("the serial `{0:?}` was not found in the pending configure list")] - SerialNotFound(Serial), -} - -impl<T> XdgSurface for Option<T> -where - T: XdgSurface, -{ - fn set_window_geometry(&mut self, geometry: Rectangle) { - self.as_mut() - .expect("xdg_surface exists but role has been destroyed?!") - .set_window_geometry(geometry) - } - - fn ack_configure(&mut self, serial: Serial) -> Result<Configure, ConfigureError> { - self.as_mut() - .expect("xdg_surface exists but role has been destroyed?!") - .ack_configure(serial) - } -} - xdg_role!( + ToplevelConfigure, + /// Role specific attributes for xdg_toplevel + /// /// This interface defines an xdg_surface role which allows a surface to, /// among other things, set window-like properties such as maximize, /// fullscreen, and minimize, set application-specific metadata like title and @@ -277,9 +197,6 @@ xdg_role!( /// xdg_surface description). /// /// Attaching a null buffer to a toplevel unmaps the surface. - XdgToplevelSurfaceRole, - ToplevelConfigure, - /// Role specific attributes for xdg_toplevel XdgToplevelSurfaceRoleAttributes { /// The parent field of a toplevel should be used /// by the compositor to determine which toplevel @@ -312,8 +229,6 @@ xdg_role!( /// /// A value of 0 on an axis means this axis is not constrained pub max_size: (i32, i32), - /// Holds the pending state as set by the client. - pub client_pending: Option<ToplevelClientPending>, /// Holds the pending state as set by the server. pub server_pending: Option<ToplevelState>, /// Holds the last server_pending state that has been acknowledged @@ -330,6 +245,9 @@ xdg_role!( ); xdg_role!( + PopupConfigure, + /// Role specific attributes for xdg_popup + /// /// A popup surface is a short-lived, temporary surface. It can be used to /// implement for example menus, popovers, tooltips and other similar user /// interface concepts. @@ -354,9 +272,6 @@ xdg_role!( /// /// The client must call wl_surface.commit on the corresponding wl_surface /// for the xdg_popup state to take effect. - XdgPopupSurfaceRole, - PopupConfigure, - /// Popup Surface Role XdgPopupSurfaceRoleAttributes { /// Holds the parent for the xdg_popup. /// @@ -383,7 +298,8 @@ xdg_role!( /// commit. pub current: PopupState, /// Holds the pending state as set by the server. - pub server_pending: Option<PopupState> + pub server_pending: Option<PopupState>, + popup_handle: Option<PopupKind> }, |attributes,configure| { attributes.last_acked = Some(configure.state); @@ -687,57 +603,55 @@ impl From<ToplevelStateSet> for Vec<xdg_toplevel::State> { /// Represents the client pending state #[derive(Debug, Clone, Copy)] -pub struct ToplevelClientPending { +pub struct SurfaceCachedState { + /// Holds the double-buffered geometry that may be specified + /// by xdg_surface.set_window_geometry. + pub geometry: Option<Rectangle>, /// Minimum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// - /// A value of `None` represents the client has not defined - /// a minimum size - pub min_size: Option<(i32, i32)>, + /// This is only relevant for xdg_toplevel, and will always be + /// `(0, 0)` for xdg_popup. + pub min_size: (i32, i32), /// Maximum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// - /// A value of `None` represents the client has not defined - /// a maximum size - pub max_size: Option<(i32, i32)>, + /// This is only relevant for xdg_toplevel, and will always be + /// `(0, 0)` for xdg_popup. + pub max_size: (i32, i32), } -impl Default for ToplevelClientPending { +impl Default for SurfaceCachedState { fn default() -> Self { Self { - min_size: None, - max_size: None, + geometry: None, + min_size: (0, 0), + max_size: (0, 0), } } } -/// Represents the possible errors -/// returned from `ToplevelSurface::with_pending_state` -/// and `PopupSurface::with_pending_state` -#[derive(Debug, Error)] -pub enum PendingStateError { - /// The operation failed because the underlying surface has been destroyed - #[error("could not retrieve the pending state because the underlying surface has been destroyed")] - DeadSurface, - /// The role of the xdg_surface has been destroyed - #[error("the role of the xdg_surface has been destroyed")] - RoleDestroyed, +impl Cacheable for SurfaceCachedState { + fn commit(&mut self) -> Self { + *self + } + fn merge_into(self, into: &mut Self) { + *into = self; + } } -pub(crate) struct ShellData<R> { +pub(crate) struct ShellData { log: ::slog::Logger, - compositor_token: CompositorToken<R>, - user_impl: Rc<RefCell<dyn FnMut(XdgRequest<R>)>>, - shell_state: Arc<Mutex<ShellState<R>>>, + user_impl: Rc<RefCell<dyn FnMut(XdgRequest)>>, + shell_state: Arc<Mutex<ShellState>>, } -impl<R> Clone for ShellData<R> { +impl Clone for ShellData { fn clone(&self) -> Self { ShellData { log: self.log.clone(), - compositor_token: self.compositor_token, user_impl: self.user_impl.clone(), shell_state: self.shell_state.clone(), } @@ -745,20 +659,18 @@ impl<R> Clone for ShellData<R> { } /// Create a new `xdg_shell` globals -pub fn xdg_shell_init<R, L, Impl>( +pub fn xdg_shell_init<L, Impl>( display: &mut Display, - ctoken: CompositorToken<R>, implementation: Impl, logger: L, ) -> ( - Arc<Mutex<ShellState<R>>>, + Arc<Mutex<ShellState>>, Global<xdg_wm_base::XdgWmBase>, Global<zxdg_shell_v6::ZxdgShellV6>, ) where - R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, L: Into<Option<::slog::Logger>>, - Impl: FnMut(XdgRequest<R>) + 'static, + Impl: FnMut(XdgRequest) + 'static, { let log = crate::slog_or_fallback(logger); let shell_state = Arc::new(Mutex::new(ShellState { @@ -768,7 +680,6 @@ where let shell_data = ShellData { log: log.new(o!("smithay_module" => "xdg_shell_handler")), - compositor_token: ctoken, user_impl: Rc::new(RefCell::new(implementation)), shell_state: shell_state.clone(), }; @@ -797,22 +708,19 @@ where /// This state allows you to retrieve a list of surfaces /// currently known to the shell global. #[derive(Debug)] -pub struct ShellState<R> { - known_toplevels: Vec<ToplevelSurface<R>>, - known_popups: Vec<PopupSurface<R>>, +pub struct ShellState { + known_toplevels: Vec<ToplevelSurface>, + known_popups: Vec<PopupSurface>, } -impl<R> ShellState<R> -where - R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static, -{ +impl ShellState { /// Access all the shell surfaces known by this handler - pub fn toplevel_surfaces(&self) -> &[ToplevelSurface<R>] { + pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] {
compositor: Introduce transaction state tracking