From 98906555f54c3d1d93482143a9fc4814a0831fbe Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 28 Nov 2021 01:22:00 +0100 Subject: [PATCH] desktop: Add new desktop abstractions module Adds a new `Window` and `Space` abstraction to make it easier to handle windows of various shells and their positions in a compositor space. --- Cargo.toml | 4 +- src/desktop/mod.rs | 7 + src/desktop/space.rs | 419 ++++++++++++++++++++++++++++++++++ src/desktop/window.rs | 510 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 5 files changed, 941 insertions(+), 1 deletion(-) create mode 100644 src/desktop/mod.rs create mode 100644 src/desktop/space.rs create mode 100644 src/desktop/window.rs diff --git a/Cargo.toml b/Cargo.toml index e959cd1..6bcd24b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ drm = { version = "0.5.0", optional = true } drm-ffi = { version = "0.2.0", optional = true } gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] } input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true } +indexmap = { version = "1.7", optional = true } lazy_static = "1" libc = "0.2.103" libseat= { version = "0.1.1", optional = true } @@ -59,7 +60,7 @@ gl_generator = { version = "0.14", optional = true } pkg-config = { version = "0.3.17", optional = true } [features] -default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] +default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "desktop", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"] backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"] backend_drm = ["drm", "drm-ffi"] @@ -71,6 +72,7 @@ backend_udev = ["udev", "input/udev"] backend_session_logind = ["dbus", "backend_session", "pkg-config"] backend_session_elogind = ["backend_session_logind"] backend_session_libseat = ["backend_session", "libseat"] +desktop = ["indexmap", "wayland_frontend"] renderer_gl = ["gl_generator", "backend_egl"] use_system_lib = ["wayland_frontend", "wayland-sys", "wayland-server/use_system_lib"] wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols", "tempfile"] diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs new file mode 100644 index 0000000..7c885a5 --- /dev/null +++ b/src/desktop/mod.rs @@ -0,0 +1,7 @@ +// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings +#![allow(missing_docs, clippy::all)] +mod space; +mod window; + +pub use self::space::*; +pub use self::window::*; diff --git a/src/desktop/space.rs b/src/desktop/space.rs new file mode 100644 index 0000000..7365cdf --- /dev/null +++ b/src/desktop/space.rs @@ -0,0 +1,419 @@ +use super::{draw_window, Window}; +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Transform}, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use indexmap::{IndexMap, IndexSet}; +use std::{ + cell::{RefCell, RefMut}, + collections::{HashMap, HashSet, VecDeque}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; +use wayland_server::protocol::wl_surface; + +static SPACE_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static::lazy_static! { + static ref SPACE_IDS: Mutex> = Mutex::new(HashSet::new()); +} +fn next_space_id() -> usize { + let mut ids = SPACE_IDS.lock().unwrap(); + if ids.len() == usize::MAX { + // Theoretically the code below wraps around correctly, + // but that is hard to detect and might deadlock. + // Maybe make this a debug_assert instead? + panic!("Out of space ids"); + } + + let mut id = SPACE_ID.fetch_add(1, Ordering::SeqCst); + while ids.iter().any(|k| *k == id) { + id = SPACE_ID.fetch_add(1, Ordering::SeqCst); + } + + ids.insert(id); + id +} + +#[derive(Default)] +struct WindowState { + location: Point, + drawn: bool, +} + +type WindowUserdata = RefCell>; +fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { + let userdata = w.user_data(); + userdata.insert_if_missing(WindowUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +#[derive(Clone, Default)] +struct OutputState { + location: Point, + render_scale: f64, + // damage and last_state in space coordinate space + old_damage: VecDeque>>, + last_state: IndexMap>, +} + +type OutputUserdata = RefCell>; +fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { + let userdata = o.user_data(); + userdata.insert_if_missing(OutputUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +// TODO: Maybe replace UnmanagedResource if nothing else comes up? +#[derive(Debug, thiserror::Error)] +pub enum SpaceError { + #[error("Window is not mapped to this space")] + UnknownWindow, +} + +#[derive(Debug)] +pub struct Space { + id: usize, + // in z-order, back to front + windows: IndexSet, + outputs: Vec, + // TODO: + //layers: Vec, + logger: ::slog::Logger, +} + +impl Drop for Space { + fn drop(&mut self) { + SPACE_IDS.lock().unwrap().remove(&self.id); + } +} + +impl Space { + pub fn new(log: L) -> Space + where + L: Into, + { + Space { + id: next_space_id(), + windows: IndexSet::new(), + outputs: Vec::new(), + logger: log.into(), + } + } + + /// Map window and moves it to top of the stack + /// + /// This can safely be called on an already mapped window + pub fn map_window(&mut self, window: &Window, location: Point) -> Result<(), SpaceError> { + window_state(self.id, window).location = location; + self.windows.shift_remove(window); + self.windows.insert(window.clone()); + Ok(()) + } + + pub fn raise_window(&mut self, window: &Window) { + let loc = window_state(self.id, window).location; + let _ = self.map_window(window, loc); + + // TODO: should this be handled by us? + window.set_activated(true); + for w in self.windows.iter() { + if w != window { + w.set_activated(false); + } + } + } + + /// Unmap a window from this space by its id + pub fn unmap_window(&mut self, window: &Window) { + if let Some(map) = window.user_data().get::() { + map.borrow_mut().remove(&self.id); + } + self.windows.shift_remove(window); + } + + /// Iterate window in z-order back to front + pub fn windows(&self) -> impl Iterator { + self.windows.iter() + } + + /// Get a reference to the window under a given point, if any + pub fn window_under(&self, point: Point) -> Option<&Window> { + self.windows.iter().find(|w| { + let loc = window_state(self.id, w).location; + let mut bbox = w.bbox(); + bbox.loc += loc; + bbox.to_f64().contains(point) + }) + } + + pub fn window_for_surface(&self, surface: &wl_surface::WlSurface) -> Option<&Window> { + if !surface.as_ref().is_alive() { + return None; + } + + self.windows + .iter() + .find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false)) + } + + pub fn window_geometry(&self, w: &Window) -> Option> { + if !self.windows.contains(w) { + return None; + } + + Some(window_rect(w, &self.id)) + } + + pub fn map_output(&mut self, output: &Output, scale: f64, location: Point) { + let mut state = output_state(self.id, output); + *state = OutputState { + location, + render_scale: scale, + ..Default::default() + }; + if !self.outputs.contains(output) { + self.outputs.push(output.clone()); + } + } + + pub fn outputs(&self) -> impl Iterator { + self.outputs.iter() + } + + pub fn unmap_output(&mut self, output: &Output) { + if let Some(map) = output.user_data().get::() { + map.borrow_mut().remove(&self.id); + } + self.outputs.retain(|o| o != output); + } + + pub fn output_geometry(&self, o: &Output) -> Option> { + if !self.outputs.contains(o) { + return None; + } + + let state = output_state(self.id, o); + o.current_mode().map(|mode| { + Rectangle::from_loc_and_size( + state.location, + mode.size.to_f64().to_logical(state.render_scale).to_i32_round(), + ) + }) + } + + pub fn output_scale(&self, o: &Output) -> Option { + if !self.outputs.contains(o) { + return None; + } + + let state = output_state(self.id, o); + Some(state.render_scale) + } + + pub fn output_for_window(&self, w: &Window) -> Option { + if !self.windows.contains(w) { + return None; + } + + let w_geo = self.window_geometry(w).unwrap(); + for o in &self.outputs { + let o_geo = self.output_geometry(o).unwrap(); + if w_geo.overlaps(o_geo) { + return Some(o.clone()); + } + } + + // TODO primary output + self.outputs.get(0).cloned() + } + + pub fn cleanup(&mut self) { + self.windows.retain(|w| w.toplevel().alive()); + } + + pub fn render_output( + &mut self, + renderer: &mut R, + output: &Output, + age: usize, + clear_color: [f32; 4], + ) -> Result> + where + R: Renderer + ImportAll, + R::TextureId: 'static, + { + let mut state = output_state(self.id, output); + let output_size = output + .current_mode() + .ok_or(RenderError::OutputNoMode)? + .size + .to_f64() + .to_logical(state.render_scale) + .to_i32_round(); + let output_geo = Rectangle::from_loc_and_size(state.location, output_size); + + // This will hold all the damage we need for this rendering step + let mut damage = Vec::>::new(); + // First add damage for windows gone + for old_window in state + .last_state + .iter() + .filter_map(|(id, w)| { + if !self.windows.iter().any(|w| w.0.id == *id) { + Some(*w) + } else { + None + } + }) + .collect::>>() + { + slog::debug!(self.logger, "Removing window at: {:?}", old_window); + damage.push(old_window); + } + + // lets iterate front to back and figure out, what new windows or unmoved windows we have + for window in self.windows.iter().rev() { + let geo = window_rect(window, &self.id); + let old_geo = state.last_state.get(&window.0.id).cloned(); + + // window was moved or resized + if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { + // Add damage for the old position of the window + damage.push(old_geo.unwrap()); + } /* else { + // window stayed at its place + // TODO: Only push surface damage + // But this would need to take subsurfaces into account and accumulate damage at least. + // Even better would be if damage would be ignored that is hidden by subsurfaces... + }*/ + // Add damage for the new position (see TODO above for a better approach) + damage.push(geo); + } + + // That is all completely new damage, which we need to store for subsequent renders + let new_damage = damage.clone(); + // We now add old damage states, if we have an age value + if age > 0 && state.old_damage.len() >= age { + // We do not need older states anymore + state.old_damage.truncate(age); + damage.extend(state.old_damage.iter().flatten().copied()); + } else { + // just damage everything, if we have no damage + damage = vec![output_geo]; + } + + // Optimize the damage for rendering + damage.retain(|rect| rect.overlaps(output_geo)); + damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); + for rect in damage.clone().iter() { + // if this rect was already removed, because it was smaller as another one, + // there is no reason to evaluate this. + if damage.contains(rect) { + // remove every rectangle that is contained in this rectangle + damage.retain(|other| !rect.contains_rect(*other)); + } + } + + let output_transform: Transform = output.current_transform().into(); + if let Err(err) = renderer.render( + output_transform + .transform_size(output_size) + .to_f64() + .to_physical(state.render_scale) + .to_i32_round(), + output_transform, + |renderer, frame| { + // First clear all damaged regions + for geo in &damage { + slog::debug!(self.logger, "Clearing at {:?}", geo); + frame.clear( + clear_color, + Some(geo.to_f64().to_physical(state.render_scale).to_i32_ceil()), + )?; + } + + // Then re-draw all window overlapping with a damage rect. + for window in self.windows.iter() { + let wgeo = window_rect(window, &self.id); + let mut loc = window_loc(window, &self.id); + if damage.iter().any(|geo| wgeo.overlaps(*geo)) { + loc -= output_geo.loc; + slog::debug!(self.logger, "Rendering window at {:?}", wgeo); + draw_window(renderer, frame, window, state.render_scale, loc, &self.logger)?; + window_state(self.id, window).drawn = true; + } + } + + Result::<(), R::Error>::Ok(()) + }, + ) { + // if the rendering errors on us, we need to be prepared, that this whole buffer was partially updated and thus now unusable. + // thus clean our old states before returning + state.old_damage = VecDeque::new(); + state.last_state = IndexMap::new(); + return Err(RenderError::Rendering(err)); + } + + // If rendering was successful capture the state and add the damage + state.last_state = self + .windows + .iter() + .map(|window| { + let wgeo = window_rect(window, &self.id); + (window.0.id, wgeo) + }) + .collect(); + state.old_damage.push_front(new_damage); + + // Return if we actually rendered something + Ok(!damage.is_empty()) + } + + pub fn send_frames(&self, all: bool, time: u32) { + for window in self.windows.iter().filter(|w| { + all || { + let mut state = window_state(self.id, w); + std::mem::replace(&mut state.drawn, false) + } + }) { + window.send_frame(time); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RenderError { + #[error(transparent)] + Rendering(R::Error), + #[error("Output has no active mode")] + OutputNoMode, +} + +fn window_rect(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + window_bbox_with_pos(window, loc) +} + +fn window_loc(window: &Window, space_id: &usize) -> Point { + window + .user_data() + .get::>>() + .unwrap() + .borrow() + .get(space_id) + .unwrap() + .location +} + +fn window_bbox_with_pos(window: &Window, pos: Point) -> Rectangle { + let mut wgeo = window.bbox(); + wgeo.loc += pos; + wgeo +} diff --git a/src/desktop/window.rs b/src/desktop/window.rs new file mode 100644 index 0000000..f3292d0 --- /dev/null +++ b/src/desktop/window.rs @@ -0,0 +1,510 @@ +use crate::{ + backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, + utils::{Logical, Physical, Point, Rectangle, Size}, + wayland::{ + compositor::{ + add_commit_hook, is_sync_subsurface, with_states, with_surface_tree_downward, + with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, + TraversalAction, + }, + shell::xdg::{SurfaceCachedState, ToplevelSurface}, + }, +}; +use std::{ + cell::RefCell, + collections::HashSet, + hash::{Hash, Hasher}, + rc::Rc, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; +use wayland_commons::user_data::UserDataMap; +use wayland_protocols::xdg_shell::server::xdg_toplevel; +use wayland_server::protocol::{wl_buffer, wl_surface}; + +static WINDOW_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static::lazy_static! { + static ref WINDOW_IDS: Mutex> = Mutex::new(HashSet::new()); +} + +fn next_window_id() -> usize { + let mut ids = WINDOW_IDS.lock().unwrap(); + if ids.len() == usize::MAX { + // Theoretically the code below wraps around correctly, + // but that is hard to detect and might deadlock. + // Maybe make this a debug_assert instead? + panic!("Out of window ids"); + } + + let mut id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); + while ids.iter().any(|k| *k == id) { + id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); + } + + ids.insert(id); + id +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Kind { + Xdg(ToplevelSurface), + #[cfg(feature = "xwayland")] + X11(X11Surface), +} + +// Big TODO +#[derive(Debug, Clone)] +pub struct X11Surface { + surface: wl_surface::WlSurface, +} + +impl std::cmp::PartialEq for X11Surface { + fn eq(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.surface == other.surface + } +} + +impl X11Surface { + pub fn alive(&self) -> bool { + self.surface.as_ref().is_alive() + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.surface) + } else { + None + } + } +} + +impl Kind { + pub fn alive(&self) -> bool { + match *self { + Kind::Xdg(ref t) => t.alive(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.alive(), + } + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + match *self { + Kind::Xdg(ref t) => t.get_surface(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.get_surface(), + } + } +} + +#[derive(Default)] +struct SurfaceState { + buffer_dimensions: Option>, + buffer_scale: i32, + buffer: Option, + texture: Option>, +} + +fn surface_commit(surface: &wl_surface::WlSurface) { + if !is_sync_subsurface(surface) { + with_surface_tree_upward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |_surf, states, _| { + states + .data_map + .insert_if_missing(|| RefCell::new(SurfaceState::default())); + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + data.update_buffer(&mut *states.cached_state.current::()); + }, + |_, _, _| true, + ); + } +} + +impl SurfaceState { + pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) { + match attrs.buffer.take() { + Some(BufferAssignment::NewBuffer { buffer, .. }) => { + // new contents + self.buffer_dimensions = buffer_dimensions(&buffer); + self.buffer_scale = attrs.buffer_scale; + if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { + if &old_buffer != self.buffer.as_ref().unwrap() { + old_buffer.release(); + } + } + self.texture = None; + } + Some(BufferAssignment::Removed) => { + // remove the contents + self.buffer_dimensions = None; + if let Some(buffer) = self.buffer.take() { + buffer.release(); + }; + self.texture = None; + } + None => {} + } + } + + /// Returns the size of the surface. + pub fn size(&self) -> Option> { + self.buffer_dimensions + .map(|dims| dims.to_logical(self.buffer_scale)) + } + + fn contains_point(&self, attrs: &SurfaceAttributes, point: Point) -> bool { + let size = match self.size() { + None => return false, // If the surface has no size, it can't have an input region. + Some(size) => size, + }; + + let rect = Rectangle { + loc: (0, 0).into(), + size, + } + .to_f64(); + + // The input region is always within the surface itself, so if the surface itself doesn't contain the + // point we can return false. + if !rect.contains(point) { + return false; + } + + // If there's no input region, we're done. + if attrs.input_region.is_none() { + return true; + } + + attrs + .input_region + .as_ref() + .unwrap() + .contains(point.to_i32_floor()) + } +} + +#[derive(Debug)] +pub(super) struct WindowInner { + pub(super) id: usize, + toplevel: Kind, + user_data: UserDataMap, +} + +impl Drop for WindowInner { + fn drop(&mut self) { + WINDOW_IDS.lock().unwrap().remove(&self.id); + } +} + +#[derive(Debug, Clone)] +pub struct Window(pub(super) Rc); + +impl PartialEq for Window { + fn eq(&self, other: &Self) -> bool { + self.0.id == other.0.id + } +} + +impl Eq for Window {} + +impl Hash for Window { + fn hash(&self, state: &mut H) { + self.0.id.hash(state); + } +} + +impl Window { + pub fn new(toplevel: Kind) -> Window { + let id = next_window_id(); + + // TODO: Do we want this? For new lets add Window::commit + //add_commit_hook(toplevel.get_surface().unwrap(), surface_commit); + + Window(Rc::new(WindowInner { + id, + toplevel, + user_data: UserDataMap::new(), + })) + } + + /// Returns the geometry of this window. + pub fn geometry(&self) -> Rectangle { + // It's the set geometry with the full bounding box as the fallback. + with_states(self.0.toplevel.get_surface().unwrap(), |states| { + states.cached_state.current::().geometry + }) + .unwrap() + .unwrap_or_else(|| self.bbox()) + } + + /// A bounding box over this window and its children. + // TODO: Cache and document when to trigger updates. If possible let space do it + pub fn bbox(&self) -> Rectangle { + let mut bounding_box = Rectangle::from_loc_and_size((0, 0), (0, 0)); + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (0, 0).into(), + |_, states, loc: &Point| { + let mut loc = *loc; + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().size()) { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + loc += current.location; + } + + // Update the bounding box. + bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); + + TraversalAction::DoChildren(loc) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |_, _, _| {}, + |_, _, _| true, + ); + } + bounding_box + } + + /// Activate/Deactivate this window + // TODO: Add more helpers for Maximize? Minimize? Fullscreen? I dunno + pub fn set_activated(&self, active: bool) -> bool { + match self.0.toplevel { + Kind::Xdg(ref t) => t + .with_pending_state(|state| { + if active { + state.states.set(xdg_toplevel::State::Activated) + } else { + state.states.unset(xdg_toplevel::State::Activated) + } + }) + .unwrap_or(false), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => unimplemented!(), + } + } + + /// Commit any changes to this window + pub fn configure(&self) { + match self.0.toplevel { + Kind::Xdg(ref t) => t.send_configure(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => unimplemented!(), + } + } + + /// Sends the frame callback to all the subsurfaces in this + /// window that requested it + pub fn send_frame(&self, time: u32) { + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_surf, states, &()| { + // the surface may not have any user_data if it is a subsurface and has not + // yet been commited + for callback in states + .cached_state + .current::() + .frame_callbacks + .drain(..) + { + callback.done(time); + } + }, + |_, _, &()| true, + ); + } + } + + /// Finds the topmost surface under this point if any and returns it together with the location of this + /// surface. + pub fn surface_under( + &self, + point: Point, + ) -> Option<(wl_surface::WlSurface, Point)> { + let found = RefCell::new(None); + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (0, 0).into(), + |wl_surface, states, location: &Point| { + let mut location = *location; + let data = states.data_map.get::>(); + + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), point - location.to_f64()) + }) + .unwrap_or(false); + if contains_the_point { + *found.borrow_mut() = Some((wl_surface.clone(), location)); + } + + TraversalAction::DoChildren(location) + }, + |_, _, _| {}, + |_, _, _| { + // only continue if the point is not found + found.borrow().is_none() + }, + ); + } + found.into_inner() + } + + pub fn toplevel(&self) -> &Kind { + &self.0.toplevel + } + + pub fn user_data(&self) -> &UserDataMap { + &self.0.user_data + } + + /// Has to be called on commit - Window handles the buffer for you + pub fn commit(surface: &wl_surface::WlSurface) { + surface_commit(surface) + } +} + +// TODO: This is basically `draw_surface_tree` from anvil. +// Can we move this somewhere, where it is also usable for other things then windows? +// Maybe add this as a helper function for surfaces to `backend::renderer`? +// How do we handle SurfaceState in that case? Like we need a closure to +// store and retrieve textures for arbitrary surface trees? Or leave this to the +// compositor, but that feels like a lot of unnecessary code dublication. + +// TODO: This does not handle ImportAll errors properly and uses only one texture slot. +// This means it is *not* compatible with MultiGPU setups at all. +// Current plan is to make sure it does not crash at least in that case and later add +// A `MultiGpuManager` that opens gpus automatically, creates renderers for them, +// implements `Renderer` and `ImportAll` itself and dispatches everything accordingly, +// even copying buffers if necessary. This abstraction will likely also handle dmabuf- +// protocol(s) (and maybe explicit sync?). Then this function will be fine and all the +// gore of handling multiple gpus will be hidden away for most if not all cases. + +// TODO: This function does not crop or scale windows to fit into a space. +// How do we want to handle this? Add an additional size property to a window? +// Let the user specify the max size and the method to handle it? + +pub fn draw_window( + renderer: &mut R, + frame: &mut F, + window: &Window, + scale: f64, + location: Point, + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + let mut result = Ok(()); + if let Some(surface) = window.0.toplevel.get_surface() { + with_surface_tree_upward( + surface, + location, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let attributes = states.cached_state.current::(); + // Import a new buffer if necessary + if data.texture.is_none() { + if let Some(buffer) = data.buffer.as_ref() { + let damage = attributes + .damage + .iter() + .map(|dmg| match dmg { + Damage::Buffer(rect) => *rect, + // TODO also apply transformations + Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), + }) + .collect::>(); + + match renderer.import_buffer(buffer, Some(states), &damage) { + Some(Ok(m)) => { + data.texture = Some(Box::new(m)); + } + Some(Err(err)) => { + slog::warn!(log, "Error loading buffer: {}", err); + } + None => { + slog::error!(log, "Unknown buffer format for: {:?}", buffer); + } + } + } + } + // Now, should we be drawn ? + if data.texture.is_some() { + // if yes, also process the children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + TraversalAction::DoChildren(location) + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + }, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let buffer_scale = data.buffer_scale; + let attributes = states.cached_state.current::(); + if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { + // we need to re-extract the subsurface offset, as the previous closure + // only passes it to our children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + // TODO: Take wp_viewporter into account + if let Err(err) = frame.render_texture_at( + texture, + location.to_f64().to_physical(scale).to_i32_round(), + buffer_scale, + scale, + attributes.buffer_transform.into(), + 1.0, + ) { + result = Err(err); + } + } + } + }, + |_, _, _| true, + ); + } + + result +} diff --git a/src/lib.rs b/src/lib.rs index bee4d66..3472e88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,8 @@ pub extern crate nix; pub mod backend; +#[cfg(feature = "desktop")] +pub mod desktop; pub mod utils; #[cfg(feature = "wayland_frontend")] pub mod wayland;