From ad55ab71c91d67d8b81fd139dab3bda496fd6d9d Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 23 Jun 2021 09:43:53 +0200 Subject: [PATCH] compositor: Introduce transaction state tracking This changes the state handling logic of wl_surface to automatically track subsurface pending in the form of transactions. The role enum (and the associated type parameter) is no more, and replaced by a general-purpose typemap-like container on surfaces. The new logic is introduced in the files: - `src/wayland/compositor/cache.rs` - `src/wayland/compositor/transaction.rs` The rest of the PR is the fallout of these changes, as well as a few trivial clippy fixes. --- Cargo.toml | 4 +- anvil/src/drawing.rs | 90 +-- anvil/src/main.rs | 2 - anvil/src/shell.rs | 570 ++++++--------- anvil/src/state.rs | 15 +- anvil/src/udev.rs | 28 +- anvil/src/window_map.rs | 199 +++--- anvil/src/winit.rs | 27 +- anvil/src/xwayland/mod.rs | 19 +- src/backend/allocator/dumb.rs | 3 +- src/backend/drm/surface/legacy.rs | 5 +- src/backend/egl/native.rs | 6 +- src/backend/renderer/gles2/mod.rs | 12 +- src/backend/renderer/mod.rs | 10 +- src/wayland/compositor/cache.rs | 204 ++++++ src/wayland/compositor/handlers.rs | 271 +++++--- src/wayland/compositor/mod.rs | 495 ++++++------- src/wayland/compositor/roles.rs | 280 -------- src/wayland/compositor/transaction.rs | 288 ++++++++ src/wayland/compositor/tree.rs | 312 +++++---- src/wayland/data_device/dnd_grab.rs | 20 +- src/wayland/data_device/mod.rs | 32 +- src/wayland/dmabuf/mod.rs | 4 +- src/wayland/explicit_synchronization/mod.rs | 223 +++--- src/wayland/output/mod.rs | 6 +- src/wayland/seat/mod.rs | 56 +- src/wayland/seat/pointer.rs | 56 +- src/wayland/shell/legacy/mod.rs | 107 +-- src/wayland/shell/legacy/wl_handlers.rs | 132 ++-- src/wayland/shell/xdg/mod.rs | 726 ++++++++------------ src/wayland/shell/xdg/xdg_handlers.rs | 381 +++++----- src/wayland/shell/xdg/zxdgv6_handlers.rs | 379 +++++----- 32 files changed, 2277 insertions(+), 2685 deletions(-) create mode 100644 src/wayland/compositor/cache.rs delete mode 100644 src/wayland/compositor/roles.rs create mode 100644 src/wayland/compositor/transaction.rs diff --git a/Cargo.toml b/Cargo.toml index e1e0612..51bf654 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,12 @@ edition = "2018" members = [ "anvil" ] [dependencies] +appendlist = "1.4" bitflags = "1" calloop = "0.8.0" cgmath = "0.18.0" dbus = { version = "0.9.0", optional = true } -libseat= { version = "0.1.1", optional = true } +downcast-rs = "1.2.0" drm-fourcc = "^2.1.1" drm = { version = "0.4.0", optional = true } drm-ffi = { version = "0.1.0", optional = true } @@ -24,6 +25,7 @@ input = { version = "0.5", default-features = false, optional = true } image = { version = "0.23.14", default-features = false, optional = true } lazy_static = "1" libc = "0.2.70" +libseat= { version = "0.1.1", optional = true } libloading = "0.7.0" nix = "0.20" slog = "2" diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 4a71622..6e61fd4 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -1,6 +1,6 @@ #![allow(clippy::too_many_arguments)] -use std::cell::RefCell; +use std::{cell::RefCell, sync::Mutex}; use slog::Logger; use smithay::{ @@ -11,13 +11,15 @@ use smithay::{ reexports::wayland_server::protocol::{wl_buffer, wl_surface}, utils::Rectangle, wayland::{ - compositor::{roles::Role, Damage, SubsurfaceRole, TraversalAction}, - data_device::DnDIconRole, - seat::CursorImageRole, + compositor::{ + get_role, with_states, with_surface_tree_upward, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, + }, + seat::CursorImageAttributes, }, }; -use crate::shell::{MyCompositorToken, MyWindowMap, SurfaceData}; +use crate::{shell::SurfaceData, window_map::WindowMap}; struct BufferTextures { buffer: Option, @@ -37,7 +39,6 @@ pub fn draw_cursor( frame: &mut F, surface: &wl_surface::WlSurface, (x, y): (i32, i32), - token: MyCompositorToken, log: &Logger, ) -> Result<(), SwapBuffersError> where @@ -46,9 +47,21 @@ where E: std::error::Error + Into, T: Texture + 'static, { - let (dx, dy) = match token.with_role_data::(surface, |data| data.hotspot) { - Ok(h) => h, - Err(_) => { + let ret = with_states(surface, |states| { + Some( + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot, + ) + }) + .unwrap_or(None); + let (dx, dy) = match ret { + Some(h) => h, + None => { warn!( log, "Trying to display as a cursor a surface that does not have the CursorImage role." @@ -56,7 +69,7 @@ where (0, 0) } }; - draw_surface_tree(renderer, frame, surface, (x - dx, y - dy), token, log) + draw_surface_tree(renderer, frame, surface, (x - dx, y - dy), log) } fn draw_surface_tree( @@ -64,7 +77,6 @@ fn draw_surface_tree( frame: &mut F, root: &wl_surface::WlSurface, location: (i32, i32), - compositor_token: MyCompositorToken, log: &Logger, ) -> Result<(), SwapBuffersError> where @@ -75,15 +87,16 @@ where { let mut result = Ok(()); - compositor_token.with_surface_tree_upward( + with_surface_tree_upward( root, location, - |_surface, attributes, role, &(mut x, mut y)| { + |_surface, states, &(mut x, mut y)| { // Pull a new buffer if available - if let Some(data) = attributes.user_data.get::>() { + if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); + let attributes = states.cached_state.current::(); if data.texture.is_none() { - if let Some(buffer) = data.current_state.buffer.take() { + if let Some(buffer) = data.buffer.take() { let damage = attributes .damage .iter() @@ -94,16 +107,18 @@ where }) .collect::>(); - match renderer.import_buffer(&buffer, Some(&attributes), &damage) { + match renderer.import_buffer(&buffer, Some(states), &damage) { Some(Ok(m)) => { - if let Some(BufferType::Shm) = buffer_type(&buffer) { + let texture_buffer = if let Some(BufferType::Shm) = buffer_type(&buffer) { buffer.release(); - } + None + } else { + Some(buffer) + }; data.texture = Some(Box::new(BufferTextures { - buffer: Some(buffer), + buffer: texture_buffer, texture: m, - }) - as Box) + })) } Some(Err(err)) => { warn!(log, "Error loading buffer: {:?}", err); @@ -119,9 +134,10 @@ where // Now, should we be drawn ? if data.texture.is_some() { // if yes, also process the children - if Role::::has(role) { - x += data.current_state.sub_location.0; - y += data.current_state.sub_location.1; + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + x += current.location.0; + y += current.location.1; } TraversalAction::DoChildren((x, y)) } else { @@ -133,10 +149,9 @@ where TraversalAction::SkipChildren } }, - |_surface, attributes, role, &(mut x, mut y)| { - if let Some(ref data) = attributes.user_data.get::>() { + |_surface, states, &(mut x, mut y)| { + if let Some(ref data) = states.data_map.get::>() { let mut data = data.borrow_mut(); - let (sub_x, sub_y) = data.current_state.sub_location; if let Some(texture) = data .texture .as_mut() @@ -144,9 +159,10 @@ where { // we need to re-extract the subsurface offset, as the previous closure // only passes it to our children - if Role::::has(role) { - x += sub_x; - y += sub_y; + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + x += current.location.0; + y += current.location.1; } if let Err(err) = frame.render_texture_at( &texture.texture, @@ -159,7 +175,7 @@ where } } }, - |_, _, _, _| true, + |_, _, _| true, ); result @@ -168,9 +184,8 @@ where pub fn draw_windows( renderer: &mut R, frame: &mut F, - window_map: &MyWindowMap, + window_map: &WindowMap, output_rect: Option, - compositor_token: MyCompositorToken, log: &::slog::Logger, ) -> Result<(), SwapBuffersError> where @@ -192,9 +207,7 @@ where } if let Some(wl_surface) = toplevel_surface.get_surface() { // this surface is a root of a subsurface tree that needs to be drawn - if let Err(err) = - draw_surface_tree(renderer, frame, &wl_surface, initial_place, compositor_token, log) - { + if let Err(err) = draw_surface_tree(renderer, frame, &wl_surface, initial_place, log) { result = Err(err); } } @@ -208,7 +221,6 @@ pub fn draw_dnd_icon( frame: &mut F, surface: &wl_surface::WlSurface, (x, y): (i32, i32), - token: MyCompositorToken, log: &::slog::Logger, ) -> Result<(), SwapBuffersError> where @@ -217,11 +229,11 @@ where E: std::error::Error + Into, T: Texture + 'static, { - if !token.has_role::(surface) { + if get_role(surface) != Some("dnd_icon") { warn!( log, "Trying to display as a dnd icon a surface that does not have the DndIcon role." ); } - draw_surface_tree(renderer, frame, surface, (x, y), token, log) + draw_surface_tree(renderer, frame, surface, (x, y), log) } diff --git a/anvil/src/main.rs b/anvil/src/main.rs index 4de14bc..020156f 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -2,8 +2,6 @@ #[macro_use] extern crate slog; -#[macro_use(define_roles)] -extern crate smithay; use std::{cell::RefCell, rc::Rc}; diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index c433155..4a2d76e 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -9,25 +9,22 @@ use smithay::{ reexports::{ wayland_protocols::xdg_shell::server::xdg_toplevel, wayland_server::{ - protocol::{wl_buffer, wl_callback, wl_pointer::ButtonState, wl_shell_surface, wl_surface}, + protocol::{wl_buffer, wl_pointer::ButtonState, wl_shell_surface, wl_surface}, Display, }, }, utils::Rectangle, wayland::{ compositor::{ - compositor_init, roles::Role, BufferAssignment, CompositorToken, RegionAttributes, - SubsurfaceRole, SurfaceEvent, TraversalAction, + compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment, + SurfaceAttributes, TraversalAction, }, - data_device::DnDIconRole, - seat::{AxisFrame, CursorImageRole, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, + seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, shell::{ - legacy::{ - wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind, ShellSurfaceRole, - }, + legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind}, xdg::{ - xdg_shell_init, Configure, ShellState as XdgShellState, XdgPopupSurfaceRole, XdgRequest, - XdgToplevelSurfaceRole, + xdg_shell_init, Configure, ShellState as XdgShellState, SurfaceCachedState, XdgRequest, + XdgToplevelSurfaceRoleAttributes, }, }, Serial, @@ -39,27 +36,10 @@ use crate::{ window_map::{Kind as SurfaceKind, PopupKind, WindowMap}, }; -#[cfg(feature = "xwayland")] -use crate::xwayland::X11SurfaceRole; - -define_roles!(Roles => - [ XdgToplevelSurface, XdgToplevelSurfaceRole ] - [ XdgPopupSurface, XdgPopupSurfaceRole ] - [ ShellSurface, ShellSurfaceRole] - #[cfg(feature = "xwayland")] - [ X11Surface, X11SurfaceRole ] - [ DnDIcon, DnDIconRole ] - [ CursorImage, CursorImageRole ] -); - -pub type MyWindowMap = WindowMap; - -pub type MyCompositorToken = CompositorToken; - struct MoveSurfaceGrab { start_data: GrabStartData, - window_map: Rc>, - toplevel: SurfaceKind, + window_map: Rc>, + toplevel: SurfaceKind, initial_window_location: (i32, i32), } @@ -150,8 +130,7 @@ impl From for xdg_toplevel::ResizeEdge { struct ResizeSurfaceGrab { start_data: GrabStartData, - ctoken: MyCompositorToken, - toplevel: SurfaceKind, + toplevel: SurfaceKind, edges: ResizeEdge, initial_window_size: (i32, i32), last_window_size: (i32, i32), @@ -197,12 +176,11 @@ impl PointerGrab for ResizeSurfaceGrab { new_window_height = (self.initial_window_size.1 as f64 + dy) as i32; } - let (min_size, max_size) = - self.ctoken - .with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { - let data = attrs.user_data.get::>().unwrap().borrow(); - (data.min_size, data.max_size) - }); + let (min_size, max_size) = with_states(self.toplevel.get_surface().unwrap(), |states| { + let data = states.cached_state.current::(); + (data.min_size, data.max_size) + }) + .unwrap(); let min_width = min_size.0.max(1); let min_height = min_size.1.max(1); @@ -224,13 +202,11 @@ impl PointerGrab for ResizeSurfaceGrab { match &self.toplevel { SurfaceKind::Xdg(xdg) => { - if xdg - .with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }) - .is_ok() - { + let ret = xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + if ret.is_ok() { xdg.send_configure(); } } @@ -264,43 +240,41 @@ impl PointerGrab for ResizeSurfaceGrab { } if let SurfaceKind::Xdg(xdg) = &self.toplevel { - if xdg - .with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }) - .is_ok() - { + let ret = xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + if ret.is_ok() { xdg.send_configure(); } - self.ctoken - .with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { - let mut data = attrs - .user_data - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::Resizing(resize_data) = data.resize_state { - data.resize_state = ResizeState::WaitingForFinalAck(resize_data, serial); - } else { - panic!("invalid resize state: {:?}", data.resize_state); - } - }); + with_states(self.toplevel.get_surface().unwrap(), |states| { + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + if let ResizeState::Resizing(resize_data) = data.resize_state { + data.resize_state = ResizeState::WaitingForFinalAck(resize_data, serial); + } else { + panic!("invalid resize state: {:?}", data.resize_state); + } + }) + .unwrap(); } else { - self.ctoken - .with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { - let mut data = attrs - .user_data - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::Resizing(resize_data) = data.resize_state { - data.resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - panic!("invalid resize state: {:?}", data.resize_state); - } - }); + with_states(self.toplevel.get_surface().unwrap(), |states| { + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + if let ResizeState::Resizing(resize_data) = data.resize_state { + data.resize_state = ResizeState::WaitingForCommit(resize_data); + } else { + panic!("invalid resize state: {:?}", data.resize_state); + } + }) + .unwrap(); } } } @@ -316,34 +290,30 @@ impl PointerGrab for ResizeSurfaceGrab { #[derive(Clone)] pub struct ShellHandles { - pub token: CompositorToken, - pub xdg_state: Arc>>, - pub wl_state: Arc>>, - pub window_map: Rc>, + pub xdg_state: Arc>, + pub wl_state: Arc>, + pub window_map: Rc>, } pub fn init_shell(display: &mut Display, log: ::slog::Logger) -> ShellHandles { // Create the compositor - let (compositor_token, _, _) = compositor_init( + compositor_init( display, - move |request, surface, ctoken, mut ddata| match request { - SurfaceEvent::Commit => { - let anvil_state = ddata.get::>().unwrap(); - let window_map = anvil_state.window_map.as_ref(); - surface_commit(&surface, ctoken, &*window_map) - } + move |surface, mut ddata| { + let anvil_state = ddata.get::>().unwrap(); + let window_map = anvil_state.window_map.as_ref(); + surface_commit(&surface, &*window_map) }, log.clone(), ); // Init a window map, to track the location of our windows - let window_map = Rc::new(RefCell::new(WindowMap::new(compositor_token))); + let window_map = Rc::new(RefCell::new(WindowMap::new())); // init the xdg_shell let xdg_window_map = window_map.clone(); let (xdg_shell_state, _, _) = xdg_shell_init( display, - compositor_token, move |shell_event| match shell_event { XdgRequest::NewToplevel { surface } => { // place the window at a random location in the [0;800]x[0;800] square @@ -441,9 +411,9 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg let geometry = xdg_window_map.borrow().geometry(&toplevel).unwrap(); let initial_window_size = (geometry.width, geometry.height); - compositor_token.with_surface_data(surface.get_surface().unwrap(), move |attrs| { - attrs - .user_data + with_states(surface.get_surface().unwrap(), move |states| { + states + .data_map .get::>() .unwrap() .borrow_mut() @@ -452,11 +422,11 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg initial_window_location, initial_window_size, }); - }); + }) + .unwrap(); let grab = ResizeSurfaceGrab { start_data, - ctoken: compositor_token, toplevel, edges: edges.into(), initial_window_size, @@ -466,93 +436,86 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg pointer.set_grab(grab, serial); } XdgRequest::AckConfigure { - surface, configure, .. + surface, + configure: Configure::Toplevel(configure), + .. } => { - if let Configure::Toplevel(configure) = configure { - let waiting_for_serial = compositor_token.with_surface_data(&surface, |attrs| { - if let Some(data) = attrs.user_data.get::>() { - if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state { - return Some(serial); + let waiting_for_serial = with_states(&surface, |states| { + if let Some(data) = states.data_map.get::>() { + if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state { + return Some(serial); + } + } + + None + }) + .unwrap(); + + if let Some(serial) = waiting_for_serial { + if configure.serial > serial { + // TODO: huh, we have missed the serial somehow. + // this should not happen, but it may be better to handle + // this case anyway + } + + if serial == configure.serial + && configure.state.states.contains(xdg_toplevel::State::Resizing) + { + with_states(&surface, |states| { + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state { + data.resize_state = ResizeState::WaitingForCommit(resize_data); + } else { + unreachable!() } - } - - None - }); - - if let Some(serial) = waiting_for_serial { - if configure.serial > serial { - // TODO: huh, we have missed the serial somehow. - // this should not happen, but it may be better to handle - // this case anyway - } - - if serial == configure.serial { - if configure.state.states.contains(xdg_toplevel::State::Resizing) { - compositor_token.with_surface_data(&surface, |attrs| { - let mut data = attrs - .user_data - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state - { - data.resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - unreachable!() - } - }) - } - } + }) + .unwrap(); } } } XdgRequest::Fullscreen { surface, output, .. } => { - if surface - .with_pending_state(|state| { - // TODO: Use size of current output the window is on and set position to (0,0) - state.states.set(xdg_toplevel::State::Fullscreen); - state.size = Some((800, 600)); - // TODO: If the provided output is None, use the output where - // the toplevel is currently shown - state.fullscreen_output = output; - }) - .is_ok() - { + let ret = surface.with_pending_state(|state| { + // TODO: Use size of current output the window is on and set position to (0,0) + state.states.set(xdg_toplevel::State::Fullscreen); + state.size = Some((800, 600)); + // TODO: If the provided output is None, use the output where + // the toplevel is currently shown + state.fullscreen_output = output; + }); + if ret.is_ok() { surface.send_configure(); } } XdgRequest::UnFullscreen { surface } => { - if surface - .with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Fullscreen); - state.size = None; - state.fullscreen_output = None; - }) - .is_ok() - { + let ret = surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Fullscreen); + state.size = None; + state.fullscreen_output = None; + }); + if ret.is_ok() { surface.send_configure(); } } XdgRequest::Maximize { surface } => { - if surface - .with_pending_state(|state| { - // TODO: Use size of current output the window is on and set position to (0,0) - state.states.set(xdg_toplevel::State::Maximized); - state.size = Some((800, 600)); - }) - .is_ok() - { + let ret = surface.with_pending_state(|state| { + // TODO: Use size of current output the window is on and set position to (0,0) + state.states.set(xdg_toplevel::State::Maximized); + state.size = Some((800, 600)); + }); + if ret.is_ok() { surface.send_configure(); } } XdgRequest::UnMaximize { surface } => { - if surface - .with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.size = None; - }) - .is_ok() - { + let ret = surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.size = None; + }); + if ret.is_ok() { surface.send_configure(); } } @@ -565,8 +528,7 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg let shell_window_map = window_map.clone(); let (wl_shell_state, _) = wl_shell_init( display, - compositor_token, - move |req: ShellRequest<_>| { + move |req: ShellRequest| { match req { ShellRequest::SetKind { surface, @@ -658,9 +620,9 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg let geometry = shell_window_map.borrow().geometry(&toplevel).unwrap(); let initial_window_size = (geometry.width, geometry.height); - compositor_token.with_surface_data(surface.get_surface().unwrap(), move |attrs| { - attrs - .user_data + with_states(surface.get_surface().unwrap(), move |states| { + states + .data_map .get::>() .unwrap() .borrow_mut() @@ -669,11 +631,11 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg initial_window_location, initial_window_size, }); - }); + }) + .unwrap(); let grab = ResizeSurfaceGrab { start_data, - ctoken: compositor_token, toplevel, edges: edges.into(), initial_window_size, @@ -689,7 +651,6 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg ); ShellHandles { - token: compositor_token, xdg_state: xdg_shell_state, wl_state: wl_shell_state, window_map, @@ -726,82 +687,43 @@ impl Default for ResizeState { } } -#[derive(Default, Clone)] -pub struct CommitedState { - pub buffer: Option, - pub input_region: Option, - pub dimensions: Option<(i32, i32)>, - pub frame_callbacks: Vec, - pub sub_location: (i32, i32), -} - #[derive(Default)] pub struct SurfaceData { + pub buffer: Option, pub texture: Option>, pub geometry: Option, pub resize_state: ResizeState, - /// Minimum width and height, as requested by the surface. - /// - /// `0` means unlimited. - pub min_size: (i32, i32), - /// Maximum width and height, as requested by the surface. - /// - /// `0` means unlimited. - pub max_size: (i32, i32), - pub current_state: CommitedState, - pub cached_state: Option, + pub dimensions: Option<(i32, i32)>, } impl SurfaceData { - /// Apply a next state into the surface current state - pub fn apply_state(&mut self, next_state: CommitedState) { - if Self::merge_state(&mut self.current_state, next_state) { - let _ = self.texture.take(); - } - } - - /// Apply a next state into the cached state - pub fn apply_cache(&mut self, next_state: CommitedState) { - match self.cached_state { - Some(ref mut cached) => { - Self::merge_state(cached, next_state); + pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) { + match attrs.buffer.take() { + Some(BufferAssignment::NewBuffer { buffer, .. }) => { + // new contents + self.dimensions = buffer_dimensions(&buffer); + if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { + old_buffer.release(); + } + self.texture = None; } - None => self.cached_state = Some(next_state), - } - } - - /// Apply the current cached state if any - pub fn apply_from_cache(&mut self) { - if let Some(cached) = self.cached_state.take() { - self.apply_state(cached); - } - } - - // merge the "next" state into the "into" state - // - // returns true if the texture cache should be invalidated - fn merge_state(into: &mut CommitedState, next: CommitedState) -> bool { - // release the previous buffer if relevant - let new_buffer = into.buffer != next.buffer; - if new_buffer { - if let Some(buffer) = into.buffer.take() { - buffer.release(); + Some(BufferAssignment::Removed) => { + // remove the contents + self.buffer = None; + self.dimensions = None; + self.texture = None; } + None => {} } - - *into = next; - new_buffer } -} -impl SurfaceData { /// Returns the size of the surface. pub fn size(&self) -> Option<(i32, i32)> { - self.current_state.dimensions + self.dimensions } /// Checks if the surface's input region contains the point. - pub fn contains_point(&self, point: (f64, f64)) -> bool { + pub fn contains_point(&self, attrs: &SurfaceAttributes, point: (f64, f64)) -> bool { let (w, h) = match self.size() { None => return false, // If the surface has no size, it can't have an input region. Some(wh) => wh, @@ -823,170 +745,73 @@ impl SurfaceData { } // If there's no input region, we're done. - if self.current_state.input_region.is_none() { + if attrs.input_region.is_none() { return true; } - self.current_state.input_region.as_ref().unwrap().contains(point) + attrs.input_region.as_ref().unwrap().contains(point) } /// Send the frame callback if it had been requested - pub fn send_frame(&mut self, time: u32) { - for callback in self.current_state.frame_callbacks.drain(..) { + pub fn send_frame(attrs: &mut SurfaceAttributes, time: u32) { + for callback in attrs.frame_callbacks.drain(..) { callback.done(time); } } } -fn surface_commit( - surface: &wl_surface::WlSurface, - token: CompositorToken, - window_map: &RefCell, -) { +fn surface_commit(surface: &wl_surface::WlSurface, window_map: &RefCell) { #[cfg(feature = "xwayland")] super::xwayland::commit_hook(surface); - let mut geometry = None; - let mut min_size = (0, 0); - let mut max_size = (0, 0); + let mut window_map = window_map.borrow_mut(); - if token.has_role::(surface) { - if let Some(SurfaceKind::Xdg(xdg)) = window_map.borrow().find(surface) { - xdg.commit(); - } - - token - .with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| { - if let XdgToplevelSurfaceRole::Some(attributes) = role { - geometry = attributes.window_geometry; - min_size = attributes.min_size; - max_size = attributes.max_size; - } - }) - .unwrap(); - } - - if token.has_role::(surface) { - if let Some(PopupKind::Xdg(xdg)) = window_map.borrow().find_popup(&surface) { - xdg.commit(); - } - - token - .with_role_data(surface, |role: &mut XdgPopupSurfaceRole| { - if let XdgPopupSurfaceRole::Some(attributes) = role { - geometry = attributes.window_geometry; - } - }) - .unwrap(); - } - - let sub_data = token - .with_role_data(surface, |&mut role: &mut SubsurfaceRole| role) - .ok(); - - let (refresh, apply_children) = token.with_surface_data(surface, |attributes| { - attributes - .user_data - .insert_if_missing(|| RefCell::new(SurfaceData::default())); - let mut data = attributes - .user_data - .get::>() - .unwrap() - .borrow_mut(); - - let mut next_state = match data.cached_state { - // There is a pending state, accumulate into it - Some(ref cached_state) => cached_state.clone(), - // Start from the current state - None => data.current_state.clone(), - }; - - if let Some(ref data) = sub_data { - next_state.sub_location = data.location; - } - - data.geometry = geometry; - next_state.input_region = attributes.input_region.clone(); - data.min_size = min_size; - data.max_size = max_size; - - // we retrieve the contents of the associated buffer and copy it - match attributes.buffer.take() { - Some(BufferAssignment::NewBuffer { buffer, .. }) => { - // new contents - next_state.dimensions = buffer_dimensions(&buffer); - next_state.buffer = Some(buffer); - } - Some(BufferAssignment::Removed) => { - // remove the contents - next_state.buffer = None; - next_state.dimensions = None; - } - None => {} - } - - // Append the current frame callbacks to the next state - next_state - .frame_callbacks - .extend(attributes.frame_callbacks.drain(..)); - - data.apply_cache(next_state); - - let apply_children = if let Some(SubsurfaceRole { sync: true, .. }) = sub_data { - false - } else { - data.apply_from_cache(); - true - }; - - (window_map.borrow().find(surface), apply_children) - }); - - // Apply the cached state of all sync children - if apply_children { - token.with_surface_tree_upward( + if !is_sync_subsurface(surface) { + // Update the buffer of all child surfaces + with_surface_tree_upward( surface, - true, - |_, _, role, &is_root| { - // only process children if the surface is sync or we are the root - if is_root { - // we are the root - TraversalAction::DoChildren(false) - } else if let Ok(sub_data) = Role::::data(role) { - if sub_data.sync || is_root { - TraversalAction::DoChildren(false) - } else { - // if we are not sync, we won't apply from cache and don't process - // the children - TraversalAction::SkipChildren - } - } else { - unreachable!(); - } + (), + |_, _, _| TraversalAction::DoChildren(()), + |_, states, _| { + states + .data_map + .insert_if_missing(|| RefCell::new(SurfaceData::default())); + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + data.update_buffer(&mut *states.cached_state.current::()); }, - |_, attributes, role, _| { - // only apply from cache if we are a sync subsurface - if let Ok(sub_data) = Role::::data(role) { - if sub_data.sync { - if let Some(data) = attributes.user_data.get::>() { - data.borrow_mut().apply_from_cache(); - } - } - } - }, - |_, _, _, _| true, - ) + |_, _, _| true, + ); } - if let Some(toplevel) = refresh { - let mut window_map = window_map.borrow_mut(); + if let Some(toplevel) = window_map.find(surface) { + // send the initial configure if relevant + if let SurfaceKind::Xdg(ref toplevel) = toplevel { + let initial_configure_sent = with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }) + .unwrap(); + if !initial_configure_sent { + toplevel.send_configure(); + } + } + window_map.refresh_toplevel(&toplevel); // Get the geometry outside since it uses the token, and so would block inside. let Rectangle { width, height, .. } = window_map.geometry(&toplevel).unwrap(); - let new_location = token.with_surface_data(surface, |attributes| { - let mut data = attributes - .user_data + let new_location = with_states(surface, |states| { + let mut data = states + .data_map .get::>() .unwrap() .borrow_mut(); @@ -1027,7 +852,8 @@ fn surface_commit( } new_location - }); + }) + .unwrap(); if let Some(location) = new_location { window_map.set_location(&toplevel, location); diff --git a/anvil/src/state.rs b/anvil/src/state.rs index 031efa5..cd335d0 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -13,7 +13,6 @@ use smithay::{ wayland_server::{protocol::wl_surface::WlSurface, Display}, }, wayland::{ - compositor::CompositorToken, data_device::{default_action_chooser, init_data_device, set_data_device_focus, DataDeviceEvent}, seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig}, shm::init_shm_global, @@ -31,8 +30,7 @@ pub struct AnvilState { pub running: Arc, pub display: Rc>, pub handle: LoopHandle<'static, AnvilState>, - pub ctoken: CompositorToken, - pub window_map: Rc>>, + pub window_map: Rc>, pub dnd_icon: Arc>>, pub log: slog::Logger, // input-related fields @@ -106,24 +104,18 @@ impl AnvilState { _ => {} }, default_action_chooser, - shell_handles.token, log.clone(), ); // init input let seat_name = backend_data.seat_name(); - let (mut seat, _) = Seat::new( - &mut display.borrow_mut(), - seat_name.clone(), - shell_handles.token, - log.clone(), - ); + let (mut seat, _) = Seat::new(&mut display.borrow_mut(), seat_name.clone(), log.clone()); let cursor_status = Arc::new(Mutex::new(CursorImageStatus::Default)); let cursor_status2 = cursor_status.clone(); - let pointer = seat.add_pointer(shell_handles.token, move |new_status| { + let pointer = seat.add_pointer(move |new_status| { // TODO: hide winit system cursor when relevant *cursor_status2.lock().unwrap() = new_status }); @@ -155,7 +147,6 @@ impl AnvilState { running: Arc::new(AtomicBool::new(true)), display, handle, - ctoken: shell_handles.token, window_map: shell_handles.window_map, dnd_icon, log, diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 1af0f0d..6a85a05 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -51,7 +51,6 @@ use smithay::{ signaling::{Linkable, SignalToken, Signaler}, utils::Rectangle, wayland::{ - compositor::CompositorToken, output::{Mode, Output, PhysicalProperties}, seat::CursorImageStatus, }, @@ -66,9 +65,8 @@ use smithay::{ wayland::dmabuf::init_dmabuf_global, }; -use crate::drawing::*; -use crate::shell::{MyWindowMap, Roles}; use crate::state::{AnvilState, Backend}; +use crate::{drawing::*, window_map::WindowMap}; #[derive(Clone)] pub struct SessionFd(RawFd); @@ -632,7 +630,6 @@ impl AnvilState { crtc, &mut *self.window_map.borrow_mut(), &mut self.backend_data.output_map, - &self.ctoken, &self.pointer_location, &device_backend.pointer_image, &*self.dnd_icon.lock().unwrap(), @@ -678,9 +675,8 @@ fn render_surface( renderer: &mut Gles2Renderer, device_id: dev_t, crtc: crtc::Handle, - window_map: &mut MyWindowMap, + window_map: &mut WindowMap, output_map: &mut Vec, - compositor_token: &CompositorToken, pointer_location: &(f64, f64), pointer_image: &Gles2Texture, dnd_icon: &Option, @@ -721,7 +717,6 @@ fn render_surface( width: width as i32, height: height as i32, }), - *compositor_token, logger, )?; @@ -736,14 +731,7 @@ fn render_surface( { if let Some(ref wl_surface) = dnd_icon.as_ref() { if wl_surface.as_ref().is_alive() { - draw_dnd_icon( - renderer, - frame, - wl_surface, - (ptr_x, ptr_y), - *compositor_token, - logger, - )?; + draw_dnd_icon(renderer, frame, wl_surface, (ptr_x, ptr_y), logger)?; } } } @@ -759,20 +747,12 @@ fn render_surface( } if let CursorImageStatus::Image(ref wl_surface) = *cursor_status { - draw_cursor( - renderer, - frame, - wl_surface, - (ptr_x, ptr_y), - *compositor_token, - logger, - )?; + draw_cursor(renderer, frame, wl_surface, (ptr_x, ptr_y), logger)?; } else { frame.render_texture_at(pointer_image, (ptr_x, ptr_y), Transform::Normal, 1.0)?; } } } - Ok(()) }, ) diff --git a/anvil/src/window_map.rs b/anvil/src/window_map.rs index 1874ba4..c4a72c5 100644 --- a/anvil/src/window_map.rs +++ b/anvil/src/window_map.rs @@ -4,10 +4,10 @@ use smithay::{ reexports::wayland_server::protocol::wl_surface, utils::Rectangle, wayland::{ - compositor::{roles::Role, CompositorToken, SubsurfaceRole, TraversalAction}, + compositor::{with_states, with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, shell::{ - legacy::{ShellSurface, ShellSurfaceRole}, - xdg::{PopupSurface, ToplevelSurface, XdgPopupSurfaceRole, XdgToplevelSurfaceRole}, + legacy::ShellSurface, + xdg::{PopupSurface, SurfaceCachedState, ToplevelSurface}, }, }, }; @@ -16,29 +16,15 @@ use crate::shell::SurfaceData; #[cfg(feature = "xwayland")] use crate::xwayland::X11Surface; -pub enum Kind { - Xdg(ToplevelSurface), - Wl(ShellSurface), +#[derive(Clone)] +pub enum Kind { + Xdg(ToplevelSurface), + Wl(ShellSurface), #[cfg(feature = "xwayland")] X11(X11Surface), } -// We implement Clone manually because #[derive(..)] would require R: Clone. -impl Clone for Kind { - fn clone(&self) -> Self { - match self { - Kind::Xdg(xdg) => Kind::Xdg(xdg.clone()), - Kind::Wl(wl) => Kind::Wl(wl.clone()), - #[cfg(feature = "xwayland")] - Kind::X11(x11) => Kind::X11(x11.clone()), - } - } -} - -impl Kind -where - R: Role + Role + Role + 'static, -{ +impl Kind { pub fn alive(&self) -> bool { match *self { Kind::Xdg(ref t) => t.alive(), @@ -68,23 +54,12 @@ where } } -pub enum PopupKind { - Xdg(PopupSurface), +#[derive(Clone)] +pub enum PopupKind { + Xdg(PopupSurface), } -// We implement Clone manually because #[derive(..)] would require R: Clone. -impl Clone for PopupKind { - fn clone(&self) -> Self { - match self { - PopupKind::Xdg(xdg) => PopupKind::Xdg(xdg.clone()), - } - } -} - -impl PopupKind -where - R: Role + 'static, -{ +impl PopupKind { pub fn alive(&self) -> bool { match *self { PopupKind::Xdg(ref t) => t.alive(), @@ -97,56 +72,53 @@ where } } -struct Window { +struct Window { location: (i32, i32), /// A bounding box over this window and its children. /// /// Used for the fast path of the check in `matching`, and as the fall-back for the window /// geometry if that's not set explicitly. bbox: Rectangle, - toplevel: Kind, + toplevel: Kind, } -impl Window -where - R: Role + Role + Role + 'static, -{ +impl Window { /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. - fn matching( - &self, - point: (f64, f64), - ctoken: CompositorToken, - ) -> Option<(wl_surface::WlSurface, (f64, f64))> { + fn matching(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> { if !self.bbox.contains((point.0 as i32, point.1 as i32)) { return None; } // need to check more carefully let found = RefCell::new(None); if let Some(wl_surface) = self.toplevel.get_surface() { - ctoken.with_surface_tree_downward( + with_surface_tree_downward( wl_surface, self.location, - |wl_surface, attributes, role, &(mut x, mut y)| { - let data = attributes.user_data.get::>(); + |wl_surface, states, &(mut x, mut y)| { + let data = states.data_map.get::>(); - if let Ok(subdata) = Role::::data(role) { - x += subdata.location.0; - y += subdata.location.1; + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + x += current.location.0; + y += current.location.1; } let surface_local_point = (point.0 - x as f64, point.1 - y as f64); - if data - .map(|data| data.borrow().contains_point(surface_local_point)) - .unwrap_or(false) - { + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), surface_local_point) + }) + .unwrap_or(false); + if contains_the_point { *found.borrow_mut() = Some((wl_surface.clone(), (x as f64, y as f64))); } TraversalAction::DoChildren((x, y)) }, - |_, _, _, _| {}, - |_, _, _, _| { + |_, _, _| {}, + |_, _, _| { // only continue if the point is not found found.borrow().is_none() }, @@ -155,20 +127,21 @@ where found.into_inner() } - fn self_update(&mut self, ctoken: CompositorToken) { + fn self_update(&mut self) { let (base_x, base_y) = self.location; let (mut min_x, mut min_y, mut max_x, mut max_y) = (base_x, base_y, base_x, base_y); if let Some(wl_surface) = self.toplevel.get_surface() { - ctoken.with_surface_tree_downward( + with_surface_tree_downward( wl_surface, (base_x, base_y), - |_, attributes, role, &(mut x, mut y)| { - let data = attributes.user_data.get::>(); + |_, states, &(mut x, mut y)| { + let data = states.data_map.get::>(); if let Some((w, h)) = data.and_then(|d| d.borrow().size()) { - if let Ok(subdata) = Role::::data(role) { - x += subdata.location.0; - y += subdata.location.1; + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + x += current.location.0; + y += current.location.1; } // Update the bounding box. @@ -184,8 +157,8 @@ where TraversalAction::SkipChildren } }, - |_, _, _, _| {}, - |_, _, _, _| true, + |_, _, _| {}, + |_, _, _| true, ); } self.bbox = Rectangle { @@ -197,85 +170,69 @@ where } /// Returns the geometry of this window. - pub fn geometry(&self, ctoken: CompositorToken) -> Rectangle { + pub fn geometry(&self) -> Rectangle { // It's the set geometry with the full bounding box as the fallback. - ctoken - .with_surface_data(self.toplevel.get_surface().unwrap(), |attributes| { - attributes - .user_data - .get::>() - .unwrap() - .borrow() - .geometry - }) - .unwrap_or(self.bbox) + with_states(self.toplevel.get_surface().unwrap(), |states| { + states.cached_state.current::().geometry + }) + .unwrap() + .unwrap_or(self.bbox) } /// Sends the frame callback to all the subsurfaces in this /// window that requested it - pub fn send_frame(&self, time: u32, ctoken: CompositorToken) { + pub fn send_frame(&self, time: u32) { if let Some(wl_surface) = self.toplevel.get_surface() { - ctoken.with_surface_tree_downward( + with_surface_tree_downward( wl_surface, (), - |_, _, _, &()| TraversalAction::DoChildren(()), - |_, attributes, _, &()| { + |_, _, &()| TraversalAction::DoChildren(()), + |_, states, &()| { // the surface may not have any user_data if it is a subsurface and has not // yet been commited - if let Some(data) = attributes.user_data.get::>() { - data.borrow_mut().send_frame(time) - } + SurfaceData::send_frame(&mut *states.cached_state.current(), time) }, - |_, _, _, &()| true, + |_, _, &()| true, ); } } } -pub struct Popup { - popup: PopupKind, +pub struct Popup { + popup: PopupKind, } -pub struct WindowMap { - ctoken: CompositorToken, - windows: Vec>, - popups: Vec>, +pub struct WindowMap { + windows: Vec, + popups: Vec, } -impl WindowMap -where - R: Role - + Role - + Role - + Role - + 'static, -{ - pub fn new(ctoken: CompositorToken) -> Self { +impl WindowMap { + pub fn new() -> Self { WindowMap { - ctoken, windows: Vec::new(), popups: Vec::new(), } } - pub fn insert(&mut self, toplevel: Kind, location: (i32, i32)) { + pub fn insert(&mut self, toplevel: Kind, location: (i32, i32)) { let mut window = Window { location, bbox: Rectangle::default(), toplevel, }; - window.self_update(self.ctoken); + window.self_update(); self.windows.insert(0, window); } - pub fn insert_popup(&mut self, popup: PopupKind) { + pub fn insert_popup(&mut self, popup: PopupKind) { let popup = Popup { popup }; self.popups.push(popup); } pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> { for w in &self.windows { - if let Some(surface) = w.matching(point, self.ctoken) { + if let Some(surface) = w.matching(point) { return Some(surface); } } @@ -288,7 +245,7 @@ where ) -> Option<(wl_surface::WlSurface, (f64, f64))> { let mut found = None; for (i, w) in self.windows.iter().enumerate() { - if let Some(surface) = w.matching(point, self.ctoken) { + if let Some(surface) = w.matching(point) { found = Some((i, surface)); break; } @@ -304,7 +261,7 @@ where pub fn with_windows_from_bottom_to_top(&self, mut f: Func) where - Func: FnMut(&Kind, (i32, i32), &Rectangle), + Func: FnMut(&Kind, (i32, i32), &Rectangle), { for w in self.windows.iter().rev() { f(&w.toplevel, w.location, &w.bbox) @@ -315,14 +272,14 @@ where self.windows.retain(|w| w.toplevel.alive()); self.popups.retain(|p| p.popup.alive()); for w in &mut self.windows { - w.self_update(self.ctoken); + w.self_update(); } } /// Refreshes the state of the toplevel, if it exists. - pub fn refresh_toplevel(&mut self, toplevel: &Kind) { + pub fn refresh_toplevel(&mut self, toplevel: &Kind) { if let Some(w) = self.windows.iter_mut().find(|w| w.toplevel.equals(toplevel)) { - w.self_update(self.ctoken); + w.self_update(); } } @@ -331,7 +288,7 @@ where } /// Finds the toplevel corresponding to the given `WlSurface`. - pub fn find(&self, surface: &wl_surface::WlSurface) -> Option> { + pub fn find(&self, surface: &wl_surface::WlSurface) -> Option { self.windows.iter().find_map(|w| { if w.toplevel .get_surface() @@ -345,7 +302,7 @@ where }) } - pub fn find_popup(&self, surface: &wl_surface::WlSurface) -> Option> { + pub fn find_popup(&self, surface: &wl_surface::WlSurface) -> Option { self.popups.iter().find_map(|p| { if p.popup .get_surface() @@ -360,7 +317,7 @@ where } /// Returns the location of the toplevel, if it exists. - pub fn location(&self, toplevel: &Kind) -> Option<(i32, i32)> { + pub fn location(&self, toplevel: &Kind) -> Option<(i32, i32)> { self.windows .iter() .find(|w| w.toplevel.equals(toplevel)) @@ -368,24 +325,24 @@ where } /// Sets the location of the toplevel, if it exists. - pub fn set_location(&mut self, toplevel: &Kind, location: (i32, i32)) { + pub fn set_location(&mut self, toplevel: &Kind, location: (i32, i32)) { if let Some(w) = self.windows.iter_mut().find(|w| w.toplevel.equals(toplevel)) { w.location = location; - w.self_update(self.ctoken); + w.self_update(); } } /// Returns the geometry of the toplevel, if it exists. - pub fn geometry(&self, toplevel: &Kind) -> Option { + pub fn geometry(&self, toplevel: &Kind) -> Option { self.windows .iter() .find(|w| w.toplevel.equals(toplevel)) - .map(|w| w.geometry(self.ctoken)) + .map(|w| w.geometry()) } pub fn send_frames(&self, time: u32) { for window in &self.windows { - window.send_frame(time, self.ctoken); + window.send_frame(time); } } } diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 7a9ab18..53c7c97 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -61,12 +61,7 @@ pub fn run_winit( * Initialize the globals */ - let mut state = AnvilState::init( - display.clone(), - event_loop.handle(), - WinitData, - log.clone(), - ); + let mut state = AnvilState::init(display.clone(), event_loop.handle(), WinitData, log.clone()); let (output, _) = Output::new( &mut display.borrow_mut(), @@ -122,14 +117,7 @@ pub fn run_winit( frame.clear([0.8, 0.8, 0.9, 1.0])?; // draw the windows - draw_windows( - renderer, - frame, - &*state.window_map.borrow(), - None, - state.ctoken, - &log, - )?; + draw_windows(renderer, frame, &*state.window_map.borrow(), None, &log)?; let (x, y) = state.pointer_location; // draw the dnd icon if any @@ -137,14 +125,7 @@ pub fn run_winit( let guard = state.dnd_icon.lock().unwrap(); if let Some(ref surface) = *guard { if surface.as_ref().is_alive() { - draw_dnd_icon( - renderer, - frame, - surface, - (x as i32, y as i32), - state.ctoken, - &log, - )?; + draw_dnd_icon(renderer, frame, surface, (x as i32, y as i32), &log)?; } } } @@ -163,7 +144,7 @@ pub fn run_winit( // draw as relevant if let CursorImageStatus::Image(ref surface) = *guard { cursor_visible = false; - draw_cursor(renderer, frame, surface, (x as i32, y as i32), state.ctoken, &log)?; + draw_cursor(renderer, frame, surface, (x as i32, y as i32), &log)?; } else { cursor_visible = true; } diff --git a/anvil/src/xwayland/mod.rs b/anvil/src/xwayland/mod.rs index 5b8d55f..59c4346 100644 --- a/anvil/src/xwayland/mod.rs +++ b/anvil/src/xwayland/mod.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap, convert::TryFrom, os::unix::net:: use smithay::{ reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, - wayland::compositor::CompositorToken, + wayland::compositor::give_role, }; use x11rb::{ @@ -20,8 +20,7 @@ use x11rb::{ }; use crate::{ - shell::{MyWindowMap, Roles}, - window_map::Kind, + window_map::{Kind, WindowMap}, AnvilState, }; @@ -37,8 +36,7 @@ impl AnvilState { } pub fn xwayland_ready(&mut self, connection: UnixStream, client: Client) { - let (wm, source) = - X11State::start_wm(connection, self.ctoken, self.window_map.clone(), self.log.clone()).unwrap(); + let (wm, source) = X11State::start_wm(connection, self.window_map.clone(), self.log.clone()).unwrap(); let wm = Rc::new(RefCell::new(wm)); client.data_map().insert_if_missing(|| Rc::clone(&wm)); self.handle @@ -70,15 +68,13 @@ struct X11State { atoms: Atoms, log: slog::Logger, unpaired_surfaces: HashMap, - token: CompositorToken, - window_map: Rc>, + window_map: Rc>, } impl X11State { fn start_wm( connection: UnixStream, - token: CompositorToken, - window_map: Rc>, + window_map: Rc>, log: slog::Logger, ) -> Result<(Self, X11Source), Box> { // Create an X11 connection. XWayland only uses screen 0. @@ -123,7 +119,6 @@ impl X11State { conn: Rc::clone(&conn), atoms, unpaired_surfaces: Default::default(), - token, window_map, log, }; @@ -209,7 +204,7 @@ impl X11State { fn new_window(&mut self, window: Window, surface: WlSurface, location: (i32, i32)) { debug!(self.log, "Matched X11 surface {:x?} to {:x?}", window, surface); - if self.token.give_role_with(&surface, X11SurfaceRole).is_err() { + if give_role(&surface, "x11_surface").is_err() { // It makes no sense to post a protocol error here since that would only kill Xwayland error!(self.log, "Surface {:x?} already has a role?!", surface); return; @@ -237,8 +232,6 @@ pub fn commit_hook(surface: &WlSurface) { } } -pub struct X11SurfaceRole; - #[derive(Clone)] pub struct X11Surface { surface: WlSurface, diff --git a/src/backend/allocator/dumb.rs b/src/backend/allocator/dumb.rs index 77b52f8..0ee8632 100644 --- a/src/backend/allocator/dumb.rs +++ b/src/backend/allocator/dumb.rs @@ -39,8 +39,7 @@ impl Allocator> for DrmDevice { // dumb buffers are always linear if modifiers .iter() - .find(|x| **x == Modifier::Invalid || **x == Modifier::Linear) - .is_none() + .all(|&x| x != Modifier::Invalid && x != Modifier::Linear) { return Err(drm::SystemError::InvalidArgument); } diff --git a/src/backend/drm/surface/legacy.rs b/src/backend/drm/surface/legacy.rs index 8531546..3f1c235 100644 --- a/src/backend/drm/surface/legacy.rs +++ b/src/backend/drm/surface/legacy.rs @@ -372,10 +372,9 @@ impl LegacyDrmSurface { let encoders = info .encoders() .iter() - .filter(|enc| enc.is_some()) - .map(|enc| enc.unwrap()) + .flatten() .map(|encoder| { - self.fd.get_encoder(encoder).map_err(|source| Error::Access { + self.fd.get_encoder(*encoder).map_err(|source| Error::Access { errmsg: "Error loading encoder info", dev: self.fd.dev_path(), source, diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 00cb950..67cf725 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -117,9 +117,11 @@ impl<'a> Debug for EGLPlatform<'a> { } } -/// Trait describing platform specific functionality to create a valid `EGLDisplay` using the `EGL_EXT_platform_base`(https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) extension. +/// Trait describing platform specific functionality to create a valid `EGLDisplay` using the +/// [`EGL_EXT_platform_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) extension. pub trait EGLNativeDisplay: Send { - /// List of supported platforms that can be used to create a display using [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) + /// List of supported platforms that can be used to create a display using + /// [`eglGetPlatformDisplayEXT`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_platform_base.txt) fn supported_platforms(&self) -> Vec>; /// Type of surfaces created diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index b82a55f..fe05447 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -35,7 +35,7 @@ use super::{ImportDma, ImportShm}; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat}; #[cfg(feature = "wayland_frontend")] -use crate::{utils::Rectangle, wayland::compositor::SurfaceAttributes}; +use crate::utils::Rectangle; #[cfg(feature = "wayland_frontend")] use wayland_server::protocol::{wl_buffer, wl_shm}; @@ -501,7 +501,7 @@ impl ImportShm for Gles2Renderer { fn import_shm_buffer( &mut self, buffer: &wl_buffer::WlBuffer, - surface: Option<&SurfaceAttributes>, + surface: Option<&crate::wayland::compositor::SurfaceData>, damage: &[Rectangle], ) -> Result { use crate::wayland::shm::with_buffer_contents; @@ -535,7 +535,7 @@ impl ImportShm for Gles2Renderer { // why not store a `Gles2Texture`? because the user might do so. // this is guaranteed a non-public internal type, so we are good. surface - .and_then(|surface| surface.user_data.get::>().cloned()) + .and_then(|surface| surface.data_map.get::>().cloned()) .unwrap_or_else(|| { let mut tex = 0; unsafe { self.gl.GenTextures(1, &mut tex) }; @@ -742,9 +742,7 @@ impl Gles2Renderer { texture.0.texture, buffer ); - if texture.0.is_external { - Ok(Some(texture)) - } else { + if !texture.0.is_external { if let Some(egl_images) = texture.0.egl_images.as_ref() { if egl_images[0] == ffi_egl::NO_IMAGE_KHR { return Ok(None); @@ -753,8 +751,8 @@ impl Gles2Renderer { let tex = Some(texture.0.texture); self.import_egl_image(egl_images[0], false, tex)?; } - Ok(Some(texture)) } + Ok(Some(texture)) } else { Ok(None) } diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index a90a609..7c58a91 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -11,7 +11,7 @@ use std::collections::HashSet; use std::error::Error; #[cfg(feature = "wayland_frontend")] -use crate::{utils::Rectangle, wayland::compositor::SurfaceAttributes}; +use crate::{utils::Rectangle, wayland::compositor::SurfaceData}; use cgmath::{prelude::*, Matrix3, Vector2}; #[cfg(feature = "wayland_frontend")] use wayland_server::protocol::{wl_buffer, wl_shm}; @@ -264,7 +264,7 @@ pub trait ImportShm: Renderer { fn import_shm_buffer( &mut self, buffer: &wl_buffer::WlBuffer, - surface: Option<&SurfaceAttributes>, + surface: Option<&crate::wayland::compositor::SurfaceData>, damage: &[Rectangle], ) -> Result<::TextureId, ::Error>; @@ -404,7 +404,7 @@ pub trait ImportAll: Renderer { fn import_buffer( &mut self, buffer: &wl_buffer::WlBuffer, - surface: Option<&SurfaceAttributes>, + surface: Option<&crate::wayland::compositor::SurfaceData>, damage: &[Rectangle], ) -> Option::TextureId, ::Error>>; } @@ -419,7 +419,7 @@ impl ImportAll for R { fn import_buffer( &mut self, buffer: &wl_buffer::WlBuffer, - surface: Option<&SurfaceAttributes>, + surface: Option<&SurfaceData>, damage: &[Rectangle], ) -> Option::TextureId, ::Error>> { match buffer_type(buffer) { @@ -439,7 +439,7 @@ impl ImportAll for R { fn import_buffer( &mut self, buffer: &wl_buffer::WlBuffer, - surface: Option<&SurfaceAttributes>, + surface: Option<&SurfaceData>, damage: &[Rectangle], ) -> Option::TextureId, ::Error>> { match buffer_type(buffer) { diff --git a/src/wayland/compositor/cache.rs b/src/wayland/compositor/cache.rs new file mode 100644 index 0000000..25aa0a0 --- /dev/null +++ b/src/wayland/compositor/cache.rs @@ -0,0 +1,204 @@ +// The caching logic is used to process surface synchronization. It creates +// an effective decoupling between the moment the client sends wl_surface.commit +// and the moment where the state that was commited is actually applied by the +// compositor. +// +// The way this is modelled in Smithay is through the `Cache` type, which is a container +// representing a cached state for a particular type. The full cached state of a surface +// is thus composed of a a set of `Cache` for all relevant `T`, as modelled by the +// `MultiCache`. +// +// The logic of the `Cache` is as follows: +// +// - The protocol handlers mutably access the `pending` state to modify it accord to +// the client requests +// - On commit, a snapshot of this pending state is created by invoking `Cacheable::commit` +// and stored in the cache alongside an externally provided id +// - When the compositor decices that a given state (represented by its commit id) should +// become active, `Cache::apply_state` is invoked with that commit id. The associated state +// is then applied to the `current` state, that the compositor can then use as a reference +// for the current window state. Note that, to preserve the commit ordering, all states +// with a commit id older than the one requested are applied as well, in order. +// +// The logic for generating these commit ids and deciding when to apply them is implemented +// and described in `transaction.rs`. + +use std::{ + cell::{RefCell, RefMut}, + collections::VecDeque, +}; + +use downcast_rs::{impl_downcast, Downcast}; + +use crate::wayland::Serial; + +/// Trait represening a value that can be used in double-buffered storage +/// +/// The type needs to implement the [`Default`] trait, which will be used +/// to initialize. You further need to provide two methods: +/// [`Cacheable::commit`] and [`Cacheable::merge_into`]. +/// +/// Double-buffered state works by having a "pending" instance of your type, +/// into which new values provided by the client are inserted. When the client +/// sends `wl_surface.commit`, the [`Cacheable::commit`] method will be +/// invoked on your value. This method is expected to produce a new instance of +/// your type, that will be stored in the cache, and eventually merged into the +/// current state. +/// +/// In most cases, this method will simply produce a copy of the pending state, +/// but you might need additionnal logic in some cases, such as for handling +/// non-cloneable resources (which thus need to be moved into the produce value). +/// +/// Then at some point the [`Cacheable::merge_into`] method of your type will be +/// invoked. In this method, `self` acts as the update that should be merged into +/// the current state provided as argument. In simple cases, the action would just +/// be to copy `self` into the current state, but mre complex cases require +/// additionnal logic. +pub trait Cacheable: Default { + /// Produce a new state to be cached from the pending state + fn commit(&mut self) -> Self; + /// Merge a state update into the current state + fn merge_into(self, into: &mut Self); +} + +struct CachedState { + pending: T, + cache: VecDeque<(Serial, T)>, + current: T, +} + +impl Default for CachedState { + fn default() -> Self { + CachedState { + pending: T::default(), + cache: VecDeque::new(), + current: T::default(), + } + } +} + +trait Cache: Downcast { + fn commit(&self, commit_id: Option); + fn apply_state(&self, commit_id: Serial); +} + +impl_downcast!(Cache); + +impl Cache for RefCell> { + fn commit(&self, commit_id: Option) { + let mut guard = self.borrow_mut(); + let me = &mut *guard; + let new_state = me.pending.commit(); + if let Some(id) = commit_id { + match me.cache.back_mut() { + Some(&mut (cid, ref mut state)) if cid == id => new_state.merge_into(state), + _ => me.cache.push_back((id, new_state)), + } + } else { + for (_, state) in me.cache.drain(..) { + state.merge_into(&mut me.current); + } + new_state.merge_into(&mut me.current); + } + } + + fn apply_state(&self, commit_id: Serial) { + let mut me = self.borrow_mut(); + loop { + if me.cache.front().map(|&(s, _)| s > commit_id).unwrap_or(true) { + // if the cache is empty or the next state has a commit_id greater than the requested one + break; + } + me.cache.pop_front().unwrap().1.merge_into(&mut me.current); + } + } +} + +/// A typemap-like container for double-buffered values +/// +/// All values inserted into this container must implement the [`Cacheable`] trait, +/// which defines their buffering semantics. They futhermore must be `Send` as the surface state +/// can be accessed from multiple threads (but `Sync` is not required, the surface internally synchronizes +/// access to its state). +/// +/// Consumers of surface state (like compositor applications using Smithay) will mostly be concerned +/// with the [`MultiCache::current`] method, which gives access to the current state of the surface for +/// a particular type. +/// +/// Writers of protocol extensions logic will mostly be concerned with the [`MultiCache::pending`] method, +/// which provides access to the pending state of the surface, in which new state from clients will be +/// stored. +/// +/// This contained has [`RefCell`]-like semantics: values of multiple stored types can be accessed at the +/// same time. The stored values are initialized lazyly the first time `current()` or `pending()` are +/// invoked with this type as argument. +pub struct MultiCache { + caches: appendlist::AppendList>, +} + +impl MultiCache { + pub(crate) fn new() -> MultiCache { + MultiCache { + caches: appendlist::AppendList::new(), + } + } + + fn find_or_insert(&self) -> &RefCell> { + for cache in &self.caches { + if let Some(v) = (**cache).as_any().downcast_ref() { + return v; + } + } + // if we reach here, then the value is not yet in the list, insert it + self.caches + .push(Box::new(RefCell::new(CachedState::::default())) as Box<_>); + (*self.caches[self.caches.len() - 1]) + .as_any() + .downcast_ref() + .unwrap() + } + + /// Acces the pending state associated with type `T` + pub fn pending(&self) -> RefMut<'_, T> { + RefMut::map(self.find_or_insert::().borrow_mut(), |cs| &mut cs.pending) + } + + /// Access the current state associated with type `T` + pub fn current(&self) -> RefMut<'_, T> { + RefMut::map(self.find_or_insert::().borrow_mut(), |cs| &mut cs.current) + } + + /// Check if the container currently contains values for type `T` + pub fn has(&self) -> bool { + self.caches.iter().any(|c| c.as_any().is::()) + } + + /// Commits the pending state, invoking Cacheable::commit() + /// + /// If commit_id is None, then the pending state is directly merged + /// into the current state. Otherwise, this id is used to store the + /// cached state. An id ca no longer be re-used as soon as not new id + /// has been used in between. Provided IDs are expected to be provided + /// in increasing order according to `Serial` semantics. + /// + /// If a None commit is given but there are some cached states, they'll + /// all be merged into the current state before merging the pending one. + pub(crate) fn commit(&mut self, commit_id: Option) { + // none of the underlying borrow_mut() can panic, as we hold + // a &mut reference to the container, non are borrowed. + for cache in &self.caches { + cache.commit(commit_id); + } + } + + /// Apply given identified cached state to the current one + /// + /// All other preceding states are applied as well, to preserve commit ordering + pub(crate) fn apply_state(&self, commit_id: Serial) { + // none of the underlying borrow_mut() can panic, as we hold + // a &mut reference to the container, non are borrowed. + for cache in &self.caches { + cache.apply_state(commit_id); + } + } +} diff --git a/src/wayland/compositor/handlers.rs b/src/wayland/compositor/handlers.rs index 2f891d1..c93e9e4 100644 --- a/src/wayland/compositor/handlers.rs +++ b/src/wayland/compositor/handlers.rs @@ -1,4 +1,12 @@ -use std::{cell::RefCell, ops::Deref as _, rc::Rc, sync::Mutex}; +use std::{ + cell::RefCell, + ops::Deref as _, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, + }, +}; use wayland_server::{ protocol::{wl_compositor, wl_region, wl_subcompositor, wl_subsurface, wl_surface}, @@ -6,23 +14,22 @@ use wayland_server::{ }; use super::{ - tree::{Location, SurfaceData}, - AlreadyHasRole, BufferAssignment, CompositorToken, Damage, Rectangle, RectangleKind, RegionAttributes, - Role, RoleType, SubsurfaceRole, SurfaceEvent, + cache::Cacheable, + tree::{Location, PrivateSurfaceData}, + AlreadyHasRole, BufferAssignment, Damage, Rectangle, RectangleKind, RegionAttributes, SurfaceAttributes, }; /* * wl_compositor */ -pub(crate) fn implement_compositor( +pub(crate) fn implement_compositor( compositor: Main, log: ::slog::Logger, implem: Rc>, ) -> wl_compositor::WlCompositor where - R: Default + Send + 'static, - Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken, DispatchData<'a>) + 'static, + Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static, { compositor.quick_assign(move |_compositor, request, _| match request { wl_compositor::Request::CreateSurface { id } => { @@ -42,29 +49,24 @@ where * wl_surface */ -type SurfaceImplemFn = - dyn for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken, DispatchData<'a>); +type SurfaceImplemFn = dyn for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>); // Internal implementation data of surfaces -pub(crate) struct SurfaceImplem { +pub(crate) struct SurfaceImplem { log: ::slog::Logger, - implem: Rc>>, + implem: Rc>, } -impl SurfaceImplem { - fn make(log: ::slog::Logger, implem: Rc>) -> SurfaceImplem +impl SurfaceImplem { + fn make(log: ::slog::Logger, implem: Rc>) -> SurfaceImplem where - Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken, DispatchData<'a>) - + 'static, + Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static, { SurfaceImplem { log, implem } } } -impl SurfaceImplem -where - R: 'static, -{ +impl SurfaceImplem { fn receive_surface_request( &mut self, req: wl_surface::Request, @@ -73,8 +75,8 @@ where ) { match req { wl_surface::Request::Attach { buffer, x, y } => { - SurfaceData::::with_data(&surface, |d| { - d.buffer = Some(match buffer { + PrivateSurfaceData::with_states(&surface, |states| { + states.cached_state.pending::().buffer = Some(match buffer { Some(buffer) => BufferAssignment::NewBuffer { buffer, delta: (x, y), @@ -84,13 +86,21 @@ where }); } wl_surface::Request::Damage { x, y, width, height } => { - SurfaceData::::with_data(&surface, |d| { - d.damage.push(Damage::Surface(Rectangle { x, y, width, height })); + PrivateSurfaceData::with_states(&surface, |states| { + states + .cached_state + .pending::() + .damage + .push(Damage::Surface(Rectangle { x, y, width, height })); }); } wl_surface::Request::Frame { callback } => { - SurfaceData::::with_data(&surface, move |d| { - d.frame_callbacks.push((*callback).clone()); + PrivateSurfaceData::with_states(&surface, |states| { + states + .cached_state + .pending::() + .frame_callbacks + .push((*callback).clone()); }); } wl_surface::Request::SetOpaqueRegion { region } => { @@ -98,29 +108,50 @@ where let attributes_mutex = r.as_ref().user_data().get::>().unwrap(); attributes_mutex.lock().unwrap().clone() }); - SurfaceData::::with_data(&surface, |d| d.opaque_region = attributes); + PrivateSurfaceData::with_states(&surface, |states| { + states.cached_state.pending::().opaque_region = attributes; + }); } wl_surface::Request::SetInputRegion { region } => { let attributes = region.map(|r| { let attributes_mutex = r.as_ref().user_data().get::>().unwrap(); attributes_mutex.lock().unwrap().clone() }); - SurfaceData::::with_data(&surface, |d| d.input_region = attributes); + PrivateSurfaceData::with_states(&surface, |states| { + states.cached_state.pending::().input_region = attributes; + }); } wl_surface::Request::Commit => { let mut user_impl = self.implem.borrow_mut(); + PrivateSurfaceData::invoke_commit_hooks(&surface); + if !surface.as_ref().is_alive() { + // the client was killed by a hook, abort + return; + } + PrivateSurfaceData::commit(&surface); trace!(self.log, "Calling user implementation for wl_surface.commit"); - (&mut *user_impl)(SurfaceEvent::Commit, surface, CompositorToken::make(), ddata); + (&mut *user_impl)(surface, ddata); } wl_surface::Request::SetBufferTransform { transform } => { - SurfaceData::::with_data(&surface, |d| d.buffer_transform = transform); + PrivateSurfaceData::with_states(&surface, |states| { + states + .cached_state + .pending::() + .buffer_transform = transform; + }); } wl_surface::Request::SetBufferScale { scale } => { - SurfaceData::::with_data(&surface, |d| d.buffer_scale = scale); + PrivateSurfaceData::with_states(&surface, |states| { + states.cached_state.pending::().buffer_scale = scale; + }); } wl_surface::Request::DamageBuffer { x, y, width, height } => { - SurfaceData::::with_data(&surface, |d| { - d.damage.push(Damage::Buffer(Rectangle { x, y, width, height })) + PrivateSurfaceData::with_states(&surface, |states| { + states + .cached_state + .pending::() + .damage + .push(Damage::Buffer(Rectangle { x, y, width, height })) }); } wl_surface::Request::Destroy => { @@ -131,22 +162,53 @@ where } } -fn implement_surface( +impl Cacheable for SurfaceAttributes { + fn commit(&mut self) -> Self { + SurfaceAttributes { + buffer: self.buffer.take(), + buffer_scale: self.buffer_scale, + buffer_transform: self.buffer_transform, + damage: std::mem::take(&mut self.damage), + opaque_region: self.opaque_region.clone(), + input_region: self.input_region.clone(), + frame_callbacks: std::mem::take(&mut self.frame_callbacks), + } + } + fn merge_into(self, into: &mut Self) { + if self.buffer.is_some() { + if let Some(BufferAssignment::NewBuffer { buffer, .. }) = + std::mem::replace(&mut into.buffer, self.buffer) + { + buffer.release(); + } + } + into.buffer_scale = self.buffer_scale; + into.buffer_transform = self.buffer_transform; + into.damage.extend(self.damage); + into.opaque_region = self.opaque_region; + into.input_region = self.input_region; + into.frame_callbacks.extend(self.frame_callbacks); + } +} + +fn implement_surface( surface: Main, log: ::slog::Logger, implem: Rc>, ) -> wl_surface::WlSurface where - R: Default + Send + 'static, - Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken, DispatchData<'a>) + 'static, + Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static, { surface.quick_assign({ let mut implem = SurfaceImplem::make(log, implem); move |surface, req, ddata| implem.receive_surface_request(req, surface.deref().clone(), ddata) }); - surface.assign_destructor(Filter::new(|surface, _, _| SurfaceData::::cleanup(&surface))); - surface.as_ref().user_data().set_threadsafe(SurfaceData::::new); - SurfaceData::::init(&surface); + surface.assign_destructor(Filter::new(|surface, _, _| PrivateSurfaceData::cleanup(&surface))); + surface + .as_ref() + .user_data() + .set_threadsafe(PrivateSurfaceData::new); + PrivateSurfaceData::init(&surface); surface.deref().clone() } @@ -188,22 +250,19 @@ fn implement_region(region: Main) -> wl_region::WlRegion { * wl_subcompositor */ -pub(crate) fn implement_subcompositor( +pub(crate) fn implement_subcompositor( subcompositor: Main, -) -> wl_subcompositor::WlSubcompositor -where - R: RoleType + Role + 'static, -{ +) -> wl_subcompositor::WlSubcompositor { subcompositor.quick_assign(move |subcompositor, request, _| match request { wl_subcompositor::Request::GetSubsurface { id, surface, parent } => { - if let Err(AlreadyHasRole) = SurfaceData::::set_parent(&surface, &parent) { + if let Err(AlreadyHasRole) = PrivateSurfaceData::set_parent(&surface, &parent) { subcompositor.as_ref().post_error( wl_subcompositor::Error::BadSurface as u32, "Surface already has a role.".into(), ); return; } - implement_subsurface::(id, surface); + implement_subsurface(id, surface); } wl_subcompositor::Request::Destroy => {} _ => unreachable!(), @@ -215,32 +274,72 @@ where * wl_subsurface */ -fn with_subsurface_attributes(subsurface: &wl_subsurface::WlSubsurface, f: F) -where - F: FnOnce(&mut SubsurfaceRole), - R: RoleType + Role + 'static, -{ - let surface = subsurface - .as_ref() - .user_data() - .get::() - .unwrap(); - SurfaceData::::with_role_data::(surface, |d| f(d)) - .expect("The surface does not have a subsurface role while it has a wl_subsurface?!"); +/// The cached state associated with a subsurface +pub struct SubsurfaceCachedState { + /// Location of the top-left corner of this subsurface + /// relative to its parent coordinate space + pub location: (i32, i32), } -fn implement_subsurface( +impl Default for SubsurfaceCachedState { + fn default() -> Self { + SubsurfaceCachedState { location: (0, 0) } + } +} + +impl Cacheable for SubsurfaceCachedState { + fn commit(&mut self) -> Self { + SubsurfaceCachedState { + location: self.location, + } + } + + fn merge_into(self, into: &mut Self) { + into.location = self.location; + } +} + +pub(crate) struct SubsurfaceState { + pub(crate) sync: AtomicBool, +} + +impl SubsurfaceState { + fn new() -> SubsurfaceState { + SubsurfaceState { + sync: AtomicBool::new(true), + } + } +} + +/// Check if a (sub)surface is effectively sync +pub fn is_effectively_sync(surface: &wl_surface::WlSurface) -> bool { + let is_direct_sync = PrivateSurfaceData::with_states(surface, |state| { + state + .data_map + .get::() + .map(|s| s.sync.load(Ordering::Acquire)) + .unwrap_or(false) + }); + if is_direct_sync { + return true; + } + if let Some(parent) = PrivateSurfaceData::get_parent(surface) { + is_effectively_sync(&parent) + } else { + false + } +} + +fn implement_subsurface( subsurface: Main, surface: wl_surface::WlSurface, -) -> wl_subsurface::WlSubsurface -where - R: RoleType + Role + 'static, -{ - subsurface.quick_assign(|subsurface, request, _| { +) -> wl_subsurface::WlSubsurface { + let data_surface = surface.clone(); + subsurface.quick_assign(move |subsurface, request, _| { match request { wl_subsurface::Request::SetPosition { x, y } => { - with_subsurface_attributes::(&subsurface, |attrs| { - attrs.location = (x, y); + PrivateSurfaceData::with_states(&surface, |state| { + state.cached_state.pending::().location = (x, y); }) } wl_subsurface::Request::PlaceAbove { sibling } => { @@ -249,7 +348,7 @@ where .user_data() .get::() .unwrap(); - if let Err(()) = SurfaceData::::reorder(surface, Location::After, &sibling) { + if let Err(()) = PrivateSurfaceData::reorder(surface, Location::After, &sibling) { subsurface.as_ref().post_error( wl_subsurface::Error::BadSurface as u32, "Provided surface is not a sibling or parent.".into(), @@ -262,18 +361,28 @@ where .user_data() .get::() .unwrap(); - if let Err(()) = SurfaceData::::reorder(surface, Location::Before, &sibling) { + if let Err(()) = PrivateSurfaceData::reorder(surface, Location::Before, &sibling) { subsurface.as_ref().post_error( wl_subsurface::Error::BadSurface as u32, "Provided surface is not a sibling or parent.".into(), ) } } - wl_subsurface::Request::SetSync => with_subsurface_attributes::(&subsurface, |attrs| { - attrs.sync = true; + wl_subsurface::Request::SetSync => PrivateSurfaceData::with_states(&surface, |state| { + state + .data_map + .get::() + .unwrap() + .sync + .store(true, Ordering::Release); }), - wl_subsurface::Request::SetDesync => with_subsurface_attributes::(&subsurface, |attrs| { - attrs.sync = false; + wl_subsurface::Request::SetDesync => PrivateSurfaceData::with_states(&surface, |state| { + state + .data_map + .get::() + .unwrap() + .sync + .store(false, Ordering::Release); }), wl_subsurface::Request::Destroy => { // Our destructor already handles it @@ -281,23 +390,25 @@ where _ => unreachable!(), } }); - subsurface.assign_destructor(Filter::new(|subsurface, _, _| { - destroy_subsurface::(&subsurface) - })); - subsurface.as_ref().user_data().set_threadsafe(|| surface); + super::with_states(&data_surface, |states| { + states.data_map.insert_if_missing_threadsafe(SubsurfaceState::new) + }) + .unwrap(); + subsurface.assign_destructor(Filter::new(|subsurface, _, _| destroy_subsurface(&subsurface))); + subsurface + .as_ref() + .user_data() + .set_threadsafe(move || data_surface); subsurface.deref().clone() } -fn destroy_subsurface(subsurface: &wl_subsurface::WlSubsurface) -where - R: RoleType + Role + 'static, -{ +fn destroy_subsurface(subsurface: &wl_subsurface::WlSubsurface) { let surface = subsurface .as_ref() .user_data() .get::() .unwrap(); if surface.as_ref().is_alive() { - SurfaceData::::unset_parent(&surface); + PrivateSurfaceData::unset_parent(&surface); } } diff --git a/src/wayland/compositor/mod.rs b/src/wayland/compositor/mod.rs index d666850..626432f 100644 --- a/src/wayland/compositor/mod.rs +++ b/src/wayland/compositor/mod.rs @@ -9,7 +9,8 @@ //! //! This implementation does a simple job: it stores in a coherent way the state of //! surface trees with subsurfaces, to provide you a direct access to the tree -//! structure and all surface metadata. +//! structure and all surface attributes, and handles the application of double-buffered +//! state. //! //! As such, you can, given a root surface with a role requiring it to be displayed, //! you can iterate over the whole tree of subsurfaces to recover all the metadata you @@ -32,54 +33,67 @@ //! # #[macro_use] extern crate smithay; //! use smithay::wayland::compositor::compositor_init; //! -//! // Declare the roles enum -//! define_roles!(MyRoles); -//! //! # let mut display = wayland_server::Display::new(); //! // Call the init function: -//! let (token, _, _) = compositor_init::( +//! compositor_init( //! &mut display, -//! |request, surface, compositor_token, dispatch_data| { +//! |surface, dispatch_data| { //! /* -//! Your handling of the user requests. +//! Your handling of surface commits //! */ //! }, //! None // put a logger here //! ); //! -//! // this `token` is what you'll use to access the surface associated data -//! //! // You're now ready to go! //! ``` //! -//! ### Use the surface metadata +//! ### Use the surface states //! -//! As you can see in the previous example, in the end we are retrieving a token from -//! the `init` function. This token is necessary to retrieve the metadata associated with -//! a surface. It can be cloned. See [`CompositorToken`] -//! for the details of what it enables you. +//! The main access to surface states is done through the [`with_states`] function, which +//! gives you access to the [`SurfaceData`] instance associated with this surface. It acts +//! as a general purpose container for associating state to a surface, double-buffered or +//! not. See its documentation for more details. //! -//! The surface metadata is held in the [`SurfaceAttributes`] -//! struct. In contains double-buffered state pending from the client as defined by the protocol for -//! [`wl_surface`](wayland_server::protocol::wl_surface), as well as your user-defined type holding -//! any data you need to have associated with a struct. See its documentation for details. +//! ### State application and hooks //! -//! This [`CompositorToken`] also provides access to the metadata associated with the role of the -//! surfaces. See the documentation of the [`roles`] submodule -//! for a detailed explanation. +//! On commit of a surface several steps are taken to update the state of the surface. Actions +//! are taken by smithay in the following order: +//! +//! 1. Commit hooks registered to this surface are invoked. Such hooks can be registered using +//! the [`add_commit_hook`] function. They are typically used by protocol extensions that +//! add state to a surface and need to check on commit that client did not request an +//! illegal state before it is applied on commit. +//! 2. The pending state is either applied and made current, or cached for later application +//! is the surface is a synchronize subsurface. If the current state is applied, state +//! of the synchronized children subsurface are applied as well at this point. +//! 3. Your user callback provided to [`compositor_init`] is invoked, so that you can access +//! the new current state of the surface. The state of sync children subsurfaces of your +//! surface may have changed as well, so this is the place to check it, using functions +//! like [`with_surface_tree_upward`] or [`with_surface_tree_downward`]. On the other hand, +//! if the surface is a sync subsurface, its current state will note have changed as +//! the result of that commit. You can check if it is using [`is_sync_subsurface`]. +//! +//! ### Surface roles +//! +//! The wayland protocol specifies that a surface needs to be assigned a role before it can +//! be displayed. Furthermore, a surface can only have a single role during its whole lifetime. +//! Smithay represents this role as a `&'static str` identifier, that can only be set once +//! on a surface. See [`give_role`] and [`get_role`] for details. This module manages the +//! subsurface role, which is identified by the string `"subsurface"`. -use std::{cell::RefCell, fmt, rc::Rc, sync::Mutex}; +use std::{cell::RefCell, rc::Rc, sync::Mutex}; +mod cache; mod handlers; -pub mod roles; +mod transaction; mod tree; -pub use self::tree::TraversalAction; -use self::{ - roles::{AlreadyHasRole, Role, RoleType, WrongRole}, - tree::SurfaceData, -}; -use crate::utils::Rectangle; +pub use self::cache::{Cacheable, MultiCache}; +pub use self::handlers::SubsurfaceCachedState; +use self::tree::PrivateSurfaceData; +pub use self::tree::{AlreadyHasRole, TraversalAction}; +use crate::utils::{DeadResource, Rectangle}; use wayland_server::{ protocol::{ wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface::WlSurface, @@ -104,6 +118,32 @@ struct Marker { _r: ::std::marker::PhantomData, } +/// The state container associated with a surface +/// +/// This general-purpose container provides 2 main storages: +/// +/// - the `data_map` storage has typemap semantics and allows you +/// to associate and access non-buffered data to the surface +/// - the `cached_state` storages allows you to associate state to +/// the surface that follows the double-buffering semantics associated +/// with the `commit` procedure of surfaces, also with typemap-like +/// semantics +/// +/// See the respective documentation of each container for its usage. +/// +/// By default, all surfaces have a [`SurfaceAttributes`] cached state, +/// and subsurface also have a [`SubsurfaceCachedState`] state as well. +pub struct SurfaceData { + /// The current role of the surface. + /// + /// If `None` if the surface has not yet been assigned a role + pub role: Option<&'static str>, + /// The non-buffered typemap storage of this surface + pub data_map: UserDataMap, + /// The double-buffered typemap storage of this surface + pub cached_state: MultiCache, +} + /// New buffer assignation for a surface #[derive(Debug)] pub enum BufferAssignment { @@ -118,14 +158,12 @@ pub enum BufferAssignment { }, } -/// Data associated with a surface, aggregated by the handlers +/// General state associated with a surface /// -/// Most of the fields of this struct represent a double-buffered state, which -/// should only be applied once a [`commit`](SurfaceEvent::Commit) -/// request is received from the surface. -/// -/// You are responsible for setting those values as you see fit to avoid -/// processing them two times. +/// The fields `buffer`, `damage` and `frame_callbacks` should be +/// reset (by clearing their contents) once you have adequately +/// processed them, as their contents are aggregated from commit to commit. +#[derive(Debug)] pub struct SurfaceAttributes { /// Buffer defining the contents of the surface /// @@ -172,26 +210,6 @@ pub struct SurfaceAttributes { /// An example possibility would be to trigger it once the frame /// associated with this commit has been displayed on the screen. pub frame_callbacks: Vec, - /// User-controlled data - /// - /// This is your field to host whatever you need. - pub user_data: UserDataMap, -} - -// UserDataMap does not implement debug, so we have to impl Debug manually -impl fmt::Debug for SurfaceAttributes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SurfaceAttributes") - .field("buffer", &self.buffer) - .field("buffer_scale", &self.buffer_scale) - .field("buffer_transform", &self.buffer_transform) - .field("opaque_region", &self.opaque_region) - .field("input_region", &self.input_region) - .field("damage", &self.damage) - .field("frame_callbacks", &self.frame_callbacks) - .field("user_data", &"...") - .finish() - } } impl Default for SurfaceAttributes { @@ -204,7 +222,6 @@ impl Default for SurfaceAttributes { input_region: None, damage: Vec::new(), frame_callbacks: Vec::new(), - user_data: UserDataMap::new(), } } } @@ -277,244 +294,160 @@ impl RegionAttributes { } } -/// A Compositor global token +/// Access the data of a surface tree from bottom to top /// -/// This token can be cloned at will, and is the entry-point to -/// access data associated with the [`wl_surface`](wayland_server::protocol::wl_surface) -/// and [`wl_region`](wayland_server::protocol::wl_region) managed -/// by the `CompositorGlobal` that provided it. -#[derive(Debug)] -pub struct CompositorToken { - _role: ::std::marker::PhantomData<*mut R>, -} - -// we implement them manually because #[derive(..)] would require R: Clone -impl Copy for CompositorToken {} -impl Clone for CompositorToken { - fn clone(&self) -> CompositorToken { - *self - } -} - -unsafe impl Send for CompositorToken {} -unsafe impl Sync for CompositorToken {} - -impl CompositorToken { - pub(crate) fn make() -> CompositorToken { - CompositorToken { - _role: ::std::marker::PhantomData, - } - } -} - -impl CompositorToken { - /// Access the data of a surface - /// - /// The closure will be called with the contents of the data associated with this surface. - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn with_surface_data(self, surface: &WlSurface, f: F) -> T - where - F: FnOnce(&mut SurfaceAttributes) -> T, - { - SurfaceData::::with_data(surface, f) - } -} - -impl CompositorToken -where - R: RoleType + Role + 'static, +/// You provide three closures, a "filter", a "processor" and a "post filter". +/// +/// The first closure is initially called on a surface to determine if its children +/// should be processed as well. It returns a `TraversalAction` reflecting that. +/// +/// The second closure is supposed to do the actual processing. The processing closure for +/// a surface may be called after the processing closure of some of its children, depending +/// on the stack ordering the client requested. Here the surfaces are processed in the same +/// order as they are supposed to be drawn: from the farthest of the screen to the nearest. +/// +/// The third closure is called once all the subtree of a node has been processed, and gives +/// an opportunity for early-stopping. If it returns `true` the processing will continue, +/// while if it returns `false` it'll stop. +/// +/// The arguments provided to the closures are, in this order: +/// +/// - The surface object itself +/// - a mutable reference to its surface attribute data +/// - a mutable reference to its role data, +/// - a custom value that is passed in a fold-like manner, but only from the output of a parent +/// to its children. See [`TraversalAction`] for details. +/// +/// If the surface not managed by the `CompositorGlobal` that provided this token, this +/// will panic (having more than one compositor is not supported). +pub fn with_surface_tree_upward( + surface: &WlSurface, + initial: T, + filter: F1, + processor: F2, + post_filter: F3, +) where + F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction, + F2: FnMut(&WlSurface, &SurfaceData, &T), + F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool, { - /// Access the data of a surface tree from bottom to top - /// - /// You provide three closures, a "filter", a "processor" and a "post filter". - /// - /// The first closure is initially called on a surface to determine if its children - /// should be processed as well. It returns a `TraversalAction` reflecting that. - /// - /// The second closure is supposed to do the actual processing. The processing closure for - /// a surface may be called after the processing closure of some of its children, depending - /// on the stack ordering the client requested. Here the surfaces are processed in the same - /// order as they are supposed to be drawn: from the farthest of the screen to the nearest. - /// - /// The third closure is called once all the subtree of a node has been processed, and gives - /// an opportunity for early-stopping. If it returns `true` the processing will continue, - /// while if it returns `false` it'll stop. - /// - /// The arguments provided to the closures are, in this order: - /// - /// - The surface object itself - /// - a mutable reference to its surface attribute data - /// - a mutable reference to its role data, - /// - a custom value that is passed in a fold-like manner, but only from the output of a parent - /// to its children. See [`TraversalAction`] for details. - /// - /// If the surface not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn with_surface_tree_upward( - self, - surface: &WlSurface, - initial: T, - filter: F1, - processor: F2, - post_filter: F3, - ) where - F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, - F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), - F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, - { - SurfaceData::::map_tree(surface, &initial, filter, processor, post_filter, false); - } + PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, false); +} - /// Access the data of a surface tree from top to bottom - /// - /// Behavior is the same as [`with_surface_tree_upward`](CompositorToken::with_surface_tree_upward), but - /// the processing is done in the reverse order, from the nearest of the screen to the deepest. - /// - /// This would typically be used to find out which surface of a subsurface tree has been clicked for example. - pub fn with_surface_tree_downward( - self, - surface: &WlSurface, - initial: T, - filter: F1, - processor: F2, - post_filter: F3, - ) where - F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, - F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), - F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, - { - SurfaceData::::map_tree(surface, &initial, filter, processor, post_filter, true); - } +/// Access the data of a surface tree from top to bottom +/// +/// Behavior is the same as [`with_surface_tree_upward`], but the processing is done in the reverse order, +/// from the nearest of the screen to the deepest. +/// +/// This would typically be used to find out which surface of a subsurface tree has been clicked for example. +pub fn with_surface_tree_downward( + surface: &WlSurface, + initial: T, + filter: F1, + processor: F2, + post_filter: F3, +) where + F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction, + F2: FnMut(&WlSurface, &SurfaceData, &T), + F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool, +{ + PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, true); +} - /// Retrieve the parent of this surface - /// - /// Returns `None` is this surface is a root surface - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn get_parent(self, surface: &WlSurface) -> Option { - SurfaceData::::get_parent(surface) +/// Retrieve the parent of this surface +/// +/// Returns `None` is this surface is a root surface +pub fn get_parent(surface: &WlSurface) -> Option { + if !surface.as_ref().is_alive() { + return None; } + PrivateSurfaceData::get_parent(surface) +} - /// Retrieve the children of this surface - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn get_children(self, surface: &WlSurface) -> Vec { - SurfaceData::::get_children(surface) +/// Retrieve the children of this surface +pub fn get_children(surface: &WlSurface) -> Vec { + if !surface.as_ref().is_alive() { + return Vec::new(); + } + PrivateSurfaceData::get_children(surface) +} + +/// Check if this subsurface is a synchronized subsurface +/// +/// Returns false if the surface is already dead +pub fn is_sync_subsurface(surface: &WlSurface) -> bool { + if !surface.as_ref().is_alive() { + return false; + } + self::handlers::is_effectively_sync(surface) +} + +/// Get the current role of this surface +pub fn get_role(surface: &WlSurface) -> Option<&'static str> { + if !surface.as_ref().is_alive() { + return None; + } + PrivateSurfaceData::get_role(surface) +} + +/// Register that this surface has given role +/// +/// Fails if the surface already has a role. +pub fn give_role(surface: &WlSurface, role: &'static str) -> Result<(), AlreadyHasRole> { + if !surface.as_ref().is_alive() { + return Ok(()); + } + PrivateSurfaceData::set_role(surface, role) +} + +/// Access the states associated to this surface +pub fn with_states(surface: &WlSurface, f: F) -> Result +where + F: FnOnce(&SurfaceData) -> T, +{ + if !surface.as_ref().is_alive() { + return Err(DeadResource); + } + Ok(PrivateSurfaceData::with_states(surface, f)) +} + +/// Retrieve the metadata associated with a `wl_region` +/// +/// If the region is not managed by the `CompositorGlobal` that provided this token, this +/// will panic (having more than one compositor is not supported). +pub fn get_region_attributes(region: &wl_region::WlRegion) -> RegionAttributes { + match region.as_ref().user_data().get::>() { + Some(mutex) => mutex.lock().unwrap().clone(), + None => panic!("Accessing the data of foreign regions is not supported."), } } -impl CompositorToken { - /// Check whether this surface as a role or not - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn has_a_role(self, surface: &WlSurface) -> bool { - SurfaceData::::has_a_role(surface) - } - - /// Check whether this surface as a specific role - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn has_role(self, surface: &WlSurface) -> bool - where - R: Role, - { - SurfaceData::::has_role::(surface) - } - - /// Register that this surface has given role with default data - /// - /// Fails if the surface already has a role. - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn give_role(self, surface: &WlSurface) -> Result<(), AlreadyHasRole> - where - R: Role, - RoleData: Default, - { - SurfaceData::::give_role::(surface) - } - - /// Register that this surface has given role with given data - /// - /// Fails if the surface already has a role and returns the data. - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn give_role_with(self, surface: &WlSurface, data: RoleData) -> Result<(), RoleData> - where - R: Role, - { - SurfaceData::::give_role_with::(surface, data) - } - - /// Access the role data of a surface - /// - /// Fails and don't call the closure if the surface doesn't have this role - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn with_role_data(self, surface: &WlSurface, f: F) -> Result - where - R: Role, - F: FnOnce(&mut RoleData) -> T, - { - SurfaceData::::with_role_data::(surface, f) - } - - /// Register that this surface does not have a role any longer and retrieve the data - /// - /// Fails if the surface didn't already have this role. - /// - /// If the surface is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn remove_role(self, surface: &WlSurface) -> Result - where - R: Role, - { - SurfaceData::::remove_role::(surface) - } - - /// Retrieve the metadata associated with a `wl_region` - /// - /// If the region is not managed by the `CompositorGlobal` that provided this token, this - /// will panic (having more than one compositor is not supported). - pub fn get_region_attributes(self, region: &wl_region::WlRegion) -> RegionAttributes { - match region.as_ref().user_data().get::>() { - Some(mutex) => mutex.lock().unwrap().clone(), - None => panic!("Accessing the data of foreign regions is not supported."), - } +/// Register a commit hook to be invoked on surface commit +/// +/// For its precise semantics, see module-level documentation. +pub fn add_commit_hook(surface: &WlSurface, hook: fn(&WlSurface)) { + if !surface.as_ref().is_alive() { + return; } + PrivateSurfaceData::add_commit_hook(surface, hook) } /// Create new [`wl_compositor`](wayland_server::protocol::wl_compositor) /// and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals. /// -/// The globals are directly registered into the event loop, and this function -/// returns a [`CompositorToken`] which you'll need access the data associated to -/// the [`wl_surface`](wayland_server::protocol::wl_surface)s. -/// -/// It also returns the two global handles, in case you wish to remove these -/// globals from the event loop in the future. -pub fn compositor_init( +/// It returns the two global handles, in case you wish to remove these globals from +/// the event loop in the future. +pub fn compositor_init( display: &mut Display, implem: Impl, logger: L, ) -> ( - CompositorToken, Global, Global, ) where L: Into>, - R: Default + RoleType + Role + Send + 'static, - Impl: for<'a> FnMut(SurfaceEvent, WlSurface, CompositorToken, DispatchData<'a>) + 'static, + Impl: for<'a> FnMut(WlSurface, DispatchData<'a>) + 'static, { let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "compositor_handler")); let implem = Rc::new(RefCell::new(implem)); @@ -522,32 +455,18 @@ where let compositor = display.create_global( 4, Filter::new(move |(new_compositor, _version), _, _| { - self::handlers::implement_compositor::(new_compositor, log.clone(), implem.clone()); + self::handlers::implement_compositor::(new_compositor, log.clone(), implem.clone()); }), ); let subcompositor = display.create_global( 1, Filter::new(move |(new_subcompositor, _version), _, _| { - self::handlers::implement_subcompositor::(new_subcompositor); + self::handlers::implement_subcompositor(new_subcompositor); }), ); - (CompositorToken::make(), compositor, subcompositor) -} - -/// User-handled events for surfaces -/// -/// The global provided by smithay cannot process these events for you, so -/// they are forwarded directly via your provided implementation, and are -/// described by this global. -#[derive(Debug)] -pub enum SurfaceEvent { - /// The double-buffered state has been validated by the client - /// - /// At this point, the pending state that has been accumulated in the [`SurfaceAttributes`] associated - /// to this surface should be atomically integrated into the current state of the surface. - Commit, + (compositor, subcompositor) } #[cfg(test)] diff --git a/src/wayland/compositor/roles.rs b/src/wayland/compositor/roles.rs deleted file mode 100644 index 038aedf..0000000 --- a/src/wayland/compositor/roles.rs +++ /dev/null @@ -1,280 +0,0 @@ -//! Tools for handling surface roles -//! -//! In the Wayland protocol, surfaces can have several different roles, which -//! define how they are to be used. The core protocol defines 3 of these roles: -//! -//! - `shell_surface`: This surface is to be considered as what is most often -//! called a "window". -//! - `pointer_surface`: This surface represent the contents of a pointer icon -//! and replaces the default pointer. -//! - `subsurface`: This surface is part of a subsurface tree, and as such has -//! a parent surface. -//! -//! A surface can have only one role at any given time. To change he role of a -//! surface, the client must first remove the previous role before assigning the -//! new one. A surface without a role is not displayed at all. -//! -//! This module provides tools to manage roles of a surface in a composable way -//! allowing all handlers of smithay to manage surface roles while being aware -//! of the possible role conflicts. -//! -//! ## General mechanism -//! -//! First, all roles need to have an unique type, holding its metadata and identifying it -//! to the type-system. Even if your role does not hold any metadata, you still need its -//! unique type, using a unit-like struct rather than `()`. -//! -//! You then need a type for managing the roles of a surface. This type holds information -//! about what is the current role of a surface, and what is the metadata associated with -//! it. -//! -//! For convenience, you can use the `define_roles!` macro provided by Smithay to define this -//! type. You can call it like this: -//! -//! ``` -//! # use smithay::define_roles; -//! // Metadata for a first role -//! #[derive(Default)] -//! pub struct MyRoleMetadata { -//! } -//! -//! // Metadata for a second role -//! #[derive(Default)] -//! pub struct MyRoleMetadata2 { -//! } -//! -//! define_roles!(Roles => -//! // You can put several roles like this -//! // first identifier is the name of the variant for this -//! // role in the generated enum, second is the token type -//! // for this role -//! [MyRoleName, MyRoleMetadata] -//! [MyRoleName2, MyRoleMetadata2] -//! /* ... */ -//! ); -//! -//! ``` -//! -//! And this will expand to an enum like this: -//! -//! ```ignore -//! pub enum Roles { -//! NoRole, -//! // The subsurface role is always inserted, as it is required -//! // by the CompositorHandler -//! Subsurface(::smithay::compositor::SubsurfaceAttributes), -//! // all your other roles come here -//! MyRoleName(MyRoleMetadata), -//! MyRoleName2(MyRoleMetadata2), -//! /* ... */ -//! } -//! ``` -//! -//! as well as implement a few trait for it, allowing it to be used by -//! all smithay handlers: -//! -//! - The trait [`RoleType`](RoleType), -//! which defines it as a type handling roles -//! - For each of your roles, the trait [`Role`](Role) -//! (where `Token` is your token type), marking its ability to handle this given role. -//! -//! All handlers that handle a specific role will require you to provide -//! them with a [`CompositorToken`](crate::wayland::compositor::CompositorToken) -//! where `R: Role`. -//! -//! See the documentation of these traits for their specific definition and -//! capabilities. - -/// An error type signifying that the surface does not have expected role -/// -/// Generated if you attempt a role operation on a surface that does -/// not have the role you asked for. -#[derive(Debug)] -pub struct WrongRole; - -impl std::fmt::Display for WrongRole { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Wrong role for surface.") - } -} - -impl std::error::Error for WrongRole {} - -/// An error type signifying that the surface already has a role and -/// cannot be assigned an other -/// -/// Generated if you attempt a role operation on a surface that does -/// not have the role you asked for. -#[derive(Debug)] -pub struct AlreadyHasRole; - -impl std::fmt::Display for AlreadyHasRole { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Surface already has a role.") - } -} - -impl std::error::Error for AlreadyHasRole {} - -/// A trait representing a type that can manage surface roles -pub trait RoleType { - /// Check if the associated surface has a role - /// - /// Only reports if the surface has any role or no role. - /// To check for a role in particular, see [`Role::has`]. - fn has_role(&self) -> bool; -} - -/// A trait representing the capability of a [`RoleType`] to handle a given role -/// -/// This trait allows to interact with the different roles a [`RoleType`] can -/// handle. -/// -/// This trait is meant to be used generically, for example, to retrieve the -/// data associated with a given role with token `TheRole`: -/// -/// ```ignore -/// let data = >::data(my_roles) -/// .expect("The surface does not have this role."); -/// ``` -/// -/// The methods of this trait are mirrored on -/// [`CompositorToken`](crate::wayland::compositor::CompositorToken) for easy -/// access to the role data of the surfaces. -/// -/// Note that if a role is automatically handled for you by a Handler provided -/// by smithay, you should not set or unset it manually on a surface. Doing so -/// would likely corrupt the internal state of these handlers, causing spurious -/// protocol errors and unreliable behaviour overall. -pub trait Role: RoleType { - /// Set the role for the associated surface with default associated data - /// - /// Fails if the surface already has a role - fn set(&mut self) -> Result<(), AlreadyHasRole> - where - R: Default, - { - self.set_with(Default::default()).map_err(|_| AlreadyHasRole) - } - - /// Set the role for the associated surface with given data - /// - /// Fails if the surface already has a role and returns the data - fn set_with(&mut self, data: R) -> Result<(), R>; - - /// Check if the associated surface has this role - fn has(&self) -> bool; - - /// Access the data associated with this role if its the current one - fn data(&self) -> Result<&R, WrongRole>; - - /// Mutably access the data associated with this role if its the current one - fn data_mut(&mut self) -> Result<&mut R, WrongRole>; - - /// Remove this role from the associated surface - /// - /// Fails if the surface does not currently have this role - fn unset(&mut self) -> Result; -} - -/// The roles defining macro -/// -/// See the docs of the [`wayland::compositor::roles`](wayland/compositor/roles/index.html) module -/// for an explanation of its use. -#[macro_export] -macro_rules! define_roles( - ($enum_name: ident) => { - define_roles!($enum_name =>); - }; - ($enum_name:ident => $($(#[$role_attr:meta])* [$role_name: ident, $role_data: ty])*) => { - define_roles!(__impl $enum_name => - // add in subsurface role - [Subsurface, $crate::wayland::compositor::SubsurfaceRole] - $($(#[$role_attr])* [$role_name, $role_data])* - ); - }; - (__impl $enum_name:ident => $($(#[$role_attr:meta])* [$role_name: ident, $role_data: ty])*) => { - pub enum $enum_name { - NoRole, - $($(#[$role_attr])* $role_name($role_data)),* - } - - impl Default for $enum_name { - fn default() -> $enum_name { - $enum_name::NoRole - } - } - - impl $crate::wayland::compositor::roles::RoleType for $enum_name { - fn has_role(&self) -> bool { - if let $enum_name::NoRole = *self { - false - } else { - true - } - } - } - - $( - $(#[$role_attr])* - impl $crate::wayland::compositor::roles::Role<$role_data> for $enum_name { - fn set_with(&mut self, data: $role_data) -> ::std::result::Result<(), $role_data> { - if let $enum_name::NoRole = *self { - *self = $enum_name::$role_name(data); - Ok(()) - } else { - Err(data) - } - } - - fn has(&self) -> bool { - if let $enum_name::$role_name(_) = *self { - true - } else { - false - } - } - - fn data(&self) -> ::std::result::Result< - &$role_data, - $crate::wayland::compositor::roles::WrongRole - > - { - if let $enum_name::$role_name(ref data) = *self { - Ok(data) - } else { - Err($crate::wayland::compositor::roles::WrongRole) - } - } - - fn data_mut(&mut self) -> ::std::result::Result< - &mut $role_data, - $crate::wayland::compositor::roles::WrongRole - > - { - if let $enum_name::$role_name(ref mut data) = *self { - Ok(data) - } else { - Err($crate::wayland::compositor::roles::WrongRole) - } - } - - fn unset(&mut self) -> ::std::result::Result< - $role_data, - $crate::wayland::compositor::roles::WrongRole - > - { - // remove self to make borrow checker happy - let temp = ::std::mem::replace(self, $enum_name::NoRole); - if let $enum_name::$role_name(data) = temp { - Ok(data) - } else { - // put it back in place - *self = temp; - Err($crate::wayland::compositor::roles::WrongRole) - } - } - } - )* - }; -); diff --git a/src/wayland/compositor/transaction.rs b/src/wayland/compositor/transaction.rs new file mode 100644 index 0000000..b818b34 --- /dev/null +++ b/src/wayland/compositor/transaction.rs @@ -0,0 +1,288 @@ +// The transaction model for handling surface states in Smithay +// +// The caching logic in `cache.rs` provides surfaces with a queue of +// pending states identified with numeric commit ids, allowing the compositor +// to precisely control *when* a state become active. This file is the second +// half: these identified states are grouped into transactions, which allow the +// synchronization of updates accross surfaces. +// +// There are 2 main cases when the state of multiple surfaces must be updated +// atomically: +// - synchronized subsurface must have their state updated at the same time as their parents +// - The upcoming `wp_transaction` protocol +// +// In these situations, the individual states in a surface queue are grouped into a transaction +// and are all applied atomically when the transaction itself is applied. The logic for creating +// new transactions is currently the following: +// +// - Each surface has an implicit "pending" transaction, into which its newly commited state is +// recorded +// - Furthermore, on commit, the pending transaction of all synchronized child subsurfaces is merged +// into the current surface's pending transaction, and a new implicit transaction is started for those +// children (logic is implemented in `handlers.rs`, in `PrivateSurfaceData::commit`). +// - Then, still on commit, if the surface is not a synchronized subsurface, its pending transaction is +// directly applied +// +// This last step will change once we have support for explicit synchronization (and further in the future, +// of the wp_transaction protocol). Explicit synchronization introduces a notion of blockers: the transaction +// cannot be applied before all blockers are released, and thus must wait for it to be the case. +// +// For thoses situations, the (currently unused) `TransactionQueue` will come into play. It is a per-client +// queue of transactions, that stores and applies them by both respecting their topological order +// (ensuring that for each surface, states are applied in the correct order) and that all transactions +// wait befor all their blockers are resolved to be merged. If a blocker is cancelled, the whole transaction +// it blocks is cancelled as well, and simply dropped. Thanks to the logic of `Cache::apply_state`, the +// associated state will be applied automatically when the next valid transaction is applied, ensuring +// global coherence. + +// A significant part of the logic of this module is not yet used, +// but will be once proper transaction & blockers support is +// added to smithay +#![allow(dead_code)] + +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; + +use wayland_server::protocol::wl_surface::WlSurface; + +use crate::wayland::Serial; + +use super::tree::PrivateSurfaceData; + +pub trait Blocker { + fn state(&self) -> BlockerState; +} + +pub enum BlockerState { + Pending, + Released, + Cancelled, +} + +struct TransactionState { + surfaces: Vec<(WlSurface, Serial)>, + blockers: Vec>, +} + +impl Default for TransactionState { + fn default() -> Self { + TransactionState { + surfaces: Vec::new(), + blockers: Vec::new(), + } + } +} + +impl TransactionState { + fn insert(&mut self, surface: WlSurface, id: Serial) { + if let Some(place) = self.surfaces.iter_mut().find(|place| place.0 == surface) { + // the surface is already in the list, update the serial + if place.1 < id { + place.1 = id; + } + } else { + // the surface is not in the list, insert it + self.surfaces.push((surface, id)); + } + } +} + +enum TransactionInner { + Data(TransactionState), + Fused(Arc>), +} + +pub(crate) struct PendingTransaction { + inner: Arc>, +} + +impl Default for PendingTransaction { + fn default() -> Self { + PendingTransaction { + inner: Arc::new(Mutex::new(TransactionInner::Data(Default::default()))), + } + } +} + +impl PendingTransaction { + fn with_inner_state T>(&self, f: F) -> T { + let mut next = self.inner.clone(); + loop { + let tmp = match *next.lock().unwrap() { + TransactionInner::Data(ref mut state) => return f(state), + TransactionInner::Fused(ref into) => into.clone(), + }; + next = tmp; + } + } + + pub(crate) fn insert_state(&self, surface: WlSurface, id: Serial) { + self.with_inner_state(|state| state.insert(surface, id)) + } + + pub(crate) fn add_blocker(&self, blocker: B) { + self.with_inner_state(|state| state.blockers.push(Box::new(blocker) as Box<_>)) + } + + pub(crate) fn is_same_as(&self, other: &PendingTransaction) -> bool { + let ptr1 = self.with_inner_state(|state| state as *const _); + let ptr2 = other.with_inner_state(|state| state as *const _); + ptr1 == ptr2 + } + + pub(crate) fn merge_into(&self, into: &PendingTransaction) { + if self.is_same_as(into) { + // nothing to do + return; + } + // extract our pending surfaces and change our link + let mut next = self.inner.clone(); + let my_state; + loop { + let tmp = { + let mut guard = next.lock().unwrap(); + match *guard { + TransactionInner::Data(ref mut state) => { + my_state = std::mem::take(state); + *guard = TransactionInner::Fused(into.inner.clone()); + break; + } + TransactionInner::Fused(ref into) => into.clone(), + } + }; + next = tmp; + } + // fuse our surfaces into our new transaction state + self.with_inner_state(|state| { + for (surface, id) in my_state.surfaces { + state.insert(surface, id); + } + state.blockers.extend(my_state.blockers); + }); + } + + pub(crate) fn finalize(mut self) -> Transaction { + // When finalizing a transaction, this *must* be the last handle to this transaction + loop { + let inner = match Arc::try_unwrap(self.inner) { + Ok(mutex) => mutex.into_inner().unwrap(), + Err(_) => panic!("Attempting to finalize a transaction but handle is not the last."), + }; + match inner { + TransactionInner::Data(TransactionState { + surfaces, blockers, .. + }) => return Transaction { surfaces, blockers }, + TransactionInner::Fused(into) => self.inner = into, + } + } + } +} +pub(crate) struct Transaction { + surfaces: Vec<(WlSurface, Serial)>, + blockers: Vec>, +} + +impl Transaction { + /// Computes the global state of the transaction wrt its blockers + /// + /// The logic is: + /// + /// - if at least one blocker is cancelled, the transaction is cancelled + /// - otherwise, if at least one blocker is pending, the transaction is pending + /// - otherwise, all blockers are released, and the transaction is also released + pub(crate) fn state(&self) -> BlockerState { + use BlockerState::*; + self.blockers + .iter() + .fold(Released, |acc, blocker| match (acc, blocker.state()) { + (Cancelled, _) | (_, Cancelled) => Cancelled, + (Pending, _) | (_, Pending) => Pending, + (Released, Released) => Released, + }) + } + + pub(crate) fn apply(self) { + for (surface, id) in self.surfaces { + PrivateSurfaceData::with_states(&surface, |states| { + states.cached_state.apply_state(id); + }) + } + } +} + +// This queue should be per-client +pub(crate) struct TransactionQueue { + transactions: Vec, + // we keep the hashset around to reuse allocations + seen_surfaces: HashSet, +} + +impl Default for TransactionQueue { + fn default() -> Self { + TransactionQueue { + transactions: Vec::new(), + seen_surfaces: HashSet::new(), + } + } +} + +impl TransactionQueue { + pub(crate) fn append(&mut self, t: Transaction) { + self.transactions.push(t); + } + + pub(crate) fn apply_ready(&mut self) { + // this is a very non-optimized implementation + // we just iterate over the queue of transactions, keeping track of which + // surface we have seen as they encode transaction dependencies + self.seen_surfaces.clear(); + // manually iterate as we're going to modify the Vec while iterating on it + let mut i = 0; + // the loop will terminate, as at every iteration either i is incremented by 1 + // or the lenght of self.transactions is reduced by 1. + while i <= self.transactions.len() { + let mut skip = false; + // does the transaction have any active blocker? + match self.transactions[i].state() { + BlockerState::Cancelled => { + // this transaction is cancelled, remove it without further processing + self.transactions.remove(i); + continue; + } + BlockerState::Pending => { + skip = true; + } + BlockerState::Released => {} + } + // if not, does this transaction depend on any previous transaction? + if !skip { + for (s, _) in &self.transactions[i].surfaces { + if !s.as_ref().is_alive() { + continue; + } + if self.seen_surfaces.contains(&s.as_ref().id()) { + skip = true; + break; + } + } + } + + if skip { + // this transaction is not yet ready and should be skipped, add its surfaces to our + // seen list + for (s, _) in &self.transactions[i].surfaces { + if !s.as_ref().is_alive() { + continue; + } + self.seen_surfaces.insert(s.as_ref().id()); + } + i += 1; + } else { + // this transaction is to be applied, yay! + self.transactions.remove(i).apply(); + } + } + } +} diff --git a/src/wayland/compositor/tree.rs b/src/wayland/compositor/tree.rs index ad2b1db..6c2e841 100644 --- a/src/wayland/compositor/tree.rs +++ b/src/wayland/compositor/tree.rs @@ -1,7 +1,14 @@ -use super::{roles::*, SubsurfaceRole, SurfaceAttributes}; -use std::sync::Mutex; +use crate::wayland::Serial; + +use super::{ + cache::MultiCache, get_children, handlers::is_effectively_sync, transaction::PendingTransaction, + SurfaceData, +}; +use std::sync::{atomic::Ordering, Mutex}; use wayland_server::protocol::wl_surface::WlSurface; +pub(crate) static SUBSURFACE_ROLE: &str = "subsurface"; + /// Node of a subsurface tree, holding some user specified data type U /// at each node /// @@ -15,13 +22,31 @@ use wayland_server::protocol::wl_surface::WlSurface; /// /// Each node also appears within its children list, to allow relative placement /// between them. -pub struct SurfaceData { +pub struct PrivateSurfaceData { parent: Option, children: Vec, - role: R, - attributes: SurfaceAttributes, + public_data: SurfaceData, + pending_transaction: PendingTransaction, + current_txid: Serial, + commit_hooks: Vec, } +/// An error type signifying that the surface already has a role and +/// cannot be assigned an other +/// +/// Generated if you attempt a role operation on a surface that does +/// not have the role you asked for. +#[derive(Debug)] +pub struct AlreadyHasRole; + +impl std::fmt::Display for AlreadyHasRole { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Surface already has a role.") + } +} + +impl std::error::Error for AlreadyHasRole {} + pub enum Location { Before, After, @@ -38,27 +63,28 @@ pub enum TraversalAction { Break, } -impl SurfaceData { - pub fn new() -> Mutex> { - Mutex::new(SurfaceData { +impl PrivateSurfaceData { + pub fn new() -> Mutex { + Mutex::new(PrivateSurfaceData { parent: None, children: vec![], - role: Default::default(), - attributes: Default::default(), + public_data: SurfaceData { + role: Default::default(), + data_map: Default::default(), + cached_state: MultiCache::new(), + }, + pending_transaction: Default::default(), + current_txid: Serial(0), + commit_hooks: Vec::new(), }) } -} -impl SurfaceData -where - R: 'static, -{ /// Initializes the surface, must be called at creation for state coherence pub fn init(surface: &WlSurface) { let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut my_data = my_data_mutex.lock().unwrap(); debug_assert!(my_data.children.is_empty()); @@ -70,7 +96,7 @@ where let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut my_data = my_data_mutex.lock().unwrap(); if let Some(old_parent) = my_data.parent.take() { @@ -78,7 +104,7 @@ where let old_parent_mutex = old_parent .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut old_parent_guard = old_parent_mutex.lock().unwrap(); old_parent_guard @@ -87,7 +113,11 @@ where } // orphan all our children for child in &my_data.children { - let child_mutex = child.as_ref().user_data().get::>>().unwrap(); + let child_mutex = child + .as_ref() + .user_data() + .get::>() + .unwrap(); if std::ptr::eq(child_mutex, my_data_mutex) { // This child is ourselves, don't do anything. continue; @@ -97,108 +127,119 @@ where child_guard.parent = None; } } -} -impl SurfaceData { - pub fn has_a_role(surface: &WlSurface) -> bool { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface + pub fn set_role(surface: &WlSurface, role: &'static str) -> Result<(), AlreadyHasRole> { + let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); - let data_guard = data_mutex.lock().unwrap(); - ::has_role(&data_guard.role) + let mut my_data = my_data_mutex.lock().unwrap(); + if my_data.public_data.role.is_some() { + return Err(AlreadyHasRole); + } + my_data.public_data.role = Some(role); + Ok(()) } - /// Check whether a surface has a given role - pub fn has_role(surface: &WlSurface) -> bool - where - R: Role, - { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface + pub fn get_role(surface: &WlSurface) -> Option<&'static str> { + let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); - let data_guard = data_mutex.lock().unwrap(); - >::has(&data_guard.role) + let my_data = my_data_mutex.lock().unwrap(); + my_data.public_data.role } - /// Register that this surface has a role, fails if it already has one - pub fn give_role(surface: &WlSurface) -> Result<(), AlreadyHasRole> - where - R: Role, - RoleData: Default, - { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface + pub fn with_states T>(surface: &WlSurface, f: F) -> T { + let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); - let mut data_guard = data_mutex.lock().unwrap(); - >::set(&mut data_guard.role) + let my_data = my_data_mutex.lock().unwrap(); + f(&my_data.public_data) } - /// Register that this surface has a role with given data - /// - /// Fails if it already has one and returns the data - pub fn give_role_with(surface: &WlSurface, data: RoleData) -> Result<(), RoleData> - where - R: Role, - { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface + pub fn add_commit_hook(surface: &WlSurface, hook: fn(&WlSurface)) { + let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); - let mut data_guard = data_mutex.lock().unwrap(); - >::set_with(&mut data_guard.role, data) + let mut my_data = my_data_mutex.lock().unwrap(); + my_data.commit_hooks.push(hook); } - /// Register that this surface has no role and returns the data - /// - /// It is a noop if this surface already didn't have one, but fails if - /// the role was "subsurface", it must be removed by the `unset_parent` method. - pub fn remove_role(surface: &WlSurface) -> Result - where - R: Role, - { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface + pub fn invoke_commit_hooks(surface: &WlSurface) { + // don't hold the mutex while the hooks are invoked + let hooks = { + let my_data_mutex = surface + .as_ref() + .user_data() + .get::>() + .unwrap(); + let my_data = my_data_mutex.lock().unwrap(); + my_data.commit_hooks.clone() + }; + for hook in hooks { + hook(surface); + } + } + + pub fn commit(surface: &WlSurface) { + let is_sync = is_effectively_sync(surface); + let children = get_children(surface); + let my_data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); - let mut data_guard = data_mutex.lock().unwrap(); - >::unset(&mut data_guard.role) + let mut my_data = my_data_mutex.lock().unwrap(); + // commit our state + let current_txid = my_data.current_txid; + my_data.public_data.cached_state.commit(Some(current_txid)); + // take all our children state into our pending transaction + for child in children { + let child_data_mutex = child + .as_ref() + .user_data() + .get::>() + .unwrap(); + // if the child is effectively sync, take its state + // this is the case if either we are effectively sync, or the child is explicitly sync + let mut child_data = child_data_mutex.lock().unwrap(); + let is_child_sync = || { + child_data + .public_data + .data_map + .get::() + .map(|s| s.sync.load(Ordering::Acquire)) + .unwrap_or(false) + }; + if is_sync || is_child_sync() { + let child_tx = std::mem::take(&mut child_data.pending_transaction); + child_tx.merge_into(&my_data.pending_transaction); + child_data.current_txid.0 = child_data.current_txid.0.wrapping_add(1); + } + } + my_data + .pending_transaction + .insert_state(surface.clone(), my_data.current_txid); + if !is_sync { + // if we are not sync, apply the transaction immediately + let tx = std::mem::take(&mut my_data.pending_transaction); + // release the mutex, as applying the transaction will try to lock it + std::mem::drop(my_data); + // apply the transaction + tx.finalize().apply(); + } } - /// Access to the role data - pub fn with_role_data(surface: &WlSurface, f: F) -> Result - where - R: Role, - F: FnOnce(&mut RoleData) -> T, - { - debug_assert!(surface.as_ref().is_alive()); - let data_mutex = surface - .as_ref() - .user_data() - .get::>>() - .unwrap(); - let mut data_guard = data_mutex.lock().unwrap(); - let data = >::data_mut(&mut data_guard.role)?; - Ok(f(data)) - } -} - -impl + 'static> SurfaceData { /// Checks if the first surface is an ancestor of the second pub fn is_ancestor(a: &WlSurface, b: &WlSurface) -> bool { - let b_mutex = b.as_ref().user_data().get::>>().unwrap(); + let b_mutex = b.as_ref().user_data().get::>().unwrap(); let b_guard = b_mutex.lock().unwrap(); if let Some(ref parent) = b_guard.parent { if parent.as_ref().equals(a.as_ref()) { @@ -225,10 +266,18 @@ impl + 'static> SurfaceData { // change child's parent { - let child_mutex = child.as_ref().user_data().get::>>().unwrap(); + let child_mutex = child + .as_ref() + .user_data() + .get::>() + .unwrap(); let mut child_guard = child_mutex.lock().unwrap(); // if surface already has a role, it cannot become a subsurface - >::set(&mut child_guard.role)?; + if child_guard.public_data.role.is_some() && child_guard.public_data.role != Some(SUBSURFACE_ROLE) + { + return Err(AlreadyHasRole); + } + child_guard.public_data.role = Some(SUBSURFACE_ROLE); debug_assert!(child_guard.parent.is_none()); child_guard.parent = Some(parent.clone()); } @@ -237,7 +286,7 @@ impl + 'static> SurfaceData { let parent_mutex = parent .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut parent_guard = parent_mutex.lock().unwrap(); parent_guard.children.push(child.clone()) @@ -251,22 +300,20 @@ impl + 'static> SurfaceData { pub fn unset_parent(child: &WlSurface) { debug_assert!(child.as_ref().is_alive()); let old_parent = { - let child_mutex = child.as_ref().user_data().get::>>().unwrap(); + let child_mutex = child + .as_ref() + .user_data() + .get::>() + .unwrap(); let mut child_guard = child_mutex.lock().unwrap(); - let old_parent = child_guard.parent.take(); - if old_parent.is_some() { - // We had a parent, so this does not have a role any more - >::unset(&mut child_guard.role) - .expect("Surface had a parent but not the subsurface role?!"); - } - old_parent + child_guard.parent.take() }; // unregister from our parent if let Some(old_parent) = old_parent { let parent_mutex = old_parent .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut parent_guard = parent_mutex.lock().unwrap(); parent_guard @@ -277,7 +324,11 @@ impl + 'static> SurfaceData { /// Retrieve the parent surface (if any) of this surface pub fn get_parent(child: &WlSurface) -> Option { - let child_mutex = child.as_ref().user_data().get::>>().unwrap(); + let child_mutex = child + .as_ref() + .user_data() + .get::>() + .unwrap(); let child_guard = child_mutex.lock().unwrap(); child_guard.parent.as_ref().cloned() } @@ -287,7 +338,7 @@ impl + 'static> SurfaceData { let parent_mutex = parent .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let parent_guard = parent_mutex.lock().unwrap(); parent_guard @@ -306,7 +357,7 @@ impl + 'static> SurfaceData { let data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let data_guard = data_mutex.lock().unwrap(); data_guard.parent.as_ref().cloned().unwrap() @@ -324,7 +375,7 @@ impl + 'static> SurfaceData { let parent_mutex = parent .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut parent_guard = parent_mutex.lock().unwrap(); let my_index = index_of(surface, &parent_guard.children).unwrap(); @@ -345,24 +396,7 @@ impl + 'static> SurfaceData { } } -impl SurfaceData { - /// Access the attributes associated with a surface - /// - /// Note that an internal lock is taken during access of this data, - /// so the tree cannot be manipulated at the same time - pub fn with_data(surface: &WlSurface, f: F) -> T - where - F: FnOnce(&mut SurfaceAttributes) -> T, - { - let data_mutex = surface - .as_ref() - .user_data() - .get::>>() - .expect("Accessing the data of foreign surfaces is not supported."); - let mut data_guard = data_mutex.lock().unwrap(); - f(&mut data_guard.attributes) - } - +impl PrivateSurfaceData { /// Access sequentially the attributes associated with a surface tree, /// in a depth-first order. /// @@ -384,9 +418,9 @@ impl SurfaceData { mut post_filter: F3, reverse: bool, ) where - F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, - F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), - F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, + F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction, + F2: FnMut(&WlSurface, &SurfaceData, &T), + F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool, { Self::map( surface, @@ -408,25 +442,25 @@ impl SurfaceData { reverse: bool, ) -> bool where - F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction, - F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), - F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, + F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction, + F2: FnMut(&WlSurface, &SurfaceData, &T), + F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool, { let data_mutex = surface .as_ref() .user_data() - .get::>>() + .get::>() .unwrap(); let mut data_guard = data_mutex.lock().unwrap(); let data_guard = &mut *data_guard; // call the filter on ourselves - match filter(surface, &mut data_guard.attributes, &mut data_guard.role, initial) { + match filter(surface, &data_guard.public_data, initial) { TraversalAction::DoChildren(t) => { // loop over children if reverse { for c in data_guard.children.iter().rev() { if c.as_ref().equals(surface.as_ref()) { - processor(surface, &mut data_guard.attributes, &mut data_guard.role, initial); + processor(surface, &data_guard.public_data, initial); } else if !Self::map(c, &t, filter, processor, post_filter, true) { return false; } @@ -434,17 +468,17 @@ impl SurfaceData { } else { for c in &data_guard.children { if c.as_ref().equals(surface.as_ref()) { - processor(surface, &mut data_guard.attributes, &mut data_guard.role, initial); + processor(surface, &data_guard.public_data, initial); } else if !Self::map(c, &t, filter, processor, post_filter, false) { return false; } } } - post_filter(surface, &mut data_guard.attributes, &mut data_guard.role, initial) + post_filter(surface, &data_guard.public_data, initial) } TraversalAction::SkipChildren => { // still process ourselves - processor(surface, &mut data_guard.attributes, &mut data_guard.role, initial); + processor(surface, &data_guard.public_data, initial); true } TraversalAction::Break => false, diff --git a/src/wayland/data_device/dnd_grab.rs b/src/wayland/data_device/dnd_grab.rs index 9a39bf1..26c5de8 100644 --- a/src/wayland/data_device/dnd_grab.rs +++ b/src/wayland/data_device/dnd_grab.rs @@ -6,14 +6,13 @@ use wayland_server::{ }; use crate::wayland::{ - compositor::{roles::Role, CompositorToken}, seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, Serial, }; -use super::{with_source_metadata, DataDeviceData, DnDIconRole, SeatData}; +use super::{with_source_metadata, DataDeviceData, SeatData}; -pub(crate) struct DnDGrab { +pub(crate) struct DnDGrab { start_data: GrabStartData, data_source: Option, current_focus: Option, @@ -22,20 +21,18 @@ pub(crate) struct DnDGrab { icon: Option, origin: wl_surface::WlSurface, callback: Rc>, - token: CompositorToken, seat: Seat, } -impl + 'static> DnDGrab { +impl DnDGrab { pub(crate) fn new( start_data: GrabStartData, source: Option, origin: wl_surface::WlSurface, seat: Seat, icon: Option, - token: CompositorToken, callback: Rc>, - ) -> DnDGrab { + ) -> DnDGrab { DnDGrab { start_data, data_source: source, @@ -45,13 +42,12 @@ impl + 'static> DnDGrab { origin, icon, callback, - token, seat, } } } -impl + 'static> PointerGrab for DnDGrab { +impl PointerGrab for DnDGrab { fn motion( &mut self, _handle: &mut PointerInnerHandle<'_>, @@ -211,11 +207,7 @@ impl + 'static> PointerGrab for DnDGrab { } } (&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped); - if let Some(icon) = self.icon.take() { - if icon.as_ref().is_alive() { - self.token.remove_role::(&icon).unwrap(); - } - } + self.icon = None; // in all cases abandon the drop // no more buttons are pressed, release the grab handle.unset_grab(serial, time); diff --git a/src/wayland/data_device/mod.rs b/src/wayland/data_device/mod.rs index 1baeffb..5f5c938 100644 --- a/src/wayland/data_device/mod.rs +++ b/src/wayland/data_device/mod.rs @@ -25,30 +25,22 @@ //! - the freestanding function [`start_dnd`] allows you to initiate a drag'n'drop event from the compositor //! itself and receive interactions of clients with it via an other dedicated callback. //! -//! The module also defines the `DnDIconRole` that you need to insert into your compositor roles enum, to -//! represent surfaces that are used as a DnD icon. +//! The module defines the role `"dnd_icon"` that is assigned to surfaces used as drag'n'drop icons. //! //! ## Initialization //! //! ``` //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; //! use smithay::wayland::data_device::{init_data_device, default_action_chooser, DnDIconRole}; //! # use smithay::wayland::compositor::compositor_init; //! -//! // You need to insert the `DndIconRole` into your roles, to handle requests from clients -//! // to set a surface as a dnd icon -//! define_roles!(Roles => [DnDIcon, DnDIconRole]); -//! //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = compositor_init::(&mut display, |_, _, _, _| {}, None); //! // init the data device: //! init_data_device( //! &mut display, // the display //! |dnd_event| { /* a callback to react to client DnD/selection actions */ }, //! default_action_chooser, // a closure to choose the DnD action depending on clients //! // negociation -//! compositor_token.clone(), // a compositor token //! None // insert a logger here //! ); //! ``` @@ -65,7 +57,7 @@ use wayland_server::{ }; use crate::wayland::{ - compositor::{roles::Role, CompositorToken}, + compositor, seat::{GrabStartData, Seat}, Serial, }; @@ -77,6 +69,8 @@ mod server_dnd_grab; pub use self::data_source::{with_source_metadata, SourceMetadata}; pub use self::server_dnd_grab::ServerDndEvent; +static DND_ICON_ROLE: &str = "dnd_icon"; + /// Events that are generated by interactions of the clients with the data device #[derive(Debug)] pub enum DataDeviceEvent { @@ -275,17 +269,15 @@ impl SeatData { /// and the second argument is the preferred action reported by the target. If no action should be /// chosen (and thus the drag'n'drop should abort on drop), return /// [`DndAction::empty()`](wayland_server::protocol::wl_data_device_manager::DndAction::empty). -pub fn init_data_device( +pub fn init_data_device( display: &mut Display, callback: C, action_choice: F, - token: CompositorToken, logger: L, ) -> Global where F: FnMut(DndAction, DndAction) -> DndAction + 'static, C: FnMut(DataDeviceEvent) + 'static, - R: Role + 'static, L: Into>, { let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "data_device_mgr")); @@ -294,7 +286,7 @@ where display.create_global( 3, Filter::new(move |(ddm, _version), _, _| { - implement_ddm(ddm, callback.clone(), action_choice.clone(), token, log.clone()); + implement_ddm(ddm, callback.clone(), action_choice.clone(), log.clone()); }), ) } @@ -371,17 +363,15 @@ pub fn start_dnd( } } -fn implement_ddm( +fn implement_ddm( ddm: Main, callback: Rc>, action_choice: Rc>, - token: CompositorToken, log: ::slog::Logger, ) -> wl_data_device_manager::WlDataDeviceManager where F: FnMut(DndAction, DndAction) -> DndAction + 'static, C: FnMut(DataDeviceEvent) + 'static, - R: Role + 'static, { use self::wl_data_device_manager::Request; ddm.quick_assign(move |_ddm, req, _data| match req { @@ -399,7 +389,6 @@ where seat.clone(), callback.clone(), action_choice.clone(), - token, log.clone(), ); seat_data.borrow_mut().known_devices.push(data_device); @@ -419,18 +408,16 @@ struct DataDeviceData { action_choice: Rc DndAction + 'static>>, } -fn implement_data_device( +fn implement_data_device( dd: Main, seat: Seat, callback: Rc>, action_choice: Rc>, - token: CompositorToken, log: ::slog::Logger, ) -> wl_data_device::WlDataDevice where F: FnMut(DndAction, DndAction) -> DndAction + 'static, C: FnMut(DataDeviceEvent) + 'static, - R: Role + 'static, { use self::wl_data_device::Request; let dd_data = DataDeviceData { @@ -449,7 +436,7 @@ where if let Some(pointer) = seat.get_pointer() { if pointer.has_grab(serial) { if let Some(ref icon) = icon { - if token.give_role::(icon).is_err() { + if compositor::give_role(icon, DND_ICON_ROLE).is_err() { dd.as_ref().post_error( wl_data_device::Error::Role as u32, "Given surface already has an other role".into(), @@ -470,7 +457,6 @@ where origin, seat.clone(), icon, - token, callback.clone(), ), serial, diff --git a/src/wayland/dmabuf/mod.rs b/src/wayland/dmabuf/mod.rs index df36991..48811f6 100644 --- a/src/wayland/dmabuf/mod.rs +++ b/src/wayland/dmabuf/mod.rs @@ -265,7 +265,7 @@ where format, DmabufFlags::from_bits_truncate(flags), ); - let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new()); + let planes = std::mem::take(&mut self.pending_planes); for (i, plane) in planes.into_iter().enumerate() { let offset = plane.offset; let stride = plane.stride; @@ -355,7 +355,7 @@ where format, DmabufFlags::from_bits_truncate(flags), ); - let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new()); + let planes = ::std::mem::take(&mut self.pending_planes); for (i, plane) in planes.into_iter().enumerate() { let offset = plane.offset; let stride = plane.stride; diff --git a/src/wayland/explicit_synchronization/mod.rs b/src/wayland/explicit_synchronization/mod.rs index dc09fa2..efecb67 100644 --- a/src/wayland/explicit_synchronization/mod.rs +++ b/src/wayland/explicit_synchronization/mod.rs @@ -21,60 +21,32 @@ //! //! ``` //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! # -//! # use smithay::wayland::compositor::roles::*; -//! # use smithay::wayland::compositor::CompositorToken; //! use smithay::wayland::explicit_synchronization::*; -//! # define_roles!(MyRoles); -//! # //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::( -//! # &mut display, -//! # |_, _, _, _| {}, -//! # None -//! # ); //! init_explicit_synchronization_global( //! &mut display, -//! compositor_token, //! None /* You can insert a logger here */ //! ); //! ``` //! -//! Then when handling a surface commit, you can retrieve the synchronization information for the surface -//! data: -//! ```no_run +//! Then when handling a surface commit, you can retrieve the synchronization information for the surface states: +//! ``` //! # extern crate wayland_server; //! # #[macro_use] extern crate smithay; //! # //! # use wayland_server::protocol::wl_surface::WlSurface; -//! # use smithay::wayland::compositor::CompositorToken; //! # use smithay::wayland::explicit_synchronization::*; //! # -//! # fn dummy_function(surface: &WlSurface, compositor_token: CompositorToken) { -//! compositor_token.with_surface_data(&surface, |surface_attributes| { -//! // While you retrieve the surface data from the commit ... -//! // Check the explicit synchronization data: -//! match get_explicit_synchronization_state(surface_attributes) { -//! Ok(sync_state) => { -//! /* This surface is explicitly synchronized, you need to handle -//! the contents of sync_state -//! */ -//! }, -//! Err(NoExplicitSync) => { -//! /* This surface is not explicitly synchronized, nothing more to do -//! */ -//! } -//! } +//! # fn dummy_function(surface: &WlSurface) { +//! use smithay::wayland::compositor::with_states; +//! with_states(&surface, |states| { +//! let explicit_sync_state = states.cached_state.current::(); +//! /* process the explicit_sync_state */ //! }); //! # } //! ``` -use std::{ - cell::RefCell, - ops::{Deref as _, DerefMut as _}, - os::unix::io::RawFd, -}; +use std::{cell::RefCell, ops::Deref as _, os::unix::io::RawFd}; use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{ zwp_linux_buffer_release_v1::ZwpLinuxBufferReleaseV1, @@ -83,7 +55,7 @@ use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{ }; use wayland_server::{protocol::wl_surface::WlSurface, Display, Filter, Global, Main}; -use crate::wayland::compositor::{CompositorToken, SurfaceAttributes}; +use super::compositor::{with_states, Cacheable, SurfaceData}; /// An object to signal end of use of a buffer #[derive(Debug)] @@ -112,6 +84,9 @@ impl ExplicitBufferRelease { /// The client is not required to fill both. `acquire` being `None` means that you don't need to wait /// before acessing the buffer, `release` being `None` means that the client does not require additionnal /// signaling that you are finished (you still need to send `wl_buffer.release`). +/// +/// When processing the current state, the whould [`Option::take`] the values from it. Otherwise they'll +/// be treated as unused and released when overwritten by the next client commit. #[derive(Debug)] pub struct ExplicitSyncState { /// An acquire `dma_fence` object, that you should wait on before accessing the contents of the @@ -122,26 +97,37 @@ pub struct ExplicitSyncState { pub release: Option, } -struct InternalState { - sync_state: ExplicitSyncState, - sync_resource: ZwpLinuxSurfaceSynchronizationV1, +impl Default for ExplicitSyncState { + fn default() -> Self { + ExplicitSyncState { + acquire: None, + release: None, + } + } +} + +impl Cacheable for ExplicitSyncState { + fn commit(&mut self) -> Self { + std::mem::take(self) + } + fn merge_into(mut self, into: &mut Self) { + if self.acquire.is_some() { + if let Some(fd) = std::mem::replace(&mut into.acquire, self.acquire.take()) { + // close the unused fd + let _ = nix::unistd::close(fd); + } + } + if self.release.is_some() { + if let Some(release) = std::mem::replace(&mut into.release, self.release.take()) { + // release the overriden state + release.immediate_release(); + } + } + } } struct ESUserData { - state: RefCell>, -} - -impl ESUserData { - fn take_state(&self) -> Option { - if let Some(state) = self.state.borrow_mut().deref_mut() { - Some(ExplicitSyncState { - acquire: state.sync_state.acquire.take(), - release: state.sync_state.release.take(), - }) - } else { - None - } - } + state: RefCell>, } /// Possible errors you can send to an ill-behaving clients @@ -167,42 +153,24 @@ impl std::fmt::Display for NoExplicitSync { impl std::error::Error for NoExplicitSync {} -/// Retrieve the explicit synchronization state commited by the client -/// -/// This state can contain an acquire fence and a release object, for synchronization (see module-level docs). -/// -/// This function will clear the pending state, preparing the surface for the next commit, as a result you -/// should always call it on surface commit to avoid getting out-of-sync with the client. -/// -/// This function returns an error if the client has not setup explicit synchronization for this surface. -pub fn get_explicit_synchronization_state( - attrs: &mut SurfaceAttributes, -) -> Result { - attrs - .user_data - .get::() - .and_then(|s| s.take_state()) - .ok_or(NoExplicitSync) -} - /// Send a synchronization error to a client /// /// See the enum definition for possible errors. These errors are protocol errors, meaning that /// the client associated with this `SurfaceAttributes` will be killed as a result of calling this /// function. -pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: ExplicitSyncError) { - if let Some(ref data) = attrs.user_data.get::() { - if let Some(state) = data.state.borrow().deref() { +pub fn send_explicit_synchronization_error(attrs: &SurfaceData, error: ExplicitSyncError) { + if let Some(ref data) = attrs.data_map.get::() { + if let Some(sync_resource) = data.state.borrow().deref() { match error { - ExplicitSyncError::InvalidFence => state.sync_resource.as_ref().post_error( + ExplicitSyncError::InvalidFence => sync_resource.as_ref().post_error( zwp_linux_surface_synchronization_v1::Error::InvalidFence as u32, "The fence specified by the client could not be imported.".into(), ), - ExplicitSyncError::UnsupportedBuffer => state.sync_resource.as_ref().post_error( + ExplicitSyncError::UnsupportedBuffer => sync_resource.as_ref().post_error( zwp_linux_surface_synchronization_v1::Error::UnsupportedBuffer as u32, "The buffer does not support explicit synchronization.".into(), ), - ExplicitSyncError::NoBuffer => state.sync_resource.as_ref().post_error( + ExplicitSyncError::NoBuffer => sync_resource.as_ref().post_error( zwp_linux_surface_synchronization_v1::Error::NoBuffer as u32, "No buffer was attached.".into(), ), @@ -214,14 +182,12 @@ pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: Exp /// Initialize the explicit synchronization global /// /// See module-level documentation for its use. -pub fn init_explicit_synchronization_global( +pub fn init_explicit_synchronization_global( display: &mut Display, - compositor: CompositorToken, logger: L, ) -> Global where L: Into>, - R: 'static, { let _log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "wayland_explicit_synchronization")); @@ -236,16 +202,17 @@ where surface, } = req { - let exists = compositor.with_surface_data(&surface, |attrs| { - attrs.user_data.insert_if_missing(|| ESUserData { + let exists = with_states(&surface, |states| { + states.data_map.insert_if_missing(|| ESUserData { state: RefCell::new(None), }); - attrs - .user_data + states + .data_map .get::() .map(|ud| ud.state.borrow().is_some()) .unwrap() - }); + }) + .unwrap_or(false); if exists { explicit_sync.as_ref().post_error( zwp_linux_explicit_synchronization_v1::Error::SynchronizationExists as u32, @@ -253,17 +220,12 @@ where ); return; } - let surface_sync = implement_surface_sync(id, surface.clone(), compositor); - compositor.with_surface_data(&surface, |attrs| { - let data = attrs.user_data.get::().unwrap(); - *data.state.borrow_mut() = Some(InternalState { - sync_state: ExplicitSyncState { - acquire: None, - release: None, - }, - sync_resource: surface_sync, - }); - }); + let surface_sync = implement_surface_sync(id, surface.clone()); + with_states(&surface, |states| { + let data = states.data_map.get::().unwrap(); + *data.state.borrow_mut() = Some(surface_sync); + }) + .unwrap(); } }); }, @@ -271,14 +233,10 @@ where ) } -fn implement_surface_sync( +fn implement_surface_sync( id: Main, surface: WlSurface, - compositor: CompositorToken, -) -> ZwpLinuxSurfaceSynchronizationV1 -where - R: 'static, -{ +) -> ZwpLinuxSurfaceSynchronizationV1 { id.quick_assign(move |surface_sync, req, _| match req { zwp_linux_surface_synchronization_v1::Request::SetAcquireFence { fd } => { if !surface.as_ref().is_alive() { @@ -287,19 +245,18 @@ where "The associated wl_surface was destroyed.".into(), ) } - compositor.with_surface_data(&surface, |attrs| { - let data = attrs.user_data.get::().unwrap(); - if let Some(state) = data.state.borrow_mut().deref_mut() { - if state.sync_state.acquire.is_some() { - surface_sync.as_ref().post_error( - zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32, - "Multiple fences added for a single surface commit.".into(), - ) - } else { - state.sync_state.acquire = Some(fd); - } + with_states(&surface, |states| { + let mut pending = states.cached_state.pending::(); + if pending.acquire.is_some() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32, + "Multiple fences added for a single surface commit.".into(), + ) + } else { + pending.acquire = Some(fd); } - }); + }) + .unwrap(); } zwp_linux_surface_synchronization_v1::Request::GetRelease { release } => { if !surface.as_ref().is_alive() { @@ -308,30 +265,30 @@ where "The associated wl_surface was destroyed.".into(), ) } - compositor.with_surface_data(&surface, |attrs| { - let data = attrs.user_data.get::().unwrap(); - if let Some(state) = data.state.borrow_mut().deref_mut() { - if state.sync_state.acquire.is_some() { - surface_sync.as_ref().post_error( - zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32, - "Multiple releases added for a single surface commit.".into(), - ) - } else { - release.quick_assign(|_, _, _| {}); - state.sync_state.release = Some(ExplicitBufferRelease { - release: release.deref().clone(), - }); - } + with_states(&surface, |states| { + let mut pending = states.cached_state.pending::(); + if pending.release.is_some() { + surface_sync.as_ref().post_error( + zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32, + "Multiple releases added for a single surface commit.".into(), + ) + } else { + release.quick_assign(|_, _, _| {}); + pending.release = Some(ExplicitBufferRelease { + release: release.deref().clone(), + }); } - }); + }) + .unwrap(); } zwp_linux_surface_synchronization_v1::Request::Destroy => { // disable the ESUserData - compositor.with_surface_data(&surface, |attrs| { - if let Some(ref mut data) = attrs.user_data.get::() { + with_states(&surface, |states| { + if let Some(ref mut data) = states.data_map.get::() { *data.state.borrow_mut() = None; } - }); + }) + .unwrap(); } _ => (), }); diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 6a3e0b5..9cc202b 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -225,7 +225,7 @@ impl Output { pub fn set_preferred(&self, mode: Mode) { let mut inner = self.inner.lock().unwrap(); inner.preferred_mode = Some(mode); - if inner.modes.iter().find(|&m| *m == mode).is_none() { + if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); } } @@ -233,7 +233,7 @@ impl Output { /// Adds a mode to the list of known modes to this output pub fn add_mode(&self, mode: Mode) { let mut inner = self.inner.lock().unwrap(); - if inner.modes.iter().find(|&m| *m == mode).is_none() { + if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); } } @@ -270,7 +270,7 @@ impl Output { ) { let mut inner = self.inner.lock().unwrap(); if let Some(mode) = new_mode { - if inner.modes.iter().find(|&m| *m == mode).is_none() { + if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); } inner.current_mode = new_mode; diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 447d93f..b9ddfca 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -9,21 +9,13 @@ //! //! ``` //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! use smithay::wayland::seat::{Seat, CursorImageRole}; -//! # use smithay::wayland::compositor::compositor_init; -//! -//! // You need to insert the `CursorImageRole` into your roles, to handle requests from clients -//! // to set a surface as a cursor image -//! define_roles!(Roles => [CursorImage, CursorImageRole]); +//! use smithay::wayland::seat::Seat; //! //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = compositor_init::(&mut display, |_, _, _, _| {}, None); //! // insert the seat: //! let (seat, seat_global) = Seat::new( //! &mut display, // the display //! "seat-0".into(), // the name of the seat, will be advertized to clients -//! compositor_token.clone(), // the compositor token //! None // insert a logger here //! ); //! ``` @@ -32,13 +24,15 @@ //! //! Once the seat is initialized, you can add capabilities to it. //! -//! Currently, only pointer and keyboard capabilities are supported by -//! smithay. +//! Currently, only pointer and keyboard capabilities are supported by smithay. //! //! You can add these capabilities via methods of the [`Seat`] struct: -//! [`add_keyboard`](Seat::add_keyboard), [`add_pointer`](Seat::add_pointer). +//! [`Seat::add_keyboard`] and [`Seat::add_pointer`]. //! These methods return handles that can be cloned and sent across thread, so you can keep one around //! in your event-handling code to forward inputs to your clients. +//! +//! This module further defines the `"cursor_image"` role, that is assigned to surfaces used by clients +//! to change the cursor icon. use std::{cell::RefCell, fmt, ops::Deref as _, rc::Rc}; @@ -48,13 +42,11 @@ mod pointer; pub use self::{ keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig}, pointer::{ - AxisFrame, CursorImageRole, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, + AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, PointerInnerHandle, }, }; -use crate::wayland::compositor::{roles::Role, CompositorToken}; - use wayland_server::{ protocol::{wl_seat, wl_surface}, Display, Filter, Global, Main, UserDataMap, @@ -130,14 +122,8 @@ impl Seat { /// You are provided with the state token to retrieve it (allowing /// you to add or remove capabilities from it), and the global handle, /// in case you want to remove it. - pub fn new( - display: &mut Display, - name: String, - token: CompositorToken, - logger: L, - ) -> (Seat, Global) + pub fn new(display: &mut Display, name: String, logger: L) -> (Seat, Global) where - R: Role + 'static, L: Into>, { let log = crate::slog_or_fallback(logger); @@ -155,7 +141,7 @@ impl Seat { let global = display.create_global( 5, Filter::new(move |(new_seat, _version), _, _| { - let seat = implement_seat(new_seat, arc.clone(), token); + let seat = implement_seat(new_seat, arc.clone()); let mut inner = arc.inner.borrow_mut(); if seat.as_ref().version() >= 2 { seat.name(arc.name.clone()); @@ -197,32 +183,25 @@ impl Seat { /// /// ``` /// # extern crate wayland_server; - /// # #[macro_use] extern crate smithay; /// # - /// # use smithay::wayland::{seat::{Seat, CursorImageRole}, compositor::compositor_init}; - /// # - /// # define_roles!(Roles => [CursorImage, CursorImageRole]); + /// # use smithay::wayland::seat::Seat; /// # /// # let mut display = wayland_server::Display::new(); - /// # let (compositor_token, _, _) = compositor_init::(&mut display, |_, _, _, _| {}, None); /// # let (mut seat, seat_global) = Seat::new( /// # &mut display, /// # "seat-0".into(), - /// # compositor_token.clone(), /// # None /// # ); /// let pointer_handle = seat.add_pointer( - /// compositor_token.clone(), /// |new_status| { /* a closure handling requests from clients tot change the cursor icon */ } /// ); /// ``` - pub fn add_pointer(&mut self, token: CompositorToken, cb: F) -> PointerHandle + pub fn add_pointer(&mut self, cb: F) -> PointerHandle where - R: Role + 'static, F: FnMut(CursorImageStatus) + 'static, { let mut inner = self.arc.inner.borrow_mut(); - let pointer = self::pointer::create_pointer_handler(token, cb); + let pointer = self::pointer::create_pointer_handler(cb); if inner.pointer.is_some() { // there is already a pointer, remove it and notify the clients // of the change @@ -344,21 +323,14 @@ impl ::std::cmp::PartialEq for Seat { } } -fn implement_seat( - seat: Main, - arc: Rc, - token: CompositorToken, -) -> wl_seat::WlSeat -where - R: Role + 'static, -{ +fn implement_seat(seat: Main, arc: Rc) -> wl_seat::WlSeat { let dest_arc = arc.clone(); seat.quick_assign(move |seat, request, _| { let arc = seat.as_ref().user_data().get::>().unwrap(); let inner = arc.inner.borrow_mut(); match request { wl_seat::Request::GetPointer { id } => { - let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref(), token); + let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref()); if let Some(ref ptr_handle) = inner.pointer { ptr_handle.new_pointer(pointer); } else { diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index 17e3f1e..7f142b5 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, fmt, ops::Deref as _, rc::Rc}; +use std::{cell::RefCell, fmt, ops::Deref as _, rc::Rc, sync::Mutex}; use wayland_server::{ protocol::{ @@ -8,12 +8,14 @@ use wayland_server::{ Filter, Main, }; -use crate::wayland::compositor::{roles::Role, CompositorToken}; +use crate::wayland::compositor; use crate::wayland::Serial; +static CURSOR_IMAGE_ROLE: &str = "cursor_image"; + /// The role representing a surface set as the pointer cursor #[derive(Debug, Default, Copy, Clone)] -pub struct CursorImageRole { +pub struct CursorImageAttributes { /// Location of the hotspot of the pointer in the surface pub hotspot: (i32, i32), } @@ -72,9 +74,8 @@ impl fmt::Debug for PointerInternal { } impl PointerInternal { - fn new(token: CompositorToken, mut cb: F) -> PointerInternal + fn new(mut cb: F) -> PointerInternal where - R: Role + 'static, F: FnMut(CursorImageStatus) + 'static, { let mut old_status = CursorImageStatus::Default; @@ -86,11 +87,7 @@ impl PointerInternal { CursorImageStatus::Image(ref new_surface) if new_surface == &surface => { // don't remove the role, we are just re-binding the same surface } - _ => { - if surface.as_ref().is_alive() { - token.remove_role::(&surface).unwrap(); - } - } + _ => {} } } cb(new_status) @@ -566,24 +563,16 @@ impl AxisFrame { } } -pub(crate) fn create_pointer_handler(token: CompositorToken, cb: F) -> PointerHandle +pub(crate) fn create_pointer_handler(cb: F) -> PointerHandle where - R: Role + 'static, F: FnMut(CursorImageStatus) + 'static, { PointerHandle { - inner: Rc::new(RefCell::new(PointerInternal::new(token, cb))), + inner: Rc::new(RefCell::new(PointerInternal::new(cb))), } } -pub(crate) fn implement_pointer( - pointer: Main, - handle: Option<&PointerHandle>, - token: CompositorToken, -) -> WlPointer -where - R: Role + 'static, -{ +pub(crate) fn implement_pointer(pointer: Main, handle: Option<&PointerHandle>) -> WlPointer { let inner = handle.map(|h| h.inner.clone()); pointer.quick_assign(move |pointer, request, _data| { match request { @@ -606,14 +595,9 @@ where if focus.as_ref().same_client_as(&pointer.as_ref()) { match surface { Some(surface) => { - let role_data = CursorImageRole { - hotspot: (hotspot_x, hotspot_y), - }; - // we gracefully tolerate the client to provide a surface that - // already had the "CursorImage" role, as most clients will - // always reuse the same surface (and they are right to do so!) - if token.with_role_data(&surface, |data| *data = role_data).is_err() - && token.give_role_with(&surface, role_data).is_err() + // tolerate re-using the same surface + if compositor::give_role(&surface, CURSOR_IMAGE_ROLE).is_err() + && compositor::get_role(&surface) != Some(CURSOR_IMAGE_ROLE) { pointer.as_ref().post_error( wl_pointer::Error::Role as u32, @@ -621,6 +605,20 @@ where ); return; } + compositor::with_states(&surface, |states| { + states.data_map.insert_if_missing_threadsafe(|| { + Mutex::new(CursorImageAttributes { hotspot: (0, 0) }) + }); + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot = (hotspot_x, hotspot_y); + }) + .unwrap(); + image_callback(CursorImageStatus::Image(surface)); } None => { diff --git a/src/wayland/shell/legacy/mod.rs b/src/wayland/shell/legacy/mod.rs index 1b5d71e..625f2b1 100644 --- a/src/wayland/shell/legacy/mod.rs +++ b/src/wayland/shell/legacy/mod.rs @@ -23,37 +23,18 @@ //! //! ### Initialization //! -//! To initialize this handler, simple use the [`wl_shell_init`] -//! function provided in this module. You will need to provide it the [`CompositorToken`](crate::wayland::compositor::CompositorToken) -//! you retrieved from an instantiation of the compositor handler provided by smithay. +//! To initialize this handler, simple use the [`wl_shell_init`] function provided in this module. //! //! ```no_run //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! # extern crate wayland_protocols; //! # -//! use smithay::wayland::compositor::roles::*; -//! use smithay::wayland::compositor::CompositorToken; -//! use smithay::wayland::shell::legacy::{wl_shell_init, ShellSurfaceRole, ShellRequest}; -//! # use wayland_server::protocol::{wl_seat, wl_output}; -//! -//! // define the roles type. You need to integrate the XdgSurface role: -//! define_roles!(MyRoles => -//! [ShellSurface, ShellSurfaceRole] -//! ); +//! use smithay::wayland::shell::legacy::{wl_shell_init, ShellRequest}; //! //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::( -//! # &mut display, -//! # |_, _, _, _| {}, -//! # None -//! # ); //! let (shell_state, _) = wl_shell_init( //! &mut display, -//! // token from the compositor implementation -//! compositor_token, //! // your implementation -//! |event: ShellRequest<_>| { /* ... */ }, +//! |event: ShellRequest| { /* ... */ }, //! None // put a logger if you want //! ); //! @@ -66,8 +47,7 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::wayland::compositor::{roles::Role, CompositorToken}; -use crate::wayland::Serial; +use crate::wayland::{compositor, Serial}; use wayland_server::{ protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}, @@ -80,7 +60,7 @@ mod wl_handlers; /// Metadata associated with the `wl_surface` role #[derive(Debug)] -pub struct ShellSurfaceRole { +pub struct ShellSurfaceAttributes { /// Title of the surface pub title: String, /// Class of the surface @@ -89,28 +69,13 @@ pub struct ShellSurfaceRole { } /// A handle to a shell surface -#[derive(Debug)] -pub struct ShellSurface { +#[derive(Debug, Clone)] +pub struct ShellSurface { wl_surface: wl_surface::WlSurface, shell_surface: wl_shell_surface::WlShellSurface, - token: CompositorToken, } -// We implement Clone manually because #[derive(..)] would require R: Clone. -impl Clone for ShellSurface { - fn clone(&self) -> Self { - Self { - wl_surface: self.wl_surface.clone(), - shell_surface: self.shell_surface.clone(), - token: self.token, - } - } -} - -impl ShellSurface -where - R: Role + 'static, -{ +impl ShellSurface { /// Is the shell surface referred by this handle still alive? pub fn alive(&self) -> bool { self.shell_surface.as_ref().is_alive() && self.wl_surface.as_ref().is_alive() @@ -145,15 +110,20 @@ where if !self.alive() { return Err(PingError::DeadSurface); } - self.token - .with_role_data(&self.wl_surface, |data| { - if let Some(pending_ping) = data.pending_ping { - return Err(PingError::PingAlreadyPending(pending_ping)); - } - data.pending_ping = Some(serial); - Ok(()) - }) - .unwrap()?; + compositor::with_states(&self.wl_surface, |states| { + let mut data = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if let Some(pending_ping) = data.pending_ping { + return Err(PingError::PingAlreadyPending(pending_ping)); + } + data.pending_ping = Some(serial); + Ok(()) + }) + .unwrap()?; self.shell_surface.ping(serial.into()); Ok(()) } @@ -225,13 +195,13 @@ pub enum ShellSurfaceKind { /// A request triggered by a `wl_shell_surface` #[derive(Debug)] -pub enum ShellRequest { +pub enum ShellRequest { /// A new shell surface was created /// /// by default it has no kind and this should not be displayed NewShellSurface { /// The created surface - surface: ShellSurface, + surface: ShellSurface, }, /// A pong event /// @@ -239,14 +209,14 @@ pub enum ShellRequest { /// event, smithay has already checked that the responded serial was valid. Pong { /// The surface that sent the pong - surface: ShellSurface, + surface: ShellSurface, }, /// Start of an interactive move /// /// The surface requests that an interactive move is started on it Move { /// The surface requesting the move - surface: ShellSurface, + surface: ShellSurface, /// Serial of the implicit grab that initiated the move serial: Serial, /// Seat associated with the move @@ -257,7 +227,7 @@ pub enum ShellRequest { /// The surface requests that an interactive resize is started on it Resize { /// The surface requesting the resize - surface: ShellSurface, + surface: ShellSurface, /// Serial of the implicit grab that initiated the resize serial: Serial, /// Seat associated with the resize @@ -268,7 +238,7 @@ pub enum ShellRequest { /// The surface changed its kind SetKind { /// The surface - surface: ShellSurface, + surface: ShellSurface, /// Its new kind kind: ShellSurfaceKind, }, @@ -279,36 +249,31 @@ pub enum ShellRequest { /// This state allows you to retrieve a list of surfaces /// currently known to the shell global. #[derive(Debug)] -pub struct ShellState { - known_surfaces: Vec>, +pub struct ShellState { + known_surfaces: Vec, } -impl ShellState -where - R: Role + 'static, -{ +impl ShellState { /// Cleans the internal surface storage by removing all dead surfaces pub(crate) fn cleanup_surfaces(&mut self) { self.known_surfaces.retain(|s| s.alive()); } /// Access all the shell surfaces known by this handler - pub fn surfaces(&self) -> &[ShellSurface] { + pub fn surfaces(&self) -> &[ShellSurface] { &self.known_surfaces[..] } } /// Create a new `wl_shell` global -pub fn wl_shell_init( +pub fn wl_shell_init( display: &mut Display, - ctoken: CompositorToken, implementation: Impl, logger: L, -) -> (Arc>>, Global) +) -> (Arc>, Global) where - R: Role + 'static, L: Into>, - Impl: FnMut(ShellRequest) + 'static, + Impl: FnMut(ShellRequest) + 'static, { let _log = crate::slog_or_fallback(logger); @@ -322,7 +287,7 @@ where let global = display.create_global( 1, Filter::new(move |(shell, _version), _, _data| { - self::wl_handlers::implement_shell(shell, ctoken, implementation.clone(), state2.clone()); + self::wl_handlers::implement_shell(shell, implementation.clone(), state2.clone()); }), ); diff --git a/src/wayland/shell/legacy/wl_handlers.rs b/src/wayland/shell/legacy/wl_handlers.rs index 29919bf..f540125 100644 --- a/src/wayland/shell/legacy/wl_handlers.rs +++ b/src/wayland/shell/legacy/wl_handlers.rs @@ -10,116 +10,116 @@ use wayland_server::{ Filter, Main, }; -use crate::wayland::compositor::{roles::Role, CompositorToken}; +static WL_SHELL_SURFACE_ROLE: &str = "wl_shell_surface"; + +use crate::wayland::compositor; use crate::wayland::Serial; -use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceKind, ShellSurfaceRole}; +use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceAttributes, ShellSurfaceKind}; -pub(crate) fn implement_shell( +pub(crate) fn implement_shell( shell: Main, - ctoken: CompositorToken, implementation: Rc>, - state: Arc>>, + state: Arc>, ) where - R: Role + 'static, - Impl: FnMut(ShellRequest) + 'static, + Impl: FnMut(ShellRequest) + 'static, { shell.quick_assign(move |shell, req, _data| { let (id, surface) = match req { wl_shell::Request::GetShellSurface { id, surface } => (id, surface), _ => unreachable!(), }; - let role_data = ShellSurfaceRole { - title: "".into(), - class: "".into(), - pending_ping: None, - }; - if ctoken.give_role_with(&surface, role_data).is_err() { + if compositor::give_role(&surface, WL_SHELL_SURFACE_ROLE).is_err() { shell .as_ref() .post_error(wl_shell::Error::Role as u32, "Surface already has a role.".into()); return; } - let shell_surface = - implement_shell_surface(id, surface, implementation.clone(), ctoken, state.clone()); + compositor::with_states(&surface, |states| { + states.data_map.insert_if_missing(|| { + Mutex::new(ShellSurfaceAttributes { + title: "".into(), + class: "".into(), + pending_ping: None, + }) + }) + }) + .unwrap(); + let shell_surface = implement_shell_surface(id, surface, implementation.clone(), state.clone()); state .lock() .unwrap() .known_surfaces - .push(make_handle(&shell_surface, ctoken)); + .push(make_handle(&shell_surface)); let mut imp = implementation.borrow_mut(); (&mut *imp)(ShellRequest::NewShellSurface { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), }); }); } -fn make_handle( - shell_surface: &wl_shell_surface::WlShellSurface, - token: CompositorToken, -) -> ShellSurface -where - R: Role + 'static, -{ +fn make_handle(shell_surface: &wl_shell_surface::WlShellSurface) -> ShellSurface { let data = shell_surface .as_ref() .user_data() - .get::>() + .get::() .unwrap(); ShellSurface { wl_surface: data.surface.clone(), shell_surface: shell_surface.clone(), - token, } } -pub(crate) struct ShellSurfaceUserData { +pub(crate) struct ShellSurfaceUserData { surface: wl_surface::WlSurface, - state: Arc>>, + state: Arc>, } -fn implement_shell_surface( +fn implement_shell_surface( shell_surface: Main, surface: wl_surface::WlSurface, implementation: Rc>, - ctoken: CompositorToken, - state: Arc>>, + state: Arc>, ) -> wl_shell_surface::WlShellSurface where - R: Role + 'static, - Impl: FnMut(ShellRequest) + 'static, + Impl: FnMut(ShellRequest) + 'static, { use self::wl_shell_surface::Request; shell_surface.quick_assign(move |shell_surface, req, _data| { let data = shell_surface .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let mut user_impl = implementation.borrow_mut(); match req { Request::Pong { serial } => { let serial = Serial::from(serial); - let valid = ctoken - .with_role_data(&data.surface, |data| { - if data.pending_ping == Some(serial) { - data.pending_ping = None; - true - } else { - false - } - }) - .expect("wl_shell_surface exists but surface has not the right role?"); + let valid = compositor::with_states(&data.surface, |states| { + let mut guard = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if guard.pending_ping == Some(serial) { + guard.pending_ping = None; + true + } else { + false + } + }) + .unwrap(); if valid { (&mut *user_impl)(ShellRequest::Pong { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), }); } } Request::Move { seat, serial } => { let serial = Serial::from(serial); (&mut *user_impl)(ShellRequest::Move { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), serial, seat, }) @@ -127,18 +127,18 @@ where Request::Resize { seat, serial, edges } => { let serial = Serial::from(serial); (&mut *user_impl)(ShellRequest::Resize { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), serial, seat, edges, }) } Request::SetToplevel => (&mut *user_impl)(ShellRequest::SetKind { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), kind: ShellSurfaceKind::Toplevel, }), Request::SetTransient { parent, x, y, flags } => (&mut *user_impl)(ShellRequest::SetKind { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), kind: ShellSurfaceKind::Transient { parent, location: (x, y), @@ -150,7 +150,7 @@ where framerate, output, } => (&mut *user_impl)(ShellRequest::SetKind { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), kind: ShellSurfaceKind::Fullscreen { method, framerate, @@ -167,7 +167,7 @@ where } => { let serial = Serial::from(serial); (&mut *user_impl)(ShellRequest::SetKind { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), kind: ShellSurfaceKind::Popup { parent, serial, @@ -178,18 +178,32 @@ where }) } Request::SetMaximized { output } => (&mut *user_impl)(ShellRequest::SetKind { - surface: make_handle(&shell_surface, ctoken), + surface: make_handle(&shell_surface), kind: ShellSurfaceKind::Maximized { output }, }), Request::SetTitle { title } => { - ctoken - .with_role_data(&data.surface, |data| data.title = title) - .expect("wl_shell_surface exists but surface has not shell_surface role?!"); + compositor::with_states(&data.surface, |states| { + let mut guard = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + guard.title = title; + }) + .unwrap(); } Request::SetClass { class_ } => { - ctoken - .with_role_data(&data.surface, |data| data.class = class_) - .expect("wl_shell_surface exists but surface has not shell_surface role?!"); + compositor::with_states(&data.surface, |states| { + let mut guard = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + guard.class = class_; + }) + .unwrap(); } _ => unreachable!(), } @@ -200,7 +214,7 @@ where let data = shell_surface .as_ref() .user_data() - .get::>() + .get::() .unwrap(); data.state.lock().unwrap().cleanup_surfaces(); }, diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index f540f06..9f7ed71 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -21,26 +21,13 @@ //! //! ### Initialization //! -//! To initialize this handler, simple use the [`xdg_shell_init`] function provided in this -//! module. You will need to provide it the [`CompositorToken`](crate::wayland::compositor::CompositorToken) -//! you retrieved from an instantiation of the compositor global provided by smithay. +//! To initialize this handler, simple use the [`xdg_shell_init`] function provided in this module. //! //! ```no_run //! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! # extern crate wayland_protocols; //! # -//! use smithay::wayland::compositor::roles::*; -//! use smithay::wayland::compositor::CompositorToken; -//! use smithay::wayland::shell::xdg::{xdg_shell_init, XdgToplevelSurfaceRole, XdgPopupSurfaceRole, XdgRequest}; -//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6; -//! # use wayland_server::protocol::{wl_seat, wl_output}; +//! use smithay::wayland::shell::xdg::{xdg_shell_init, XdgRequest}; //! -//! // define the roles type. You need to integrate the XdgSurface role: -//! define_roles!(MyRoles => -//! [XdgToplevelSurface, XdgToplevelSurfaceRole] -//! [XdgPopupSurface, XdgPopupSurfaceRole] -//! ); //! //! // define the metadata you want associated with the shell clients //! #[derive(Default)] @@ -49,17 +36,10 @@ //! } //! //! # let mut display = wayland_server::Display::new(); -//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::( -//! # &mut display, -//! # |_, _, _, _| {}, -//! # None -//! # ); //! let (shell_state, _, _) = xdg_shell_init( //! &mut display, -//! // token from the compositor implementation -//! compositor_token, //! // your implementation -//! |event: XdgRequest<_>| { /* ... */ }, +//! |event: XdgRequest| { /* ... */ }, //! None // put a logger if you want //! ); //! @@ -87,9 +67,10 @@ //! the subhandler you provided, or via methods on the [`ShellState`] //! that you are given (in an `Arc>`) as return value of the `init` function. +use crate::utils::DeadResource; use crate::utils::Rectangle; -use crate::wayland::compositor::roles::WrongRole; -use crate::wayland::compositor::{roles::Role, CompositorToken}; +use crate::wayland::compositor; +use crate::wayland::compositor::Cacheable; use crate::wayland::{Serial, SERIAL_COUNTER}; use std::fmt::Debug; use std::{ @@ -97,7 +78,6 @@ use std::{ rc::Rc, sync::{Arc, Mutex}, }; -use thiserror::Error; use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_surface_v6; use wayland_protocols::xdg_shell::server::xdg_surface; use wayland_protocols::{ @@ -117,24 +97,12 @@ mod xdg_handlers; mod zxdgv6_handlers; macro_rules! xdg_role { - ($(#[$role_attr:meta])* $role:ident, - $configure:ty, + ($configure:ty, $(#[$attr:meta])* $element:ident {$($(#[$field_attr:meta])* $vis:vis$field:ident:$type:ty),*}, $role_ack_configure:expr) => { - $(#[$role_attr])* - pub type $role = Option<$element>; $(#[$attr])* pub struct $element { - /// Defines the current window geometry of the surface if any. - pub window_geometry: Option, - - /// Holds the double-buffered geometry that may be specified - /// by xdg_surface.set_window_geometry. This should be moved to the - /// current configuration on a commit of the underlying - /// wl_surface. - pub client_pending_window_geometry: Option, - /// Defines if the surface has received at least one /// xdg_surface.ack_configure from the client pub configured: bool, @@ -161,12 +129,8 @@ macro_rules! xdg_role { )* } - impl XdgSurface for $element { - fn set_window_geometry(&mut self, geometry: Rectangle) { - self.client_pending_window_geometry = Some(geometry); - } - - fn ack_configure(&mut self, serial: Serial) -> Result { + impl $element { + fn ack_configure(&mut self, serial: Serial) -> Option { let configure = match self .pending_configures .iter() @@ -174,7 +138,7 @@ macro_rules! xdg_role { { Some(configure) => (*configure).clone(), None => { - return Err(ConfigureError::SerialNotFound(serial)); + return None; } }; @@ -191,15 +155,13 @@ macro_rules! xdg_role { // Clean old configures self.pending_configures.retain(|c| c.serial > serial); - Ok(configure.into()) + Some(configure.into()) } } impl Default for $element { fn default() -> Self { Self { - window_geometry: None, - client_pending_window_geometry: None, configured: false, configure_serial: None, pending_configures: Vec::new(), @@ -214,52 +176,10 @@ macro_rules! xdg_role { }; } -impl CompositorToken -where - R: Role + Role + 'static, -{ - fn with_xdg_role(&self, surface: &wl_surface::WlSurface, f: F) -> Result - where - F: FnOnce(&mut dyn XdgSurface) -> T, - { - if self.has_role::(surface) { - self.with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| f(role)) - } else { - self.with_role_data(surface, |role: &mut XdgPopupSurfaceRole| f(role)) - } - } -} - -trait XdgSurface { - fn set_window_geometry(&mut self, geometry: Rectangle); - - fn ack_configure(&mut self, serial: Serial) -> Result; -} - -#[derive(Debug, Error)] -enum ConfigureError { - #[error("the serial `{0:?}` was not found in the pending configure list")] - SerialNotFound(Serial), -} - -impl XdgSurface for Option -where - T: XdgSurface, -{ - fn set_window_geometry(&mut self, geometry: Rectangle) { - self.as_mut() - .expect("xdg_surface exists but role has been destroyed?!") - .set_window_geometry(geometry) - } - - fn ack_configure(&mut self, serial: Serial) -> Result { - self.as_mut() - .expect("xdg_surface exists but role has been destroyed?!") - .ack_configure(serial) - } -} - xdg_role!( + ToplevelConfigure, + /// Role specific attributes for xdg_toplevel + /// /// This interface defines an xdg_surface role which allows a surface to, /// among other things, set window-like properties such as maximize, /// fullscreen, and minimize, set application-specific metadata like title and @@ -277,9 +197,6 @@ xdg_role!( /// xdg_surface description). /// /// Attaching a null buffer to a toplevel unmaps the surface. - XdgToplevelSurfaceRole, - ToplevelConfigure, - /// Role specific attributes for xdg_toplevel XdgToplevelSurfaceRoleAttributes { /// The parent field of a toplevel should be used /// by the compositor to determine which toplevel @@ -312,8 +229,6 @@ xdg_role!( /// /// A value of 0 on an axis means this axis is not constrained pub max_size: (i32, i32), - /// Holds the pending state as set by the client. - pub client_pending: Option, /// Holds the pending state as set by the server. pub server_pending: Option, /// Holds the last server_pending state that has been acknowledged @@ -330,6 +245,9 @@ xdg_role!( ); xdg_role!( + PopupConfigure, + /// Role specific attributes for xdg_popup + /// /// A popup surface is a short-lived, temporary surface. It can be used to /// implement for example menus, popovers, tooltips and other similar user /// interface concepts. @@ -354,9 +272,6 @@ xdg_role!( /// /// The client must call wl_surface.commit on the corresponding wl_surface /// for the xdg_popup state to take effect. - XdgPopupSurfaceRole, - PopupConfigure, - /// Popup Surface Role XdgPopupSurfaceRoleAttributes { /// Holds the parent for the xdg_popup. /// @@ -383,7 +298,8 @@ xdg_role!( /// commit. pub current: PopupState, /// Holds the pending state as set by the server. - pub server_pending: Option + pub server_pending: Option, + popup_handle: Option }, |attributes,configure| { attributes.last_acked = Some(configure.state); @@ -687,57 +603,55 @@ impl From for Vec { /// Represents the client pending state #[derive(Debug, Clone, Copy)] -pub struct ToplevelClientPending { +pub struct SurfaceCachedState { + /// Holds the double-buffered geometry that may be specified + /// by xdg_surface.set_window_geometry. + pub geometry: Option, /// Minimum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// - /// A value of `None` represents the client has not defined - /// a minimum size - pub min_size: Option<(i32, i32)>, + /// This is only relevant for xdg_toplevel, and will always be + /// `(0, 0)` for xdg_popup. + pub min_size: (i32, i32), /// Maximum size requested for this surface /// /// A value of 0 on an axis means this axis is not constrained /// - /// A value of `None` represents the client has not defined - /// a maximum size - pub max_size: Option<(i32, i32)>, + /// This is only relevant for xdg_toplevel, and will always be + /// `(0, 0)` for xdg_popup. + pub max_size: (i32, i32), } -impl Default for ToplevelClientPending { +impl Default for SurfaceCachedState { fn default() -> Self { Self { - min_size: None, - max_size: None, + geometry: None, + min_size: (0, 0), + max_size: (0, 0), } } } -/// Represents the possible errors -/// returned from `ToplevelSurface::with_pending_state` -/// and `PopupSurface::with_pending_state` -#[derive(Debug, Error)] -pub enum PendingStateError { - /// The operation failed because the underlying surface has been destroyed - #[error("could not retrieve the pending state because the underlying surface has been destroyed")] - DeadSurface, - /// The role of the xdg_surface has been destroyed - #[error("the role of the xdg_surface has been destroyed")] - RoleDestroyed, +impl Cacheable for SurfaceCachedState { + fn commit(&mut self) -> Self { + *self + } + fn merge_into(self, into: &mut Self) { + *into = self; + } } -pub(crate) struct ShellData { +pub(crate) struct ShellData { log: ::slog::Logger, - compositor_token: CompositorToken, - user_impl: Rc)>>, - shell_state: Arc>>, + user_impl: Rc>, + shell_state: Arc>, } -impl Clone for ShellData { +impl Clone for ShellData { fn clone(&self) -> Self { ShellData { log: self.log.clone(), - compositor_token: self.compositor_token, user_impl: self.user_impl.clone(), shell_state: self.shell_state.clone(), } @@ -745,20 +659,18 @@ impl Clone for ShellData { } /// Create a new `xdg_shell` globals -pub fn xdg_shell_init( +pub fn xdg_shell_init( display: &mut Display, - ctoken: CompositorToken, implementation: Impl, logger: L, ) -> ( - Arc>>, + Arc>, Global, Global, ) where - R: Role + Role + 'static, L: Into>, - Impl: FnMut(XdgRequest) + 'static, + Impl: FnMut(XdgRequest) + 'static, { let log = crate::slog_or_fallback(logger); let shell_state = Arc::new(Mutex::new(ShellState { @@ -768,7 +680,6 @@ where let shell_data = ShellData { log: log.new(o!("smithay_module" => "xdg_shell_handler")), - compositor_token: ctoken, user_impl: Rc::new(RefCell::new(implementation)), shell_state: shell_state.clone(), }; @@ -797,22 +708,19 @@ where /// This state allows you to retrieve a list of surfaces /// currently known to the shell global. #[derive(Debug)] -pub struct ShellState { - known_toplevels: Vec>, - known_popups: Vec>, +pub struct ShellState { + known_toplevels: Vec, + known_popups: Vec, } -impl ShellState -where - R: Role + Role + 'static, -{ +impl ShellState { /// Access all the shell surfaces known by this handler - pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] { + pub fn toplevel_surfaces(&self) -> &[ToplevelSurface] { &self.known_toplevels[..] } /// Access all the popup surfaces known by this handler - pub fn popup_surfaces(&self) -> &[PopupSurface] { + pub fn popup_surfaces(&self) -> &[PopupSurface] { &self.known_popups[..] } } @@ -850,15 +758,11 @@ fn make_shell_client_data() -> ShellClientData { /// You can use this handle to access a storage for any /// client-specific data you wish to associate with it. #[derive(Debug)] -pub struct ShellClient { +pub struct ShellClient { kind: ShellClientKind, - _token: CompositorToken, } -impl ShellClient -where - R: Role + Role + 'static, -{ +impl ShellClient { /// Is the shell client represented by this handle still connected? pub fn alive(&self) -> bool { match self.kind { @@ -897,7 +801,7 @@ where let user_data = shell .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let mut guard = user_data.client_data.lock().unwrap(); if let Some(pending_ping) = guard.pending_ping { @@ -910,7 +814,7 @@ where let user_data = shell .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let mut guard = user_data.client_data.lock().unwrap(); if let Some(pending_ping) = guard.pending_ping { @@ -936,7 +840,7 @@ where let data = shell .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let mut guard = data.client_data.lock().unwrap(); Ok(f(&mut guard.data)) @@ -945,7 +849,7 @@ where let data = shell .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let mut guard = data.client_data.lock().unwrap(); Ok(f(&mut guard.data)) @@ -961,28 +865,13 @@ pub(crate) enum ToplevelKind { } /// A handle to a toplevel surface -#[derive(Debug)] -pub struct ToplevelSurface { +#[derive(Debug, Clone)] +pub struct ToplevelSurface { wl_surface: wl_surface::WlSurface, shell_surface: ToplevelKind, - token: CompositorToken, } -// We implement Clone manually because #[derive(..)] would require R: Clone. -impl Clone for ToplevelSurface { - fn clone(&self) -> Self { - Self { - wl_surface: self.wl_surface.clone(), - shell_surface: self.shell_surface.clone(), - token: self.token, - } - } -} - -impl ToplevelSurface -where - R: Role + 'static, -{ +impl ToplevelSurface { /// Is the toplevel surface referred by this handle still alive? pub fn alive(&self) -> bool { let shell_alive = match self.shell_surface { @@ -1000,7 +889,7 @@ where /// Retrieve the shell client owning this toplevel surface /// /// Returns `None` if the surface does actually no longer exist. - pub fn client(&self) -> Option> { + pub fn client(&self) -> Option { if !self.alive() { return None; } @@ -1010,7 +899,7 @@ where let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); ShellClientKind::Xdg(data.wm_base.clone()) } @@ -1018,16 +907,13 @@ where let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); ShellClientKind::ZxdgV6(data.shell.clone()) } }; - Some(ShellClient { - kind: shell, - _token: self.token, - }) + Some(ShellClient { kind: shell }) } /// Gets the current pending state for a configure @@ -1071,46 +957,34 @@ where /// The serial of this configure will be tracked waiting for the client to ACK it. pub fn send_configure(&self) { if let Some(surface) = self.get_surface() { - let configure = self - .token - .with_role_data(surface, |role| { - if let Some(attributes) = role { - if let Some(pending) = self.get_pending_state(attributes) { - let configure = ToplevelConfigure { - serial: SERIAL_COUNTER.next_serial(), - state: pending, - }; + let configure = compositor::with_states(surface, |states| { + let mut attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if let Some(pending) = self.get_pending_state(&mut *attributes) { + let configure = ToplevelConfigure { + serial: SERIAL_COUNTER.next_serial(), + state: pending, + }; - attributes.pending_configures.push(configure.clone()); + attributes.pending_configures.push(configure.clone()); + attributes.initial_configure_sent = true; - Some((configure, !attributes.initial_configure_sent)) - } else { - None - } - } else { - None - } - }) - .unwrap_or(None); - - if let Some((configure, initial)) = configure { - match self.shell_surface { - ToplevelKind::Xdg(ref s) => { - self::xdg_handlers::send_toplevel_configure::(s, configure) - } - ToplevelKind::ZxdgV6(ref s) => { - self::zxdgv6_handlers::send_toplevel_configure::(s, configure) - } + Some(configure) + } else { + None } - - if initial { - self.token - .with_role_data(surface, |role| { - if let XdgToplevelSurfaceRole::Some(attributes) = role { - attributes.initial_configure_sent = true; - } - }) - .unwrap(); + }) + .unwrap_or(None); + if let Some(configure) = configure { + match self.shell_surface { + ToplevelKind::Xdg(ref s) => self::xdg_handlers::send_toplevel_configure(s, configure), + ToplevelKind::ZxdgV6(ref s) => { + self::zxdgv6_handlers::send_toplevel_configure(s, configure) + } } } } @@ -1120,46 +994,19 @@ where /// /// This should be called when the underlying WlSurface /// handles a wl_surface.commit request. - pub fn commit(&self) { - if let Some(surface) = self.get_surface() { - let send_initial_configure = self - .token - .with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| { - if let XdgToplevelSurfaceRole::Some(attributes) = role { - if let Some(window_geometry) = attributes.client_pending_window_geometry.take() { - attributes.window_geometry = Some(window_geometry); - } - - if attributes.initial_configure_sent { - if let Some(state) = attributes.last_acked.clone() { - if state != attributes.current { - attributes.current = state; - } - } - - if let Some(mut client_pending) = attributes.client_pending.take() { - // update state from the client that doesn't need compositor approval - if let Some(size) = client_pending.max_size.take() { - attributes.max_size = size; - } - - if let Some(size) = client_pending.min_size.take() { - attributes.min_size = size; - } - } - } - - !attributes.initial_configure_sent - } else { - false - } - }) - .unwrap_or(false); - - if send_initial_configure { - self.send_configure(); + pub(crate) fn commit_hook(surface: &wl_surface::WlSurface) { + compositor::with_states(surface, |states| { + let mut guard = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if let Some(state) = guard.last_acked.clone() { + guard.current = state; } - } + }) + .unwrap(); } /// Make sure this surface was configured @@ -1174,21 +1021,23 @@ where if !self.alive() { return false; } - let configured = self - .token - .with_role_data::(&self.wl_surface, |data| { - data.as_ref() - .expect("xdg_toplevel exists but role has been destroyed?!") - .configured - }) - .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); + let configured = compositor::with_states(&self.wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .configured + }) + .unwrap(); if !configured { match self.shell_surface { ToplevelKind::Xdg(ref s) => { let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); data.xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, @@ -1199,7 +1048,7 @@ where let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); data.xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, @@ -1240,28 +1089,29 @@ where /// /// The state will be sent to the client when calling /// send_configure. - pub fn with_pending_state(&self, f: F) -> Result + pub fn with_pending_state(&self, f: F) -> Result where F: FnOnce(&mut ToplevelState) -> T, { if !self.alive() { - return Err(PendingStateError::DeadSurface); + return Err(DeadResource); } - self.token - .with_role_data(&self.wl_surface, |role| { - if let Some(attributes) = role { - if attributes.server_pending.is_none() { - attributes.server_pending = Some(attributes.current.clone()); - } + Ok(compositor::with_states(&self.wl_surface, |states| { + let mut attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if attributes.server_pending.is_none() { + attributes.server_pending = Some(attributes.current.clone()); + } - let server_pending = attributes.server_pending.as_mut().unwrap(); - Ok(f(server_pending)) - } else { - Err(PendingStateError::RoleDestroyed) - } - }) - .unwrap() + let server_pending = attributes.server_pending.as_mut().unwrap(); + f(server_pending) + }) + .unwrap()) } } @@ -1275,28 +1125,13 @@ pub(crate) enum PopupKind { /// /// This is an unified abstraction over the popup surfaces /// of both `wl_shell` and `xdg_shell`. -#[derive(Debug)] -pub struct PopupSurface { +#[derive(Debug, Clone)] +pub struct PopupSurface { wl_surface: wl_surface::WlSurface, shell_surface: PopupKind, - token: CompositorToken, } -// We implement Clone manually because #[derive(..)] would require R: Clone. -impl Clone for PopupSurface { - fn clone(&self) -> Self { - Self { - wl_surface: self.wl_surface.clone(), - shell_surface: self.shell_surface.clone(), - token: self.token, - } - } -} - -impl PopupSurface -where - R: Role + 'static, -{ +impl PopupSurface { /// Is the popup surface referred by this handle still alive? pub fn alive(&self) -> bool { let shell_alive = match self.shell_surface { @@ -1312,11 +1147,17 @@ where if !self.alive() { None } else { - self.token - .with_role_data(&self.wl_surface, |role: &mut XdgPopupSurfaceRole| { - role.as_ref().and_then(|role| role.parent.clone()) - }) - .unwrap() + compositor::with_states(&self.wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .parent + .clone() + }) + .unwrap() } } @@ -1328,7 +1169,7 @@ where /// Retrieve the shell client owning this popup surface /// /// Returns `None` if the surface does actually no longer exist. - pub fn client(&self) -> Option> { + pub fn client(&self) -> Option { if !self.alive() { return None; } @@ -1338,7 +1179,7 @@ where let data = p .as_ref() .user_data() - .get::>() + .get::() .unwrap(); ShellClientKind::Xdg(data.wm_base.clone()) } @@ -1346,16 +1187,13 @@ where let data = p .as_ref() .user_data() - .get::>() + .get::() .unwrap(); ShellClientKind::ZxdgV6(data.shell.clone()) } }; - Some(ShellClient { - kind: shell, - _token: self.token, - }) + Some(ShellClient { kind: shell }) } /// Send a configure event to this popup surface to suggest it a new configuration @@ -1363,48 +1201,39 @@ where /// The serial of this configure will be tracked waiting for the client to ACK it. pub fn send_configure(&self) { if let Some(surface) = self.get_surface() { - if let Some((configure, initial)) = self - .token - .with_role_data(surface, |role| { - if let XdgPopupSurfaceRole::Some(attributes) = role { - if !attributes.initial_configure_sent || attributes.server_pending.is_some() { - let pending = attributes.server_pending.take().unwrap_or(attributes.current); + let next_configure = compositor::with_states(surface, |states| { + let mut attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if !attributes.initial_configure_sent || attributes.server_pending.is_some() { + let pending = attributes.server_pending.take().unwrap_or(attributes.current); - let configure = PopupConfigure { - state: pending, - serial: SERIAL_COUNTER.next_serial(), - }; + let configure = PopupConfigure { + state: pending, + serial: SERIAL_COUNTER.next_serial(), + }; - attributes.pending_configures.push(configure); + attributes.pending_configures.push(configure); + attributes.initial_configure_sent = true; - Some((configure, !attributes.initial_configure_sent)) - } else { - None - } - } else { - None - } - }) - .unwrap_or(None) - { + Some(configure) + } else { + None + } + }) + .unwrap_or(None); + if let Some(configure) = next_configure { match self.shell_surface { PopupKind::Xdg(ref p) => { - self::xdg_handlers::send_popup_configure::(p, configure); + self::xdg_handlers::send_popup_configure(p, configure); } PopupKind::ZxdgV6(ref p) => { - self::zxdgv6_handlers::send_popup_configure::(p, configure); + self::zxdgv6_handlers::send_popup_configure(p, configure); } } - - if initial { - self.token - .with_role_data(surface, |role| { - if let XdgPopupSurfaceRole::Some(attributes) = role { - attributes.initial_configure_sent = true; - } - }) - .unwrap(); - } } } } @@ -1413,82 +1242,66 @@ where /// /// This should be called when the underlying WlSurface /// handles a wl_surface.commit request. - pub fn commit(&self) { - if let Some(surface) = self.get_surface() { - let has_parent = self - .token - .with_role_data(surface, |role| { - if let Some(attributes) = role { - attributes.parent.is_some() - } else { - false - } - }) - .unwrap_or(false); + pub(crate) fn commit_hook(surface: &wl_surface::WlSurface) { + let send_error_to = compositor::with_states(surface, |states| { + let attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if attributes.parent.is_none() { + attributes.popup_handle.clone() + } else { + None + } + }) + .unwrap_or(None); + if let Some(handle) = send_error_to { + match handle { + PopupKind::Xdg(ref s) => { + let data = s + .as_ref() + .user_data() + .get::() + .unwrap(); + data.xdg_surface.as_ref().post_error( + xdg_surface::Error::NotConstructed as u32, + "Surface has not been configured yet.".into(), + ); + } + PopupKind::ZxdgV6(ref s) => { + let data = s + .as_ref() + .user_data() + .get::() + .unwrap(); + data.xdg_surface.as_ref().post_error( + zxdg_surface_v6::Error::NotConstructed as u32, + "Surface has not been configured yet.".into(), + ); + } + } + return; + } - if !has_parent { - match self.shell_surface { - PopupKind::Xdg(ref s) => { - let data = s - .as_ref() - .user_data() - .get::>() - .unwrap(); - data.xdg_surface.as_ref().post_error( - xdg_surface::Error::NotConstructed as u32, - "Surface has not been configured yet.".into(), - ); - } - PopupKind::ZxdgV6(ref s) => { - let data = s - .as_ref() - .user_data() - .get::>() - .unwrap(); - data.xdg_surface.as_ref().post_error( - zxdg_surface_v6::Error::NotConstructed as u32, - "Surface has not been configured yet.".into(), - ); + compositor::with_states(surface, |states| { + let mut attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if attributes.initial_configure_sent { + if let Some(state) = attributes.last_acked { + if state != attributes.current { + attributes.current = state; } } - return; } - - let send_initial_configure = self - .token - .with_role_data(surface, |role: &mut XdgPopupSurfaceRole| { - if let XdgPopupSurfaceRole::Some(attributes) = role { - if let Some(window_geometry) = attributes.client_pending_window_geometry.take() { - attributes.window_geometry = Some(window_geometry); - } - - if attributes.initial_configure_sent { - if let Some(state) = attributes.last_acked { - if state != attributes.current { - attributes.current = state; - } - } - - if attributes.window_geometry.is_none() { - attributes.window_geometry = Some(Rectangle { - width: attributes.current.geometry.width, - height: attributes.current.geometry.height, - ..Default::default() - }) - } - } - - !attributes.initial_configure_sent - } else { - false - } - }) - .unwrap_or(false); - - if send_initial_configure { - self.send_configure(); - } - } + !attributes.initial_configure_sent + }) + .unwrap(); } /// Make sure this surface was configured @@ -1503,21 +1316,23 @@ where if !self.alive() { return false; } - let configured = self - .token - .with_role_data::(&self.wl_surface, |data| { - data.as_ref() - .expect("xdg_popup exists but role has been destroyed?!") - .configured - }) - .expect("A shell surface object exists but the surface does not have the shell_surface role ?!"); + let configured = compositor::with_states(&self.wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .configured + }) + .unwrap(); if !configured { match self.shell_surface { PopupKind::Xdg(ref s) => { let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); data.xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, @@ -1528,7 +1343,7 @@ where let data = s .as_ref() .user_data() - .get::>() + .get::() .unwrap(); data.xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, @@ -1572,28 +1387,29 @@ where /// /// The state will be sent to the client when calling /// send_configure. - pub fn with_pending_state(&self, f: F) -> Result + pub fn with_pending_state(&self, f: F) -> Result where F: FnOnce(&mut PopupState) -> T, { if !self.alive() { - return Err(PendingStateError::DeadSurface); + return Err(DeadResource); } - self.token - .with_role_data(&self.wl_surface, |role| { - if let Some(attributes) = role { - if attributes.server_pending.is_none() { - attributes.server_pending = Some(attributes.current); - } + Ok(compositor::with_states(&self.wl_surface, |states| { + let mut attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if attributes.server_pending.is_none() { + attributes.server_pending = Some(attributes.current); + } - let server_pending = attributes.server_pending.as_mut().unwrap(); - Ok(f(server_pending)) - } else { - Err(PendingStateError::RoleDestroyed) - } - }) - .unwrap() + let server_pending = attributes.server_pending.as_mut().unwrap(); + f(server_pending) + }) + .unwrap()) } } @@ -1656,11 +1472,11 @@ impl From for Configure { /// /// Depending on what you want to do, you might ignore some of them #[derive(Debug)] -pub enum XdgRequest { +pub enum XdgRequest { /// A new shell client was instantiated NewClient { /// the client - client: ShellClient, + client: ShellClient, }, /// The pong for a pending ping of this shell client was received /// @@ -1668,7 +1484,7 @@ pub enum XdgRequest { /// from the pending ping. ClientPong { /// the client - client: ShellClient, + client: ShellClient, }, /// A new toplevel surface was created /// @@ -1676,7 +1492,7 @@ pub enum XdgRequest { /// client as to how its window should be NewToplevel { /// the surface - surface: ToplevelSurface, + surface: ToplevelSurface, }, /// A new popup surface was created /// @@ -1684,12 +1500,12 @@ pub enum XdgRequest { /// client as to how its popup should be NewPopup { /// the surface - surface: PopupSurface, + surface: PopupSurface, }, /// The client requested the start of an interactive move for this surface Move { /// the surface - surface: ToplevelSurface, + surface: ToplevelSurface, /// the seat associated to this move seat: wl_seat::WlSeat, /// the grab serial @@ -1698,7 +1514,7 @@ pub enum XdgRequest { /// The client requested the start of an interactive resize for this surface Resize { /// The surface - surface: ToplevelSurface, + surface: ToplevelSurface, /// The seat associated with this resize seat: wl_seat::WlSeat, /// The grab serial @@ -1712,7 +1528,7 @@ pub enum XdgRequest { /// the grab area. Grab { /// The surface - surface: PopupSurface, + surface: PopupSurface, /// The seat to grab seat: wl_seat::WlSeat, /// The grab serial @@ -1721,29 +1537,29 @@ pub enum XdgRequest { /// A toplevel surface requested to be maximized Maximize { /// The surface - surface: ToplevelSurface, + surface: ToplevelSurface, }, /// A toplevel surface requested to stop being maximized UnMaximize { /// The surface - surface: ToplevelSurface, + surface: ToplevelSurface, }, /// A toplevel surface requested to be set fullscreen Fullscreen { /// The surface - surface: ToplevelSurface, + 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, + surface: ToplevelSurface, }, /// A toplevel surface requested to be minimized Minimize { /// The surface - surface: ToplevelSurface, + surface: ToplevelSurface, }, /// The client requests the window menu to be displayed on this surface at this location /// @@ -1751,7 +1567,7 @@ pub enum XdgRequest { /// control of the window (maximize/minimize/close/move/etc...). ShowWindowMenu { /// The surface - surface: ToplevelSurface, + surface: ToplevelSurface, /// The seat associated with this input grab seat: wl_seat::WlSeat, /// the grab serial diff --git a/src/wayland/shell/xdg/xdg_handlers.rs b/src/wayland/shell/xdg/xdg_handlers.rs index 5bb03d0..3663321 100644 --- a/src/wayland/shell/xdg/xdg_handlers.rs +++ b/src/wayland/shell/xdg/xdg_handlers.rs @@ -1,7 +1,8 @@ +use std::sync::atomic::{AtomicBool, Ordering}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; -use crate::wayland::compositor::{roles::*, CompositorToken}; -use crate::wayland::shell::xdg::{ConfigureError, PopupState}; +use crate::wayland::compositor; +use crate::wayland::shell::xdg::PopupState; use crate::wayland::Serial; use wayland_protocols::xdg_shell::server::{ xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, @@ -12,25 +13,25 @@ use crate::utils::Rectangle; use super::{ make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData, - ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole, - XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes, + ShellData, SurfaceCachedState, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRoleAttributes, + XdgRequest, XdgToplevelSurfaceRoleAttributes, }; -pub(crate) fn implement_wm_base( +static XDG_TOPLEVEL_ROLE: &str = "xdg_toplevel"; +static XDG_POPUP_ROLE: &str = "xdg_toplevel"; + +pub(crate) fn implement_wm_base( shell: Main, - shell_data: &ShellData, -) -> xdg_wm_base::XdgWmBase -where - R: Role + Role + 'static, -{ - shell.quick_assign(|shell, req, _data| wm_implementation::(req, shell.deref().clone())); + shell_data: &ShellData, +) -> xdg_wm_base::XdgWmBase { + shell.quick_assign(|shell, req, _data| wm_implementation(req, shell.deref().clone())); shell.as_ref().user_data().set(|| ShellUserData { shell_data: shell_data.clone(), client_data: Mutex::new(make_shell_client_data()), }); let mut user_impl = shell_data.user_impl.borrow_mut(); (&mut *user_impl)(XdgRequest::NewClient { - client: make_shell_client(&shell, shell_data.compositor_token), + client: make_shell_client(&shell), }); shell.deref().clone() } @@ -39,26 +40,19 @@ where * xdg_shell */ -pub(crate) struct ShellUserData { - shell_data: ShellData, +pub(crate) struct ShellUserData { + shell_data: ShellData, pub(crate) client_data: Mutex, } -pub(crate) fn make_shell_client( - resource: &xdg_wm_base::XdgWmBase, - token: CompositorToken, -) -> ShellClient { +pub(crate) fn make_shell_client(resource: &xdg_wm_base::XdgWmBase) -> ShellClient { ShellClient { kind: super::ShellClientKind::Xdg(resource.clone()), - _token: token, } } -fn wm_implementation(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) -where - R: Role + Role + 'static, -{ - let data = shell.as_ref().user_data().get::>().unwrap(); +fn wm_implementation(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) { + let data = shell.as_ref().user_data().get::().unwrap(); match request { xdg_wm_base::Request::Destroy => { // all is handled by destructor @@ -70,14 +64,13 @@ where // Do not assign a role to the surface here // xdg_surface is not role, only xdg_toplevel and // xdg_popup are defined as roles - id.quick_assign(|surface, req, _data| { - xdg_surface_implementation::(req, surface.deref().clone()) - }); - id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface::(surface))); + id.quick_assign(|surface, req, _data| xdg_surface_implementation(req, surface.deref().clone())); + id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface(surface))); id.as_ref().user_data().set(|| XdgSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: surface, wm_base: shell.clone(), + has_active_role: AtomicBool::new(false), }); } xdg_wm_base::Request::Pong { serial } => { @@ -94,7 +87,7 @@ where if valid { let mut user_impl = data.shell_data.user_impl.borrow_mut(); (&mut *user_impl)(XdgRequest::ClientPong { - client: make_shell_client(&shell, data.shell_data.compositor_token), + client: make_shell_client(&shell), }); } } @@ -169,21 +162,15 @@ fn implement_positioner(positioner: Main) -> xdg_ * xdg_surface */ -struct XdgSurfaceUserData { - shell_data: ShellData, +struct XdgSurfaceUserData { + shell_data: ShellData, wl_surface: wl_surface::WlSurface, wm_base: xdg_wm_base::XdgWmBase, + has_active_role: AtomicBool, } -fn destroy_surface(surface: xdg_surface::XdgSurface) -where - R: Role + Role + 'static, -{ - let data = surface - .as_ref() - .user_data() - .get::>() - .unwrap(); +fn destroy_surface(surface: xdg_surface::XdgSurface) { + let data = surface.as_ref().user_data().get::().unwrap(); if !data.wl_surface.as_ref().is_alive() { // the wl_surface is destroyed, this means the client is not // trying to change the role but it's a cleanup (possibly a @@ -191,25 +178,12 @@ where return; } - if !data.shell_data.compositor_token.has_a_role(&data.wl_surface) { + if compositor::get_role(&data.wl_surface).is_none() { // No role assigned to the surface, we can exit early. return; } - let has_active_xdg_role = data - .shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| { - role.is_some() - }) - .unwrap_or(false) - || data - .shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role: &mut XdgPopupSurfaceRole| role.is_some()) - .unwrap_or(false); - - if has_active_xdg_role { + if data.has_active_role.load(Ordering::Acquire) { data.wm_base.as_ref().post_error( xdg_wm_base::Error::Role as u32, "xdg_surface was destroyed before its role object".into(), @@ -217,14 +191,11 @@ where } } -fn xdg_surface_implementation(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) -where - R: Role + Role + 'static, -{ +fn xdg_surface_implementation(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) { let data = xdg_surface .as_ref() .user_data() - .get::>() + .get::() .unwrap(); match request { xdg_surface::Request::Destroy => { @@ -235,14 +206,7 @@ where let surface = &data.wl_surface; let shell = &data.wm_base; - let role_data = XdgToplevelSurfaceRole::Some(Default::default()); - - if data - .shell_data - .compositor_token - .give_role_with(&surface, role_data) - .is_err() - { + if compositor::give_role(surface, XDG_TOPLEVEL_ROLE).is_err() { shell.as_ref().post_error( xdg_wm_base::Error::Role as u32, "Surface already has a role.".into(), @@ -250,10 +214,19 @@ where return; } - id.quick_assign(|toplevel, req, _data| { - toplevel_implementation::(req, toplevel.deref().clone()) - }); - id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel::(toplevel))); + data.has_active_role.store(true, Ordering::Release); + + compositor::with_states(surface, |states| { + states + .data_map + .insert_if_missing_threadsafe(|| Mutex::new(XdgToplevelSurfaceRoleAttributes::default())) + }) + .unwrap(); + + compositor::add_commit_hook(surface, super::ToplevelSurface::commit_hook); + + id.quick_assign(|toplevel, req, _data| toplevel_implementation(req, toplevel.deref().clone())); + id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel(toplevel))); id.as_ref().user_data().set(|| ShellSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: data.wl_surface.clone(), @@ -286,11 +259,7 @@ where .clone(); let parent_surface = parent.map(|parent| { - let parent_data = parent - .as_ref() - .user_data() - .get::>() - .unwrap(); + let parent_data = parent.as_ref().user_data().get::().unwrap(); parent_data.wl_surface.clone() }); @@ -298,21 +267,15 @@ where let surface = &data.wl_surface; let shell = &data.wm_base; - let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes { + let attributes = XdgPopupSurfaceRoleAttributes { parent: parent_surface, server_pending: Some(PopupState { // Set the positioner data as the popup geometry geometry: positioner_data.get_geometry(), }), ..Default::default() - }); - - if data - .shell_data - .compositor_token - .give_role_with(&surface, role_data) - .is_err() - { + }; + if compositor::give_role(surface, XDG_POPUP_ROLE).is_err() { shell.as_ref().post_error( xdg_wm_base::Error::Role as u32, "Surface already has a role.".into(), @@ -320,8 +283,25 @@ where return; } - id.quick_assign(|popup, req, _data| xdg_popup_implementation::(req, popup.deref().clone())); - id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::(popup))); + data.has_active_role.store(true, Ordering::Release); + + compositor::with_states(surface, |states| { + states + .data_map + .insert_if_missing_threadsafe(|| Mutex::new(XdgPopupSurfaceRoleAttributes::default())); + *states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() = attributes; + }) + .unwrap(); + + compositor::add_commit_hook(surface, super::PopupSurface::commit_hook); + + id.quick_assign(|popup, req, _data| xdg_popup_implementation(req, popup.deref().clone())); + id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup(popup))); id.as_ref().user_data().set(|| ShellSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: data.wl_surface.clone(), @@ -346,33 +326,28 @@ where // which is a protocol error. let surface = &data.wl_surface; - if !data.shell_data.compositor_token.has_a_role(surface) { - data.wm_base.as_ref().post_error( + let role = compositor::get_role(surface); + + if role.is_none() { + xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, "xdg_surface must have a role.".into(), ); return; } - // Set the next window geometry here, the geometry will be moved from - // next to the current geometry on a commit. This has to be done currently - // in anvil as the whole commit logic is implemented there until a proper - // abstraction has been found to handle commits within roles. This also - // ensures that a commit for a xdg_surface follows the rules for subsurfaces. - let has_wrong_role = data - .shell_data - .compositor_token - .with_xdg_role(surface, |role| { - role.set_window_geometry(Rectangle { x, y, width, height }) - }) - .is_err(); - - if has_wrong_role { + if role != Some(XDG_TOPLEVEL_ROLE) && role != Some(XDG_POPUP_ROLE) { data.wm_base.as_ref().post_error( xdg_wm_base::Error::Role as u32, "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), ); } + + compositor::with_states(surface, |states| { + states.cached_state.pending::().geometry = + Some(Rectangle { x, y, width, height }); + }) + .unwrap(); } xdg_surface::Request::AckConfigure { serial } => { let serial = Serial::from(serial); @@ -381,8 +356,8 @@ where // Check the role of the surface, this can be either xdg_toplevel // or xdg_popup. If none of the role matches the xdg_surface has no role set // which is a protocol error. - if !data.shell_data.compositor_token.has_a_role(surface) { - data.wm_base.as_ref().post_error( + if compositor::get_role(surface).is_none() { + xdg_surface.as_ref().post_error( xdg_surface::Error::NotConstructed as u32, "xdg_surface must have a role.".into(), ); @@ -401,21 +376,38 @@ where // width, height, min/max size, maximized, fullscreen, resizing, activated // // This can be used to integrate custom protocol extensions - // - let configure = match data - .shell_data - .compositor_token - .with_xdg_role(surface, |role| role.ack_configure(serial)) - { - Ok(Ok(configure)) => configure, - Ok(Err(ConfigureError::SerialNotFound(serial))) => { + let found_configure = compositor::with_states(surface, |states| { + if states.role == Some(XDG_TOPLEVEL_ROLE) { + Ok(states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .ack_configure(serial)) + } else if states.role == Some(XDG_POPUP_ROLE) { + Ok(states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .ack_configure(serial)) + } else { + Err(()) + } + }) + .unwrap(); + let configure = match found_configure { + Ok(Some(configure)) => configure, + Ok(None) => { data.wm_base.as_ref().post_error( xdg_wm_base::Error::InvalidSurfaceState as u32, format!("wrong configure serial: {}", ::from(serial)), ); return; } - Err(_) => { + Err(()) => { data.wm_base.as_ref().post_error( xdg_wm_base::Error::Role as u32, "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), @@ -438,64 +430,54 @@ where * xdg_toplevel */ -pub(crate) struct ShellSurfaceUserData { - pub(crate) shell_data: ShellData, +pub(crate) struct ShellSurfaceUserData { + pub(crate) shell_data: ShellData, pub(crate) wl_surface: wl_surface::WlSurface, pub(crate) wm_base: xdg_wm_base::XdgWmBase, pub(crate) xdg_surface: xdg_surface::XdgSurface, } // Utility functions allowing to factor out a lot of the upcoming logic -fn with_surface_toplevel_role_data( - shell_data: &ShellData, - toplevel: &xdg_toplevel::XdgToplevel, - f: F, -) -> T +fn with_surface_toplevel_role_data(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T where - R: Role + 'static, F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T, { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); - shell_data - .compositor_token - .with_role_data::(&data.wl_surface, |role| { - let attributes = role - .as_mut() - .expect("xdg_toplevel exists but role has been destroyed?!"); - f(attributes) - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!") -} - -fn with_surface_toplevel_client_pending( - shell_data: &ShellData, - toplevel: &xdg_toplevel::XdgToplevel, - f: F, -) -> T -where - R: Role + 'static, - F: FnOnce(&mut ToplevelClientPending) -> T, -{ - with_surface_toplevel_role_data(shell_data, toplevel, |data| { - if data.client_pending.is_none() { - data.client_pending = Some(Default::default()); - } - f(&mut data.client_pending.as_mut().unwrap()) + compositor::with_states(&data.wl_surface, |states| { + f(&mut *states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap()) }) + .unwrap() } -pub fn send_toplevel_configure(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) +fn with_toplevel_pending_state(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T where - R: Role + 'static, + F: FnOnce(&mut SurfaceCachedState) -> T, { + let data = toplevel + .as_ref() + .user_data() + .get::() + .unwrap(); + compositor::with_states(&data.wl_surface, |states| { + f(&mut *states.cached_state.pending::()) + }) + .unwrap() +} + +pub fn send_toplevel_configure(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let (width, height) = configure.state.size.unwrap_or((0, 0)); @@ -518,27 +500,23 @@ where data.xdg_surface.configure(serial.into()); } -fn make_toplevel_handle(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface { +fn make_toplevel_handle(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); super::ToplevelSurface { wl_surface: data.wl_surface.clone(), shell_surface: ToplevelKind::Xdg(resource.clone()), - token: data.shell_data.compositor_token, } } -fn toplevel_implementation(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) -where - R: Role + 'static, -{ +fn toplevel_implementation(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); match request { xdg_toplevel::Request::Destroy => { @@ -546,12 +524,12 @@ where } xdg_toplevel::Request::SetParent { parent } => { // Parent is not double buffered, we can set it directly - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| { + with_surface_toplevel_role_data(&toplevel, |data| { data.parent = parent.map(|toplevel_surface_parent| { toplevel_surface_parent .as_ref() .user_data() - .get::>() + .get::() .unwrap() .wl_surface .clone() @@ -560,13 +538,13 @@ where } xdg_toplevel::Request::SetTitle { title } => { // Title is not double buffered, we can set it directly - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| { + with_surface_toplevel_role_data(&toplevel, |data| { data.title = Some(title); }); } xdg_toplevel::Request::SetAppId { app_id } => { // AppId is not double buffered, we can set it directly - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| { + with_surface_toplevel_role_data(&toplevel, |role| { role.app_id = Some(app_id); }); } @@ -606,13 +584,13 @@ where }); } xdg_toplevel::Request::SetMaxSize { width, height } => { - with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { - toplevel_data.max_size = Some((width, height)); + with_toplevel_pending_state(&toplevel, |toplevel_data| { + toplevel_data.max_size = (width, height); }); } xdg_toplevel::Request::SetMinSize { width, height } => { - with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { - toplevel_data.min_size = Some((width, height)); + with_toplevel_pending_state(&toplevel, |toplevel_data| { + toplevel_data.min_size = (width, height); }); } xdg_toplevel::Request::SetMaximized => { @@ -649,26 +627,14 @@ where } } -fn destroy_toplevel(toplevel: xdg_toplevel::XdgToplevel) -where - R: Role + 'static, -{ +fn destroy_toplevel(toplevel: xdg_toplevel::XdgToplevel) { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); - if !data.wl_surface.as_ref().is_alive() { - // the wl_surface is destroyed, this means the client is not - // trying to change the role but it's a cleanup (possibly a - // disconnecting client), ignore the protocol check. - } else { - data.shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role_data| { - *role_data = XdgToplevelSurfaceRole::None; - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); + if let Some(data) = data.xdg_surface.as_ref().user_data().get::() { + data.has_active_role.store(false, Ordering::Release); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data @@ -683,14 +649,11 @@ where * xdg_popup */ -pub(crate) fn send_popup_configure(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) -where - R: Role + 'static, -{ +pub(crate) fn send_popup_configure(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let serial = configure.serial; @@ -704,28 +667,20 @@ where data.xdg_surface.configure(serial.into()); } -fn make_popup_handle(resource: &xdg_popup::XdgPopup) -> super::PopupSurface { +fn make_popup_handle(resource: &xdg_popup::XdgPopup) -> super::PopupSurface { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); super::PopupSurface { wl_surface: data.wl_surface.clone(), shell_surface: PopupKind::Xdg(resource.clone()), - token: data.shell_data.compositor_token, } } -fn xdg_popup_implementation(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) -where - R: Role + 'static, -{ - let data = popup - .as_ref() - .user_data() - .get::>() - .unwrap(); +fn xdg_popup_implementation(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) { + let data = popup.as_ref().user_data().get::().unwrap(); match request { xdg_popup::Request::Destroy => { // all is handled by our destructor @@ -744,26 +699,10 @@ where } } -fn destroy_popup(popup: xdg_popup::XdgPopup) -where - R: Role + 'static, -{ - let data = popup - .as_ref() - .user_data() - .get::>() - .unwrap(); - if !data.wl_surface.as_ref().is_alive() { - // the wl_surface is destroyed, this means the client is not - // trying to change the role but it's a cleanup (possibly a - // disconnecting client), ignore the protocol check. - } else { - data.shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role_data| { - *role_data = XdgPopupSurfaceRole::None; - }) - .expect("xdg_popup exists but surface has not shell_surface role?!"); +fn destroy_popup(popup: xdg_popup::XdgPopup) { + let data = popup.as_ref().user_data().get::().unwrap(); + if let Some(data) = data.xdg_surface.as_ref().user_data().get::() { + data.has_active_role.store(false, Ordering::Release); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data diff --git a/src/wayland/shell/xdg/zxdgv6_handlers.rs b/src/wayland/shell/xdg/zxdgv6_handlers.rs index d77db06..b762721 100644 --- a/src/wayland/shell/xdg/zxdgv6_handlers.rs +++ b/src/wayland/shell/xdg/zxdgv6_handlers.rs @@ -1,7 +1,8 @@ +use std::sync::atomic::{AtomicBool, Ordering}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; -use crate::wayland::compositor::{roles::*, CompositorToken}; -use crate::wayland::shell::xdg::{ConfigureError, PopupState}; +use crate::wayland::compositor; +use crate::wayland::shell::xdg::PopupState; use crate::wayland::Serial; use wayland_protocols::{ unstable::xdg_shell::v6::server::{ @@ -15,25 +16,25 @@ use crate::utils::Rectangle; use super::{ make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData, - ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole, - XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes, + ShellData, SurfaceCachedState, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRoleAttributes, + XdgRequest, XdgToplevelSurfaceRoleAttributes, }; -pub(crate) fn implement_shell( +static ZXDG_TOPLEVEL_ROLE: &str = "zxdg_toplevel"; +static ZXDG_POPUP_ROLE: &str = "zxdg_toplevel"; + +pub(crate) fn implement_shell( shell: Main, - shell_data: &ShellData, -) -> zxdg_shell_v6::ZxdgShellV6 -where - R: Role + Role + 'static, -{ - shell.quick_assign(|shell, req, _data| shell_implementation::(req, shell.deref().clone())); + shell_data: &ShellData, +) -> zxdg_shell_v6::ZxdgShellV6 { + shell.quick_assign(|shell, req, _data| shell_implementation(req, shell.deref().clone())); shell.as_ref().user_data().set(|| ShellUserData { shell_data: shell_data.clone(), client_data: Mutex::new(make_shell_client_data()), }); let mut user_impl = shell_data.user_impl.borrow_mut(); (&mut *user_impl)(XdgRequest::NewClient { - client: make_shell_client(&shell, shell_data.compositor_token), + client: make_shell_client(&shell), }); shell.deref().clone() } @@ -42,26 +43,19 @@ where * xdg_shell */ -pub(crate) struct ShellUserData { - shell_data: ShellData, +pub(crate) struct ShellUserData { + shell_data: ShellData, pub(crate) client_data: Mutex, } -pub(crate) fn make_shell_client( - resource: &zxdg_shell_v6::ZxdgShellV6, - token: CompositorToken, -) -> ShellClient { +pub(crate) fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient { ShellClient { kind: super::ShellClientKind::ZxdgV6(resource.clone()), - _token: token, } } -fn shell_implementation(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6) -where - R: Role + Role + 'static, -{ - let data = shell.as_ref().user_data().get::>().unwrap(); +fn shell_implementation(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6) { + let data = shell.as_ref().user_data().get::().unwrap(); match request { zxdg_shell_v6::Request::Destroy => { // all is handled by destructor @@ -70,14 +64,13 @@ where implement_positioner(id); } zxdg_shell_v6::Request::GetXdgSurface { id, surface } => { - id.quick_assign(|surface, req, _data| { - xdg_surface_implementation::(req, surface.deref().clone()) - }); - id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface::(surface))); + id.quick_assign(|surface, req, _data| xdg_surface_implementation(req, surface.deref().clone())); + id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface(surface))); id.as_ref().user_data().set(|| XdgSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: surface, shell: shell.clone(), + has_active_role: AtomicBool::new(false), }); } zxdg_shell_v6::Request::Pong { serial } => { @@ -94,7 +87,7 @@ where if valid { let mut user_impl = data.shell_data.user_impl.borrow_mut(); (&mut *user_impl)(XdgRequest::ClientPong { - client: make_shell_client(&shell, data.shell_data.compositor_token), + client: make_shell_client(&shell), }); } } @@ -185,21 +178,15 @@ fn implement_positioner( * xdg_surface */ -struct XdgSurfaceUserData { - shell_data: ShellData, +struct XdgSurfaceUserData { + shell_data: ShellData, wl_surface: wl_surface::WlSurface, shell: zxdg_shell_v6::ZxdgShellV6, + has_active_role: AtomicBool, } -fn destroy_surface(surface: zxdg_surface_v6::ZxdgSurfaceV6) -where - R: Role + Role + 'static, -{ - let data = surface - .as_ref() - .user_data() - .get::>() - .unwrap(); +fn destroy_surface(surface: zxdg_surface_v6::ZxdgSurfaceV6) { + let data = surface.as_ref().user_data().get::().unwrap(); if !data.wl_surface.as_ref().is_alive() { // the wl_surface is destroyed, this means the client is not // trying to change the role but it's a cleanup (possibly a @@ -207,25 +194,12 @@ where return; } - if !data.shell_data.compositor_token.has_a_role(&data.wl_surface) { + if compositor::get_role(&data.wl_surface).is_none() { // No role assigned to the surface, we can exit early. return; } - let has_active_xdg_role = data - .shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| { - role.is_some() - }) - .unwrap_or(false) - || data - .shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role: &mut XdgPopupSurfaceRole| role.is_some()) - .unwrap_or(false); - - if has_active_xdg_role { + if data.has_active_role.load(Ordering::Acquire) { data.shell.as_ref().post_error( zxdg_shell_v6::Error::Role as u32, "xdg_surface was destroyed before its role object".into(), @@ -233,16 +207,14 @@ where } } -fn xdg_surface_implementation( +fn xdg_surface_implementation( request: zxdg_surface_v6::Request, xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6, -) where - R: Role + Role + 'static, -{ +) { let data = xdg_surface .as_ref() .user_data() - .get::>() + .get::() .unwrap(); match request { zxdg_surface_v6::Request::Destroy => { @@ -253,14 +225,7 @@ fn xdg_surface_implementation( let surface = &data.wl_surface; let shell = &data.shell; - let role_data = XdgToplevelSurfaceRole::Some(Default::default()); - - if data - .shell_data - .compositor_token - .give_role_with(&surface, role_data) - .is_err() - { + if compositor::give_role(surface, ZXDG_TOPLEVEL_ROLE).is_err() { shell.as_ref().post_error( zxdg_shell_v6::Error::Role as u32, "Surface already has a role.".into(), @@ -268,10 +233,19 @@ fn xdg_surface_implementation( return; } - id.quick_assign(|toplevel, req, _data| { - toplevel_implementation::(req, toplevel.deref().clone()) - }); - id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel::(toplevel))); + data.has_active_role.store(true, Ordering::Release); + + compositor::with_states(surface, |states| { + states + .data_map + .insert_if_missing_threadsafe(|| Mutex::new(XdgToplevelSurfaceRoleAttributes::default())) + }) + .unwrap(); + + compositor::add_commit_hook(surface, super::ToplevelSurface::commit_hook); + + id.quick_assign(|toplevel, req, _data| toplevel_implementation(req, toplevel.deref().clone())); + id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel(toplevel))); id.as_ref().user_data().set(|| ShellSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: data.wl_surface.clone(), @@ -304,11 +278,7 @@ fn xdg_surface_implementation( .clone(); let parent_surface = { - let parent_data = parent - .as_ref() - .user_data() - .get::>() - .unwrap(); + let parent_data = parent.as_ref().user_data().get::().unwrap(); parent_data.wl_surface.clone() }; @@ -316,21 +286,15 @@ fn xdg_surface_implementation( let surface = &data.wl_surface; let shell = &data.shell; - let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes { + let attributes = XdgPopupSurfaceRoleAttributes { parent: Some(parent_surface), server_pending: Some(PopupState { // Set the positioner data as the popup geometry geometry: positioner_data.get_geometry(), }), ..Default::default() - }); - - if data - .shell_data - .compositor_token - .give_role_with(&surface, role_data) - .is_err() - { + }; + if compositor::give_role(surface, ZXDG_POPUP_ROLE).is_err() { shell.as_ref().post_error( zxdg_shell_v6::Error::Role as u32, "Surface already has a role.".into(), @@ -338,8 +302,25 @@ fn xdg_surface_implementation( return; } - id.quick_assign(|popup, req, _data| popup_implementation::(req, popup.deref().clone())); - id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::(popup))); + data.has_active_role.store(true, Ordering::Release); + + compositor::with_states(surface, |states| { + states + .data_map + .insert_if_missing_threadsafe(|| Mutex::new(XdgPopupSurfaceRoleAttributes::default())); + *states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() = attributes; + }) + .unwrap(); + + compositor::add_commit_hook(surface, super::PopupSurface::commit_hook); + + id.quick_assign(|popup, req, _data| popup_implementation(req, popup.deref().clone())); + id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup(popup))); id.as_ref().user_data().set(|| ShellSurfaceUserData { shell_data: data.shell_data.clone(), wl_surface: data.wl_surface.clone(), @@ -364,33 +345,28 @@ fn xdg_surface_implementation( // which is a protocol error. let surface = &data.wl_surface; - if !data.shell_data.compositor_token.has_a_role(surface) { - data.shell.as_ref().post_error( + let role = compositor::get_role(surface); + + if role.is_none() { + xdg_surface.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, "xdg_surface must have a role.".into(), ); return; } - // Set the next window geometry here, the geometry will be moved from - // next to the current geometry on a commit. This has to be done currently - // in anvil as the whole commit logic is implemented there until a proper - // abstraction has been found to handle commits within roles. This also - // ensures that a commit for a xdg_surface follows the rules for subsurfaces. - let has_wrong_role = data - .shell_data - .compositor_token - .with_xdg_role(surface, |role| { - role.set_window_geometry(Rectangle { x, y, width, height }) - }) - .is_err(); - - if has_wrong_role { + if role != Some(ZXDG_TOPLEVEL_ROLE) && role != Some(ZXDG_POPUP_ROLE) { data.shell.as_ref().post_error( zxdg_shell_v6::Error::Role as u32, "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), ); } + + compositor::with_states(surface, |states| { + states.cached_state.pending::().geometry = + Some(Rectangle { x, y, width, height }); + }) + .unwrap(); } zxdg_surface_v6::Request::AckConfigure { serial } => { let serial = Serial::from(serial); @@ -399,7 +375,7 @@ fn xdg_surface_implementation( // Check the role of the surface, this can be either xdg_toplevel // or xdg_popup. If none of the role matches the xdg_surface has no role set // which is a protocol error. - if !data.shell_data.compositor_token.has_a_role(surface) { + if compositor::get_role(surface).is_none() { data.shell.as_ref().post_error( zxdg_surface_v6::Error::NotConstructed as u32, "xdg_surface must have a role.".into(), @@ -420,13 +396,31 @@ fn xdg_surface_implementation( // // This can be used to integrate custom protocol extensions // - let configure = match data - .shell_data - .compositor_token - .with_xdg_role(surface, |role| role.ack_configure(serial)) - { - Ok(Ok(configure)) => configure, - Ok(Err(ConfigureError::SerialNotFound(serial))) => { + let found_configure = compositor::with_states(surface, |states| { + if states.role == Some(ZXDG_TOPLEVEL_ROLE) { + Ok(states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .ack_configure(serial)) + } else if states.role == Some(ZXDG_POPUP_ROLE) { + Ok(states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .ack_configure(serial)) + } else { + Err(()) + } + }) + .unwrap(); + let configure = match found_configure { + Ok(Some(configure)) => configure, + Ok(None) => { data.shell.as_ref().post_error( zxdg_shell_v6::Error::InvalidSurfaceState as u32, format!("wrong configure serial: {}", ::from(serial)), @@ -456,64 +450,54 @@ fn xdg_surface_implementation( * xdg_toplevel */ -pub struct ShellSurfaceUserData { - pub(crate) shell_data: ShellData, +pub struct ShellSurfaceUserData { + pub(crate) shell_data: ShellData, pub(crate) wl_surface: wl_surface::WlSurface, pub(crate) shell: zxdg_shell_v6::ZxdgShellV6, pub(crate) xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6, } // Utility functions allowing to factor out a lot of the upcoming logic -fn with_surface_toplevel_role_data( - shell_data: &ShellData, - toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, - f: F, -) -> T +fn with_surface_toplevel_role_data(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) -> T where - R: Role + 'static, F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T, { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); - shell_data - .compositor_token - .with_role_data::(&data.wl_surface, |role| { - let attributes = role - .as_mut() - .expect("xdg_toplevel exists but role has been destroyed?!"); - f(attributes) - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!") -} - -fn with_surface_toplevel_client_pending( - shell_data: &ShellData, - toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, - f: F, -) -> T -where - R: Role + 'static, - F: FnOnce(&mut ToplevelClientPending) -> T, -{ - with_surface_toplevel_role_data(shell_data, toplevel, |data| { - if data.client_pending.is_none() { - data.client_pending = Some(Default::default()); - } - f(&mut data.client_pending.as_mut().unwrap()) + compositor::with_states(&data.wl_surface, |states| { + f(&mut *states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap()) }) + .unwrap() } -pub fn send_toplevel_configure(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure) +fn with_toplevel_pending_state(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) -> T where - R: Role + 'static, + F: FnOnce(&mut SurfaceCachedState) -> T, { + let data = toplevel + .as_ref() + .user_data() + .get::() + .unwrap(); + compositor::with_states(&data.wl_surface, |states| { + f(&mut *states.cached_state.pending::()) + }) + .unwrap() +} + +pub fn send_toplevel_configure(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure) { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let (width, height) = configure.state.size.unwrap_or((0, 0)); @@ -536,41 +520,35 @@ where data.xdg_surface.configure(serial.into()); } -fn make_toplevel_handle( - resource: &zxdg_toplevel_v6::ZxdgToplevelV6, -) -> super::ToplevelSurface { +fn make_toplevel_handle(resource: &zxdg_toplevel_v6::ZxdgToplevelV6) -> super::ToplevelSurface { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); super::ToplevelSurface { wl_surface: data.wl_surface.clone(), shell_surface: ToplevelKind::ZxdgV6(resource.clone()), - token: data.shell_data.compositor_token, } } -fn toplevel_implementation(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) -where - R: Role + 'static, -{ +fn toplevel_implementation(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); match request { zxdg_toplevel_v6::Request::Destroy => { // all it done by the destructor } zxdg_toplevel_v6::Request::SetParent { parent } => { - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| { + with_surface_toplevel_role_data(&toplevel, |data| { data.parent = parent.map(|toplevel_surface_parent| { let parent_data = toplevel_surface_parent .as_ref() .user_data() - .get::>() + .get::() .unwrap(); parent_data.wl_surface.clone() }) @@ -578,13 +556,13 @@ where } zxdg_toplevel_v6::Request::SetTitle { title } => { // Title is not double buffered, we can set it directly - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| { + with_surface_toplevel_role_data(&toplevel, |data| { data.title = Some(title); }); } zxdg_toplevel_v6::Request::SetAppId { app_id } => { // AppId is not double buffered, we can set it directly - with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| { + with_surface_toplevel_role_data(&toplevel, |role| { role.app_id = Some(app_id); }); } @@ -623,13 +601,13 @@ where }); } zxdg_toplevel_v6::Request::SetMaxSize { width, height } => { - with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { - toplevel_data.max_size = Some((width, height)); + with_toplevel_pending_state(&toplevel, |toplevel_data| { + toplevel_data.max_size = (width, height); }); } zxdg_toplevel_v6::Request::SetMinSize { width, height } => { - with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { - toplevel_data.min_size = Some((width, height)); + with_toplevel_pending_state(&toplevel, |toplevel_data| { + toplevel_data.min_size = (width, height); }); } zxdg_toplevel_v6::Request::SetMaximized => { @@ -666,26 +644,14 @@ where } } -fn destroy_toplevel(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) -where - R: Role + 'static, -{ +fn destroy_toplevel(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) { let data = toplevel .as_ref() .user_data() - .get::>() + .get::() .unwrap(); - if !data.wl_surface.as_ref().is_alive() { - // the wl_surface is destroyed, this means the client is not - // trying to change the role but it's a cleanup (possibly a - // disconnecting client), ignore the protocol check. - } else { - data.shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role_data| { - *role_data = XdgToplevelSurfaceRole::None; - }) - .expect("xdg_toplevel exists but surface has not shell_surface role?!"); + if let Some(data) = data.xdg_surface.as_ref().user_data().get::() { + data.has_active_role.store(false, Ordering::Release); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data @@ -700,14 +666,11 @@ where * xdg_popup */ -pub(crate) fn send_popup_configure(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure) -where - R: Role + 'static, -{ +pub(crate) fn send_popup_configure(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure) { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); let serial = configure.serial; @@ -721,28 +684,20 @@ where data.xdg_surface.configure(serial.into()); } -fn make_popup_handle(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface { +fn make_popup_handle(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface { let data = resource .as_ref() .user_data() - .get::>() + .get::() .unwrap(); super::PopupSurface { wl_surface: data.wl_surface.clone(), shell_surface: PopupKind::ZxdgV6(resource.clone()), - token: data.shell_data.compositor_token, } } -fn popup_implementation(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6) -where - R: Role + 'static, -{ - let data = popup - .as_ref() - .user_data() - .get::>() - .unwrap(); +fn popup_implementation(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6) { + let data = popup.as_ref().user_data().get::().unwrap(); match request { zxdg_popup_v6::Request::Destroy => { // all is handled by our destructor @@ -761,26 +716,10 @@ where } } -fn destroy_popup(popup: zxdg_popup_v6::ZxdgPopupV6) -where - R: Role + 'static, -{ - let data = popup - .as_ref() - .user_data() - .get::>() - .unwrap(); - if !data.wl_surface.as_ref().is_alive() { - // the wl_surface is destroyed, this means the client is not - // trying to change the role but it's a cleanup (possibly a - // disconnecting client), ignore the protocol check. - } else { - data.shell_data - .compositor_token - .with_role_data(&data.wl_surface, |role_data| { - *role_data = XdgPopupSurfaceRole::None; - }) - .expect("xdg_popup exists but surface has not shell_surface role?!"); +fn destroy_popup(popup: zxdg_popup_v6::ZxdgPopupV6) { + let data = popup.as_ref().user_data().get::().unwrap(); + if let Some(data) = data.xdg_surface.as_ref().user_data().get::() { + data.has_active_role.store(false, Ordering::Release); } // remove this surface from the known ones (as well as any leftover dead surface) data.shell_data