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.
pull/10727/head
Leah Amelia Chen 2026-03-18 02:07:28 +08:00
parent d9070dbee2
commit 9e2e99c55f
No known key found for this signature in database
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,