From b8d4b17a8a2aab575864ead036bc8fbbc10e44a8 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 18 Apr 2018 12:51:08 +0200 Subject: [PATCH] wayland.shell: split wl and xdg & port xdg --- Cargo.toml | 2 +- src/wayland/mod.rs | 2 +- src/wayland/shell/legacy/mod.rs | 1 + src/wayland/shell/{ => legacy}/wl_handlers.rs | 0 src/wayland/shell/mod.rs | 942 +----------------- src/wayland/shell/xdg/mod.rs | 808 +++++++++++++++ src/wayland/shell/xdg/xdg_handlers.rs | 748 ++++++++++++++ src/wayland/shell/xdg_handlers.rs | 697 ------------- 8 files changed, 1561 insertions(+), 1639 deletions(-) create mode 100644 src/wayland/shell/legacy/mod.rs rename src/wayland/shell/{ => legacy}/wl_handlers.rs (100%) create mode 100644 src/wayland/shell/xdg/mod.rs create mode 100644 src/wayland/shell/xdg/xdg_handlers.rs delete mode 100644 src/wayland/shell/xdg_handlers.rs diff --git a/Cargo.toml b/Cargo.toml index eace2cc..05b4d8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ slog-async = "2.2" rand = "0.3" [features] -default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "backend_session_logind"] +default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium"] backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] backend_drm = ["drm", "gbm"] backend_libinput = ["input"] diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index fa9ae4c..ccdb2d9 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -22,4 +22,4 @@ pub mod compositor; pub mod output; pub mod seat; pub mod shm; -//pub mod shell; +pub mod shell; diff --git a/src/wayland/shell/legacy/mod.rs b/src/wayland/shell/legacy/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/wayland/shell/legacy/mod.rs @@ -0,0 +1 @@ + diff --git a/src/wayland/shell/wl_handlers.rs b/src/wayland/shell/legacy/wl_handlers.rs similarity index 100% rename from src/wayland/shell/wl_handlers.rs rename to src/wayland/shell/legacy/wl_handlers.rs diff --git a/src/wayland/shell/mod.rs b/src/wayland/shell/mod.rs index c3c7fe7..e34306c 100644 --- a/src/wayland/shell/mod.rs +++ b/src/wayland/shell/mod.rs @@ -1,940 +1,2 @@ -//! Utilities for handling shell surfaces, toplevel and popups -//! -//! This module provides automatic handling of shell surfaces objects, by being registered -//! as a global handler for `wl_shell` and `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 also includes a compatibility -//! layer for the deprecated `wl_shell` global. -//! -//! 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 maneer. 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 `shell_init` function provided in this -//! module. You will need to provide it the `CompositorToken` you retrieved from an -//! instanciation of the `CompositorHandler` 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::{shell_init, ShellSurfaceRole, ShellSurfaceUserImplementation}; -//! use wayland_server::protocol::wl_shell::WlShell; -//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6; -//! use wayland_server::{EventLoop, EventLoopHandle}; -//! # use wayland_server::protocol::{wl_seat, wl_output}; -//! # use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_toplevel_v6; -//! # #[derive(Default)] struct MySurfaceData; -//! -//! // define the roles type. You need to integrate the ShellSurface role: -//! define_roles!(MyRoles => -//! [ShellSurface, ShellSurfaceRole] -//! ); -//! -//! // define the metadata you want associated with the shell clients -//! #[derive(Default)] -//! struct MyShellData { -//! /* ... */ -//! } -//! -//! # fn main() { -//! # let (_display, mut event_loop) = wayland_server::create_display(); -//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<(), MyRoles, _, _>( -//! # &mut event_loop, -//! # unimplemented!(), -//! # (), -//! # None -//! # ); -//! // define your implementation for shell -//! let my_shell_implementation = ShellSurfaceUserImplementation { -//! new_client: |evlh, idata, client| { unimplemented!() }, -//! client_pong: |evlh, idata, client| { unimplemented!() }, -//! new_toplevel: |evlh, idata, toplevel| { unimplemented!() }, -//! new_popup: |evlh, idata, popup| { unimplemented!() }, -//! move_: |evlh, idata, toplevel, seat, serial| { unimplemented!() }, -//! resize: |evlh, idata, toplevel, seat, serial, edges| { unimplemented!() }, -//! grab: |evlh, idata, popup, seat, serial| { unimplemented!() }, -//! change_display_state: |evlh, idata, toplevel, maximized, minimized, fullscreen, output| { -//! unimplemented!() -//! }, -//! show_window_menu: |evlh, idata, toplevel, seat, serial, x, y| { unimplemented!() }, -//! }; -//! -//! // define your implementation data -//! let my_shell_implementation_data = (); -//! -//! let (shell_state_token, _, _) = shell_init::<_, _, _, _, MyShellData, _>( -//! &mut event_loop, -//! compositor_token, // token from the compositor implementation -//! my_shell_implementation, // instance of shell::ShellSurfaceUserImplementation -//! my_shell_implementation_data, // whatever data you need here -//! None // put a logger if you want -//! ); -//! -//! // You're now ready to go! -//! # } -//! ``` -//! -//! ### Access to shell surface and clients data -//! -//! There are mainly 3 kind of objects that you'll manipulate from this implementation: -//! -//! - `ShellClient`: This is a handle representing an isntanciation of a shell global -//! you can associate client-wise metadata to it (this is the `MyShellData` type in -//! the example above). -//! - `ToplevelSurface`: This is a handle representing a toplevel surface, you can -//! retrive a list of all currently alive toplevel surface from the `ShellState`. -//! - `PopupSurface`: This is a handle representing a popup/tooltip surface. Similarly, -//! you can get a list of all currently alive popup surface from the `ShellState`. -//! -//! You'll obtain these objects though two means: either via the callback methods of -//! the subhandler you provided, or via methods on the `ShellState` that you can -//! access from the `state()` of the event loop and the token returned by the init -//! function. - -use std::cell::RefCell; -use std::rc::Rc; -use utils::Rectangle; -use wayland::compositor::CompositorToken; -use wayland::compositor::roles::Role; -use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, - zxdg_positioner_v6 as xdg_positioner, - zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6}; -use wayland_server::{EventLoopHandle, EventResult, Global, Liveness, Resource, StateToken}; -use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}; - -mod wl_handlers; -mod xdg_handlers; - -/// Metadata associated with the `shell_surface` role -pub struct ShellSurfaceRole { - /// Pending state as requested by the client - /// - /// The data in this field are double-buffered, you should - /// apply them on a surface commit. - pub pending_state: ShellSurfacePendingState, - /// Geometry of the surface - /// - /// Defines, in surface relative coordinates, what should - /// be considered as "the surface itself", regarding focus, - /// window alignment, etc... - /// - /// By default, you should consider the full contents of the - /// buffers of this surface and its subsurfaces. - pub window_geometry: Option, - /// List of non-acked configures pending - /// - /// Whenever a configure is acked by the client, all configure - /// older than it are discarded as well. As such, this vec contains - /// the serials of all the configure send to this surface that are - /// newer than the last ack received. - pub pending_configures: Vec, - /// Has this surface acked at least one configure? - /// - /// xdg_shell defines it as illegal to commit on a surface that has - /// not yet acked a configure. - pub configured: bool, -} - -#[derive(Copy, Clone, Debug)] -/// 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: (i32, i32), - /// Anchor rectangle in the parent surface coordinates - /// relative to which the surface must be positioned - pub anchor_rect: Rectangle, - /// 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 criterias constraint the - /// surface - pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, - /// Offset placement relative to the anchor point - pub offset: (i32, i32), -} - -/// Contents of the pending state of a shell surface, depending on its role -pub enum ShellSurfacePendingState { - /// This a regular, toplevel surface - /// - /// This corresponds to either the `xdg_toplevel` role from the - /// `xdg_shell` protocol, or the result of `set_toplevel` using the - /// `wl_shell` protocol. - /// - /// This is what you'll generaly interpret as "a window". - Toplevel(ToplevelState), - /// This is a popup surface - /// - /// This corresponds to either the `xdg_popup` role from the - /// `xdg_shell` protocol, or the result of `set_popup` using the - /// `wl_shell` protocol. - /// - /// This are mostly for small tooltips and similar short-lived - /// surfaces. - Popup(PopupState), - /// This surface was not yet assigned a kind - None, -} - -/// State of a regular toplevel surface -pub struct ToplevelState { - /// Parent of this surface - /// - /// If this surface has a parent, it should be hidden - /// or displayed, brought up at the same time as it. - pub parent: Option, - /// Title of this shell surface - pub title: String, - /// App id for this shell surface - /// - /// This identifier can be used to group surface together - /// as being several instance of the same app. This can - /// also be used as the D-Bus name for the app. - pub app_id: String, - /// Minimum size requested for this surface - /// - /// A value of 0 on an axis means this axis is not constrained - pub min_size: (i32, i32), - /// Maximum size requested for this surface - /// - /// A value of 0 on an axis means this axis is not constrained - pub max_size: (i32, i32), -} - -impl ToplevelState { - /// Clone this ToplevelState - /// - /// If the parent surface refers to a surface that no longer - /// exists, it is replaced by `None` in the process. - pub fn clone(&self) -> ToplevelState { - ToplevelState { - parent: self.parent.as_ref().and_then(|p| p.clone()), - title: self.title.clone(), - app_id: self.app_id.clone(), - min_size: self.min_size, - max_size: self.max_size, - } - } -} - -/// The pending state of a popup surface -pub struct PopupState { - /// Parent of this popup surface - pub parent: wl_surface::WlSurface, - /// The positioner specifying how this tooltip should - /// be placed relative to its parent. - pub positioner: PositionerState, -} - -impl PopupState { - /// Clone this PopupState - /// - /// If the parent surface refers to a surface that no longer - /// exists, this will return `None`, as the popup can no - /// longer be meaningfully displayed. - pub fn clone(&self) -> Option { - if let Some(p) = self.parent.clone() { - Some(PopupState { - parent: p, - positioner: self.positioner, - }) - } else { - // the parent surface does no exist any longer, - // this popup does not make any sense now - None - } - } -} - -impl Default for ShellSurfacePendingState { - fn default() -> ShellSurfacePendingState { - ShellSurfacePendingState::None - } -} - -/// Internal implementation data of shell surfaces -/// -/// This type is only visible as type parameter of -/// the `Global` handle you are provided. -pub struct ShellSurfaceIData { - log: ::slog::Logger, - compositor_token: CompositorToken, - implementation: ShellSurfaceUserImplementation, - idata: Rc>, - state_token: StateToken>, -} - -impl Clone for ShellSurfaceIData { - fn clone(&self) -> ShellSurfaceIData { - ShellSurfaceIData { - log: self.log.clone(), - compositor_token: self.compositor_token, - implementation: self.implementation, - idata: self.idata.clone(), - state_token: self.state_token.clone(), - } - } -} - -/// Create new `xdg_shell` and `wl_shell` globals. -/// -/// The globals are directly registered into the eventloop, and this function -/// returns a `StateToken<_>` which you'll need access the list of shell -/// surfaces created by your clients. -/// -/// It also returns the two global handles, in case you whish to remove these -/// globals from the event loop in the future. -pub fn shell_init( - evlh: &mut EventLoopHandle, token: CompositorToken, - implementation: ShellSurfaceUserImplementation, idata: SID, logger: L, -) -> ( - StateToken>, - Global>, - Global>, -) -where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: Default + 'static, - L: Into>, -{ - let log = ::slog_or_stdlog(logger); - let shell_state = ShellState { - known_toplevels: Vec::new(), - known_popups: Vec::new(), - }; - let shell_state_token = evlh.state().insert(shell_state); - - let shell_surface_idata = ShellSurfaceIData { - log: log.new(o!("smithay_module" => "shell_handler")), - compositor_token: token, - implementation: implementation, - idata: Rc::new(RefCell::new(idata)), - state_token: shell_state_token.clone(), - }; - - // TODO: init globals - let wl_shell_global = evlh.register_global( - 1, - self::wl_handlers::wl_shell_bind::, - shell_surface_idata.clone(), - ); - let xdg_shell_global = evlh.register_global( - 1, - self::xdg_handlers::xdg_shell_bind::, - shell_surface_idata.clone(), - ); - - (shell_state_token, wl_shell_global, xdg_shell_global) -} - -/// Shell global state -/// -/// This state allows you to retrieve a list of surfaces -/// currently known to the shell global. -pub struct ShellState { - known_toplevels: Vec>, - known_popups: Vec>, -} - -impl ShellState -where - U: 'static, - R: Role + 'static, - CID: 'static, - SD: 'static, -{ - /// Cleans the internal surface storage by removing all dead surfaces - pub fn cleanup_surfaces(&mut self) { - self.known_toplevels.retain(|s| s.alive()); - self.known_popups.retain(|s| s.alive()); - } - - /// Access all the shell surfaces known by this handler - pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] { - &self.known_toplevels[..] - } - - /// Access all the popup surfaces known by this handler - pub fn popup_surfaces(&self) -> &[PopupSurface] { - &self.known_popups[..] - } -} - -/* - * User interaction - */ - -enum ShellClientKind { - Wl(wl_shell::WlShell), - Xdg(zxdg_shell_v6::ZxdgShellV6), -} - -pub(crate) struct ShellClientData { - pending_ping: u32, - data: SD, -} - -fn make_shell_client_data() -> ShellClientData { - ShellClientData { - pending_ping: 0, - data: Default::default(), - } -} - -/// A shell client -/// -/// This represents an instanciation of a shell -/// global (be it `wl_shell` or `xdg_shell`). -/// -/// Most of the time, you can consider that a -/// wayland client will be a single shell client. -/// -/// You can use this handle to access a storage for any -/// client-specific data you wish to associate with it. -pub struct ShellClient { - kind: ShellClientKind, - _data: ::std::marker::PhantomData<*mut SD>, -} - -impl ShellClient { - /// Is the shell client represented by this handle still connected? - pub fn alive(&self) -> bool { - match self.kind { - ShellClientKind::Wl(ref s) => s.status() == Liveness::Alive, - ShellClientKind::Xdg(ref s) => s.status() == Liveness::Alive, - } - } - - /// Checks if this handle and the other one actually refer to the - /// same shell client - pub fn equals(&self, other: &Self) -> bool { - match (&self.kind, &other.kind) { - (&ShellClientKind::Wl(ref s1), &ShellClientKind::Wl(ref s2)) => s1.equals(s2), - (&ShellClientKind::Xdg(ref s1), &ShellClientKind::Xdg(ref s2)) => s1.equals(s2), - _ => false, - } - } - - /// Send a ping request to this shell client - /// - /// You'll receive the reply in the `Handler::cient_pong()` method. - /// - /// 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: u32) -> Result<(), ()> { - if !self.alive() { - return Err(()); - } - match self.kind { - ShellClientKind::Wl(ref shell) => { - let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; - let mut guard = mutex.lock().unwrap(); - if guard.0.pending_ping == 0 { - return Err(()); - } - guard.0.pending_ping = serial; - if let Some(surface) = guard.1.first() { - // there is at least one surface, send the ping - // if there is no surface, the ping will remain pending - // and will be sent when the client creates a surface - surface.ping(serial); - } - } - ShellClientKind::Xdg(ref shell) => { - let mutex = - unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; - let mut guard = mutex.lock().unwrap(); - if guard.pending_ping == 0 { - return Err(()); - } - guard.pending_ping = serial; - shell.ping(serial); - } - } - Ok(()) - } - - /// Access the user data associated with this shell client - pub fn with_data(&self, f: F) -> Result - where - F: FnOnce(&mut SD) -> T, - { - if !self.alive() { - return Err(()); - } - match self.kind { - ShellClientKind::Wl(ref shell) => { - let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData) }; - let mut guard = mutex.lock().unwrap(); - Ok(f(&mut guard.0.data)) - } - ShellClientKind::Xdg(ref shell) => { - let mutex = - unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; - let mut guard = mutex.lock().unwrap(); - Ok(f(&mut guard.data)) - } - } - } -} - -enum SurfaceKind { - Wl(wl_shell_surface::WlShellSurface), - XdgToplevel(zxdg_toplevel_v6::ZxdgToplevelV6), - XdgPopup(zxdg_popup_v6::ZxdgPopupV6), -} - -/// A handle to a toplevel surface -/// -/// This is an unified abstraction over the toplevel surfaces -/// of both `wl_shell` and `xdg_shell`. -pub struct ToplevelSurface { - wl_surface: wl_surface::WlSurface, - shell_surface: SurfaceKind, - token: CompositorToken, - _shell_data: ::std::marker::PhantomData, -} - -impl ToplevelSurface -where - U: 'static, - R: Role + 'static, - CID: 'static, - SD: 'static, -{ - /// Is the toplevel surface refered by this handle still alive? - pub fn alive(&self) -> bool { - let shell_surface_alive = match self.shell_surface { - SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive, - SurfaceKind::XdgToplevel(ref s) => s.status() == Liveness::Alive, - SurfaceKind::XdgPopup(_) => unreachable!(), - }; - shell_surface_alive && self.wl_surface.status() == Liveness::Alive - } - - /// Do this handle and the other one actually refer to the same toplevel surface? - pub fn equals(&self, other: &Self) -> bool { - self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) - } - - /// Retrieve the shell client owning this toplevel surface - /// - /// Returns `None` if the surface does actually no longer exist. - pub fn client(&self) -> Option> { - if !self.alive() { - return None; - } - match self.shell_surface { - SurfaceKind::Wl(ref s) => { - let &(_, ref shell) = - unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; - Some(ShellClient { - kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }), - _data: ::std::marker::PhantomData, - }) - } - SurfaceKind::XdgToplevel(ref s) => { - let &(_, ref shell, _) = - unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) }; - Some(ShellClient { - kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), - _data: ::std::marker::PhantomData, - }) - } - SurfaceKind::XdgPopup(_) => unreachable!(), - } - } - - /// Send a configure event to this toplevel surface to suggest it a new configuration - /// - /// The serial of this configure will be tracked waiting for the client to ACK it. - pub fn send_configure(&self, cfg: ToplevelConfigure) -> EventResult<()> { - if !self.alive() { - return EventResult::Destroyed; - } - match self.shell_surface { - SurfaceKind::Wl(ref s) => self::wl_handlers::send_toplevel_configure(s, cfg), - SurfaceKind::XdgToplevel(ref s) => { - self::xdg_handlers::send_toplevel_configure(self.token, s, cfg) - } - SurfaceKind::XdgPopup(_) => unreachable!(), - } - EventResult::Sent(()) - } - - /// Make sure this surface was configured - /// - /// Returns `true` if it was, if not, returns `false` and raise - /// a protocol error to the associated client. Also returns `false` - /// if the surface is already destroyed. - /// - /// xdg_shell mandates that a client acks a configure before commiting - /// anything. - pub fn ensure_configured(&self) -> bool { - if !self.alive() { - return false; - } - let configured = self.token - .with_role_data::(&self.wl_surface, |data| data.configured) - .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); - if !configured { - if let SurfaceKind::XdgToplevel(ref s) = self.shell_surface { - let ptr = s.get_user_data(); - let &(_, _, ref xdg_surface) = - unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; - xdg_surface.post_error( - zxdg_surface_v6::Error::NotConstructed as u32, - "Surface has not been confgured yet.".into(), - ); - } else { - unreachable!(); - } - } - configured - } - - /// Send a "close" event to the client - pub fn send_close(&self) -> EventResult<()> { - if !self.alive() { - return EventResult::Destroyed; - } - match self.shell_surface { - SurfaceKind::Wl(_) => EventResult::Sent(()), - SurfaceKind::XdgToplevel(ref s) => s.close(), - SurfaceKind::XdgPopup(_) => unreachable!(), - } - } - - /// 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 - } - } - - /// Retrieve a copy of the pending state of this toplevel surface - /// - /// Returns `None` of the toplevel surface actually no longer exists. - pub fn get_pending_state(&self) -> Option { - if !self.alive() { - return None; - } - self.token - .with_role_data::(&self.wl_surface, |data| match data.pending_state { - ShellSurfacePendingState::Toplevel(ref state) => Some(state.clone()), - _ => None, - }) - .ok() - .and_then(|x| x) - } -} - -/// A handle to a popup surface -/// -/// This is an unified abstraction over the popup surfaces -/// of both `wl_shell` and `xdg_shell`. -pub struct PopupSurface { - wl_surface: wl_surface::WlSurface, - shell_surface: SurfaceKind, - token: CompositorToken, - _shell_data: ::std::marker::PhantomData, -} - -impl PopupSurface -where - U: 'static, - R: Role + 'static, - CID: 'static, - SD: 'static, -{ - /// Is the popup surface refered by this handle still alive? - pub fn alive(&self) -> bool { - let shell_surface_alive = match self.shell_surface { - SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive, - SurfaceKind::XdgPopup(ref s) => s.status() == Liveness::Alive, - SurfaceKind::XdgToplevel(_) => unreachable!(), - }; - shell_surface_alive && self.wl_surface.status() == Liveness::Alive - } - - /// Do this handle and the other one actually refer to the same popup surface? - pub fn equals(&self, other: &Self) -> bool { - self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) - } - - /// Retrieve the shell client owning this popup surface - /// - /// Returns `None` if the surface does actually no longer exist. - pub fn client(&self) -> Option> { - if !self.alive() { - return None; - } - match self.shell_surface { - SurfaceKind::Wl(ref s) => { - let &(_, ref shell) = - unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; - Some(ShellClient { - kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }), - _data: ::std::marker::PhantomData, - }) - } - SurfaceKind::XdgPopup(ref s) => { - let &(_, ref shell, _) = - unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) }; - Some(ShellClient { - kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), - _data: ::std::marker::PhantomData, - }) - } - SurfaceKind::XdgToplevel(_) => unreachable!(), - } - } - - /// Send a configure event to this toplevel surface to suggest it a new configuration - /// - /// The serial of this configure will be tracked waiting for the client to ACK it. - pub fn send_configure(&self, cfg: PopupConfigure) -> EventResult<()> { - if !self.alive() { - return EventResult::Destroyed; - } - match self.shell_surface { - SurfaceKind::Wl(ref s) => self::wl_handlers::send_popup_configure(s, cfg), - SurfaceKind::XdgPopup(ref s) => self::xdg_handlers::send_popup_configure(self.token, s, cfg), - SurfaceKind::XdgToplevel(_) => unreachable!(), - } - EventResult::Sent(()) - } - - /// Make sure this surface was configured - /// - /// Returns `true` if it was, if not, returns `false` and raise - /// a protocol error to the associated client. Also returns `false` - /// if the surface is already destroyed. - /// - /// xdg_shell mandates that a client acks a configure before commiting - /// anything. - pub fn ensure_configured(&self) -> bool { - if !self.alive() { - return false; - } - let configured = self.token - .with_role_data::(&self.wl_surface, |data| data.configured) - .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); - if !configured { - if let SurfaceKind::XdgPopup(ref s) = self.shell_surface { - let ptr = s.get_user_data(); - let &(_, _, ref xdg_surface) = - unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; - xdg_surface.post_error( - zxdg_surface_v6::Error::NotConstructed as u32, - "Surface has not been confgured yet.".into(), - ); - } else { - unreachable!(); - } - } - configured - } - - /// Send a 'popup_done' event to the popup surface - /// - /// It means that the use has dismissed the popup surface, or that - /// the pointer has left the area of popup grab if there was a grab. - pub fn send_popup_done(&self) -> EventResult<()> { - if !self.alive() { - return EventResult::Destroyed; - } - match self.shell_surface { - SurfaceKind::Wl(ref s) => s.popup_done(), - SurfaceKind::XdgPopup(ref s) => s.popup_done(), - SurfaceKind::XdgToplevel(_) => unreachable!(), - } - } - - /// 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 - } - } - - /// Retrieve a copy of the pending state of this popup surface - /// - /// Returns `None` of the popup surface actually no longer exists. - pub fn get_pending_state(&self) -> Option { - if !self.alive() { - return None; - } - self.token - .with_role_data::(&self.wl_surface, |data| match data.pending_state { - ShellSurfacePendingState::Popup(ref state) => state.clone(), - _ => None, - }) - .ok() - .and_then(|x| x) - } -} - -/// A configure message for toplevel surfaces -pub struct ToplevelConfigure { - /// A suggestion for a new size for the surface - pub size: Option<(i32, i32)>, - /// A notification of what are the current states of this surface - /// - /// A surface can be any combination of these possible states - /// at the same time. - pub states: Vec, - /// A serial number to track ACK from the client - /// - /// This should be an ever increasing number, as the ACK-ing - /// from a client for a serial will validate all pending lower - /// serials. - pub serial: u32, -} - -/// A configure message for popup surface -pub struct PopupConfigure { - /// The position chosen for this popup relative to - /// its parent - pub position: (i32, i32), - /// A suggested size for the popup - pub size: (i32, i32), - /// A serial number to track ACK from the client - /// - /// This should be an ever increasing number, as the ACK-ing - /// from a client for a serial will validate all pending lower - /// serials. - pub serial: u32, -} - -/// A sub-implementation for the shell -/// -/// You need to provide this to handle events that the provided implementation -/// cannot process for you directly. -/// -/// Depending on what you want to do, you might implement some of these functions -/// as doing nothing. -pub struct ShellSurfaceUserImplementation { - /// A new shell client was instanciated - pub new_client: fn(evlh: &mut EventLoopHandle, idata: &mut SID, 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. - pub client_pong: fn(evlh: &mut EventLoopHandle, idata: &mut SID, client: ShellClient), - /// A new toplevel surface was created - /// - /// You need to return a `ToplevelConfigure` from this function, which will be sent - /// to the client to configure this surface - pub new_toplevel: - fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: ToplevelSurface) - -> ToplevelConfigure, - /// A new popup surface was created - /// - /// You need to return a `PopupConfigure` from this function, which will be sent - /// to the client to configure this surface - pub new_popup: fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: PopupSurface) - -> PopupConfigure, - /// The client requested the start of an interactive move for this surface - pub move_: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, - ), - /// The client requested the start of an interactive resize for this surface - /// - /// The `edges` argument specifies which part of the window's border is being dragged. - pub resize: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, - edges: zxdg_toplevel_v6::ResizeEdge, - ), - /// 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. - pub grab: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: PopupSurface, - seat: &wl_seat::WlSeat, - serial: u32, - ), - /// A toplevel surface requested its display state to be changed - /// - /// Each field represents the request of the client for a specific property: - /// - /// - `None`: no request is made to change this property - /// - `Some(true)`: this property should be enabled - /// - `Some(false)`: this property should be disabled - /// - /// For fullscreen/maximization, the client can also optionnaly request a specific - /// output. - /// - /// You are to answer with a `ToplevelConfigure` that will be sent to the client in - /// response. - pub change_display_state: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - maximized: Option, - minimized: Option, - fullscreen: Option, - output: Option<&wl_output::WlOutput>, - ) -> ToplevelConfigure, - /// The client requests the window menu to be displayed on this surface at this location - /// - /// This menu belongs to the compositor. It is typically expected to contain options for - /// control of the window (maximize/minimize/close/move/etc...). - pub show_window_menu: fn( - evlh: &mut EventLoopHandle, - idata: &mut SID, - surface: ToplevelSurface, - seat: &wl_seat::WlSeat, - serial: u32, - x: i32, - y: i32, - ), -} - -impl Copy for ShellSurfaceUserImplementation {} -impl Clone for ShellSurfaceUserImplementation { - fn clone(&self) -> ShellSurfaceUserImplementation { - *self - } -} +pub mod legacy; +pub mod xdg; diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs new file mode 100644 index 0000000..ed6b5c4 --- /dev/null +++ b/src/wayland/shell/xdg/mod.rs @@ -0,0 +1,808 @@ +//! Utilities for handling shell surfaces, toplevel and popups using 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 maneer. 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 `shell_init` function provided in this +//! module. You will need to provide it the `CompositorToken` you retrieved from an +//! instanciation of the `CompositorHandler` 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::{shell_init, ShellSurfaceRole, ShellSurfaceUserImplementation}; +//! use wayland_server::protocol::wl_shell::WlShell; +//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6; +//! use wayland_server::{EventLoop, EventLoopHandle}; +//! # use wayland_server::protocol::{wl_seat, wl_output}; +//! # use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_toplevel_v6; +//! # #[derive(Default)] struct MySurfaceData; +//! +//! // define the roles type. You need to integrate the ShellSurface role: +//! define_roles!(MyRoles => +//! [ShellSurface, ShellSurfaceRole] +//! ); +//! +//! // define the metadata you want associated with the shell clients +//! #[derive(Default)] +//! struct MyShellData { +//! /* ... */ +//! } +//! +//! # fn main() { +//! # let (_display, mut event_loop) = wayland_server::create_display(); +//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<(), MyRoles, _, _>( +//! # &mut event_loop, +//! # unimplemented!(), +//! # (), +//! # None +//! # ); +//! // define your implementation for shell +//! let my_shell_implementation = ShellSurfaceUserImplementation { +//! new_client: |evlh, idata, client| { unimplemented!() }, +//! client_pong: |evlh, idata, client| { unimplemented!() }, +//! new_toplevel: |evlh, idata, toplevel| { unimplemented!() }, +//! new_popup: |evlh, idata, popup| { unimplemented!() }, +//! move_: |evlh, idata, toplevel, seat, serial| { unimplemented!() }, +//! resize: |evlh, idata, toplevel, seat, serial, edges| { unimplemented!() }, +//! grab: |evlh, idata, popup, seat, serial| { unimplemented!() }, +//! change_display_state: |evlh, idata, toplevel, maximized, minimized, fullscreen, output| { +//! unimplemented!() +//! }, +//! show_window_menu: |evlh, idata, toplevel, seat, serial, x, y| { unimplemented!() }, +//! }; +//! +//! // define your implementation data +//! let my_shell_implementation_data = (); +//! +//! let (shell_state_token, _, _) = shell_init::<_, _, _, _, MyShellData, _>( +//! &mut event_loop, +//! compositor_token, // token from the compositor implementation +//! my_shell_implementation, // instance of shell::ShellSurfaceUserImplementation +//! my_shell_implementation_data, // whatever data you need here +//! None // put a logger if you want +//! ); +//! +//! // You're now ready to go! +//! # } +//! ``` +//! +//! ### Access to shell surface and clients data +//! +//! There are mainly 3 kind of objects that you'll manipulate from this implementation: +//! +//! - `ShellClient`: This is a handle representing an isntanciation of a shell global +//! you can associate client-wise metadata to it (this is the `MyShellData` type in +//! the example above). +//! - `ToplevelSurface`: This is a handle representing a toplevel surface, you can +//! retrive a list of all currently alive toplevel surface from the `ShellState`. +//! - `PopupSurface`: This is a handle representing a popup/tooltip surface. Similarly, +//! you can get a list of all currently alive popup surface from the `ShellState`. +//! +//! You'll obtain these objects though two means: either via the callback methods of +//! the subhandler you provided, or via methods on the `ShellState` that you can +//! access from the `state()` of the event loop and the token returned by the init +//! function. + +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use utils::Rectangle; +use wayland::compositor::CompositorToken; +use wayland::compositor::roles::Role; +use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, + zxdg_positioner_v6 as xdg_positioner, + zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6}; +use wayland_server::{Display, Global, LoopToken, Resource}; +use wayland_server::commons::Implementation; +use wayland_server::protocol::{wl_output, wl_seat, wl_surface}; + +mod xdg_handlers; + +/// Metadata associated with the `shell_surface` role +pub struct ShellSurfaceRole { + /// Pending state as requested by the client + /// + /// The data in this field are double-buffered, you should + /// apply them on a surface commit. + pub pending_state: ShellSurfacePendingState, + /// Geometry of the surface + /// + /// Defines, in surface relative coordinates, what should + /// be considered as "the surface itself", regarding focus, + /// window alignment, etc... + /// + /// By default, you should consider the full contents of the + /// buffers of this surface and its subsurfaces. + pub window_geometry: Option, + /// List of non-acked configures pending + /// + /// Whenever a configure is acked by the client, all configure + /// older than it are discarded as well. As such, this vec contains + /// the serials of all the configure send to this surface that are + /// newer than the last ack received. + pub pending_configures: Vec, + /// Has this surface acked at least one configure? + /// + /// xdg_shell defines it as illegal to commit on a surface that has + /// not yet acked a configure. + pub configured: bool, +} + +#[derive(Copy, Clone, Debug)] +/// 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: (i32, i32), + /// Anchor rectangle in the parent surface coordinates + /// relative to which the surface must be positioned + pub anchor_rect: Rectangle, + /// 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 criterias constraint the + /// surface + pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, + /// Offset placement relative to the anchor point + pub offset: (i32, i32), +} + +/// Contents of the pending state of a shell surface, depending on its role +pub enum ShellSurfacePendingState { + /// This a regular, toplevel surface + /// + /// This corresponds to the `xdg_toplevel` role + /// + /// This is what you'll generaly interpret as "a window". + Toplevel(ToplevelState), + /// This is a popup surface + /// + /// This corresponds to the `xdg_popup` role + /// + /// This are mostly for small tooltips and similar short-lived + /// surfaces. + Popup(PopupState), + /// This surface was not yet assigned a kind + None, +} + +/// State of a regular toplevel surface +pub struct ToplevelState { + /// Parent of this surface + /// + /// If this surface has a parent, it should be hidden + /// or displayed, brought up at the same time as it. + pub parent: Option>, + /// Title of this shell surface + pub title: String, + /// App id for this shell surface + /// + /// This identifier can be used to group surface together + /// as being several instance of the same app. This can + /// also be used as the D-Bus name for the app. + pub app_id: String, + /// Minimum size requested for this surface + /// + /// A value of 0 on an axis means this axis is not constrained + pub min_size: (i32, i32), + /// Maximum size requested for this surface + /// + /// A value of 0 on an axis means this axis is not constrained + pub max_size: (i32, i32), +} + +impl Clone for ToplevelState { + fn clone(&self) -> ToplevelState { + ToplevelState { + parent: self.parent.as_ref().map(|p| p.clone()), + title: self.title.clone(), + app_id: self.app_id.clone(), + min_size: self.min_size, + max_size: self.max_size, + } + } +} + +/// The pending state of a popup surface +pub struct PopupState { + /// Parent of this popup surface + pub parent: Resource, + /// The positioner specifying how this tooltip should + /// be placed relative to its parent. + pub positioner: PositionerState, +} + +impl Clone for PopupState { + fn clone(&self) -> PopupState { + PopupState { + parent: self.parent.clone(), + positioner: self.positioner, + } + } +} + +impl Default for ShellSurfacePendingState { + fn default() -> ShellSurfacePendingState { + ShellSurfacePendingState::None + } +} + +pub(crate) struct ShellImplementation { + log: ::slog::Logger, + compositor_token: CompositorToken, + loop_token: LoopToken, + user_impl: Rc>>>, + shell_state: Arc>>, +} + +impl Clone for ShellImplementation { + fn clone(&self) -> Self { + ShellImplementation { + log: self.log.clone(), + compositor_token: self.compositor_token.clone(), + loop_token: self.loop_token.clone(), + user_impl: self.user_impl.clone(), + shell_state: self.shell_state.clone(), + } + } +} + +/// Create a new `xdg_shell` globals +pub fn shell_init( + display: &mut Display, + ltoken: LoopToken, + ctoken: CompositorToken, + implementation: Impl, + logger: L, +) -> ( + Arc>>, + Global, +) +where + U: 'static, + R: Role + 'static, + SD: Default + 'static, + L: Into>, + Impl: Implementation<(), ShellEvent>, +{ + let log = ::slog_or_stdlog(logger); + let shell_state = Arc::new(Mutex::new(ShellState { + known_toplevels: Vec::new(), + known_popups: Vec::new(), + })); + + let shell_impl = ShellImplementation { + log: log.new(o!("smithay_module" => "shell_handler")), + loop_token: ltoken.clone(), + compositor_token: ctoken, + user_impl: Rc::new(RefCell::new(implementation)), + shell_state: shell_state.clone(), + }; + + let xdg_shell_global = display.create_global(<oken, 1, move |_version, shell| { + self::xdg_handlers::implement_shell(shell, &shell_impl); + }); + + (shell_state, xdg_shell_global) +} + +/// Shell global state +/// +/// This state allows you to retrieve a list of surfaces +/// currently known to the shell global. +pub struct ShellState { + known_toplevels: Vec>, + known_popups: Vec>, +} + +impl ShellState +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + /// Cleans the internal surface storage by removing all dead surfaces + pub fn cleanup_surfaces(&mut self) { + self.known_toplevels.retain(|s| s.alive()); + self.known_popups.retain(|s| s.alive()); + } + + /// Access all the shell surfaces known by this handler + pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] { + &self.known_toplevels[..] + } + + /// Access all the popup surfaces known by this handler + pub fn popup_surfaces(&self) -> &[PopupSurface] { + &self.known_popups[..] + } +} + +/* + * User interaction + */ + +enum ShellClientKind { + Xdg(Resource), +} + +pub(crate) struct ShellClientData { + pending_ping: u32, + data: SD, +} + +fn make_shell_client_data() -> ShellClientData { + ShellClientData { + pending_ping: 0, + data: Default::default(), + } +} + +/// A shell client +/// +/// This represents an instanciation of a shell +/// global (be it `wl_shell` or `xdg_shell`). +/// +/// Most of the time, you can consider that a +/// wayland client will be a single shell client. +/// +/// You can use this handle to access a storage for any +/// client-specific data you wish to associate with it. +pub struct ShellClient { + kind: ShellClientKind, + _data: ::std::marker::PhantomData<*mut SD>, +} + +impl ShellClient { + /// Is the shell client represented by this handle still connected? + pub fn alive(&self) -> bool { + match self.kind { + ShellClientKind::Xdg(ref s) => s.is_alive(), + } + } + + /// Checks if this handle and the other one actually refer to the + /// same shell client + pub fn equals(&self, other: &Self) -> bool { + match (&self.kind, &other.kind) { + (&ShellClientKind::Xdg(ref s1), &ShellClientKind::Xdg(ref s2)) => s1.equals(s2), + } + } + + /// Send a ping request to this shell client + /// + /// You'll receive the reply in the `Handler::cient_pong()` method. + /// + /// 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: u32) -> Result<(), ()> { + if !self.alive() { + return Err(()); + } + match self.kind { + ShellClientKind::Xdg(ref shell) => { + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.pending_ping == 0 { + return Err(()); + } + guard.pending_ping = serial; + shell.send(zxdg_shell_v6::Event::Ping { serial }); + } + } + Ok(()) + } + + /// Access the user data associated with this shell client + pub fn with_data(&self, f: F) -> Result + where + F: FnOnce(&mut SD) -> T, + { + if !self.alive() { + return Err(()); + } + match self.kind { + ShellClientKind::Xdg(ref shell) => { + let mutex = + unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + Ok(f(&mut guard.data)) + } + } + } +} + +/// A handle to a toplevel surface +pub struct ToplevelSurface { + wl_surface: Resource, + shell_surface: Resource, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl ToplevelSurface +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + /// Is the toplevel surface refered by this handle still alive? + pub fn alive(&self) -> bool { + self.shell_surface.is_alive() && self.wl_surface.is_alive() + } + + /// Do this handle and the other one actually refer to the same toplevel surface? + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + /// Retrieve the shell client owning this toplevel surface + /// + /// Returns `None` if the surface does actually no longer exist. + pub fn client(&self) -> Option> { + if !self.alive() { + return None; + } + let &(_, ref shell, _) = unsafe { + &*(self.shell_surface.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) + }; + Some(ShellClient { + kind: ShellClientKind::Xdg(shell.clone()), + _data: ::std::marker::PhantomData, + }) + } + + /// Send a configure event to this toplevel surface to suggest it a new configuration + /// + /// The serial of this configure will be tracked waiting for the client to ACK it. + pub fn send_configure(&self, cfg: ToplevelConfigure) { + if !self.alive() { + return; + } + self::xdg_handlers::send_toplevel_configure(self.token, &self.shell_surface, cfg); + } + + /// Make sure this surface was configured + /// + /// Returns `true` if it was, if not, returns `false` and raise + /// a protocol error to the associated client. Also returns `false` + /// if the surface is already destroyed. + /// + /// xdg_shell mandates that a client acks a configure before commiting + /// anything. + pub fn ensure_configured(&self) -> bool { + if !self.alive() { + return false; + } + let configured = self.token + .with_role_data::(&self.wl_surface, |data| data.configured) + .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); + if !configured { + let ptr = self.shell_surface.get_user_data(); + let &(_, _, ref xdg_surface) = + unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; + xdg_surface.post_error( + zxdg_surface_v6::Error::NotConstructed as u32, + "Surface has not been confgured yet.".into(), + ); + } + configured + } + + /// Send a "close" event to the client + pub fn send_close(&self) { + self.shell_surface.send(zxdg_toplevel_v6::Event::Close); + } + + /// 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<&Resource> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + /// Retrieve a copy of the pending state of this toplevel surface + /// + /// Returns `None` of the toplevel surface actually no longer exists. + pub fn get_pending_state(&self) -> Option { + if !self.alive() { + return None; + } + self.token + .with_role_data::(&self.wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref state) => Some(state.clone()), + _ => None, + }) + .ok() + .and_then(|x| x) + } +} + +/// A handle to a popup surface +/// +/// This is an unified abstraction over the popup surfaces +/// of both `wl_shell` and `xdg_shell`. +pub struct PopupSurface { + wl_surface: Resource, + shell_surface: Resource, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl PopupSurface +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + /// Is the popup surface refered by this handle still alive? + pub fn alive(&self) -> bool { + self.shell_surface.is_alive() && self.wl_surface.is_alive() + } + + /// Do this handle and the other one actually refer to the same popup surface? + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + /// Retrieve the shell client owning this popup surface + /// + /// Returns `None` if the surface does actually no longer exist. + pub fn client(&self) -> Option> { + if !self.alive() { + return None; + } + let &(_, ref shell, _) = unsafe { + &*(self.shell_surface.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) + }; + Some(ShellClient { + kind: ShellClientKind::Xdg(shell.clone()), + _data: ::std::marker::PhantomData, + }) + } + + /// Send a configure event to this toplevel surface to suggest it a new configuration + /// + /// The serial of this configure will be tracked waiting for the client to ACK it. + pub fn send_configure(&self, cfg: PopupConfigure) { + if !self.alive() { + return; + } + self::xdg_handlers::send_popup_configure(self.token, &self.shell_surface, cfg); + } + + /// Make sure this surface was configured + /// + /// Returns `true` if it was, if not, returns `false` and raise + /// a protocol error to the associated client. Also returns `false` + /// if the surface is already destroyed. + /// + /// xdg_shell mandates that a client acks a configure before commiting + /// anything. + pub fn ensure_configured(&self) -> bool { + if !self.alive() { + return false; + } + let configured = self.token + .with_role_data::(&self.wl_surface, |data| data.configured) + .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); + if !configured { + let ptr = self.shell_surface.get_user_data(); + let &(_, _, ref xdg_surface) = + unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) }; + xdg_surface.post_error( + zxdg_surface_v6::Error::NotConstructed as u32, + "Surface has not been confgured yet.".into(), + ); + } + configured + } + + /// Send a 'popup_done' event to the popup surface + /// + /// It means that the use has dismissed the popup surface, or that + /// the pointer has left the area of popup grab if there was a grab. + pub fn send_popup_done(&self) { + self.shell_surface.send(zxdg_popup_v6::Event::PopupDone); + } + + /// 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<&Resource> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + /// Retrieve a copy of the pending state of this popup surface + /// + /// Returns `None` of the popup surface actually no longer exists. + pub fn get_pending_state(&self) -> Option { + if !self.alive() { + return None; + } + self.token + .with_role_data::(&self.wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Popup(ref state) => Some(state.clone()), + _ => None, + }) + .ok() + .and_then(|x| x) + } +} + +/// A configure message for toplevel surfaces +pub struct ToplevelConfigure { + /// A suggestion for a new size for the surface + pub size: Option<(i32, i32)>, + /// A notification of what are the current states of this surface + /// + /// A surface can be any combination of these possible states + /// at the same time. + pub states: Vec, + /// A serial number to track ACK from the client + /// + /// This should be an ever increasing number, as the ACK-ing + /// from a client for a serial will validate all pending lower + /// serials. + pub serial: u32, +} + +/// A configure message for popup surface +pub struct PopupConfigure { + /// The position chosen for this popup relative to + /// its parent + pub position: (i32, i32), + /// A suggested size for the popup + pub size: (i32, i32), + /// A serial number to track ACK from the client + /// + /// This should be an ever increasing number, as the ACK-ing + /// from a client for a serial will validate all pending lower + /// serials. + pub serial: u32, +} + +/// Events generated by xdg shell surfaces +/// +/// These are events that the provided implementation cannot process +/// for you directly. +/// +/// Depending on what you want to do, you might ignore some of them +pub enum ShellEvent { + /// A new shell client was instanciated + NewClient { + /// the client + 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. + ClientPong { + /// the client + 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 + NewToplevel { + /// the surface + 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 + NewPopup { + /// the surface + surface: PopupSurface, + }, + /// The client requested the start of an interactive move for this surface + Move { + /// the surface + surface: ToplevelSurface, + /// the seat associated to this move + seat: Resource, + /// the grab serial + serial: u32, + }, + /// The client requested the start of an interactive resize for this surface + Resize { + /// The surface + surface: ToplevelSurface, + /// The seat associated with this resize + seat: Resource, + /// The grab serial + serial: u32, + /// Specification of which part of the window's border is being dragged + edges: zxdg_toplevel_v6::ResizeEdge, + }, + /// 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. + Grab { + /// The surface + surface: PopupSurface, + /// The seat to grab + seat: Resource, + /// The grab serial + serial: u32, + }, + /// A toplevel surface requested to be maximized + Maximize { + /// The surface + surface: ToplevelSurface, + }, + /// A toplevel surface requested to stop being maximized + UnMaximize { + /// The surface + surface: ToplevelSurface, + }, + /// A toplevel surface requested to be set fullscreen + Fullscreen { + /// The surface + surface: ToplevelSurface, + /// The output (if any) on which the fullscreen is requested + output: Option>, + }, + /// A toplevel surface request to stop being fullscreen + UnFullscreen { + /// The surface + surface: ToplevelSurface, + }, + /// A toplevel surface requested to be minimized + Minimize { + /// The surface + surface: ToplevelSurface, + }, + /// The client requests the window menu to be displayed on this surface at this location + /// + /// This menu belongs to the compositor. It is typically expected to contain options for + /// control of the window (maximize/minimize/close/move/etc...). + ShowWindowMenu { + /// The surface + surface: ToplevelSurface, + /// The seat associated with this input grab + seat: Resource, + /// the grab serial + serial: u32, + /// location of the menu request + location: (i32, i32), + }, +} diff --git a/src/wayland/shell/xdg/xdg_handlers.rs b/src/wayland/shell/xdg/xdg_handlers.rs new file mode 100644 index 0000000..3f979d3 --- /dev/null +++ b/src/wayland/shell/xdg/xdg_handlers.rs @@ -0,0 +1,748 @@ +use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient, + ShellClientData, ShellEvent, ShellImplementation, ShellSurfacePendingState, ShellSurfaceRole, + ToplevelConfigure, ToplevelState}; +use std::sync::Mutex; +use utils::Rectangle; +use wayland::compositor::CompositorToken; +use wayland::compositor::roles::*; +use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6, + zxdg_surface_v6, zxdg_toplevel_v6}; +use wayland_server::{LoopToken, NewResource, Resource}; +use wayland_server::commons::{downcast_impl, Implementation}; +use wayland_server::protocol::wl_surface; + +pub(crate) fn implement_shell( + shell: NewResource, + implem: &ShellImplementation, +) -> Resource +where + U: 'static, + R: Role + 'static, + SD: Default + 'static, +{ + let shell = shell.implement_nonsend( + implem.clone(), + Some(|shell, _| destroy_shell::(&shell)), + &implem.loop_token, + ); + shell.set_user_data(Box::into_raw(Box::new(Mutex::new(make_shell_client_data::()))) as *mut _); + let mut user_impl = implem.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::NewClient { + client: make_shell_client(&shell), + }, + (), + ); + shell +} + +/* + * xdg_shell + */ + +pub(crate) type ShellUserData = Mutex>; + +fn destroy_shell(shell: &Resource) { + let ptr = shell.get_user_data(); + shell.set_user_data(::std::ptr::null_mut()); + let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; + // explicit call to drop to not forget what we're doing here + ::std::mem::drop(data); +} + +pub(crate) fn make_shell_client(resource: &Resource) -> ShellClient { + ShellClient { + kind: super::ShellClientKind::Xdg(resource.clone()), + _data: ::std::marker::PhantomData, + } +} + +impl Implementation, zxdg_shell_v6::Request> + for ShellImplementation +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + fn receive(&mut self, request: zxdg_shell_v6::Request, shell: Resource) { + match request { + zxdg_shell_v6::Request::Destroy => { + // all is handled by destructor + } + zxdg_shell_v6::Request::CreatePositioner { id } => { + implement_positioner(id, &self.loop_token); + } + zxdg_shell_v6::Request::GetXdgSurface { id, surface } => { + let role_data = ShellSurfaceRole { + pending_state: ShellSurfacePendingState::None, + window_geometry: None, + pending_configures: Vec::new(), + configured: false, + }; + if self.compositor_token + .give_role_with(&surface, role_data) + .is_err() + { + shell.post_error( + zxdg_shell_v6::Error::Role as u32, + "Surface already has a role.".into(), + ); + return; + } + let xdg_surface = id.implement_nonsend( + self.clone(), + Some(destroy_surface::), + &self.loop_token, + ); + xdg_surface + .set_user_data(Box::into_raw(Box::new((surface.clone(), shell.clone()))) as *mut _); + } + zxdg_shell_v6::Request::Pong { serial } => { + let valid = { + let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.pending_ping == serial { + guard.pending_ping = 0; + true + } else { + false + } + }; + if valid { + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::ClientPong { + client: make_shell_client(&shell), + }, + (), + ); + } + } + } + } +} + +/* + * xdg_positioner + */ + +fn destroy_positioner(positioner: &Resource) { + let ptr = positioner.get_user_data(); + positioner.set_user_data(::std::ptr::null_mut()); + // drop the PositionerState + let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) }; + // explicit call to drop to not forget what we're doing here + ::std::mem::drop(surface); +} + +fn implement_positioner( + positioner: NewResource, + token: &LoopToken, +) -> Resource { + let positioner = positioner.implement_nonsend( + |request, positioner: Resource<_>| { + let ptr = positioner.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + match request { + zxdg_positioner_v6::Request::Destroy => { + // handled by destructor + } + zxdg_positioner_v6::Request::SetSize { width, height } => { + if width < 1 || height < 1 { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner.".into(), + ); + } else { + state.rect_size = (width, height); + } + } + zxdg_positioner_v6::Request::SetAnchorRect { + x, + y, + width, + height, + } => { + if width < 1 || height < 1 { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner's anchor rectangle.".into(), + ); + } else { + state.anchor_rect = Rectangle { + x, + y, + width, + height, + }; + } + } + zxdg_positioner_v6::Request::SetAnchor { anchor } => { + use self::zxdg_positioner_v6::Anchor; + if anchor.contains(Anchor::Left | Anchor::Right) + || anchor.contains(Anchor::Top | Anchor::Bottom) + { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid anchor for positioner.".into(), + ); + } else { + state.anchor_edges = anchor; + } + } + zxdg_positioner_v6::Request::SetGravity { gravity } => { + use self::zxdg_positioner_v6::Gravity; + if gravity.contains(Gravity::Left | Gravity::Right) + || gravity.contains(Gravity::Top | Gravity::Bottom) + { + positioner.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid gravity for positioner.".into(), + ); + } else { + state.gravity = gravity; + } + } + zxdg_positioner_v6::Request::SetConstraintAdjustment { + constraint_adjustment, + } => { + let constraint_adjustment = + zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); + state.constraint_adjustment = constraint_adjustment; + } + zxdg_positioner_v6::Request::SetOffset { x, y } => { + state.offset = (x, y); + } + } + }, + Some(|positioner, _| destroy_positioner(&positioner)), + token, + ); + let data = PositionerState { + rect_size: (0, 0), + anchor_rect: Rectangle { + x: 0, + y: 0, + width: 0, + height: 0, + }, + anchor_edges: zxdg_positioner_v6::Anchor::empty(), + gravity: zxdg_positioner_v6::Gravity::empty(), + constraint_adjustment: zxdg_positioner_v6::ConstraintAdjustment::empty(), + offset: (0, 0), + }; + positioner.set_user_data(Box::into_raw(Box::new(data)) as *mut _); + positioner +} + +/* + * xdg_surface + */ + +type XdgSurfaceUserData = ( + Resource, + Resource, +); + +fn destroy_surface( + surface: Resource, + implem: Box, zxdg_surface_v6::Request>>, +) where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); + let ptr = surface.get_user_data(); + surface.set_user_data(::std::ptr::null_mut()); + // take back ownership of the userdata + let data = unsafe { Box::from_raw(ptr as *mut XdgSurfaceUserData) }; + implem + .compositor_token + .with_role_data::(&data.0, |rdata| { + if let ShellSurfacePendingState::None = rdata.pending_state { + // all is good + } else { + data.1.post_error( + zxdg_shell_v6::Error::Role as u32, + "xdg_surface was destroyed before its role object".into(), + ); + } + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); +} + +impl Implementation, zxdg_surface_v6::Request> + for ShellImplementation +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + fn receive( + &mut self, + request: zxdg_surface_v6::Request, + xdg_surface: Resource, + ) { + let ptr = xdg_surface.get_user_data(); + let &(ref surface, ref shell) = unsafe { &*(ptr as *mut XdgSurfaceUserData) }; + match request { + zxdg_surface_v6::Request::Destroy => { + // all is handled by our destructor + } + zxdg_surface_v6::Request::GetToplevel { id } => { + self.compositor_token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState { + parent: None, + title: String::new(), + app_id: String::new(), + min_size: (0, 0), + max_size: (0, 0), + }); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); + let toplevel = id.implement_nonsend( + self.clone(), + Some(destroy_toplevel::), + &self.loop_token, + ); + toplevel.set_user_data(Box::into_raw(Box::new(( + surface.clone(), + shell.clone(), + xdg_surface.clone(), + ))) as *mut _); + + self.shell_state + .lock() + .unwrap() + .known_toplevels + .push(make_toplevel_handle(self.compositor_token, &toplevel)); + + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::NewToplevel { surface: handle }, ()); + } + zxdg_surface_v6::Request::GetPopup { + id, + parent, + positioner, + } => { + let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) }; + + let parent_ptr = parent.get_user_data(); + let &(ref parent_surface, _) = unsafe { &*(parent_ptr as *mut XdgSurfaceUserData) }; + self.compositor_token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::Popup(PopupState { + parent: parent_surface.clone(), + positioner: *positioner_data, + }); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); + let popup = id.implement_nonsend( + self.clone(), + Some(destroy_popup::), + &self.loop_token, + ); + popup.set_user_data(Box::into_raw(Box::new(( + surface.clone(), + shell.clone(), + xdg_surface.clone(), + ))) as *mut _); + + self.shell_state + .lock() + .unwrap() + .known_popups + .push(make_popup_handle(self.compositor_token, &popup)); + + let handle = make_popup_handle(self.compositor_token, &popup); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::NewPopup { surface: handle }, ()); + } + zxdg_surface_v6::Request::SetWindowGeometry { + x, + y, + width, + height, + } => { + self.compositor_token + .with_role_data::(surface, |data| { + data.window_geometry = Some(Rectangle { + x, + y, + width, + height, + }); + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); + } + zxdg_surface_v6::Request::AckConfigure { serial } => { + self.compositor_token + .with_role_data::(surface, |data| { + let mut found = false; + data.pending_configures.retain(|&s| { + if s == serial { + found = true; + } + s > serial + }); + if !found { + // client responded to a non-existing configure + shell.post_error( + zxdg_shell_v6::Error::InvalidSurfaceState as u32, + format!("Wrong configure serial: {}", serial), + ); + } + data.configured = true; + }) + .expect("xdg_surface exists but surface has not shell_surface role?!"); + } + } + } +} + +/* + * xdg_toplevel + */ + +pub type ShellSurfaceUserData = ( + Resource, + Resource, + Resource, +); + +// Utility functions allowing to factor out a lot of the upcoming logic +fn with_surface_toplevel_data( + implem: &ShellImplementation, + toplevel: &Resource, + f: F, +) where + U: 'static, + R: Role + 'static, + SD: 'static, + F: FnOnce(&mut ToplevelState), +{ + let ptr = toplevel.get_user_data(); + let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + implem + .compositor_token + .with_role_data::(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), + _ => unreachable!(), + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); +} + +pub fn send_toplevel_configure( + token: CompositorToken, + resource: &Resource, + configure: ToplevelConfigure, +) where + U: 'static, + R: Role + 'static, +{ + let &(ref surface, _, ref shell_surface) = + unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let (width, height) = configure.size.unwrap_or((0, 0)); + // convert the Vec (which is really a Vec) into Vec + let states = { + let mut states = configure.states; + let ptr = states.as_mut_ptr(); + let len = states.len(); + let cap = states.capacity(); + ::std::mem::forget(states); + unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } + }; + let serial = configure.serial; + resource.send(zxdg_toplevel_v6::Event::Configure { + width, + height, + states, + }); + shell_surface.send(zxdg_surface_v6::Event::Configure { serial }); + // Add the configure as pending + token + .with_role_data::(surface, |data| data.pending_configures.push(serial)) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); +} + +fn make_toplevel_handle( + token: CompositorToken, + resource: &Resource, +) -> super::ToplevelSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::ToplevelSurface { + wl_surface: wl_surface.clone(), + shell_surface: resource.clone(), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +impl Implementation, zxdg_toplevel_v6::Request> + for ShellImplementation +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + fn receive( + &mut self, + request: zxdg_toplevel_v6::Request, + toplevel: Resource, + ) { + match request { + zxdg_toplevel_v6::Request::Destroy => { + // all it done by the destructor + } + zxdg_toplevel_v6::Request::SetParent { parent } => { + with_surface_toplevel_data(self, &toplevel, |toplevel_data| { + toplevel_data.parent = parent.map(|toplevel_surface_parent| { + let parent_ptr = toplevel_surface_parent.get_user_data(); + let &(ref parent_surface, _, _) = + unsafe { &*(parent_ptr as *mut ShellSurfaceUserData) }; + parent_surface.clone() + }) + }); + } + zxdg_toplevel_v6::Request::SetTitle { title } => { + with_surface_toplevel_data(self, &toplevel, |toplevel_data| { + toplevel_data.title = title; + }); + } + zxdg_toplevel_v6::Request::SetAppId { app_id } => { + with_surface_toplevel_data(self, &toplevel, |toplevel_data| { + toplevel_data.app_id = app_id; + }); + } + zxdg_toplevel_v6::Request::ShowWindowMenu { seat, serial, x, y } => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::ShowWindowMenu { + surface: handle, + seat, + serial, + location: (x, y), + }, + (), + ); + } + zxdg_toplevel_v6::Request::Move { seat, serial } => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::Move { + surface: handle, + seat, + serial, + }, + (), + ); + } + zxdg_toplevel_v6::Request::Resize { + seat, + serial, + edges, + } => { + let edges = zxdg_toplevel_v6::ResizeEdge::from_raw(edges) + .unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::Resize { + surface: handle, + seat, + serial, + edges, + }, + (), + ); + } + zxdg_toplevel_v6::Request::SetMaxSize { width, height } => { + with_surface_toplevel_data(self, &toplevel, |toplevel_data| { + toplevel_data.max_size = (width, height); + }); + } + zxdg_toplevel_v6::Request::SetMinSize { width, height } => { + with_surface_toplevel_data(self, &toplevel, |toplevel_data| { + toplevel_data.max_size = (width, height); + }); + } + zxdg_toplevel_v6::Request::SetMaximized => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::Maximize { surface: handle }, ()); + } + zxdg_toplevel_v6::Request::UnsetMaximized => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::UnMaximize { surface: handle }, ()); + } + zxdg_toplevel_v6::Request::SetFullscreen { output } => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::Fullscreen { + surface: handle, + output, + }, + (), + ); + } + zxdg_toplevel_v6::Request::UnsetFullscreen => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::UnFullscreen { surface: handle }, ()); + } + zxdg_toplevel_v6::Request::SetMinimized => { + let handle = make_toplevel_handle(self.compositor_token, &toplevel); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive(ShellEvent::Minimize { surface: handle }, ()); + } + } + } +} + +fn destroy_toplevel( + toplevel: Resource, + implem: Box, zxdg_toplevel_v6::Request>>, +) where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); + let ptr = toplevel.get_user_data(); + toplevel.set_user_data(::std::ptr::null_mut()); + // take back ownership of the userdata + let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + implem + .compositor_token + .with_role_data::(&data.0, |data| { + data.pending_state = ShellSurfacePendingState::None; + data.configured = false; + }) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); + // remove this surface from the known ones (as well as any leftover dead surface) + implem + .shell_state + .lock() + .unwrap() + .known_toplevels + .retain(|other| { + other + .get_surface() + .map(|s| !s.equals(&data.0)) + .unwrap_or(false) + }); +} + +/* + * xdg_popup + */ + +pub(crate) fn send_popup_configure( + token: CompositorToken, + resource: &Resource, + configure: PopupConfigure, +) where + U: 'static, + R: Role + 'static, +{ + let &(ref surface, _, ref shell_surface) = + unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let (x, y) = configure.position; + let (width, height) = configure.size; + let serial = configure.serial; + resource.send(zxdg_popup_v6::Event::Configure { + x, + y, + width, + height, + }); + shell_surface.send(zxdg_surface_v6::Event::Configure { serial }); + // Add the configure as pending + token + .with_role_data::(surface, |data| data.pending_configures.push(serial)) + .expect("xdg_toplevel exists but surface has not shell_surface role?!"); +} + +fn make_popup_handle( + token: CompositorToken, + resource: &Resource, +) -> super::PopupSurface { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + super::PopupSurface { + wl_surface: wl_surface.clone(), + shell_surface: resource.clone(), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +impl Implementation, zxdg_popup_v6::Request> + for ShellImplementation +where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + fn receive(&mut self, request: zxdg_popup_v6::Request, popup: Resource) { + match request { + zxdg_popup_v6::Request::Destroy => { + // all is handled by our destructor + } + zxdg_popup_v6::Request::Grab { seat, serial } => { + let handle = make_popup_handle(self.compositor_token, &popup); + let mut user_impl = self.user_impl.borrow_mut(); + user_impl.receive( + ShellEvent::Grab { + surface: handle, + seat, + serial, + }, + (), + ); + } + } + } +} + +fn destroy_popup( + popup: Resource, + implem: Box, zxdg_popup_v6::Request>>, +) where + U: 'static, + R: Role + 'static, + SD: 'static, +{ + let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); + let ptr = popup.get_user_data(); + popup.set_user_data(::std::ptr::null_mut()); + // take back ownership of the userdata + let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + implem + .compositor_token + .with_role_data::(&data.0, |data| { + data.pending_state = ShellSurfacePendingState::None; + data.configured = false; + }) + .expect("xdg_popup exists but surface has not shell_surface role?!"); + // remove this surface from the known ones (as well as any leftover dead surface) + implem + .shell_state + .lock() + .unwrap() + .known_popups + .retain(|other| { + other + .get_surface() + .map(|s| !s.equals(&data.0)) + .unwrap_or(false) + }); +} diff --git a/src/wayland/shell/xdg_handlers.rs b/src/wayland/shell/xdg_handlers.rs deleted file mode 100644 index baedaf7..0000000 --- a/src/wayland/shell/xdg_handlers.rs +++ /dev/null @@ -1,697 +0,0 @@ -use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient, - ShellClientData, ShellSurfaceIData, ShellSurfacePendingState, ShellSurfaceRole, - ToplevelConfigure, ToplevelState}; -use std::sync::Mutex; -use utils::Rectangle; -use wayland::compositor::CompositorToken; -use wayland::compositor::roles::*; -use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6, - zxdg_surface_v6, zxdg_toplevel_v6}; -use wayland_server::{Client, EventLoopHandle, Resource}; -use wayland_server::protocol::{wl_output, wl_surface}; - -pub(crate) fn xdg_shell_bind( - evlh: &mut EventLoopHandle, idata: &mut ShellSurfaceIData, _: &Client, - shell: zxdg_shell_v6::ZxdgShellV6, -) where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: Default + 'static, -{ - shell.set_user_data(Box::into_raw(Box::new(Mutex::new(make_shell_client_data::()))) as *mut _); - evlh.register( - &shell, - shell_implementation(), - idata.clone(), - Some(destroy_shell::), - ); - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.new_client)(evlh, &mut *user_idata, make_shell_client(&shell)); -} - -/* - * xdg_shell - */ - -pub(crate) type ShellUserData = Mutex>; - -fn destroy_shell(shell: &zxdg_shell_v6::ZxdgShellV6) { - let ptr = shell.get_user_data(); - shell.set_user_data(::std::ptr::null_mut()); - let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; - // explicit call to drop to not forget what we're doing here - ::std::mem::drop(data); -} - -pub(crate) fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient { - ShellClient { - kind: super::ShellClientKind::Xdg(unsafe { resource.clone_unchecked() }), - _data: ::std::marker::PhantomData, - } -} - -fn shell_implementation( -) -> zxdg_shell_v6::Implementation> -where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, -{ - zxdg_shell_v6::Implementation { - destroy: |_, _, _, _| {}, - create_positioner: |evlh, _, _, _, positioner| { - let data = PositionerState { - rect_size: (0, 0), - anchor_rect: Rectangle { - x: 0, - y: 0, - width: 0, - height: 0, - }, - anchor_edges: zxdg_positioner_v6::Anchor::empty(), - gravity: zxdg_positioner_v6::Gravity::empty(), - constraint_adjustment: zxdg_positioner_v6::ConstraintAdjustment::empty(), - offset: (0, 0), - }; - positioner.set_user_data(Box::into_raw(Box::new(data)) as *mut _); - evlh.register( - &positioner, - positioner_implementation(), - (), - Some(destroy_positioner), - ); - }, - get_xdg_surface: |evlh, idata, _, shell, xdg_surface, wl_surface| { - let role_data = ShellSurfaceRole { - pending_state: ShellSurfacePendingState::None, - window_geometry: None, - pending_configures: Vec::new(), - configured: false, - }; - if idata - .compositor_token - .give_role_with(wl_surface, role_data) - .is_err() - { - shell.post_error( - zxdg_shell_v6::Error::Role as u32, - "Surface already has a role.".into(), - ); - return; - } - xdg_surface.set_user_data(Box::into_raw(Box::new(( - unsafe { wl_surface.clone_unchecked() }, - unsafe { shell.clone_unchecked() }, - ))) as *mut _); - evlh.register( - &xdg_surface, - surface_implementation(), - idata.clone(), - Some(destroy_surface), - ); - }, - pong: |evlh, idata, _, shell, serial| { - let valid = { - let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; - let mut guard = mutex.lock().unwrap(); - if guard.pending_ping == serial { - guard.pending_ping = 0; - true - } else { - false - } - }; - if valid { - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.client_pong)(evlh, &mut *user_idata, make_shell_client(shell)); - } - }, - } -} - -/* - * xdg_positioner - */ - -fn destroy_positioner(positioner: &zxdg_positioner_v6::ZxdgPositionerV6) { - let ptr = positioner.get_user_data(); - positioner.set_user_data(::std::ptr::null_mut()); - // drop the PositionerState - let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) }; - // explicit call to drop to not forget what we're doing here - ::std::mem::drop(surface); -} - -fn positioner_implementation() -> zxdg_positioner_v6::Implementation<()> { - zxdg_positioner_v6::Implementation { - destroy: |_, _, _, _| {}, - set_size: |_, _, _, positioner, width, height| { - if width < 1 || height < 1 { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid size for positioner.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.rect_size = (width, height); - } - }, - set_anchor_rect: |_, _, _, positioner, x, y, width, height| { - if width < 1 || height < 1 { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid size for positioner's anchor rectangle.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.anchor_rect = Rectangle { - x, - y, - width, - height, - }; - } - }, - set_anchor: |_, _, _, positioner, anchor| { - use self::zxdg_positioner_v6::Anchor; - if anchor.contains(Anchor::Left | Anchor::Right) || anchor.contains(Anchor::Top | Anchor::Bottom) - { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid anchor for positioner.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.anchor_edges = anchor; - } - }, - set_gravity: |_, _, _, positioner, gravity| { - use self::zxdg_positioner_v6::Gravity; - if gravity.contains(Gravity::Left | Gravity::Right) - || gravity.contains(Gravity::Top | Gravity::Bottom) - { - positioner.post_error( - zxdg_positioner_v6::Error::InvalidInput as u32, - "Invalid gravity for positioner.".into(), - ); - } else { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.gravity = gravity; - } - }, - set_constraint_adjustment: |_, _, _, positioner, constraint_adjustment| { - let constraint_adjustment = - zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.constraint_adjustment = constraint_adjustment; - }, - set_offset: |_, _, _, positioner, x, y| { - let ptr = positioner.get_user_data(); - let state = unsafe { &mut *(ptr as *mut PositionerState) }; - state.offset = (x, y); - }, - } -} - -/* - * xdg_surface - */ - -fn destroy_surface(surface: &zxdg_surface_v6::ZxdgSurfaceV6) { - let ptr = surface.get_user_data(); - surface.set_user_data(::std::ptr::null_mut()); - // drop the state - let data = - unsafe { Box::from_raw(ptr as *mut (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6)) }; - // explicit call to drop to not forget what we're doing here - ::std::mem::drop(data); -} - -fn surface_implementation( -) -> zxdg_surface_v6::Implementation> -where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, -{ - zxdg_surface_v6::Implementation { - destroy: |_, idata, _, xdg_surface| { - let ptr = xdg_surface.get_user_data(); - let &(ref surface, ref shell) = - unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - idata - .compositor_token - .with_role_data::(surface, |data| { - if let ShellSurfacePendingState::None = data.pending_state { - // all is good - } else { - shell.post_error( - zxdg_shell_v6::Error::Role as u32, - "xdg_surface was destroyed before its role object".into(), - ); - } - }) - .expect("xdg_surface exists but surface has not shell_surface role?!"); - }, - get_toplevel: |evlh, idata, _, xdg_surface, toplevel| { - let ptr = xdg_surface.get_user_data(); - let &(ref surface, ref shell) = - unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - idata - .compositor_token - .with_role_data::(surface, |data| { - data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState { - parent: None, - title: String::new(), - app_id: String::new(), - min_size: (0, 0), - max_size: (0, 0), - }); - }) - .expect("xdg_surface exists but surface has not shell_surface role?!"); - - toplevel.set_user_data(Box::into_raw(Box::new(unsafe { - ( - surface.clone_unchecked(), - shell.clone_unchecked(), - xdg_surface.clone_unchecked(), - ) - })) as *mut _); - evlh.register( - &toplevel, - toplevel_implementation(), - idata.clone(), - Some(destroy_toplevel), - ); - - // register to self - evlh.state() - .get_mut(&idata.state_token) - .known_toplevels - .push(make_toplevel_handle(idata.compositor_token, &toplevel)); - - // intial configure event - let handle = make_toplevel_handle(idata.compositor_token, &toplevel); - let mut user_idata = idata.idata.borrow_mut(); - let configure = (idata.implementation.new_toplevel)(evlh, &mut *user_idata, handle); - send_toplevel_configure(idata.compositor_token, &toplevel, configure); - }, - get_popup: |evlh, idata, _, xdg_surface, popup, parent, positioner| { - let ptr = xdg_surface.get_user_data(); - let &(ref surface, ref shell) = - unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - - let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) }; - - let parent_ptr = parent.get_user_data(); - let &(ref parent_surface, _) = - unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - - idata - .compositor_token - .with_role_data::(surface, |data| { - data.pending_state = ShellSurfacePendingState::Popup(PopupState { - parent: unsafe { parent_surface.clone_unchecked() }, - positioner: *positioner_data, - }); - }) - .expect("xdg_surface exists but surface has not shell_surface role?!"); - - popup.set_user_data(Box::into_raw(Box::new(unsafe { - ( - surface.clone_unchecked(), - shell.clone_unchecked(), - xdg_surface.clone_unchecked(), - ) - })) as *mut _); - evlh.register( - &popup, - popup_implementation(), - idata.clone(), - Some(destroy_popup), - ); - - // register to self - evlh.state() - .get_mut(&idata.state_token) - .known_popups - .push(make_popup_handle(idata.compositor_token, &popup)); - - // intial configure event - let handle = make_popup_handle(idata.compositor_token, &popup); - let mut user_idata = idata.idata.borrow_mut(); - let configure = (idata.implementation.new_popup)(evlh, &mut *user_idata, handle); - send_popup_configure(idata.compositor_token, &popup, configure); - }, - set_window_geometry: |_, idata, _, surface, x, y, width, height| { - let ptr = surface.get_user_data(); - let &(ref surface, _) = - unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - idata - .compositor_token - .with_role_data::(surface, |data| { - data.window_geometry = Some(Rectangle { - x, - y, - width, - height, - }); - }) - .expect("xdg_surface exists but surface has not shell_surface role?!"); - }, - ack_configure: |_, idata, _, surface, serial| { - let ptr = surface.get_user_data(); - let &(ref surface, ref shell) = - unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - idata - .compositor_token - .with_role_data::(surface, |data| { - let mut found = false; - data.pending_configures.retain(|&s| { - if s == serial { - found = true; - } - s > serial - }); - if !found { - // client responded to a non-existing configure - shell.post_error( - zxdg_shell_v6::Error::InvalidSurfaceState as u32, - format!("Wrong configure serial: {}", serial), - ); - } - data.configured = true; - }) - .expect("xdg_surface exists but surface has not shell_surface role?!"); - }, - } -} - -/* - * xdg_toplevel - */ - -pub type ShellSurfaceUserData = ( - wl_surface::WlSurface, - zxdg_shell_v6::ZxdgShellV6, - zxdg_surface_v6::ZxdgSurfaceV6, -); - -fn destroy_toplevel(surface: &zxdg_toplevel_v6::ZxdgToplevelV6) { - let ptr = surface.get_user_data(); - surface.set_user_data(::std::ptr::null_mut()); - // drop the PositionerState - let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; - // explicit call to drop to not forget what we're doing there - ::std::mem::drop(data); -} - -// Utility functions allowing to factor out a lot of the upcoming logic -fn with_surface_toplevel_data( - idata: &ShellSurfaceIData, toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F -) where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, - F: FnOnce(&mut ToplevelState), -{ - let ptr = toplevel.get_user_data(); - let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; - idata - .compositor_token - .with_role_data::(surface, |data| match data.pending_state { - ShellSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), - _ => unreachable!(), - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); -} - -fn xdg_handle_display_state_change( - evlh: &mut EventLoopHandle, idata: &ShellSurfaceIData, - toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, maximized: Option, minimized: Option, - fullscreen: Option, output: Option<&wl_output::WlOutput>, -) where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, -{ - let handle = make_toplevel_handle(idata.compositor_token, toplevel); - // handler callback - let mut user_idata = idata.idata.borrow_mut(); - let configure = (idata.implementation.change_display_state)( - evlh, - &mut *user_idata, - handle, - maximized, - minimized, - fullscreen, - output, - ); - // send the configure response to client - send_toplevel_configure(idata.compositor_token, toplevel, configure); -} - -pub fn send_toplevel_configure( - token: CompositorToken, resource: &zxdg_toplevel_v6::ZxdgToplevelV6, - configure: ToplevelConfigure, -) where - U: 'static, - R: Role + 'static, - ID: 'static, -{ - let &(ref surface, _, ref shell_surface) = - unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; - let (w, h) = configure.size.unwrap_or((0, 0)); - // convert the Vec (which is really a Vec) into Vec - let states = { - let mut states = configure.states; - let ptr = states.as_mut_ptr(); - let len = states.len(); - let cap = states.capacity(); - ::std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - let serial = configure.serial; - resource.configure(w, h, states); - shell_surface.configure(serial); - // Add the configure as pending - token - .with_role_data::(surface, |data| data.pending_configures.push(serial)) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); -} - -fn make_toplevel_handle( - token: CompositorToken, resource: &zxdg_toplevel_v6::ZxdgToplevelV6 -) -> super::ToplevelSurface { - let ptr = resource.get_user_data(); - let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; - super::ToplevelSurface { - wl_surface: unsafe { wl_surface.clone_unchecked() }, - shell_surface: super::SurfaceKind::XdgToplevel(unsafe { resource.clone_unchecked() }), - token: token, - _shell_data: ::std::marker::PhantomData, - } -} - -fn toplevel_implementation( -) -> zxdg_toplevel_v6::Implementation> -where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, -{ - zxdg_toplevel_v6::Implementation { - destroy: |evlh, idata, _, toplevel| { - let ptr = toplevel.get_user_data(); - let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; - idata - .compositor_token - .with_role_data::(surface, |data| { - data.pending_state = ShellSurfacePendingState::None; - data.configured = false; - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); - // remove this surface from the known ones (as well as any leftover dead surface) - evlh.state() - .get_mut(&idata.state_token) - .known_toplevels - .retain(|other| { - other - .get_surface() - .map(|s| !s.equals(surface)) - .unwrap_or(false) - }); - }, - set_parent: |_, idata, _, toplevel, parent| { - with_surface_toplevel_data(idata, toplevel, |toplevel_data| { - toplevel_data.parent = parent.map(|toplevel_surface_parent| { - let parent_ptr = toplevel_surface_parent.get_user_data(); - let &(ref parent_surface, _) = - unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; - unsafe { parent_surface.clone_unchecked() } - }) - }); - }, - set_title: |_, idata, _, toplevel, title| { - with_surface_toplevel_data(idata, toplevel, |toplevel_data| { - toplevel_data.title = title; - }); - }, - set_app_id: |_, idata, _, toplevel, app_id| { - with_surface_toplevel_data(idata, toplevel, |toplevel_data| { - toplevel_data.app_id = app_id; - }); - }, - show_window_menu: |evlh, idata, _, toplevel, seat, serial, x, y| { - let handle = make_toplevel_handle(idata.compositor_token, toplevel); - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.show_window_menu)(evlh, &mut *user_idata, handle, seat, serial, x, y) - }, - move_: |evlh, idata, _, toplevel, seat, serial| { - let handle = make_toplevel_handle(idata.compositor_token, toplevel); - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.move_)(evlh, &mut *user_idata, handle, seat, serial) - }, - resize: |evlh, idata, _, toplevel, seat, serial, edges| { - let edges = - zxdg_toplevel_v6::ResizeEdge::from_raw(edges).unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); - let handle = make_toplevel_handle(idata.compositor_token, toplevel); - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.resize)(evlh, &mut *user_idata, handle, seat, serial, edges) - }, - set_max_size: |_, idata, _, toplevel, width, height| { - with_surface_toplevel_data(idata, toplevel, |toplevel_data| { - toplevel_data.max_size = (width, height); - }) - }, - set_min_size: |_, idata, _, toplevel, width, height| { - with_surface_toplevel_data(idata, toplevel, |toplevel_data| { - toplevel_data.min_size = (width, height); - }) - }, - set_maximized: |evlh, idata, _, toplevel| { - xdg_handle_display_state_change(evlh, idata, toplevel, Some(true), None, None, None); - }, - unset_maximized: |evlh, idata, _, toplevel| { - xdg_handle_display_state_change(evlh, idata, toplevel, Some(false), None, None, None); - }, - set_fullscreen: |evlh, idata, _, toplevel, seat| { - xdg_handle_display_state_change(evlh, idata, toplevel, None, None, Some(true), seat); - }, - unset_fullscreen: |evlh, idata, _, toplevel| { - xdg_handle_display_state_change(evlh, idata, toplevel, None, None, Some(false), None); - }, - set_minimized: |evlh, idata, _, toplevel| { - xdg_handle_display_state_change(evlh, idata, toplevel, None, Some(true), None, None); - }, - } -} - -/* - * xdg_popup - */ - -fn destroy_popup(surface: &zxdg_popup_v6::ZxdgPopupV6) { - let ptr = surface.get_user_data(); - surface.set_user_data(::std::ptr::null_mut()); - // drop the PositionerState - let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; - // explicit call to drop to not forget what we're doing - ::std::mem::drop(data); -} - -pub(crate) fn send_popup_configure( - token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure -) where - U: 'static, - R: Role + 'static, - ID: 'static, -{ - let &(ref surface, _, ref shell_surface) = - unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; - let (x, y) = configure.position; - let (w, h) = configure.size; - let serial = configure.serial; - resource.configure(x, y, w, h); - shell_surface.configure(serial); - // Add the configure as pending - token - .with_role_data::(surface, |data| data.pending_configures.push(serial)) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); -} - -fn make_popup_handle( - token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6 -) -> super::PopupSurface { - let ptr = resource.get_user_data(); - let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; - super::PopupSurface { - wl_surface: unsafe { wl_surface.clone_unchecked() }, - shell_surface: super::SurfaceKind::XdgPopup(unsafe { resource.clone_unchecked() }), - token: token, - _shell_data: ::std::marker::PhantomData, - } -} - -fn popup_implementation( -) -> zxdg_popup_v6::Implementation> -where - U: 'static, - R: Role + 'static, - CID: 'static, - SID: 'static, - SD: 'static, -{ - zxdg_popup_v6::Implementation { - destroy: |evlh, idata, _, popup| { - let ptr = popup.get_user_data(); - let &(ref surface, _, _) = unsafe { - &*(ptr - as *mut ( - wl_surface::WlSurface, - zxdg_shell_v6::ZxdgShellV6, - zxdg_surface_v6::ZxdgSurfaceV6, - )) - }; - idata - .compositor_token - .with_role_data::(surface, |data| { - data.pending_state = ShellSurfacePendingState::None; - data.configured = false; - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); - // remove this surface from the known ones (as well as any leftover dead surface) - evlh.state() - .get_mut(&idata.state_token) - .known_popups - .retain(|other| { - other - .get_surface() - .map(|s| !s.equals(surface)) - .unwrap_or(false) - }); - }, - grab: |evlh, idata, _, popup, seat, serial| { - let handle = make_popup_handle(idata.compositor_token, popup); - let mut user_idata = idata.idata.borrow_mut(); - (idata.implementation.grab)(evlh, &mut *user_idata, handle, seat, serial) - }, - } -}