desktop: layer-shell support

This commit is contained in:
Victor Brekenfeld 2021-12-13 22:06:39 +01:00
parent cea88fdde0
commit 3b39d780fe
5 changed files with 639 additions and 4 deletions

501
src/desktop/layer.rs Normal file
View File

@ -0,0 +1,501 @@
use crate::{
backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture},
desktop::{utils::*, PopupManager},
utils::{user_data::UserDataMap, Logical, Point, Rectangle},
wayland::{
compositor::with_states,
output::{Inner as OutputInner, Output},
shell::wlr_layer::{
Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
LayerSurfaceCachedState,
},
},
};
use indexmap::IndexSet;
use wayland_server::protocol::wl_surface::WlSurface;
use std::{
cell::{RefCell, RefMut},
collections::HashSet,
hash::{Hash, Hasher},
rc::Rc,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, Weak,
},
};
// TODO: Should this be a macro?
static LAYER_ID: AtomicUsize = AtomicUsize::new(0);
lazy_static::lazy_static! {
static ref LAYER_IDS: Mutex<HashSet<usize>> = Mutex::new(HashSet::new());
}
fn next_layer_id() -> usize {
let mut ids = LAYER_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 = LAYER_ID.fetch_add(1, Ordering::SeqCst);
while ids.iter().any(|k| *k == id) {
id = LAYER_ID.fetch_add(1, Ordering::SeqCst);
}
ids.insert(id);
id
}
#[derive(Debug)]
pub struct LayerMap {
layers: IndexSet<LayerSurface>,
output: Weak<(Mutex<OutputInner>, wayland_server::UserDataMap)>,
zone: Rectangle<i32, Logical>,
}
pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> {
let userdata = o.user_data();
let weak_output = Arc::downgrade(&o.inner);
userdata.insert_if_missing(|| {
RefCell::new(LayerMap {
layers: IndexSet::new(),
output: weak_output,
zone: Rectangle::from_loc_and_size(
(0, 0),
o.current_mode()
.map(|mode| mode.size.to_logical(o.current_scale()))
.unwrap_or((0, 0).into()),
),
})
});
userdata.get::<RefCell<LayerMap>>().unwrap().borrow_mut()
}
#[derive(Debug, thiserror::Error)]
pub enum LayerError {
#[error("Layer is already mapped to a different map")]
AlreadyMapped,
}
impl LayerMap {
pub fn map_layer(&mut self, layer: &LayerSurface) -> Result<(), LayerError> {
if !self.layers.contains(layer) {
if layer
.0
.userdata
.get::<LayerUserdata>()
.map(|s| s.borrow().is_some())
.unwrap_or(false)
{
return Err(LayerError::AlreadyMapped);
}
self.layers.insert(layer.clone());
self.arrange();
}
Ok(())
}
pub fn unmap_layer(&mut self, layer: &LayerSurface) {
if self.layers.shift_remove(layer) {
let _ = layer.user_data().get::<LayerUserdata>().take();
self.arrange();
}
}
pub fn non_exclusive_zone(&self) -> Rectangle<i32, Logical> {
self.zone
}
pub fn layer_geometry(&self, layer: &LayerSurface) -> Rectangle<i32, Logical> {
let mut bbox = layer.bbox_with_popups();
let state = layer_state(layer);
bbox.loc += state.location;
bbox
}
pub fn layer_under(&self, layer: WlrLayer, point: Point<f64, Logical>) -> Option<&LayerSurface> {
self.layers_on(layer).rev().find(|l| {
let bbox = self.layer_geometry(l);
bbox.to_f64().contains(point)
})
}
pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LayerSurface> {
self.layers.iter()
}
pub fn layers_on(&self, layer: WlrLayer) -> impl DoubleEndedIterator<Item = &LayerSurface> {
self.layers
.iter()
.filter(move |l| l.layer().map(|l| l == layer).unwrap_or(false))
}
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<&LayerSurface> {
if !surface.as_ref().is_alive() {
return None;
}
self.layers
.iter()
.find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false))
}
pub fn arrange(&mut self) {
if let Some(output) = self.output() {
let output_rect = Rectangle::from_loc_and_size(
(0, 0),
output
.current_mode()
.map(|mode| mode.size.to_logical(output.current_scale()))
.unwrap_or((0, 0).into()),
);
let mut zone = output_rect.clone();
slog::debug!(
crate::slog_or_fallback(None),
"Arranging layers into {:?}",
output_rect.size
);
for layer in self.layers.iter() {
let surface = if let Some(surface) = layer.get_surface() {
surface
} else {
continue;
};
let data = with_states(surface, |states| {
*states.cached_state.current::<LayerSurfaceCachedState>()
})
.unwrap();
let source = match data.exclusive_zone {
ExclusiveZone::Neutral | ExclusiveZone::Exclusive(_) => &zone,
ExclusiveZone::DontCare => &output_rect,
};
let mut size = data.size;
if size.w == 0 {
size.w = source.size.w / 2;
}
if size.h == 0 {
size.h = source.size.h / 2;
}
if data.anchor.anchored_horizontally() {
size.w = source.size.w;
}
if data.anchor.anchored_vertically() {
size.h = source.size.h;
}
let x = if data.anchor.contains(Anchor::LEFT) {
source.loc.x + data.margin.left
} else if data.anchor.contains(Anchor::RIGHT) {
source.loc.x + (source.size.w - size.w) - data.margin.right
} else {
source.loc.x + ((source.size.w / 2) - (size.w / 2))
};
let y = if data.anchor.contains(Anchor::TOP) {
source.loc.y + data.margin.top
} else if data.anchor.contains(Anchor::BOTTOM) {
source.loc.y + (source.size.h - size.h) - data.margin.bottom
} else {
source.loc.y + ((source.size.h / 2) - (size.h / 2))
};
let location: Point<i32, Logical> = (x, y).into();
if let ExclusiveZone::Exclusive(amount) = data.exclusive_zone {
match data.anchor {
x if x.contains(Anchor::LEFT) && !x.contains(Anchor::RIGHT) => {
zone.loc.x += amount as i32 + data.margin.left + data.margin.right
}
x if x.contains(Anchor::TOP) && !x.contains(Anchor::BOTTOM) => {
zone.loc.y += amount as i32 + data.margin.top + data.margin.bottom
}
x if x.contains(Anchor::RIGHT) && !x.contains(Anchor::LEFT) => {
zone.size.w -= amount as i32 + data.margin.left + data.margin.right
}
x if x.contains(Anchor::BOTTOM) && !x.contains(Anchor::TOP) => {
zone.size.h -= amount as i32 + data.margin.top + data.margin.top
}
_ => {}
}
}
slog::debug!(
crate::slog_or_fallback(None),
"Setting layer to pos {:?} and size {:?}",
location,
size
);
if layer
.0
.surface
.with_pending_state(|state| {
state.size.replace(size).map(|old| old != size).unwrap_or(true)
})
.unwrap()
{
layer.0.surface.send_configure();
}
layer_state(&layer).location = location;
}
slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone);
self.zone = zone;
}
}
fn output(&self) -> Option<Output> {
self.output.upgrade().map(|inner| Output { inner })
}
pub fn cleanup(&mut self) {
self.layers.retain(|layer| layer.alive())
}
}
#[derive(Debug, Default)]
pub(super) struct LayerState {
location: Point<i32, Logical>,
}
type LayerUserdata = RefCell<Option<LayerState>>;
fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> {
let userdata = layer.user_data();
userdata.insert_if_missing(LayerUserdata::default);
RefMut::map(userdata.get::<LayerUserdata>().unwrap().borrow_mut(), |opt| {
if opt.is_none() {
*opt = Some(LayerState::default());
}
opt.as_mut().unwrap()
})
}
#[derive(Debug, Clone)]
pub struct LayerSurface(pub(crate) Rc<LayerSurfaceInner>);
impl PartialEq for LayerSurface {
fn eq(&self, other: &Self) -> bool {
self.0.id == other.0.id
}
}
impl Eq for LayerSurface {}
impl Hash for LayerSurface {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.id.hash(state);
}
}
#[derive(Debug)]
pub struct LayerSurfaceInner {
pub(crate) id: usize,
surface: WlrLayerSurface,
namespace: String,
userdata: UserDataMap,
}
impl Drop for LayerSurfaceInner {
fn drop(&mut self) {
LAYER_IDS.lock().unwrap().remove(&self.id);
}
}
impl LayerSurface {
pub fn new(surface: WlrLayerSurface, namespace: String) -> LayerSurface {
LayerSurface(Rc::new(LayerSurfaceInner {
id: next_layer_id(),
surface,
namespace,
userdata: UserDataMap::new(),
}))
}
pub fn alive(&self) -> bool {
self.0.surface.alive()
}
pub fn layer_surface(&self) -> &WlrLayerSurface {
&self.0.surface
}
pub fn get_surface(&self) -> Option<&WlSurface> {
self.0.surface.get_surface()
}
pub fn cached_state(&self) -> Option<LayerSurfaceCachedState> {
self.0.surface.get_surface().map(|surface| {
with_states(surface, |states| {
*states.cached_state.current::<LayerSurfaceCachedState>()
})
.unwrap()
})
}
pub fn can_receive_keyboard_focus(&self) -> bool {
self.0
.surface
.get_surface()
.map(|surface| {
with_states(surface, |states| {
match states
.cached_state
.current::<LayerSurfaceCachedState>()
.keyboard_interactivity
{
KeyboardInteractivity::Exclusive | KeyboardInteractivity::OnDemand => true,
KeyboardInteractivity::None => false,
}
})
.unwrap()
})
.unwrap_or(false)
}
pub fn layer(&self) -> Option<WlrLayer> {
self.0.surface.get_surface().map(|surface| {
with_states(surface, |states| {
states.cached_state.current::<LayerSurfaceCachedState>().layer
})
.unwrap()
})
}
pub fn namespace(&self) -> &str {
&self.0.namespace
}
/// 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> {
if let Some(surface) = self.0.surface.get_surface() {
bbox_from_surface_tree(surface, (0, 0))
} else {
Rectangle::from_loc_and_size((0, 0), (0, 0))
}
}
pub fn bbox_with_popups(&self) -> Rectangle<i32, Logical> {
let mut bounding_box = self.bbox();
if let Some(surface) = self.0.surface.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location));
}
}
}
bounding_box
}
/// 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<(WlSurface, Point<i32, Logical>)> {
if let Some(surface) = self.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(result) = popup
.get_surface()
.and_then(|surface| under_from_surface_tree(surface, point, location))
{
return Some(result);
}
}
under_from_surface_tree(surface, point, (0, 0))
} else {
None
}
}
/// Damage of all the surfaces of this layer
pub(super) fn accumulated_damage(&self) -> Vec<Rectangle<i32, Logical>> {
let mut damage = Vec::new();
if let Some(surface) = self.get_surface() {
damage.extend(damage_from_surface_tree(surface, (0, 0)));
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let popup_damage = damage_from_surface_tree(surface, location);
damage.extend(popup_damage);
}
}
}
damage
}
/// 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.surface.get_surface() {
send_frames_surface_tree(wl_surface, time)
}
}
pub fn user_data(&self) -> &UserDataMap {
&self.0.userdata
}
}
pub fn draw_layer<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
layer: &LayerSurface,
scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<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,
{
if let Some(surface) = layer.get_surface() {
draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?;
for (popup, p_location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let damage = damage
.iter()
.cloned()
.map(|mut geo| {
geo.loc -= p_location;
geo
})
.collect::<Vec<_>>();
draw_surface_tree(
renderer,
frame,
surface,
scale,
location + p_location,
&damage,
log,
)?;
}
}
}
Ok(())
}

View File

@ -1,11 +1,13 @@
// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings
#![allow(missing_docs, clippy::all)]
mod layer;
mod output;
mod popup;
mod space;
pub mod utils;
mod window;
pub use self::layer::*;
pub use self::popup::*;
pub use self::space::*;
pub use self::window::*;

View File

@ -13,6 +13,7 @@ use std::{
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(super) enum ToplevelId {
Xdg(usize),
Layer(usize),
}
impl ToplevelId {
@ -22,6 +23,13 @@ impl ToplevelId {
_ => false,
}
}
pub fn is_layer(&self) -> bool {
match self {
ToplevelId::Layer(_) => true,
_ => false,
}
}
}
#[derive(Clone, Default)]

View File

@ -1,11 +1,12 @@
use super::{draw_window, Window};
use crate::{
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform},
desktop::output::*,
desktop::{layer::*, output::*},
utils::{Logical, Point, Rectangle},
wayland::{
compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
output::Output,
shell::wlr_layer::Layer as WlrLayer,
},
};
use indexmap::{IndexMap, IndexSet};
@ -56,6 +57,20 @@ fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> {
})
}
#[derive(Default)]
struct LayerState {
drawn: bool,
}
type LayerUserdata = RefCell<HashMap<usize, LayerState>>;
fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> {
let userdata = l.user_data();
userdata.insert_if_missing(LayerUserdata::default);
RefMut::map(userdata.get::<LayerUserdata>().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 {
@ -158,6 +173,16 @@ impl Space {
.find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false))
}
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<LayerSurface> {
if !surface.as_ref().is_alive() {
return None;
}
self.outputs.iter().find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(surface).cloned()
})
}
pub fn window_geometry(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
if !self.windows.contains(w) {
return None;
@ -389,6 +414,7 @@ impl Space {
.to_logical(state.render_scale)
.to_i32_round();
let output_geo = Rectangle::from_loc_and_size(state.location, output_size);
let layer_map = layer_map_for_output(output);
// This will hold all the damage we need for this rendering step
let mut damage = Vec::<Rectangle<i32, Logical>>::new();
@ -397,7 +423,9 @@ impl Space {
.last_state
.iter()
.filter_map(|(id, w)| {
if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) {
if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id)
&& !layer_map.layers().any(|l| ToplevelId::Layer(l.0.id) == *id)
{
Some(*w)
} else {
None
@ -428,6 +456,23 @@ impl Space {
}));
}
}
for layer in layer_map.layers() {
let geo = layer_map.layer_geometry(layer);
let old_geo = state.last_state.get(&ToplevelId::Layer(layer.0.id)).cloned();
// layer moved or resized
if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) {
// Add damage for the old position of the layer
damage.push(old_geo.unwrap());
damage.push(geo);
} else {
let location = geo.loc;
damage.extend(layer.accumulated_damage().into_iter().map(|mut rect| {
rect.loc += location;
rect
}));
}
}
// That is all completely new damage, which we need to store for subsequent renders
let new_damage = damage.clone();
@ -485,7 +530,39 @@ impl Space {
.collect::<Vec<_>>(),
)?;
// Then re-draw all window overlapping with a damage rect.
// Then re-draw all windows & layers overlapping with a damage rect.
for layer in layer_map
.layers_on(WlrLayer::Background)
.chain(layer_map.layers_on(WlrLayer::Bottom))
{
let lgeo = layer_map.layer_geometry(layer);
if damage.iter().any(|geo| lgeo.overlaps(*geo)) {
let layer_damage = damage
.iter()
.filter(|geo| geo.overlaps(lgeo))
.map(|geo| geo.intersection(lgeo))
.map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size))
.collect::<Vec<_>>();
slog::trace!(
self.logger,
"Rendering layer at {:?} with damage {:#?}",
lgeo,
damage
);
draw_layer(
renderer,
frame,
layer,
state.render_scale,
lgeo.loc,
&layer_damage,
&self.logger,
)?;
layer_state(self.id, layer).drawn = true;
}
}
for window in self.windows.iter() {
let wgeo = window_rect_with_popups(window, &self.id);
let mut loc = window_loc(window, &self.id);
@ -516,6 +593,37 @@ impl Space {
}
}
for layer in layer_map
.layers_on(WlrLayer::Top)
.chain(layer_map.layers_on(WlrLayer::Overlay))
{
let lgeo = layer_map.layer_geometry(layer);
if damage.iter().any(|geo| lgeo.overlaps(*geo)) {
let layer_damage = damage
.iter()
.filter(|geo| geo.overlaps(lgeo))
.map(|geo| geo.intersection(lgeo))
.map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size))
.collect::<Vec<_>>();
slog::trace!(
self.logger,
"Rendering layer at {:?} with damage {:#?}",
lgeo,
damage
);
draw_layer(
renderer,
frame,
layer,
state.render_scale,
lgeo.loc,
&layer_damage,
&self.logger,
)?;
layer_state(self.id, layer).drawn = true;
}
}
Result::<(), R::Error>::Ok(())
},
) {
@ -534,6 +642,10 @@ impl Space {
let wgeo = window_rect_with_popups(window, &self.id);
(ToplevelId::Xdg(window.0.id), wgeo)
})
.chain(layer_map.layers().map(|layer| {
let lgeo = layer_map.layer_geometry(layer);
(ToplevelId::Layer(layer.0.id), lgeo)
}))
.collect();
state.old_damage.push_front(new_damage);
@ -549,6 +661,18 @@ impl Space {
}) {
window.send_frame(time);
}
for output in self.outputs.iter() {
let map = layer_map_for_output(output);
for layer in map.layers().filter(|l| {
all || {
let mut state = layer_state(self.id, l);
std::mem::replace(&mut state.drawn, false)
}
}) {
layer.send_frame(time);
}
}
}
}

View File

@ -50,7 +50,7 @@ impl Default for Layer {
/// - some applications are not interested in keyboard events
/// and not allowing them to be focused can improve the desktop experience
/// - some applications will want to take exclusive keyboard focus.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyboardInteractivity {
/// This value indicates that this surface is not interested in keyboard events
/// and the compositor should never assign it the keyboard focus.