diff --git a/src/wayland/shell/xdg/decoration.rs b/src/wayland/shell/xdg/decoration.rs new file mode 100644 index 0000000..0a4e8f8 --- /dev/null +++ b/src/wayland/shell/xdg/decoration.rs @@ -0,0 +1,153 @@ +//! XDG Window decoration manager +//! +//! This interface allows a compositor to announce support for server-side decorations. +//! +//! A client can use this protocol to request being decorated by a supporting compositor. +//! +//! +//! ```no_run +//! # extern crate wayland_server; +//! # +//! use smithay::wayland::shell::xdg::decoration::{init_xdg_decoration_manager, XdgDecorationRequest}; +//! use smithay::reexports::wayland_protocols::unstable::xdg_decoration::v1::server::zxdg_toplevel_decoration_v1::Mode; +//! +//! # let mut display = wayland_server::Display::new(); +//! +//! init_xdg_decoration_manager( +//! &mut display, +//! |req, _ddata| match req { +//! XdgDecorationRequest::NewToplevelDecoration { toplevel } => { +//! let res = toplevel.with_pending_state(|state| { +//! // Advertise server side decoration +//! state.decoration_mode = Some(Mode::ServerSide); +//! }); +//! +//! if res.is_ok() { +//! toplevel.send_configure(); +//! } +//! } +//! XdgDecorationRequest::SetMode { .. } => {} +//! XdgDecorationRequest::UnsetMode { .. } => {} +//! }, +//! None, +//! ); +//! + +use std::{cell::RefCell, ops::Deref, rc::Rc}; +use wayland_protocols::unstable::xdg_decoration::v1::server::{ + zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1}, + zxdg_toplevel_decoration_v1::{self, Mode, ZxdgToplevelDecorationV1}, +}; +use wayland_server::{DispatchData, Display, Filter, Global, Main}; + +use super::ToplevelSurface; +use crate::wayland::shell::xdg::xdg_handlers::ShellSurfaceUserData; + +/// Events generated by xdg decoration manager +#[derive(Debug)] +pub enum XdgDecorationRequest { + /// A new toplevel decoration was instantiated + NewToplevelDecoration { + /// The toplevel asosiated with decoration + toplevel: ToplevelSurface, + }, + /// Informs the compositor that the client prefers the provided decoration mode. + SetMode { + /// The toplevel asosiated with decoration + toplevel: ToplevelSurface, + /// The decoration mode + mode: Mode, + }, + /// Informs the compositor that the client doesn't prefer a particular decoration mode. + UnsetMode { + /// The toplevel asosiated with decoration + toplevel: ToplevelSurface, + }, +} + +/// Create a new XDG Decoration Manager global +pub fn init_xdg_decoration_manager( + display: &mut Display, + implementation: Impl, + _logger: L, +) -> Global +where + L: Into>, + Impl: FnMut(XdgDecorationRequest, DispatchData<'_>) + 'static, +{ + let cb = Rc::new(RefCell::new(implementation)); + display.create_global( + 1, + Filter::new( + move |(manager, _version): (Main, _), _, _| { + let cb = cb.clone(); + manager.quick_assign(move |_manager, request, ddata| { + match request { + zxdg_decoration_manager_v1::Request::Destroy => { + // All is handled by destructor. + } + zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => { + if let Some(data) = toplevel.as_ref().user_data().get::() { + if data.decoration.borrow().is_none() { + *data.decoration.borrow_mut() = Some(id.deref().clone()); + } else { + use wayland_protocols::unstable::xdg_decoration::v1::server::zxdg_toplevel_decoration_v1::Error; + id.as_ref().post_error(Error::AlreadyConstructed as u32, "toplevel decoration is already constructed".to_string()); + } + + let toplevel = ToplevelSurface { + shell_surface: toplevel.clone(), + wl_surface: data.wl_surface.clone(), + }; + + (&mut *cb.borrow_mut())( + XdgDecorationRequest::NewToplevelDecoration { + toplevel: toplevel.clone(), + }, + ddata, + ); + + let cb = cb.clone(); + id.quick_assign(move |_, request, ddata| match request { + zxdg_toplevel_decoration_v1::Request::SetMode { mode } => { + (&mut *cb.borrow_mut())( + XdgDecorationRequest::SetMode { + toplevel: toplevel.clone(), + mode, + }, + ddata, + ); + } + zxdg_toplevel_decoration_v1::Request::UnsetMode => { + (&mut *cb.borrow_mut())( + XdgDecorationRequest::UnsetMode { + toplevel: toplevel.clone(), + }, + ddata, + ); + } + _ => {} + }); + } + + id.assign_destructor(Filter::new( + move |_decoration: ZxdgToplevelDecorationV1, _, _| { + if let Some(data) = + toplevel.as_ref().user_data().get::() + { + *data.decoration.borrow_mut() = None; + } + }, + )); + } + _ => unreachable!(), + } + }); + }, + ), + ) +} + +pub(super) fn send_decoration_configure(id: &ZxdgToplevelDecorationV1, mode: Mode) { + id.configure(mode) +} diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index 9fc8dee..e29a826 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -74,6 +74,8 @@ use std::{ sync::{Arc, Mutex}, }; +use wayland_protocols::unstable::xdg_decoration; +use wayland_protocols::unstable::xdg_decoration::v1::server::zxdg_toplevel_decoration_v1; use wayland_protocols::xdg_shell::server::xdg_surface; use wayland_protocols::xdg_shell::server::{xdg_popup, xdg_positioner, xdg_toplevel, xdg_wm_base}; use wayland_server::DispatchData; @@ -82,8 +84,12 @@ use wayland_server::{ Display, Filter, Global, UserDataMap, }; +use self::xdg_handlers::ShellSurfaceUserData; + use super::PingError; +pub mod decoration; + // handlers for the xdg_shell protocol pub(super) mod xdg_handlers; @@ -549,6 +555,9 @@ pub struct ToplevelState { /// The output for a fullscreen display pub fullscreen_output: Option, + + /// The xdg decoration mode of the surface + pub decoration_mode: Option, } impl Default for ToplevelState { @@ -557,6 +566,7 @@ impl Default for ToplevelState { fullscreen_output: None, states: Default::default(), size: None, + decoration_mode: None, } } } @@ -567,6 +577,7 @@ impl Clone for ToplevelState { fullscreen_output: self.fullscreen_output.clone(), states: self.states.clone(), size: self.size, + decoration_mode: self.decoration_mode, } } } @@ -980,6 +991,35 @@ impl ToplevelSurface { }) .unwrap_or(None); if let Some(configure) = configure { + let decoration_mode = compositor::with_states(surface, |states| { + let attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attributes.current.decoration_mode.clone() + }) + .unwrap_or(None); + + if configure.state.decoration_mode != decoration_mode { + if let Some(data) = self + .shell_surface + .as_ref() + .user_data() + .get::() + { + if let Some(decoration) = &*data.decoration.borrow() { + self::decoration::send_decoration_configure( + decoration, + configure.state.decoration_mode.unwrap_or( + xdg_decoration::v1::server::zxdg_toplevel_decoration_v1::Mode::ClientSide, + ), + ); + } + } + } + self::xdg_handlers::send_toplevel_configure(&self.shell_surface, configure) } } diff --git a/src/wayland/shell/xdg/xdg_handlers.rs b/src/wayland/shell/xdg/xdg_handlers.rs index 5ec06e3..ce57b2b 100644 --- a/src/wayland/shell/xdg/xdg_handlers.rs +++ b/src/wayland/shell/xdg/xdg_handlers.rs @@ -4,6 +4,7 @@ use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use crate::wayland::compositor; use crate::wayland::shell::xdg::{PopupState, XDG_POPUP_ROLE, XDG_TOPLEVEL_ROLE}; use crate::wayland::Serial; +use wayland_protocols::unstable::xdg_decoration::v1::server::zxdg_toplevel_decoration_v1; use wayland_protocols::xdg_shell::server::{ xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, }; @@ -259,6 +260,7 @@ fn xdg_surface_implementation( wl_surface: data.wl_surface.clone(), xdg_surface: xdg_surface.clone(), wm_base: data.wm_base.clone(), + decoration: Default::default(), }); data.shell_data @@ -334,6 +336,7 @@ fn xdg_surface_implementation( wl_surface: data.wl_surface.clone(), xdg_surface: xdg_surface.clone(), wm_base: data.wm_base.clone(), + decoration: Default::default(), }); data.shell_data @@ -471,6 +474,7 @@ pub(crate) struct ShellSurfaceUserData { pub(crate) wl_surface: wl_surface::WlSurface, pub(crate) wm_base: xdg_wm_base::XdgWmBase, pub(crate) xdg_surface: xdg_surface::XdgSurface, + pub(crate) decoration: RefCell>, } // Utility functions allowing to factor out a lot of the upcoming logic