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.
This commit is contained in:
Victor Berger 2021-06-23 09:43:53 +02:00 committed by Victor Berger
parent 736e4e8bec
commit ad55ab71c9
32 changed files with 2277 additions and 2685 deletions

View File

@ -11,11 +11,12 @@ edition = "2018"
members = [ "anvil" ] members = [ "anvil" ]
[dependencies] [dependencies]
appendlist = "1.4"
bitflags = "1" bitflags = "1"
calloop = "0.8.0" calloop = "0.8.0"
cgmath = "0.18.0" cgmath = "0.18.0"
dbus = { version = "0.9.0", optional = true } 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-fourcc = "^2.1.1"
drm = { version = "0.4.0", optional = true } drm = { version = "0.4.0", optional = true }
drm-ffi = { version = "0.1.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 } image = { version = "0.23.14", default-features = false, optional = true }
lazy_static = "1" lazy_static = "1"
libc = "0.2.70" libc = "0.2.70"
libseat= { version = "0.1.1", optional = true }
libloading = "0.7.0" libloading = "0.7.0"
nix = "0.20" nix = "0.20"
slog = "2" slog = "2"

View File

@ -1,6 +1,6 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use std::cell::RefCell; use std::{cell::RefCell, sync::Mutex};
use slog::Logger; use slog::Logger;
use smithay::{ use smithay::{
@ -11,13 +11,15 @@ use smithay::{
reexports::wayland_server::protocol::{wl_buffer, wl_surface}, reexports::wayland_server::protocol::{wl_buffer, wl_surface},
utils::Rectangle, utils::Rectangle,
wayland::{ wayland::{
compositor::{roles::Role, Damage, SubsurfaceRole, TraversalAction}, compositor::{
data_device::DnDIconRole, get_role, with_states, with_surface_tree_upward, Damage, SubsurfaceCachedState,
seat::CursorImageRole, SurfaceAttributes, TraversalAction,
},
seat::CursorImageAttributes,
}, },
}; };
use crate::shell::{MyCompositorToken, MyWindowMap, SurfaceData}; use crate::{shell::SurfaceData, window_map::WindowMap};
struct BufferTextures<T> { struct BufferTextures<T> {
buffer: Option<wl_buffer::WlBuffer>, buffer: Option<wl_buffer::WlBuffer>,
@ -37,7 +39,6 @@ pub fn draw_cursor<R, E, F, T>(
frame: &mut F, frame: &mut F,
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
(x, y): (i32, i32), (x, y): (i32, i32),
token: MyCompositorToken,
log: &Logger, log: &Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where where
@ -46,9 +47,21 @@ where
E: std::error::Error + Into<SwapBuffersError>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
let (dx, dy) = match token.with_role_data::<CursorImageRole, _, _>(surface, |data| data.hotspot) { let ret = with_states(surface, |states| {
Ok(h) => h, Some(
Err(_) => { states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.unwrap()
.lock()
.unwrap()
.hotspot,
)
})
.unwrap_or(None);
let (dx, dy) = match ret {
Some(h) => h,
None => {
warn!( warn!(
log, log,
"Trying to display as a cursor a surface that does not have the CursorImage role." "Trying to display as a cursor a surface that does not have the CursorImage role."
@ -56,7 +69,7 @@ where
(0, 0) (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<R, E, F, T>( fn draw_surface_tree<R, E, F, T>(
@ -64,7 +77,6 @@ fn draw_surface_tree<R, E, F, T>(
frame: &mut F, frame: &mut F,
root: &wl_surface::WlSurface, root: &wl_surface::WlSurface,
location: (i32, i32), location: (i32, i32),
compositor_token: MyCompositorToken,
log: &Logger, log: &Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where where
@ -75,15 +87,16 @@ where
{ {
let mut result = Ok(()); let mut result = Ok(());
compositor_token.with_surface_tree_upward( with_surface_tree_upward(
root, root,
location, location,
|_surface, attributes, role, &(mut x, mut y)| { |_surface, states, &(mut x, mut y)| {
// Pull a new buffer if available // Pull a new buffer if available
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
let attributes = states.cached_state.current::<SurfaceAttributes>();
if data.texture.is_none() { if data.texture.is_none() {
if let Some(buffer) = data.current_state.buffer.take() { if let Some(buffer) = data.buffer.take() {
let damage = attributes let damage = attributes
.damage .damage
.iter() .iter()
@ -94,16 +107,18 @@ where
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match renderer.import_buffer(&buffer, Some(&attributes), &damage) { match renderer.import_buffer(&buffer, Some(states), &damage) {
Some(Ok(m)) => { Some(Ok(m)) => {
if let Some(BufferType::Shm) = buffer_type(&buffer) { let texture_buffer = if let Some(BufferType::Shm) = buffer_type(&buffer) {
buffer.release(); buffer.release();
} None
} else {
Some(buffer)
};
data.texture = Some(Box::new(BufferTextures { data.texture = Some(Box::new(BufferTextures {
buffer: Some(buffer), buffer: texture_buffer,
texture: m, texture: m,
}) }))
as Box<dyn std::any::Any + 'static>)
} }
Some(Err(err)) => { Some(Err(err)) => {
warn!(log, "Error loading buffer: {:?}", err); warn!(log, "Error loading buffer: {:?}", err);
@ -119,9 +134,10 @@ where
// Now, should we be drawn ? // Now, should we be drawn ?
if data.texture.is_some() { if data.texture.is_some() {
// if yes, also process the children // if yes, also process the children
if Role::<SubsurfaceRole>::has(role) { if states.role == Some("subsurface") {
x += data.current_state.sub_location.0; let current = states.cached_state.current::<SubsurfaceCachedState>();
y += data.current_state.sub_location.1; x += current.location.0;
y += current.location.1;
} }
TraversalAction::DoChildren((x, y)) TraversalAction::DoChildren((x, y))
} else { } else {
@ -133,10 +149,9 @@ where
TraversalAction::SkipChildren TraversalAction::SkipChildren
} }
}, },
|_surface, attributes, role, &(mut x, mut y)| { |_surface, states, &(mut x, mut y)| {
if let Some(ref data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(ref data) = states.data_map.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
let (sub_x, sub_y) = data.current_state.sub_location;
if let Some(texture) = data if let Some(texture) = data
.texture .texture
.as_mut() .as_mut()
@ -144,9 +159,10 @@ where
{ {
// we need to re-extract the subsurface offset, as the previous closure // we need to re-extract the subsurface offset, as the previous closure
// only passes it to our children // only passes it to our children
if Role::<SubsurfaceRole>::has(role) { if states.role == Some("subsurface") {
x += sub_x; let current = states.cached_state.current::<SubsurfaceCachedState>();
y += sub_y; x += current.location.0;
y += current.location.1;
} }
if let Err(err) = frame.render_texture_at( if let Err(err) = frame.render_texture_at(
&texture.texture, &texture.texture,
@ -159,7 +175,7 @@ where
} }
} }
}, },
|_, _, _, _| true, |_, _, _| true,
); );
result result
@ -168,9 +184,8 @@ where
pub fn draw_windows<R, E, F, T>( pub fn draw_windows<R, E, F, T>(
renderer: &mut R, renderer: &mut R,
frame: &mut F, frame: &mut F,
window_map: &MyWindowMap, window_map: &WindowMap,
output_rect: Option<Rectangle>, output_rect: Option<Rectangle>,
compositor_token: MyCompositorToken,
log: &::slog::Logger, log: &::slog::Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where where
@ -192,9 +207,7 @@ where
} }
if let Some(wl_surface) = toplevel_surface.get_surface() { if let Some(wl_surface) = toplevel_surface.get_surface() {
// this surface is a root of a subsurface tree that needs to be drawn // this surface is a root of a subsurface tree that needs to be drawn
if let Err(err) = if let Err(err) = draw_surface_tree(renderer, frame, &wl_surface, initial_place, log) {
draw_surface_tree(renderer, frame, &wl_surface, initial_place, compositor_token, log)
{
result = Err(err); result = Err(err);
} }
} }
@ -208,7 +221,6 @@ pub fn draw_dnd_icon<R, E, F, T>(
frame: &mut F, frame: &mut F,
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
(x, y): (i32, i32), (x, y): (i32, i32),
token: MyCompositorToken,
log: &::slog::Logger, log: &::slog::Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where where
@ -217,11 +229,11 @@ where
E: std::error::Error + Into<SwapBuffersError>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
if !token.has_role::<DnDIconRole>(surface) { if get_role(surface) != Some("dnd_icon") {
warn!( warn!(
log, log,
"Trying to display as a dnd icon a surface that does not have the DndIcon role." "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)
} }

View File

@ -2,8 +2,6 @@
#[macro_use] #[macro_use]
extern crate slog; extern crate slog;
#[macro_use(define_roles)]
extern crate smithay;
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};

View File

@ -9,25 +9,22 @@ use smithay::{
reexports::{ reexports::{
wayland_protocols::xdg_shell::server::xdg_toplevel, wayland_protocols::xdg_shell::server::xdg_toplevel,
wayland_server::{ 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, Display,
}, },
}, },
utils::Rectangle, utils::Rectangle,
wayland::{ wayland::{
compositor::{ compositor::{
compositor_init, roles::Role, BufferAssignment, CompositorToken, RegionAttributes, compositor_init, is_sync_subsurface, with_states, with_surface_tree_upward, BufferAssignment,
SubsurfaceRole, SurfaceEvent, TraversalAction, SurfaceAttributes, TraversalAction,
}, },
data_device::DnDIconRole, seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat},
seat::{AxisFrame, CursorImageRole, GrabStartData, PointerGrab, PointerInnerHandle, Seat},
shell::{ shell::{
legacy::{ legacy::{wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind},
wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind, ShellSurfaceRole,
},
xdg::{ xdg::{
xdg_shell_init, Configure, ShellState as XdgShellState, XdgPopupSurfaceRole, XdgRequest, xdg_shell_init, Configure, ShellState as XdgShellState, SurfaceCachedState, XdgRequest,
XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
}, },
}, },
Serial, Serial,
@ -39,27 +36,10 @@ use crate::{
window_map::{Kind as SurfaceKind, PopupKind, WindowMap}, 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<Roles>;
pub type MyCompositorToken = CompositorToken<Roles>;
struct MoveSurfaceGrab { struct MoveSurfaceGrab {
start_data: GrabStartData, start_data: GrabStartData,
window_map: Rc<RefCell<MyWindowMap>>, window_map: Rc<RefCell<WindowMap>>,
toplevel: SurfaceKind<Roles>, toplevel: SurfaceKind,
initial_window_location: (i32, i32), initial_window_location: (i32, i32),
} }
@ -150,8 +130,7 @@ impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
struct ResizeSurfaceGrab { struct ResizeSurfaceGrab {
start_data: GrabStartData, start_data: GrabStartData,
ctoken: MyCompositorToken, toplevel: SurfaceKind,
toplevel: SurfaceKind<Roles>,
edges: ResizeEdge, edges: ResizeEdge,
initial_window_size: (i32, i32), initial_window_size: (i32, i32),
last_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; new_window_height = (self.initial_window_size.1 as f64 + dy) as i32;
} }
let (min_size, max_size) = let (min_size, max_size) = with_states(self.toplevel.get_surface().unwrap(), |states| {
self.ctoken let data = states.cached_state.current::<SurfaceCachedState>();
.with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| {
let data = attrs.user_data.get::<RefCell<SurfaceData>>().unwrap().borrow();
(data.min_size, data.max_size) (data.min_size, data.max_size)
}); })
.unwrap();
let min_width = min_size.0.max(1); let min_width = min_size.0.max(1);
let min_height = min_size.1.max(1); let min_height = min_size.1.max(1);
@ -224,13 +202,11 @@ impl PointerGrab for ResizeSurfaceGrab {
match &self.toplevel { match &self.toplevel {
SurfaceKind::Xdg(xdg) => { SurfaceKind::Xdg(xdg) => {
if xdg let ret = xdg.with_pending_state(|state| {
.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Resizing); state.states.set(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size); state.size = Some(self.last_window_size);
}) });
.is_ok() if ret.is_ok() {
{
xdg.send_configure(); xdg.send_configure();
} }
} }
@ -264,20 +240,17 @@ impl PointerGrab for ResizeSurfaceGrab {
} }
if let SurfaceKind::Xdg(xdg) = &self.toplevel { if let SurfaceKind::Xdg(xdg) = &self.toplevel {
if xdg let ret = xdg.with_pending_state(|state| {
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Resizing); state.states.unset(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size); state.size = Some(self.last_window_size);
}) });
.is_ok() if ret.is_ok() {
{
xdg.send_configure(); xdg.send_configure();
} }
self.ctoken with_states(self.toplevel.get_surface().unwrap(), |states| {
.with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { let mut data = states
let mut data = attrs .data_map
.user_data
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
@ -286,12 +259,12 @@ impl PointerGrab for ResizeSurfaceGrab {
} else { } else {
panic!("invalid resize state: {:?}", data.resize_state); panic!("invalid resize state: {:?}", data.resize_state);
} }
}); })
.unwrap();
} else { } else {
self.ctoken with_states(self.toplevel.get_surface().unwrap(), |states| {
.with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { let mut data = states
let mut data = attrs .data_map
.user_data
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
@ -300,7 +273,8 @@ impl PointerGrab for ResizeSurfaceGrab {
} else { } else {
panic!("invalid resize state: {:?}", data.resize_state); panic!("invalid resize state: {:?}", data.resize_state);
} }
}); })
.unwrap();
} }
} }
} }
@ -316,34 +290,30 @@ impl PointerGrab for ResizeSurfaceGrab {
#[derive(Clone)] #[derive(Clone)]
pub struct ShellHandles { pub struct ShellHandles {
pub token: CompositorToken<Roles>, pub xdg_state: Arc<Mutex<XdgShellState>>,
pub xdg_state: Arc<Mutex<XdgShellState<Roles>>>, pub wl_state: Arc<Mutex<WlShellState>>,
pub wl_state: Arc<Mutex<WlShellState<Roles>>>, pub window_map: Rc<RefCell<WindowMap>>,
pub window_map: Rc<RefCell<MyWindowMap>>,
} }
pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logger) -> ShellHandles { pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logger) -> ShellHandles {
// Create the compositor // Create the compositor
let (compositor_token, _, _) = compositor_init( compositor_init(
display, display,
move |request, surface, ctoken, mut ddata| match request { move |surface, mut ddata| {
SurfaceEvent::Commit => {
let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap(); let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap();
let window_map = anvil_state.window_map.as_ref(); let window_map = anvil_state.window_map.as_ref();
surface_commit(&surface, ctoken, &*window_map) surface_commit(&surface, &*window_map)
}
}, },
log.clone(), log.clone(),
); );
// Init a window map, to track the location of our windows // 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 // init the xdg_shell
let xdg_window_map = window_map.clone(); let xdg_window_map = window_map.clone();
let (xdg_shell_state, _, _) = xdg_shell_init( let (xdg_shell_state, _, _) = xdg_shell_init(
display, display,
compositor_token,
move |shell_event| match shell_event { move |shell_event| match shell_event {
XdgRequest::NewToplevel { surface } => { XdgRequest::NewToplevel { surface } => {
// place the window at a random location in the [0;800]x[0;800] square // place the window at a random location in the [0;800]x[0;800] square
@ -441,9 +411,9 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
let geometry = xdg_window_map.borrow().geometry(&toplevel).unwrap(); let geometry = xdg_window_map.borrow().geometry(&toplevel).unwrap();
let initial_window_size = (geometry.width, geometry.height); let initial_window_size = (geometry.width, geometry.height);
compositor_token.with_surface_data(surface.get_surface().unwrap(), move |attrs| { with_states(surface.get_surface().unwrap(), move |states| {
attrs states
.user_data .data_map
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
@ -452,11 +422,11 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
initial_window_location, initial_window_location,
initial_window_size, initial_window_size,
}); });
}); })
.unwrap();
let grab = ResizeSurfaceGrab { let grab = ResizeSurfaceGrab {
start_data, start_data,
ctoken: compositor_token,
toplevel, toplevel,
edges: edges.into(), edges: edges.into(),
initial_window_size, initial_window_size,
@ -466,18 +436,20 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial);
} }
XdgRequest::AckConfigure { XdgRequest::AckConfigure {
surface, configure, .. surface,
configure: Configure::Toplevel(configure),
..
} => { } => {
if let Configure::Toplevel(configure) = configure { let waiting_for_serial = with_states(&surface, |states| {
let waiting_for_serial = compositor_token.with_surface_data(&surface, |attrs| { if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
if let Some(data) = attrs.user_data.get::<RefCell<SurfaceData>>() {
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state { if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
return Some(serial); return Some(serial);
} }
} }
None None
}); })
.unwrap();
if let Some(serial) = waiting_for_serial { if let Some(serial) = waiting_for_serial {
if configure.serial > serial { if configure.serial > serial {
@ -486,73 +458,64 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
// this case anyway // this case anyway
} }
if serial == configure.serial { if serial == configure.serial
if configure.state.states.contains(xdg_toplevel::State::Resizing) { && configure.state.states.contains(xdg_toplevel::State::Resizing)
compositor_token.with_surface_data(&surface, |attrs| { {
let mut data = attrs with_states(&surface, |states| {
.user_data let mut data = states
.data_map
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state {
{
data.resize_state = ResizeState::WaitingForCommit(resize_data); data.resize_state = ResizeState::WaitingForCommit(resize_data);
} else { } else {
unreachable!() unreachable!()
} }
}) })
} .unwrap();
}
} }
} }
} }
XdgRequest::Fullscreen { surface, output, .. } => { XdgRequest::Fullscreen { surface, output, .. } => {
if surface let ret = surface.with_pending_state(|state| {
.with_pending_state(|state| {
// TODO: Use size of current output the window is on and set position to (0,0) // TODO: Use size of current output the window is on and set position to (0,0)
state.states.set(xdg_toplevel::State::Fullscreen); state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some((800, 600)); state.size = Some((800, 600));
// TODO: If the provided output is None, use the output where // TODO: If the provided output is None, use the output where
// the toplevel is currently shown // the toplevel is currently shown
state.fullscreen_output = output; state.fullscreen_output = output;
}) });
.is_ok() if ret.is_ok() {
{
surface.send_configure(); surface.send_configure();
} }
} }
XdgRequest::UnFullscreen { surface } => { XdgRequest::UnFullscreen { surface } => {
if surface let ret = surface.with_pending_state(|state| {
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen); state.states.unset(xdg_toplevel::State::Fullscreen);
state.size = None; state.size = None;
state.fullscreen_output = None; state.fullscreen_output = None;
}) });
.is_ok() if ret.is_ok() {
{
surface.send_configure(); surface.send_configure();
} }
} }
XdgRequest::Maximize { surface } => { XdgRequest::Maximize { surface } => {
if surface let ret = surface.with_pending_state(|state| {
.with_pending_state(|state| {
// TODO: Use size of current output the window is on and set position to (0,0) // TODO: Use size of current output the window is on and set position to (0,0)
state.states.set(xdg_toplevel::State::Maximized); state.states.set(xdg_toplevel::State::Maximized);
state.size = Some((800, 600)); state.size = Some((800, 600));
}) });
.is_ok() if ret.is_ok() {
{
surface.send_configure(); surface.send_configure();
} }
} }
XdgRequest::UnMaximize { surface } => { XdgRequest::UnMaximize { surface } => {
if surface let ret = surface.with_pending_state(|state| {
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized); state.states.unset(xdg_toplevel::State::Maximized);
state.size = None; state.size = None;
}) });
.is_ok() if ret.is_ok() {
{
surface.send_configure(); surface.send_configure();
} }
} }
@ -565,8 +528,7 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
let shell_window_map = window_map.clone(); let shell_window_map = window_map.clone();
let (wl_shell_state, _) = wl_shell_init( let (wl_shell_state, _) = wl_shell_init(
display, display,
compositor_token, move |req: ShellRequest| {
move |req: ShellRequest<_>| {
match req { match req {
ShellRequest::SetKind { ShellRequest::SetKind {
surface, surface,
@ -658,9 +620,9 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
let geometry = shell_window_map.borrow().geometry(&toplevel).unwrap(); let geometry = shell_window_map.borrow().geometry(&toplevel).unwrap();
let initial_window_size = (geometry.width, geometry.height); let initial_window_size = (geometry.width, geometry.height);
compositor_token.with_surface_data(surface.get_surface().unwrap(), move |attrs| { with_states(surface.get_surface().unwrap(), move |states| {
attrs states
.user_data .data_map
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut() .borrow_mut()
@ -669,11 +631,11 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
initial_window_location, initial_window_location,
initial_window_size, initial_window_size,
}); });
}); })
.unwrap();
let grab = ResizeSurfaceGrab { let grab = ResizeSurfaceGrab {
start_data, start_data,
ctoken: compositor_token,
toplevel, toplevel,
edges: edges.into(), edges: edges.into(),
initial_window_size, initial_window_size,
@ -689,7 +651,6 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
); );
ShellHandles { ShellHandles {
token: compositor_token,
xdg_state: xdg_shell_state, xdg_state: xdg_shell_state,
wl_state: wl_shell_state, wl_state: wl_shell_state,
window_map, window_map,
@ -726,82 +687,43 @@ impl Default for ResizeState {
} }
} }
#[derive(Default, Clone)]
pub struct CommitedState {
pub buffer: Option<wl_buffer::WlBuffer>,
pub input_region: Option<RegionAttributes>,
pub dimensions: Option<(i32, i32)>,
pub frame_callbacks: Vec<wl_callback::WlCallback>,
pub sub_location: (i32, i32),
}
#[derive(Default)] #[derive(Default)]
pub struct SurfaceData { pub struct SurfaceData {
pub buffer: Option<wl_buffer::WlBuffer>,
pub texture: Option<Box<dyn std::any::Any + 'static>>, pub texture: Option<Box<dyn std::any::Any + 'static>>,
pub geometry: Option<Rectangle>, pub geometry: Option<Rectangle>,
pub resize_state: ResizeState, pub resize_state: ResizeState,
/// Minimum width and height, as requested by the surface. pub dimensions: Option<(i32, i32)>,
///
/// `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<CommitedState>,
} }
impl SurfaceData { impl SurfaceData {
/// Apply a next state into the surface current state pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) {
pub fn apply_state(&mut self, next_state: CommitedState) { match attrs.buffer.take() {
if Self::merge_state(&mut self.current_state, next_state) { Some(BufferAssignment::NewBuffer { buffer, .. }) => {
let _ = self.texture.take(); // 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;
}
Some(BufferAssignment::Removed) => {
// remove the contents
self.buffer = None;
self.dimensions = None;
self.texture = None;
}
None => {}
} }
} }
/// 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);
}
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();
}
}
*into = next;
new_buffer
}
}
impl SurfaceData {
/// Returns the size of the surface. /// Returns the size of the surface.
pub fn size(&self) -> Option<(i32, i32)> { pub fn size(&self) -> Option<(i32, i32)> {
self.current_state.dimensions self.dimensions
} }
/// Checks if the surface's input region contains the point. /// 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() { let (w, h) = match self.size() {
None => return false, // If the surface has no size, it can't have an input region. None => return false, // If the surface has no size, it can't have an input region.
Some(wh) => wh, Some(wh) => wh,
@ -823,170 +745,73 @@ impl SurfaceData {
} }
// If there's no input region, we're done. // 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; 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 /// Send the frame callback if it had been requested
pub fn send_frame(&mut self, time: u32) { pub fn send_frame(attrs: &mut SurfaceAttributes, time: u32) {
for callback in self.current_state.frame_callbacks.drain(..) { for callback in attrs.frame_callbacks.drain(..) {
callback.done(time); callback.done(time);
} }
} }
} }
fn surface_commit( fn surface_commit(surface: &wl_surface::WlSurface, window_map: &RefCell<WindowMap>) {
surface: &wl_surface::WlSurface,
token: CompositorToken<Roles>,
window_map: &RefCell<MyWindowMap>,
) {
#[cfg(feature = "xwayland")] #[cfg(feature = "xwayland")]
super::xwayland::commit_hook(surface); super::xwayland::commit_hook(surface);
let mut geometry = None; let mut window_map = window_map.borrow_mut();
let mut min_size = (0, 0);
let mut max_size = (0, 0);
if token.has_role::<XdgToplevelSurfaceRole>(surface) { if !is_sync_subsurface(surface) {
if let Some(SurfaceKind::Xdg(xdg)) = window_map.borrow().find(surface) { // Update the buffer of all child surfaces
xdg.commit(); with_surface_tree_upward(
} surface,
(),
token |_, _, _| TraversalAction::DoChildren(()),
.with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| { |_, states, _| {
if let XdgToplevelSurfaceRole::Some(attributes) = role { states
geometry = attributes.window_geometry; .data_map
min_size = attributes.min_size;
max_size = attributes.max_size;
}
})
.unwrap();
}
if token.has_role::<XdgPopupSurfaceRole>(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())); .insert_if_missing(|| RefCell::new(SurfaceData::default()));
let mut data = attributes let mut data = states
.user_data .data_map
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
data.update_buffer(&mut *states.cached_state.current::<SurfaceAttributes>());
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(
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::<SubsurfaceRole>::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!();
}
}, },
|_, attributes, role, _| { |_, _, _| true,
// only apply from cache if we are a sync subsurface );
if let Ok(sub_data) = Role::<SubsurfaceRole>::data(role) { }
if sub_data.sync {
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(toplevel) = window_map.find(surface) {
data.borrow_mut().apply_from_cache(); // send the initial configure if relevant
} if let SurfaceKind::Xdg(ref toplevel) = toplevel {
} let initial_configure_sent = with_states(surface, |states| {
} states
}, .data_map
|_, _, _, _| true, .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
) .unwrap()
.lock()
.unwrap()
.initial_configure_sent
})
.unwrap();
if !initial_configure_sent {
toplevel.send_configure();
}
} }
if let Some(toplevel) = refresh {
let mut window_map = window_map.borrow_mut();
window_map.refresh_toplevel(&toplevel); window_map.refresh_toplevel(&toplevel);
// Get the geometry outside since it uses the token, and so would block inside. // Get the geometry outside since it uses the token, and so would block inside.
let Rectangle { width, height, .. } = window_map.geometry(&toplevel).unwrap(); let Rectangle { width, height, .. } = window_map.geometry(&toplevel).unwrap();
let new_location = token.with_surface_data(surface, |attributes| { let new_location = with_states(surface, |states| {
let mut data = attributes let mut data = states
.user_data .data_map
.get::<RefCell<SurfaceData>>() .get::<RefCell<SurfaceData>>()
.unwrap() .unwrap()
.borrow_mut(); .borrow_mut();
@ -1027,7 +852,8 @@ fn surface_commit(
} }
new_location new_location
}); })
.unwrap();
if let Some(location) = new_location { if let Some(location) = new_location {
window_map.set_location(&toplevel, location); window_map.set_location(&toplevel, location);

View File

@ -13,7 +13,6 @@ use smithay::{
wayland_server::{protocol::wl_surface::WlSurface, Display}, wayland_server::{protocol::wl_surface::WlSurface, Display},
}, },
wayland::{ wayland::{
compositor::CompositorToken,
data_device::{default_action_chooser, init_data_device, set_data_device_focus, DataDeviceEvent}, data_device::{default_action_chooser, init_data_device, set_data_device_focus, DataDeviceEvent},
seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig}, seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig},
shm::init_shm_global, shm::init_shm_global,
@ -31,8 +30,7 @@ pub struct AnvilState<BackendData> {
pub running: Arc<AtomicBool>, pub running: Arc<AtomicBool>,
pub display: Rc<RefCell<Display>>, pub display: Rc<RefCell<Display>>,
pub handle: LoopHandle<'static, AnvilState<BackendData>>, pub handle: LoopHandle<'static, AnvilState<BackendData>>,
pub ctoken: CompositorToken<crate::shell::Roles>, pub window_map: Rc<RefCell<crate::window_map::WindowMap>>,
pub window_map: Rc<RefCell<crate::window_map::WindowMap<crate::shell::Roles>>>,
pub dnd_icon: Arc<Mutex<Option<WlSurface>>>, pub dnd_icon: Arc<Mutex<Option<WlSurface>>>,
pub log: slog::Logger, pub log: slog::Logger,
// input-related fields // input-related fields
@ -106,24 +104,18 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
_ => {} _ => {}
}, },
default_action_chooser, default_action_chooser,
shell_handles.token,
log.clone(), log.clone(),
); );
// init input // init input
let seat_name = backend_data.seat_name(); let seat_name = backend_data.seat_name();
let (mut seat, _) = Seat::new( let (mut seat, _) = Seat::new(&mut display.borrow_mut(), seat_name.clone(), log.clone());
&mut display.borrow_mut(),
seat_name.clone(),
shell_handles.token,
log.clone(),
);
let cursor_status = Arc::new(Mutex::new(CursorImageStatus::Default)); let cursor_status = Arc::new(Mutex::new(CursorImageStatus::Default));
let cursor_status2 = cursor_status.clone(); 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 // TODO: hide winit system cursor when relevant
*cursor_status2.lock().unwrap() = new_status *cursor_status2.lock().unwrap() = new_status
}); });
@ -155,7 +147,6 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
running: Arc::new(AtomicBool::new(true)), running: Arc::new(AtomicBool::new(true)),
display, display,
handle, handle,
ctoken: shell_handles.token,
window_map: shell_handles.window_map, window_map: shell_handles.window_map,
dnd_icon, dnd_icon,
log, log,

View File

@ -51,7 +51,6 @@ use smithay::{
signaling::{Linkable, SignalToken, Signaler}, signaling::{Linkable, SignalToken, Signaler},
utils::Rectangle, utils::Rectangle,
wayland::{ wayland::{
compositor::CompositorToken,
output::{Mode, Output, PhysicalProperties}, output::{Mode, Output, PhysicalProperties},
seat::CursorImageStatus, seat::CursorImageStatus,
}, },
@ -66,9 +65,8 @@ use smithay::{
wayland::dmabuf::init_dmabuf_global, wayland::dmabuf::init_dmabuf_global,
}; };
use crate::drawing::*;
use crate::shell::{MyWindowMap, Roles};
use crate::state::{AnvilState, Backend}; use crate::state::{AnvilState, Backend};
use crate::{drawing::*, window_map::WindowMap};
#[derive(Clone)] #[derive(Clone)]
pub struct SessionFd(RawFd); pub struct SessionFd(RawFd);
@ -632,7 +630,6 @@ impl AnvilState<UdevData> {
crtc, crtc,
&mut *self.window_map.borrow_mut(), &mut *self.window_map.borrow_mut(),
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&self.ctoken,
&self.pointer_location, &self.pointer_location,
&device_backend.pointer_image, &device_backend.pointer_image,
&*self.dnd_icon.lock().unwrap(), &*self.dnd_icon.lock().unwrap(),
@ -678,9 +675,8 @@ fn render_surface(
renderer: &mut Gles2Renderer, renderer: &mut Gles2Renderer,
device_id: dev_t, device_id: dev_t,
crtc: crtc::Handle, crtc: crtc::Handle,
window_map: &mut MyWindowMap, window_map: &mut WindowMap,
output_map: &mut Vec<MyOutput>, output_map: &mut Vec<MyOutput>,
compositor_token: &CompositorToken<Roles>,
pointer_location: &(f64, f64), pointer_location: &(f64, f64),
pointer_image: &Gles2Texture, pointer_image: &Gles2Texture,
dnd_icon: &Option<wl_surface::WlSurface>, dnd_icon: &Option<wl_surface::WlSurface>,
@ -721,7 +717,6 @@ fn render_surface(
width: width as i32, width: width as i32,
height: height as i32, height: height as i32,
}), }),
*compositor_token,
logger, logger,
)?; )?;
@ -736,14 +731,7 @@ fn render_surface(
{ {
if let Some(ref wl_surface) = dnd_icon.as_ref() { if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() { if wl_surface.as_ref().is_alive() {
draw_dnd_icon( draw_dnd_icon(renderer, frame, wl_surface, (ptr_x, ptr_y), logger)?;
renderer,
frame,
wl_surface,
(ptr_x, ptr_y),
*compositor_token,
logger,
)?;
} }
} }
} }
@ -759,20 +747,12 @@ fn render_surface(
} }
if let CursorImageStatus::Image(ref wl_surface) = *cursor_status { if let CursorImageStatus::Image(ref wl_surface) = *cursor_status {
draw_cursor( draw_cursor(renderer, frame, wl_surface, (ptr_x, ptr_y), logger)?;
renderer,
frame,
wl_surface,
(ptr_x, ptr_y),
*compositor_token,
logger,
)?;
} else { } else {
frame.render_texture_at(pointer_image, (ptr_x, ptr_y), Transform::Normal, 1.0)?; frame.render_texture_at(pointer_image, (ptr_x, ptr_y), Transform::Normal, 1.0)?;
} }
} }
} }
Ok(()) Ok(())
}, },
) )

View File

@ -4,10 +4,10 @@ use smithay::{
reexports::wayland_server::protocol::wl_surface, reexports::wayland_server::protocol::wl_surface,
utils::Rectangle, utils::Rectangle,
wayland::{ wayland::{
compositor::{roles::Role, CompositorToken, SubsurfaceRole, TraversalAction}, compositor::{with_states, with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
shell::{ shell::{
legacy::{ShellSurface, ShellSurfaceRole}, legacy::ShellSurface,
xdg::{PopupSurface, ToplevelSurface, XdgPopupSurfaceRole, XdgToplevelSurfaceRole}, xdg::{PopupSurface, SurfaceCachedState, ToplevelSurface},
}, },
}, },
}; };
@ -16,29 +16,15 @@ use crate::shell::SurfaceData;
#[cfg(feature = "xwayland")] #[cfg(feature = "xwayland")]
use crate::xwayland::X11Surface; use crate::xwayland::X11Surface;
pub enum Kind<R> { #[derive(Clone)]
Xdg(ToplevelSurface<R>), pub enum Kind {
Wl(ShellSurface<R>), Xdg(ToplevelSurface),
Wl(ShellSurface),
#[cfg(feature = "xwayland")] #[cfg(feature = "xwayland")]
X11(X11Surface), X11(X11Surface),
} }
// We implement Clone manually because #[derive(..)] would require R: Clone. impl Kind {
impl<R> Clone for Kind<R> {
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<R> Kind<R>
where
R: Role<SubsurfaceRole> + Role<XdgToplevelSurfaceRole> + Role<ShellSurfaceRole> + 'static,
{
pub fn alive(&self) -> bool { pub fn alive(&self) -> bool {
match *self { match *self {
Kind::Xdg(ref t) => t.alive(), Kind::Xdg(ref t) => t.alive(),
@ -68,23 +54,12 @@ where
} }
} }
pub enum PopupKind<R> { #[derive(Clone)]
Xdg(PopupSurface<R>), pub enum PopupKind {
Xdg(PopupSurface),
} }
// We implement Clone manually because #[derive(..)] would require R: Clone. impl PopupKind {
impl<R> Clone for PopupKind<R> {
fn clone(&self) -> Self {
match self {
PopupKind::Xdg(xdg) => PopupKind::Xdg(xdg.clone()),
}
}
}
impl<R> PopupKind<R>
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
pub fn alive(&self) -> bool { pub fn alive(&self) -> bool {
match *self { match *self {
PopupKind::Xdg(ref t) => t.alive(), PopupKind::Xdg(ref t) => t.alive(),
@ -97,56 +72,53 @@ where
} }
} }
struct Window<R> { struct Window {
location: (i32, i32), location: (i32, i32),
/// A bounding box over this window and its children. /// 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 /// 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. /// geometry if that's not set explicitly.
bbox: Rectangle, bbox: Rectangle,
toplevel: Kind<R>, toplevel: Kind,
} }
impl<R> Window<R> impl Window {
where
R: Role<SubsurfaceRole> + Role<XdgToplevelSurfaceRole> + Role<ShellSurfaceRole> + 'static,
{
/// Finds the topmost surface under this point if any and returns it together with the location of this /// Finds the topmost surface under this point if any and returns it together with the location of this
/// surface. /// surface.
fn matching( fn matching(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> {
&self,
point: (f64, f64),
ctoken: CompositorToken<R>,
) -> Option<(wl_surface::WlSurface, (f64, f64))> {
if !self.bbox.contains((point.0 as i32, point.1 as i32)) { if !self.bbox.contains((point.0 as i32, point.1 as i32)) {
return None; return None;
} }
// need to check more carefully // need to check more carefully
let found = RefCell::new(None); let found = RefCell::new(None);
if let Some(wl_surface) = self.toplevel.get_surface() { if let Some(wl_surface) = self.toplevel.get_surface() {
ctoken.with_surface_tree_downward( with_surface_tree_downward(
wl_surface, wl_surface,
self.location, self.location,
|wl_surface, attributes, role, &(mut x, mut y)| { |wl_surface, states, &(mut x, mut y)| {
let data = attributes.user_data.get::<RefCell<SurfaceData>>(); let data = states.data_map.get::<RefCell<SurfaceData>>();
if let Ok(subdata) = Role::<SubsurfaceRole>::data(role) { if states.role == Some("subsurface") {
x += subdata.location.0; let current = states.cached_state.current::<SubsurfaceCachedState>();
y += subdata.location.1; x += current.location.0;
y += current.location.1;
} }
let surface_local_point = (point.0 - x as f64, point.1 - y as f64); let surface_local_point = (point.0 - x as f64, point.1 - y as f64);
if data let contains_the_point = data
.map(|data| data.borrow().contains_point(surface_local_point)) .map(|data| {
.unwrap_or(false) 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))); *found.borrow_mut() = Some((wl_surface.clone(), (x as f64, y as f64)));
} }
TraversalAction::DoChildren((x, y)) TraversalAction::DoChildren((x, y))
}, },
|_, _, _, _| {}, |_, _, _| {},
|_, _, _, _| { |_, _, _| {
// only continue if the point is not found // only continue if the point is not found
found.borrow().is_none() found.borrow().is_none()
}, },
@ -155,20 +127,21 @@ where
found.into_inner() found.into_inner()
} }
fn self_update(&mut self, ctoken: CompositorToken<R>) { fn self_update(&mut self) {
let (base_x, base_y) = self.location; 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); 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() { if let Some(wl_surface) = self.toplevel.get_surface() {
ctoken.with_surface_tree_downward( with_surface_tree_downward(
wl_surface, wl_surface,
(base_x, base_y), (base_x, base_y),
|_, attributes, role, &(mut x, mut y)| { |_, states, &(mut x, mut y)| {
let data = attributes.user_data.get::<RefCell<SurfaceData>>(); let data = states.data_map.get::<RefCell<SurfaceData>>();
if let Some((w, h)) = data.and_then(|d| d.borrow().size()) { if let Some((w, h)) = data.and_then(|d| d.borrow().size()) {
if let Ok(subdata) = Role::<SubsurfaceRole>::data(role) { if states.role == Some("subsurface") {
x += subdata.location.0; let current = states.cached_state.current::<SubsurfaceCachedState>();
y += subdata.location.1; x += current.location.0;
y += current.location.1;
} }
// Update the bounding box. // Update the bounding box.
@ -184,8 +157,8 @@ where
TraversalAction::SkipChildren TraversalAction::SkipChildren
} }
}, },
|_, _, _, _| {}, |_, _, _| {},
|_, _, _, _| true, |_, _, _| true,
); );
} }
self.bbox = Rectangle { self.bbox = Rectangle {
@ -197,85 +170,69 @@ where
} }
/// Returns the geometry of this window. /// Returns the geometry of this window.
pub fn geometry(&self, ctoken: CompositorToken<R>) -> Rectangle { pub fn geometry(&self) -> Rectangle {
// It's the set geometry with the full bounding box as the fallback. // It's the set geometry with the full bounding box as the fallback.
ctoken with_states(self.toplevel.get_surface().unwrap(), |states| {
.with_surface_data(self.toplevel.get_surface().unwrap(), |attributes| { states.cached_state.current::<SurfaceCachedState>().geometry
attributes
.user_data
.get::<RefCell<SurfaceData>>()
.unwrap()
.borrow()
.geometry
}) })
.unwrap()
.unwrap_or(self.bbox) .unwrap_or(self.bbox)
} }
/// Sends the frame callback to all the subsurfaces in this /// Sends the frame callback to all the subsurfaces in this
/// window that requested it /// window that requested it
pub fn send_frame(&self, time: u32, ctoken: CompositorToken<R>) { pub fn send_frame(&self, time: u32) {
if let Some(wl_surface) = self.toplevel.get_surface() { if let Some(wl_surface) = self.toplevel.get_surface() {
ctoken.with_surface_tree_downward( with_surface_tree_downward(
wl_surface, wl_surface,
(), (),
|_, _, _, &()| TraversalAction::DoChildren(()), |_, _, &()| TraversalAction::DoChildren(()),
|_, attributes, _, &()| { |_, states, &()| {
// the surface may not have any user_data if it is a subsurface and has not // the surface may not have any user_data if it is a subsurface and has not
// yet been commited // yet been commited
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() { SurfaceData::send_frame(&mut *states.cached_state.current(), time)
data.borrow_mut().send_frame(time)
}
}, },
|_, _, _, &()| true, |_, _, &()| true,
); );
} }
} }
} }
pub struct Popup<R> { pub struct Popup {
popup: PopupKind<R>, popup: PopupKind,
} }
pub struct WindowMap<R> { pub struct WindowMap {
ctoken: CompositorToken<R>, windows: Vec<Window>,
windows: Vec<Window<R>>, popups: Vec<Popup>,
popups: Vec<Popup<R>>,
} }
impl<R> WindowMap<R> impl WindowMap {
where pub fn new() -> Self {
R: Role<SubsurfaceRole>
+ Role<XdgToplevelSurfaceRole>
+ Role<XdgPopupSurfaceRole>
+ Role<ShellSurfaceRole>
+ 'static,
{
pub fn new(ctoken: CompositorToken<R>) -> Self {
WindowMap { WindowMap {
ctoken,
windows: Vec::new(), windows: Vec::new(),
popups: Vec::new(), popups: Vec::new(),
} }
} }
pub fn insert(&mut self, toplevel: Kind<R>, location: (i32, i32)) { pub fn insert(&mut self, toplevel: Kind, location: (i32, i32)) {
let mut window = Window { let mut window = Window {
location, location,
bbox: Rectangle::default(), bbox: Rectangle::default(),
toplevel, toplevel,
}; };
window.self_update(self.ctoken); window.self_update();
self.windows.insert(0, window); self.windows.insert(0, window);
} }
pub fn insert_popup(&mut self, popup: PopupKind<R>) { pub fn insert_popup(&mut self, popup: PopupKind) {
let popup = Popup { popup }; let popup = Popup { popup };
self.popups.push(popup); self.popups.push(popup);
} }
pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> { pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> {
for w in &self.windows { for w in &self.windows {
if let Some(surface) = w.matching(point, self.ctoken) { if let Some(surface) = w.matching(point) {
return Some(surface); return Some(surface);
} }
} }
@ -288,7 +245,7 @@ where
) -> Option<(wl_surface::WlSurface, (f64, f64))> { ) -> Option<(wl_surface::WlSurface, (f64, f64))> {
let mut found = None; let mut found = None;
for (i, w) in self.windows.iter().enumerate() { 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)); found = Some((i, surface));
break; break;
} }
@ -304,7 +261,7 @@ where
pub fn with_windows_from_bottom_to_top<Func>(&self, mut f: Func) pub fn with_windows_from_bottom_to_top<Func>(&self, mut f: Func)
where where
Func: FnMut(&Kind<R>, (i32, i32), &Rectangle), Func: FnMut(&Kind, (i32, i32), &Rectangle),
{ {
for w in self.windows.iter().rev() { for w in self.windows.iter().rev() {
f(&w.toplevel, w.location, &w.bbox) f(&w.toplevel, w.location, &w.bbox)
@ -315,14 +272,14 @@ where
self.windows.retain(|w| w.toplevel.alive()); self.windows.retain(|w| w.toplevel.alive());
self.popups.retain(|p| p.popup.alive()); self.popups.retain(|p| p.popup.alive());
for w in &mut self.windows { for w in &mut self.windows {
w.self_update(self.ctoken); w.self_update();
} }
} }
/// Refreshes the state of the toplevel, if it exists. /// Refreshes the state of the toplevel, if it exists.
pub fn refresh_toplevel(&mut self, toplevel: &Kind<R>) { pub fn refresh_toplevel(&mut self, toplevel: &Kind) {
if let Some(w) = self.windows.iter_mut().find(|w| w.toplevel.equals(toplevel)) { 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`. /// Finds the toplevel corresponding to the given `WlSurface`.
pub fn find(&self, surface: &wl_surface::WlSurface) -> Option<Kind<R>> { pub fn find(&self, surface: &wl_surface::WlSurface) -> Option<Kind> {
self.windows.iter().find_map(|w| { self.windows.iter().find_map(|w| {
if w.toplevel if w.toplevel
.get_surface() .get_surface()
@ -345,7 +302,7 @@ where
}) })
} }
pub fn find_popup(&self, surface: &wl_surface::WlSurface) -> Option<PopupKind<R>> { pub fn find_popup(&self, surface: &wl_surface::WlSurface) -> Option<PopupKind> {
self.popups.iter().find_map(|p| { self.popups.iter().find_map(|p| {
if p.popup if p.popup
.get_surface() .get_surface()
@ -360,7 +317,7 @@ where
} }
/// Returns the location of the toplevel, if it exists. /// Returns the location of the toplevel, if it exists.
pub fn location(&self, toplevel: &Kind<R>) -> Option<(i32, i32)> { pub fn location(&self, toplevel: &Kind) -> Option<(i32, i32)> {
self.windows self.windows
.iter() .iter()
.find(|w| w.toplevel.equals(toplevel)) .find(|w| w.toplevel.equals(toplevel))
@ -368,24 +325,24 @@ where
} }
/// Sets the location of the toplevel, if it exists. /// Sets the location of the toplevel, if it exists.
pub fn set_location(&mut self, toplevel: &Kind<R>, 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)) { if let Some(w) = self.windows.iter_mut().find(|w| w.toplevel.equals(toplevel)) {
w.location = location; w.location = location;
w.self_update(self.ctoken); w.self_update();
} }
} }
/// Returns the geometry of the toplevel, if it exists. /// Returns the geometry of the toplevel, if it exists.
pub fn geometry(&self, toplevel: &Kind<R>) -> Option<Rectangle> { pub fn geometry(&self, toplevel: &Kind) -> Option<Rectangle> {
self.windows self.windows
.iter() .iter()
.find(|w| w.toplevel.equals(toplevel)) .find(|w| w.toplevel.equals(toplevel))
.map(|w| w.geometry(self.ctoken)) .map(|w| w.geometry())
} }
pub fn send_frames(&self, time: u32) { pub fn send_frames(&self, time: u32) {
for window in &self.windows { for window in &self.windows {
window.send_frame(time, self.ctoken); window.send_frame(time);
} }
} }
} }

View File

@ -61,12 +61,7 @@ pub fn run_winit(
* Initialize the globals * Initialize the globals
*/ */
let mut state = AnvilState::init( let mut state = AnvilState::init(display.clone(), event_loop.handle(), WinitData, log.clone());
display.clone(),
event_loop.handle(),
WinitData,
log.clone(),
);
let (output, _) = Output::new( let (output, _) = Output::new(
&mut display.borrow_mut(), &mut display.borrow_mut(),
@ -122,14 +117,7 @@ pub fn run_winit(
frame.clear([0.8, 0.8, 0.9, 1.0])?; frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the windows // draw the windows
draw_windows( draw_windows(renderer, frame, &*state.window_map.borrow(), None, &log)?;
renderer,
frame,
&*state.window_map.borrow(),
None,
state.ctoken,
&log,
)?;
let (x, y) = state.pointer_location; let (x, y) = state.pointer_location;
// draw the dnd icon if any // draw the dnd icon if any
@ -137,14 +125,7 @@ pub fn run_winit(
let guard = state.dnd_icon.lock().unwrap(); let guard = state.dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard { if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() { if surface.as_ref().is_alive() {
draw_dnd_icon( draw_dnd_icon(renderer, frame, surface, (x as i32, y as i32), &log)?;
renderer,
frame,
surface,
(x as i32, y as i32),
state.ctoken,
&log,
)?;
} }
} }
} }
@ -163,7 +144,7 @@ pub fn run_winit(
// draw as relevant // draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard { if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false; 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 { } else {
cursor_visible = true; cursor_visible = true;
} }

View File

@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap, convert::TryFrom, os::unix::net::
use smithay::{ use smithay::{
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, reexports::wayland_server::{protocol::wl_surface::WlSurface, Client},
wayland::compositor::CompositorToken, wayland::compositor::give_role,
}; };
use x11rb::{ use x11rb::{
@ -20,8 +20,7 @@ use x11rb::{
}; };
use crate::{ use crate::{
shell::{MyWindowMap, Roles}, window_map::{Kind, WindowMap},
window_map::Kind,
AnvilState, AnvilState,
}; };
@ -37,8 +36,7 @@ impl<BackendData: 'static> AnvilState<BackendData> {
} }
pub fn xwayland_ready(&mut self, connection: UnixStream, client: Client) { pub fn xwayland_ready(&mut self, connection: UnixStream, client: Client) {
let (wm, source) = let (wm, source) = X11State::start_wm(connection, self.window_map.clone(), self.log.clone()).unwrap();
X11State::start_wm(connection, self.ctoken, self.window_map.clone(), self.log.clone()).unwrap();
let wm = Rc::new(RefCell::new(wm)); let wm = Rc::new(RefCell::new(wm));
client.data_map().insert_if_missing(|| Rc::clone(&wm)); client.data_map().insert_if_missing(|| Rc::clone(&wm));
self.handle self.handle
@ -70,15 +68,13 @@ struct X11State {
atoms: Atoms, atoms: Atoms,
log: slog::Logger, log: slog::Logger,
unpaired_surfaces: HashMap<u32, (Window, (i32, i32))>, unpaired_surfaces: HashMap<u32, (Window, (i32, i32))>,
token: CompositorToken<Roles>, window_map: Rc<RefCell<WindowMap>>,
window_map: Rc<RefCell<MyWindowMap>>,
} }
impl X11State { impl X11State {
fn start_wm( fn start_wm(
connection: UnixStream, connection: UnixStream,
token: CompositorToken<Roles>, window_map: Rc<RefCell<WindowMap>>,
window_map: Rc<RefCell<MyWindowMap>>,
log: slog::Logger, log: slog::Logger,
) -> Result<(Self, X11Source), Box<dyn std::error::Error>> { ) -> Result<(Self, X11Source), Box<dyn std::error::Error>> {
// Create an X11 connection. XWayland only uses screen 0. // Create an X11 connection. XWayland only uses screen 0.
@ -123,7 +119,6 @@ impl X11State {
conn: Rc::clone(&conn), conn: Rc::clone(&conn),
atoms, atoms,
unpaired_surfaces: Default::default(), unpaired_surfaces: Default::default(),
token,
window_map, window_map,
log, log,
}; };
@ -209,7 +204,7 @@ impl X11State {
fn new_window(&mut self, window: Window, surface: WlSurface, location: (i32, i32)) { fn new_window(&mut self, window: Window, surface: WlSurface, location: (i32, i32)) {
debug!(self.log, "Matched X11 surface {:x?} to {:x?}", window, surface); 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 // 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); error!(self.log, "Surface {:x?} already has a role?!", surface);
return; return;
@ -237,8 +232,6 @@ pub fn commit_hook(surface: &WlSurface) {
} }
} }
pub struct X11SurfaceRole;
#[derive(Clone)] #[derive(Clone)]
pub struct X11Surface { pub struct X11Surface {
surface: WlSurface, surface: WlSurface,

View File

@ -39,8 +39,7 @@ impl<A: AsRawFd + 'static> Allocator<DumbBuffer<A>> for DrmDevice<A> {
// dumb buffers are always linear // dumb buffers are always linear
if modifiers if modifiers
.iter() .iter()
.find(|x| **x == Modifier::Invalid || **x == Modifier::Linear) .all(|&x| x != Modifier::Invalid && x != Modifier::Linear)
.is_none()
{ {
return Err(drm::SystemError::InvalidArgument); return Err(drm::SystemError::InvalidArgument);
} }

View File

@ -372,10 +372,9 @@ impl<A: AsRawFd + 'static> LegacyDrmSurface<A> {
let encoders = info let encoders = info
.encoders() .encoders()
.iter() .iter()
.filter(|enc| enc.is_some()) .flatten()
.map(|enc| enc.unwrap())
.map(|encoder| { .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", errmsg: "Error loading encoder info",
dev: self.fd.dev_path(), dev: self.fd.dev_path(),
source, source,

View File

@ -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 { 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<EGLPlatform<'_>>; fn supported_platforms(&self) -> Vec<EGLPlatform<'_>>;
/// Type of surfaces created /// Type of surfaces created

View File

@ -35,7 +35,7 @@ use super::{ImportDma, ImportShm};
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat}; use crate::backend::egl::{display::EGLBufferReader, Format as EGLFormat};
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
use crate::{utils::Rectangle, wayland::compositor::SurfaceAttributes}; use crate::utils::Rectangle;
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm}; use wayland_server::protocol::{wl_buffer, wl_shm};
@ -501,7 +501,7 @@ impl ImportShm for Gles2Renderer {
fn import_shm_buffer( fn import_shm_buffer(
&mut self, &mut self,
buffer: &wl_buffer::WlBuffer, buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>, surface: Option<&crate::wayland::compositor::SurfaceData>,
damage: &[Rectangle], damage: &[Rectangle],
) -> Result<Gles2Texture, Gles2Error> { ) -> Result<Gles2Texture, Gles2Error> {
use crate::wayland::shm::with_buffer_contents; 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. // why not store a `Gles2Texture`? because the user might do so.
// this is guaranteed a non-public internal type, so we are good. // this is guaranteed a non-public internal type, so we are good.
surface surface
.and_then(|surface| surface.user_data.get::<Rc<Gles2TextureInternal>>().cloned()) .and_then(|surface| surface.data_map.get::<Rc<Gles2TextureInternal>>().cloned())
.unwrap_or_else(|| { .unwrap_or_else(|| {
let mut tex = 0; let mut tex = 0;
unsafe { self.gl.GenTextures(1, &mut tex) }; unsafe { self.gl.GenTextures(1, &mut tex) };
@ -742,9 +742,7 @@ impl Gles2Renderer {
texture.0.texture, texture.0.texture,
buffer buffer
); );
if texture.0.is_external { if !texture.0.is_external {
Ok(Some(texture))
} else {
if let Some(egl_images) = texture.0.egl_images.as_ref() { if let Some(egl_images) = texture.0.egl_images.as_ref() {
if egl_images[0] == ffi_egl::NO_IMAGE_KHR { if egl_images[0] == ffi_egl::NO_IMAGE_KHR {
return Ok(None); return Ok(None);
@ -753,8 +751,8 @@ impl Gles2Renderer {
let tex = Some(texture.0.texture); let tex = Some(texture.0.texture);
self.import_egl_image(egl_images[0], false, tex)?; self.import_egl_image(egl_images[0], false, tex)?;
} }
Ok(Some(texture))
} }
Ok(Some(texture))
} else { } else {
Ok(None) Ok(None)
} }

View File

@ -11,7 +11,7 @@ use std::collections::HashSet;
use std::error::Error; use std::error::Error;
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
use crate::{utils::Rectangle, wayland::compositor::SurfaceAttributes}; use crate::{utils::Rectangle, wayland::compositor::SurfaceData};
use cgmath::{prelude::*, Matrix3, Vector2}; use cgmath::{prelude::*, Matrix3, Vector2};
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm}; use wayland_server::protocol::{wl_buffer, wl_shm};
@ -264,7 +264,7 @@ pub trait ImportShm: Renderer {
fn import_shm_buffer( fn import_shm_buffer(
&mut self, &mut self,
buffer: &wl_buffer::WlBuffer, buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>, surface: Option<&crate::wayland::compositor::SurfaceData>,
damage: &[Rectangle], damage: &[Rectangle],
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>; ) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
@ -404,7 +404,7 @@ pub trait ImportAll: Renderer {
fn import_buffer( fn import_buffer(
&mut self, &mut self,
buffer: &wl_buffer::WlBuffer, buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>, surface: Option<&crate::wayland::compositor::SurfaceData>,
damage: &[Rectangle], damage: &[Rectangle],
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>>; ) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>>;
} }
@ -419,7 +419,7 @@ impl<R: Renderer + ImportShm + ImportEgl + ImportDma> ImportAll for R {
fn import_buffer( fn import_buffer(
&mut self, &mut self,
buffer: &wl_buffer::WlBuffer, buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>, surface: Option<&SurfaceData>,
damage: &[Rectangle], damage: &[Rectangle],
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> { ) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> {
match buffer_type(buffer) { match buffer_type(buffer) {
@ -439,7 +439,7 @@ impl<R: Renderer + ImportShm + ImportDma> ImportAll for R {
fn import_buffer( fn import_buffer(
&mut self, &mut self,
buffer: &wl_buffer::WlBuffer, buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>, surface: Option<&SurfaceData>,
damage: &[Rectangle], damage: &[Rectangle],
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> { ) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> {
match buffer_type(buffer) { match buffer_type(buffer) {

View File

@ -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<T>` 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<T> {
pending: T,
cache: VecDeque<(Serial, T)>,
current: T,
}
impl<T: Default> Default for CachedState<T> {
fn default() -> Self {
CachedState {
pending: T::default(),
cache: VecDeque::new(),
current: T::default(),
}
}
}
trait Cache: Downcast {
fn commit(&self, commit_id: Option<Serial>);
fn apply_state(&self, commit_id: Serial);
}
impl_downcast!(Cache);
impl<T: Cacheable + 'static> Cache for RefCell<CachedState<T>> {
fn commit(&self, commit_id: Option<Serial>) {
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<Box<dyn Cache + Send>>,
}
impl MultiCache {
pub(crate) fn new() -> MultiCache {
MultiCache {
caches: appendlist::AppendList::new(),
}
}
fn find_or_insert<T: Cacheable + Send + 'static>(&self) -> &RefCell<CachedState<T>> {
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::<T>::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<T: Cacheable + Send + 'static>(&self) -> RefMut<'_, T> {
RefMut::map(self.find_or_insert::<T>().borrow_mut(), |cs| &mut cs.pending)
}
/// Access the current state associated with type `T`
pub fn current<T: Cacheable + Send + 'static>(&self) -> RefMut<'_, T> {
RefMut::map(self.find_or_insert::<T>().borrow_mut(), |cs| &mut cs.current)
}
/// Check if the container currently contains values for type `T`
pub fn has<T: Cacheable + Send + 'static>(&self) -> bool {
self.caches.iter().any(|c| c.as_any().is::<T>())
}
/// 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<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.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);
}
}
}

View File

@ -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::{ use wayland_server::{
protocol::{wl_compositor, wl_region, wl_subcompositor, wl_subsurface, wl_surface}, protocol::{wl_compositor, wl_region, wl_subcompositor, wl_subsurface, wl_surface},
@ -6,23 +14,22 @@ use wayland_server::{
}; };
use super::{ use super::{
tree::{Location, SurfaceData}, cache::Cacheable,
AlreadyHasRole, BufferAssignment, CompositorToken, Damage, Rectangle, RectangleKind, RegionAttributes, tree::{Location, PrivateSurfaceData},
Role, RoleType, SubsurfaceRole, SurfaceEvent, AlreadyHasRole, BufferAssignment, Damage, Rectangle, RectangleKind, RegionAttributes, SurfaceAttributes,
}; };
/* /*
* wl_compositor * wl_compositor
*/ */
pub(crate) fn implement_compositor<R, Impl>( pub(crate) fn implement_compositor<Impl>(
compositor: Main<wl_compositor::WlCompositor>, compositor: Main<wl_compositor::WlCompositor>,
log: ::slog::Logger, log: ::slog::Logger,
implem: Rc<RefCell<Impl>>, implem: Rc<RefCell<Impl>>,
) -> wl_compositor::WlCompositor ) -> wl_compositor::WlCompositor
where where
R: Default + Send + 'static, Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
{ {
compositor.quick_assign(move |_compositor, request, _| match request { compositor.quick_assign(move |_compositor, request, _| match request {
wl_compositor::Request::CreateSurface { id } => { wl_compositor::Request::CreateSurface { id } => {
@ -42,29 +49,24 @@ where
* wl_surface * wl_surface
*/ */
type SurfaceImplemFn<R> = type SurfaceImplemFn = dyn for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>);
dyn for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>);
// Internal implementation data of surfaces // Internal implementation data of surfaces
pub(crate) struct SurfaceImplem<R> { pub(crate) struct SurfaceImplem {
log: ::slog::Logger, log: ::slog::Logger,
implem: Rc<RefCell<SurfaceImplemFn<R>>>, implem: Rc<RefCell<SurfaceImplemFn>>,
} }
impl<R> SurfaceImplem<R> { impl SurfaceImplem {
fn make<Impl>(log: ::slog::Logger, implem: Rc<RefCell<Impl>>) -> SurfaceImplem<R> fn make<Impl>(log: ::slog::Logger, implem: Rc<RefCell<Impl>>) -> SurfaceImplem
where where
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>) Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
+ 'static,
{ {
SurfaceImplem { log, implem } SurfaceImplem { log, implem }
} }
} }
impl<R> SurfaceImplem<R> impl SurfaceImplem {
where
R: 'static,
{
fn receive_surface_request( fn receive_surface_request(
&mut self, &mut self,
req: wl_surface::Request, req: wl_surface::Request,
@ -73,8 +75,8 @@ where
) { ) {
match req { match req {
wl_surface::Request::Attach { buffer, x, y } => { wl_surface::Request::Attach { buffer, x, y } => {
SurfaceData::<R>::with_data(&surface, |d| { PrivateSurfaceData::with_states(&surface, |states| {
d.buffer = Some(match buffer { states.cached_state.pending::<SurfaceAttributes>().buffer = Some(match buffer {
Some(buffer) => BufferAssignment::NewBuffer { Some(buffer) => BufferAssignment::NewBuffer {
buffer, buffer,
delta: (x, y), delta: (x, y),
@ -84,13 +86,21 @@ where
}); });
} }
wl_surface::Request::Damage { x, y, width, height } => { wl_surface::Request::Damage { x, y, width, height } => {
SurfaceData::<R>::with_data(&surface, |d| { PrivateSurfaceData::with_states(&surface, |states| {
d.damage.push(Damage::Surface(Rectangle { x, y, width, height })); states
.cached_state
.pending::<SurfaceAttributes>()
.damage
.push(Damage::Surface(Rectangle { x, y, width, height }));
}); });
} }
wl_surface::Request::Frame { callback } => { wl_surface::Request::Frame { callback } => {
SurfaceData::<R>::with_data(&surface, move |d| { PrivateSurfaceData::with_states(&surface, |states| {
d.frame_callbacks.push((*callback).clone()); states
.cached_state
.pending::<SurfaceAttributes>()
.frame_callbacks
.push((*callback).clone());
}); });
} }
wl_surface::Request::SetOpaqueRegion { region } => { wl_surface::Request::SetOpaqueRegion { region } => {
@ -98,29 +108,50 @@ where
let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap(); let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap();
attributes_mutex.lock().unwrap().clone() attributes_mutex.lock().unwrap().clone()
}); });
SurfaceData::<R>::with_data(&surface, |d| d.opaque_region = attributes); PrivateSurfaceData::with_states(&surface, |states| {
states.cached_state.pending::<SurfaceAttributes>().opaque_region = attributes;
});
} }
wl_surface::Request::SetInputRegion { region } => { wl_surface::Request::SetInputRegion { region } => {
let attributes = region.map(|r| { let attributes = region.map(|r| {
let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap(); let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap();
attributes_mutex.lock().unwrap().clone() attributes_mutex.lock().unwrap().clone()
}); });
SurfaceData::<R>::with_data(&surface, |d| d.input_region = attributes); PrivateSurfaceData::with_states(&surface, |states| {
states.cached_state.pending::<SurfaceAttributes>().input_region = attributes;
});
} }
wl_surface::Request::Commit => { wl_surface::Request::Commit => {
let mut user_impl = self.implem.borrow_mut(); 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"); 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 } => { wl_surface::Request::SetBufferTransform { transform } => {
SurfaceData::<R>::with_data(&surface, |d| d.buffer_transform = transform); PrivateSurfaceData::with_states(&surface, |states| {
states
.cached_state
.pending::<SurfaceAttributes>()
.buffer_transform = transform;
});
} }
wl_surface::Request::SetBufferScale { scale } => { wl_surface::Request::SetBufferScale { scale } => {
SurfaceData::<R>::with_data(&surface, |d| d.buffer_scale = scale); PrivateSurfaceData::with_states(&surface, |states| {
states.cached_state.pending::<SurfaceAttributes>().buffer_scale = scale;
});
} }
wl_surface::Request::DamageBuffer { x, y, width, height } => { wl_surface::Request::DamageBuffer { x, y, width, height } => {
SurfaceData::<R>::with_data(&surface, |d| { PrivateSurfaceData::with_states(&surface, |states| {
d.damage.push(Damage::Buffer(Rectangle { x, y, width, height })) states
.cached_state
.pending::<SurfaceAttributes>()
.damage
.push(Damage::Buffer(Rectangle { x, y, width, height }))
}); });
} }
wl_surface::Request::Destroy => { wl_surface::Request::Destroy => {
@ -131,22 +162,53 @@ where
} }
} }
fn implement_surface<R, Impl>( 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<Impl>(
surface: Main<wl_surface::WlSurface>, surface: Main<wl_surface::WlSurface>,
log: ::slog::Logger, log: ::slog::Logger,
implem: Rc<RefCell<Impl>>, implem: Rc<RefCell<Impl>>,
) -> wl_surface::WlSurface ) -> wl_surface::WlSurface
where where
R: Default + Send + 'static, Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
{ {
surface.quick_assign({ surface.quick_assign({
let mut implem = SurfaceImplem::make(log, implem); let mut implem = SurfaceImplem::make(log, implem);
move |surface, req, ddata| implem.receive_surface_request(req, surface.deref().clone(), ddata) move |surface, req, ddata| implem.receive_surface_request(req, surface.deref().clone(), ddata)
}); });
surface.assign_destructor(Filter::new(|surface, _, _| SurfaceData::<R>::cleanup(&surface))); surface.assign_destructor(Filter::new(|surface, _, _| PrivateSurfaceData::cleanup(&surface)));
surface.as_ref().user_data().set_threadsafe(SurfaceData::<R>::new); surface
SurfaceData::<R>::init(&surface); .as_ref()
.user_data()
.set_threadsafe(PrivateSurfaceData::new);
PrivateSurfaceData::init(&surface);
surface.deref().clone() surface.deref().clone()
} }
@ -188,22 +250,19 @@ fn implement_region(region: Main<wl_region::WlRegion>) -> wl_region::WlRegion {
* wl_subcompositor * wl_subcompositor
*/ */
pub(crate) fn implement_subcompositor<R>( pub(crate) fn implement_subcompositor(
subcompositor: Main<wl_subcompositor::WlSubcompositor>, subcompositor: Main<wl_subcompositor::WlSubcompositor>,
) -> wl_subcompositor::WlSubcompositor ) -> wl_subcompositor::WlSubcompositor {
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{
subcompositor.quick_assign(move |subcompositor, request, _| match request { subcompositor.quick_assign(move |subcompositor, request, _| match request {
wl_subcompositor::Request::GetSubsurface { id, surface, parent } => { wl_subcompositor::Request::GetSubsurface { id, surface, parent } => {
if let Err(AlreadyHasRole) = SurfaceData::<R>::set_parent(&surface, &parent) { if let Err(AlreadyHasRole) = PrivateSurfaceData::set_parent(&surface, &parent) {
subcompositor.as_ref().post_error( subcompositor.as_ref().post_error(
wl_subcompositor::Error::BadSurface as u32, wl_subcompositor::Error::BadSurface as u32,
"Surface already has a role.".into(), "Surface already has a role.".into(),
); );
return; return;
} }
implement_subsurface::<R>(id, surface); implement_subsurface(id, surface);
} }
wl_subcompositor::Request::Destroy => {} wl_subcompositor::Request::Destroy => {}
_ => unreachable!(), _ => unreachable!(),
@ -215,32 +274,72 @@ where
* wl_subsurface * wl_subsurface
*/ */
fn with_subsurface_attributes<R, F>(subsurface: &wl_subsurface::WlSubsurface, f: F) /// The cached state associated with a subsurface
where pub struct SubsurfaceCachedState {
F: FnOnce(&mut SubsurfaceRole), /// Location of the top-left corner of this subsurface
R: RoleType + Role<SubsurfaceRole> + 'static, /// relative to its parent coordinate space
{ pub location: (i32, i32),
let surface = subsurface
.as_ref()
.user_data()
.get::<wl_surface::WlSurface>()
.unwrap();
SurfaceData::<R>::with_role_data::<SubsurfaceRole, _, _>(surface, |d| f(d))
.expect("The surface does not have a subsurface role while it has a wl_subsurface?!");
} }
fn implement_subsurface<R>( 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::<SubsurfaceState>()
.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<wl_subsurface::WlSubsurface>, subsurface: Main<wl_subsurface::WlSubsurface>,
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
) -> wl_subsurface::WlSubsurface ) -> wl_subsurface::WlSubsurface {
where let data_surface = surface.clone();
R: RoleType + Role<SubsurfaceRole> + 'static, subsurface.quick_assign(move |subsurface, request, _| {
{
subsurface.quick_assign(|subsurface, request, _| {
match request { match request {
wl_subsurface::Request::SetPosition { x, y } => { wl_subsurface::Request::SetPosition { x, y } => {
with_subsurface_attributes::<R, _>(&subsurface, |attrs| { PrivateSurfaceData::with_states(&surface, |state| {
attrs.location = (x, y); state.cached_state.pending::<SubsurfaceCachedState>().location = (x, y);
}) })
} }
wl_subsurface::Request::PlaceAbove { sibling } => { wl_subsurface::Request::PlaceAbove { sibling } => {
@ -249,7 +348,7 @@ where
.user_data() .user_data()
.get::<wl_surface::WlSurface>() .get::<wl_surface::WlSurface>()
.unwrap(); .unwrap();
if let Err(()) = SurfaceData::<R>::reorder(surface, Location::After, &sibling) { if let Err(()) = PrivateSurfaceData::reorder(surface, Location::After, &sibling) {
subsurface.as_ref().post_error( subsurface.as_ref().post_error(
wl_subsurface::Error::BadSurface as u32, wl_subsurface::Error::BadSurface as u32,
"Provided surface is not a sibling or parent.".into(), "Provided surface is not a sibling or parent.".into(),
@ -262,18 +361,28 @@ where
.user_data() .user_data()
.get::<wl_surface::WlSurface>() .get::<wl_surface::WlSurface>()
.unwrap(); .unwrap();
if let Err(()) = SurfaceData::<R>::reorder(surface, Location::Before, &sibling) { if let Err(()) = PrivateSurfaceData::reorder(surface, Location::Before, &sibling) {
subsurface.as_ref().post_error( subsurface.as_ref().post_error(
wl_subsurface::Error::BadSurface as u32, wl_subsurface::Error::BadSurface as u32,
"Provided surface is not a sibling or parent.".into(), "Provided surface is not a sibling or parent.".into(),
) )
} }
} }
wl_subsurface::Request::SetSync => with_subsurface_attributes::<R, _>(&subsurface, |attrs| { wl_subsurface::Request::SetSync => PrivateSurfaceData::with_states(&surface, |state| {
attrs.sync = true; state
.data_map
.get::<SubsurfaceState>()
.unwrap()
.sync
.store(true, Ordering::Release);
}), }),
wl_subsurface::Request::SetDesync => with_subsurface_attributes::<R, _>(&subsurface, |attrs| { wl_subsurface::Request::SetDesync => PrivateSurfaceData::with_states(&surface, |state| {
attrs.sync = false; state
.data_map
.get::<SubsurfaceState>()
.unwrap()
.sync
.store(false, Ordering::Release);
}), }),
wl_subsurface::Request::Destroy => { wl_subsurface::Request::Destroy => {
// Our destructor already handles it // Our destructor already handles it
@ -281,23 +390,25 @@ where
_ => unreachable!(), _ => unreachable!(),
} }
}); });
subsurface.assign_destructor(Filter::new(|subsurface, _, _| { super::with_states(&data_surface, |states| {
destroy_subsurface::<R>(&subsurface) states.data_map.insert_if_missing_threadsafe(SubsurfaceState::new)
})); })
subsurface.as_ref().user_data().set_threadsafe(|| surface); .unwrap();
subsurface.assign_destructor(Filter::new(|subsurface, _, _| destroy_subsurface(&subsurface)));
subsurface
.as_ref()
.user_data()
.set_threadsafe(move || data_surface);
subsurface.deref().clone() subsurface.deref().clone()
} }
fn destroy_subsurface<R>(subsurface: &wl_subsurface::WlSubsurface) fn destroy_subsurface(subsurface: &wl_subsurface::WlSubsurface) {
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{
let surface = subsurface let surface = subsurface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<wl_surface::WlSurface>() .get::<wl_surface::WlSurface>()
.unwrap(); .unwrap();
if surface.as_ref().is_alive() { if surface.as_ref().is_alive() {
SurfaceData::<R>::unset_parent(&surface); PrivateSurfaceData::unset_parent(&surface);
} }
} }

View File

@ -9,7 +9,8 @@
//! //!
//! This implementation does a simple job: it stores in a coherent way the state of //! 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 //! 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, //! 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 //! you can iterate over the whole tree of subsurfaces to recover all the metadata you
@ -32,54 +33,67 @@
//! # #[macro_use] extern crate smithay; //! # #[macro_use] extern crate smithay;
//! use smithay::wayland::compositor::compositor_init; //! use smithay::wayland::compositor::compositor_init;
//! //!
//! // Declare the roles enum
//! define_roles!(MyRoles);
//!
//! # let mut display = wayland_server::Display::new(); //! # let mut display = wayland_server::Display::new();
//! // Call the init function: //! // Call the init function:
//! let (token, _, _) = compositor_init::<MyRoles, _, _>( //! compositor_init(
//! &mut display, //! &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 //! None // put a logger here
//! ); //! );
//! //!
//! // this `token` is what you'll use to access the surface associated data
//!
//! // You're now ready to go! //! // 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 main access to surface states is done through the [`with_states`] function, which
//! the `init` function. This token is necessary to retrieve the metadata associated with //! gives you access to the [`SurfaceData`] instance associated with this surface. It acts
//! a surface. It can be cloned. See [`CompositorToken`] //! as a general purpose container for associating state to a surface, double-buffered or
//! for the details of what it enables you. //! not. See its documentation for more details.
//! //!
//! The surface metadata is held in the [`SurfaceAttributes`] //! ### State application and hooks
//! 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.
//! //!
//! This [`CompositorToken`] also provides access to the metadata associated with the role of the //! On commit of a surface several steps are taken to update the state of the surface. Actions
//! surfaces. See the documentation of the [`roles`] submodule //! are taken by smithay in the following order:
//! for a detailed explanation. //!
//! 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; mod handlers;
pub mod roles; mod transaction;
mod tree; mod tree;
pub use self::tree::TraversalAction; pub use self::cache::{Cacheable, MultiCache};
use self::{ pub use self::handlers::SubsurfaceCachedState;
roles::{AlreadyHasRole, Role, RoleType, WrongRole}, use self::tree::PrivateSurfaceData;
tree::SurfaceData, pub use self::tree::{AlreadyHasRole, TraversalAction};
}; use crate::utils::{DeadResource, Rectangle};
use crate::utils::Rectangle;
use wayland_server::{ use wayland_server::{
protocol::{ protocol::{
wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface::WlSurface, wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface::WlSurface,
@ -104,6 +118,32 @@ struct Marker<R> {
_r: ::std::marker::PhantomData<R>, _r: ::std::marker::PhantomData<R>,
} }
/// 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 /// New buffer assignation for a surface
#[derive(Debug)] #[derive(Debug)]
pub enum BufferAssignment { 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 /// The fields `buffer`, `damage` and `frame_callbacks` should be
/// should only be applied once a [`commit`](SurfaceEvent::Commit) /// reset (by clearing their contents) once you have adequately
/// request is received from the surface. /// processed them, as their contents are aggregated from commit to commit.
/// #[derive(Debug)]
/// You are responsible for setting those values as you see fit to avoid
/// processing them two times.
pub struct SurfaceAttributes { pub struct SurfaceAttributes {
/// Buffer defining the contents of the surface /// 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 /// An example possibility would be to trigger it once the frame
/// associated with this commit has been displayed on the screen. /// associated with this commit has been displayed on the screen.
pub frame_callbacks: Vec<wl_callback::WlCallback>, pub frame_callbacks: Vec<wl_callback::WlCallback>,
/// 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 { impl Default for SurfaceAttributes {
@ -204,7 +222,6 @@ impl Default for SurfaceAttributes {
input_region: None, input_region: None,
damage: Vec::new(), damage: Vec::new(),
frame_callbacks: 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 /// You provide three closures, a "filter", a "processor" and a "post filter".
/// access data associated with the [`wl_surface`](wayland_server::protocol::wl_surface) ///
/// and [`wl_region`](wayland_server::protocol::wl_region) managed /// The first closure is initially called on a surface to determine if its children
/// by the `CompositorGlobal` that provided it. /// should be processed as well. It returns a `TraversalAction<T>` reflecting that.
#[derive(Debug)] ///
pub struct CompositorToken<R> { /// The second closure is supposed to do the actual processing. The processing closure for
_role: ::std::marker::PhantomData<*mut R>, /// 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.
// we implement them manually because #[derive(..)] would require R: Clone ///
impl<R> Copy for CompositorToken<R> {} /// The third closure is called once all the subtree of a node has been processed, and gives
impl<R> Clone for CompositorToken<R> { /// an opportunity for early-stopping. If it returns `true` the processing will continue,
fn clone(&self) -> CompositorToken<R> { /// while if it returns `false` it'll stop.
*self ///
} /// The arguments provided to the closures are, in this order:
} ///
/// - The surface object itself
unsafe impl<R> Send for CompositorToken<R> {} /// - a mutable reference to its surface attribute data
unsafe impl<R> Sync for CompositorToken<R> {} /// - 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
impl<R> CompositorToken<R> { /// to its children. See [`TraversalAction`] for details.
pub(crate) fn make() -> CompositorToken<R> { ///
CompositorToken { /// If the surface not managed by the `CompositorGlobal` that provided this token, this
_role: ::std::marker::PhantomData, /// will panic (having more than one compositor is not supported).
} pub fn with_surface_tree_upward<F1, F2, F3, T>(
} surface: &WlSurface,
} initial: T,
filter: F1,
impl<R: 'static> CompositorToken<R> { processor: F2,
/// Access the data of a surface post_filter: F3,
/// ) where
/// The closure will be called with the contents of the data associated with this surface. F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
/// F2: FnMut(&WlSurface, &SurfaceData, &T),
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
/// will panic (having more than one compositor is not supported).
pub fn with_surface_data<F, T>(self, surface: &WlSurface, f: F) -> T
where
F: FnOnce(&mut SurfaceAttributes) -> T,
{
SurfaceData::<R>::with_data(surface, f)
}
}
impl<R> CompositorToken<R>
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{ {
/// Access the data of a surface tree from bottom to top PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, false);
///
/// 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<T>` 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<F1, F2, F3, T>(
self,
surface: &WlSurface,
initial: T,
filter: F1,
processor: F2,
post_filter: F3,
) where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool,
{
SurfaceData::<R>::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<F1, F2, F3, T>(
self,
surface: &WlSurface,
initial: T,
filter: F1,
processor: F2,
post_filter: F3,
) where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool,
{
SurfaceData::<R>::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<WlSurface> {
SurfaceData::<R>::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<WlSurface> {
SurfaceData::<R>::get_children(surface)
}
} }
impl<R: RoleType + 'static> CompositorToken<R> { /// Access the data of a surface tree from top to bottom
/// Check whether this surface as a role or not ///
/// /// Behavior is the same as [`with_surface_tree_upward`], but the processing is done in the reverse order,
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this /// from the nearest of the screen to the deepest.
/// will panic (having more than one compositor is not supported). ///
pub fn has_a_role(self, surface: &WlSurface) -> bool { /// This would typically be used to find out which surface of a subsurface tree has been clicked for example.
SurfaceData::<R>::has_a_role(surface) pub fn with_surface_tree_downward<F1, F2, F3, T>(
} surface: &WlSurface,
initial: T,
filter: F1,
processor: F2,
post_filter: F3,
) where
F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &SurfaceData, &T),
F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
{
PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, true);
}
/// Check whether this surface as a specific role /// Retrieve the parent of this surface
/// ///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this /// Returns `None` is this surface is a root surface
/// will panic (having more than one compositor is not supported). pub fn get_parent(surface: &WlSurface) -> Option<WlSurface> {
pub fn has_role<RoleData>(self, surface: &WlSurface) -> bool if !surface.as_ref().is_alive() {
where return None;
R: Role<RoleData>,
{
SurfaceData::<R>::has_role::<RoleData>(surface)
} }
PrivateSurfaceData::get_parent(surface)
}
/// Register that this surface has given role with default data /// Retrieve the children of this surface
/// pub fn get_children(surface: &WlSurface) -> Vec<WlSurface> {
/// Fails if the surface already has a role. if !surface.as_ref().is_alive() {
/// return Vec::new();
/// 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<RoleData>(self, surface: &WlSurface) -> Result<(), AlreadyHasRole>
where
R: Role<RoleData>,
RoleData: Default,
{
SurfaceData::<R>::give_role::<RoleData>(surface)
} }
PrivateSurfaceData::get_children(surface)
}
/// Register that this surface has given role with given data /// Check if this subsurface is a synchronized subsurface
/// ///
/// Fails if the surface already has a role and returns the data. /// Returns false if the surface is already dead
/// pub fn is_sync_subsurface(surface: &WlSurface) -> bool {
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this if !surface.as_ref().is_alive() {
/// will panic (having more than one compositor is not supported). return false;
pub fn give_role_with<RoleData>(self, surface: &WlSurface, data: RoleData) -> Result<(), RoleData>
where
R: Role<RoleData>,
{
SurfaceData::<R>::give_role_with::<RoleData>(surface, data)
} }
self::handlers::is_effectively_sync(surface)
}
/// Access the role data of a surface /// Get the current role of this surface
/// pub fn get_role(surface: &WlSurface) -> Option<&'static str> {
/// Fails and don't call the closure if the surface doesn't have this role if !surface.as_ref().is_alive() {
/// return None;
/// 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<RoleData, F, T>(self, surface: &WlSurface, f: F) -> Result<T, WrongRole>
where
R: Role<RoleData>,
F: FnOnce(&mut RoleData) -> T,
{
SurfaceData::<R>::with_role_data::<RoleData, _, _>(surface, f)
} }
PrivateSurfaceData::get_role(surface)
}
/// Register that this surface does not have a role any longer and retrieve the data /// Register that this surface has given role
/// ///
/// Fails if the surface didn't already have this role. /// Fails if the surface already has a role.
/// pub fn give_role(surface: &WlSurface, role: &'static str) -> Result<(), AlreadyHasRole> {
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this if !surface.as_ref().is_alive() {
/// will panic (having more than one compositor is not supported). return Ok(());
pub fn remove_role<RoleData>(self, surface: &WlSurface) -> Result<RoleData, WrongRole>
where
R: Role<RoleData>,
{
SurfaceData::<R>::remove_role::<RoleData>(surface)
} }
PrivateSurfaceData::set_role(surface, role)
}
/// Retrieve the metadata associated with a `wl_region` /// Access the states associated to this surface
/// pub fn with_states<F, T>(surface: &WlSurface, f: F) -> Result<T, DeadResource>
/// If the region is not managed by the `CompositorGlobal` that provided this token, this where
/// will panic (having more than one compositor is not supported). F: FnOnce(&SurfaceData) -> T,
pub fn get_region_attributes(self, region: &wl_region::WlRegion) -> RegionAttributes { {
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::<Mutex<RegionAttributes>>() { match region.as_ref().user_data().get::<Mutex<RegionAttributes>>() {
Some(mutex) => mutex.lock().unwrap().clone(), Some(mutex) => mutex.lock().unwrap().clone(),
None => panic!("Accessing the data of foreign regions is not supported."), 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) /// Create new [`wl_compositor`](wayland_server::protocol::wl_compositor)
/// and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals. /// and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals.
/// ///
/// The globals are directly registered into the event loop, and this function /// It returns the two global handles, in case you wish to remove these globals from
/// returns a [`CompositorToken`] which you'll need access the data associated to /// the event loop in the future.
/// the [`wl_surface`](wayland_server::protocol::wl_surface)s. pub fn compositor_init<Impl, L>(
///
/// 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<R, Impl, L>(
display: &mut Display, display: &mut Display,
implem: Impl, implem: Impl,
logger: L, logger: L,
) -> ( ) -> (
CompositorToken<R>,
Global<wl_compositor::WlCompositor>, Global<wl_compositor::WlCompositor>,
Global<wl_subcompositor::WlSubcompositor>, Global<wl_subcompositor::WlSubcompositor>,
) )
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
R: Default + RoleType + Role<SubsurfaceRole> + Send + 'static, Impl: for<'a> FnMut(WlSurface, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(SurfaceEvent, WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
{ {
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "compositor_handler")); let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "compositor_handler"));
let implem = Rc::new(RefCell::new(implem)); let implem = Rc::new(RefCell::new(implem));
@ -522,32 +455,18 @@ where
let compositor = display.create_global( let compositor = display.create_global(
4, 4,
Filter::new(move |(new_compositor, _version), _, _| { Filter::new(move |(new_compositor, _version), _, _| {
self::handlers::implement_compositor::<R, Impl>(new_compositor, log.clone(), implem.clone()); self::handlers::implement_compositor::<Impl>(new_compositor, log.clone(), implem.clone());
}), }),
); );
let subcompositor = display.create_global( let subcompositor = display.create_global(
1, 1,
Filter::new(move |(new_subcompositor, _version), _, _| { Filter::new(move |(new_subcompositor, _version), _, _| {
self::handlers::implement_subcompositor::<R>(new_subcompositor); self::handlers::implement_subcompositor(new_subcompositor);
}), }),
); );
(CompositorToken::make(), compositor, subcompositor) (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,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -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<Token>`](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<U, R, H>`](crate::wayland::compositor::CompositorToken)
//! where `R: Role<TheToken>`.
//!
//! 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 = <MyRoles as Role<RoleData>>::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<R>: 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<R, WrongRole>;
}
/// 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)
}
}
}
)*
};
);

View File

@ -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<Box<dyn Blocker + Send>>,
}
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<Mutex<TransactionInner>>),
}
pub(crate) struct PendingTransaction {
inner: Arc<Mutex<TransactionInner>>,
}
impl Default for PendingTransaction {
fn default() -> Self {
PendingTransaction {
inner: Arc::new(Mutex::new(TransactionInner::Data(Default::default()))),
}
}
}
impl PendingTransaction {
fn with_inner_state<T, F: FnOnce(&mut TransactionState) -> 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<B: Blocker + Send + 'static>(&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<Box<dyn Blocker + Send>>,
}
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<Transaction>,
// we keep the hashset around to reuse allocations
seen_surfaces: HashSet<u32>,
}
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();
}
}
}
}

View File

@ -1,7 +1,14 @@
use super::{roles::*, SubsurfaceRole, SurfaceAttributes}; use crate::wayland::Serial;
use std::sync::Mutex;
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; 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 /// Node of a subsurface tree, holding some user specified data type U
/// at each node /// 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 /// Each node also appears within its children list, to allow relative placement
/// between them. /// between them.
pub struct SurfaceData<R> { pub struct PrivateSurfaceData {
parent: Option<WlSurface>, parent: Option<WlSurface>,
children: Vec<WlSurface>, children: Vec<WlSurface>,
role: R, public_data: SurfaceData,
attributes: SurfaceAttributes, pending_transaction: PendingTransaction,
current_txid: Serial,
commit_hooks: Vec<fn(&WlSurface)>,
} }
/// 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 { pub enum Location {
Before, Before,
After, After,
@ -38,27 +63,28 @@ pub enum TraversalAction<T> {
Break, Break,
} }
impl<R: Default> SurfaceData<R> { impl PrivateSurfaceData {
pub fn new() -> Mutex<SurfaceData<R>> { pub fn new() -> Mutex<PrivateSurfaceData> {
Mutex::new(SurfaceData { Mutex::new(PrivateSurfaceData {
parent: None, parent: None,
children: vec![], children: vec![],
public_data: SurfaceData {
role: Default::default(), role: Default::default(),
attributes: Default::default(), data_map: Default::default(),
cached_state: MultiCache::new(),
},
pending_transaction: Default::default(),
current_txid: Serial(0),
commit_hooks: Vec::new(),
}) })
} }
}
impl<R> SurfaceData<R>
where
R: 'static,
{
/// Initializes the surface, must be called at creation for state coherence /// Initializes the surface, must be called at creation for state coherence
pub fn init(surface: &WlSurface) { pub fn init(surface: &WlSurface) {
let my_data_mutex = surface let my_data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut my_data = my_data_mutex.lock().unwrap(); let mut my_data = my_data_mutex.lock().unwrap();
debug_assert!(my_data.children.is_empty()); debug_assert!(my_data.children.is_empty());
@ -70,7 +96,7 @@ where
let my_data_mutex = surface let my_data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut my_data = my_data_mutex.lock().unwrap(); let mut my_data = my_data_mutex.lock().unwrap();
if let Some(old_parent) = my_data.parent.take() { if let Some(old_parent) = my_data.parent.take() {
@ -78,7 +104,7 @@ where
let old_parent_mutex = old_parent let old_parent_mutex = old_parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut old_parent_guard = old_parent_mutex.lock().unwrap(); let mut old_parent_guard = old_parent_mutex.lock().unwrap();
old_parent_guard old_parent_guard
@ -87,7 +113,11 @@ where
} }
// orphan all our children // orphan all our children
for child in &my_data.children { for child in &my_data.children {
let child_mutex = child.as_ref().user_data().get::<Mutex<SurfaceData<R>>>().unwrap(); let child_mutex = child
.as_ref()
.user_data()
.get::<Mutex<PrivateSurfaceData>>()
.unwrap();
if std::ptr::eq(child_mutex, my_data_mutex) { if std::ptr::eq(child_mutex, my_data_mutex) {
// This child is ourselves, don't do anything. // This child is ourselves, don't do anything.
continue; continue;
@ -97,108 +127,119 @@ where
child_guard.parent = None; child_guard.parent = None;
} }
} }
}
impl<R: RoleType + 'static> SurfaceData<R> { pub fn set_role(surface: &WlSurface, role: &'static str) -> Result<(), AlreadyHasRole> {
pub fn has_a_role(surface: &WlSurface) -> bool { let my_data_mutex = surface
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let data_guard = data_mutex.lock().unwrap(); let mut my_data = my_data_mutex.lock().unwrap();
<R as RoleType>::has_role(&data_guard.role) 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 get_role(surface: &WlSurface) -> Option<&'static str> {
pub fn has_role<RoleData>(surface: &WlSurface) -> bool let my_data_mutex = surface
where
R: Role<RoleData>,
{
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let data_guard = data_mutex.lock().unwrap(); let my_data = my_data_mutex.lock().unwrap();
<R as Role<RoleData>>::has(&data_guard.role) my_data.public_data.role
} }
/// Register that this surface has a role, fails if it already has one pub fn with_states<T, F: FnOnce(&SurfaceData) -> T>(surface: &WlSurface, f: F) -> T {
pub fn give_role<RoleData>(surface: &WlSurface) -> Result<(), AlreadyHasRole> let my_data_mutex = surface
where
R: Role<RoleData>,
RoleData: Default,
{
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut data_guard = data_mutex.lock().unwrap(); let my_data = my_data_mutex.lock().unwrap();
<R as Role<RoleData>>::set(&mut data_guard.role) f(&my_data.public_data)
} }
/// Register that this surface has a role with given data pub fn add_commit_hook(surface: &WlSurface, hook: fn(&WlSurface)) {
/// let my_data_mutex = surface
/// Fails if it already has one and returns the data
pub fn give_role_with<RoleData>(surface: &WlSurface, data: RoleData) -> Result<(), RoleData>
where
R: Role<RoleData>,
{
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut data_guard = data_mutex.lock().unwrap(); let mut my_data = my_data_mutex.lock().unwrap();
<R as Role<RoleData>>::set_with(&mut data_guard.role, data) my_data.commit_hooks.push(hook);
} }
/// Register that this surface has no role and returns the data pub fn invoke_commit_hooks(surface: &WlSurface) {
/// // don't hold the mutex while the hooks are invoked
/// It is a noop if this surface already didn't have one, but fails if let hooks = {
/// the role was "subsurface", it must be removed by the `unset_parent` method. let my_data_mutex = surface
pub fn remove_role<RoleData>(surface: &WlSurface) -> Result<RoleData, WrongRole>
where
R: Role<RoleData>,
{
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut data_guard = data_mutex.lock().unwrap(); let my_data = my_data_mutex.lock().unwrap();
<R as Role<RoleData>>::unset(&mut data_guard.role) my_data.commit_hooks.clone()
};
for hook in hooks {
hook(surface);
}
} }
/// Access to the role data pub fn commit(surface: &WlSurface) {
pub fn with_role_data<RoleData, F, T>(surface: &WlSurface, f: F) -> Result<T, WrongRole> let is_sync = is_effectively_sync(surface);
where let children = get_children(surface);
R: Role<RoleData>, let my_data_mutex = surface
F: FnOnce(&mut RoleData) -> T,
{
debug_assert!(surface.as_ref().is_alive());
let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut data_guard = data_mutex.lock().unwrap(); let mut my_data = my_data_mutex.lock().unwrap();
let data = <R as Role<RoleData>>::data_mut(&mut data_guard.role)?; // commit our state
Ok(f(data)) 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::<Mutex<PrivateSurfaceData>>()
.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::<super::handlers::SubsurfaceState>()
.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();
}
} }
}
impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
/// Checks if the first surface is an ancestor of the second /// Checks if the first surface is an ancestor of the second
pub fn is_ancestor(a: &WlSurface, b: &WlSurface) -> bool { pub fn is_ancestor(a: &WlSurface, b: &WlSurface) -> bool {
let b_mutex = b.as_ref().user_data().get::<Mutex<SurfaceData<R>>>().unwrap(); let b_mutex = b.as_ref().user_data().get::<Mutex<PrivateSurfaceData>>().unwrap();
let b_guard = b_mutex.lock().unwrap(); let b_guard = b_mutex.lock().unwrap();
if let Some(ref parent) = b_guard.parent { if let Some(ref parent) = b_guard.parent {
if parent.as_ref().equals(a.as_ref()) { if parent.as_ref().equals(a.as_ref()) {
@ -225,10 +266,18 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
// change child's parent // change child's parent
{ {
let child_mutex = child.as_ref().user_data().get::<Mutex<SurfaceData<R>>>().unwrap(); let child_mutex = child
.as_ref()
.user_data()
.get::<Mutex<PrivateSurfaceData>>()
.unwrap();
let mut child_guard = child_mutex.lock().unwrap(); let mut child_guard = child_mutex.lock().unwrap();
// if surface already has a role, it cannot become a subsurface // if surface already has a role, it cannot become a subsurface
<R as Role<SubsurfaceRole>>::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()); debug_assert!(child_guard.parent.is_none());
child_guard.parent = Some(parent.clone()); child_guard.parent = Some(parent.clone());
} }
@ -237,7 +286,7 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
let parent_mutex = parent let parent_mutex = parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut parent_guard = parent_mutex.lock().unwrap(); let mut parent_guard = parent_mutex.lock().unwrap();
parent_guard.children.push(child.clone()) parent_guard.children.push(child.clone())
@ -251,22 +300,20 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
pub fn unset_parent(child: &WlSurface) { pub fn unset_parent(child: &WlSurface) {
debug_assert!(child.as_ref().is_alive()); debug_assert!(child.as_ref().is_alive());
let old_parent = { let old_parent = {
let child_mutex = child.as_ref().user_data().get::<Mutex<SurfaceData<R>>>().unwrap(); let child_mutex = child
.as_ref()
.user_data()
.get::<Mutex<PrivateSurfaceData>>()
.unwrap();
let mut child_guard = child_mutex.lock().unwrap(); let mut child_guard = child_mutex.lock().unwrap();
let old_parent = child_guard.parent.take(); child_guard.parent.take()
if old_parent.is_some() {
// We had a parent, so this does not have a role any more
<R as Role<SubsurfaceRole>>::unset(&mut child_guard.role)
.expect("Surface had a parent but not the subsurface role?!");
}
old_parent
}; };
// unregister from our parent // unregister from our parent
if let Some(old_parent) = old_parent { if let Some(old_parent) = old_parent {
let parent_mutex = old_parent let parent_mutex = old_parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut parent_guard = parent_mutex.lock().unwrap(); let mut parent_guard = parent_mutex.lock().unwrap();
parent_guard parent_guard
@ -277,7 +324,11 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
/// Retrieve the parent surface (if any) of this surface /// Retrieve the parent surface (if any) of this surface
pub fn get_parent(child: &WlSurface) -> Option<WlSurface> { pub fn get_parent(child: &WlSurface) -> Option<WlSurface> {
let child_mutex = child.as_ref().user_data().get::<Mutex<SurfaceData<R>>>().unwrap(); let child_mutex = child
.as_ref()
.user_data()
.get::<Mutex<PrivateSurfaceData>>()
.unwrap();
let child_guard = child_mutex.lock().unwrap(); let child_guard = child_mutex.lock().unwrap();
child_guard.parent.as_ref().cloned() child_guard.parent.as_ref().cloned()
} }
@ -287,7 +338,7 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
let parent_mutex = parent let parent_mutex = parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let parent_guard = parent_mutex.lock().unwrap(); let parent_guard = parent_mutex.lock().unwrap();
parent_guard parent_guard
@ -306,7 +357,7 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
let data_mutex = surface let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let data_guard = data_mutex.lock().unwrap(); let data_guard = data_mutex.lock().unwrap();
data_guard.parent.as_ref().cloned().unwrap() data_guard.parent.as_ref().cloned().unwrap()
@ -324,7 +375,7 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
let parent_mutex = parent let parent_mutex = parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut parent_guard = parent_mutex.lock().unwrap(); let mut parent_guard = parent_mutex.lock().unwrap();
let my_index = index_of(surface, &parent_guard.children).unwrap(); let my_index = index_of(surface, &parent_guard.children).unwrap();
@ -345,24 +396,7 @@ impl<R: RoleType + Role<SubsurfaceRole> + 'static> SurfaceData<R> {
} }
} }
impl<R: 'static> SurfaceData<R> { impl PrivateSurfaceData {
/// 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<T, F>(surface: &WlSurface, f: F) -> T
where
F: FnOnce(&mut SurfaceAttributes) -> T,
{
let data_mutex = surface
.as_ref()
.user_data()
.get::<Mutex<SurfaceData<R>>>()
.expect("Accessing the data of foreign surfaces is not supported.");
let mut data_guard = data_mutex.lock().unwrap();
f(&mut data_guard.attributes)
}
/// Access sequentially the attributes associated with a surface tree, /// Access sequentially the attributes associated with a surface tree,
/// in a depth-first order. /// in a depth-first order.
/// ///
@ -384,9 +418,9 @@ impl<R: 'static> SurfaceData<R> {
mut post_filter: F3, mut post_filter: F3,
reverse: bool, reverse: bool,
) where ) where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>, F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), F2: FnMut(&WlSurface, &SurfaceData, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
{ {
Self::map( Self::map(
surface, surface,
@ -408,25 +442,25 @@ impl<R: 'static> SurfaceData<R> {
reverse: bool, reverse: bool,
) -> bool ) -> bool
where where
F1: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> TraversalAction<T>, F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T), F2: FnMut(&WlSurface, &SurfaceData, &T),
F3: FnMut(&WlSurface, &mut SurfaceAttributes, &mut R, &T) -> bool, F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
{ {
let data_mutex = surface let data_mutex = surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<Mutex<SurfaceData<R>>>() .get::<Mutex<PrivateSurfaceData>>()
.unwrap(); .unwrap();
let mut data_guard = data_mutex.lock().unwrap(); let mut data_guard = data_mutex.lock().unwrap();
let data_guard = &mut *data_guard; let data_guard = &mut *data_guard;
// call the filter on ourselves // 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) => { TraversalAction::DoChildren(t) => {
// loop over children // loop over children
if reverse { if reverse {
for c in data_guard.children.iter().rev() { for c in data_guard.children.iter().rev() {
if c.as_ref().equals(surface.as_ref()) { 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) { } else if !Self::map(c, &t, filter, processor, post_filter, true) {
return false; return false;
} }
@ -434,17 +468,17 @@ impl<R: 'static> SurfaceData<R> {
} else { } else {
for c in &data_guard.children { for c in &data_guard.children {
if c.as_ref().equals(surface.as_ref()) { 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) { } else if !Self::map(c, &t, filter, processor, post_filter, false) {
return 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 => { TraversalAction::SkipChildren => {
// still process ourselves // still process ourselves
processor(surface, &mut data_guard.attributes, &mut data_guard.role, initial); processor(surface, &data_guard.public_data, initial);
true true
} }
TraversalAction::Break => false, TraversalAction::Break => false,

View File

@ -6,14 +6,13 @@ use wayland_server::{
}; };
use crate::wayland::{ use crate::wayland::{
compositor::{roles::Role, CompositorToken},
seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat}, seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat},
Serial, Serial,
}; };
use super::{with_source_metadata, DataDeviceData, DnDIconRole, SeatData}; use super::{with_source_metadata, DataDeviceData, SeatData};
pub(crate) struct DnDGrab<R> { pub(crate) struct DnDGrab {
start_data: GrabStartData, start_data: GrabStartData,
data_source: Option<wl_data_source::WlDataSource>, data_source: Option<wl_data_source::WlDataSource>,
current_focus: Option<wl_surface::WlSurface>, current_focus: Option<wl_surface::WlSurface>,
@ -22,20 +21,18 @@ pub(crate) struct DnDGrab<R> {
icon: Option<wl_surface::WlSurface>, icon: Option<wl_surface::WlSurface>,
origin: wl_surface::WlSurface, origin: wl_surface::WlSurface,
callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>, callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>,
token: CompositorToken<R>,
seat: Seat, seat: Seat,
} }
impl<R: Role<DnDIconRole> + 'static> DnDGrab<R> { impl DnDGrab {
pub(crate) fn new( pub(crate) fn new(
start_data: GrabStartData, start_data: GrabStartData,
source: Option<wl_data_source::WlDataSource>, source: Option<wl_data_source::WlDataSource>,
origin: wl_surface::WlSurface, origin: wl_surface::WlSurface,
seat: Seat, seat: Seat,
icon: Option<wl_surface::WlSurface>, icon: Option<wl_surface::WlSurface>,
token: CompositorToken<R>,
callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>, callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>,
) -> DnDGrab<R> { ) -> DnDGrab {
DnDGrab { DnDGrab {
start_data, start_data,
data_source: source, data_source: source,
@ -45,13 +42,12 @@ impl<R: Role<DnDIconRole> + 'static> DnDGrab<R> {
origin, origin,
icon, icon,
callback, callback,
token,
seat, seat,
} }
} }
} }
impl<R: Role<DnDIconRole> + 'static> PointerGrab for DnDGrab<R> { impl PointerGrab for DnDGrab {
fn motion( fn motion(
&mut self, &mut self,
_handle: &mut PointerInnerHandle<'_>, _handle: &mut PointerInnerHandle<'_>,
@ -211,11 +207,7 @@ impl<R: Role<DnDIconRole> + 'static> PointerGrab for DnDGrab<R> {
} }
} }
(&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped); (&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped);
if let Some(icon) = self.icon.take() { self.icon = None;
if icon.as_ref().is_alive() {
self.token.remove_role::<super::DnDIconRole>(&icon).unwrap();
}
}
// in all cases abandon the drop // in all cases abandon the drop
// no more buttons are pressed, release the grab // no more buttons are pressed, release the grab
handle.unset_grab(serial, time); handle.unset_grab(serial, time);

View File

@ -25,30 +25,22 @@
//! - the freestanding function [`start_dnd`] allows you to initiate a drag'n'drop event from the compositor //! - 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. //! 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 //! The module defines the role `"dnd_icon"` that is assigned to surfaces used as drag'n'drop icons.
//! represent surfaces that are used as a DnD icon.
//! //!
//! ## Initialization //! ## Initialization
//! //!
//! ``` //! ```
//! # extern crate wayland_server; //! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::data_device::{init_data_device, default_action_chooser, DnDIconRole}; //! use smithay::wayland::data_device::{init_data_device, default_action_chooser, DnDIconRole};
//! # use smithay::wayland::compositor::compositor_init; //! # 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 mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = compositor_init::<Roles, _, _>(&mut display, |_, _, _, _| {}, None);
//! // init the data device: //! // init the data device:
//! init_data_device( //! init_data_device(
//! &mut display, // the display //! &mut display, // the display
//! |dnd_event| { /* a callback to react to client DnD/selection actions */ }, //! |dnd_event| { /* a callback to react to client DnD/selection actions */ },
//! default_action_chooser, // a closure to choose the DnD action depending on clients //! default_action_chooser, // a closure to choose the DnD action depending on clients
//! // negociation //! // negociation
//! compositor_token.clone(), // a compositor token
//! None // insert a logger here //! None // insert a logger here
//! ); //! );
//! ``` //! ```
@ -65,7 +57,7 @@ use wayland_server::{
}; };
use crate::wayland::{ use crate::wayland::{
compositor::{roles::Role, CompositorToken}, compositor,
seat::{GrabStartData, Seat}, seat::{GrabStartData, Seat},
Serial, Serial,
}; };
@ -77,6 +69,8 @@ mod server_dnd_grab;
pub use self::data_source::{with_source_metadata, SourceMetadata}; pub use self::data_source::{with_source_metadata, SourceMetadata};
pub use self::server_dnd_grab::ServerDndEvent; 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 /// Events that are generated by interactions of the clients with the data device
#[derive(Debug)] #[derive(Debug)]
pub enum DataDeviceEvent { 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 /// 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 /// chosen (and thus the drag'n'drop should abort on drop), return
/// [`DndAction::empty()`](wayland_server::protocol::wl_data_device_manager::DndAction::empty). /// [`DndAction::empty()`](wayland_server::protocol::wl_data_device_manager::DndAction::empty).
pub fn init_data_device<F, C, R, L>( pub fn init_data_device<F, C, L>(
display: &mut Display, display: &mut Display,
callback: C, callback: C,
action_choice: F, action_choice: F,
token: CompositorToken<R>,
logger: L, logger: L,
) -> Global<wl_data_device_manager::WlDataDeviceManager> ) -> Global<wl_data_device_manager::WlDataDeviceManager>
where where
F: FnMut(DndAction, DndAction) -> DndAction + 'static, F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static, C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "data_device_mgr")); let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "data_device_mgr"));
@ -294,7 +286,7 @@ where
display.create_global( display.create_global(
3, 3,
Filter::new(move |(ddm, _version), _, _| { 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<C>(
} }
} }
fn implement_ddm<F, C, R>( fn implement_ddm<F, C>(
ddm: Main<wl_data_device_manager::WlDataDeviceManager>, ddm: Main<wl_data_device_manager::WlDataDeviceManager>,
callback: Rc<RefCell<C>>, callback: Rc<RefCell<C>>,
action_choice: Rc<RefCell<F>>, action_choice: Rc<RefCell<F>>,
token: CompositorToken<R>,
log: ::slog::Logger, log: ::slog::Logger,
) -> wl_data_device_manager::WlDataDeviceManager ) -> wl_data_device_manager::WlDataDeviceManager
where where
F: FnMut(DndAction, DndAction) -> DndAction + 'static, F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static, C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
{ {
use self::wl_data_device_manager::Request; use self::wl_data_device_manager::Request;
ddm.quick_assign(move |_ddm, req, _data| match req { ddm.quick_assign(move |_ddm, req, _data| match req {
@ -399,7 +389,6 @@ where
seat.clone(), seat.clone(),
callback.clone(), callback.clone(),
action_choice.clone(), action_choice.clone(),
token,
log.clone(), log.clone(),
); );
seat_data.borrow_mut().known_devices.push(data_device); seat_data.borrow_mut().known_devices.push(data_device);
@ -419,18 +408,16 @@ struct DataDeviceData {
action_choice: Rc<RefCell<dyn FnMut(DndAction, DndAction) -> DndAction + 'static>>, action_choice: Rc<RefCell<dyn FnMut(DndAction, DndAction) -> DndAction + 'static>>,
} }
fn implement_data_device<F, C, R>( fn implement_data_device<F, C>(
dd: Main<wl_data_device::WlDataDevice>, dd: Main<wl_data_device::WlDataDevice>,
seat: Seat, seat: Seat,
callback: Rc<RefCell<C>>, callback: Rc<RefCell<C>>,
action_choice: Rc<RefCell<F>>, action_choice: Rc<RefCell<F>>,
token: CompositorToken<R>,
log: ::slog::Logger, log: ::slog::Logger,
) -> wl_data_device::WlDataDevice ) -> wl_data_device::WlDataDevice
where where
F: FnMut(DndAction, DndAction) -> DndAction + 'static, F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static, C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
{ {
use self::wl_data_device::Request; use self::wl_data_device::Request;
let dd_data = DataDeviceData { let dd_data = DataDeviceData {
@ -449,7 +436,7 @@ where
if let Some(pointer) = seat.get_pointer() { if let Some(pointer) = seat.get_pointer() {
if pointer.has_grab(serial) { if pointer.has_grab(serial) {
if let Some(ref icon) = icon { if let Some(ref icon) = icon {
if token.give_role::<DnDIconRole>(icon).is_err() { if compositor::give_role(icon, DND_ICON_ROLE).is_err() {
dd.as_ref().post_error( dd.as_ref().post_error(
wl_data_device::Error::Role as u32, wl_data_device::Error::Role as u32,
"Given surface already has an other role".into(), "Given surface already has an other role".into(),
@ -470,7 +457,6 @@ where
origin, origin,
seat.clone(), seat.clone(),
icon, icon,
token,
callback.clone(), callback.clone(),
), ),
serial, serial,

View File

@ -265,7 +265,7 @@ where
format, format,
DmabufFlags::from_bits_truncate(flags), 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() { for (i, plane) in planes.into_iter().enumerate() {
let offset = plane.offset; let offset = plane.offset;
let stride = plane.stride; let stride = plane.stride;
@ -355,7 +355,7 @@ where
format, format,
DmabufFlags::from_bits_truncate(flags), 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() { for (i, plane) in planes.into_iter().enumerate() {
let offset = plane.offset; let offset = plane.offset;
let stride = plane.stride; let stride = plane.stride;

View File

@ -21,60 +21,32 @@
//! //!
//! ``` //! ```
//! # extern crate wayland_server; //! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! #
//! # use smithay::wayland::compositor::roles::*;
//! # use smithay::wayland::compositor::CompositorToken;
//! use smithay::wayland::explicit_synchronization::*; //! use smithay::wayland::explicit_synchronization::*;
//! # define_roles!(MyRoles);
//! #
//! # let mut display = wayland_server::Display::new(); //! # let mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<MyRoles, _, _>(
//! # &mut display,
//! # |_, _, _, _| {},
//! # None
//! # );
//! init_explicit_synchronization_global( //! init_explicit_synchronization_global(
//! &mut display, //! &mut display,
//! compositor_token,
//! None /* You can insert a logger here */ //! None /* You can insert a logger here */
//! ); //! );
//! ``` //! ```
//! //!
//! Then when handling a surface commit, you can retrieve the synchronization information for the surface //! Then when handling a surface commit, you can retrieve the synchronization information for the surface states:
//! data: //! ```
//! ```no_run
//! # extern crate wayland_server; //! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay; //! # #[macro_use] extern crate smithay;
//! # //! #
//! # use wayland_server::protocol::wl_surface::WlSurface; //! # use wayland_server::protocol::wl_surface::WlSurface;
//! # use smithay::wayland::compositor::CompositorToken;
//! # use smithay::wayland::explicit_synchronization::*; //! # use smithay::wayland::explicit_synchronization::*;
//! # //! #
//! # fn dummy_function<R: 'static>(surface: &WlSurface, compositor_token: CompositorToken<R>) { //! # fn dummy_function<R: 'static>(surface: &WlSurface) {
//! compositor_token.with_surface_data(&surface, |surface_attributes| { //! use smithay::wayland::compositor::with_states;
//! // While you retrieve the surface data from the commit ... //! with_states(&surface, |states| {
//! // Check the explicit synchronization data: //! let explicit_sync_state = states.cached_state.current::<ExplicitSyncState>();
//! match get_explicit_synchronization_state(surface_attributes) { //! /* process the explicit_sync_state */
//! 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
//! */
//! }
//! }
//! }); //! });
//! # } //! # }
//! ``` //! ```
use std::{ use std::{cell::RefCell, ops::Deref as _, os::unix::io::RawFd};
cell::RefCell,
ops::{Deref as _, DerefMut as _},
os::unix::io::RawFd,
};
use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{ use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{
zwp_linux_buffer_release_v1::ZwpLinuxBufferReleaseV1, 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 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 /// An object to signal end of use of a buffer
#[derive(Debug)] #[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 /// 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 /// 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`). /// 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)] #[derive(Debug)]
pub struct ExplicitSyncState { pub struct ExplicitSyncState {
/// An acquire `dma_fence` object, that you should wait on before accessing the contents of the /// 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<ExplicitBufferRelease>, pub release: Option<ExplicitBufferRelease>,
} }
struct InternalState { impl Default for ExplicitSyncState {
sync_state: ExplicitSyncState, fn default() -> Self {
sync_resource: ZwpLinuxSurfaceSynchronizationV1, 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 { struct ESUserData {
state: RefCell<Option<InternalState>>, state: RefCell<Option<ZwpLinuxSurfaceSynchronizationV1>>,
}
impl ESUserData {
fn take_state(&self) -> Option<ExplicitSyncState> {
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
}
}
} }
/// Possible errors you can send to an ill-behaving clients /// 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 {} 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<ExplicitSyncState, NoExplicitSync> {
attrs
.user_data
.get::<ESUserData>()
.and_then(|s| s.take_state())
.ok_or(NoExplicitSync)
}
/// Send a synchronization error to a client /// Send a synchronization error to a client
/// ///
/// See the enum definition for possible errors. These errors are protocol errors, meaning that /// 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 /// the client associated with this `SurfaceAttributes` will be killed as a result of calling this
/// function. /// function.
pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: ExplicitSyncError) { pub fn send_explicit_synchronization_error(attrs: &SurfaceData, error: ExplicitSyncError) {
if let Some(ref data) = attrs.user_data.get::<ESUserData>() { if let Some(ref data) = attrs.data_map.get::<ESUserData>() {
if let Some(state) = data.state.borrow().deref() { if let Some(sync_resource) = data.state.borrow().deref() {
match error { 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, zwp_linux_surface_synchronization_v1::Error::InvalidFence as u32,
"The fence specified by the client could not be imported.".into(), "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, zwp_linux_surface_synchronization_v1::Error::UnsupportedBuffer as u32,
"The buffer does not support explicit synchronization.".into(), "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, zwp_linux_surface_synchronization_v1::Error::NoBuffer as u32,
"No buffer was attached.".into(), "No buffer was attached.".into(),
), ),
@ -214,14 +182,12 @@ pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: Exp
/// Initialize the explicit synchronization global /// Initialize the explicit synchronization global
/// ///
/// See module-level documentation for its use. /// See module-level documentation for its use.
pub fn init_explicit_synchronization_global<R, L>( pub fn init_explicit_synchronization_global<L>(
display: &mut Display, display: &mut Display,
compositor: CompositorToken<R>,
logger: L, logger: L,
) -> Global<ZwpLinuxExplicitSynchronizationV1> ) -> Global<ZwpLinuxExplicitSynchronizationV1>
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
R: 'static,
{ {
let _log = let _log =
crate::slog_or_fallback(logger).new(o!("smithay_module" => "wayland_explicit_synchronization")); crate::slog_or_fallback(logger).new(o!("smithay_module" => "wayland_explicit_synchronization"));
@ -236,16 +202,17 @@ where
surface, surface,
} = req } = req
{ {
let exists = compositor.with_surface_data(&surface, |attrs| { let exists = with_states(&surface, |states| {
attrs.user_data.insert_if_missing(|| ESUserData { states.data_map.insert_if_missing(|| ESUserData {
state: RefCell::new(None), state: RefCell::new(None),
}); });
attrs states
.user_data .data_map
.get::<ESUserData>() .get::<ESUserData>()
.map(|ud| ud.state.borrow().is_some()) .map(|ud| ud.state.borrow().is_some())
.unwrap() .unwrap()
}); })
.unwrap_or(false);
if exists { if exists {
explicit_sync.as_ref().post_error( explicit_sync.as_ref().post_error(
zwp_linux_explicit_synchronization_v1::Error::SynchronizationExists as u32, zwp_linux_explicit_synchronization_v1::Error::SynchronizationExists as u32,
@ -253,17 +220,12 @@ where
); );
return; return;
} }
let surface_sync = implement_surface_sync(id, surface.clone(), compositor); let surface_sync = implement_surface_sync(id, surface.clone());
compositor.with_surface_data(&surface, |attrs| { with_states(&surface, |states| {
let data = attrs.user_data.get::<ESUserData>().unwrap(); let data = states.data_map.get::<ESUserData>().unwrap();
*data.state.borrow_mut() = Some(InternalState { *data.state.borrow_mut() = Some(surface_sync);
sync_state: ExplicitSyncState { })
acquire: None, .unwrap();
release: None,
},
sync_resource: surface_sync,
});
});
} }
}); });
}, },
@ -271,14 +233,10 @@ where
) )
} }
fn implement_surface_sync<R>( fn implement_surface_sync(
id: Main<ZwpLinuxSurfaceSynchronizationV1>, id: Main<ZwpLinuxSurfaceSynchronizationV1>,
surface: WlSurface, surface: WlSurface,
compositor: CompositorToken<R>, ) -> ZwpLinuxSurfaceSynchronizationV1 {
) -> ZwpLinuxSurfaceSynchronizationV1
where
R: 'static,
{
id.quick_assign(move |surface_sync, req, _| match req { id.quick_assign(move |surface_sync, req, _| match req {
zwp_linux_surface_synchronization_v1::Request::SetAcquireFence { fd } => { zwp_linux_surface_synchronization_v1::Request::SetAcquireFence { fd } => {
if !surface.as_ref().is_alive() { if !surface.as_ref().is_alive() {
@ -287,19 +245,18 @@ where
"The associated wl_surface was destroyed.".into(), "The associated wl_surface was destroyed.".into(),
) )
} }
compositor.with_surface_data(&surface, |attrs| { with_states(&surface, |states| {
let data = attrs.user_data.get::<ESUserData>().unwrap(); let mut pending = states.cached_state.pending::<ExplicitSyncState>();
if let Some(state) = data.state.borrow_mut().deref_mut() { if pending.acquire.is_some() {
if state.sync_state.acquire.is_some() {
surface_sync.as_ref().post_error( surface_sync.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32, zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32,
"Multiple fences added for a single surface commit.".into(), "Multiple fences added for a single surface commit.".into(),
) )
} else { } else {
state.sync_state.acquire = Some(fd); pending.acquire = Some(fd);
} }
} })
}); .unwrap();
} }
zwp_linux_surface_synchronization_v1::Request::GetRelease { release } => { zwp_linux_surface_synchronization_v1::Request::GetRelease { release } => {
if !surface.as_ref().is_alive() { if !surface.as_ref().is_alive() {
@ -308,30 +265,30 @@ where
"The associated wl_surface was destroyed.".into(), "The associated wl_surface was destroyed.".into(),
) )
} }
compositor.with_surface_data(&surface, |attrs| { with_states(&surface, |states| {
let data = attrs.user_data.get::<ESUserData>().unwrap(); let mut pending = states.cached_state.pending::<ExplicitSyncState>();
if let Some(state) = data.state.borrow_mut().deref_mut() { if pending.release.is_some() {
if state.sync_state.acquire.is_some() {
surface_sync.as_ref().post_error( surface_sync.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32, zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32,
"Multiple releases added for a single surface commit.".into(), "Multiple releases added for a single surface commit.".into(),
) )
} else { } else {
release.quick_assign(|_, _, _| {}); release.quick_assign(|_, _, _| {});
state.sync_state.release = Some(ExplicitBufferRelease { pending.release = Some(ExplicitBufferRelease {
release: release.deref().clone(), release: release.deref().clone(),
}); });
} }
} })
}); .unwrap();
} }
zwp_linux_surface_synchronization_v1::Request::Destroy => { zwp_linux_surface_synchronization_v1::Request::Destroy => {
// disable the ESUserData // disable the ESUserData
compositor.with_surface_data(&surface, |attrs| { with_states(&surface, |states| {
if let Some(ref mut data) = attrs.user_data.get::<ESUserData>() { if let Some(ref mut data) = states.data_map.get::<ESUserData>() {
*data.state.borrow_mut() = None; *data.state.borrow_mut() = None;
} }
}); })
.unwrap();
} }
_ => (), _ => (),
}); });

View File

@ -225,7 +225,7 @@ impl Output {
pub fn set_preferred(&self, mode: Mode) { pub fn set_preferred(&self, mode: Mode) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
inner.preferred_mode = Some(mode); 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); inner.modes.push(mode);
} }
} }
@ -233,7 +233,7 @@ impl Output {
/// Adds a mode to the list of known modes to this output /// Adds a mode to the list of known modes to this output
pub fn add_mode(&self, mode: Mode) { pub fn add_mode(&self, mode: Mode) {
let mut inner = self.inner.lock().unwrap(); 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); inner.modes.push(mode);
} }
} }
@ -270,7 +270,7 @@ impl Output {
) { ) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
if let Some(mode) = new_mode { 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.modes.push(mode);
} }
inner.current_mode = new_mode; inner.current_mode = new_mode;

View File

@ -9,21 +9,13 @@
//! //!
//! ``` //! ```
//! # extern crate wayland_server; //! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay; //! use smithay::wayland::seat::Seat;
//! 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]);
//! //!
//! # let mut display = wayland_server::Display::new(); //! # let mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = compositor_init::<Roles, _, _>(&mut display, |_, _, _, _| {}, None);
//! // insert the seat: //! // insert the seat:
//! let (seat, seat_global) = Seat::new( //! let (seat, seat_global) = Seat::new(
//! &mut display, // the display //! &mut display, // the display
//! "seat-0".into(), // the name of the seat, will be advertized to clients //! "seat-0".into(), // the name of the seat, will be advertized to clients
//! compositor_token.clone(), // the compositor token
//! None // insert a logger here //! None // insert a logger here
//! ); //! );
//! ``` //! ```
@ -32,13 +24,15 @@
//! //!
//! Once the seat is initialized, you can add capabilities to it. //! Once the seat is initialized, you can add capabilities to it.
//! //!
//! Currently, only pointer and keyboard capabilities are supported by //! Currently, only pointer and keyboard capabilities are supported by smithay.
//! smithay.
//! //!
//! You can add these capabilities via methods of the [`Seat`] struct: //! 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 //! 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. //! 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}; use std::{cell::RefCell, fmt, ops::Deref as _, rc::Rc};
@ -48,13 +42,11 @@ mod pointer;
pub use self::{ pub use self::{
keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig}, keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig},
pointer::{ pointer::{
AxisFrame, CursorImageRole, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle, AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle,
PointerInnerHandle, PointerInnerHandle,
}, },
}; };
use crate::wayland::compositor::{roles::Role, CompositorToken};
use wayland_server::{ use wayland_server::{
protocol::{wl_seat, wl_surface}, protocol::{wl_seat, wl_surface},
Display, Filter, Global, Main, UserDataMap, Display, Filter, Global, Main, UserDataMap,
@ -130,14 +122,8 @@ impl Seat {
/// You are provided with the state token to retrieve it (allowing /// You are provided with the state token to retrieve it (allowing
/// you to add or remove capabilities from it), and the global handle, /// you to add or remove capabilities from it), and the global handle,
/// in case you want to remove it. /// in case you want to remove it.
pub fn new<R, L>( pub fn new<L>(display: &mut Display, name: String, logger: L) -> (Seat, Global<wl_seat::WlSeat>)
display: &mut Display,
name: String,
token: CompositorToken<R>,
logger: L,
) -> (Seat, Global<wl_seat::WlSeat>)
where where
R: Role<CursorImageRole> + 'static,
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
{ {
let log = crate::slog_or_fallback(logger); let log = crate::slog_or_fallback(logger);
@ -155,7 +141,7 @@ impl Seat {
let global = display.create_global( let global = display.create_global(
5, 5,
Filter::new(move |(new_seat, _version), _, _| { 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(); let mut inner = arc.inner.borrow_mut();
if seat.as_ref().version() >= 2 { if seat.as_ref().version() >= 2 {
seat.name(arc.name.clone()); seat.name(arc.name.clone());
@ -197,32 +183,25 @@ impl Seat {
/// ///
/// ``` /// ```
/// # extern crate wayland_server; /// # extern crate wayland_server;
/// # #[macro_use] extern crate smithay;
/// # /// #
/// # use smithay::wayland::{seat::{Seat, CursorImageRole}, compositor::compositor_init}; /// # use smithay::wayland::seat::Seat;
/// #
/// # define_roles!(Roles => [CursorImage, CursorImageRole]);
/// # /// #
/// # let mut display = wayland_server::Display::new(); /// # let mut display = wayland_server::Display::new();
/// # let (compositor_token, _, _) = compositor_init::<Roles, _, _>(&mut display, |_, _, _, _| {}, None);
/// # let (mut seat, seat_global) = Seat::new( /// # let (mut seat, seat_global) = Seat::new(
/// # &mut display, /// # &mut display,
/// # "seat-0".into(), /// # "seat-0".into(),
/// # compositor_token.clone(),
/// # None /// # None
/// # ); /// # );
/// let pointer_handle = seat.add_pointer( /// let pointer_handle = seat.add_pointer(
/// compositor_token.clone(),
/// |new_status| { /* a closure handling requests from clients tot change the cursor icon */ } /// |new_status| { /* a closure handling requests from clients tot change the cursor icon */ }
/// ); /// );
/// ``` /// ```
pub fn add_pointer<R, F>(&mut self, token: CompositorToken<R>, cb: F) -> PointerHandle pub fn add_pointer<F>(&mut self, cb: F) -> PointerHandle
where where
R: Role<CursorImageRole> + 'static,
F: FnMut(CursorImageStatus) + 'static, F: FnMut(CursorImageStatus) + 'static,
{ {
let mut inner = self.arc.inner.borrow_mut(); 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() { if inner.pointer.is_some() {
// there is already a pointer, remove it and notify the clients // there is already a pointer, remove it and notify the clients
// of the change // of the change
@ -344,21 +323,14 @@ impl ::std::cmp::PartialEq for Seat {
} }
} }
fn implement_seat<R>( fn implement_seat(seat: Main<wl_seat::WlSeat>, arc: Rc<SeatRc>) -> wl_seat::WlSeat {
seat: Main<wl_seat::WlSeat>,
arc: Rc<SeatRc>,
token: CompositorToken<R>,
) -> wl_seat::WlSeat
where
R: Role<CursorImageRole> + 'static,
{
let dest_arc = arc.clone(); let dest_arc = arc.clone();
seat.quick_assign(move |seat, request, _| { seat.quick_assign(move |seat, request, _| {
let arc = seat.as_ref().user_data().get::<Rc<SeatRc>>().unwrap(); let arc = seat.as_ref().user_data().get::<Rc<SeatRc>>().unwrap();
let inner = arc.inner.borrow_mut(); let inner = arc.inner.borrow_mut();
match request { match request {
wl_seat::Request::GetPointer { id } => { 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 { if let Some(ref ptr_handle) = inner.pointer {
ptr_handle.new_pointer(pointer); ptr_handle.new_pointer(pointer);
} else { } else {

View File

@ -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::{ use wayland_server::{
protocol::{ protocol::{
@ -8,12 +8,14 @@ use wayland_server::{
Filter, Main, Filter, Main,
}; };
use crate::wayland::compositor::{roles::Role, CompositorToken}; use crate::wayland::compositor;
use crate::wayland::Serial; use crate::wayland::Serial;
static CURSOR_IMAGE_ROLE: &str = "cursor_image";
/// The role representing a surface set as the pointer cursor /// The role representing a surface set as the pointer cursor
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct CursorImageRole { pub struct CursorImageAttributes {
/// Location of the hotspot of the pointer in the surface /// Location of the hotspot of the pointer in the surface
pub hotspot: (i32, i32), pub hotspot: (i32, i32),
} }
@ -72,9 +74,8 @@ impl fmt::Debug for PointerInternal {
} }
impl PointerInternal { impl PointerInternal {
fn new<F, R>(token: CompositorToken<R>, mut cb: F) -> PointerInternal fn new<F>(mut cb: F) -> PointerInternal
where where
R: Role<CursorImageRole> + 'static,
F: FnMut(CursorImageStatus) + 'static, F: FnMut(CursorImageStatus) + 'static,
{ {
let mut old_status = CursorImageStatus::Default; let mut old_status = CursorImageStatus::Default;
@ -86,11 +87,7 @@ impl PointerInternal {
CursorImageStatus::Image(ref new_surface) if new_surface == &surface => { CursorImageStatus::Image(ref new_surface) if new_surface == &surface => {
// don't remove the role, we are just re-binding the same surface // don't remove the role, we are just re-binding the same surface
} }
_ => { _ => {}
if surface.as_ref().is_alive() {
token.remove_role::<CursorImageRole>(&surface).unwrap();
}
}
} }
} }
cb(new_status) cb(new_status)
@ -566,24 +563,16 @@ impl AxisFrame {
} }
} }
pub(crate) fn create_pointer_handler<F, R>(token: CompositorToken<R>, cb: F) -> PointerHandle pub(crate) fn create_pointer_handler<F>(cb: F) -> PointerHandle
where where
R: Role<CursorImageRole> + 'static,
F: FnMut(CursorImageStatus) + 'static, F: FnMut(CursorImageStatus) + 'static,
{ {
PointerHandle { PointerHandle {
inner: Rc::new(RefCell::new(PointerInternal::new(token, cb))), inner: Rc::new(RefCell::new(PointerInternal::new(cb))),
} }
} }
pub(crate) fn implement_pointer<R>( pub(crate) fn implement_pointer(pointer: Main<WlPointer>, handle: Option<&PointerHandle>) -> WlPointer {
pointer: Main<WlPointer>,
handle: Option<&PointerHandle>,
token: CompositorToken<R>,
) -> WlPointer
where
R: Role<CursorImageRole> + 'static,
{
let inner = handle.map(|h| h.inner.clone()); let inner = handle.map(|h| h.inner.clone());
pointer.quick_assign(move |pointer, request, _data| { pointer.quick_assign(move |pointer, request, _data| {
match request { match request {
@ -606,14 +595,9 @@ where
if focus.as_ref().same_client_as(&pointer.as_ref()) { if focus.as_ref().same_client_as(&pointer.as_ref()) {
match surface { match surface {
Some(surface) => { Some(surface) => {
let role_data = CursorImageRole { // tolerate re-using the same surface
hotspot: (hotspot_x, hotspot_y), if compositor::give_role(&surface, CURSOR_IMAGE_ROLE).is_err()
}; && compositor::get_role(&surface) != Some(CURSOR_IMAGE_ROLE)
// 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()
{ {
pointer.as_ref().post_error( pointer.as_ref().post_error(
wl_pointer::Error::Role as u32, wl_pointer::Error::Role as u32,
@ -621,6 +605,20 @@ where
); );
return; return;
} }
compositor::with_states(&surface, |states| {
states.data_map.insert_if_missing_threadsafe(|| {
Mutex::new(CursorImageAttributes { hotspot: (0, 0) })
});
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.unwrap()
.lock()
.unwrap()
.hotspot = (hotspot_x, hotspot_y);
})
.unwrap();
image_callback(CursorImageStatus::Image(surface)); image_callback(CursorImageStatus::Image(surface));
} }
None => { None => {

View File

@ -23,37 +23,18 @@
//! //!
//! ### Initialization //! ### Initialization
//! //!
//! To initialize this handler, simple use the [`wl_shell_init`] //! To initialize this handler, simple use the [`wl_shell_init`] function provided in this module.
//! 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.
//! //!
//! ```no_run //! ```no_run
//! # extern crate wayland_server; //! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! # extern crate wayland_protocols;
//! # //! #
//! use smithay::wayland::compositor::roles::*; //! use smithay::wayland::shell::legacy::{wl_shell_init, ShellRequest};
//! 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]
//! );
//! //!
//! # let mut display = wayland_server::Display::new(); //! # let mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<MyRoles, _, _>(
//! # &mut display,
//! # |_, _, _, _| {},
//! # None
//! # );
//! let (shell_state, _) = wl_shell_init( //! let (shell_state, _) = wl_shell_init(
//! &mut display, //! &mut display,
//! // token from the compositor implementation
//! compositor_token,
//! // your implementation //! // your implementation
//! |event: ShellRequest<_>| { /* ... */ }, //! |event: ShellRequest| { /* ... */ },
//! None // put a logger if you want //! None // put a logger if you want
//! ); //! );
//! //!
@ -66,8 +47,7 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use crate::wayland::compositor::{roles::Role, CompositorToken}; use crate::wayland::{compositor, Serial};
use crate::wayland::Serial;
use wayland_server::{ use wayland_server::{
protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface}, 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 /// Metadata associated with the `wl_surface` role
#[derive(Debug)] #[derive(Debug)]
pub struct ShellSurfaceRole { pub struct ShellSurfaceAttributes {
/// Title of the surface /// Title of the surface
pub title: String, pub title: String,
/// Class of the surface /// Class of the surface
@ -89,28 +69,13 @@ pub struct ShellSurfaceRole {
} }
/// A handle to a shell surface /// A handle to a shell surface
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct ShellSurface<R> { pub struct ShellSurface {
wl_surface: wl_surface::WlSurface, wl_surface: wl_surface::WlSurface,
shell_surface: wl_shell_surface::WlShellSurface, shell_surface: wl_shell_surface::WlShellSurface,
token: CompositorToken<R>,
} }
// We implement Clone manually because #[derive(..)] would require R: Clone. impl ShellSurface {
impl<R> Clone for ShellSurface<R> {
fn clone(&self) -> Self {
Self {
wl_surface: self.wl_surface.clone(),
shell_surface: self.shell_surface.clone(),
token: self.token,
}
}
}
impl<R> ShellSurface<R>
where
R: Role<ShellSurfaceRole> + 'static,
{
/// Is the shell surface referred by this handle still alive? /// Is the shell surface referred by this handle still alive?
pub fn alive(&self) -> bool { pub fn alive(&self) -> bool {
self.shell_surface.as_ref().is_alive() && self.wl_surface.as_ref().is_alive() self.shell_surface.as_ref().is_alive() && self.wl_surface.as_ref().is_alive()
@ -145,8 +110,13 @@ where
if !self.alive() { if !self.alive() {
return Err(PingError::DeadSurface); return Err(PingError::DeadSurface);
} }
self.token compositor::with_states(&self.wl_surface, |states| {
.with_role_data(&self.wl_surface, |data| { let mut data = states
.data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
if let Some(pending_ping) = data.pending_ping { if let Some(pending_ping) = data.pending_ping {
return Err(PingError::PingAlreadyPending(pending_ping)); return Err(PingError::PingAlreadyPending(pending_ping));
} }
@ -225,13 +195,13 @@ pub enum ShellSurfaceKind {
/// A request triggered by a `wl_shell_surface` /// A request triggered by a `wl_shell_surface`
#[derive(Debug)] #[derive(Debug)]
pub enum ShellRequest<R> { pub enum ShellRequest {
/// A new shell surface was created /// A new shell surface was created
/// ///
/// by default it has no kind and this should not be displayed /// by default it has no kind and this should not be displayed
NewShellSurface { NewShellSurface {
/// The created surface /// The created surface
surface: ShellSurface<R>, surface: ShellSurface,
}, },
/// A pong event /// A pong event
/// ///
@ -239,14 +209,14 @@ pub enum ShellRequest<R> {
/// event, smithay has already checked that the responded serial was valid. /// event, smithay has already checked that the responded serial was valid.
Pong { Pong {
/// The surface that sent the pong /// The surface that sent the pong
surface: ShellSurface<R>, surface: ShellSurface,
}, },
/// Start of an interactive move /// Start of an interactive move
/// ///
/// The surface requests that an interactive move is started on it /// The surface requests that an interactive move is started on it
Move { Move {
/// The surface requesting the move /// The surface requesting the move
surface: ShellSurface<R>, surface: ShellSurface,
/// Serial of the implicit grab that initiated the move /// Serial of the implicit grab that initiated the move
serial: Serial, serial: Serial,
/// Seat associated with the move /// Seat associated with the move
@ -257,7 +227,7 @@ pub enum ShellRequest<R> {
/// The surface requests that an interactive resize is started on it /// The surface requests that an interactive resize is started on it
Resize { Resize {
/// The surface requesting the resize /// The surface requesting the resize
surface: ShellSurface<R>, surface: ShellSurface,
/// Serial of the implicit grab that initiated the resize /// Serial of the implicit grab that initiated the resize
serial: Serial, serial: Serial,
/// Seat associated with the resize /// Seat associated with the resize
@ -268,7 +238,7 @@ pub enum ShellRequest<R> {
/// The surface changed its kind /// The surface changed its kind
SetKind { SetKind {
/// The surface /// The surface
surface: ShellSurface<R>, surface: ShellSurface,
/// Its new kind /// Its new kind
kind: ShellSurfaceKind, kind: ShellSurfaceKind,
}, },
@ -279,36 +249,31 @@ pub enum ShellRequest<R> {
/// This state allows you to retrieve a list of surfaces /// This state allows you to retrieve a list of surfaces
/// currently known to the shell global. /// currently known to the shell global.
#[derive(Debug)] #[derive(Debug)]
pub struct ShellState<R> { pub struct ShellState {
known_surfaces: Vec<ShellSurface<R>>, known_surfaces: Vec<ShellSurface>,
} }
impl<R> ShellState<R> impl ShellState {
where
R: Role<ShellSurfaceRole> + 'static,
{
/// Cleans the internal surface storage by removing all dead surfaces /// Cleans the internal surface storage by removing all dead surfaces
pub(crate) fn cleanup_surfaces(&mut self) { pub(crate) fn cleanup_surfaces(&mut self) {
self.known_surfaces.retain(|s| s.alive()); self.known_surfaces.retain(|s| s.alive());
} }
/// Access all the shell surfaces known by this handler /// Access all the shell surfaces known by this handler
pub fn surfaces(&self) -> &[ShellSurface<R>] { pub fn surfaces(&self) -> &[ShellSurface] {
&self.known_surfaces[..] &self.known_surfaces[..]
} }
} }
/// Create a new `wl_shell` global /// Create a new `wl_shell` global
pub fn wl_shell_init<R, L, Impl>( pub fn wl_shell_init<L, Impl>(
display: &mut Display, display: &mut Display,
ctoken: CompositorToken<R>,
implementation: Impl, implementation: Impl,
logger: L, logger: L,
) -> (Arc<Mutex<ShellState<R>>>, Global<wl_shell::WlShell>) ) -> (Arc<Mutex<ShellState>>, Global<wl_shell::WlShell>)
where where
R: Role<ShellSurfaceRole> + 'static,
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,
Impl: FnMut(ShellRequest<R>) + 'static, Impl: FnMut(ShellRequest) + 'static,
{ {
let _log = crate::slog_or_fallback(logger); let _log = crate::slog_or_fallback(logger);
@ -322,7 +287,7 @@ where
let global = display.create_global( let global = display.create_global(
1, 1,
Filter::new(move |(shell, _version), _, _data| { 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());
}), }),
); );

View File

@ -10,116 +10,116 @@ use wayland_server::{
Filter, Main, 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 crate::wayland::Serial;
use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceKind, ShellSurfaceRole}; use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceAttributes, ShellSurfaceKind};
pub(crate) fn implement_shell<R, Impl>( pub(crate) fn implement_shell<Impl>(
shell: Main<wl_shell::WlShell>, shell: Main<wl_shell::WlShell>,
ctoken: CompositorToken<R>,
implementation: Rc<RefCell<Impl>>, implementation: Rc<RefCell<Impl>>,
state: Arc<Mutex<ShellState<R>>>, state: Arc<Mutex<ShellState>>,
) where ) where
R: Role<ShellSurfaceRole> + 'static, Impl: FnMut(ShellRequest) + 'static,
Impl: FnMut(ShellRequest<R>) + 'static,
{ {
shell.quick_assign(move |shell, req, _data| { shell.quick_assign(move |shell, req, _data| {
let (id, surface) = match req { let (id, surface) = match req {
wl_shell::Request::GetShellSurface { id, surface } => (id, surface), wl_shell::Request::GetShellSurface { id, surface } => (id, surface),
_ => unreachable!(), _ => unreachable!(),
}; };
let role_data = ShellSurfaceRole { if compositor::give_role(&surface, WL_SHELL_SURFACE_ROLE).is_err() {
title: "".into(),
class: "".into(),
pending_ping: None,
};
if ctoken.give_role_with(&surface, role_data).is_err() {
shell shell
.as_ref() .as_ref()
.post_error(wl_shell::Error::Role as u32, "Surface already has a role.".into()); .post_error(wl_shell::Error::Role as u32, "Surface already has a role.".into());
return; return;
} }
let shell_surface = compositor::with_states(&surface, |states| {
implement_shell_surface(id, surface, implementation.clone(), ctoken, state.clone()); 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 state
.lock() .lock()
.unwrap() .unwrap()
.known_surfaces .known_surfaces
.push(make_handle(&shell_surface, ctoken)); .push(make_handle(&shell_surface));
let mut imp = implementation.borrow_mut(); let mut imp = implementation.borrow_mut();
(&mut *imp)(ShellRequest::NewShellSurface { (&mut *imp)(ShellRequest::NewShellSurface {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
}); });
}); });
} }
fn make_handle<R>( fn make_handle(shell_surface: &wl_shell_surface::WlShellSurface) -> ShellSurface {
shell_surface: &wl_shell_surface::WlShellSurface,
token: CompositorToken<R>,
) -> ShellSurface<R>
where
R: Role<ShellSurfaceRole> + 'static,
{
let data = shell_surface let data = shell_surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
ShellSurface { ShellSurface {
wl_surface: data.surface.clone(), wl_surface: data.surface.clone(),
shell_surface: shell_surface.clone(), shell_surface: shell_surface.clone(),
token,
} }
} }
pub(crate) struct ShellSurfaceUserData<R> { pub(crate) struct ShellSurfaceUserData {
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
state: Arc<Mutex<ShellState<R>>>, state: Arc<Mutex<ShellState>>,
} }
fn implement_shell_surface<R, Impl>( fn implement_shell_surface<Impl>(
shell_surface: Main<wl_shell_surface::WlShellSurface>, shell_surface: Main<wl_shell_surface::WlShellSurface>,
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
implementation: Rc<RefCell<Impl>>, implementation: Rc<RefCell<Impl>>,
ctoken: CompositorToken<R>, state: Arc<Mutex<ShellState>>,
state: Arc<Mutex<ShellState<R>>>,
) -> wl_shell_surface::WlShellSurface ) -> wl_shell_surface::WlShellSurface
where where
R: Role<ShellSurfaceRole> + 'static, Impl: FnMut(ShellRequest) + 'static,
Impl: FnMut(ShellRequest<R>) + 'static,
{ {
use self::wl_shell_surface::Request; use self::wl_shell_surface::Request;
shell_surface.quick_assign(move |shell_surface, req, _data| { shell_surface.quick_assign(move |shell_surface, req, _data| {
let data = shell_surface let data = shell_surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
let mut user_impl = implementation.borrow_mut(); let mut user_impl = implementation.borrow_mut();
match req { match req {
Request::Pong { serial } => { Request::Pong { serial } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
let valid = ctoken let valid = compositor::with_states(&data.surface, |states| {
.with_role_data(&data.surface, |data| { let mut guard = states
if data.pending_ping == Some(serial) { .data_map
data.pending_ping = None; .get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
if guard.pending_ping == Some(serial) {
guard.pending_ping = None;
true true
} else { } else {
false false
} }
}) })
.expect("wl_shell_surface exists but surface has not the right role?"); .unwrap();
if valid { if valid {
(&mut *user_impl)(ShellRequest::Pong { (&mut *user_impl)(ShellRequest::Pong {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
}); });
} }
} }
Request::Move { seat, serial } => { Request::Move { seat, serial } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::Move { (&mut *user_impl)(ShellRequest::Move {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
serial, serial,
seat, seat,
}) })
@ -127,18 +127,18 @@ where
Request::Resize { seat, serial, edges } => { Request::Resize { seat, serial, edges } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::Resize { (&mut *user_impl)(ShellRequest::Resize {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
serial, serial,
seat, seat,
edges, edges,
}) })
} }
Request::SetToplevel => (&mut *user_impl)(ShellRequest::SetKind { Request::SetToplevel => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Toplevel, kind: ShellSurfaceKind::Toplevel,
}), }),
Request::SetTransient { parent, x, y, flags } => (&mut *user_impl)(ShellRequest::SetKind { 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 { kind: ShellSurfaceKind::Transient {
parent, parent,
location: (x, y), location: (x, y),
@ -150,7 +150,7 @@ where
framerate, framerate,
output, output,
} => (&mut *user_impl)(ShellRequest::SetKind { } => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Fullscreen { kind: ShellSurfaceKind::Fullscreen {
method, method,
framerate, framerate,
@ -167,7 +167,7 @@ where
} => { } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::SetKind { (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Popup { kind: ShellSurfaceKind::Popup {
parent, parent,
serial, serial,
@ -178,18 +178,32 @@ where
}) })
} }
Request::SetMaximized { output } => (&mut *user_impl)(ShellRequest::SetKind { Request::SetMaximized { output } => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken), surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Maximized { output }, kind: ShellSurfaceKind::Maximized { output },
}), }),
Request::SetTitle { title } => { Request::SetTitle { title } => {
ctoken compositor::with_states(&data.surface, |states| {
.with_role_data(&data.surface, |data| data.title = title) let mut guard = states
.expect("wl_shell_surface exists but surface has not shell_surface role?!"); .data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
guard.title = title;
})
.unwrap();
} }
Request::SetClass { class_ } => { Request::SetClass { class_ } => {
ctoken compositor::with_states(&data.surface, |states| {
.with_role_data(&data.surface, |data| data.class = class_) let mut guard = states
.expect("wl_shell_surface exists but surface has not shell_surface role?!"); .data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
guard.class = class_;
})
.unwrap();
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -200,7 +214,7 @@ where
let data = shell_surface let data = shell_surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
data.state.lock().unwrap().cleanup_surfaces(); data.state.lock().unwrap().cleanup_surfaces();
}, },

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex};
use crate::wayland::compositor::{roles::*, CompositorToken}; use crate::wayland::compositor;
use crate::wayland::shell::xdg::{ConfigureError, PopupState}; use crate::wayland::shell::xdg::PopupState;
use crate::wayland::Serial; use crate::wayland::Serial;
use wayland_protocols::xdg_shell::server::{ use wayland_protocols::xdg_shell::server::{
xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
@ -12,25 +13,25 @@ use crate::utils::Rectangle;
use super::{ use super::{
make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData, make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole, ShellData, SurfaceCachedState, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRoleAttributes,
XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRoleAttributes,
}; };
pub(crate) fn implement_wm_base<R>( static XDG_TOPLEVEL_ROLE: &str = "xdg_toplevel";
static XDG_POPUP_ROLE: &str = "xdg_toplevel";
pub(crate) fn implement_wm_base(
shell: Main<xdg_wm_base::XdgWmBase>, shell: Main<xdg_wm_base::XdgWmBase>,
shell_data: &ShellData<R>, shell_data: &ShellData,
) -> xdg_wm_base::XdgWmBase ) -> xdg_wm_base::XdgWmBase {
where shell.quick_assign(|shell, req, _data| wm_implementation(req, shell.deref().clone()));
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
shell.quick_assign(|shell, req, _data| wm_implementation::<R>(req, shell.deref().clone()));
shell.as_ref().user_data().set(|| ShellUserData { shell.as_ref().user_data().set(|| ShellUserData {
shell_data: shell_data.clone(), shell_data: shell_data.clone(),
client_data: Mutex::new(make_shell_client_data()), client_data: Mutex::new(make_shell_client_data()),
}); });
let mut user_impl = shell_data.user_impl.borrow_mut(); let mut user_impl = shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::NewClient { (&mut *user_impl)(XdgRequest::NewClient {
client: make_shell_client(&shell, shell_data.compositor_token), client: make_shell_client(&shell),
}); });
shell.deref().clone() shell.deref().clone()
} }
@ -39,26 +40,19 @@ where
* xdg_shell * xdg_shell
*/ */
pub(crate) struct ShellUserData<R> { pub(crate) struct ShellUserData {
shell_data: ShellData<R>, shell_data: ShellData,
pub(crate) client_data: Mutex<ShellClientData>, pub(crate) client_data: Mutex<ShellClientData>,
} }
pub(crate) fn make_shell_client<R>( pub(crate) fn make_shell_client(resource: &xdg_wm_base::XdgWmBase) -> ShellClient {
resource: &xdg_wm_base::XdgWmBase,
token: CompositorToken<R>,
) -> ShellClient<R> {
ShellClient { ShellClient {
kind: super::ShellClientKind::Xdg(resource.clone()), kind: super::ShellClientKind::Xdg(resource.clone()),
_token: token,
} }
} }
fn wm_implementation<R>(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) fn wm_implementation(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) {
where let data = shell.as_ref().user_data().get::<ShellUserData>().unwrap();
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request { match request {
xdg_wm_base::Request::Destroy => { xdg_wm_base::Request::Destroy => {
// all is handled by destructor // all is handled by destructor
@ -70,14 +64,13 @@ where
// Do not assign a role to the surface here // Do not assign a role to the surface here
// xdg_surface is not role, only xdg_toplevel and // xdg_surface is not role, only xdg_toplevel and
// xdg_popup are defined as roles // xdg_popup are defined as roles
id.quick_assign(|surface, req, _data| { id.quick_assign(|surface, req, _data| xdg_surface_implementation(req, surface.deref().clone()));
xdg_surface_implementation::<R>(req, surface.deref().clone()) id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface(surface)));
});
id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface::<R>(surface)));
id.as_ref().user_data().set(|| XdgSurfaceUserData { id.as_ref().user_data().set(|| XdgSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: surface, wl_surface: surface,
wm_base: shell.clone(), wm_base: shell.clone(),
has_active_role: AtomicBool::new(false),
}); });
} }
xdg_wm_base::Request::Pong { serial } => { xdg_wm_base::Request::Pong { serial } => {
@ -94,7 +87,7 @@ where
if valid { if valid {
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::ClientPong { (&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_positioner::XdgPositioner>) -> xdg_
* xdg_surface * xdg_surface
*/ */
struct XdgSurfaceUserData<R> { struct XdgSurfaceUserData {
shell_data: ShellData<R>, shell_data: ShellData,
wl_surface: wl_surface::WlSurface, wl_surface: wl_surface::WlSurface,
wm_base: xdg_wm_base::XdgWmBase, wm_base: xdg_wm_base::XdgWmBase,
has_active_role: AtomicBool,
} }
fn destroy_surface<R>(surface: xdg_surface::XdgSurface) fn destroy_surface(surface: xdg_surface::XdgSurface) {
where let data = surface.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = surface
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
if !data.wl_surface.as_ref().is_alive() { if !data.wl_surface.as_ref().is_alive() {
// the wl_surface is destroyed, this means the client is not // the wl_surface is destroyed, this means the client is not
// trying to change the role but it's a cleanup (possibly a // trying to change the role but it's a cleanup (possibly a
@ -191,25 +178,12 @@ where
return; 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. // No role assigned to the surface, we can exit early.
return; return;
} }
let has_active_xdg_role = data if data.has_active_role.load(Ordering::Acquire) {
.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 {
data.wm_base.as_ref().post_error( data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32, xdg_wm_base::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(), "xdg_surface was destroyed before its role object".into(),
@ -217,14 +191,11 @@ where
} }
} }
fn xdg_surface_implementation<R>(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) fn xdg_surface_implementation(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) {
where
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = xdg_surface let data = xdg_surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<XdgSurfaceUserData<R>>() .get::<XdgSurfaceUserData>()
.unwrap(); .unwrap();
match request { match request {
xdg_surface::Request::Destroy => { xdg_surface::Request::Destroy => {
@ -235,14 +206,7 @@ where
let surface = &data.wl_surface; let surface = &data.wl_surface;
let shell = &data.wm_base; let shell = &data.wm_base;
let role_data = XdgToplevelSurfaceRole::Some(Default::default()); if compositor::give_role(surface, XDG_TOPLEVEL_ROLE).is_err() {
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error( shell.as_ref().post_error(
xdg_wm_base::Error::Role as u32, xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(), "Surface already has a role.".into(),
@ -250,10 +214,19 @@ where
return; return;
} }
id.quick_assign(|toplevel, req, _data| { data.has_active_role.store(true, Ordering::Release);
toplevel_implementation::<R>(req, toplevel.deref().clone())
}); compositor::with_states(surface, |states| {
id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel::<R>(toplevel))); 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 { id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
@ -286,11 +259,7 @@ where
.clone(); .clone();
let parent_surface = parent.map(|parent| { let parent_surface = parent.map(|parent| {
let parent_data = parent let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
parent_data.wl_surface.clone() parent_data.wl_surface.clone()
}); });
@ -298,21 +267,15 @@ where
let surface = &data.wl_surface; let surface = &data.wl_surface;
let shell = &data.wm_base; let shell = &data.wm_base;
let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes { let attributes = XdgPopupSurfaceRoleAttributes {
parent: parent_surface, parent: parent_surface,
server_pending: Some(PopupState { server_pending: Some(PopupState {
// Set the positioner data as the popup geometry // Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(), geometry: positioner_data.get_geometry(),
}), }),
..Default::default() ..Default::default()
}); };
if compositor::give_role(surface, XDG_POPUP_ROLE).is_err() {
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error( shell.as_ref().post_error(
xdg_wm_base::Error::Role as u32, xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(), "Surface already has a role.".into(),
@ -320,8 +283,25 @@ where
return; return;
} }
id.quick_assign(|popup, req, _data| xdg_popup_implementation::<R>(req, popup.deref().clone())); data.has_active_role.store(true, Ordering::Release);
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
compositor::with_states(surface, |states| {
states
.data_map
.insert_if_missing_threadsafe(|| Mutex::new(XdgPopupSurfaceRoleAttributes::default()));
*states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.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 { id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
@ -346,33 +326,28 @@ where
// which is a protocol error. // which is a protocol error.
let surface = &data.wl_surface; let surface = &data.wl_surface;
if !data.shell_data.compositor_token.has_a_role(surface) { let role = compositor::get_role(surface);
data.wm_base.as_ref().post_error(
if role.is_none() {
xdg_surface.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32, xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(), "xdg_surface must have a role.".into(),
); );
return; return;
} }
// Set the next window geometry here, the geometry will be moved from if role != Some(XDG_TOPLEVEL_ROLE) && role != Some(XDG_POPUP_ROLE) {
// 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 {
data.wm_base.as_ref().post_error( data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32, xdg_wm_base::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
); );
} }
compositor::with_states(surface, |states| {
states.cached_state.pending::<SurfaceCachedState>().geometry =
Some(Rectangle { x, y, width, height });
})
.unwrap();
} }
xdg_surface::Request::AckConfigure { serial } => { xdg_surface::Request::AckConfigure { serial } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
@ -381,8 +356,8 @@ where
// Check the role of the surface, this can be either xdg_toplevel // 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 // or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error. // which is a protocol error.
if !data.shell_data.compositor_token.has_a_role(surface) { if compositor::get_role(surface).is_none() {
data.wm_base.as_ref().post_error( xdg_surface.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32, xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(), "xdg_surface must have a role.".into(),
); );
@ -401,21 +376,38 @@ where
// width, height, min/max size, maximized, fullscreen, resizing, activated // width, height, min/max size, maximized, fullscreen, resizing, activated
// //
// This can be used to integrate custom protocol extensions // This can be used to integrate custom protocol extensions
// let found_configure = compositor::with_states(surface, |states| {
let configure = match data if states.role == Some(XDG_TOPLEVEL_ROLE) {
.shell_data Ok(states
.compositor_token .data_map
.with_xdg_role(surface, |role| role.ack_configure(serial)) .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
{ .unwrap()
Ok(Ok(configure)) => configure, .lock()
Ok(Err(ConfigureError::SerialNotFound(serial))) => { .unwrap()
.ack_configure(serial))
} else if states.role == Some(XDG_POPUP_ROLE) {
Ok(states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.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( data.wm_base.as_ref().post_error(
xdg_wm_base::Error::InvalidSurfaceState as u32, xdg_wm_base::Error::InvalidSurfaceState as u32,
format!("wrong configure serial: {}", <u32>::from(serial)), format!("wrong configure serial: {}", <u32>::from(serial)),
); );
return; return;
} }
Err(_) => { Err(()) => {
data.wm_base.as_ref().post_error( data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32, xdg_wm_base::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
@ -438,64 +430,54 @@ where
* xdg_toplevel * xdg_toplevel
*/ */
pub(crate) struct ShellSurfaceUserData<R> { pub(crate) struct ShellSurfaceUserData {
pub(crate) shell_data: ShellData<R>, pub(crate) shell_data: ShellData,
pub(crate) wl_surface: wl_surface::WlSurface, pub(crate) wl_surface: wl_surface::WlSurface,
pub(crate) wm_base: xdg_wm_base::XdgWmBase, pub(crate) wm_base: xdg_wm_base::XdgWmBase,
pub(crate) xdg_surface: xdg_surface::XdgSurface, pub(crate) xdg_surface: xdg_surface::XdgSurface,
} }
// Utility functions allowing to factor out a lot of the upcoming logic // Utility functions allowing to factor out a lot of the upcoming logic
fn with_surface_toplevel_role_data<R, F, T>( fn with_surface_toplevel_role_data<F, T>(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
where where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T, F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
shell_data compositor::with_states(&data.wl_surface, |states| {
.compositor_token f(&mut *states
.with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| { .data_map
let attributes = role .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.as_mut() .unwrap()
.expect("xdg_toplevel exists but role has been destroyed?!"); .lock()
f(attributes) .unwrap())
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!") .unwrap()
} }
fn with_surface_toplevel_client_pending<R, F, T>( fn with_toplevel_pending_state<F, T>(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
where where
R: Role<XdgToplevelSurfaceRole> + 'static, F: FnOnce(&mut SurfaceCachedState) -> T,
F: FnOnce(&mut ToplevelClientPending) -> T,
{ {
with_surface_toplevel_role_data(shell_data, toplevel, |data| { let data = toplevel
if data.client_pending.is_none() { .as_ref()
data.client_pending = Some(Default::default()); .user_data()
} .get::<ShellSurfaceUserData>()
f(&mut data.client_pending.as_mut().unwrap()) .unwrap();
compositor::with_states(&data.wl_surface, |states| {
f(&mut *states.cached_state.pending::<SurfaceCachedState>())
}) })
.unwrap()
} }
pub fn send_toplevel_configure<R>(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) pub fn send_toplevel_configure(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
let (width, height) = configure.state.size.unwrap_or((0, 0)); let (width, height) = configure.state.size.unwrap_or((0, 0));
@ -518,27 +500,23 @@ where
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
} }
fn make_toplevel_handle<R: 'static>(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface<R> { fn make_toplevel_handle(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
super::ToplevelSurface { super::ToplevelSurface {
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
shell_surface: ToplevelKind::Xdg(resource.clone()), shell_surface: ToplevelKind::Xdg(resource.clone()),
token: data.shell_data.compositor_token,
} }
} }
fn toplevel_implementation<R>(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) fn toplevel_implementation(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
match request { match request {
xdg_toplevel::Request::Destroy => { xdg_toplevel::Request::Destroy => {
@ -546,12 +524,12 @@ where
} }
xdg_toplevel::Request::SetParent { parent } => { xdg_toplevel::Request::SetParent { parent } => {
// Parent is not double buffered, we can set it directly // 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| { data.parent = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent toplevel_surface_parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap() .unwrap()
.wl_surface .wl_surface
.clone() .clone()
@ -560,13 +538,13 @@ where
} }
xdg_toplevel::Request::SetTitle { title } => { xdg_toplevel::Request::SetTitle { title } => {
// Title is not double buffered, we can set it directly // 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); data.title = Some(title);
}); });
} }
xdg_toplevel::Request::SetAppId { app_id } => { xdg_toplevel::Request::SetAppId { app_id } => {
// AppId is not double buffered, we can set it directly // 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); role.app_id = Some(app_id);
}); });
} }
@ -606,13 +584,13 @@ where
}); });
} }
xdg_toplevel::Request::SetMaxSize { width, height } => { xdg_toplevel::Request::SetMaxSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.max_size = Some((width, height)); toplevel_data.max_size = (width, height);
}); });
} }
xdg_toplevel::Request::SetMinSize { width, height } => { xdg_toplevel::Request::SetMinSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.min_size = Some((width, height)); toplevel_data.min_size = (width, height);
}); });
} }
xdg_toplevel::Request::SetMaximized => { xdg_toplevel::Request::SetMaximized => {
@ -649,26 +627,14 @@ where
} }
} }
fn destroy_toplevel<R>(toplevel: xdg_toplevel::XdgToplevel) fn destroy_toplevel(toplevel: xdg_toplevel::XdgToplevel) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
if !data.wl_surface.as_ref().is_alive() { if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
// the wl_surface is destroyed, this means the client is not data.has_active_role.store(false, Ordering::Release);
// 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?!");
} }
// remove this surface from the known ones (as well as any leftover dead surface) // remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data data.shell_data
@ -683,14 +649,11 @@ where
* xdg_popup * xdg_popup
*/ */
pub(crate) fn send_popup_configure<R>(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) pub(crate) fn send_popup_configure(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) {
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
let serial = configure.serial; let serial = configure.serial;
@ -704,28 +667,20 @@ where
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
} }
fn make_popup_handle<R: 'static>(resource: &xdg_popup::XdgPopup) -> super::PopupSurface<R> { fn make_popup_handle(resource: &xdg_popup::XdgPopup) -> super::PopupSurface {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
super::PopupSurface { super::PopupSurface {
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
shell_surface: PopupKind::Xdg(resource.clone()), shell_surface: PopupKind::Xdg(resource.clone()),
token: data.shell_data.compositor_token,
} }
} }
fn xdg_popup_implementation<R>(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) fn xdg_popup_implementation(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) {
where let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
match request { match request {
xdg_popup::Request::Destroy => { xdg_popup::Request::Destroy => {
// all is handled by our destructor // all is handled by our destructor
@ -744,26 +699,10 @@ where
} }
} }
fn destroy_popup<R>(popup: xdg_popup::XdgPopup) fn destroy_popup(popup: xdg_popup::XdgPopup) {
where let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
R: Role<XdgPopupSurfaceRole> + 'static, if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
{ data.has_active_role.store(false, Ordering::Release);
let data = popup
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.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?!");
} }
// remove this surface from the known ones (as well as any leftover dead surface) // remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data data.shell_data

View File

@ -1,7 +1,8 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex};
use crate::wayland::compositor::{roles::*, CompositorToken}; use crate::wayland::compositor;
use crate::wayland::shell::xdg::{ConfigureError, PopupState}; use crate::wayland::shell::xdg::PopupState;
use crate::wayland::Serial; use crate::wayland::Serial;
use wayland_protocols::{ use wayland_protocols::{
unstable::xdg_shell::v6::server::{ unstable::xdg_shell::v6::server::{
@ -15,25 +16,25 @@ use crate::utils::Rectangle;
use super::{ use super::{
make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData, make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole, ShellData, SurfaceCachedState, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRoleAttributes,
XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRoleAttributes,
}; };
pub(crate) fn implement_shell<R>( static ZXDG_TOPLEVEL_ROLE: &str = "zxdg_toplevel";
static ZXDG_POPUP_ROLE: &str = "zxdg_toplevel";
pub(crate) fn implement_shell(
shell: Main<zxdg_shell_v6::ZxdgShellV6>, shell: Main<zxdg_shell_v6::ZxdgShellV6>,
shell_data: &ShellData<R>, shell_data: &ShellData,
) -> zxdg_shell_v6::ZxdgShellV6 ) -> zxdg_shell_v6::ZxdgShellV6 {
where shell.quick_assign(|shell, req, _data| shell_implementation(req, shell.deref().clone()));
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
shell.quick_assign(|shell, req, _data| shell_implementation::<R>(req, shell.deref().clone()));
shell.as_ref().user_data().set(|| ShellUserData { shell.as_ref().user_data().set(|| ShellUserData {
shell_data: shell_data.clone(), shell_data: shell_data.clone(),
client_data: Mutex::new(make_shell_client_data()), client_data: Mutex::new(make_shell_client_data()),
}); });
let mut user_impl = shell_data.user_impl.borrow_mut(); let mut user_impl = shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::NewClient { (&mut *user_impl)(XdgRequest::NewClient {
client: make_shell_client(&shell, shell_data.compositor_token), client: make_shell_client(&shell),
}); });
shell.deref().clone() shell.deref().clone()
} }
@ -42,26 +43,19 @@ where
* xdg_shell * xdg_shell
*/ */
pub(crate) struct ShellUserData<R> { pub(crate) struct ShellUserData {
shell_data: ShellData<R>, shell_data: ShellData,
pub(crate) client_data: Mutex<ShellClientData>, pub(crate) client_data: Mutex<ShellClientData>,
} }
pub(crate) fn make_shell_client<R>( pub(crate) fn make_shell_client(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient {
resource: &zxdg_shell_v6::ZxdgShellV6,
token: CompositorToken<R>,
) -> ShellClient<R> {
ShellClient { ShellClient {
kind: super::ShellClientKind::ZxdgV6(resource.clone()), kind: super::ShellClientKind::ZxdgV6(resource.clone()),
_token: token,
} }
} }
fn shell_implementation<R>(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6) fn shell_implementation(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6) {
where let data = shell.as_ref().user_data().get::<ShellUserData>().unwrap();
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request { match request {
zxdg_shell_v6::Request::Destroy => { zxdg_shell_v6::Request::Destroy => {
// all is handled by destructor // all is handled by destructor
@ -70,14 +64,13 @@ where
implement_positioner(id); implement_positioner(id);
} }
zxdg_shell_v6::Request::GetXdgSurface { id, surface } => { zxdg_shell_v6::Request::GetXdgSurface { id, surface } => {
id.quick_assign(|surface, req, _data| { id.quick_assign(|surface, req, _data| xdg_surface_implementation(req, surface.deref().clone()));
xdg_surface_implementation::<R>(req, surface.deref().clone()) id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface(surface)));
});
id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface::<R>(surface)));
id.as_ref().user_data().set(|| XdgSurfaceUserData { id.as_ref().user_data().set(|| XdgSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: surface, wl_surface: surface,
shell: shell.clone(), shell: shell.clone(),
has_active_role: AtomicBool::new(false),
}); });
} }
zxdg_shell_v6::Request::Pong { serial } => { zxdg_shell_v6::Request::Pong { serial } => {
@ -94,7 +87,7 @@ where
if valid { if valid {
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::ClientPong { (&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 * xdg_surface
*/ */
struct XdgSurfaceUserData<R> { struct XdgSurfaceUserData {
shell_data: ShellData<R>, shell_data: ShellData,
wl_surface: wl_surface::WlSurface, wl_surface: wl_surface::WlSurface,
shell: zxdg_shell_v6::ZxdgShellV6, shell: zxdg_shell_v6::ZxdgShellV6,
has_active_role: AtomicBool,
} }
fn destroy_surface<R>(surface: zxdg_surface_v6::ZxdgSurfaceV6) fn destroy_surface(surface: zxdg_surface_v6::ZxdgSurfaceV6) {
where let data = surface.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = surface
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
if !data.wl_surface.as_ref().is_alive() { if !data.wl_surface.as_ref().is_alive() {
// the wl_surface is destroyed, this means the client is not // the wl_surface is destroyed, this means the client is not
// trying to change the role but it's a cleanup (possibly a // trying to change the role but it's a cleanup (possibly a
@ -207,25 +194,12 @@ where
return; 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. // No role assigned to the surface, we can exit early.
return; return;
} }
let has_active_xdg_role = data if data.has_active_role.load(Ordering::Acquire) {
.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 {
data.shell.as_ref().post_error( data.shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32, zxdg_shell_v6::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(), "xdg_surface was destroyed before its role object".into(),
@ -233,16 +207,14 @@ where
} }
} }
fn xdg_surface_implementation<R>( fn xdg_surface_implementation(
request: zxdg_surface_v6::Request, request: zxdg_surface_v6::Request,
xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6, xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6,
) where ) {
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = xdg_surface let data = xdg_surface
.as_ref() .as_ref()
.user_data() .user_data()
.get::<XdgSurfaceUserData<R>>() .get::<XdgSurfaceUserData>()
.unwrap(); .unwrap();
match request { match request {
zxdg_surface_v6::Request::Destroy => { zxdg_surface_v6::Request::Destroy => {
@ -253,14 +225,7 @@ fn xdg_surface_implementation<R>(
let surface = &data.wl_surface; let surface = &data.wl_surface;
let shell = &data.shell; let shell = &data.shell;
let role_data = XdgToplevelSurfaceRole::Some(Default::default()); if compositor::give_role(surface, ZXDG_TOPLEVEL_ROLE).is_err() {
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error( shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32, zxdg_shell_v6::Error::Role as u32,
"Surface already has a role.".into(), "Surface already has a role.".into(),
@ -268,10 +233,19 @@ fn xdg_surface_implementation<R>(
return; return;
} }
id.quick_assign(|toplevel, req, _data| { data.has_active_role.store(true, Ordering::Release);
toplevel_implementation::<R>(req, toplevel.deref().clone())
}); compositor::with_states(surface, |states| {
id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel::<R>(toplevel))); 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 { id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
@ -304,11 +278,7 @@ fn xdg_surface_implementation<R>(
.clone(); .clone();
let parent_surface = { let parent_surface = {
let parent_data = parent let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
parent_data.wl_surface.clone() parent_data.wl_surface.clone()
}; };
@ -316,21 +286,15 @@ fn xdg_surface_implementation<R>(
let surface = &data.wl_surface; let surface = &data.wl_surface;
let shell = &data.shell; let shell = &data.shell;
let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes { let attributes = XdgPopupSurfaceRoleAttributes {
parent: Some(parent_surface), parent: Some(parent_surface),
server_pending: Some(PopupState { server_pending: Some(PopupState {
// Set the positioner data as the popup geometry // Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(), geometry: positioner_data.get_geometry(),
}), }),
..Default::default() ..Default::default()
}); };
if compositor::give_role(surface, ZXDG_POPUP_ROLE).is_err() {
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error( shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32, zxdg_shell_v6::Error::Role as u32,
"Surface already has a role.".into(), "Surface already has a role.".into(),
@ -338,8 +302,25 @@ fn xdg_surface_implementation<R>(
return; return;
} }
id.quick_assign(|popup, req, _data| popup_implementation::<R>(req, popup.deref().clone())); data.has_active_role.store(true, Ordering::Release);
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
compositor::with_states(surface, |states| {
states
.data_map
.insert_if_missing_threadsafe(|| Mutex::new(XdgPopupSurfaceRoleAttributes::default()));
*states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.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 { id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
@ -364,33 +345,28 @@ fn xdg_surface_implementation<R>(
// which is a protocol error. // which is a protocol error.
let surface = &data.wl_surface; let surface = &data.wl_surface;
if !data.shell_data.compositor_token.has_a_role(surface) { let role = compositor::get_role(surface);
data.shell.as_ref().post_error(
if role.is_none() {
xdg_surface.as_ref().post_error(
zxdg_surface_v6::Error::NotConstructed as u32, zxdg_surface_v6::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(), "xdg_surface must have a role.".into(),
); );
return; return;
} }
// Set the next window geometry here, the geometry will be moved from if role != Some(ZXDG_TOPLEVEL_ROLE) && role != Some(ZXDG_POPUP_ROLE) {
// 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 {
data.shell.as_ref().post_error( data.shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32, zxdg_shell_v6::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(), "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
); );
} }
compositor::with_states(surface, |states| {
states.cached_state.pending::<SurfaceCachedState>().geometry =
Some(Rectangle { x, y, width, height });
})
.unwrap();
} }
zxdg_surface_v6::Request::AckConfigure { serial } => { zxdg_surface_v6::Request::AckConfigure { serial } => {
let serial = Serial::from(serial); let serial = Serial::from(serial);
@ -399,7 +375,7 @@ fn xdg_surface_implementation<R>(
// Check the role of the surface, this can be either xdg_toplevel // 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 // or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error. // 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( data.shell.as_ref().post_error(
zxdg_surface_v6::Error::NotConstructed as u32, zxdg_surface_v6::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(), "xdg_surface must have a role.".into(),
@ -420,13 +396,31 @@ fn xdg_surface_implementation<R>(
// //
// This can be used to integrate custom protocol extensions // This can be used to integrate custom protocol extensions
// //
let configure = match data let found_configure = compositor::with_states(surface, |states| {
.shell_data if states.role == Some(ZXDG_TOPLEVEL_ROLE) {
.compositor_token Ok(states
.with_xdg_role(surface, |role| role.ack_configure(serial)) .data_map
{ .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
Ok(Ok(configure)) => configure, .unwrap()
Ok(Err(ConfigureError::SerialNotFound(serial))) => { .lock()
.unwrap()
.ack_configure(serial))
} else if states.role == Some(ZXDG_POPUP_ROLE) {
Ok(states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.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( data.shell.as_ref().post_error(
zxdg_shell_v6::Error::InvalidSurfaceState as u32, zxdg_shell_v6::Error::InvalidSurfaceState as u32,
format!("wrong configure serial: {}", <u32>::from(serial)), format!("wrong configure serial: {}", <u32>::from(serial)),
@ -456,64 +450,54 @@ fn xdg_surface_implementation<R>(
* xdg_toplevel * xdg_toplevel
*/ */
pub struct ShellSurfaceUserData<R> { pub struct ShellSurfaceUserData {
pub(crate) shell_data: ShellData<R>, pub(crate) shell_data: ShellData,
pub(crate) wl_surface: wl_surface::WlSurface, pub(crate) wl_surface: wl_surface::WlSurface,
pub(crate) shell: zxdg_shell_v6::ZxdgShellV6, pub(crate) shell: zxdg_shell_v6::ZxdgShellV6,
pub(crate) xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6, pub(crate) xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6,
} }
// Utility functions allowing to factor out a lot of the upcoming logic // Utility functions allowing to factor out a lot of the upcoming logic
fn with_surface_toplevel_role_data<R, F, T>( fn with_surface_toplevel_role_data<F, T>(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) -> T
shell_data: &ShellData<R>,
toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6,
f: F,
) -> T
where where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T, F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
shell_data compositor::with_states(&data.wl_surface, |states| {
.compositor_token f(&mut *states
.with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| { .data_map
let attributes = role .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.as_mut() .unwrap()
.expect("xdg_toplevel exists but role has been destroyed?!"); .lock()
f(attributes) .unwrap())
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!") .unwrap()
} }
fn with_surface_toplevel_client_pending<R, F, T>( fn with_toplevel_pending_state<F, T>(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) -> T
shell_data: &ShellData<R>,
toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6,
f: F,
) -> T
where where
R: Role<XdgToplevelSurfaceRole> + 'static, F: FnOnce(&mut SurfaceCachedState) -> T,
F: FnOnce(&mut ToplevelClientPending) -> T,
{ {
with_surface_toplevel_role_data(shell_data, toplevel, |data| { let data = toplevel
if data.client_pending.is_none() { .as_ref()
data.client_pending = Some(Default::default()); .user_data()
} .get::<ShellSurfaceUserData>()
f(&mut data.client_pending.as_mut().unwrap()) .unwrap();
compositor::with_states(&data.wl_surface, |states| {
f(&mut *states.cached_state.pending::<SurfaceCachedState>())
}) })
.unwrap()
} }
pub fn send_toplevel_configure<R>(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure) pub fn send_toplevel_configure(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
let (width, height) = configure.state.size.unwrap_or((0, 0)); let (width, height) = configure.state.size.unwrap_or((0, 0));
@ -536,41 +520,35 @@ where
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
} }
fn make_toplevel_handle<R: 'static>( fn make_toplevel_handle(resource: &zxdg_toplevel_v6::ZxdgToplevelV6) -> super::ToplevelSurface {
resource: &zxdg_toplevel_v6::ZxdgToplevelV6,
) -> super::ToplevelSurface<R> {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
super::ToplevelSurface { super::ToplevelSurface {
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
shell_surface: ToplevelKind::ZxdgV6(resource.clone()), shell_surface: ToplevelKind::ZxdgV6(resource.clone()),
token: data.shell_data.compositor_token,
} }
} }
fn toplevel_implementation<R>(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) fn toplevel_implementation(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
match request { match request {
zxdg_toplevel_v6::Request::Destroy => { zxdg_toplevel_v6::Request::Destroy => {
// all it done by the destructor // all it done by the destructor
} }
zxdg_toplevel_v6::Request::SetParent { parent } => { 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| { data.parent = parent.map(|toplevel_surface_parent| {
let parent_data = toplevel_surface_parent let parent_data = toplevel_surface_parent
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
parent_data.wl_surface.clone() parent_data.wl_surface.clone()
}) })
@ -578,13 +556,13 @@ where
} }
zxdg_toplevel_v6::Request::SetTitle { title } => { zxdg_toplevel_v6::Request::SetTitle { title } => {
// Title is not double buffered, we can set it directly // 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); data.title = Some(title);
}); });
} }
zxdg_toplevel_v6::Request::SetAppId { app_id } => { zxdg_toplevel_v6::Request::SetAppId { app_id } => {
// AppId is not double buffered, we can set it directly // 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); role.app_id = Some(app_id);
}); });
} }
@ -623,13 +601,13 @@ where
}); });
} }
zxdg_toplevel_v6::Request::SetMaxSize { width, height } => { zxdg_toplevel_v6::Request::SetMaxSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.max_size = Some((width, height)); toplevel_data.max_size = (width, height);
}); });
} }
zxdg_toplevel_v6::Request::SetMinSize { width, height } => { zxdg_toplevel_v6::Request::SetMinSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| { with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.min_size = Some((width, height)); toplevel_data.min_size = (width, height);
}); });
} }
zxdg_toplevel_v6::Request::SetMaximized => { zxdg_toplevel_v6::Request::SetMaximized => {
@ -666,26 +644,14 @@ where
} }
} }
fn destroy_toplevel<R>(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) fn destroy_toplevel(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) {
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
if !data.wl_surface.as_ref().is_alive() { if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
// the wl_surface is destroyed, this means the client is not data.has_active_role.store(false, Ordering::Release);
// 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?!");
} }
// remove this surface from the known ones (as well as any leftover dead surface) // remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data data.shell_data
@ -700,14 +666,11 @@ where
* xdg_popup * xdg_popup
*/ */
pub(crate) fn send_popup_configure<R>(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure) pub(crate) fn send_popup_configure(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure) {
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
let serial = configure.serial; let serial = configure.serial;
@ -721,28 +684,20 @@ where
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
} }
fn make_popup_handle<R: 'static>(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface<R> { fn make_popup_handle(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData>()
.unwrap(); .unwrap();
super::PopupSurface { super::PopupSurface {
wl_surface: data.wl_surface.clone(), wl_surface: data.wl_surface.clone(),
shell_surface: PopupKind::ZxdgV6(resource.clone()), shell_surface: PopupKind::ZxdgV6(resource.clone()),
token: data.shell_data.compositor_token,
} }
} }
fn popup_implementation<R>(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6) fn popup_implementation(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6) {
where let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
match request { match request {
zxdg_popup_v6::Request::Destroy => { zxdg_popup_v6::Request::Destroy => {
// all is handled by our destructor // all is handled by our destructor
@ -761,26 +716,10 @@ where
} }
} }
fn destroy_popup<R>(popup: zxdg_popup_v6::ZxdgPopupV6) fn destroy_popup(popup: zxdg_popup_v6::ZxdgPopupV6) {
where let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
R: Role<XdgPopupSurfaceRole> + 'static, if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
{ data.has_active_role.store(false, Ordering::Release);
let data = popup
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.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?!");
} }
// remove this surface from the known ones (as well as any leftover dead surface) // remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data data.shell_data