desktop: layer-shell support
This commit is contained in:
parent
cea88fdde0
commit
3b39d780fe
|
@ -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(())
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings
|
// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings
|
||||||
#![allow(missing_docs, clippy::all)]
|
#![allow(missing_docs, clippy::all)]
|
||||||
|
mod layer;
|
||||||
mod output;
|
mod output;
|
||||||
mod popup;
|
mod popup;
|
||||||
mod space;
|
mod space;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
pub use self::layer::*;
|
||||||
pub use self::popup::*;
|
pub use self::popup::*;
|
||||||
pub use self::space::*;
|
pub use self::space::*;
|
||||||
pub use self::window::*;
|
pub use self::window::*;
|
||||||
|
|
|
@ -13,6 +13,7 @@ use std::{
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub(super) enum ToplevelId {
|
pub(super) enum ToplevelId {
|
||||||
Xdg(usize),
|
Xdg(usize),
|
||||||
|
Layer(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelId {
|
impl ToplevelId {
|
||||||
|
@ -22,6 +23,13 @@ impl ToplevelId {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_layer(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ToplevelId::Layer(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use super::{draw_window, Window};
|
use super::{draw_window, Window};
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform},
|
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform},
|
||||||
desktop::output::*,
|
desktop::{layer::*, output::*},
|
||||||
utils::{Logical, Point, Rectangle},
|
utils::{Logical, Point, Rectangle},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
|
compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
shell::wlr_layer::Layer as WlrLayer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use indexmap::{IndexMap, IndexSet};
|
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?
|
// TODO: Maybe replace UnmanagedResource if nothing else comes up?
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum SpaceError {
|
pub enum SpaceError {
|
||||||
|
@ -158,6 +173,16 @@ impl Space {
|
||||||
.find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false))
|
.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>> {
|
pub fn window_geometry(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
|
||||||
if !self.windows.contains(w) {
|
if !self.windows.contains(w) {
|
||||||
return None;
|
return None;
|
||||||
|
@ -389,6 +414,7 @@ impl Space {
|
||||||
.to_logical(state.render_scale)
|
.to_logical(state.render_scale)
|
||||||
.to_i32_round();
|
.to_i32_round();
|
||||||
let output_geo = Rectangle::from_loc_and_size(state.location, output_size);
|
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
|
// This will hold all the damage we need for this rendering step
|
||||||
let mut damage = Vec::<Rectangle<i32, Logical>>::new();
|
let mut damage = Vec::<Rectangle<i32, Logical>>::new();
|
||||||
|
@ -397,7 +423,9 @@ impl Space {
|
||||||
.last_state
|
.last_state
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(id, w)| {
|
.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)
|
Some(*w)
|
||||||
} else {
|
} else {
|
||||||
None
|
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
|
// That is all completely new damage, which we need to store for subsequent renders
|
||||||
let new_damage = damage.clone();
|
let new_damage = damage.clone();
|
||||||
|
@ -485,7 +530,39 @@ impl Space {
|
||||||
.collect::<Vec<_>>(),
|
.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() {
|
for window in self.windows.iter() {
|
||||||
let wgeo = window_rect_with_popups(window, &self.id);
|
let wgeo = window_rect_with_popups(window, &self.id);
|
||||||
let mut loc = window_loc(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(())
|
Result::<(), R::Error>::Ok(())
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -534,6 +642,10 @@ impl Space {
|
||||||
let wgeo = window_rect_with_popups(window, &self.id);
|
let wgeo = window_rect_with_popups(window, &self.id);
|
||||||
(ToplevelId::Xdg(window.0.id), wgeo)
|
(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();
|
.collect();
|
||||||
state.old_damage.push_front(new_damage);
|
state.old_damage.push_front(new_damage);
|
||||||
|
|
||||||
|
@ -549,6 +661,18 @@ impl Space {
|
||||||
}) {
|
}) {
|
||||||
window.send_frame(time);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ impl Default for Layer {
|
||||||
/// - some applications are not interested in keyboard events
|
/// - some applications are not interested in keyboard events
|
||||||
/// and not allowing them to be focused can improve the desktop experience
|
/// and not allowing them to be focused can improve the desktop experience
|
||||||
/// - some applications will want to take exclusive keyboard focus.
|
/// - some applications will want to take exclusive keyboard focus.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum KeyboardInteractivity {
|
pub enum KeyboardInteractivity {
|
||||||
/// This value indicates that this surface is not interested in keyboard events
|
/// This value indicates that this surface is not interested in keyboard events
|
||||||
/// and the compositor should never assign it the keyboard focus.
|
/// and the compositor should never assign it the keyboard focus.
|
||||||
|
|
Loading…
Reference in New Issue