🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
//! This module contains the [`Space`] helper class as well has related
//! rendering helpers to add custom elements or different clients to a space.
use crate::{
backend::renderer::{
damage::{Error as OutputDamageTrackerError, OutputDamageTracker, RenderOutputResult},
element::{AsRenderElements, RenderElement, Wrap},
Color32F, Renderer, Texture,
},
output::{Output, OutputModeSource, OutputNoMode},
utils::{IsAlive, Logical, Point, Rectangle, Scale, Transform},
};
#[cfg(feature = "wayland_frontend")]
use crate::{
backend::renderer::{element::surface::WaylandSurfaceRenderElement, ImportAll},
desktop::{layer_map_for_output, LayerSurface, WindowSurfaceType},
wayland::shell::wlr_layer::Layer,
};
use std::{collections::HashMap, fmt};
use tracing::{debug, debug_span, instrument};
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::wl_surface::WlSurface;
mod element;
mod output;
mod utils;
#[cfg(feature = "wayland_frontend")]
pub(crate) mod wayland;
pub use self::element::*;
use self::output::*;
pub use self::utils::*;
crate::utils::ids::id_gen!(space_id);
#[derive(Debug)]
struct InnerElement<E> {
element: E,
location: Point<i32, Logical>,
outputs: HashMap<Output, Rectangle<i32, Logical>>,
}
/// Represents two dimensional plane to map windows and outputs upon.
///
/// Space is generic over the types of elements mapped onto it.
/// The simplest usecase is a `Space<Window>`, but other types can be used
/// by implementing [`SpaceElement`]. Multiple types might be quickly aggregated into
/// an enum by using the [`space_elements!`]-macro.
#[derive(Debug)]
pub struct Space<E: SpaceElement> {
pub(super) id: usize,
// in z-order, back to front
elements: Vec<InnerElement<E>>,
outputs: Vec<Output>,
span: tracing::Span,
}
impl<E: SpaceElement> PartialEq for Space<E> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<E: SpaceElement> Drop for Space<E> {
#[inline]
fn drop(&mut self) {
space_id::remove(self.id);
}
}
impl<E: SpaceElement> Default for Space<E> {
#[inline]
fn default() -> Self {
let id = space_id::next();
let span = debug_span!("desktop_space", id);
Self {
id,
elements: Default::default(),
outputs: Default::default(),
span,
}
}
}
impl<E: SpaceElement + PartialEq> Space<E> {
/// Gets the id of this space
pub fn id(&self) -> usize {
self.id
}
/// Map a [`SpaceElement`] and move it to top of the stack
///
/// This can safely be called on an already mapped window
/// to update its location inside the space.
///
/// If activate is true it will set the new windows state
/// to be activate and removes that state from every
/// other mapped window.
pub fn map_element<P>(&mut self, element: E, location: P, activate: bool)
where
P: Into<Point<i32, Logical>>,
{
#[allow(clippy::mutable_key_type)]
let outputs = if let Some(pos) = self.elements.iter().position(|inner| inner.element == element) {
self.elements.remove(pos).outputs
} else {
HashMap::new()
};
let inner = InnerElement {
element,
location: location.into(),
outputs,
};
self.insert_elem(inner, activate);
}
/// Moves an already mapped [`SpaceElement`] to top of the stack
///
/// This function does nothing for unmapped windows.
///
/// If activate is true it will set the new windows state
/// to be activate and removes that state from every
/// other mapped window.
pub fn raise_element(&mut self, element: &E, activate: bool) {
if let Some(pos) = self.elements.iter().position(|inner| &inner.element == element) {
let inner = self.elements.remove(pos);
self.insert_elem(inner, activate);
}
}
fn insert_elem(&mut self, elem: InnerElement<E>, activate: bool) {
if activate {
elem.element.set_activate(true);
for e in self.elements.iter() {
e.element.set_activate(false);
}
}
self.elements.push(elem);
self.elements
.sort_by(|e1, e2| e1.element.z_index().cmp(&e2.element.z_index()));
}
/// Unmap a [`SpaceElement`] from this space.
///
/// This function does nothing for already unmapped windows
pub fn unmap_elem(&mut self, element: &E) {
if let Some(pos) = self.elements.iter().position(|inner| &inner.element == element) {
let elem = self.elements.remove(pos);
for output in elem.outputs.keys() {
elem.element.output_leave(output);
}
}
}
/// Iterate elements in z-order back to front
pub fn elements(&self) -> impl DoubleEndedIterator<Item = &E> + ExactSizeIterator {
self.elements.iter().map(|e| &e.element)
}
/// Iterate elements on a specific output in z-order back to front
pub fn elements_for_output<'output>(
&'output self,
output: &'output Output,
) -> impl DoubleEndedIterator<Item = &'output E> {
self.elements
.iter()
.filter(|e| e.outputs.contains_key(output))
.map(|e| &e.element)
}
/// Finds the topmost element under this point if any and returns it
/// together with the location of this element relative to this space.
///
/// This is equivalent to iterating the elements in the space from
/// top to bottom and testing if the point is within the elements
/// input region and returning the first matching one.
///
/// Note that [`SpaceElement::is_in_input_region`] expects the point
/// to be relative to the elements origin.
pub fn element_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> Option<(&E, Point<i32, Logical>)> {
let point = point.into();
self.elements
.iter()
.rev()
.filter(|e| e.bbox().to_f64().contains(point))
.find_map(|e| {
// we need to offset the point to the location where the surface is actually drawn
let render_location = e.render_location();
if e.element.is_in_input_region(&(point - render_location.to_f64())) {
Some((&e.element, render_location))
} else {
None
}
})
}
/// Get a reference to the outputs under a given point
pub fn output_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> impl Iterator<Item = &Output> {
let point = point.into();
self.outputs.iter().rev().filter(move |o| {
let bbox = self.output_geometry(o);
bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false)
})
}
/// Returns the layer surface matching a given surface, if any
///
/// `surface_type` can be used to limit the types of surfaces queried for equality.
#[cfg(feature = "wayland_frontend")]
pub fn layer_for_surface(
&self,
surface: &WlSurface,
surface_type: WindowSurfaceType,
) -> Option<LayerSurface> {
self.outputs.iter().find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(surface, surface_type).cloned()
})
}
/// Returns the location of a [`SpaceElement`] inside the Space.
pub fn element_location(&self, elem: &E) -> Option<Point<i32, Logical>> {
self.elements
.iter()
.find(|e| &e.element == elem)
.map(|e| e.location)
}
/// Returns the bounding box of a [`SpaceElement`] including its relative position inside the Space.
pub fn element_bbox(&self, elem: &E) -> Option<Rectangle<i32, Logical>> {
self.elements
.iter()
.find(|e| &e.element == elem)
.map(|e| e.bbox())
}
/// Returns the geometry of a [`SpaceElement`] including its relative position inside the Space.
///
/// This area is usually defined as the contents of the window, excluding decorations.
pub fn element_geometry(&self, elem: &E) -> Option<Rectangle<i32, Logical>> {
self.elements
.iter()
.find(|e| &e.element == elem)
.map(|e| e.geometry())
}
/// Maps an [`Output`] inside the space.
///
/// Can be safely called on an already mapped
/// [`Output`] to update its location.
///
/// *Note:* Remapping an output does reset it's damage memory.
pub fn map_output<P: Into<Point<i32, Logical>>>(&mut self, output: &Output, location: P) {
let location = location.into();
set_output_location(self.id, output, location);
if !self.outputs.contains(output) {
debug!(parent: &self.span, output = output.name(), "Mapping output at {:?}", location);
self.outputs.push(output.clone());
}
}
/// Iterate over all mapped [`Output`]s of this space.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.outputs.iter()
}
/// Unmap an [`Output`] from this space.
///
/// Does nothing if the output was not previously mapped.
pub fn unmap_output(&mut self, output: &Output) {
if !self.outputs.contains(output) {
return;
}
debug!(parent: &self.span, output = output.name(), "Unmapping output");
set_output_location(self.id, output, None);
self.outputs.retain(|o| o != output);
}
/// Returns the geometry of the output including it's relative position inside the space.
///
/// The size is matching the amount of logical pixels of the space visible on the output
/// given its current mode and scale.
pub fn output_geometry(&self, o: &Output) -> Option<Rectangle<i32, Logical>> {
if !self.outputs.contains(o) {
return None;
}
let transform: Transform = o.current_transform();
let location = output_location(self.id, o);
o.current_mode().map(|mode| {
Rectangle::new(
location,
transform
.transform_size(mode.size)
.to_f64()
.to_logical(o.current_scale().fractional_scale())
.to_i32_ceil(),
)
})
}
/// Returns all [`Output`]s a [`SpaceElement`] overlaps with.
pub fn outputs_for_element(&self, elem: &E) -> Vec<Output> {
if !self.elements.iter().any(|e| &e.element == elem) {
return Vec::new();
}
self.elements
.iter()
.find(|e| &e.element == elem)
.into_iter()
.flat_map(|e| &e.outputs)
.map(|(o, _)| o)
.cloned()
.collect()
}
/// Refresh some internal values and update client state,
/// meaning this will handle output enter and leave events
/// for mapped outputs and windows based on their position.
///
/// Needs to be called periodically, at best before every
/// wayland socket flush.
#[profiling::function]
pub fn refresh(&mut self) {
self.elements.retain(|e| e.alive());
let outputs = self
.outputs
.iter()
.cloned()
.map(|o| {
let geo = self.output_geometry(&o).unwrap_or_else(Rectangle::zero);
(o, geo)
})
.collect::<Vec<_>>();
for e in &mut self.elements {
let bbox = e.bbox();
for (output, output_geometry) in &outputs {
// Check if the bounding box of the toplevel intersects with the output
if let Some(mut overlap) = output_geometry.intersection(bbox) {
// output_enter expects the overlap to be relative to the element
overlap.loc -= bbox.loc;
let old = e.outputs.insert(output.clone(), overlap);
if old.is_none() || matches!(old, Some(old_overlap) if old_overlap != overlap) {
e.element.output_enter(output, overlap);
}
} else if e.outputs.remove(output).is_some() {
e.element.output_leave(output);
}
}
e.outputs.retain(|output, _| {
if !outputs.iter().any(|(o, _)| o == output) {
e.element.output_leave(output);
false
} else {
true
}
});
}
self.elements.iter().for_each(|e| e.element.refresh());
for (output, _) in outputs {
output.cleanup();
}
}
/// Retrieve the render elements for a given region of the space.
///
/// *Note:* Because this is not rendering a specific output,
/// this will not contain layer surfaces.
/// Use [`Space::render_elements_for_output`], if you care about this.
#[instrument(level = "trace", skip(self, renderer, scale), parent = &self.span)]
#[profiling::function]
pub fn render_elements_for_region<'a, R: Renderer, S: Into<Scale<f64>>>(
&'a self,
renderer: &mut R,
region: &Rectangle<i32, Logical>,
scale: S,
alpha: f32,
) -> Vec<<E as AsRenderElements<R>>::RenderElement>
where
R::TextureId: Texture + 'static,
E: AsRenderElements<R>,
<E as AsRenderElements<R>>::RenderElement: 'a,
{
let scale = scale.into();
self.elements
.iter()
.rev()
.filter(|e| {
let geometry = e.bbox();
region.overlaps(geometry)
})
.flat_map(|e| {
let location = e.render_location() - region.loc;
e.element
.render_elements::<<E as AsRenderElements<R>>::RenderElement>(
renderer,
location.to_physical_precise_round(scale),
scale,
alpha,
)
})
.collect::<Vec<_>>()
}
/// Retrieve the render elements for an output
#[instrument(level = "trace", skip(self, renderer), parent = &self.span)]
#[profiling::function]
pub fn render_elements_for_output<
'a,
#[cfg(feature = "wayland_frontend")] R: Renderer + ImportAll,
#[cfg(not(feature = "wayland_frontend"))] R: Renderer,
>(
&'a self,
renderer: &mut R,
output: &Output,
alpha: f32,
) -> Result<Vec<SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>>, OutputError>
where
R::TextureId: Clone + Texture + 'static,
E: AsRenderElements<R>,
<E as AsRenderElements<R>>::RenderElement: 'a,
SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>:
From<Wrap<<E as AsRenderElements<R>>::RenderElement>>,
{
if !self.outputs.contains(output) {
return Err(OutputError::Unmapped);
}
let output_scale = output.current_scale().fractional_scale();
// The unwrap is safe or we would have returned OutputError::Unmapped already
let output_geo = self.output_geometry(output).unwrap();
let mut space_elements: Vec<SpaceElements<'a, E>> =
self.elements.iter().rev().map(SpaceElements::Element).collect();
#[cfg(feature = "wayland_frontend")]
{
let layer_map = layer_map_for_output(output);
space_elements.extend(layer_map.layers().rev().cloned().map(|l| SpaceElements::Layer {
surface: l,
output_location: output_geo.loc,
}));
}
space_elements.sort_by_key(|e| std::cmp::Reverse(e.z_index()));
Ok(space_elements
.into_iter()
.filter(|e| {
let geometry = e.bbox();
output_geo.overlaps(geometry)
})
.flat_map(|e| {
let location = e.render_location() - output_geo.loc;
e.render_elements::<SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>>(
renderer,
location.to_physical_precise_round(output_scale),
Scale::from(output_scale),
alpha,
)
})
.collect::<Vec<_>>())
}
}
/// Errors thrown by [`Space::elements_for_output`]
#[derive(thiserror::Error, Debug)]
pub enum OutputError {
/// The given [`Output`] has no set mode
#[error(transparent)]
NoMode(#[from] OutputNoMode),
/// The given [`Output`] is not mapped to this [`Space`].
#[error("Output was not mapped to this space")]
Unmapped,
}
impl<E: IsAlive> IsAlive for InnerElement<E> {
#[inline]
fn alive(&self) -> bool {
self.element.alive()
}
}
impl<E: SpaceElement> InnerElement<E> {
// the inner geometry of the element in space coordinates
fn geometry(&self) -> Rectangle<i32, Logical> {
let mut geo = self.element.geometry();
geo.loc = self.location;
geo
}
// the bounding box of the element in space coordinates
fn bbox(&self) -> Rectangle<i32, Logical> {
let mut bbox = self.element.bbox();
bbox.loc += self.location - self.element.geometry().loc;
bbox
}
fn render_location(&self) -> Point<i32, Logical> {
self.location - self.element.geometry().loc
}
}
#[cfg(feature = "wayland_frontend")]
crate::backend::renderer::element::render_elements! {
/// Defines the render elements used internally by a [`Space`]
///
/// Use them in place of `E` in `space_render_elements` or
/// `render_output` if you do not need custom render elements
pub SpaceRenderElements<R, E> where
R: ImportAll;
/// A single wayland surface
Surface=WaylandSurfaceRenderElement<R>,
/// A single texture
Element=Wrap<E>,
}
#[cfg(not(feature = "wayland_frontend"))]
crate::backend::renderer::element::render_elements! {
/// Defines the render elements used internally by a [`Space`]
///
/// Use them in place of `E` in `space_render_elements` or
/// `render_output` if you do not need custom render elements
pub SpaceRenderElements<R, E>;
/// A single texture
Element=Wrap<E>,
}
impl<
#[cfg(feature = "wayland_frontend")] R: Renderer + ImportAll,
#[cfg(not(feature = "wayland_frontend"))] R: Renderer,
E: RenderElement<R> + std::fmt::Debug,
> std::fmt::Debug for SpaceRenderElements<R, E>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "wayland_frontend")]
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
Self::Element(arg0) => f.debug_tuple("Element").field(arg0).finish(),
Self::_GenericCatcher(_) => unreachable!(),
}
}
}
#[cfg(feature = "wayland_frontend")]
crate::backend::renderer::element::render_elements! {
OutputRenderElements<'a, R, E, C> where
R: ImportAll;
Space=SpaceRenderElements<R, E>,
Custom=&'a C,
}
#[cfg(not(feature = "wayland_frontend"))]
crate::backend::renderer::element::render_elements! {
OutputRenderElements<'a, R, E, C>;
Space=SpaceRenderElements<R, E>,
Custom=&'a C,
}
/// Get the render elements for a specific output
///
/// If multiple spaces are given their elements will be stacked
/// the same way.
///
/// *Note*: If the `wayland_frontend`-feature is enabled
/// this will include layer-shell surfaces added to this
/// outputs [`LayerMap`](crate::desktop::LayerMap).
#[instrument(level = "trace", skip(spaces, renderer))]
#[profiling::function]
pub fn space_render_elements<
'a,
#[cfg(feature = "wayland_frontend")] R: Renderer + ImportAll,
#[cfg(not(feature = "wayland_frontend"))] R: Renderer,
E: SpaceElement + PartialEq + AsRenderElements<R> + 'a,
S: IntoIterator<Item = &'a Space<E>>,
>(
renderer: &mut R,
spaces: S,
output: &Output,
alpha: f32,
) -> Result<Vec<SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>>, OutputNoMode>
where
R::TextureId: Clone + Texture + 'static,
<E as AsRenderElements<R>>::RenderElement: 'a,
SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>:
From<Wrap<<E as AsRenderElements<R>>::RenderElement>>,
{
let mut render_elements = Vec::new();
let output_scale = output.current_scale().fractional_scale();
#[cfg(feature = "wayland_frontend")]
let layer_map = layer_map_for_output(output);
#[cfg(feature = "wayland_frontend")]
let lower = {
let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map
.layers()
.rev()
.partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom));
render_elements.extend(
upper
.into_iter()
.filter_map(|surface| layer_map.layer_geometry(surface).map(|geo| (geo.loc, surface)))
.flat_map(|(loc, surface)| {
AsRenderElements::<R>::render_elements::<WaylandSurfaceRenderElement<R>>(
surface,
renderer,
loc.to_physical_precise_round(output_scale),
Scale::from(output_scale),
alpha,
)
.into_iter()
.map(SpaceRenderElements::Surface)
}),
);
lower
};
for space in spaces {
let _guard = space.span.enter();
if let Some(output_geo) = space.output_geometry(output) {
render_elements.extend(
space
.render_elements_for_region(renderer, &output_geo, output_scale, alpha)
.into_iter()
.map(|e| SpaceRenderElements::Element(Wrap::from(e))),
);
}
}
#[cfg(feature = "wayland_frontend")]
render_elements.extend(
lower
.into_iter()
.filter_map(|surface| layer_map.layer_geometry(surface).map(|geo| (geo.loc, surface)))
.flat_map(|(loc, surface)| {
AsRenderElements::<R>::render_elements::<WaylandSurfaceRenderElement<R>>(
surface,
renderer,
loc.to_physical_precise_round(output_scale),
Scale::from(output_scale),
alpha,
)
.into_iter()
.map(SpaceRenderElements::Surface)
}),
);
Ok(render_elements)
}
/// Render a output
///
/// If multiple spaces are given their elements will be stacked
/// the same way.
#[allow(clippy::too_many_arguments)]
#[profiling::function]
pub fn render_output<
'a,
'd,
#[cfg(feature = "wayland_frontend")] R: Renderer + ImportAll,
#[cfg(not(feature = "wayland_frontend"))] R: Renderer,
C: RenderElement<R>,
E: SpaceElement + PartialEq + AsRenderElements<R> + 'a,
S: IntoIterator<Item = &'a Space<E>>,
>(
output: &Output,
renderer: &mut R,
framebuffer: &mut R::Framebuffer<'_>,
alpha: f32,
age: usize,
spaces: S,
custom_elements: &'a [C],
damage_tracker: &'d mut OutputDamageTracker,
clear_color: impl Into<Color32F>,
) -> Result<RenderOutputResult<'d>, OutputDamageTrackerError<R::Error>>
where
R::TextureId: Clone + Texture + 'static,
<E as AsRenderElements<R>>::RenderElement: 'a,
SpaceRenderElements<R, <E as AsRenderElements<R>>::RenderElement>:
From<Wrap<<E as AsRenderElements<R>>::RenderElement>>,
{
if let OutputModeSource::Auto(renderer_output) = damage_tracker.mode() {
assert!(renderer_output == output);
}
let space_render_elements = space_render_elements(renderer, spaces, output, alpha)?;
let mut render_elements: Vec<OutputRenderElements<'a, R, <E as AsRenderElements<R>>::RenderElement, C>> =
Vec::with_capacity(custom_elements.len() + space_render_elements.len());
render_elements.extend(custom_elements.iter().map(OutputRenderElements::Custom));
render_elements.extend(space_render_elements.into_iter().map(OutputRenderElements::Space));
damage_tracker.render_output(renderer, framebuffer, age, &render_elements, clear_color)
}