Skip to content

Commit cd7ec7d

Browse files
committed
Add geometry-corner-radius and background-effect popup rules
1 parent 4b61cd8 commit cd7ec7d

File tree

12 files changed

+222
-28
lines changed

12 files changed

+222
-28
lines changed

docs/wiki/Configuration:-Layer-Rules.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ layer-rule {
4242
noise 0.05
4343
saturation 3
4444
}
45+
46+
popups {
47+
opacity 0.5
48+
geometry-corner-radius 6
49+
50+
background-effect {
51+
xray true
52+
blur true
53+
noise 0.05
54+
saturation 3
55+
}
56+
}
4557
}
4658
```
4759

@@ -241,3 +253,42 @@ layer-rule {
241253
}
242254
}
243255
```
256+
257+
#### `popups`
258+
259+
<sup>Since: next release</sup>
260+
261+
Override properties for this layer surface's pop-ups (e.g. a menu opened by clicking an item in Waybar).
262+
263+
The properties work the same way as the corresponding layer-rule properties, except that they apply to the layer surface's pop-ups rather than to the layer surface itself.
264+
265+
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
266+
Other properties apply independently.
267+
268+
> [!NOTE]
269+
> This block affects only pop-ups created by the app via Wayland's [xdg-popup](https://wayland.app/protocols/xdg-shell#xdg_popup) (which should be most of them).
270+
>
271+
> Some desktop shells will emulate pop-ups by drawing something that looks like a pop-up inside a regular layer surface.
272+
> As far as niri is concerned, those are just layer surfaces and not pop-ups, so this block won't apply to them.
273+
>
274+
> This block also does not affect input-method pop-ups, such as Fcitx.
275+
276+
```kdl
277+
// Blur the background behind Waybar popup menus.
278+
layer-rule {
279+
match namespace="^waybar$"
280+
281+
popups {
282+
// Match the default GTK 3 popup corner radius.
283+
geometry-corner-radius 6
284+
opacity 0.85
285+
286+
background-effect {
287+
blur true
288+
}
289+
}
290+
}
291+
```
292+
293+
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the layer surface correctly sets its Wayland geometry to exclude any shadows.
294+
Pop-ups with custom shapes will need the app to implement the [ext-background-effect protocol](https://wayland.app/protocols/ext-background-effect-v1) to work properly.

docs/wiki/Configuration:-Window-Rules.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ window-rule {
107107
saturation 3
108108
}
109109
110+
popups {
111+
opacity 0.5
112+
geometry-corner-radius 15
113+
114+
background-effect {
115+
xray true
116+
blur true
117+
noise 0.05
118+
saturation 3
119+
}
120+
}
121+
110122
min-width 100
111123
max-width 200
112124
min-height 300
@@ -941,6 +953,67 @@ window-rule {
941953
}
942954
```
943955

956+
#### `popups`
957+
958+
<sup>Since: next release</sup>
959+
960+
Override properties for this window's pop-ups (menus and tooltips).
961+
962+
The properties work the same way as the corresponding window-rule properties, except that they apply to the window's pop-ups rather than to the window itself.
963+
964+
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
965+
Other properties apply independently.
966+
967+
> [!NOTE]
968+
> This block affects only pop-ups created by the app via Wayland's [xdg-popup](https://wayland.app/protocols/xdg-shell#xdg_popup) (which should be most of them).
969+
>
970+
> Examples of things that look like pop-ups that won't work:
971+
>
972+
> - Fully emulated by the client, i.e. not a pop-up at all, the client just draws something that looks like a pop-up inside its window.
973+
> These are common in game engines and in web apps, e.g. the right click menu in Google Docs or in Electron apps like Discord.
974+
>
975+
> - Uses a wl-subsurface instead of an xdg-popup.
976+
> Common in older apps using GTK 3, notably Firefox still uses these for some menus.
977+
> Subsurfaces are an indivisible part of a surface and they aren't usually pop-ups, so it wouldn't make sense for niri to apply these rules to them.
978+
>
979+
> These emulated pop-ups come with other downsides: they cannot reliably extend outside their window, and if the app tries to do that, they will be clipped by rules such as `clip-to-geometry`.
980+
> So most modern apps will correctly use xdg-popup, which is the intended way to show pop-ups on Wayland.
981+
>
982+
> This block also does not affect input-method pop-ups, such as Fcitx.
983+
>
984+
> For pop-ups created by your desktop shell or desktop components, use the corresponding [layer rule](./Configuration:-Layer-Rules.md#popups).
985+
986+
```kdl
987+
// Blur the background behind pop-up menus in Nautilus.
988+
window-rule {
989+
match app-id="Nautilus"
990+
991+
popups {
992+
// Matches the default libadwaita pop-up corner radius.
993+
geometry-corner-radius 15
994+
995+
// Note: it'll look better to set background opacity
996+
// through your GTK theme CSS and not here.
997+
// This is just an example that makes it look obvious.
998+
opacity 0.5
999+
1000+
background-effect {
1001+
blur true
1002+
}
1003+
}
1004+
}
1005+
```
1006+
1007+
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the window correctly sets its Wayland geometry to exclude any shadows.
1008+
For example, GTK 4 pop-ups with pointing arrows (`has-arrow=true` property) are *not* rounded rectangles—the arrow sticks out—so if you enable blur, it will also stick out of the pop-up.
1009+
1010+
| Correct | Wrong |
1011+
|-----------------------------------------------------|--------------------------------------------------------------------------------|
1012+
| The pop-up is a rounded rectangle. Blur looks fine. | The pop-up is not a rounded rectangle. Blur extends above, where the arrow is. |
1013+
| ![](./img/popup-no-arrow.png) | ![](./img/popup-arrow.png) |
1014+
1015+
These pop-ups with custom shapes will need the app to implement the [ext-background-effect protocol](https://wayland.app/protocols/ext-background-effect-v1) to work properly.
1016+
9441017
#### Size Overrides
9451018

9461019
You can amend the window's minimum and maximum size in logical pixels.

docs/wiki/Window-Effects.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Blur enabled via the window rule will follow the window corner radius set via [`
4040
On the other hand, blur enabled through `ext-background-effect` will exactly follow the shape requested by the window.
4141
If the window or layer has clientside rounded corners or other complex shape, it should set a corresponding blur shape through `ext-background-effect`, then it will get correctly shaped background blur without any manual niri configuration.
4242

43+
Windows can also blur their pop-up menus using `ext-background-effect`.
44+
On the niri side, you can do it with a `popups` block inside [`window-rule`](./Configuration:-Window-Rules.md#popups) and [`layer-rule`](./Configuration:-Layer-Rules.md#popups).
45+
See those wiki pages for examples and limitations.
46+
4347
Global blur settings are configured in the [`blur {}` config section](./Configuration:-Miscellaneous.md#blur) and apply to all background blur.
4448

4549
### Xray

docs/wiki/img/popup-arrow.png

Lines changed: 3 additions & 0 deletions
Loading

docs/wiki/img/popup-no-arrow.png

Lines changed: 3 additions & 0 deletions
Loading

niri-config/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,13 @@ mod tests {
18641864
},
18651865
popups: PopupsRule {
18661866
opacity: None,
1867+
geometry_corner_radius: None,
1868+
background_effect: BackgroundEffectRule {
1869+
xray: None,
1870+
blur: None,
1871+
noise: None,
1872+
saturation: None,
1873+
},
18671874
},
18681875
},
18691876
],
@@ -1908,6 +1915,13 @@ mod tests {
19081915
},
19091916
popups: PopupsRule {
19101917
opacity: None,
1918+
geometry_corner_radius: None,
1919+
background_effect: BackgroundEffectRule {
1920+
xray: None,
1921+
blur: None,
1922+
noise: None,
1923+
saturation: None,
1924+
},
19111925
},
19121926
},
19131927
],

niri-config/src/window_rule.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use niri_ipc::ColumnDisplay;
22

33
use crate::appearance::{
4-
BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule,
4+
BackgroundEffect, BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule,
5+
TabIndicatorRule,
56
};
67
use crate::layout::DefaultPresetSize;
78
use crate::utils::{MergeWith, RegexEq};
@@ -85,20 +86,34 @@ pub struct WindowRule {
8586
pub struct PopupsRule {
8687
#[knuffel(child, unwrap(argument))]
8788
pub opacity: Option<f32>,
89+
#[knuffel(child)]
90+
pub geometry_corner_radius: Option<CornerRadius>,
91+
#[knuffel(child, default)]
92+
pub background_effect: BackgroundEffectRule,
8893
}
8994

9095
/// Resolved popup-specific rules.
9196
#[derive(Debug, Default, Clone, Copy, PartialEq)]
9297
pub struct ResolvedPopupsRules {
9398
/// Extra opacity to draw popups with.
9499
pub opacity: Option<f32>,
100+
101+
/// Corner radius to assume the popups have.
102+
pub geometry_corner_radius: Option<CornerRadius>,
103+
104+
/// Background effect configuration for popups.
105+
pub background_effect: BackgroundEffect,
95106
}
96107

97108
impl MergeWith<PopupsRule> for ResolvedPopupsRules {
98109
fn merge_with(&mut self, part: &PopupsRule) {
99110
if let Some(x) = part.opacity {
100111
self.opacity = Some(x);
101112
}
113+
if let Some(x) = part.geometry_corner_radius {
114+
self.geometry_corner_radius = Some(x);
115+
}
116+
self.background_effect.merge_with(&part.background_effect);
102117
}
103118
}
104119

src/layer/mapped.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ impl MappedLayer {
258258
mut ctx: RenderCtx<R>,
259259
ns: Option<usize>,
260260
location: Point<f64, Logical>,
261+
xray_pos: XrayPos,
261262
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
262263
) {
263264
if ctx.target.should_block_out(self.rules.block_out_from) {
@@ -294,10 +295,12 @@ impl MappedLayer {
294295
let geometry = Rectangle::new(location + offset.to_f64(), popup_geo.size.to_f64());
295296
let surface_off = popup_geo.loc.upscale(-1).to_f64();
296297
let surface_anim_scale = Scale::from(1.);
297-
let effect = niri_config::BackgroundEffect {
298-
xray: Some(false),
299-
..Default::default()
300-
};
298+
let mut effect = popup_rules.background_effect;
299+
// Default xray to false for pop-ups since they're always on top of something.
300+
if effect.xray.is_none() {
301+
effect.xray = Some(false);
302+
}
303+
let xray_pos = xray_pos.offset(offset.to_f64());
301304
background_effect::render_for_tile(
302305
ctx.as_gles(),
303306
ns,
@@ -308,10 +311,10 @@ impl MappedLayer {
308311
surface_off,
309312
surface_anim_scale,
310313
self.blur_config,
311-
niri_config::CornerRadius::default(),
314+
popup_rules.geometry_corner_radius.unwrap_or_default(),
312315
effect,
313316
false,
314-
XrayPos::default(),
317+
xray_pos,
315318
&mut |elem| push(elem.into()),
316319
);
317320
}

src/layout/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,10 @@ pub trait LayoutElement {
166166
location: Point<f64, Logical>,
167167
scale: Scale<f64>,
168168
alpha: f32,
169+
xray_pos: XrayPos,
169170
push: &mut dyn FnMut(LayoutElementRenderElement<R>),
170171
) {
171-
self.render_popups(ctx.r(), location, scale, alpha, push);
172+
self.render_popups(ctx.r(), location, scale, alpha, xray_pos, push);
172173
self.render_normal(ctx.r(), location, scale, alpha, push);
173174
}
174175

@@ -191,9 +192,10 @@ pub trait LayoutElement {
191192
location: Point<f64, Logical>,
192193
scale: Scale<f64>,
193194
alpha: f32,
195+
xray_pos: XrayPos,
194196
push: &mut dyn FnMut(LayoutElementRenderElement<R>),
195197
) {
196-
let _ = (ctx, location, scale, alpha, push);
198+
let _ = (ctx, location, scale, alpha, xray_pos, push);
197199
}
198200

199201
/// Renders the background effect behind the main surface of the element.

src/layout/tile.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,10 +1065,14 @@ impl<W: LayoutElement> Tile<W> {
10651065
.scaled_by(1. - expanded_progress as f32);
10661066

10671067
// Popups go on top, whether it's resize or not.
1068-
self.window
1069-
.render_popups(ctx.r(), window_render_loc, scale, win_alpha, &mut |elem| {
1070-
push(elem.into())
1071-
});
1068+
self.window.render_popups(
1069+
ctx.r(),
1070+
window_render_loc,
1071+
scale,
1072+
win_alpha,
1073+
xray_pos,
1074+
&mut |elem| push(elem.into()),
1075+
);
10721076

10731077
// If we're resizing, try to render a shader, or a fallback.
10741078
let mut pushed_resize = false;

0 commit comments

Comments
 (0)