From 6bcaaf9d21fca9477aa2c45c5d60235662c58056 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sat, 21 Feb 2026 13:37:56 +0300 Subject: [PATCH] Add layer matcher to layer-rule --- docs/wiki/Configuration:-Layer-Rules.md | 17 ++++++++++ niri-config/src/layer_rule.rs | 2 ++ niri-config/src/lib.rs | 1 + niri-ipc/src/lib.rs | 14 ++++++++ src/handlers/layer_shell.rs | 43 +++++++++++++++++++++++-- src/layer/mapped.rs | 26 +++++++++++++++ src/layer/mod.rs | 13 ++++++++ 7 files changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/wiki/Configuration:-Layer-Rules.md b/docs/wiki/Configuration:-Layer-Rules.md index 04055bd9..033886fa 100644 --- a/docs/wiki/Configuration:-Layer-Rules.md +++ b/docs/wiki/Configuration:-Layer-Rules.md @@ -14,6 +14,7 @@ Here are all matchers and properties that a layer rule could have: layer-rule { match namespace="waybar" match at-startup=true + match layer="top" // Properties that apply continuously. opacity 0.5 @@ -69,6 +70,22 @@ layer-rule { } ``` +#### `layer` + +Since: next release + +Matches surfaces on this layer-shell layer. +Can be `"background"`, `"bottom"`, `"top"`, or `"overlay"`. + +```kdl +// Make all overlay-layer surfaces FLOAT. +layer-rule { + match layer="overlay" + + baba-is-float true +} +``` + ### Dynamic Properties These properties apply continuously to open layer-shell surfaces. diff --git a/niri-config/src/layer_rule.rs b/niri-config/src/layer_rule.rs index c11edc13..b77aa849 100644 --- a/niri-config/src/layer_rule.rs +++ b/niri-config/src/layer_rule.rs @@ -28,4 +28,6 @@ pub struct Match { pub namespace: Option, #[knuffel(property)] pub at_startup: Option, + #[knuffel(property, str)] + pub layer: Option, } diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index b61fe1c1..d1cd7bd0 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1859,6 +1859,7 @@ mod tests { ), ), at_startup: None, + layer: None, }, ], excludes: [], diff --git a/niri-ipc/src/lib.rs b/niri-ipc/src/lib.rs index 50845633..b05f9994 100644 --- a/niri-ipc/src/lib.rs +++ b/niri-ipc/src/lib.rs @@ -1868,6 +1868,20 @@ impl FromStr for Transform { } } +impl FromStr for Layer { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "background" => Ok(Self::Background), + "bottom" => Ok(Self::Bottom), + "top" => Ok(Self::Top), + "overlay" => Ok(Self::Overlay), + _ => Err("invalid layer, can be \"background\", \"bottom\", \"top\" or \"overlay\""), + } + } +} + impl FromStr for ModeToSet { type Err = &'static str; diff --git a/src/handlers/layer_shell.rs b/src/handlers/layer_shell.rs index e0f1084f..dec9f5d7 100644 --- a/src/handlers/layer_shell.rs +++ b/src/handlers/layer_shell.rs @@ -3,10 +3,10 @@ use smithay::desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurf use smithay::output::Output; use smithay::reexports::wayland_server::protocol::wl_output::WlOutput; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; -use smithay::wayland::compositor::{get_parent, with_states}; +use smithay::wayland::compositor::{add_pre_commit_hook, get_parent, with_states, HookId}; use smithay::wayland::shell::wlr_layer::{ - self, Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler, - WlrLayerShellState, + self, Layer, LayerSurface as WlrLayerSurface, LayerSurfaceCachedState, LayerSurfaceData, + WlrLayerShellHandler, WlrLayerShellState, }; use smithay::wayland::shell::xdg::PopupSurface; @@ -126,8 +126,10 @@ impl State { let output_size = output_size(&output); let scale = output.current_scale().fractional_scale(); + let hook = add_mapped_layer_pre_commit_hook(layer); let mapped = MappedLayer::new( layer.clone(), + hook, rules, output_size, scale, @@ -142,6 +144,21 @@ impl State { if prev.is_some() { error!("MappedLayer was present for an unmapped surface"); } + } else { + // The surface remains mapped. + if let Some(mapped) = self.niri.mapped_layer_surfaces.get_mut(layer) { + // Check if the layer changed. + if mapped.take_recompute_rules_on_commit() { + let config = self.niri.config.borrow(); + if mapped + .recompute_layer_rules(&config.layer_rules, self.niri.is_at_startup) + { + mapped.update_config(&config); + } + } + } else { + error!("MappedLayer missing for a mapped surface"); + } } // Give focus to newly mapped on-demand surfaces. Some launchers like lxqt-runner rely @@ -204,3 +221,23 @@ impl State { true } } + +fn add_mapped_layer_pre_commit_hook(layer: &LayerSurface) -> HookId { + add_pre_commit_hook::(layer.wl_surface(), move |state, _dh, surface| { + let layer_changed = with_states(surface, |states| { + let mut guard = states.cached_state.get::(); + let pending_layer = guard.pending().layer; + let current_layer = guard.current().layer; + pending_layer != current_layer + }); + + if layer_changed { + for mapped in state.niri.mapped_layer_surfaces.values_mut() { + if mapped.surface().wl_surface() == surface { + mapped.set_recompute_rules_on_commit(); + break; + } + } + } + }) +} diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs index b4fe81d2..13f657cf 100644 --- a/src/layer/mapped.rs +++ b/src/layer/mapped.rs @@ -4,6 +4,7 @@ use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; use smithay::backend::renderer::element::Kind; use smithay::desktop::{LayerSurface, PopupManager}; use smithay::utils::{Logical, Point, Scale, Size}; +use smithay::wayland::compositor::{remove_pre_commit_hook, HookId}; use smithay::wayland::shell::wlr_layer::{ExclusiveZone, Layer}; use super::ResolvedLayerRules; @@ -22,9 +23,17 @@ pub struct MappedLayer { /// The surface itself. surface: LayerSurface, + /// Pre-commit hook that we have on all mapped layer surfaces. + pre_commit_hook: HookId, + /// Up-to-date rules. rules: ResolvedLayerRules, + /// Whether to recompute layer rules on the next commit. + /// + /// Set in the pre-commit hook when the layer changes; consumed in the commit handler. + recompute_rules_on_commit: bool, + /// Buffer to draw instead of the surface when it should be blocked out. block_out_buffer: SolidColorBuffer, @@ -52,6 +61,7 @@ niri_render_elements! { impl MappedLayer { pub fn new( surface: LayerSurface, + pre_commit_hook: HookId, rules: ResolvedLayerRules, view_size: Size, scale: f64, @@ -65,7 +75,9 @@ impl MappedLayer { Self { surface, + pre_commit_hook, rules, + recompute_rules_on_commit: false, block_out_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]), view_size, scale, @@ -128,6 +140,14 @@ impl MappedLayer { true } + pub fn set_recompute_rules_on_commit(&mut self) { + self.recompute_rules_on_commit = true; + } + + pub fn take_recompute_rules_on_commit(&mut self) -> bool { + std::mem::take(&mut self.recompute_rules_on_commit) + } + pub fn place_within_backdrop(&self) -> bool { if !self.rules.place_within_backdrop { return false; @@ -232,3 +252,9 @@ impl MappedLayer { } } } + +impl Drop for MappedLayer { + fn drop(&mut self) { + remove_pre_commit_hook(self.surface.wl_surface(), self.pre_commit_hook.clone()); + } +} diff --git a/src/layer/mod.rs b/src/layer/mod.rs index b74b5608..f03edbda 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -2,6 +2,7 @@ use niri_config::layer_rule::{LayerRule, Match}; use niri_config::utils::MergeWith as _; use niri_config::{BlockOutFrom, CornerRadius, ShadowRule}; use smithay::desktop::LayerSurface; +use smithay::wayland::shell::wlr_layer::Layer; pub mod mapped; pub use mapped::MappedLayer; @@ -83,5 +84,17 @@ fn surface_matches(surface: &LayerSurface, m: &Match) -> bool { } } + if let Some(layer) = m.layer { + let surface_layer = match surface.layer() { + Layer::Background => niri_ipc::Layer::Background, + Layer::Bottom => niri_ipc::Layer::Bottom, + Layer::Top => niri_ipc::Layer::Top, + Layer::Overlay => niri_ipc::Layer::Overlay, + }; + if layer != surface_layer { + return false; + } + } + true }