Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions protocol/pantheon-desktop-shell-v1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,24 @@
Tell the shell that the panel would like to be visible in the multitasking view.
</description>
</request>

<request name="add_blur">
<description summary="add blur">
Tell the window manager to add background blur.
</description>

<arg name="left" type="uint"/>
<arg name="right" type="uint"/>
<arg name="top" type="uint"/>
<arg name="bottom" type="uint"/>
<arg name="clip_radius" type="uint"/>
</request>

<request name="remove_blur">
<description summary="remove blur">
Tell the window manager to remove blur that was set in set_blur_region.
</description>
</request>
</interface>

<interface name="io_elementary_pantheon_widget_v1" version="1">
Expand Down
6 changes: 6 additions & 0 deletions protocol/pantheon-desktop-shell.vapi
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace Pantheon.Desktop {
public SetSize set_size;
public SetHideMode set_hide_mode;
public RequestVisibleInMultitaskingView request_visible_in_multitasking_view;
public AddBlur add_blur;
public RemoveBlur remove_blur;
}

[CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")]
Expand Down Expand Up @@ -78,6 +80,10 @@ namespace Pantheon.Desktop {
[CCode (has_target = false, has_typedef = false)]
public delegate void RequestVisibleInMultitaskingView (Wl.Client client, Wl.Resource resource);
[CCode (has_target = false, has_typedef = false)]
public delegate void AddBlur (Wl.Client client, Wl.Resource resource, uint left, uint right, uint top, uint bottom, uint clip_radius);
[CCode (has_target = false, has_typedef = false)]
public delegate void RemoveBlur (Wl.Client client, Wl.Resource resource);
[CCode (has_target = false, has_typedef = false)]
public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource);
[CCode (has_target = false, has_typedef = false)]
public delegate void MakeCentered (Wl.Client client, Wl.Resource resource);
Expand Down
10 changes: 5 additions & 5 deletions src/BackgroundBlurEffect.vala
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
uniform float clip_radius;

float rounded_rect_coverage (vec2 p) {
float center_left = clip_radius + 1.5;
float center_right = actor_size.x - clip_radius - 0.55;
float center_left = clip_radius;
float center_right = actor_size.x - clip_radius;

float center_x;
if (p.x < center_left) {
Expand All @@ -80,8 +80,8 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
return 1.0;
}

float center_top = clip_radius + 1.5;
float center_bottom = actor_size.y - clip_radius - 0.55;
float center_top = clip_radius;
float center_bottom = actor_size.y - clip_radius;

float center_y;
if (p.y < center_top) {
Expand All @@ -107,7 +107,7 @@ public class Gala.BackgroundBlurEffect : Clutter.Effect {
return 1.0;
}
// Only pixels on the edge of the curve need expensive antialiasing
return outer_radius - sqrt (dist_squared);
return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
}
""",

Expand Down
150 changes: 150 additions & 0 deletions src/BlurManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Gala.BlurManager : Object {
private struct BlurData {
Clutter.Actor actor;
BackgroundBlurEffect blur_effect;
uint left;
uint right;
uint top;
uint bottom;
uint clip_radius;
}

private const int BLUR_RADIUS = 12;

private static BlurManager instance;

public static void init (WindowManagerGala wm) {
if (instance != null) {
return;
}

instance = new BlurManager (wm);
}

public static unowned BlurManager? get_instance () {
return instance;
}

public WindowManagerGala wm { get; construct; }

private GLib.HashTable<Meta.Window, BlurData?> blurred_windows = new GLib.HashTable<Meta.Window, BlurData?> (null, null);

private BlurManager (WindowManagerGala wm) {
Object (wm: wm);
}

construct {
wm.get_display ().window_created.connect ((window) => {
window.notify["mutter-hints"].connect ((obj, pspec) => parse_mutter_hints ((Meta.Window) obj));
parse_mutter_hints (window);
});
}

/**
* Blurs the given region of the given window.
*/
public void add_blur (Meta.Window window, uint left, uint right, uint top, uint bottom, uint clip_radius) {
unowned var window_actor = (Meta.WindowActor) window.get_compositor_private ();
if (window_actor == null) {
critical ("Cannot blur actor: Actor is null");
return;
}

var blur_data = blurred_windows[window];
if (blur_data == null) {
var blur_effect = new BackgroundBlurEffect (BLUR_RADIUS, (int) clip_radius, 1.0f);

var blurred_actor = new Clutter.Actor ();
blurred_actor.add_effect (blur_effect);
window_actor.insert_child_below (blurred_actor, null);

blur_data = { blurred_actor, blur_effect, left, right, top, bottom, clip_radius };
blurred_windows[window] = blur_data;

window.size_changed.connect (on_size_changed);
}

var buffer_rect = window.get_buffer_rect ();
var frame_rect = window.get_frame_rect ();
var x_shadow_size = frame_rect.x - buffer_rect.x;
var y_shadow_size = frame_rect.y - buffer_rect.y;

blur_data.actor.set_position (x_shadow_size + left, y_shadow_size + top);
blur_data.actor.set_size (frame_rect.width - left - right, frame_rect.height - top - bottom);
}

public void remove_blur (Meta.Window window) {
var blur_data = blurred_windows[window];
if (blur_data == null) {
return;
}

var actor = blur_data.actor;
actor.remove_effect (blur_data.blur_effect);

unowned var parent = actor.get_parent ();
if (parent != null) {
parent.remove_child (actor);
}

blurred_windows.remove (window);
}

private void on_size_changed (Meta.Window window) {
var blur_data = blurred_windows[window];
if (blur_data == null) {
return;
}

add_blur (window, blur_data.left, blur_data.right, blur_data.top, blur_data.bottom, blur_data.clip_radius);
}

//X11 only
private void parse_mutter_hints (Meta.Window window) {
if (window.mutter_hints == null) {
return;
}

var mutter_hints = window.mutter_hints.split (":");
foreach (var mutter_hint in mutter_hints) {
var split = mutter_hint.split ("=");

if (split.length != 2) {
continue;
}

var key = split[0];
var val = split[1];

switch (key) {
case "blur":
var split_val = val.split (",");
if (split_val.length != 5) {
break;
}

uint parsed_left = 0, parsed_right = 0, parsed_top = 0, parsed_bottom = 0, parsed_clip_radius = 0;
if (
uint.try_parse (split_val[0], out parsed_left) &&
uint.try_parse (split_val[1], out parsed_right) &&
uint.try_parse (split_val[2], out parsed_top) &&
uint.try_parse (split_val[3], out parsed_bottom) &&
uint.try_parse (split_val[4], out parsed_clip_radius)
) {
add_blur (window, parsed_left, parsed_right, parsed_top, parsed_bottom, parsed_clip_radius);
} else {
warning ("Failed to parse %s as width and height", val);
}

break;
default:
break;
}
}
}
}
36 changes: 36 additions & 0 deletions src/PantheonShell.vala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ namespace Gala {
set_size,
set_hide_mode,
request_visible_in_multitasking_view,
add_blur,
remove_blur,
};

wayland_pantheon_widget_interface = {
Expand Down Expand Up @@ -310,6 +312,40 @@ namespace Gala {
ShellClientsManager.get_instance ().request_visible_in_multitasking_view (window);
}

internal static void add_blur (Wl.Client client, Wl.Resource resource, uint left, uint right, uint top, uint bottom, uint clip_radius) {
unowned PanelSurface? panel_surface = resource.get_user_data<PanelSurface> ();
if (panel_surface.wayland_surface == null) {
warning ("Window tried to set blur region but wayland surface is null.");
return;
}

Meta.Window? window;
panel_surface.wayland_surface.get ("window", out window, null);
if (window == null) {
warning ("Window tried to set blur region but wayland surface had no associated window.");
return;
}

BlurManager.get_instance ().add_blur (window, left, right, top, bottom, clip_radius);
}

internal static void remove_blur (Wl.Client client, Wl.Resource resource) {
unowned PanelSurface? panel_surface = resource.get_user_data<PanelSurface> ();
if (panel_surface.wayland_surface == null) {
warning ("Window tried to remove blur but wayland surface is null.");
return;
}

Meta.Window? window;
panel_surface.wayland_surface.get ("window", out window, null);
if (window == null) {
warning ("Window tried to remove blur but wayland surface had no associated window.");
return;
}

BlurManager.get_instance ().remove_blur (window);
}

internal static void set_keep_above (Wl.Client client, Wl.Resource resource) {
unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data<ExtendedBehaviorSurface> ();
if (eb_surface.wayland_surface == null) {
Expand Down
1 change: 1 addition & 0 deletions src/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ namespace Gala {

public override void start () {
ShellClientsManager.init (this);
BlurManager.init (this);
daemon_manager = new DaemonManager (get_display ());

show_stage ();
Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
gala_bin_sources = files(
'BackgroundBlurEffect.vala',
'BlurManager.vala',
'DBus.vala',
'DBusAccelerator.vala',
'DaemonManager.vala',
Expand Down