Skip to content

Commit abf0769

Browse files
committed
Add Blur API
1 parent 8321358 commit abf0769

File tree

7 files changed

+232
-1
lines changed

7 files changed

+232
-1
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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
*/
55

66
public class Gala.BackgroundBlurEffect : Clutter.Effect {
7+
/**
8+
* Our rounded corners effect is antialiased, so we need to add a small offset to have proper corners
9+
*/
10+
private const int CLIP_RADIUS_OFFSET = 2;
711
private const float MIN_DOWNSCALE_SIZE = 256.0f;
812
private const float MAX_RADIUS = 12.0f;
913
private const int FORCE_REFRESH_FRAMES = 2;
@@ -145,7 +149,7 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
145149
}
146150

147151
private void update_clip_radius () {
148-
float[] _clip_radius = { clip_radius * monitor_scale };
152+
float[] _clip_radius = { clip_radius * monitor_scale + CLIP_RADIUS_OFFSET };
149153
round_pipeline.set_uniform_float (round_clip_radius_location, 1, 1, _clip_radius);
150154
}
151155

src/BlurManager.vala

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
unowned var monitor_manager = wm.get_display ().get_context ().get_backend ().get_monitor_manager ();
48+
monitor_manager.monitors_changed.connect (update_monitors);
49+
}
50+
51+
/**
52+
* Blurs the given region of the given window.
53+
*/
54+
public void add_blur (Meta.Window window, uint left, uint right, uint top, uint bottom, uint clip_radius) {
55+
unowned var window_actor = (Meta.WindowActor) window.get_compositor_private ();
56+
if (window_actor == null) {
57+
critical ("Cannot blur actor: Actor is null");
58+
return;
59+
}
60+
61+
var monitor_scaling_factor = wm.get_display ().get_monitor_scale (window.get_monitor ());
62+
63+
var blur_data = blurred_windows[window];
64+
if (blur_data == null) {
65+
var blur_effect = new BackgroundBlurEffect (BLUR_RADIUS, clip_radius, monitor_scaling_factor);
66+
67+
var blurred_actor = new Clutter.Actor ();
68+
blurred_actor.add_effect (blur_effect);
69+
window_actor.insert_child_below (blurred_actor, null);
70+
71+
blur_data = { blurred_actor, blur_effect, left, right, top, bottom, clip_radius };
72+
blurred_windows[window] = blur_data;
73+
74+
window.size_changed.connect (on_size_changed);
75+
}
76+
77+
var buffer_rect = window.get_buffer_rect ();
78+
var frame_rect = window.get_frame_rect ();
79+
var x_shadow_size = (frame_rect.x - buffer_rect.x) / monitor_scaling_factor;
80+
var y_shadow_size = (frame_rect.y - buffer_rect.y) / monitor_scaling_factor;
81+
82+
blur_data.actor.set_position (x_shadow_size + left, y_shadow_size + top);
83+
blur_data.actor.set_size (frame_rect.width - left - right, frame_rect.height - top - bottom);
84+
blur_data.blur_effect.monitor_scale = monitor_scaling_factor;
85+
}
86+
87+
public void remove_blur (Meta.Window window) {
88+
var blur_data = blurred_windows[window];
89+
if (blur_data == null) {
90+
return;
91+
}
92+
93+
var actor = blur_data.actor;
94+
actor.remove_effect (blur_data.blur_effect);
95+
96+
unowned var parent = actor.get_parent ();
97+
if (parent != null) {
98+
parent.remove_child (actor);
99+
}
100+
101+
blurred_windows.remove (window);
102+
}
103+
104+
private void on_size_changed (Meta.Window window) {
105+
var blur_data = blurred_windows[window];
106+
if (blur_data == null) {
107+
return;
108+
}
109+
110+
add_blur (window, blur_data.left, blur_data.right, blur_data.top, blur_data.bottom, blur_data.clip_radius);
111+
}
112+
113+
private void update_monitors () {
114+
foreach (unowned var window in blurred_windows.get_keys ()) {
115+
var blur_data = blurred_windows[window];
116+
117+
var monitor_scaling_factor = window.display.get_monitor_scale (window.get_monitor ());
118+
blur_data.blur_effect.monitor_scale = monitor_scaling_factor;
119+
}
120+
}
121+
122+
//X11 only
123+
private void parse_mutter_hints (Meta.Window window) {
124+
if (window.mutter_hints == null) {
125+
return;
126+
}
127+
128+
var mutter_hints = window.mutter_hints.split (":");
129+
foreach (var mutter_hint in mutter_hints) {
130+
var split = mutter_hint.split ("=");
131+
132+
if (split.length != 2) {
133+
continue;
134+
}
135+
136+
var key = split[0];
137+
var val = split[1];
138+
139+
switch (key) {
140+
case "blur":
141+
var split_val = val.split (",");
142+
if (split_val.length != 5) {
143+
break;
144+
}
145+
146+
uint parsed_left = 0, parsed_right = 0, parsed_top = 0, parsed_bottom = 0, parsed_clip_radius = 0;
147+
if (
148+
uint.try_parse (split_val[0], out parsed_left) &&
149+
uint.try_parse (split_val[1], out parsed_right) &&
150+
uint.try_parse (split_val[2], out parsed_top) &&
151+
uint.try_parse (split_val[3], out parsed_bottom) &&
152+
uint.try_parse (split_val[4], out parsed_clip_radius)
153+
) {
154+
add_blur (window, parsed_left, parsed_right, parsed_top, parsed_bottom, parsed_clip_radius);
155+
} else {
156+
warning ("Failed to parse %s as width and height", val);
157+
}
158+
159+
break;
160+
default:
161+
break;
162+
}
163+
}
164+
}
165+
}

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)