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.
This commit is contained in:
parent
76787fb7df
commit
98906555f5
|
@ -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"]
|
||||
|
|
|
@ -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::*;
|
|
@ -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<HashSet<usize>> = 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<i32, Logical>,
|
||||
drawn: bool,
|
||||
}
|
||||
|
||||
type WindowUserdata = RefCell<HashMap<usize, WindowState>>;
|
||||
fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> {
|
||||
let userdata = w.user_data();
|
||||
userdata.insert_if_missing(WindowUserdata::default);
|
||||
RefMut::map(userdata.get::<WindowUserdata>().unwrap().borrow_mut(), |m| {
|
||||
m.entry(space).or_default()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct OutputState {
|
||||
location: Point<i32, Logical>,
|
||||
render_scale: f64,
|
||||
// damage and last_state in space coordinate space
|
||||
old_damage: VecDeque<Vec<Rectangle<i32, Logical>>>,
|
||||
last_state: IndexMap<usize, Rectangle<i32, Logical>>,
|
||||
}
|
||||
|
||||
type OutputUserdata = RefCell<HashMap<usize, OutputState>>;
|
||||
fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> {
|
||||
let userdata = o.user_data();
|
||||
userdata.insert_if_missing(OutputUserdata::default);
|
||||
RefMut::map(userdata.get::<OutputUserdata>().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<Window>,
|
||||
outputs: Vec<Output>,
|
||||
// TODO:
|
||||
//layers: Vec<Layer>,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
impl Drop for Space {
|
||||
fn drop(&mut self) {
|
||||
SPACE_IDS.lock().unwrap().remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Space {
|
||||
pub fn new<L>(log: L) -> Space
|
||||
where
|
||||
L: Into<slog::Logger>,
|
||||
{
|
||||
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<i32, Logical>) -> 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::<WindowUserdata>() {
|
||||
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<Item = &Window> {
|
||||
self.windows.iter()
|
||||
}
|
||||
|
||||
/// Get a reference to the window under a given point, if any
|
||||
pub fn window_under(&self, point: Point<f64, Logical>) -> 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<Rectangle<i32, Logical>> {
|
||||
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<i32, Logical>) {
|
||||
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<Item = &Output> {
|
||||
self.outputs.iter()
|
||||
}
|
||||
|
||||
pub fn unmap_output(&mut self, output: &Output) {
|
||||
if let Some(map) = output.user_data().get::<OutputUserdata>() {
|
||||
map.borrow_mut().remove(&self.id);
|
||||
}
|
||||
self.outputs.retain(|o| o != output);
|
||||
}
|
||||
|
||||
pub fn output_geometry(&self, o: &Output) -> Option<Rectangle<i32, Logical>> {
|
||||
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<f64> {
|
||||
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<Output> {
|
||||
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<R>(
|
||||
&mut self,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
age: usize,
|
||||
clear_color: [f32; 4],
|
||||
) -> Result<bool, RenderError<R>>
|
||||
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::<Rectangle<i32, Logical>>::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::<Vec<Rectangle<i32, Logical>>>()
|
||||
{
|
||||
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<R: Renderer> {
|
||||
#[error(transparent)]
|
||||
Rendering(R::Error),
|
||||
#[error("Output has no active mode")]
|
||||
OutputNoMode,
|
||||
}
|
||||
|
||||
fn window_rect(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
|
||||
let loc = window_loc(window, space_id);
|
||||
window_bbox_with_pos(window, loc)
|
||||
}
|
||||
|
||||
fn window_loc(window: &Window, space_id: &usize) -> Point<i32, Logical> {
|
||||
window
|
||||
.user_data()
|
||||
.get::<RefCell<HashMap<usize, WindowState>>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.get(space_id)
|
||||
.unwrap()
|
||||
.location
|
||||
}
|
||||
|
||||
fn window_bbox_with_pos(window: &Window, pos: Point<i32, Logical>) -> Rectangle<i32, Logical> {
|
||||
let mut wgeo = window.bbox();
|
||||
wgeo.loc += pos;
|
||||
wgeo
|
||||
}
|
|
@ -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<HashSet<usize>> = 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<Size<i32, Physical>>,
|
||||
buffer_scale: i32,
|
||||
buffer: Option<wl_buffer::WlBuffer>,
|
||||
texture: Option<Box<dyn std::any::Any + 'static>>,
|
||||
}
|
||||
|
||||
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::<RefCell<SurfaceState>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
data.update_buffer(&mut *states.cached_state.current::<SurfaceAttributes>());
|
||||
},
|
||||
|_, _, _| 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<Size<i32, Logical>> {
|
||||
self.buffer_dimensions
|
||||
.map(|dims| dims.to_logical(self.buffer_scale))
|
||||
}
|
||||
|
||||
fn contains_point(&self, attrs: &SurfaceAttributes, point: Point<f64, Logical>) -> 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<WindowInner>);
|
||||
|
||||
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<H: Hasher>(&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<i32, Logical> {
|
||||
// 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::<SurfaceCachedState>().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<i32, Logical> {
|
||||
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<i32, Logical>| {
|
||||
let mut loc = *loc;
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if let Some(size) = data.and_then(|d| d.borrow().size()) {
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
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::<SurfaceAttributes>()
|
||||
.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<f64, Logical>,
|
||||
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> {
|
||||
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<i32, Logical>| {
|
||||
let mut location = *location;
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
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<R, E, F, T>(
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
window: &Window,
|
||||
scale: f64,
|
||||
location: Point<i32, Logical>,
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
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::<RefCell<SurfaceState>>() {
|
||||
let mut data = data.borrow_mut();
|
||||
let attributes = states.cached_state.current::<SurfaceAttributes>();
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
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::<SubsurfaceCachedState>();
|
||||
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::<RefCell<SurfaceState>>() {
|
||||
let mut data = data.borrow_mut();
|
||||
let buffer_scale = data.buffer_scale;
|
||||
let attributes = states.cached_state.current::<SurfaceAttributes>();
|
||||
if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::<T>()) {
|
||||
// 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::<SubsurfaceCachedState>();
|
||||
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
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue