gtk/wayland: replace KDE blur with ext-background-effect-v1

The venerable KDE blur protocol has been replaced with the compositor-
agnostic ext-background-effect-v1 protocol, to be implemented by Niri and
others. The new protocol is much easier to use overall, though we do need
to calculate the blur region manually like X11.
This commit is contained in:
Leah Amelia Chen
2026-03-18 02:07:28 +08:00
parent d9070dbee2
commit 9e2e99c55f
9 changed files with 116 additions and 46 deletions

View File

@@ -91,8 +91,8 @@
.lazy = true,
},
.wayland_protocols = .{
.url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
.hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
.url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
.hash = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
.lazy = true,
},
.plasma_wayland_protocols = .{

5
build.zig.zon.json generated
View File

@@ -139,6 +139,11 @@
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
},
"N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": {
"name": "wayland_protocols",
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
"hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="
},
"N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": {
"name": "wuffs",
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",

8
build.zig.zon.nix generated
View File

@@ -306,6 +306,14 @@ in
hash = "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=";
};
}
{
name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA";
path = fetchZigArtifact {
name = "wayland_protocols";
url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz";
hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=";
};
}
{
name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs";
path = fetchZigArtifact {

1
build.zig.zon.txt generated
View File

@@ -34,3 +34,4 @@ https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz

View File

@@ -167,6 +167,12 @@
"dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
"sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8"
},
{
"type": "archive",
"url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz",
"dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA",
"sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",

View File

@@ -1071,21 +1071,6 @@ pub const Window = extern struct {
self.syncAppearance();
}
fn propGdkSurfaceHeight(
_: *gdk.Surface,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
// X11 needs to fix blurring on resize, but winproto implementations
// could do anything.
self.private().winproto.resizeEvent() catch |err| {
log.warn(
"winproto resize event failed error={}",
.{err},
);
};
}
fn propIsActive(
_: *gtk.Window,
_: *gobject.ParamSpec,
@@ -1111,7 +1096,7 @@ pub const Window = extern struct {
};
}
fn propGdkSurfaceWidth(
fn propGdkSurfaceDims(
_: *gdk.Surface,
_: *gobject.ParamSpec,
self: *Self,
@@ -1282,14 +1267,14 @@ pub const Window = extern struct {
_ = gobject.Object.signals.notify.connect(
gdk_surface,
*Self,
propGdkSurfaceWidth,
propGdkSurfaceDims,
self,
.{ .detail = "width" },
);
_ = gobject.Object.signals.notify.connect(
gdk_surface,
*Self,
propGdkSurfaceHeight,
propGdkSurfaceDims,
self,
.{ .detail = "height" },
);

View File

@@ -10,6 +10,7 @@ const layer_shell = @import("gtk4-layer-shell");
const wayland = @import("wayland");
const wl = wayland.client.wl;
const ext = wayland.client.ext;
const kde = wayland.client.kde;
const org = wayland.client.org;
const xdg = wayland.client.xdg;
@@ -96,8 +97,8 @@ pub const Window = struct {
/// The context from the app where we can load our Wayland interfaces.
globals: *Globals,
/// A token that, when present, indicates that the window is blurred.
blur_token: ?*org.KdeKwinBlur = null,
/// Object that controls background effects like background blur.
bg_effect: ?*ext.BackgroundEffectSurfaceV1 = null,
/// Object that controls the decoration mode (client/server/auto)
/// of the window.
@@ -148,6 +149,20 @@ pub const Window = struct {
break :deco deco;
};
const bg_effect: ?*ext.BackgroundEffectSurfaceV1 = bg: {
const mgr = app.globals.get(.ext_background_effect) orelse
break :bg null;
const bg_effect: *ext.BackgroundEffectSurfaceV1 = mgr.getBackgroundEffect(
wl_surface,
) catch |err| {
log.warn("could not create background effect object={}", .{err});
break :bg null;
};
break :bg bg_effect;
};
if (apprt_window.isQuickTerminal()) {
_ = gdk.Surface.signals.enter_monitor.connect(
gdk_surface,
@@ -163,17 +178,22 @@ pub const Window = struct {
.surface = wl_surface,
.globals = app.globals,
.decoration = deco,
.bg_effect = bg_effect,
};
}
pub fn deinit(self: Window, alloc: Allocator) void {
_ = alloc;
if (self.blur_token) |blur| blur.release();
if (self.bg_effect) |bg| bg.destroy();
if (self.decoration) |deco| deco.release();
if (self.slide) |slide| slide.release();
}
pub fn resizeEvent(_: *Window) !void {}
pub fn resizeEvent(self: *Window) !void {
self.syncBlur() catch |err| {
log.err("failed to sync blur={}", .{err});
};
}
pub fn syncAppearance(self: *Window) !void {
self.syncBlur() catch |err| {
@@ -224,28 +244,53 @@ pub const Window = struct {
/// Update the blur state of the window.
fn syncBlur(self: *Window) !void {
const manager = self.globals.get(.kde_blur_manager) orelse return;
const compositor = self.globals.get(.compositor) orelse return;
const bg = self.bg_effect orelse return;
if (!self.globals.state.bg_effect_capabilities.blur) return;
const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return;
const blur = config.@"background-blur";
if (self.blur_token) |tok| {
// Only release token when transitioning from blurred -> not blurred
if (!blur.enabled()) {
manager.unset(self.surface);
tok.release();
self.blur_token = null;
}
} else {
// Only acquire token when transitioning from not blurred -> blurred
if (blur.enabled()) {
const tok = try manager.create(self.surface);
tok.commit();
self.blur_token = tok;
}
}
const region = region: {
if (!blur.enabled()) break :region null;
// NOTE(pluiedev): CSDs are a f--king mistake.
// Please, GNOME, stop this nonsense of making a window ~30% bigger
// internally than how they really are just for your shadows and
// rounded corners and all that fluff. Please. I beg of you.
const native = self.apprt_window.as(gtk.Native);
const surface = native.getSurface() orelse break :region null;
const region = try compositor.createRegion();
var x: f64 = 0;
var y: f64 = 0;
native.getSurfaceTransform(&x, &y);
// Slightly inset the blur region
x += 1;
y += 1;
var width: f64 = @floatFromInt(surface.getWidth());
var height: f64 = @floatFromInt(surface.getHeight());
width -= x * 2;
height -= y * 2;
if (width <= 0 or height <= 0) break :region null;
// FIXME: Add rounded corners
region.add(
@intFromFloat(x),
@intFromFloat(y),
@intFromFloat(width),
@intFromFloat(height),
);
break :region region;
};
errdefer if (region) |r| r.destroy();
bg.setBlurRegion(region);
}
fn syncDecoration(self: *Window) !void {

View File

@@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
const wayland = @import("wayland");
const wl = wayland.client.wl;
const ext = wayland.client.ext;
const kde = wayland.client.kde;
const org = wayland.client.org;
const xdg = wayland.client.xdg;
@@ -26,7 +27,8 @@ const Binding = struct {
};
pub const Tag = enum {
kde_blur_manager,
compositor,
ext_background_effect,
kde_decoration_manager,
kde_slide_manager,
kde_output_order,
@@ -34,7 +36,8 @@ pub const Tag = enum {
fn Type(comptime self: Tag) type {
return switch (self) {
.kde_blur_manager => org.KdeKwinBlurManager,
.compositor => wl.Compositor,
.ext_background_effect => ext.BackgroundEffectManagerV1,
.kde_decoration_manager => org.KdeKwinServerDecorationManager,
.kde_slide_manager => org.KdeKwinSlideManager,
.kde_output_order => kde.OutputOrderV1,
@@ -56,6 +59,8 @@ pub const State = struct {
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
bg_effect_capabilities: ext.BackgroundEffectManagerV1.Capability = .{},
/// Reset cached state derived from kde_output_order_v1.
fn resetOutputOrder(self: *State, alloc: Allocator) void {
if (self.primary_output_name) |name| alloc.free(name);
@@ -102,6 +107,11 @@ fn onGlobalAttached(self: *Globals, comptime tag: Tag) void {
// keeps listener setup and object lifetime in one
// place and also supports globals that appear later.
switch (tag) {
.ext_background_effect => {
const v = self.get(tag) orelse return;
v.setListener(*Globals, bgEffectListener, self);
self.needs_roundtrip = true;
},
.kde_decoration_manager => {
const v = self.get(tag) orelse return;
v.setListener(*Globals, decoManagerListener, self);
@@ -179,6 +189,18 @@ fn registryListener(
}
}
fn bgEffectListener(
_: *ext.BackgroundEffectManagerV1,
event: ext.BackgroundEffectManagerV1.Event,
self: *Globals,
) void {
switch (event) {
.capabilities => |cap| {
self.state.bg_effect_capabilities = cap.flags;
},
}
}
fn decoManagerListener(
_: *org.KdeKwinServerDecorationManager,
event: org.KdeKwinServerDecorationManager.Event,

View File

@@ -626,9 +626,6 @@ fn addGtkNg(
.wayland_protocols = wayland_protocols_dep.path(""),
});
scanner.addCustomProtocol(
plasma_wayland_protocols_dep.path("src/protocols/blur.xml"),
);
// FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
scanner.addCustomProtocol(
plasma_wayland_protocols_dep.path("src/protocols/server-decoration.xml"),
@@ -640,13 +637,14 @@ fn addGtkNg(
plasma_wayland_protocols_dep.path("src/protocols/kde-output-order-v1.xml"),
);
scanner.addSystemProtocol("staging/xdg-activation/xdg-activation-v1.xml");
scanner.addSystemProtocol("staging/ext-background-effect/ext-background-effect-v1.xml");
scanner.generate("wl_compositor", 1);
scanner.generate("org_kde_kwin_blur_manager", 1);
scanner.generate("org_kde_kwin_server_decoration_manager", 1);
scanner.generate("org_kde_kwin_slide_manager", 1);
scanner.generate("kde_output_order_v1", 1);
scanner.generate("xdg_activation_v1", 1);
scanner.generate("ext_background_effect_manager_v1", 1);
step.root_module.addImport("wayland", b.createModule(.{
.root_source_file = scanner.result,