diff options
| -rw-r--r-- | niri-config/src/layer_rule.rs | 2 | ||||
| -rw-r--r-- | niri-config/src/lib.rs | 1 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 15 | ||||
| -rw-r--r-- | src/layer/mapped.rs | 18 | ||||
| -rw-r--r-- | src/layer/mod.rs | 7 | ||||
| -rw-r--r-- | src/niri.rs | 75 | ||||
| -rw-r--r-- | wiki/Configuration:-Layer-Rules.md | 23 | ||||
| -rw-r--r-- | wiki/Overview.md | 19 |
8 files changed, 137 insertions, 23 deletions
diff --git a/niri-config/src/layer_rule.rs b/niri-config/src/layer_rule.rs index f97b2c0d..261a45a0 100644 --- a/niri-config/src/layer_rule.rs +++ b/niri-config/src/layer_rule.rs @@ -15,6 +15,8 @@ pub struct LayerRule { pub shadow: ShadowRule, #[knuffel(child)] pub geometry_corner_radius: Option<CornerRadius>, + #[knuffel(child, unwrap(argument))] + pub place_within_backdrop: Option<bool>, } #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 0021462e..d418b4ff 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -4888,6 +4888,7 @@ mod tests { inactive_color: None, }, geometry_corner_radius: None, + place_within_backdrop: None, }, ], binds: Binds( diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 4dfce86d..bf61e37e 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -319,10 +319,17 @@ impl XdgShellHandler for State { // FIXME: somewhere here we probably need to check is_overview_open to match the logic // in update_keyboard_focus(). - if layers - .layer_for_surface(&root, WindowSurfaceType::TOPLEVEL) - .is_none() - { + if let Some(layer) = layers.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL) { + // This is a grab for a layer surface. + + if let Some(mapped) = self.niri.mapped_layer_surfaces.get(layer) { + if mapped.place_within_backdrop() { + trace!("ignoring popup grab for a layer surface within overview backdrop"); + let _ = PopupManager::dismiss_popup(&root, &popup); + return; + } + } + } else { // This is a grab for a regular window; check that there's no layer surface with a // higher input priority. diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs index 5e62d033..78cfadbf 100644 --- a/src/layer/mapped.rs +++ b/src/layer/mapped.rs @@ -6,6 +6,7 @@ use smithay::backend::renderer::element::surface::{ use smithay::backend::renderer::element::Kind; use smithay::desktop::{LayerSurface, PopupManager}; use smithay::utils::{Logical, Point, Scale, Size}; +use smithay::wayland::shell::wlr_layer::{ExclusiveZone, Layer}; use super::ResolvedLayerRules; use crate::layout::shadow::Shadow; @@ -96,6 +97,23 @@ impl MappedLayer { true } + pub fn place_within_backdrop(&self) -> bool { + if !self.rules.place_within_backdrop { + return false; + } + + if self.surface.layer() != Layer::Background { + return false; + } + + let state = self.surface.cached_state(); + if state.exclusive_zone != ExclusiveZone::DontCare { + return false; + } + + true + } + pub fn render<R: NiriRenderer>( &self, renderer: &mut R, diff --git a/src/layer/mod.rs b/src/layer/mod.rs index 36e7ee67..3b27737c 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -19,6 +19,9 @@ pub struct ResolvedLayerRules { /// Corner radius to assume this layer surface has. pub geometry_corner_radius: Option<CornerRadius>, + + /// Whether to place this layer surface within the overview backdrop. + pub place_within_backdrop: bool, } impl ResolvedLayerRules { @@ -37,6 +40,7 @@ impl ResolvedLayerRules { inactive_color: None, }, geometry_corner_radius: None, + place_within_backdrop: false, } } @@ -73,6 +77,9 @@ impl ResolvedLayerRules { if let Some(x) = rule.geometry_corner_radius { resolved.geometry_corner_radius = Some(x); } + if let Some(x) = rule.place_within_backdrop { + resolved.place_within_backdrop = x; + } resolved.shadow.merge_with(&rule.shadow); } diff --git a/src/niri.rs b/src/niri.rs index be553293..4aa353d1 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -992,9 +992,19 @@ impl State { // Clean up on-demand layer surface focus if necessary. if let Some(surface) = &self.niri.layer_shell_on_demand_focus { // Still alive and has on-demand interactivity. - let good = surface.alive() + let mut good = surface.alive() && surface.cached_state().keyboard_interactivity == wlr_layer::KeyboardInteractivity::OnDemand; + + // Check if it moved to the overview backdrop. + if let Some(mapped) = self.niri.mapped_layer_surfaces.get(surface) { + if mapped.place_within_backdrop() { + good = false; + } + } else { + good = false; + } + if !good { self.niri.layer_shell_on_demand_focus = None; } @@ -1042,6 +1052,11 @@ impl State { return None; } + let mapped = self.niri.mapped_layer_surfaces.get(surface)?; + if mapped.place_within_backdrop() { + return None; + } + let surface = surface.wl_surface().clone(); Some(KeyboardFocus::LayerShell { surface }) }) @@ -1513,11 +1528,10 @@ impl State { resized_outputs.push(output.clone()); } - let mut background_color = config + let background_color = config .map(|c| c.background_color) .unwrap_or(DEFAULT_BACKGROUND_COLOR) .to_array_unpremul(); - background_color[3] = 1.; let background_color = Color32F::from(background_color); let mut backdrop_color = config @@ -2710,11 +2724,10 @@ impl Niri { .map(|c| ipc_transform_to_smithay(c.transform)) .unwrap_or(Transform::Normal); - let mut background_color = c + let background_color = c .map(|c| c.background_color) .unwrap_or(DEFAULT_BACKGROUND_COLOR) .to_array_unpremul(); - background_color[3] = 1.; let mut backdrop_color = c .and_then(|c| c.backdrop_color) @@ -2991,6 +3004,11 @@ impl Niri { .layers_on(layer) .rev() .find_map(|layer_surface| { + let mapped = self.mapped_layer_surfaces.get(layer_surface)?; + if mapped.place_within_backdrop() { + return None; + } + let mut layer_pos_within_output = layers.layer_geometry(layer_surface).unwrap().loc.to_f64(); @@ -3141,6 +3159,11 @@ impl Niri { .layers_on(layer) .rev() .find_map(|layer_surface| { + let mapped = self.mapped_layer_surfaces.get(layer_surface)?; + if mapped.place_within_backdrop() { + return None; + } + let mut layer_pos_within_output = layers.layer_geometry(layer_surface).unwrap().loc.to_f64(); @@ -4021,19 +4044,27 @@ impl Niri { // Get layer-shell elements. let layer_map = layer_map_for_output(output); - let mut extend_from_layer = |elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, - layer| { - self.render_layer(renderer, target, output_scale, &layer_map, layer, elements); - }; + let mut extend_from_layer = + |elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, layer, for_backdrop| { + self.render_layer( + renderer, + target, + output_scale, + &layer_map, + layer, + elements, + for_backdrop, + ); + }; // The overlay layer elements go next. let mut layer_elems = SplitElements::default(); - extend_from_layer(&mut layer_elems, Layer::Overlay); + extend_from_layer(&mut layer_elems, Layer::Overlay, false); elements.extend(layer_elems.into_iter().map(OutputRenderElements::from)); // Collect the top layer elements. let mut layer_elems = SplitElements::default(); - extend_from_layer(&mut layer_elems, Layer::Top); + extend_from_layer(&mut layer_elems, Layer::Top, false); let top_layer = layer_elems; // When rendering above the top layer, we put the regular monitor elements first. @@ -4041,8 +4072,8 @@ impl Niri { if mon.render_above_top_layer() { // Collect all other layer-shell elements. let mut layer_elems = SplitElements::default(); - extend_from_layer(&mut layer_elems, Layer::Bottom); - extend_from_layer(&mut layer_elems, Layer::Background); + extend_from_layer(&mut layer_elems, Layer::Bottom, false); + extend_from_layer(&mut layer_elems, Layer::Background, false); elements.extend( int_move_elements @@ -4062,8 +4093,7 @@ impl Niri { ); elements.extend(top_layer.into_iter().map(OutputRenderElements::from)); - elements.extend(layer_elems.popups.drain(..).map(OutputRenderElements::from)); - elements.extend(layer_elems.normal.drain(..).map(OutputRenderElements::from)); + elements.extend(layer_elems.into_iter().map(OutputRenderElements::from)); elements.push(OutputRenderElements::from(background)); @@ -4090,8 +4120,8 @@ impl Niri { for (ws_geo, ws_elements) in monitor_elements { // Collect all other layer-shell elements. let mut layer_elems = SplitElements::default(); - extend_from_layer(&mut layer_elems, Layer::Bottom); - extend_from_layer(&mut layer_elems, Layer::Background); + extend_from_layer(&mut layer_elems, Layer::Bottom, false); + extend_from_layer(&mut layer_elems, Layer::Background, false); elements.extend( layer_elems @@ -4126,6 +4156,10 @@ impl Niri { } // Then the backdrop. + let mut layer_elems = SplitElements::default(); + extend_from_layer(&mut layer_elems, Layer::Background, true); + elements.extend(layer_elems.into_iter().map(OutputRenderElements::from)); + elements.push(backdrop); if self.debug_draw_opaque_regions { @@ -4135,6 +4169,7 @@ impl Niri { elements } + #[allow(clippy::too_many_arguments)] fn render_layer<R: NiriRenderer>( &self, renderer: &mut R, @@ -4143,10 +4178,16 @@ impl Niri { layer_map: &LayerMap, layer: Layer, elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, + for_backdrop: bool, ) { // LayerMap returns layers in reverse stacking order. let iter = layer_map.layers_on(layer).rev().filter_map(|surface| { let mapped = self.mapped_layer_surfaces.get(surface)?; + + if for_backdrop != mapped.place_within_backdrop() { + return None; + } + let geo = layer_map.layer_geometry(surface)?; Some((mapped, geo)) }); diff --git a/wiki/Configuration:-Layer-Rules.md b/wiki/Configuration:-Layer-Rules.md index 84a8f7d6..8688295c 100644 --- a/wiki/Configuration:-Layer-Rules.md +++ b/wiki/Configuration:-Layer-Rules.md @@ -32,6 +32,8 @@ layer-rule { } geometry-corner-radius 12 + + place-within-backdrop true } ``` @@ -129,7 +131,7 @@ That is, enabling shadows in the layout config section won't automatically enabl // Add a shadow for fuzzel. layer-rule { match namespace="^launcher$" - + shadow { on } @@ -149,6 +151,25 @@ This setting will only affect the shadow—it will round its corners to match th ```kdl layer-rule { + match namespace="^launcher$" + geometry-corner-radius 12 } ``` + +#### `place-within-backdrop` + +<sup>Since: next release</sup> + +Set to `true` to place the surface into the backdrop visible in the [Overview](./Overview.md) and between workspaces. + +This will only work for *background* layer surfaces that ignore exclusive zones (typical for wallpaper tools). + +```kdl +// Put swaybg inside the overview backdrop. +layer-rule { + match namespace="^wallpaper$" + + place-within-backdrop true +} +``` diff --git a/wiki/Overview.md b/wiki/Overview.md index c907eb67..5c9bae11 100644 --- a/wiki/Overview.md +++ b/wiki/Overview.md @@ -14,7 +14,7 @@ While in the overview, all keyboard binds keep working, while pointing devices g > [!TIP] > The overview needs to draw a background under every workspace. -> So, layer-shell surfaces work this way: the *background* and *bottom* layers zoom out and remain under workspaces, while the *top* and *overlay* layers remain on top of the overview. +> So, layer-shell surfaces work this way: the *background* and *bottom* layers zoom out and remain on the workspaces, while the *top* and *overlay* layers remain on top of the overview. > > Put your bar on the *top* layer. @@ -59,3 +59,20 @@ gestures { } } ``` + +### Backdrop customization + +Apart from setting a custom backdrop color like described above, you can also put a layer-shell wallpaper into the backdrop with a [layer rule](./Configuration:-Layer-Rules.md#place-within-backdrop), for example: + +```kdl +// Put swaybg inside the overview backdrop. +layer-rule { + match namespace="^wallpaper$" + place-within-backdrop true +} +``` + +This will only work for *background* layer surfaces that ignore exclusive zones (typical for wallpaper tools). + +You can run two different wallpaper tools (like swaybg and swww), one for the backdrop and one for the normal workspace background. +This way you could set the backdrop one to a blurred version of the wallpaper for a nice effect. |
