From 3128585fc971fc6404f52d03f47c9a14f9048d32 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Tue, 5 Sep 2017 19:50:22 +0200 Subject: [PATCH] shell: shell backend first draft --- Cargo.toml | 1 + src/lib.rs | 25 +- src/shell/global.rs | 54 +++ src/shell/mod.rs | 503 ++++++++++++++++++++++++++ src/shell/wl_handlers.rs | 365 +++++++++++++++++++ src/shell/xdg_handlers.rs | 731 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1667 insertions(+), 12 deletions(-) create mode 100644 src/shell/global.rs create mode 100644 src/shell/mod.rs create mode 100644 src/shell/wl_handlers.rs create mode 100644 src/shell/xdg_handlers.rs diff --git a/Cargo.toml b/Cargo.toml index 855f6a8..57fa1e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ glium = { version = "0.16.0", optional = true, default-features = false } input = { version = "0.2.0", optional = true } clippy = { version = "*", optional = true } rental = "0.4.11" +wayland-protocols = { version = "0.9.9", features = ["unstable_protocols", "server"] } [build-dependencies] gl_generator = "0.5" diff --git a/src/lib.rs b/src/lib.rs index d706968..d6c17a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,24 +4,24 @@ //! Most entry points in the modules can take an optionnal `slog::Logger` as argument //! that will be used as a drain for logging. If `None` is provided, they'll log to `slog-stdlog`. - #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] -#[macro_use] -extern crate wayland_server; extern crate nix; -extern crate xkbcommon; -extern crate tempfile; #[macro_use] extern crate rental; +extern crate tempfile; +extern crate wayland_protocols; +#[macro_use] +extern crate wayland_server; +extern crate xkbcommon; -#[cfg(feature = "backend_winit")] -extern crate winit; -#[cfg(feature = "backend_winit")] -extern crate wayland_client; #[cfg(feature = "backend_libinput")] extern crate input; +#[cfg(feature = "backend_winit")] +extern crate wayland_client; +#[cfg(feature = "backend_winit")] +extern crate winit; extern crate libloading; @@ -36,13 +36,14 @@ pub mod backend; pub mod compositor; pub mod shm; pub mod keyboard; +pub mod shell; fn slog_or_stdlog(logger: L) -> ::slog::Logger where L: Into>, { use slog::Drain; - logger.into().unwrap_or_else(|| { - ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), o!()) - }) + logger + .into() + .unwrap_or_else(|| ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), o!())) } diff --git a/src/shell/global.rs b/src/shell/global.rs new file mode 100644 index 0000000..07e13a3 --- /dev/null +++ b/src/shell/global.rs @@ -0,0 +1,54 @@ +use super::{Handler as UserHandler, ShellClientData, ShellHandler, ShellSurfaceRole}; +use super::wl_handlers::WlShellDestructor; +use super::xdg_handlers::XdgShellDestructor; + +use compositor::Handler as CompositorHandler; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::zxdg_shell_v6; +use wayland_server::{Client, EventLoopHandle, GlobalHandler, Resource}; +use wayland_server::protocol::{wl_shell, wl_shell_surface}; + +fn shell_client_data() -> ShellClientData { + ShellClientData { + pending_ping: 0, + data: Default::default(), + } +} + +impl GlobalHandler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Default + Send + 'static, +{ + fn bind(&mut self, evlh: &mut EventLoopHandle, _: &Client, global: wl_shell::WlShell) { + debug!(self.log, "New wl_shell global binded."); + global.set_user_data(Box::into_raw(Box::new(Mutex::new(( + shell_client_data::(), + Vec::::new(), + )))) as *mut _); + evlh.register_with_destructor::<_, Self, WlShellDestructor>(&global, self.my_id); + } +} + +impl GlobalHandler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Default + Send + 'static, +{ + fn bind(&mut self, evlh: &mut EventLoopHandle, _: &Client, global: zxdg_shell_v6::ZxdgShellV6) { + debug!(self.log, "New xdg_shell global binded."); + global.set_user_data( + Box::into_raw(Box::new(Mutex::new(shell_client_data::()))) as *mut _, + ); + evlh.register_with_destructor::<_, Self, XdgShellDestructor>(&global, self.my_id); + } +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs new file mode 100644 index 0000000..00cbc5c --- /dev/null +++ b/src/shell/mod.rs @@ -0,0 +1,503 @@ +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::Role; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_popup_v6, zxdg_positioner_v6 as xdg_positioner, + zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6}; + +use wayland_server::{EventLoopHandle, EventResult, Init, Liveness, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}; + +mod global; +mod wl_handlers; +mod xdg_handlers; + +pub struct ShellSurfaceRole { + pub pending_state: ShellSurfacePendingState, + pub window_geometry: Option, + pub pending_configures: Vec, + pub configured: bool, +} + +#[derive(Copy, Clone, Debug)] +pub struct PositionerState { + pub rect_size: (i32, i32), + pub anchor_rect: Rectangle, + pub anchor_edges: xdg_positioner::Anchor, + pub gravity: xdg_positioner::Gravity, + pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, + pub offset: (i32, i32), +} + +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. + 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` protocole + Popup(PopupState), + /// This surface was not yet assigned a kind + None, +} + +pub struct ToplevelState { + pub parent: Option, + pub title: String, + pub app_id: String, + pub min_size: (i32, i32), + pub max_size: (i32, i32), +} + +impl ToplevelState { + 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, + } + } +} + +pub struct PopupState { + pub parent: wl_surface::WlSurface, + pub positioner: PositionerState, +} + +impl PopupState { + pub fn clone(&self) -> Option { + if let Some(p) = self.parent.clone() { + Some(PopupState { + parent: p, + positioner: self.positioner.clone(), + }) + } 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 + } +} + +pub struct ShellHandler { + my_id: usize, + log: ::slog::Logger, + token: CompositorToken, + handler: SH, + known_toplevels: Vec>, + known_popups: Vec>, + _shell_data: ::std::marker::PhantomData, +} + +impl Init for ShellHandler { + fn init(&mut self, _evqh: &mut EventLoopHandle, index: usize) { + self.my_id = index; + debug!(self.log, "Init finished") + } +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + /// Create a new CompositorHandler + pub fn new(handler: SH, token: CompositorToken, logger: L) -> ShellHandler + where + L: Into>, + { + let log = ::slog_or_stdlog(logger); + ShellHandler { + my_id: ::std::usize::MAX, + log: log.new(o!("smithay_module" => "shell_handler")), + token: token, + handler: handler, + known_toplevels: Vec::new(), + known_popups: Vec::new(), + _shell_data: ::std::marker::PhantomData, + } + } + + /// Access the inner handler of this CompositorHandler + pub fn get_handler(&mut self) -> &mut SH { + &mut self.handler + } + + /// 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), +} + +struct ShellClientData { + pending_ping: u32, + data: SD, +} + +pub struct ShellClient { + kind: ShellClientKind, + _data: ::std::marker::PhantomData<*mut SD>, +} + +impl ShellClient { + pub fn alive(&self) -> bool { + match self.kind { + ShellClientKind::Wl(ref s) => s.status() == Liveness::Alive, + ShellClientKind::Xdg(ref s) => s.status() == Liveness::Alive, + } + } + + 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(()) + } + + 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), +} + +pub struct ToplevelSurface { + wl_surface: wl_surface::WlSurface, + shell_surface: SurfaceKind, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl ToplevelSurface +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + 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 + } + + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + pub fn client(&self) -> ShellClient { + match self.shell_surface { + SurfaceKind::Wl(ref s) => { + let &(_, ref shell) = + unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; + 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) }; + ShellClient { + kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } + } + SurfaceKind::XdgPopup(_) => unreachable!(), + } + } + + 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 + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + 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) + } +} + +pub struct PopupSurface { + wl_surface: wl_surface::WlSurface, + shell_surface: SurfaceKind, + token: CompositorToken, + _shell_data: ::std::marker::PhantomData, +} + +impl PopupSurface +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, +{ + 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 + } + + pub fn equals(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface) + } + + pub fn client(&self) -> ShellClient { + match self.shell_surface { + SurfaceKind::Wl(ref s) => { + let &(_, ref shell) = + unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) }; + 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) }; + ShellClient { + kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } + } + SurfaceKind::XdgToplevel(_) => unreachable!(), + } + } + + 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(()) + } + + 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!(), + } + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.wl_surface) + } else { + None + } + } + + 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) + } +} + +pub struct ToplevelConfigure { + pub size: Option<(i32, i32)>, + pub states: Vec, + pub serial: u32, +} + +pub struct PopupConfigure { + pub position: (i32, i32), + pub size: (i32, i32), + pub serial: u32, +} + +pub trait Handler { + fn new_client(&mut self, evlh: &mut EventLoopHandle, client: ShellClient); + fn client_pong(&mut self, evlh: &mut EventLoopHandle, client: ShellClient); + fn new_toplevel(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface) + -> ToplevelConfigure; + fn new_popup(&mut self, evlh: &mut EventLoopHandle, surface: PopupSurface) + -> PopupConfigure; + fn move_(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32); + fn resize(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32, edges: zxdg_toplevel_v6::ResizeEdge); + fn grab(&mut self, evlh: &mut EventLoopHandle, surface: PopupSurface, + seat: &wl_seat::WlSeat, serial: u32); + fn change_display_state(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + maximized: Option, minimized: Option, fullscreen: Option, + output: Option<&wl_output::WlOutput>) + -> ToplevelConfigure; + fn show_window_menu(&mut self, evlh: &mut EventLoopHandle, surface: ToplevelSurface, + seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32); +} diff --git a/src/shell/wl_handlers.rs b/src/shell/wl_handlers.rs new file mode 100644 index 0000000..89edb94 --- /dev/null +++ b/src/shell/wl_handlers.rs @@ -0,0 +1,365 @@ +use super::{Handler as UserHandler, PopupConfigure, PopupState, PositionerState, ShellClient, + ShellClientData, ShellHandler, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, + ToplevelState}; + +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_positioner_v6 as xdg_positioner, zxdg_toplevel_v6}; + +use wayland_server::{Client, Destroy, EventLoopHandle, Init, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}; + +pub struct WlShellDestructor { + _data: ::std::marker::PhantomData, +} + +/* + * wl_shell + */ + +pub type ShellUserData = Mutex<(ShellClientData, Vec)>; + +impl Destroy for WlShellDestructor { + fn destroy(shell: &wl_shell::WlShell) { + let ptr = shell.get_user_data(); + shell.set_user_data(::std::ptr::null_mut()); + let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; + } +} + +pub fn make_shell_client(resource: &wl_shell::WlShell) -> ShellClient { + ShellClient { + kind: super::ShellClientKind::Wl(unsafe { resource.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } +} + +impl wl_shell::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn get_shell_surface(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell::WlShell, id: wl_shell_surface::WlShellSurface, + surface: &wl_surface::WlSurface) { + trace!(self.log, "Creating new wl_shell_surface."); + let role_data = ShellSurfaceRole { + pending_state: ShellSurfacePendingState::None, + window_geometry: None, + pending_configures: Vec::new(), + configured: true, + }; + if let Err(_) = self.token.give_role_with(surface, role_data) { + resource.post_error( + wl_shell::Error::Role as u32, + "Surface already has a role.".into(), + ); + return; + } + id.set_user_data( + Box::into_raw(Box::new(unsafe { surface.clone_unchecked() })) as *mut _, + ); + evqh.register_with_destructor::<_, Self, WlShellDestructor>(&id, self.my_id); + + // register ourselves to the wl_shell for ping handling + let mutex = unsafe { &*(resource.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.1.len() == 0 && guard.0.pending_ping != 0 { + // there is a pending ping that no surface could receive yet, send it + // note this is not possible that it was received and then a wl_shell_surface was + // destroyed, because wl_shell_surface has no destructor! + id.ping(guard.0.pending_ping); + } + guard.1.push(id); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH:[UserHandler, Send], SD: [Send]>, + wl_shell::Handler, + wl_shell::WlShell +); + +/* + * wl_shell_surface + */ + +pub type ShellSurfaceUserData = (wl_surface::WlSurface, wl_shell::WlShell); + +impl Destroy for WlShellDestructor { + fn destroy(shell_surface: &wl_shell_surface::WlShellSurface) { + let ptr = shell_surface.get_user_data(); + shell_surface.set_user_data(::std::ptr::null_mut()); + // drop the WlSurface object + let surface = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; + } +} + +fn make_toplevel_handle(token: CompositorToken, + resource: &wl_shell_surface::WlShellSurface) + -> 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::Wl(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +fn make_popup_handle(token: CompositorToken, + resource: &wl_shell_surface::WlShellSurface) + -> 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::Wl(unsafe { resource.clone_unchecked() }), + token: token, + _shell_data: ::std::marker::PhantomData, + } +} + +pub fn send_toplevel_configure(resource: &wl_shell_surface::WlShellSurface, configure: ToplevelConfigure) { + let (w, h) = configure.size.unwrap_or((0, 0)); + resource.configure(wl_shell_surface::Resize::empty(), w, h); +} + +pub fn send_popup_configure(resource: &wl_shell_surface::WlShellSurface, configure: PopupConfigure) { + let (w, h) = configure.size; + resource.configure(wl_shell_surface::Resize::empty(), w, h); +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn wl_handle_display_state_change(&mut self, evqh: &mut EventLoopHandle, + resource: &wl_shell_surface::WlShellSurface, + maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>) { + let handle = make_toplevel_handle(self.token, resource); + // handler callback + let configure = + self.handler + .change_display_state(evqh, handle, maximized, minimized, fullscreen, output); + // send the configure response to client + let (w, h) = configure.size.unwrap_or((0, 0)); + resource.configure(wl_shell_surface::None, w, h); + } + + fn wl_ensure_toplevel(&mut self, evqh: &mut EventLoopHandle, + resource: &wl_shell_surface::WlShellSurface) { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + // copy token to make borrow checker happy + let token = self.token; + let need_send = token + .with_role_data::(wl_surface, |data| { + match data.pending_state { + ShellSurfacePendingState::Toplevel(_) => { + return false; + } + ShellSurfacePendingState::Popup(_) => { + // this is no longer a popup, deregister it + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + } + ShellSurfacePendingState::None => {} + } + // This was not previously toplevel, need to make it toplevel + data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState { + parent: None, + title: String::new(), + app_id: String::new(), + min_size: (0, 0), + max_size: (0, 0), + }); + return true; + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + // we need to notify about this new toplevel surface + if need_send { + let handle = make_toplevel_handle(self.token, resource); + let configure = self.handler.new_toplevel(evqh, handle); + send_toplevel_configure(resource, configure); + } + } +} + +impl wl_shell_surface::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn pong(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, serial: u32) { + let &(_, ref shell) = unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; + let valid = { + let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; + let mut guard = mutex.lock().unwrap(); + if guard.0.pending_ping == serial { + guard.0.pending_ping = 0; + true + } else { + false + } + }; + if valid { + self.handler.client_pong(evqh, make_shell_client(shell)); + } + } + + fn move_(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler.move_(evqh, handle, seat, serial); + } + + fn resize(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32, + edges: wl_shell_surface::Resize) { + let edges = zxdg_toplevel_v6::ResizeEdge::from_raw(edges.bits()) + .unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); + let handle = make_toplevel_handle(self.token, resource); + self.handler.resize(evqh, handle, seat, serial, edges); + } + + fn set_toplevel(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface) { + self.wl_ensure_toplevel(evqh, resource); + self.wl_handle_display_state_change(evqh, resource, Some(false), Some(false), Some(false), None) + } + + fn set_transient(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, parent: &wl_surface::WlSurface, x: i32, + y: i32, flags: wl_shell_surface::Transient) { + self.wl_ensure_toplevel(evqh, resource); + // set the parent + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data::(wl_surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.parent = Some(unsafe { parent.clone_unchecked() }); + } + _ => unreachable!(), + }) + .unwrap(); + // set as regular surface + self.wl_handle_display_state_change(evqh, resource, Some(false), Some(false), Some(false), None) + } + + fn set_fullscreen(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, + method: wl_shell_surface::FullscreenMethod, framerate: u32, + output: Option<&wl_output::WlOutput>) { + self.wl_ensure_toplevel(evqh, resource); + self.wl_handle_display_state_change(evqh, resource, Some(false), Some(false), Some(true), output) + } + + fn set_popup(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, seat: &wl_seat::WlSeat, serial: u32, + parent: &wl_surface::WlSurface, x: i32, y: i32, flags: wl_shell_surface::Transient) { + let ptr = resource.get_user_data(); + let &(ref wl_surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + // we are reseting the popup state, so remove this surface from everywhere + self.known_toplevels.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(wl_surface)) + .unwrap_or(false) + }); + self.token + .with_role_data(wl_surface, |data| { + data.pending_state = ShellSurfacePendingState::Popup(PopupState { + parent: unsafe { parent.clone_unchecked() }, + positioner: PositionerState { + rect_size: (1, 1), + anchor_rect: Rectangle { + x, + y, + width: 1, + height: 1, + }, + anchor_edges: xdg_positioner::Anchor::empty(), + gravity: xdg_positioner::Gravity::empty(), + constraint_adjustment: xdg_positioner::ConstraintAdjustment::empty(), + offset: (0, 0), + }, + }); + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + + // notify the handler about this new popup + let handle = make_popup_handle(self.token, resource); + let configure = self.handler.new_popup(evqh, handle); + send_popup_configure(resource, configure); + } + + fn set_maximized(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, output: Option<&wl_output::WlOutput>) { + self.wl_ensure_toplevel(evqh, resource); + self.wl_handle_display_state_change(evqh, resource, Some(true), Some(false), Some(false), output) + } + + fn set_title(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, title: String) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.title = title; + } + _ => {} + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + } + + fn set_class(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &wl_shell_surface::WlShellSurface, class_: String) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.token + .with_role_data(surface, |data| match data.pending_state { + ShellSurfacePendingState::Toplevel(ref mut state) => { + state.app_id = class_; + } + _ => {} + }) + .expect("wl_shell_surface exists but wl_surface has wrong role?!"); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + wl_shell_surface::Handler, + wl_shell_surface::WlShellSurface +); diff --git a/src/shell/xdg_handlers.rs b/src/shell/xdg_handlers.rs new file mode 100644 index 0000000..50c92a7 --- /dev/null +++ b/src/shell/xdg_handlers.rs @@ -0,0 +1,731 @@ +use super::{Handler as UserHandler, PopupConfigure, PopupState, PositionerState, ShellClient, + ShellClientData, ShellHandler, ShellSurfacePendingState, ShellSurfaceRole, ToplevelConfigure, + ToplevelState}; + +use compositor::{CompositorToken, Handler as CompositorHandler, Rectangle}; +use compositor::roles::*; + +use std::sync::Mutex; + +use wayland_protocols::unstable::xdg_shell::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6, + zxdg_surface_v6, zxdg_toplevel_v6}; +use wayland_server::{Client, Destroy, EventLoopHandle, Init, Resource}; +use wayland_server::protocol::{wl_output, wl_seat, wl_surface}; + +pub struct XdgShellDestructor { + _data: ::std::marker::PhantomData, +} + +/* + * xdg_shell + */ + +pub type ShellUserData = Mutex>; + +impl Destroy for XdgShellDestructor { + fn destroy(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) }; + } +} + +pub fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient { + ShellClient { + kind: super::ShellClientKind::Xdg(unsafe { resource.clone_unchecked() }), + _data: ::std::marker::PhantomData, + } +} + +impl zxdg_shell_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_shell_v6::ZxdgShellV6) { + } + fn create_positioner(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_shell_v6::ZxdgShellV6, id: zxdg_positioner_v6::ZxdgPositionerV6) { + trace!(self.log, "Creating new xdg_positioner."); + id.set_user_data(Box::into_raw(Box::new(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), + })) as *mut _); + evqh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + } + fn get_xdg_surface(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_shell_v6::ZxdgShellV6, id: zxdg_surface_v6::ZxdgSurfaceV6, + surface: &wl_surface::WlSurface) { + trace!(self.log, "Creating new wl_shell_surface."); + let role_data = ShellSurfaceRole { + pending_state: ShellSurfacePendingState::None, + window_geometry: None, + pending_configures: Vec::new(), + configured: false, + }; + if let Err(_) = self.token.give_role_with(surface, role_data) { + resource.post_error( + zxdg_shell_v6::Error::Role as u32, + "Surface already has a role.".into(), + ); + return; + } + id.set_user_data( + Box::into_raw(Box::new((unsafe { surface.clone_unchecked() }, unsafe { + resource.clone_unchecked() + }))) as *mut _, + ); + evqh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + } + + fn pong(&mut self, evqh: &mut EventLoopHandle, client: &Client, resource: &zxdg_shell_v6::ZxdgShellV6, + serial: u32) { + let valid = { + let mutex = unsafe { &*(resource.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 { + self.handler.client_pong(evqh, make_shell_client(resource)); + } + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_shell_v6::Handler, + zxdg_shell_v6::ZxdgShellV6 +); + +/* + * xdg_positioner + */ + +impl Destroy for XdgShellDestructor { + fn destroy(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) }; + } +} + +impl zxdg_positioner_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6) { + } + + fn set_size(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, width: i32, height: i32) { + if width < 1 || height < 1 { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.rect_size = (width, height); + } + } + + fn set_anchor_rect(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, x: i32, y: i32, width: i32, + height: i32) { + if width < 1 || height < 1 { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid size for positioner's anchor rectangle.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.anchor_rect = Rectangle { + x, + y, + width, + height, + }; + } + } + + fn set_anchor(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, anchor: zxdg_positioner_v6::Anchor) { + use self::zxdg_positioner_v6::{AnchorBottom, AnchorLeft, AnchorRight, AnchorTop}; + if anchor.contains(AnchorLeft | AnchorRight) || anchor.contains(AnchorTop | AnchorBottom) { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid anchor for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.anchor_edges = anchor; + } + } + + fn set_gravity(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, gravity: zxdg_positioner_v6::Gravity) { + use self::zxdg_positioner_v6::{GravityBottom, GravityLeft, GravityRight, GravityTop}; + if gravity.contains(GravityLeft | GravityRight) || gravity.contains(GravityTop | GravityBottom) { + resource.post_error( + zxdg_positioner_v6::Error::InvalidInput as u32, + "Invalid gravity for positioner.".into(), + ); + } else { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.gravity = gravity; + } + } + + fn set_constraint_adjustment(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, + constraint_adjustment: u32) { + let constraint_adjustment = + zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.constraint_adjustment = constraint_adjustment; + } + + fn set_offset(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_positioner_v6::ZxdgPositionerV6, x: i32, y: i32) { + let ptr = resource.get_user_data(); + let state = unsafe { &mut *(ptr as *mut PositionerState) }; + state.offset = (x, y); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_positioner_v6::Handler, + zxdg_positioner_v6::ZxdgPositionerV6 +); + +/* + * xdg_surface + */ + +impl Destroy for XdgShellDestructor { + fn destroy(surface: &zxdg_surface_v6::ZxdgSurfaceV6) { + 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 (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6), + ) + }; + } +} + +impl zxdg_surface_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.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?!", + ); + } + + fn get_toplevel(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, id: zxdg_toplevel_v6::ZxdgToplevelV6) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.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?!", + ); + + id.set_user_data(Box::into_raw(Box::new(unsafe { + ( + surface.clone_unchecked(), + shell.clone_unchecked(), + resource.clone_unchecked(), + ) + })) as *mut _); + evqh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + + // register to self + self.known_toplevels + .push(make_toplevel_handle(self.token, &id)); + + // intial configure event + let handle = make_toplevel_handle(self.token, &id); + let configure = self.handler.new_toplevel(evqh, handle); + send_toplevel_configure(self.token, &id, configure); + } + + fn get_popup(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, id: zxdg_popup_v6::ZxdgPopupV6, + parent: &zxdg_surface_v6::ZxdgSurfaceV6, + positioner: &zxdg_positioner_v6::ZxdgPositionerV6) { + let ptr = resource.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 { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + + self.token + .with_role_data::(surface, |data| { + data.pending_state = ShellSurfacePendingState::Popup(PopupState { + parent: unsafe { parent_surface.clone_unchecked() }, + positioner: positioner_data.clone(), + }); + }) + .expect( + "xdg_surface exists but surface has not shell_surface role?!", + ); + + id.set_user_data(Box::into_raw(Box::new(unsafe { + ( + surface.clone_unchecked(), + shell.clone_unchecked(), + resource.clone_unchecked(), + ) + })) as *mut _); + evqh.register_with_destructor::<_, Self, XdgShellDestructor>(&id, self.my_id); + + // register to self + self.known_popups.push(make_popup_handle(self.token, &id)); + + // intial configure event + let handle = make_popup_handle(self.token, &id); + let configure = self.handler.new_popup(evqh, handle); + send_popup_configure(self.token, &id, configure); + } + + fn set_window_geometry(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, x: i32, y: i32, width: i32, + height: i32) { + let ptr = resource.get_user_data(); + let &(ref surface, _) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.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?!", + ); + } + + fn ack_configure(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_surface_v6::ZxdgSurfaceV6, serial: u32) { + let ptr = resource.get_user_data(); + let &(ref surface, ref shell) = + unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) }; + self.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?!", + ); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_surface_v6::Handler, + zxdg_surface_v6::ZxdgSurfaceV6 +); + +/* + * xdg_toplevel + */ + +pub type ShellSurfaceUserData = ( + wl_surface::WlSurface, + zxdg_shell_v6::ZxdgShellV6, + zxdg_surface_v6::ZxdgSurfaceV6, +); + +impl Destroy for XdgShellDestructor { + fn destroy(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) }; + } +} + +impl ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + // Utility function allowing to factor out a lot of the upcoming logic + fn with_surface_toplevel_data(&self, resource: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) + where + F: FnOnce(&mut ToplevelState), + { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.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(&mut self, evqh: &mut EventLoopHandle, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + maximized: Option, minimized: Option, + fullscreen: Option, output: Option<&wl_output::WlOutput>) { + let handle = make_toplevel_handle(self.token, resource); + // handler callback + let configure = + self.handler + .change_display_state(evqh, handle, maximized, minimized, fullscreen, output); + // send the configure response to client + send_toplevel_configure(self.token, resource, configure); + } +} + +pub fn send_toplevel_configure(token: CompositorToken, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + configure: ToplevelConfigure) +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + '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, + } +} + +impl zxdg_toplevel_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; + self.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) + self.known_toplevels.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(surface)) + .unwrap_or(false) + }); + } + + fn set_parent(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, + parent: Option<&zxdg_toplevel_v6::ZxdgToplevelV6>) { + self.with_surface_toplevel_data(resource, |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() } + }) + }); + } + + fn set_title(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, title: String) { + self.with_surface_toplevel_data(resource, |toplevel_data| { toplevel_data.title = title; }); + } + + fn set_app_id(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, app_id: String) { + self.with_surface_toplevel_data(resource, |toplevel_data| { toplevel_data.app_id = app_id; }); + } + + fn show_window_menu(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32, + x: i32, y: i32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler + .show_window_menu(evqh, handle, seat, serial, x, y); + } + + fn move_(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_toplevel_handle(self.token, resource); + self.handler.move_(evqh, handle, seat, serial); + } + + fn resize(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, seat: &wl_seat::WlSeat, serial: u32, edges: u32) { + let edges = + zxdg_toplevel_v6::ResizeEdge::from_raw(edges).unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); + let handle = make_toplevel_handle(self.token, resource); + self.handler.resize(evqh, handle, seat, serial, edges); + } + + fn set_max_size(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, width: i32, height: i32) { + self.with_surface_toplevel_data(resource, |toplevel_data| { + toplevel_data.max_size = (width, height); + }); + } + + fn set_min_size(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, width: i32, height: i32) { + self.with_surface_toplevel_data(resource, |toplevel_data| { + toplevel_data.min_size = (width, height); + }); + } + + fn set_maximized(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evqh, resource, Some(true), None, None, None); + } + + fn unset_maximized(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evqh, resource, Some(false), None, None, None); + } + + fn set_fullscreen(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6, output: Option<&wl_output::WlOutput>) { + self.xdg_handle_display_state_change(evqh, resource, None, None, Some(true), output); + } + + fn unset_fullscreen(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evqh, resource, None, None, Some(false), None); + } + + fn set_minimized(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_toplevel_v6::ZxdgToplevelV6) { + self.xdg_handle_display_state_change(evqh, resource, None, Some(true), None, None); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_toplevel_v6::Handler, + zxdg_toplevel_v6::ZxdgToplevelV6 +); + +/* + * xdg_popup + */ + + + +impl Destroy for XdgShellDestructor { + fn destroy(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) }; + } +} + +pub fn send_popup_configure(token: CompositorToken, resource: &zxdg_popup_v6::ZxdgPopupV6, + configure: PopupConfigure) +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + '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, + } +} + +impl zxdg_popup_v6::Handler for ShellHandler +where + U: Send + 'static, + R: Role + Send + 'static, + H: CompositorHandler + Send + 'static, + SH: UserHandler + Send + 'static, + SD: Send + 'static, +{ + fn destroy(&mut self, evqh: &mut EventLoopHandle, client: &Client, + resource: &zxdg_popup_v6::ZxdgPopupV6) { + let ptr = resource.get_user_data(); + let &(ref surface, _, _) = unsafe { + &*(ptr as + *mut ( + wl_surface::WlSurface, + zxdg_shell_v6::ZxdgShellV6, + zxdg_surface_v6::ZxdgSurfaceV6, + )) + }; + self.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) + self.known_popups.retain(|other| { + other + .get_surface() + .map(|s| !s.equals(surface)) + .unwrap_or(false) + }); + } + + fn grab(&mut self, evqh: &mut EventLoopHandle, client: &Client, resource: &zxdg_popup_v6::ZxdgPopupV6, + seat: &wl_seat::WlSeat, serial: u32) { + let handle = make_popup_handle(self.token, resource); + self.handler.grab(evqh, handle, seat, serial); + } +} + +server_declare_handler!( + ShellHandler, Send], H:[CompositorHandler, Send], SH: [UserHandler, Send], SD: [Send]>, + zxdg_popup_v6::Handler, + zxdg_popup_v6::ZxdgPopupV6 +);