aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-01-21 09:40:00 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-01-21 11:31:30 +0300
commitacd4cb51aa0e013bfec14444ee48a01c60ebaf8a (patch)
tree8530bbe3697d4af8a6e6391e5be5997a4b68c6f4
parent5ebcae997e672dcf0b9c73da383fa40f55a85fcc (diff)
downloadniri-acd4cb51aa0e013bfec14444ee48a01c60ebaf8a.tar.gz
niri-acd4cb51aa0e013bfec14444ee48a01c60ebaf8a.tar.bz2
niri-acd4cb51aa0e013bfec14444ee48a01c60ebaf8a.zip
Implement shadows for layer surfaces
-rw-r--r--niri-config/src/layer_rule.rs6
-rw-r--r--niri-config/src/lib.rs2
-rw-r--r--src/handlers/layer_shell.rs5
-rw-r--r--src/layer/mapped.rs37
-rw-r--r--src/layer/mod.rs24
-rw-r--r--src/niri.rs7
-rw-r--r--wiki/Configuration:-Layer-Rules.md58
7 files changed, 133 insertions, 6 deletions
diff --git a/niri-config/src/layer_rule.rs b/niri-config/src/layer_rule.rs
index dc6fbd8d..f97b2c0d 100644
--- a/niri-config/src/layer_rule.rs
+++ b/niri-config/src/layer_rule.rs
@@ -1,4 +1,4 @@
-use crate::{BlockOutFrom, RegexEq};
+use crate::{BlockOutFrom, CornerRadius, RegexEq, ShadowRule};
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct LayerRule {
@@ -11,6 +11,10 @@ pub struct LayerRule {
pub opacity: Option<f32>,
#[knuffel(child, unwrap(argument))]
pub block_out_from: Option<BlockOutFrom>,
+ #[knuffel(child, default)]
+ pub shadow: ShadowRule,
+ #[knuffel(child)]
+ pub geometry_corner_radius: Option<CornerRadius>,
}
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index fae5129e..db301151 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -3778,6 +3778,8 @@ mod tests {
excludes: vec![],
opacity: None,
block_out_from: Some(BlockOutFrom::Screencast),
+ shadow: ShadowRule::default(),
+ geometry_corner_radius: None,
}
],
workspaces: vec![
diff --git a/src/handlers/layer_shell.rs b/src/handlers/layer_shell.rs
index 1888e11c..73a61e6e 100644
--- a/src/handlers/layer_shell.rs
+++ b/src/handlers/layer_shell.rs
@@ -132,10 +132,11 @@ impl State {
// Resolve rules for newly mapped layer surfaces.
if was_unmapped {
- let rules = &self.niri.config.borrow().layer_rules;
+ let config = self.niri.config.borrow();
+ let rules = &config.layer_rules;
let rules =
ResolvedLayerRules::compute(rules, layer, self.niri.is_at_startup);
- let mapped = MappedLayer::new(layer.clone(), rules);
+ let mapped = MappedLayer::new(layer.clone(), rules, &config);
let prev = self
.niri
.mapped_layer_surfaces
diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs
index dfc09fe3..493bbc58 100644
--- a/src/layer/mapped.rs
+++ b/src/layer/mapped.rs
@@ -1,4 +1,5 @@
use niri_config::layer_rule::LayerRule;
+use niri_config::Config;
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
};
@@ -7,8 +8,10 @@ use smithay::desktop::{LayerSurface, PopupManager};
use smithay::utils::{Logical, Point, Scale, Size};
use super::ResolvedLayerRules;
+use crate::layout::shadow::Shadow;
use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
+use crate::render_helpers::shadow::ShadowRenderElement;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::{RenderTarget, SplitElements};
@@ -22,29 +25,56 @@ pub struct MappedLayer {
/// Buffer to draw instead of the surface when it should be blocked out.
block_out_buffer: SolidColorBuffer,
+
+ /// The shadow around the surface.
+ shadow: Shadow,
}
niri_render_elements! {
LayerSurfaceRenderElement<R> => {
Wayland = WaylandSurfaceRenderElement<R>,
SolidColor = SolidColorRenderElement,
+ Shadow = ShadowRenderElement,
}
}
impl MappedLayer {
- pub fn new(surface: LayerSurface, rules: ResolvedLayerRules) -> Self {
+ pub fn new(surface: LayerSurface, rules: ResolvedLayerRules, config: &Config) -> Self {
+ let mut shadow_config = config.layout.shadow;
+ // Shadows for layer surfaces need to be explicitly enabled.
+ shadow_config.on = false;
+ let shadow_config = rules.shadow.resolve_against(shadow_config);
+
Self {
surface,
rules,
block_out_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]),
+ shadow: Shadow::new(shadow_config),
}
}
+ pub fn update_config(&mut self, config: &Config) {
+ let mut shadow_config = config.layout.shadow;
+ // Shadows for layer surfaces need to be explicitly enabled.
+ shadow_config.on = false;
+ let shadow_config = self.rules.shadow.resolve_against(shadow_config);
+ self.shadow.update_config(shadow_config);
+ }
+
+ pub fn update_shaders(&mut self) {
+ self.shadow.update_shaders();
+ }
+
pub fn update_render_elements(&mut self, size: Size<f64, Logical>, scale: Scale<f64>) {
// Round to physical pixels.
let size = size.to_physical_precise_round(scale).to_logical(scale);
self.block_out_buffer.resize(size);
+
+ let radius = self.rules.geometry_corner_radius.unwrap_or_default();
+ // FIXME: is_active based on keyboard focus?
+ self.shadow
+ .update_render_elements(size, true, radius, scale.x);
}
pub fn surface(&self) -> &LayerSurface {
@@ -81,6 +111,7 @@ impl MappedLayer {
// Round to physical pixels.
let location = location.to_physical_precise_round(scale).to_logical(scale);
+ // FIXME: take geometry-corner-radius into account.
let elem = SolidColorRenderElement::from_buffer(
&self.block_out_buffer,
location,
@@ -117,6 +148,10 @@ impl MappedLayer {
);
}
+ let location = location.to_physical_precise_round(scale).to_logical(scale);
+ rv.normal
+ .extend(self.shadow.render(renderer, location).map(Into::into));
+
rv
}
}
diff --git a/src/layer/mod.rs b/src/layer/mod.rs
index 72e804d5..36e7ee67 100644
--- a/src/layer/mod.rs
+++ b/src/layer/mod.rs
@@ -1,5 +1,5 @@
use niri_config::layer_rule::{LayerRule, Match};
-use niri_config::BlockOutFrom;
+use niri_config::{BlockOutFrom, CornerRadius, ShadowRule};
use smithay::desktop::LayerSurface;
pub mod mapped;
@@ -13,6 +13,12 @@ pub struct ResolvedLayerRules {
/// Whether to block out this layer surface from certain render targets.
pub block_out_from: Option<BlockOutFrom>,
+
+ /// Shadow overrides.
+ pub shadow: ShadowRule,
+
+ /// Corner radius to assume this layer surface has.
+ pub geometry_corner_radius: Option<CornerRadius>,
}
impl ResolvedLayerRules {
@@ -20,6 +26,17 @@ impl ResolvedLayerRules {
Self {
opacity: None,
block_out_from: None,
+ shadow: ShadowRule {
+ off: false,
+ on: false,
+ offset: None,
+ softness: None,
+ spread: None,
+ draw_behind_window: None,
+ color: None,
+ inactive_color: None,
+ },
+ geometry_corner_radius: None,
}
}
@@ -53,6 +70,11 @@ impl ResolvedLayerRules {
if let Some(x) = rule.block_out_from {
resolved.block_out_from = Some(x);
}
+ if let Some(x) = rule.geometry_corner_radius {
+ resolved.geometry_corner_radius = Some(x);
+ }
+
+ resolved.shadow.merge_with(&rule.shadow);
}
resolved
diff --git a/src/niri.rs b/src/niri.rs
index a8d6f004..a578ced3 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -1097,6 +1097,9 @@ impl State {
}
self.niri.layout.update_config(&config);
+ for mapped in self.niri.mapped_layer_surfaces.values_mut() {
+ mapped.update_config(&config);
+ }
// Create new named workspaces.
for ws_config in &config.workspaces {
@@ -5101,11 +5104,13 @@ impl Niri {
let mut changed = false;
{
- let rules = &self.config.borrow().layer_rules;
+ let config = self.config.borrow();
+ let rules = &config.layer_rules;
for mapped in self.mapped_layer_surfaces.values_mut() {
if mapped.recompute_layer_rules(rules, self.is_at_startup) {
changed = true;
+ mapped.update_config(&config);
}
}
}
diff --git a/wiki/Configuration:-Layer-Rules.md b/wiki/Configuration:-Layer-Rules.md
index 297991e3..3bf262c9 100644
--- a/wiki/Configuration:-Layer-Rules.md
+++ b/wiki/Configuration:-Layer-Rules.md
@@ -19,6 +19,19 @@ layer-rule {
opacity 0.5
block-out-from "screencast"
// block-out-from "screen-capture"
+
+ shadow {
+ on
+ // off
+ softness 40
+ spread 5
+ offset x=0 y=5
+ draw-behind-window true
+ color "#00000064"
+ // inactive-color "#00000064"
+ }
+
+ geometry-corner-radius 12
}
```
@@ -95,3 +108,48 @@ layer-rule {
opacity 0.95
}
```
+
+#### `shadow`
+
+<sup>Since: next release</sup>
+
+Override the shadow options for the surface.
+
+These rules have the same options as the normal shadow config in the [layout](./Configuration:-Layout.md) section, so check the documentation there.
+
+Unlike window shadows, layer surface shadows always need to be enabled with a layer rule.
+That is, enabling shadows in the layout config section won't automatically enable them for layer surfaces.
+
+> [!NOTE]
+> Layer surfaces have no way to tell niri about their *visual geometry*.
+> For example, if a layer surface includes some invisible margins (like mako), niri has no way of knowing that, and will draw the shadow behind the entire surface, including the invisible margins.
+>
+> So to use niri shadows, you'll need to configure layer-shell clients to remove their own margins or shadows.
+
+```kdl
+// Add a shadow for fuzzel.
+layer-rule {
+ match namespace="^launcher$"
+
+ shadow {
+ on
+ }
+
+ // Fuzzel defaults to 10 px rounded corners.
+ geometry-corner-radius 10
+}
+```
+
+#### `geometry-corner-radius`
+
+<sup>Since: next release</sup>
+
+Set the corner radius of the surface.
+
+This setting will only affect the shadow—it will round its corners to match the geometry corner radius.
+
+```kdl
+layer-rule {
+ geometry-corner-radius 12
+}
+```