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" ]
[dependencies]
appendlist = "1.4"
bitflags = "1"
calloop = "0.8.0"
cgmath = "0.18.0"
dbus = { version = "0.9.0", optional = true }
libseat= { version = "0.1.1", optional = true }
downcast-rs = "1.2.0"
drm-fourcc = "^2.1.1"
drm = { version = "0.4.0", optional = true }
drm-ffi = { version = "0.1.0", optional = true }
@ -24,6 +25,7 @@ input = { version = "0.5", default-features = false, optional = true }
image = { version = "0.23.14", default-features = false, optional = true }
lazy_static = "1"
libc = "0.2.70"
libseat= { version = "0.1.1", optional = true }
libloading = "0.7.0"
nix = "0.20"
slog = "2"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 {
/// 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<'_>>;
/// Type of surfaces created

View File

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

View File

@ -11,7 +11,7 @@ use std::collections::HashSet;
use std::error::Error;
#[cfg(feature = "wayland_frontend")]
use crate::{utils::Rectangle, wayland::compositor::SurfaceAttributes};
use crate::{utils::Rectangle, wayland::compositor::SurfaceData};
use cgmath::{prelude::*, Matrix3, Vector2};
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm};
@ -264,7 +264,7 @@ pub trait ImportShm: Renderer {
fn import_shm_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
surface: Option<&crate::wayland::compositor::SurfaceData>,
damage: &[Rectangle],
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
@ -404,7 +404,7 @@ pub trait ImportAll: Renderer {
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
surface: Option<&crate::wayland::compositor::SurfaceData>,
damage: &[Rectangle],
) -> Option<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(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
surface: Option<&SurfaceData>,
damage: &[Rectangle],
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> {
match buffer_type(buffer) {
@ -439,7 +439,7 @@ impl<R: Renderer + ImportShm + ImportDma> ImportAll for R {
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
surface: Option<&SurfaceData>,
damage: &[Rectangle],
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> {
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::{
protocol::{wl_compositor, wl_region, wl_subcompositor, wl_subsurface, wl_surface},
@ -6,23 +14,22 @@ use wayland_server::{
};
use super::{
tree::{Location, SurfaceData},
AlreadyHasRole, BufferAssignment, CompositorToken, Damage, Rectangle, RectangleKind, RegionAttributes,
Role, RoleType, SubsurfaceRole, SurfaceEvent,
cache::Cacheable,
tree::{Location, PrivateSurfaceData},
AlreadyHasRole, BufferAssignment, Damage, Rectangle, RectangleKind, RegionAttributes, SurfaceAttributes,
};
/*
* wl_compositor
*/
pub(crate) fn implement_compositor<R, Impl>(
pub(crate) fn implement_compositor<Impl>(
compositor: Main<wl_compositor::WlCompositor>,
log: ::slog::Logger,
implem: Rc<RefCell<Impl>>,
) -> wl_compositor::WlCompositor
where
R: Default + Send + 'static,
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
{
compositor.quick_assign(move |_compositor, request, _| match request {
wl_compositor::Request::CreateSurface { id } => {
@ -42,29 +49,24 @@ where
* wl_surface
*/
type SurfaceImplemFn<R> =
dyn for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>);
type SurfaceImplemFn = dyn for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>);
// Internal implementation data of surfaces
pub(crate) struct SurfaceImplem<R> {
pub(crate) struct SurfaceImplem {
log: ::slog::Logger,
implem: Rc<RefCell<SurfaceImplemFn<R>>>,
implem: Rc<RefCell<SurfaceImplemFn>>,
}
impl<R> SurfaceImplem<R> {
fn make<Impl>(log: ::slog::Logger, implem: Rc<RefCell<Impl>>) -> SurfaceImplem<R>
impl SurfaceImplem {
fn make<Impl>(log: ::slog::Logger, implem: Rc<RefCell<Impl>>) -> SurfaceImplem
where
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>)
+ 'static,
Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
{
SurfaceImplem { log, implem }
}
}
impl<R> SurfaceImplem<R>
where
R: 'static,
{
impl SurfaceImplem {
fn receive_surface_request(
&mut self,
req: wl_surface::Request,
@ -73,8 +75,8 @@ where
) {
match req {
wl_surface::Request::Attach { buffer, x, y } => {
SurfaceData::<R>::with_data(&surface, |d| {
d.buffer = Some(match buffer {
PrivateSurfaceData::with_states(&surface, |states| {
states.cached_state.pending::<SurfaceAttributes>().buffer = Some(match buffer {
Some(buffer) => BufferAssignment::NewBuffer {
buffer,
delta: (x, y),
@ -84,13 +86,21 @@ where
});
}
wl_surface::Request::Damage { x, y, width, height } => {
SurfaceData::<R>::with_data(&surface, |d| {
d.damage.push(Damage::Surface(Rectangle { x, y, width, height }));
PrivateSurfaceData::with_states(&surface, |states| {
states
.cached_state
.pending::<SurfaceAttributes>()
.damage
.push(Damage::Surface(Rectangle { x, y, width, height }));
});
}
wl_surface::Request::Frame { callback } => {
SurfaceData::<R>::with_data(&surface, move |d| {
d.frame_callbacks.push((*callback).clone());
PrivateSurfaceData::with_states(&surface, |states| {
states
.cached_state
.pending::<SurfaceAttributes>()
.frame_callbacks
.push((*callback).clone());
});
}
wl_surface::Request::SetOpaqueRegion { region } => {
@ -98,29 +108,50 @@ where
let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap();
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 } => {
let attributes = region.map(|r| {
let attributes_mutex = r.as_ref().user_data().get::<Mutex<RegionAttributes>>().unwrap();
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 => {
let mut user_impl = self.implem.borrow_mut();
PrivateSurfaceData::invoke_commit_hooks(&surface);
if !surface.as_ref().is_alive() {
// the client was killed by a hook, abort
return;
}
PrivateSurfaceData::commit(&surface);
trace!(self.log, "Calling user implementation for wl_surface.commit");
(&mut *user_impl)(SurfaceEvent::Commit, surface, CompositorToken::make(), ddata);
(&mut *user_impl)(surface, ddata);
}
wl_surface::Request::SetBufferTransform { transform } => {
SurfaceData::<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 } => {
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 } => {
SurfaceData::<R>::with_data(&surface, |d| {
d.damage.push(Damage::Buffer(Rectangle { x, y, width, height }))
PrivateSurfaceData::with_states(&surface, |states| {
states
.cached_state
.pending::<SurfaceAttributes>()
.damage
.push(Damage::Buffer(Rectangle { x, y, width, height }))
});
}
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>,
log: ::slog::Logger,
implem: Rc<RefCell<Impl>>,
) -> wl_surface::WlSurface
where
R: Default + Send + 'static,
Impl: for<'a> FnMut(SurfaceEvent, wl_surface::WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(wl_surface::WlSurface, DispatchData<'a>) + 'static,
{
surface.quick_assign({
let mut implem = SurfaceImplem::make(log, implem);
move |surface, req, ddata| implem.receive_surface_request(req, surface.deref().clone(), ddata)
});
surface.assign_destructor(Filter::new(|surface, _, _| SurfaceData::<R>::cleanup(&surface)));
surface.as_ref().user_data().set_threadsafe(SurfaceData::<R>::new);
SurfaceData::<R>::init(&surface);
surface.assign_destructor(Filter::new(|surface, _, _| PrivateSurfaceData::cleanup(&surface)));
surface
.as_ref()
.user_data()
.set_threadsafe(PrivateSurfaceData::new);
PrivateSurfaceData::init(&surface);
surface.deref().clone()
}
@ -188,22 +250,19 @@ fn implement_region(region: Main<wl_region::WlRegion>) -> wl_region::WlRegion {
* wl_subcompositor
*/
pub(crate) fn implement_subcompositor<R>(
pub(crate) fn implement_subcompositor(
subcompositor: Main<wl_subcompositor::WlSubcompositor>,
) -> wl_subcompositor::WlSubcompositor
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{
) -> wl_subcompositor::WlSubcompositor {
subcompositor.quick_assign(move |subcompositor, request, _| match request {
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(
wl_subcompositor::Error::BadSurface as u32,
"Surface already has a role.".into(),
);
return;
}
implement_subsurface::<R>(id, surface);
implement_subsurface(id, surface);
}
wl_subcompositor::Request::Destroy => {}
_ => unreachable!(),
@ -215,32 +274,72 @@ where
* wl_subsurface
*/
fn with_subsurface_attributes<R, F>(subsurface: &wl_subsurface::WlSubsurface, f: F)
where
F: FnOnce(&mut SubsurfaceRole),
R: RoleType + Role<SubsurfaceRole> + 'static,
{
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?!");
/// The cached state associated with a subsurface
pub struct SubsurfaceCachedState {
/// Location of the top-left corner of this subsurface
/// relative to its parent coordinate space
pub location: (i32, i32),
}
fn implement_subsurface<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>,
surface: wl_surface::WlSurface,
) -> wl_subsurface::WlSubsurface
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{
subsurface.quick_assign(|subsurface, request, _| {
) -> wl_subsurface::WlSubsurface {
let data_surface = surface.clone();
subsurface.quick_assign(move |subsurface, request, _| {
match request {
wl_subsurface::Request::SetPosition { x, y } => {
with_subsurface_attributes::<R, _>(&subsurface, |attrs| {
attrs.location = (x, y);
PrivateSurfaceData::with_states(&surface, |state| {
state.cached_state.pending::<SubsurfaceCachedState>().location = (x, y);
})
}
wl_subsurface::Request::PlaceAbove { sibling } => {
@ -249,7 +348,7 @@ where
.user_data()
.get::<wl_surface::WlSurface>()
.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(
wl_subsurface::Error::BadSurface as u32,
"Provided surface is not a sibling or parent.".into(),
@ -262,18 +361,28 @@ where
.user_data()
.get::<wl_surface::WlSurface>()
.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(
wl_subsurface::Error::BadSurface as u32,
"Provided surface is not a sibling or parent.".into(),
)
}
}
wl_subsurface::Request::SetSync => with_subsurface_attributes::<R, _>(&subsurface, |attrs| {
attrs.sync = true;
wl_subsurface::Request::SetSync => PrivateSurfaceData::with_states(&surface, |state| {
state
.data_map
.get::<SubsurfaceState>()
.unwrap()
.sync
.store(true, Ordering::Release);
}),
wl_subsurface::Request::SetDesync => with_subsurface_attributes::<R, _>(&subsurface, |attrs| {
attrs.sync = false;
wl_subsurface::Request::SetDesync => PrivateSurfaceData::with_states(&surface, |state| {
state
.data_map
.get::<SubsurfaceState>()
.unwrap()
.sync
.store(false, Ordering::Release);
}),
wl_subsurface::Request::Destroy => {
// Our destructor already handles it
@ -281,23 +390,25 @@ where
_ => unreachable!(),
}
});
subsurface.assign_destructor(Filter::new(|subsurface, _, _| {
destroy_subsurface::<R>(&subsurface)
}));
subsurface.as_ref().user_data().set_threadsafe(|| surface);
super::with_states(&data_surface, |states| {
states.data_map.insert_if_missing_threadsafe(SubsurfaceState::new)
})
.unwrap();
subsurface.assign_destructor(Filter::new(|subsurface, _, _| destroy_subsurface(&subsurface)));
subsurface
.as_ref()
.user_data()
.set_threadsafe(move || data_surface);
subsurface.deref().clone()
}
fn destroy_subsurface<R>(subsurface: &wl_subsurface::WlSubsurface)
where
R: RoleType + Role<SubsurfaceRole> + 'static,
{
fn destroy_subsurface(subsurface: &wl_subsurface::WlSubsurface) {
let surface = subsurface
.as_ref()
.user_data()
.get::<wl_surface::WlSurface>()
.unwrap();
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
//! surface trees with subsurfaces, to provide you a direct access to the tree
//! structure and all surface metadata.
//! structure and all surface attributes, and handles the application of double-buffered
//! state.
//!
//! As such, you can, given a root surface with a role requiring it to be displayed,
//! you can iterate over the whole tree of subsurfaces to recover all the metadata you
@ -32,54 +33,67 @@
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::compositor::compositor_init;
//!
//! // Declare the roles enum
//! define_roles!(MyRoles);
//!
//! # let mut display = wayland_server::Display::new();
//! // Call the init function:
//! let (token, _, _) = compositor_init::<MyRoles, _, _>(
//! compositor_init(
//! &mut display,
//! |request, surface, compositor_token, dispatch_data| {
//! |surface, dispatch_data| {
//! /*
//! Your handling of the user requests.
//! Your handling of surface commits
//! */
//! },
//! None // put a logger here
//! );
//!
//! // this `token` is what you'll use to access the surface associated data
//!
//! // You're now ready to go!
//! ```
//!
//! ### Use the surface metadata
//! ### Use the surface states
//!
//! As you can see in the previous example, in the end we are retrieving a token from
//! the `init` function. This token is necessary to retrieve the metadata associated with
//! a surface. It can be cloned. See [`CompositorToken`]
//! for the details of what it enables you.
//! The main access to surface states is done through the [`with_states`] function, which
//! gives you access to the [`SurfaceData`] instance associated with this surface. It acts
//! as a general purpose container for associating state to a surface, double-buffered or
//! not. See its documentation for more details.
//!
//! The surface metadata is held in the [`SurfaceAttributes`]
//! struct. In contains double-buffered state pending from the client as defined by the protocol for
//! [`wl_surface`](wayland_server::protocol::wl_surface), as well as your user-defined type holding
//! any data you need to have associated with a struct. See its documentation for details.
//! ### State application and hooks
//!
//! This [`CompositorToken`] also provides access to the metadata associated with the role of the
//! surfaces. See the documentation of the [`roles`] submodule
//! for a detailed explanation.
//! On commit of a surface several steps are taken to update the state of the surface. Actions
//! are taken by smithay in the following order:
//!
//! 1. Commit hooks registered to this surface are invoked. Such hooks can be registered using
//! the [`add_commit_hook`] function. They are typically used by protocol extensions that
//! add state to a surface and need to check on commit that client did not request an
//! illegal state before it is applied on commit.
//! 2. The pending state is either applied and made current, or cached for later application
//! is the surface is a synchronize subsurface. If the current state is applied, state
//! of the synchronized children subsurface are applied as well at this point.
//! 3. Your user callback provided to [`compositor_init`] is invoked, so that you can access
//! the new current state of the surface. The state of sync children subsurfaces of your
//! surface may have changed as well, so this is the place to check it, using functions
//! like [`with_surface_tree_upward`] or [`with_surface_tree_downward`]. On the other hand,
//! if the surface is a sync subsurface, its current state will note have changed as
//! the result of that commit. You can check if it is using [`is_sync_subsurface`].
//!
//! ### Surface roles
//!
//! The wayland protocol specifies that a surface needs to be assigned a role before it can
//! be displayed. Furthermore, a surface can only have a single role during its whole lifetime.
//! Smithay represents this role as a `&'static str` identifier, that can only be set once
//! on a surface. See [`give_role`] and [`get_role`] for details. This module manages the
//! subsurface role, which is identified by the string `"subsurface"`.
use std::{cell::RefCell, fmt, rc::Rc, sync::Mutex};
use std::{cell::RefCell, rc::Rc, sync::Mutex};
mod cache;
mod handlers;
pub mod roles;
mod transaction;
mod tree;
pub use self::tree::TraversalAction;
use self::{
roles::{AlreadyHasRole, Role, RoleType, WrongRole},
tree::SurfaceData,
};
use crate::utils::Rectangle;
pub use self::cache::{Cacheable, MultiCache};
pub use self::handlers::SubsurfaceCachedState;
use self::tree::PrivateSurfaceData;
pub use self::tree::{AlreadyHasRole, TraversalAction};
use crate::utils::{DeadResource, Rectangle};
use wayland_server::{
protocol::{
wl_buffer, wl_callback, wl_compositor, wl_output, wl_region, wl_subcompositor, wl_surface::WlSurface,
@ -104,6 +118,32 @@ struct Marker<R> {
_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
#[derive(Debug)]
pub enum BufferAssignment {
@ -118,14 +158,12 @@ pub enum BufferAssignment {
},
}
/// Data associated with a surface, aggregated by the handlers
/// General state associated with a surface
///
/// Most of the fields of this struct represent a double-buffered state, which
/// should only be applied once a [`commit`](SurfaceEvent::Commit)
/// request is received from the surface.
///
/// You are responsible for setting those values as you see fit to avoid
/// processing them two times.
/// The fields `buffer`, `damage` and `frame_callbacks` should be
/// reset (by clearing their contents) once you have adequately
/// processed them, as their contents are aggregated from commit to commit.
#[derive(Debug)]
pub struct SurfaceAttributes {
/// Buffer defining the contents of the surface
///
@ -172,26 +210,6 @@ pub struct SurfaceAttributes {
/// An example possibility would be to trigger it once the frame
/// associated with this commit has been displayed on the screen.
pub frame_callbacks: Vec<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 {
@ -204,7 +222,6 @@ impl Default for SurfaceAttributes {
input_region: None,
damage: Vec::new(),
frame_callbacks: Vec::new(),
user_data: UserDataMap::new(),
}
}
}
@ -277,55 +294,6 @@ impl RegionAttributes {
}
}
/// A Compositor global token
///
/// This token can be cloned at will, and is the entry-point to
/// access data associated with the [`wl_surface`](wayland_server::protocol::wl_surface)
/// and [`wl_region`](wayland_server::protocol::wl_region) managed
/// by the `CompositorGlobal` that provided it.
#[derive(Debug)]
pub struct CompositorToken<R> {
_role: ::std::marker::PhantomData<*mut R>,
}
// we implement them manually because #[derive(..)] would require R: Clone
impl<R> Copy for CompositorToken<R> {}
impl<R> Clone for CompositorToken<R> {
fn clone(&self) -> CompositorToken<R> {
*self
}
}
unsafe impl<R> Send for CompositorToken<R> {}
unsafe impl<R> Sync for CompositorToken<R> {}
impl<R> CompositorToken<R> {
pub(crate) fn make() -> CompositorToken<R> {
CompositorToken {
_role: ::std::marker::PhantomData,
}
}
}
impl<R: 'static> CompositorToken<R> {
/// Access the data of a surface
///
/// The closure will be called with the contents of the data associated with this surface.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn with_surface_data<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
///
/// You provide three closures, a "filter", a "processor" and a "post filter".
@ -353,168 +321,133 @@ where
/// 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,
F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &SurfaceData, &T),
F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
{
SurfaceData::<R>::map_tree(surface, &initial, filter, processor, post_filter, false);
PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, false);
}
/// Access the data of a surface tree from top to bottom
///
/// Behavior is the same as [`with_surface_tree_upward`](CompositorToken::with_surface_tree_upward), but
/// the processing is done in the reverse order, from the nearest of the screen to the deepest.
/// Behavior is the same as [`with_surface_tree_upward`], but the processing is done in the reverse order,
/// from the nearest of the screen to the deepest.
///
/// This would typically be used to find out which surface of a subsurface tree has been clicked for example.
pub fn with_surface_tree_downward<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,
F1: FnMut(&WlSurface, &SurfaceData, &T) -> TraversalAction<T>,
F2: FnMut(&WlSurface, &SurfaceData, &T),
F3: FnMut(&WlSurface, &SurfaceData, &T) -> bool,
{
SurfaceData::<R>::map_tree(surface, &initial, filter, processor, post_filter, true);
PrivateSurfaceData::map_tree(surface, &initial, filter, processor, post_filter, true);
}
/// Retrieve the parent of this surface
///
/// Returns `None` is this surface is a root surface
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn get_parent(self, surface: &WlSurface) -> Option<WlSurface> {
SurfaceData::<R>::get_parent(surface)
pub fn get_parent(surface: &WlSurface) -> Option<WlSurface> {
if !surface.as_ref().is_alive() {
return None;
}
PrivateSurfaceData::get_parent(surface)
}
/// Retrieve the children of this surface
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn get_children(self, surface: &WlSurface) -> Vec<WlSurface> {
SurfaceData::<R>::get_children(surface)
pub fn get_children(surface: &WlSurface) -> Vec<WlSurface> {
if !surface.as_ref().is_alive() {
return Vec::new();
}
PrivateSurfaceData::get_children(surface)
}
impl<R: RoleType + 'static> CompositorToken<R> {
/// Check whether this surface as a role or not
/// Check if this subsurface is a synchronized subsurface
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn has_a_role(self, surface: &WlSurface) -> bool {
SurfaceData::<R>::has_a_role(surface)
/// Returns false if the surface is already dead
pub fn is_sync_subsurface(surface: &WlSurface) -> bool {
if !surface.as_ref().is_alive() {
return false;
}
self::handlers::is_effectively_sync(surface)
}
/// Check whether this surface as a specific role
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn has_role<RoleData>(self, surface: &WlSurface) -> bool
where
R: Role<RoleData>,
{
SurfaceData::<R>::has_role::<RoleData>(surface)
/// Get the current role of this surface
pub fn get_role(surface: &WlSurface) -> Option<&'static str> {
if !surface.as_ref().is_alive() {
return None;
}
PrivateSurfaceData::get_role(surface)
}
/// Register that this surface has given role with default data
/// Register that this surface has given role
///
/// Fails if the surface already has a role.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn give_role<RoleData>(self, surface: &WlSurface) -> Result<(), AlreadyHasRole>
where
R: Role<RoleData>,
RoleData: Default,
{
SurfaceData::<R>::give_role::<RoleData>(surface)
pub fn give_role(surface: &WlSurface, role: &'static str) -> Result<(), AlreadyHasRole> {
if !surface.as_ref().is_alive() {
return Ok(());
}
PrivateSurfaceData::set_role(surface, role)
}
/// Register that this surface has given role with given data
///
/// Fails if the surface already has a role and returns the data.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn give_role_with<RoleData>(self, surface: &WlSurface, data: RoleData) -> Result<(), RoleData>
/// Access the states associated to this surface
pub fn with_states<F, T>(surface: &WlSurface, f: F) -> Result<T, DeadResource>
where
R: Role<RoleData>,
F: FnOnce(&SurfaceData) -> T,
{
SurfaceData::<R>::give_role_with::<RoleData>(surface, data)
if !surface.as_ref().is_alive() {
return Err(DeadResource);
}
/// Access the role data of a surface
///
/// Fails and don't call the closure if the surface doesn't have this role
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn with_role_data<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)
}
/// Register that this surface does not have a role any longer and retrieve the data
///
/// Fails if the surface didn't already have this role.
///
/// If the surface is not managed by the `CompositorGlobal` that provided this token, this
/// will panic (having more than one compositor is not supported).
pub fn remove_role<RoleData>(self, surface: &WlSurface) -> Result<RoleData, WrongRole>
where
R: Role<RoleData>,
{
SurfaceData::<R>::remove_role::<RoleData>(surface)
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(self, region: &wl_region::WlRegion) -> RegionAttributes {
pub fn get_region_attributes(region: &wl_region::WlRegion) -> RegionAttributes {
match region.as_ref().user_data().get::<Mutex<RegionAttributes>>() {
Some(mutex) => mutex.lock().unwrap().clone(),
None => panic!("Accessing the data of foreign regions is not supported."),
}
}
/// Register a commit hook to be invoked on surface commit
///
/// For its precise semantics, see module-level documentation.
pub fn add_commit_hook(surface: &WlSurface, hook: fn(&WlSurface)) {
if !surface.as_ref().is_alive() {
return;
}
PrivateSurfaceData::add_commit_hook(surface, hook)
}
/// Create new [`wl_compositor`](wayland_server::protocol::wl_compositor)
/// and [`wl_subcompositor`](wayland_server::protocol::wl_subcompositor) globals.
///
/// The globals are directly registered into the event loop, and this function
/// returns a [`CompositorToken`] which you'll need access the data associated to
/// the [`wl_surface`](wayland_server::protocol::wl_surface)s.
///
/// It also returns the two global handles, in case you wish to remove these
/// globals from the event loop in the future.
pub fn compositor_init<R, Impl, L>(
/// It returns the two global handles, in case you wish to remove these globals from
/// the event loop in the future.
pub fn compositor_init<Impl, L>(
display: &mut Display,
implem: Impl,
logger: L,
) -> (
CompositorToken<R>,
Global<wl_compositor::WlCompositor>,
Global<wl_subcompositor::WlSubcompositor>,
)
where
L: Into<Option<::slog::Logger>>,
R: Default + RoleType + Role<SubsurfaceRole> + Send + 'static,
Impl: for<'a> FnMut(SurfaceEvent, WlSurface, CompositorToken<R>, DispatchData<'a>) + 'static,
Impl: for<'a> FnMut(WlSurface, DispatchData<'a>) + 'static,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "compositor_handler"));
let implem = Rc::new(RefCell::new(implem));
@ -522,32 +455,18 @@ where
let compositor = display.create_global(
4,
Filter::new(move |(new_compositor, _version), _, _| {
self::handlers::implement_compositor::<R, Impl>(new_compositor, log.clone(), implem.clone());
self::handlers::implement_compositor::<Impl>(new_compositor, log.clone(), implem.clone());
}),
);
let subcompositor = display.create_global(
1,
Filter::new(move |(new_subcompositor, _version), _, _| {
self::handlers::implement_subcompositor::<R>(new_subcompositor);
self::handlers::implement_subcompositor(new_subcompositor);
}),
);
(CompositorToken::make(), compositor, subcompositor)
}
/// User-handled events for surfaces
///
/// The global provided by smithay cannot process these events for you, so
/// they are forwarded directly via your provided implementation, and are
/// described by this global.
#[derive(Debug)]
pub enum SurfaceEvent {
/// The double-buffered state has been validated by the client
///
/// At this point, the pending state that has been accumulated in the [`SurfaceAttributes`] associated
/// to this surface should be atomically integrated into the current state of the surface.
Commit,
(compositor, subcompositor)
}
#[cfg(test)]

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

View File

@ -6,14 +6,13 @@ use wayland_server::{
};
use crate::wayland::{
compositor::{roles::Role, CompositorToken},
seat::{AxisFrame, GrabStartData, PointerGrab, PointerInnerHandle, Seat},
Serial,
};
use super::{with_source_metadata, DataDeviceData, DnDIconRole, SeatData};
use super::{with_source_metadata, DataDeviceData, SeatData};
pub(crate) struct DnDGrab<R> {
pub(crate) struct DnDGrab {
start_data: GrabStartData,
data_source: Option<wl_data_source::WlDataSource>,
current_focus: Option<wl_surface::WlSurface>,
@ -22,20 +21,18 @@ pub(crate) struct DnDGrab<R> {
icon: Option<wl_surface::WlSurface>,
origin: wl_surface::WlSurface,
callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>,
token: CompositorToken<R>,
seat: Seat,
}
impl<R: Role<DnDIconRole> + 'static> DnDGrab<R> {
impl DnDGrab {
pub(crate) fn new(
start_data: GrabStartData,
source: Option<wl_data_source::WlDataSource>,
origin: wl_surface::WlSurface,
seat: Seat,
icon: Option<wl_surface::WlSurface>,
token: CompositorToken<R>,
callback: Rc<RefCell<dyn FnMut(super::DataDeviceEvent)>>,
) -> DnDGrab<R> {
) -> DnDGrab {
DnDGrab {
start_data,
data_source: source,
@ -45,13 +42,12 @@ impl<R: Role<DnDIconRole> + 'static> DnDGrab<R> {
origin,
icon,
callback,
token,
seat,
}
}
}
impl<R: Role<DnDIconRole> + 'static> PointerGrab for DnDGrab<R> {
impl PointerGrab for DnDGrab {
fn motion(
&mut self,
_handle: &mut PointerInnerHandle<'_>,
@ -211,11 +207,7 @@ impl<R: Role<DnDIconRole> + 'static> PointerGrab for DnDGrab<R> {
}
}
(&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped);
if let Some(icon) = self.icon.take() {
if icon.as_ref().is_alive() {
self.token.remove_role::<super::DnDIconRole>(&icon).unwrap();
}
}
self.icon = None;
// in all cases abandon the drop
// no more buttons are pressed, release the grab
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
//! itself and receive interactions of clients with it via an other dedicated callback.
//!
//! The module also defines the `DnDIconRole` that you need to insert into your compositor roles enum, to
//! represent surfaces that are used as a DnD icon.
//! The module defines the role `"dnd_icon"` that is assigned to surfaces used as drag'n'drop icons.
//!
//! ## Initialization
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::data_device::{init_data_device, default_action_chooser, DnDIconRole};
//! # use smithay::wayland::compositor::compositor_init;
//!
//! // You need to insert the `DndIconRole` into your roles, to handle requests from clients
//! // to set a surface as a dnd icon
//! define_roles!(Roles => [DnDIcon, DnDIconRole]);
//!
//! # let mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = compositor_init::<Roles, _, _>(&mut display, |_, _, _, _| {}, None);
//! // init the data device:
//! init_data_device(
//! &mut display, // the display
//! |dnd_event| { /* a callback to react to client DnD/selection actions */ },
//! default_action_chooser, // a closure to choose the DnD action depending on clients
//! // negociation
//! compositor_token.clone(), // a compositor token
//! None // insert a logger here
//! );
//! ```
@ -65,7 +57,7 @@ use wayland_server::{
};
use crate::wayland::{
compositor::{roles::Role, CompositorToken},
compositor,
seat::{GrabStartData, Seat},
Serial,
};
@ -77,6 +69,8 @@ mod server_dnd_grab;
pub use self::data_source::{with_source_metadata, SourceMetadata};
pub use self::server_dnd_grab::ServerDndEvent;
static DND_ICON_ROLE: &str = "dnd_icon";
/// Events that are generated by interactions of the clients with the data device
#[derive(Debug)]
pub enum DataDeviceEvent {
@ -275,17 +269,15 @@ impl SeatData {
/// and the second argument is the preferred action reported by the target. If no action should be
/// chosen (and thus the drag'n'drop should abort on drop), return
/// [`DndAction::empty()`](wayland_server::protocol::wl_data_device_manager::DndAction::empty).
pub fn init_data_device<F, C, R, L>(
pub fn init_data_device<F, C, L>(
display: &mut Display,
callback: C,
action_choice: F,
token: CompositorToken<R>,
logger: L,
) -> Global<wl_data_device_manager::WlDataDeviceManager>
where
F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
L: Into<Option<::slog::Logger>>,
{
let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "data_device_mgr"));
@ -294,7 +286,7 @@ where
display.create_global(
3,
Filter::new(move |(ddm, _version), _, _| {
implement_ddm(ddm, callback.clone(), action_choice.clone(), token, log.clone());
implement_ddm(ddm, callback.clone(), action_choice.clone(), log.clone());
}),
)
}
@ -371,17 +363,15 @@ pub fn start_dnd<C>(
}
}
fn implement_ddm<F, C, R>(
fn implement_ddm<F, C>(
ddm: Main<wl_data_device_manager::WlDataDeviceManager>,
callback: Rc<RefCell<C>>,
action_choice: Rc<RefCell<F>>,
token: CompositorToken<R>,
log: ::slog::Logger,
) -> wl_data_device_manager::WlDataDeviceManager
where
F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
{
use self::wl_data_device_manager::Request;
ddm.quick_assign(move |_ddm, req, _data| match req {
@ -399,7 +389,6 @@ where
seat.clone(),
callback.clone(),
action_choice.clone(),
token,
log.clone(),
);
seat_data.borrow_mut().known_devices.push(data_device);
@ -419,18 +408,16 @@ struct DataDeviceData {
action_choice: Rc<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>,
seat: Seat,
callback: Rc<RefCell<C>>,
action_choice: Rc<RefCell<F>>,
token: CompositorToken<R>,
log: ::slog::Logger,
) -> wl_data_device::WlDataDevice
where
F: FnMut(DndAction, DndAction) -> DndAction + 'static,
C: FnMut(DataDeviceEvent) + 'static,
R: Role<DnDIconRole> + 'static,
{
use self::wl_data_device::Request;
let dd_data = DataDeviceData {
@ -449,7 +436,7 @@ where
if let Some(pointer) = seat.get_pointer() {
if pointer.has_grab(serial) {
if let Some(ref icon) = icon {
if token.give_role::<DnDIconRole>(icon).is_err() {
if compositor::give_role(icon, DND_ICON_ROLE).is_err() {
dd.as_ref().post_error(
wl_data_device::Error::Role as u32,
"Given surface already has an other role".into(),
@ -470,7 +457,6 @@ where
origin,
seat.clone(),
icon,
token,
callback.clone(),
),
serial,

View File

@ -265,7 +265,7 @@ where
format,
DmabufFlags::from_bits_truncate(flags),
);
let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new());
let planes = std::mem::take(&mut self.pending_planes);
for (i, plane) in planes.into_iter().enumerate() {
let offset = plane.offset;
let stride = plane.stride;
@ -355,7 +355,7 @@ where
format,
DmabufFlags::from_bits_truncate(flags),
);
let planes = ::std::mem::replace(&mut self.pending_planes, Vec::new());
let planes = ::std::mem::take(&mut self.pending_planes);
for (i, plane) in planes.into_iter().enumerate() {
let offset = plane.offset;
let stride = plane.stride;

View File

@ -21,60 +21,32 @@
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! #
//! # use smithay::wayland::compositor::roles::*;
//! # use smithay::wayland::compositor::CompositorToken;
//! use smithay::wayland::explicit_synchronization::*;
//! # define_roles!(MyRoles);
//! #
//! # let mut display = wayland_server::Display::new();
//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<MyRoles, _, _>(
//! # &mut display,
//! # |_, _, _, _| {},
//! # None
//! # );
//! init_explicit_synchronization_global(
//! &mut display,
//! compositor_token,
//! None /* You can insert a logger here */
//! );
//! ```
//!
//! Then when handling a surface commit, you can retrieve the synchronization information for the surface
//! data:
//! ```no_run
//! Then when handling a surface commit, you can retrieve the synchronization information for the surface states:
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! #
//! # use wayland_server::protocol::wl_surface::WlSurface;
//! # use smithay::wayland::compositor::CompositorToken;
//! # use smithay::wayland::explicit_synchronization::*;
//! #
//! # fn dummy_function<R: 'static>(surface: &WlSurface, compositor_token: CompositorToken<R>) {
//! compositor_token.with_surface_data(&surface, |surface_attributes| {
//! // While you retrieve the surface data from the commit ...
//! // Check the explicit synchronization data:
//! match get_explicit_synchronization_state(surface_attributes) {
//! Ok(sync_state) => {
//! /* This surface is explicitly synchronized, you need to handle
//! the contents of sync_state
//! */
//! },
//! Err(NoExplicitSync) => {
//! /* This surface is not explicitly synchronized, nothing more to do
//! */
//! }
//! }
//! # fn dummy_function<R: 'static>(surface: &WlSurface) {
//! use smithay::wayland::compositor::with_states;
//! with_states(&surface, |states| {
//! let explicit_sync_state = states.cached_state.current::<ExplicitSyncState>();
//! /* process the explicit_sync_state */
//! });
//! # }
//! ```
use std::{
cell::RefCell,
ops::{Deref as _, DerefMut as _},
os::unix::io::RawFd,
};
use std::{cell::RefCell, ops::Deref as _, os::unix::io::RawFd};
use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{
zwp_linux_buffer_release_v1::ZwpLinuxBufferReleaseV1,
@ -83,7 +55,7 @@ use wayland_protocols::unstable::linux_explicit_synchronization::v1::server::{
};
use wayland_server::{protocol::wl_surface::WlSurface, Display, Filter, Global, Main};
use crate::wayland::compositor::{CompositorToken, SurfaceAttributes};
use super::compositor::{with_states, Cacheable, SurfaceData};
/// An object to signal end of use of a buffer
#[derive(Debug)]
@ -112,6 +84,9 @@ impl ExplicitBufferRelease {
/// The client is not required to fill both. `acquire` being `None` means that you don't need to wait
/// before acessing the buffer, `release` being `None` means that the client does not require additionnal
/// signaling that you are finished (you still need to send `wl_buffer.release`).
///
/// When processing the current state, the whould [`Option::take`] the values from it. Otherwise they'll
/// be treated as unused and released when overwritten by the next client commit.
#[derive(Debug)]
pub struct ExplicitSyncState {
/// An acquire `dma_fence` object, that you should wait on before accessing the contents of the
@ -122,26 +97,37 @@ pub struct ExplicitSyncState {
pub release: Option<ExplicitBufferRelease>,
}
struct InternalState {
sync_state: ExplicitSyncState,
sync_resource: ZwpLinuxSurfaceSynchronizationV1,
impl Default for ExplicitSyncState {
fn default() -> Self {
ExplicitSyncState {
acquire: None,
release: None,
}
}
}
impl Cacheable for ExplicitSyncState {
fn commit(&mut self) -> Self {
std::mem::take(self)
}
fn merge_into(mut self, into: &mut Self) {
if self.acquire.is_some() {
if let Some(fd) = std::mem::replace(&mut into.acquire, self.acquire.take()) {
// close the unused fd
let _ = nix::unistd::close(fd);
}
}
if self.release.is_some() {
if let Some(release) = std::mem::replace(&mut into.release, self.release.take()) {
// release the overriden state
release.immediate_release();
}
}
}
}
struct ESUserData {
state: RefCell<Option<InternalState>>,
}
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
}
}
state: RefCell<Option<ZwpLinuxSurfaceSynchronizationV1>>,
}
/// Possible errors you can send to an ill-behaving clients
@ -167,42 +153,24 @@ impl std::fmt::Display for NoExplicitSync {
impl std::error::Error for NoExplicitSync {}
/// Retrieve the explicit synchronization state commited by the client
///
/// This state can contain an acquire fence and a release object, for synchronization (see module-level docs).
///
/// This function will clear the pending state, preparing the surface for the next commit, as a result you
/// should always call it on surface commit to avoid getting out-of-sync with the client.
///
/// This function returns an error if the client has not setup explicit synchronization for this surface.
pub fn get_explicit_synchronization_state(
attrs: &mut SurfaceAttributes,
) -> Result<ExplicitSyncState, NoExplicitSync> {
attrs
.user_data
.get::<ESUserData>()
.and_then(|s| s.take_state())
.ok_or(NoExplicitSync)
}
/// Send a synchronization error to a client
///
/// See the enum definition for possible errors. These errors are protocol errors, meaning that
/// the client associated with this `SurfaceAttributes` will be killed as a result of calling this
/// function.
pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: ExplicitSyncError) {
if let Some(ref data) = attrs.user_data.get::<ESUserData>() {
if let Some(state) = data.state.borrow().deref() {
pub fn send_explicit_synchronization_error(attrs: &SurfaceData, error: ExplicitSyncError) {
if let Some(ref data) = attrs.data_map.get::<ESUserData>() {
if let Some(sync_resource) = data.state.borrow().deref() {
match error {
ExplicitSyncError::InvalidFence => state.sync_resource.as_ref().post_error(
ExplicitSyncError::InvalidFence => sync_resource.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::InvalidFence as u32,
"The fence specified by the client could not be imported.".into(),
),
ExplicitSyncError::UnsupportedBuffer => state.sync_resource.as_ref().post_error(
ExplicitSyncError::UnsupportedBuffer => sync_resource.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::UnsupportedBuffer as u32,
"The buffer does not support explicit synchronization.".into(),
),
ExplicitSyncError::NoBuffer => state.sync_resource.as_ref().post_error(
ExplicitSyncError::NoBuffer => sync_resource.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::NoBuffer as u32,
"No buffer was attached.".into(),
),
@ -214,14 +182,12 @@ pub fn send_explicit_synchronization_error(attrs: &SurfaceAttributes, error: Exp
/// Initialize the explicit synchronization global
///
/// See module-level documentation for its use.
pub fn init_explicit_synchronization_global<R, L>(
pub fn init_explicit_synchronization_global<L>(
display: &mut Display,
compositor: CompositorToken<R>,
logger: L,
) -> Global<ZwpLinuxExplicitSynchronizationV1>
where
L: Into<Option<::slog::Logger>>,
R: 'static,
{
let _log =
crate::slog_or_fallback(logger).new(o!("smithay_module" => "wayland_explicit_synchronization"));
@ -236,16 +202,17 @@ where
surface,
} = req
{
let exists = compositor.with_surface_data(&surface, |attrs| {
attrs.user_data.insert_if_missing(|| ESUserData {
let exists = with_states(&surface, |states| {
states.data_map.insert_if_missing(|| ESUserData {
state: RefCell::new(None),
});
attrs
.user_data
states
.data_map
.get::<ESUserData>()
.map(|ud| ud.state.borrow().is_some())
.unwrap()
});
})
.unwrap_or(false);
if exists {
explicit_sync.as_ref().post_error(
zwp_linux_explicit_synchronization_v1::Error::SynchronizationExists as u32,
@ -253,17 +220,12 @@ where
);
return;
}
let surface_sync = implement_surface_sync(id, surface.clone(), compositor);
compositor.with_surface_data(&surface, |attrs| {
let data = attrs.user_data.get::<ESUserData>().unwrap();
*data.state.borrow_mut() = Some(InternalState {
sync_state: ExplicitSyncState {
acquire: None,
release: None,
},
sync_resource: surface_sync,
});
});
let surface_sync = implement_surface_sync(id, surface.clone());
with_states(&surface, |states| {
let data = states.data_map.get::<ESUserData>().unwrap();
*data.state.borrow_mut() = Some(surface_sync);
})
.unwrap();
}
});
},
@ -271,14 +233,10 @@ where
)
}
fn implement_surface_sync<R>(
fn implement_surface_sync(
id: Main<ZwpLinuxSurfaceSynchronizationV1>,
surface: WlSurface,
compositor: CompositorToken<R>,
) -> ZwpLinuxSurfaceSynchronizationV1
where
R: 'static,
{
) -> ZwpLinuxSurfaceSynchronizationV1 {
id.quick_assign(move |surface_sync, req, _| match req {
zwp_linux_surface_synchronization_v1::Request::SetAcquireFence { fd } => {
if !surface.as_ref().is_alive() {
@ -287,19 +245,18 @@ where
"The associated wl_surface was destroyed.".into(),
)
}
compositor.with_surface_data(&surface, |attrs| {
let data = attrs.user_data.get::<ESUserData>().unwrap();
if let Some(state) = data.state.borrow_mut().deref_mut() {
if state.sync_state.acquire.is_some() {
with_states(&surface, |states| {
let mut pending = states.cached_state.pending::<ExplicitSyncState>();
if pending.acquire.is_some() {
surface_sync.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::DuplicateFence as u32,
"Multiple fences added for a single surface commit.".into(),
)
} else {
state.sync_state.acquire = Some(fd);
pending.acquire = Some(fd);
}
}
});
})
.unwrap();
}
zwp_linux_surface_synchronization_v1::Request::GetRelease { release } => {
if !surface.as_ref().is_alive() {
@ -308,30 +265,30 @@ where
"The associated wl_surface was destroyed.".into(),
)
}
compositor.with_surface_data(&surface, |attrs| {
let data = attrs.user_data.get::<ESUserData>().unwrap();
if let Some(state) = data.state.borrow_mut().deref_mut() {
if state.sync_state.acquire.is_some() {
with_states(&surface, |states| {
let mut pending = states.cached_state.pending::<ExplicitSyncState>();
if pending.release.is_some() {
surface_sync.as_ref().post_error(
zwp_linux_surface_synchronization_v1::Error::DuplicateRelease as u32,
"Multiple releases added for a single surface commit.".into(),
)
} else {
release.quick_assign(|_, _, _| {});
state.sync_state.release = Some(ExplicitBufferRelease {
pending.release = Some(ExplicitBufferRelease {
release: release.deref().clone(),
});
}
}
});
})
.unwrap();
}
zwp_linux_surface_synchronization_v1::Request::Destroy => {
// disable the ESUserData
compositor.with_surface_data(&surface, |attrs| {
if let Some(ref mut data) = attrs.user_data.get::<ESUserData>() {
with_states(&surface, |states| {
if let Some(ref mut data) = states.data_map.get::<ESUserData>() {
*data.state.borrow_mut() = None;
}
});
})
.unwrap();
}
_ => (),
});

View File

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

View File

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

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

View File

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

View File

@ -10,116 +10,116 @@ use wayland_server::{
Filter, Main,
};
use crate::wayland::compositor::{roles::Role, CompositorToken};
static WL_SHELL_SURFACE_ROLE: &str = "wl_shell_surface";
use crate::wayland::compositor;
use crate::wayland::Serial;
use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceKind, ShellSurfaceRole};
use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceAttributes, ShellSurfaceKind};
pub(crate) fn implement_shell<R, Impl>(
pub(crate) fn implement_shell<Impl>(
shell: Main<wl_shell::WlShell>,
ctoken: CompositorToken<R>,
implementation: Rc<RefCell<Impl>>,
state: Arc<Mutex<ShellState<R>>>,
state: Arc<Mutex<ShellState>>,
) where
R: Role<ShellSurfaceRole> + 'static,
Impl: FnMut(ShellRequest<R>) + 'static,
Impl: FnMut(ShellRequest) + 'static,
{
shell.quick_assign(move |shell, req, _data| {
let (id, surface) = match req {
wl_shell::Request::GetShellSurface { id, surface } => (id, surface),
_ => unreachable!(),
};
let role_data = ShellSurfaceRole {
title: "".into(),
class: "".into(),
pending_ping: None,
};
if ctoken.give_role_with(&surface, role_data).is_err() {
if compositor::give_role(&surface, WL_SHELL_SURFACE_ROLE).is_err() {
shell
.as_ref()
.post_error(wl_shell::Error::Role as u32, "Surface already has a role.".into());
return;
}
let shell_surface =
implement_shell_surface(id, surface, implementation.clone(), ctoken, state.clone());
compositor::with_states(&surface, |states| {
states.data_map.insert_if_missing(|| {
Mutex::new(ShellSurfaceAttributes {
title: "".into(),
class: "".into(),
pending_ping: None,
})
})
})
.unwrap();
let shell_surface = implement_shell_surface(id, surface, implementation.clone(), state.clone());
state
.lock()
.unwrap()
.known_surfaces
.push(make_handle(&shell_surface, ctoken));
.push(make_handle(&shell_surface));
let mut imp = implementation.borrow_mut();
(&mut *imp)(ShellRequest::NewShellSurface {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
});
});
}
fn make_handle<R>(
shell_surface: &wl_shell_surface::WlShellSurface,
token: CompositorToken<R>,
) -> ShellSurface<R>
where
R: Role<ShellSurfaceRole> + 'static,
{
fn make_handle(shell_surface: &wl_shell_surface::WlShellSurface) -> ShellSurface {
let data = shell_surface
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
ShellSurface {
wl_surface: data.surface.clone(),
shell_surface: shell_surface.clone(),
token,
}
}
pub(crate) struct ShellSurfaceUserData<R> {
pub(crate) struct ShellSurfaceUserData {
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>,
surface: wl_surface::WlSurface,
implementation: Rc<RefCell<Impl>>,
ctoken: CompositorToken<R>,
state: Arc<Mutex<ShellState<R>>>,
state: Arc<Mutex<ShellState>>,
) -> wl_shell_surface::WlShellSurface
where
R: Role<ShellSurfaceRole> + 'static,
Impl: FnMut(ShellRequest<R>) + 'static,
Impl: FnMut(ShellRequest) + 'static,
{
use self::wl_shell_surface::Request;
shell_surface.quick_assign(move |shell_surface, req, _data| {
let data = shell_surface
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
let mut user_impl = implementation.borrow_mut();
match req {
Request::Pong { serial } => {
let serial = Serial::from(serial);
let valid = ctoken
.with_role_data(&data.surface, |data| {
if data.pending_ping == Some(serial) {
data.pending_ping = None;
let valid = compositor::with_states(&data.surface, |states| {
let mut guard = states
.data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
if guard.pending_ping == Some(serial) {
guard.pending_ping = None;
true
} else {
false
}
})
.expect("wl_shell_surface exists but surface has not the right role?");
.unwrap();
if valid {
(&mut *user_impl)(ShellRequest::Pong {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
});
}
}
Request::Move { seat, serial } => {
let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::Move {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
serial,
seat,
})
@ -127,18 +127,18 @@ where
Request::Resize { seat, serial, edges } => {
let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::Resize {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
serial,
seat,
edges,
})
}
Request::SetToplevel => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Toplevel,
}),
Request::SetTransient { parent, x, y, flags } => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Transient {
parent,
location: (x, y),
@ -150,7 +150,7 @@ where
framerate,
output,
} => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Fullscreen {
method,
framerate,
@ -167,7 +167,7 @@ where
} => {
let serial = Serial::from(serial);
(&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Popup {
parent,
serial,
@ -178,18 +178,32 @@ where
})
}
Request::SetMaximized { output } => (&mut *user_impl)(ShellRequest::SetKind {
surface: make_handle(&shell_surface, ctoken),
surface: make_handle(&shell_surface),
kind: ShellSurfaceKind::Maximized { output },
}),
Request::SetTitle { title } => {
ctoken
.with_role_data(&data.surface, |data| data.title = title)
.expect("wl_shell_surface exists but surface has not shell_surface role?!");
compositor::with_states(&data.surface, |states| {
let mut guard = states
.data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
guard.title = title;
})
.unwrap();
}
Request::SetClass { class_ } => {
ctoken
.with_role_data(&data.surface, |data| data.class = class_)
.expect("wl_shell_surface exists but surface has not shell_surface role?!");
compositor::with_states(&data.surface, |states| {
let mut guard = states
.data_map
.get::<Mutex<ShellSurfaceAttributes>>()
.unwrap()
.lock()
.unwrap();
guard.class = class_;
})
.unwrap();
}
_ => unreachable!(),
}
@ -200,7 +214,7 @@ where
let data = shell_surface
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
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 crate::wayland::compositor::{roles::*, CompositorToken};
use crate::wayland::shell::xdg::{ConfigureError, PopupState};
use crate::wayland::compositor;
use crate::wayland::shell::xdg::PopupState;
use crate::wayland::Serial;
use wayland_protocols::xdg_shell::server::{
xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
@ -12,25 +13,25 @@ use crate::utils::Rectangle;
use super::{
make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole,
XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
ShellData, SurfaceCachedState, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRoleAttributes,
XdgRequest, XdgToplevelSurfaceRoleAttributes,
};
pub(crate) fn implement_wm_base<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_data: &ShellData<R>,
) -> xdg_wm_base::XdgWmBase
where
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
shell.quick_assign(|shell, req, _data| wm_implementation::<R>(req, shell.deref().clone()));
shell_data: &ShellData,
) -> xdg_wm_base::XdgWmBase {
shell.quick_assign(|shell, req, _data| wm_implementation(req, shell.deref().clone()));
shell.as_ref().user_data().set(|| ShellUserData {
shell_data: shell_data.clone(),
client_data: Mutex::new(make_shell_client_data()),
});
let mut user_impl = shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::NewClient {
client: make_shell_client(&shell, shell_data.compositor_token),
client: make_shell_client(&shell),
});
shell.deref().clone()
}
@ -39,26 +40,19 @@ where
* xdg_shell
*/
pub(crate) struct ShellUserData<R> {
shell_data: ShellData<R>,
pub(crate) struct ShellUserData {
shell_data: ShellData,
pub(crate) client_data: Mutex<ShellClientData>,
}
pub(crate) fn make_shell_client<R>(
resource: &xdg_wm_base::XdgWmBase,
token: CompositorToken<R>,
) -> ShellClient<R> {
pub(crate) fn make_shell_client(resource: &xdg_wm_base::XdgWmBase) -> ShellClient {
ShellClient {
kind: super::ShellClientKind::Xdg(resource.clone()),
_token: token,
}
}
fn wm_implementation<R>(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase)
where
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
fn wm_implementation(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) {
let data = shell.as_ref().user_data().get::<ShellUserData>().unwrap();
match request {
xdg_wm_base::Request::Destroy => {
// all is handled by destructor
@ -70,14 +64,13 @@ where
// Do not assign a role to the surface here
// xdg_surface is not role, only xdg_toplevel and
// xdg_popup are defined as roles
id.quick_assign(|surface, req, _data| {
xdg_surface_implementation::<R>(req, surface.deref().clone())
});
id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface::<R>(surface)));
id.quick_assign(|surface, req, _data| xdg_surface_implementation(req, surface.deref().clone()));
id.assign_destructor(Filter::new(|surface, _, _data| destroy_surface(surface)));
id.as_ref().user_data().set(|| XdgSurfaceUserData {
shell_data: data.shell_data.clone(),
wl_surface: surface,
wm_base: shell.clone(),
has_active_role: AtomicBool::new(false),
});
}
xdg_wm_base::Request::Pong { serial } => {
@ -94,7 +87,7 @@ where
if valid {
let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::ClientPong {
client: make_shell_client(&shell, data.shell_data.compositor_token),
client: make_shell_client(&shell),
});
}
}
@ -169,21 +162,15 @@ fn implement_positioner(positioner: Main<xdg_positioner::XdgPositioner>) -> xdg_
* xdg_surface
*/
struct XdgSurfaceUserData<R> {
shell_data: ShellData<R>,
struct XdgSurfaceUserData {
shell_data: ShellData,
wl_surface: wl_surface::WlSurface,
wm_base: xdg_wm_base::XdgWmBase,
has_active_role: AtomicBool,
}
fn destroy_surface<R>(surface: xdg_surface::XdgSurface)
where
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = surface
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
fn destroy_surface(surface: xdg_surface::XdgSurface) {
let data = surface.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
if !data.wl_surface.as_ref().is_alive() {
// the wl_surface is destroyed, this means the client is not
// trying to change the role but it's a cleanup (possibly a
@ -191,25 +178,12 @@ where
return;
}
if !data.shell_data.compositor_token.has_a_role(&data.wl_surface) {
if compositor::get_role(&data.wl_surface).is_none() {
// No role assigned to the surface, we can exit early.
return;
}
let has_active_xdg_role = data
.shell_data
.compositor_token
.with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| {
role.is_some()
})
.unwrap_or(false)
|| data
.shell_data
.compositor_token
.with_role_data(&data.wl_surface, |role: &mut XdgPopupSurfaceRole| role.is_some())
.unwrap_or(false);
if has_active_xdg_role {
if data.has_active_role.load(Ordering::Acquire) {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(),
@ -217,14 +191,11 @@ where
}
}
fn xdg_surface_implementation<R>(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface)
where
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
fn xdg_surface_implementation(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) {
let data = xdg_surface
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.get::<XdgSurfaceUserData>()
.unwrap();
match request {
xdg_surface::Request::Destroy => {
@ -235,14 +206,7 @@ where
let surface = &data.wl_surface;
let shell = &data.wm_base;
let role_data = XdgToplevelSurfaceRole::Some(Default::default());
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
if compositor::give_role(surface, XDG_TOPLEVEL_ROLE).is_err() {
shell.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(),
@ -250,10 +214,19 @@ where
return;
}
id.quick_assign(|toplevel, req, _data| {
toplevel_implementation::<R>(req, toplevel.deref().clone())
});
id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel::<R>(toplevel)));
data.has_active_role.store(true, Ordering::Release);
compositor::with_states(surface, |states| {
states
.data_map
.insert_if_missing_threadsafe(|| Mutex::new(XdgToplevelSurfaceRoleAttributes::default()))
})
.unwrap();
compositor::add_commit_hook(surface, super::ToplevelSurface::commit_hook);
id.quick_assign(|toplevel, req, _data| toplevel_implementation(req, toplevel.deref().clone()));
id.assign_destructor(Filter::new(|toplevel, _, _data| destroy_toplevel(toplevel)));
id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(),
@ -286,11 +259,7 @@ where
.clone();
let parent_surface = parent.map(|parent| {
let parent_data = parent
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
let parent_data = parent.as_ref().user_data().get::<XdgSurfaceUserData>().unwrap();
parent_data.wl_surface.clone()
});
@ -298,21 +267,15 @@ where
let surface = &data.wl_surface;
let shell = &data.wm_base;
let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes {
let attributes = XdgPopupSurfaceRoleAttributes {
parent: parent_surface,
server_pending: Some(PopupState {
// Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(),
}),
..Default::default()
});
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
};
if compositor::give_role(surface, XDG_POPUP_ROLE).is_err() {
shell.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(),
@ -320,8 +283,25 @@ where
return;
}
id.quick_assign(|popup, req, _data| xdg_popup_implementation::<R>(req, popup.deref().clone()));
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
data.has_active_role.store(true, Ordering::Release);
compositor::with_states(surface, |states| {
states
.data_map
.insert_if_missing_threadsafe(|| Mutex::new(XdgPopupSurfaceRoleAttributes::default()));
*states
.data_map
.get::<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 {
shell_data: data.shell_data.clone(),
wl_surface: data.wl_surface.clone(),
@ -346,33 +326,28 @@ where
// which is a protocol error.
let surface = &data.wl_surface;
if !data.shell_data.compositor_token.has_a_role(surface) {
data.wm_base.as_ref().post_error(
let role = compositor::get_role(surface);
if role.is_none() {
xdg_surface.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
return;
}
// Set the next window geometry here, the geometry will be moved from
// next to the current geometry on a commit. This has to be done currently
// in anvil as the whole commit logic is implemented there until a proper
// abstraction has been found to handle commits within roles. This also
// ensures that a commit for a xdg_surface follows the rules for subsurfaces.
let has_wrong_role = data
.shell_data
.compositor_token
.with_xdg_role(surface, |role| {
role.set_window_geometry(Rectangle { x, y, width, height })
})
.is_err();
if has_wrong_role {
if role != Some(XDG_TOPLEVEL_ROLE) && role != Some(XDG_POPUP_ROLE) {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
);
}
compositor::with_states(surface, |states| {
states.cached_state.pending::<SurfaceCachedState>().geometry =
Some(Rectangle { x, y, width, height });
})
.unwrap();
}
xdg_surface::Request::AckConfigure { serial } => {
let serial = Serial::from(serial);
@ -381,8 +356,8 @@ where
// Check the role of the surface, this can be either xdg_toplevel
// or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error.
if !data.shell_data.compositor_token.has_a_role(surface) {
data.wm_base.as_ref().post_error(
if compositor::get_role(surface).is_none() {
xdg_surface.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
@ -401,21 +376,38 @@ where
// width, height, min/max size, maximized, fullscreen, resizing, activated
//
// This can be used to integrate custom protocol extensions
//
let configure = match data
.shell_data
.compositor_token
.with_xdg_role(surface, |role| role.ack_configure(serial))
{
Ok(Ok(configure)) => configure,
Ok(Err(ConfigureError::SerialNotFound(serial))) => {
let found_configure = compositor::with_states(surface, |states| {
if states.role == Some(XDG_TOPLEVEL_ROLE) {
Ok(states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.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(
xdg_wm_base::Error::InvalidSurfaceState as u32,
format!("wrong configure serial: {}", <u32>::from(serial)),
);
return;
}
Err(_) => {
Err(()) => {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
@ -438,64 +430,54 @@ where
* xdg_toplevel
*/
pub(crate) struct ShellSurfaceUserData<R> {
pub(crate) shell_data: ShellData<R>,
pub(crate) struct ShellSurfaceUserData {
pub(crate) shell_data: ShellData,
pub(crate) wl_surface: wl_surface::WlSurface,
pub(crate) wm_base: xdg_wm_base::XdgWmBase,
pub(crate) xdg_surface: xdg_surface::XdgSurface,
}
// Utility functions allowing to factor out a lot of the upcoming logic
fn with_surface_toplevel_role_data<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
fn with_surface_toplevel_role_data<F, T>(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T
where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
shell_data
.compositor_token
.with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| {
let attributes = role
.as_mut()
.expect("xdg_toplevel exists but role has been destroyed?!");
f(attributes)
compositor::with_states(&data.wl_surface, |states| {
f(&mut *states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!")
.unwrap()
}
fn with_surface_toplevel_client_pending<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
fn with_toplevel_pending_state<F, T>(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T
where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelClientPending) -> T,
F: FnOnce(&mut SurfaceCachedState) -> T,
{
with_surface_toplevel_role_data(shell_data, toplevel, |data| {
if data.client_pending.is_none() {
data.client_pending = Some(Default::default());
}
f(&mut data.client_pending.as_mut().unwrap())
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData>()
.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)
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
pub fn send_toplevel_configure(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) {
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
let (width, height) = configure.state.size.unwrap_or((0, 0));
@ -518,27 +500,23 @@ where
data.xdg_surface.configure(serial.into());
}
fn make_toplevel_handle<R: 'static>(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface<R> {
fn make_toplevel_handle(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface {
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
super::ToplevelSurface {
wl_surface: data.wl_surface.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)
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
fn toplevel_implementation(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) {
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
match request {
xdg_toplevel::Request::Destroy => {
@ -546,12 +524,12 @@ where
}
xdg_toplevel::Request::SetParent { parent } => {
// Parent is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
with_surface_toplevel_role_data(&toplevel, |data| {
data.parent = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap()
.wl_surface
.clone()
@ -560,13 +538,13 @@ where
}
xdg_toplevel::Request::SetTitle { title } => {
// Title is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
with_surface_toplevel_role_data(&toplevel, |data| {
data.title = Some(title);
});
}
xdg_toplevel::Request::SetAppId { app_id } => {
// AppId is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| {
with_surface_toplevel_role_data(&toplevel, |role| {
role.app_id = Some(app_id);
});
}
@ -606,13 +584,13 @@ where
});
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = Some((width, height));
with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.max_size = (width, height);
});
}
xdg_toplevel::Request::SetMinSize { width, height } => {
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = Some((width, height));
with_toplevel_pending_state(&toplevel, |toplevel_data| {
toplevel_data.min_size = (width, height);
});
}
xdg_toplevel::Request::SetMaximized => {
@ -649,26 +627,14 @@ where
}
}
fn destroy_toplevel<R>(toplevel: xdg_toplevel::XdgToplevel)
where
R: Role<XdgToplevelSurfaceRole> + 'static,
{
fn destroy_toplevel(toplevel: xdg_toplevel::XdgToplevel) {
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
if !data.wl_surface.as_ref().is_alive() {
// the wl_surface is destroyed, this means the client is not
// trying to change the role but it's a cleanup (possibly a
// disconnecting client), ignore the protocol check.
} else {
data.shell_data
.compositor_token
.with_role_data(&data.wl_surface, |role_data| {
*role_data = XdgToplevelSurfaceRole::None;
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
data.has_active_role.store(false, Ordering::Release);
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data
@ -683,14 +649,11 @@ where
* xdg_popup
*/
pub(crate) fn send_popup_configure<R>(resource: &xdg_popup::XdgPopup, configure: PopupConfigure)
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
pub(crate) fn send_popup_configure(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) {
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
let serial = configure.serial;
@ -704,28 +667,20 @@ where
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
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.get::<ShellSurfaceUserData>()
.unwrap();
super::PopupSurface {
wl_surface: data.wl_surface.clone(),
shell_surface: PopupKind::Xdg(resource.clone()),
token: data.shell_data.compositor_token,
}
}
fn xdg_popup_implementation<R>(request: xdg_popup::Request, popup: xdg_popup::XdgPopup)
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
fn xdg_popup_implementation(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) {
let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
match request {
xdg_popup::Request::Destroy => {
// all is handled by our destructor
@ -744,26 +699,10 @@ where
}
}
fn destroy_popup<R>(popup: xdg_popup::XdgPopup)
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
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?!");
fn destroy_popup(popup: xdg_popup::XdgPopup) {
let data = popup.as_ref().user_data().get::<ShellSurfaceUserData>().unwrap();
if let Some(data) = data.xdg_surface.as_ref().user_data().get::<XdgSurfaceUserData>() {
data.has_active_role.store(false, Ordering::Release);
}
// remove this surface from the known ones (as well as any leftover dead surface)
data.shell_data

View File

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