Skip to content

Commit 0035139

Browse files
authored
Add Blur API
1 parent 3e606b1 commit 0035139

File tree

7 files changed

+222
-5
lines changed

7 files changed

+222
-5
lines changed

protocol/pantheon-desktop-shell-v1.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@
104104
Tell the shell that the panel would like to be visible in the multitasking view.
105105
</description>
106106
</request>
107+
108+
<request name="add_blur">
109+
<description summary="add blur">
110+
Tell the window manager to add background blur.
111+
</description>
112+
113+
<arg name="left" type="uint"/>
114+
<arg name="right" type="uint"/>
115+
<arg name="top" type="uint"/>
116+
<arg name="bottom" type="uint"/>
117+
<arg name="clip_radius" type="uint"/>
118+
</request>
119+
120+
<request name="remove_blur">
121+
<description summary="remove blur">
122+
Tell the window manager to remove blur that was set in set_blur_region.
123+
</description>
124+
</request>
107125
</interface>
108126

109127
<interface name="io_elementary_pantheon_widget_v1" version="1">

protocol/pantheon-desktop-shell.vapi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ namespace Pantheon.Desktop {
4242
public SetSize set_size;
4343
public SetHideMode set_hide_mode;
4444
public RequestVisibleInMultitaskingView request_visible_in_multitasking_view;
45+
public AddBlur add_blur;
46+
public RemoveBlur remove_blur;
4547
}
4648

4749
[CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")]
@@ -78,6 +80,10 @@ namespace Pantheon.Desktop {
7880
[CCode (has_target = false, has_typedef = false)]
7981
public delegate void RequestVisibleInMultitaskingView (Wl.Client client, Wl.Resource resource);
8082
[CCode (has_target = false, has_typedef = false)]
83+
public delegate void AddBlur (Wl.Client client, Wl.Resource resource, uint left, uint right, uint top, uint bottom, uint clip_radius);
84+
[CCode (has_target = false, has_typedef = false)]
85+
public delegate void RemoveBlur (Wl.Client client, Wl.Resource resource);
86+
[CCode (has_target = false, has_typedef = false)]
8187
public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource);
8288
[CCode (has_target = false, has_typedef = false)]
8389
public delegate void MakeCentered (Wl.Client client, Wl.Resource resource);

src/BackgroundBlurEffect.vala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
6868
uniform float clip_radius;
6969
7070
float rounded_rect_coverage (vec2 p) {
71-
float center_left = clip_radius + 1.5;
72-
float center_right = actor_size.x - clip_radius - 0.55;
71+
float center_left = clip_radius;
72+
float center_right = actor_size.x - clip_radius;
7373
7474
float center_x;
7575
if (p.x < center_left) {
@@ -80,8 +80,8 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
8080
return 1.0;
8181
}
8282
83-
float center_top = clip_radius + 1.5;
84-
float center_bottom = actor_size.y - clip_radius - 0.55;
83+
float center_top = clip_radius;
84+
float center_bottom = actor_size.y - clip_radius;
8585
8686
float center_y;
8787
if (p.y < center_top) {
@@ -107,7 +107,7 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
107107
return 1.0;
108108
}
109109
// Only pixels on the edge of the curve need expensive antialiasing
110-
return outer_radius - sqrt (dist_squared);
110+
return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
111111
}
112112
""",
113113

@@ -340,6 +340,11 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
340340
var width = (int) actor_box.get_width ();
341341
var height = (int) actor_box.get_height ();
342342

343+
if (width < 0 || height < 0) {
344+
warning ("BackgroundBlurEffect: Couldn't update framebuffers, incorrect size");
345+
return false;
346+
}
347+
343348
var downscale_factor = calculate_downscale_factor (width, height, real_blur_radius);
344349

345350
var updated = update_actor_fbo (width, height, downscale_factor) && update_rounded_fbo (width, height, downscale_factor) && update_background_fbo (width, height);

src/BlurManager.vala

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
public class Gala.BlurManager : Object {
7+
private struct BlurData {
8+
Clutter.Actor actor;
9+
BackgroundBlurEffect blur_effect;
10+
uint left;
11+
uint right;
12+
uint top;
13+
uint bottom;
14+
uint clip_radius;
15+
}
16+
17+
private const int BLUR_RADIUS = 12;
18+
19+
private static BlurManager instance;
20+
21+
public static void init (WindowManagerGala wm) {
22+
if (instance != null) {
23+
return;
24+
}
25+
26+
instance = new BlurManager (wm);
27+
}
28+
29+
public static unowned BlurManager? get_instance () {
30+
return instance;
31+
}
32+
33+
public WindowManagerGala wm { get; construct; }
34+
35+
private GLib.HashTable<Meta.Window, BlurData?> blurred_windows = new GLib.HashTable<Meta.Window, BlurData?> (null, null);
36+
37+
private BlurManager (WindowManagerGala wm) {
38+
Object (wm: wm);
39+
}
40+
41+
construct {
42+
wm.get_display ().window_created.connect ((window) => {
43+
window.notify["mutter-hints"].connect ((obj, pspec) => parse_mutter_hints ((Meta.Window) obj));
44+
parse_mutter_hints (window);
45+
});
46+
}
47+
48+
/**
49+
* Blurs the given region of the given window.
50+
*/
51+
public void add_blur (Meta.Window window, uint left, uint right, uint top, uint bottom, uint clip_radius) {
52+
unowned var window_actor = (Meta.WindowActor) window.get_compositor_private ();
53+
if (window_actor == null) {
54+
critical ("Cannot blur actor: Actor is null");
55+
return;
56+
}
57+
58+
var blur_data = blurred_windows[window];
59+
if (blur_data == null) {
60+
var blur_effect = new BackgroundBlurEffect (BLUR_RADIUS, (int) clip_radius, 1.0f);
61+
62+
var blurred_actor = new Clutter.Actor ();
63+
blurred_actor.add_effect (blur_effect);
64+
window_actor.insert_child_below (blurred_actor, null);
65+
66+
blur_data = { blurred_actor, blur_effect, left, right, top, bottom, clip_radius };
67+
blurred_windows[window] = blur_data;
68+
69+
window.size_changed.connect (on_size_changed);
70+
}
71+
72+
var buffer_rect = window.get_buffer_rect ();
73+
var frame_rect = window.get_frame_rect ();
74+
var x_shadow_size = frame_rect.x - buffer_rect.x;
75+
var y_shadow_size = frame_rect.y - buffer_rect.y;
76+
77+
blur_data.actor.set_position (x_shadow_size + left, y_shadow_size + top);
78+
blur_data.actor.set_size (frame_rect.width - left - right, frame_rect.height - top - bottom);
79+
}
80+
81+
public void remove_blur (Meta.Window window) {
82+
var blur_data = blurred_windows[window];
83+
if (blur_data == null) {
84+
return;
85+
}
86+
87+
var actor = blur_data.actor;
88+
actor.remove_effect (blur_data.blur_effect);
89+
90+
unowned var parent = actor.get_parent ();
91+
if (parent != null) {
92+
parent.remove_child (actor);
93+
}
94+
95+
blurred_windows.remove (window);
96+
}
97+
98+
private void on_size_changed (Meta.Window window) {
99+
var blur_data = blurred_windows[window];
100+
if (blur_data == null) {
101+
return;
102+
}
103+
104+
add_blur (window, blur_data.left, blur_data.right, blur_data.top, blur_data.bottom, blur_data.clip_radius);
105+
}
106+
107+
//X11 only
108+
private void parse_mutter_hints (Meta.Window window) {
109+
if (window.mutter_hints == null) {
110+
return;
111+
}
112+
113+
var mutter_hints = window.mutter_hints.split (":");
114+
foreach (var mutter_hint in mutter_hints) {
115+
var split = mutter_hint.split ("=");
116+
117+
if (split.length != 2) {
118+
continue;
119+
}
120+
121+
var key = split[0];
122+
var val = split[1];
123+
124+
switch (key) {
125+
case "blur":
126+
var split_val = val.split (",");
127+
if (split_val.length != 5) {
128+
break;
129+
}
130+
131+
uint parsed_left = 0, parsed_right = 0, parsed_top = 0, parsed_bottom = 0, parsed_clip_radius = 0;
132+
if (
133+
uint.try_parse (split_val[0], out parsed_left) &&
134+
uint.try_parse (split_val[1], out parsed_right) &&
135+
uint.try_parse (split_val[2], out parsed_top) &&
136+
uint.try_parse (split_val[3], out parsed_bottom) &&
137+
uint.try_parse (split_val[4], out parsed_clip_radius)
138+
) {
139+
add_blur (window, parsed_left, parsed_right, parsed_top, parsed_bottom, parsed_clip_radius);
140+
} else {
141+
warning ("Failed to parse %s as width and height", val);
142+
}
143+
144+
break;
145+
default:
146+
break;
147+
}
148+
}
149+
}
150+
}

src/PantheonShell.vala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ namespace Gala {
4040
set_size,
4141
set_hide_mode,
4242
request_visible_in_multitasking_view,
43+
add_blur,
44+
remove_blur,
4345
};
4446

4547
wayland_pantheon_widget_interface = {
@@ -310,6 +312,40 @@ namespace Gala {
310312
ShellClientsManager.get_instance ().request_visible_in_multitasking_view (window);
311313
}
312314

315+
internal static void add_blur (Wl.Client client, Wl.Resource resource, uint left, uint right, uint top, uint bottom, uint clip_radius) {
316+
unowned PanelSurface? panel_surface = resource.get_user_data<PanelSurface> ();
317+
if (panel_surface.wayland_surface == null) {
318+
warning ("Window tried to set blur region but wayland surface is null.");
319+
return;
320+
}
321+
322+
Meta.Window? window;
323+
panel_surface.wayland_surface.get ("window", out window, null);
324+
if (window == null) {
325+
warning ("Window tried to set blur region but wayland surface had no associated window.");
326+
return;
327+
}
328+
329+
BlurManager.get_instance ().add_blur (window, left, right, top, bottom, clip_radius);
330+
}
331+
332+
internal static void remove_blur (Wl.Client client, Wl.Resource resource) {
333+
unowned PanelSurface? panel_surface = resource.get_user_data<PanelSurface> ();
334+
if (panel_surface.wayland_surface == null) {
335+
warning ("Window tried to remove blur but wayland surface is null.");
336+
return;
337+
}
338+
339+
Meta.Window? window;
340+
panel_surface.wayland_surface.get ("window", out window, null);
341+
if (window == null) {
342+
warning ("Window tried to remove blur but wayland surface had no associated window.");
343+
return;
344+
}
345+
346+
BlurManager.get_instance ().remove_blur (window);
347+
}
348+
313349
internal static void set_keep_above (Wl.Client client, Wl.Resource resource) {
314350
unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data<ExtendedBehaviorSurface> ();
315351
if (eb_surface.wayland_surface == null) {

src/WindowManager.vala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ namespace Gala {
127127

128128
public override void start () {
129129
ShellClientsManager.init (this);
130+
BlurManager.init (this);
130131
daemon_manager = new DaemonManager (get_display ());
131132

132133
show_stage ();

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
gala_bin_sources = files(
22
'BackgroundBlurEffect.vala',
3+
'BlurManager.vala',
34
'DBus.vala',
45
'DBusAccelerator.vala',
56
'DaemonManager.vala',

0 commit comments

Comments
 (0)