Implement ext-background-effect protocol

This commit is contained in:
Ivan Molodetskikh
2026-04-14 07:35:11 +00:00
parent 73c0ce75d8
commit 931123f38c
12 changed files with 612 additions and 12 deletions
+2 -1
View File
@@ -1079,7 +1079,8 @@ pub struct BackgroundEffect {
/// Whether to blur the background.
///
/// - `None`: no blur
/// - `None`: blur when the window/layer requests it (e.g. through ext-background-effect
/// protocol)
/// - `Some(false)`: never blur
/// - `Some(true)`: always blur
pub blur: Option<bool>,
+123
View File
@@ -0,0 +1,123 @@
use std::sync::{Arc, Mutex};
use smithay::delegate_background_effect;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Rectangle};
use smithay::wayland::background_effect::{
self, BackgroundEffectSurfaceCachedState, ExtBackgroundEffectHandler,
};
use smithay::wayland::compositor::{
add_post_commit_hook, with_states, RegionAttributes, SurfaceData,
};
use crate::niri::State;
use crate::utils::region::region_to_non_overlapping_rects;
/// Per-surface cache for processed blur region (non-overlapping rects).
#[derive(Default)]
struct CachedBlurRegionUserData(Mutex<CachedBlurRegionInner>);
#[derive(Default)]
struct CachedBlurRegionInner {
/// Whether a region change is pending to be committed.
pending_dirty: bool,
/// Whether the region must be recomputed.
dirty: bool,
/// Whether the post-commit hook has been registered for this surface.
hook_registered: bool,
/// Cached non-overlapping rects in surface-local coordinates.
///
/// `None` means there's no blur region.
rects: Option<Arc<Vec<Rectangle<i32, Logical>>>>,
}
/// Gets the cached blur region for a surface, lazily recomputing if dirty.
pub fn get_cached_blur_region(states: &SurfaceData) -> Option<Arc<Vec<Rectangle<i32, Logical>>>> {
let cache = states
.data_map
.get_or_insert_threadsafe(CachedBlurRegionUserData::default);
let mut guard = cache.0.lock().unwrap();
if guard.dirty {
guard.dirty = false;
recompute_blur_region(states, &mut guard);
}
guard.rects.clone()
}
fn recompute_blur_region(states: &SurfaceData, inner: &mut CachedBlurRegionInner) {
let cached = &states.cached_state;
let rects = if let Some(arc) = &mut inner.rects {
if Arc::strong_count(arc) > 1 {
debug!("cloning rects due to non-unique reference");
}
arc
} else {
inner.rects.insert(Arc::new(Vec::new()))
};
let rects = Arc::make_mut(rects);
if cached.has::<BackgroundEffectSurfaceCachedState>() {
let mut guard = cached.get::<BackgroundEffectSurfaceCachedState>();
if let Some(region) = &guard.current().blur_region {
region_to_non_overlapping_rects(region, rects);
} else {
inner.rects = None;
}
return;
}
inner.rects = None;
}
fn mark_blur_region_pending_dirty(wl_surface: &WlSurface) {
let register_hook = with_states(wl_surface, |states| {
let cache = states
.data_map
.get_or_insert_threadsafe(CachedBlurRegionUserData::default);
let mut guard = cache.0.lock().unwrap();
guard.pending_dirty = true;
if guard.hook_registered {
false
} else {
guard.hook_registered = true;
true
}
});
if register_hook {
add_post_commit_hook::<State, _>(wl_surface, |_state, _dh, surface| {
with_states(surface, |states| {
if let Some(cache) = states.data_map.get::<CachedBlurRegionUserData>() {
let mut guard = cache.0.lock().unwrap();
if guard.pending_dirty {
guard.pending_dirty = false;
guard.dirty = true;
crate::render_helpers::background_effect::damage_surface(states);
}
} else {
error!("unexpected missing CachedBlurRegionUserData");
}
});
});
}
}
impl ExtBackgroundEffectHandler for State {
fn capabilities(&self) -> background_effect::Capability {
background_effect::Capability::Blur
}
fn set_blur_region(&mut self, wl_surface: WlSurface, _region: RegionAttributes) {
mark_blur_region_pending_dirty(&wl_surface);
}
fn unset_blur_region(&mut self, wl_surface: WlSurface) {
mark_blur_region_pending_dirty(&wl_surface);
}
}
delegate_background_effect!(State);
+1
View File
@@ -1,3 +1,4 @@
pub mod background_effect;
mod compositor;
mod layer_shell;
mod xdg_shell;
+5
View File
@@ -233,6 +233,8 @@ impl MappedLayer {
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
let geometry = Rectangle::new(location, self.block_out_buffer.size());
let surface_off = Point::new(0., 0.); // No geometry on layer surfaces.
let surface_anim_scale = Scale::from(1.);
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
background_effect::render_for_tile(
ctx.as_gles(),
@@ -240,9 +242,12 @@ impl MappedLayer {
self.scale,
false,
surface,
surface_off,
surface_anim_scale,
self.blur_config,
radius,
self.rules.background_effect,
should_block_out,
xray_pos,
&mut |elem| push(elem.into()),
);
+1
View File
@@ -203,6 +203,7 @@ pub trait LayoutElement {
_geometry: Rectangle<f64, Logical>,
_scale: f64,
_clip_to_geometry: bool,
_surface_anim_scale: Scale<f64>,
_radius: CornerRadius,
_xray_pos: XrayPos,
_push: &mut dyn FnMut(BackgroundEffectElement),
+2
View File
@@ -1282,11 +1282,13 @@ impl<W: LayoutElement> Tile<W> {
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
}
let surface_anim_scale = animated_window_size / window_size;
self.window.render_background_effect(
ctx.as_gles(),
area,
self.scale,
clip_to_geometry,
surface_anim_scale,
radius,
xray_pos,
&mut |elem| push(elem.into()),
+4
View File
@@ -71,6 +71,7 @@ use smithay::utils::{
ClockSource, IsAlive as _, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size,
Transform, SERIAL_COUNTER,
};
use smithay::wayland::background_effect::BackgroundEffectState;
use smithay::wayland::compositor::{
with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler,
CompositorState, HookId, SurfaceData, TraversalAction,
@@ -280,6 +281,7 @@ pub struct Niri {
pub screencopy_state: ScreencopyManagerState,
pub output_management_state: OutputManagementManagerState,
pub viewporter_state: ViewporterState,
pub background_effect_state: BackgroundEffectState,
pub xdg_foreign_state: XdgForeignState,
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
@@ -2329,6 +2331,7 @@ impl Niri {
let screencopy_state =
ScreencopyManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
let viewporter_state = ViewporterState::new::<State>(&display_handle);
let background_effect_state = BackgroundEffectState::new::<State>(&display_handle);
let xdg_foreign_state = XdgForeignState::new::<State>(&display_handle);
let is_tty = matches!(backend, Backend::Tty(_));
@@ -2512,6 +2515,7 @@ impl Niri {
output_management_state,
screencopy_state,
viewporter_state,
background_effect_state,
xdg_foreign_state,
text_input_state,
input_method_state,
+103 -8
View File
@@ -1,15 +1,18 @@
use std::sync::Mutex;
use std::sync::{Arc, Mutex};
use niri_config::CornerRadius;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Rectangle};
use smithay::utils::{Logical, Point, Rectangle, Scale};
use smithay::wayland::compositor::{with_states, SurfaceData};
use wayland_server::protocol::wl_surface::WlSurface;
use crate::handlers::background_effect::get_cached_blur_region;
use crate::niri_render_elements;
use crate::render_helpers::damage::ExtraDamage;
use crate::render_helpers::xray::{XrayElement, XrayPos};
use crate::render_helpers::RenderCtx;
use crate::utils::region::TransformedRegion;
use crate::utils::surface_geo;
#[derive(Debug)]
pub struct BackgroundEffect {
@@ -46,6 +49,10 @@ impl Options {
pub struct RenderParams {
/// Geometry of the background effect.
pub geometry: Rectangle<f64, Logical>,
/// Effect subregion, will be clipped to `geometry`.
///
/// `subregion.iter()` should return `geometry`-relative rectangles.
pub subregion: Option<TransformedRegion>,
/// Geometry and radius for clipping in the same coordinate space as `geometry`.
pub clip: Option<(Rectangle<f64, Logical>, CornerRadius)>,
/// Scale to use for rounding to physical pixels.
@@ -80,6 +87,11 @@ impl BackgroundEffect {
}
}
/// Damage the background effect, for example when a blur subregion changes.
pub fn damage(&mut self) {
self.damage.damage_all();
}
pub fn update_config(&mut self, config: niri_config::Blur) {
if self.blur_config == config {
return;
@@ -93,9 +105,17 @@ impl BackgroundEffect {
&mut self,
corner_radius: CornerRadius,
effect: niri_config::BackgroundEffect,
has_blur_region: bool,
) {
// If the surface explicitly requests a blur region, default blur to true.
let blur = if has_blur_region {
effect.blur != Some(false)
} else {
effect.blur == Some(true)
};
let mut options = Options {
blur: effect.blur == Some(true),
blur,
xray: effect.xray == Some(true),
noise: effect.noise,
saturation: effect.saturation,
@@ -171,6 +191,62 @@ impl BackgroundEffect {
}
}
fn render_params_for_tile(
geometry: Rectangle<f64, Logical>,
scale: f64,
clip_to_geometry: bool,
block_out: bool,
blur_region: Option<Arc<Vec<Rectangle<i32, Logical>>>>,
surface_geo: Rectangle<f64, Logical>,
surface_anim_scale: Scale<f64>,
) -> Option<RenderParams> {
// Effects not requested by the surface itself are drawn to match the geometry.
let mut clip = true;
let mut effect_geometry = geometry;
let mut subregion = None;
if let Some(rects) = blur_region {
if rects.is_empty() {
// Surface has a set, but empty blur region.
return None;
} else {
// If the surface itself requests the effects, apply different defaults.
clip = clip_to_geometry;
// Use geometry-shaped blur for blocked-out windows to avoid unintentionally
// leaking any surface shapes. We render those windows as geometry-shaped solid
// rectangles anyway.
if block_out {
clip = true;
} else {
let mut surface_geo = surface_geo.upscale(surface_anim_scale);
surface_geo.loc += geometry.loc;
subregion = Some(TransformedRegion {
rects,
scale: surface_anim_scale,
offset: surface_geo.loc,
});
surface_geo = surface_geo
.to_physical_precise_round(scale)
.to_logical(scale);
effect_geometry = surface_geo;
}
}
}
// This corner radius is reset to self.corner_radius in render().
let clip = clip.then_some((geometry, CornerRadius::default()));
Some(RenderParams {
geometry: effect_geometry,
subregion,
clip,
scale,
})
}
/// Per-surface background effect stored in its data map.
struct SurfaceBackgroundEffect(Mutex<BackgroundEffect>);
@@ -182,6 +258,12 @@ impl SurfaceBackgroundEffect {
}
}
pub fn damage_surface(states: &SurfaceData) {
if let Some(effect) = states.data_map.get::<SurfaceBackgroundEffect>() {
effect.0.lock().unwrap().damage();
}
}
// Silence, Clippy
// A Smithay user is talking
#[allow(clippy::too_many_arguments)]
@@ -191,9 +273,12 @@ pub fn render_for_tile(
scale: f64,
clip_to_geometry: bool,
surface: &WlSurface,
surface_off: Point<f64, Logical>,
surface_anim_scale: Scale<f64>,
blur_config: niri_config::Blur,
radius: CornerRadius,
effect: niri_config::BackgroundEffect,
should_block_out: bool,
xray_pos: XrayPos,
push: &mut dyn FnMut(BackgroundEffectElement),
) {
@@ -201,19 +286,29 @@ pub fn render_for_tile(
let background_effect = SurfaceBackgroundEffect::get(states);
let mut background_effect = background_effect.0.lock().unwrap();
let blur_region = get_cached_blur_region(states);
let has_blur_region = blur_region.as_ref().is_some_and(|r| !r.is_empty());
background_effect.update_config(blur_config);
background_effect.update_render_elements(radius, effect);
background_effect.update_render_elements(radius, effect, has_blur_region);
if !background_effect.is_visible() {
return;
}
// Effects not requested by the surface itself are drawn to match the geometry.
let _ = clip_to_geometry;
let params = RenderParams {
let mut surface_geo = surface_geo(states).unwrap_or_default().to_f64();
surface_geo.loc += surface_off;
let Some(params) = render_params_for_tile(
geometry,
clip: Some((geometry, CornerRadius::default())),
scale,
clip_to_geometry,
should_block_out,
blur_region,
surface_geo,
surface_anim_scale,
) else {
return;
};
let xray_pos = xray_pos.offset(params.geometry.loc - geometry.loc);
+29 -1
View File
@@ -10,7 +10,7 @@ use smithay::backend::renderer::gles::{
};
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
use smithay::backend::renderer::Color32F;
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Transform};
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
use crate::render_helpers::background_effect::RenderParams;
@@ -18,6 +18,7 @@ use crate::render_helpers::effect_buffer::EffectBuffer;
use crate::render_helpers::renderer::AsGlesFrame as _;
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
use crate::render_helpers::{RenderCtx, RenderTarget};
use crate::utils::region::TransformedRegion;
#[derive(Debug)]
pub struct Xray {
@@ -69,6 +70,7 @@ pub struct XrayElement {
id: Id,
geometry: Rectangle<f64, Logical>,
src: Rectangle<f64, Buffer>,
subregion: Option<TransformedRegion>,
input_to_clip_geo: Mat3,
clip_geo_size: Vec2,
corner_radius: CornerRadius,
@@ -189,6 +191,7 @@ impl Xray {
id: background.id().clone(),
geometry,
src,
subregion: params.subregion.clone(),
input_to_clip_geo,
clip_geo_size,
corner_radius,
@@ -238,6 +241,7 @@ impl Xray {
id: backdrop.id().clone(),
geometry: params.geometry,
src,
subregion: params.subregion.clone(),
input_to_clip_geo,
clip_geo_size,
corner_radius: corner_radius.scaled_by(zoom as f32),
@@ -309,6 +313,30 @@ impl RenderElement<GlesRenderer> for XrayElement {
}
};
// FIXME: avoid reallocating a fresh Vec here somehow.
let mut filtered_damage = Vec::new();
let damage = if let Some(subregion) = &self.subregion {
let src_to_geo = self.geometry.size / self.src.size;
// Compute crop in geometry coordinates.
let mut crop = src;
crop.loc -= self.src.loc;
crop = crop.upscale(src_to_geo);
let mut crop = crop.to_logical(1., Transform::Normal, &Size::default());
// Then convert to subregion coordinates.
crop.loc += self.geometry.loc;
subregion.filter_damage(crop, dst, damage, &mut filtered_damage);
if filtered_damage.is_empty() {
return Ok(());
}
&filtered_damage[..]
} else {
damage
};
let uniforms = self.program.is_some().then(|| self.compute_uniforms());
let uniforms = uniforms.as_ref().map_or(&[][..], |x| &x[..]);
+16 -1
View File
@@ -14,7 +14,9 @@ use bitflags::bitflags;
use directories::UserDirs;
use git_version::git_version;
use niri_config::{Config, OutputName};
use smithay::backend::renderer::utils::with_renderer_surface_state;
use smithay::backend::renderer::utils::{
with_renderer_surface_state, RendererSurfaceStateUserData,
};
use smithay::input::pointer::CursorIcon;
use smithay::output::{self, Output};
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
@@ -35,6 +37,7 @@ use crate::handlers::KdeDecorationsModeState;
use crate::niri::ClientState;
pub mod id;
pub mod region;
pub mod scale;
pub mod signals;
pub mod spawning;
@@ -325,6 +328,18 @@ pub fn is_laptop_panel(connector: &str) -> bool {
matches!(connector.get(..4), Some("eDP-" | "LVDS" | "DSI-"))
}
/// Returns the geometry of the surface.
///
/// Returns `None` if the surface isn't mapped.
pub fn surface_geo(states: &SurfaceData) -> Option<Rectangle<i32, Logical>> {
let data = states.data_map.get::<RendererSurfaceStateUserData>();
data.and_then(|d| d.lock().unwrap().view())
.map(|view| Rectangle {
loc: view.offset,
size: view.dst,
})
}
pub fn with_toplevel_role<T>(
toplevel: &ToplevelSurface,
f: impl FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
+320
View File
@@ -0,0 +1,320 @@
use std::cmp::{max, min};
use std::collections::BTreeSet;
use std::sync::Arc;
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale};
use smithay::wayland::compositor::{RectangleKind, RegionAttributes};
/// Helper for fractionally transforming an i32 region while preserving adjacent rects.
///
/// Naively applying floating point transforms may cause adjacent rects to go misaligned due to
/// rounding differences. This struct helps apply the transforms in such a way as to preserve
/// alignment.
#[derive(Debug, Clone)]
pub struct TransformedRegion {
/// Non-overlapping rects (usually in surface-local coordinates).
pub rects: Arc<Vec<Rectangle<i32, Logical>>>,
/// Scale to apply to each rect.
pub scale: Scale<f64>,
/// Translation to apply to each rect after scaling.
pub offset: Point<f64, Logical>,
}
impl TransformedRegion {
/// Returns an iterator over the top-left and bottom-right corners of transformed rects.
pub fn iter(&self) -> impl Iterator<Item = (Point<f64, Logical>, Point<f64, Logical>)> + '_ {
self.rects.iter().map(|r| {
// Here we start in a happy i32 world where everything lines up, and rectangle loc +
// size is exactly equal to the adjacent rectangle's loc.
//
// Unfortunately, we're about to descend to the floating point hell. And we *really*
// want adjacent rects to remain adjacent no matter what. So we'll convert our rects to
// their extremities (rather than loc and size), and operate on those. Coordinates from
// adjacent rects will undergo exactly the same floating point operations, so when
// they're ultimately rounded to physical pixels, they will remain adjacent.
let r = r.to_f64();
let mut a = r.loc;
// f64 is enough to represent this i32 addition exactly.
let mut b = r.loc + r.size.to_point();
a = a.upscale(self.scale);
b = b.upscale(self.scale);
a += self.offset;
b += self.offset;
(a, b)
})
}
/// Intersects damage with this subregion.
pub fn filter_damage(
&self,
// Same coordinate space as self.iter().
crop: Rectangle<f64, Logical>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
filtered: &mut Vec<Rectangle<i32, Physical>>,
) {
let scale = dst.size.to_f64() / crop.size;
let cs = crop.size.to_point();
for (mut a, mut b) in self.iter() {
// Convert to dst-relative.
a -= crop.loc;
b -= crop.loc;
// Intersect with crop.
let ia = Point::new(f64::max(a.x, 0.), f64::max(a.y, 0.));
let ib = Point::new(f64::min(b.x, cs.x), f64::min(b.y, cs.y));
if ib.x <= ia.x || ib.y <= ia.y {
// No intersection.
continue;
}
// Round extremities to physical pixels, ensuring that adjacent rectangles stay adjacent
// at fractional scales.
let ia = ia.to_physical_precise_round(scale);
let ib = ib.to_physical_precise_round(scale);
let r = Rectangle::from_extremities(ia, ib);
// Intersect with each damage rect.
for d in damage {
if let Some(intersection) = r.intersection(*d) {
filtered.push(intersection);
}
}
}
}
}
pub fn region_to_non_overlapping_rects(
region: &RegionAttributes,
output: &mut Vec<Rectangle<i32, Logical>>,
) {
let _span = tracy_client::span!("region_to_non_overlapping_rects");
output.clear();
// Collect all unique Y coordinates.
let ys = BTreeSet::from_iter(
region
.rects
.iter()
.flat_map(|(_, r)| [r.loc.y, r.loc.y + r.size.h]),
);
let mut ys = ys.into_iter();
let Some(mut lo) = ys.next() else {
// The region was empty.
return;
};
// Sorted list of non-overlapping [start, end) tuples.
let mut spans = Vec::<(i32, i32)>::new();
// Iterate over Y bands.
for hi in ys {
spans.clear();
'region: for (kind, r) in &region.rects {
// Skip rects that don't overlap with the Y band.
if hi <= r.loc.y || r.loc.y + r.size.h <= lo {
continue;
}
let mut x1 = r.loc.x;
let mut x2 = r.loc.x + r.size.w;
if x1 == x2 {
// Empty rect.
continue;
}
match *kind {
RectangleKind::Add => {
// Iterate over existing spans backwards.
for i in (0..spans.len()).rev() {
let (start, end) = spans[i];
// New span is to the right.
if end < x1 {
spans.insert(i + 1, (x1, x2));
continue 'region;
}
// New span is to the left.
if x2 < start {
continue;
}
// New span overlaps this span; merge them.
spans.remove(i);
x1 = min(x1, start);
x2 = max(x2, end);
}
spans.insert(0, (x1, x2));
}
RectangleKind::Subtract => {
// Iterate over existing spans backwards.
for i in (0..spans.len()).rev() {
let (start, end) = spans[i];
// Subtract span is to the right.
if end <= x1 {
continue 'region;
}
// Subtract span is to the left.
if x2 <= start {
continue;
}
// Subtract span overlaps this span.
spans.remove(i);
if x2 < end {
spans.insert(i, (x2, end));
}
if start < x1 {
spans.insert(i, (start, x1));
}
}
}
}
}
for (x1, x2) in spans.drain(..) {
output.push(Rectangle::from_extremities((x1, lo), (x2, hi)));
}
lo = hi;
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write as _;
use insta::assert_snapshot;
use proptest::prelude::*;
use smithay::utils::{Logical, Point, Rectangle, Size};
use smithay::wayland::compositor::{RectangleKind, RegionAttributes};
use super::region_to_non_overlapping_rects;
#[allow(clippy::type_complexity)]
fn check(rects: &[(RectangleKind, (i32, i32, i32, i32))]) -> String {
let region = RegionAttributes {
rects: rects
.iter()
.map(|(kind, (x1, y1, x2, y2))| {
(*kind, Rectangle::from_extremities((*x1, *y1), (*x2, *y2)))
})
.collect(),
};
let mut output = Vec::new();
region_to_non_overlapping_rects(&region, &mut output);
let mut s = String::new();
for r in &output {
let x1 = r.loc.x;
let y1 = r.loc.y;
let x2 = x1 + r.size.w;
let y2 = y1 + r.size.h;
writeln!(s, "{x1:2} {y1:2} - {x2:2} {y2:2}").unwrap();
}
s
}
#[test]
fn test_region_to_non_overlapping_rects() {
use RectangleKind::*;
// empty_region
assert_snapshot!(check(&[]), @"");
// single_rectangle
assert_snapshot!(check(&[(Add, (0, 0, 10, 10))]), @" 0 0 - 10 10");
// empty_rectangle
assert_snapshot!(check(&[(Add, (0, 0, 0, 1))]), @"");
assert_snapshot!(check(&[(Add, (0, 0, 1, 0))]), @"");
// two_non_overlapping
assert_snapshot!(
check(&[(Add, (0, 0, 5, 10)), (Add, (7, 0, 12, 10))]),
@"
0 0 - 5 10
7 0 - 12 10
"
);
// two_overlapping
assert_snapshot!(
check(&[(Add, (0, 0, 10, 10)), (Add, (5, 5, 15, 15))]),
@"
0 0 - 10 5
0 5 - 15 10
5 10 - 15 15
"
);
// subtraction
assert_snapshot!(
check(&[(Add, (0, 0, 20, 20)), (Subtract, (5, 5, 15, 15))]),
@"
0 0 - 20 5
0 5 - 5 15
15 5 - 20 15
0 15 - 20 20
"
);
// adjacent_rectangles
assert_snapshot!(
check(&[(Add, (0, 0, 10, 10)), (Add, (10, 0, 20, 10))]),
@" 0 0 - 20 10"
);
}
proptest! {
#[test]
fn non_overlapping_output(
rects in proptest::collection::vec(
(
prop_oneof![Just(RectangleKind::Add), Just(RectangleKind::Subtract)],
(0..20i32, 0..20i32, 0..20i32, 0..20i32),
),
1..10,
)
) {
let region = RegionAttributes {
rects: rects
.into_iter()
.map(|(kind, (x, y, w, h))| {
(kind, Rectangle::new(Point::new(x, y), Size::new(w, h)))
})
.collect(),
};
let mut output: Vec<Rectangle<i32, Logical>> = Vec::new();
region_to_non_overlapping_rects(&region, &mut output);
for i in 0..output.len() {
prop_assert!(!output[i].is_empty());
// Verify no pair of output rectangles overlaps.
for j in (i + 1)..output.len() {
prop_assert!(
!output[i].overlaps(output[j]),
"rectangles overlap: {:?} and {:?}",
output[i],
output[j],
);
}
}
}
}
}
+6 -1
View File
@@ -1,7 +1,7 @@
use std::cell::{Cell, Ref, RefCell};
use std::time::Duration;
use niri_config::{Color, CornerRadius, GradientInterpolation, WindowRule};
use niri_config::{Color, Config, CornerRadius, GradientInterpolation, WindowRule};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::Kind;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -689,19 +689,24 @@ impl LayoutElement for Mapped {
geometry: Rectangle<f64, Logical>,
scale: f64,
clip_to_geometry: bool,
surface_anim_scale: Scale<f64>,
radius: CornerRadius,
xray_pos: XrayPos,
push: &mut dyn FnMut(BackgroundEffectElement),
) {
let should_block_out = ctx.target.should_block_out(self.rules.block_out_from);
background_effect::render_for_tile(
ctx,
geometry,
scale,
clip_to_geometry,
self.toplevel().wl_surface(),
self.buf_loc().to_f64(),
surface_anim_scale,
self.blur_config,
radius,
self.rules.background_effect,
should_block_out,
xray_pos,
push,
);