gtk(wayland): add support for background blur on KDE Plasma

This commit is contained in:
Leah Amelia Chen
2025-01-02 21:44:16 +08:00
committed by Mitchell Hashimoto
parent 31439f311d
commit 9184395cba
5 changed files with 80 additions and 5 deletions

View File

@ -1479,7 +1479,14 @@ fn addDeps(
const wayland = b.createModule(.{ .root_source_file = scanner.result });
const plasma_wayland_protocols = b.dependency("plasma_wayland_protocols", .{
.target = target,
.optimize = optimize,
});
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml"));
scanner.generate("wl_compositor", 1);
scanner.generate("org_kde_kwin_blur_manager", 1);
step.root_module.addImport("wayland", wayland);
step.linkSystemLibrary2("wayland-client", dynamic_link_opts);

View File

@ -847,9 +847,11 @@ fn configChange(
new_config: *const Config,
) void {
switch (target) {
// We don't do anything for surface config change events. There
// is nothing to sync with regards to a surface today.
.surface => {},
.surface => |surface| {
if (surface.rt_surface.container.window()) |window| window.syncAppearance(new_config) catch |err| {
log.warn("error syncing appearance changes to window err={}", .{err});
};
},
.app => {
// We clone (to take ownership) and update our configuration.

View File

@ -392,6 +392,17 @@ pub fn init(self: *Window, app: *App) !void {
c.gtk_widget_show(window);
}
/// Updates appearance based on config settings. Will be called once upon window
/// realization, and every time the config is reloaded.
///
/// TODO: Many of the initial style settings in `create` could possibly be made
/// reactive by moving them here.
pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
if (self.wayland) |*wl| {
try wl.setBlur(config.@"background-blur-radius" > 0);
}
}
/// Sets up the GTK actions for the window scope. Actions are how GTK handles
/// menus and such. The menu is defined in App.zig but the action is defined
/// here. The string name binds them.
@ -565,6 +576,10 @@ fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
self.wayland = wayland.SurfaceState.init(v, wl);
}
self.syncAppearance(&self.app.config) catch |err| {
log.err("failed to initialize appearance={}", .{err});
};
return true;
}

View File

@ -2,6 +2,7 @@ const std = @import("std");
const c = @import("c.zig").c;
const wayland = @import("wayland");
const wl = wayland.client.wl;
const org = wayland.client.org;
const build_options = @import("build_options");
const log = std.log.scoped(.gtk_wayland);
@ -9,6 +10,7 @@ const log = std.log.scoped(.gtk_wayland);
/// Wayland state that contains application-wide Wayland objects (e.g. wl_display).
pub const AppState = struct {
display: *wl.Display,
blur_manager: ?*org.KdeKwinBlurManager = null,
pub fn init(display: ?*c.GdkDisplay) ?AppState {
if (comptime !build_options.wayland) return null;
@ -45,6 +47,9 @@ pub const SurfaceState = struct {
app_state: *AppState,
surface: *wl.Surface,
/// A token that, when present, indicates that the window is blurred.
blur_token: ?*org.KdeKwinBlur = null,
pub fn init(window: *c.GtkWindow, app_state: *AppState) ?SurfaceState {
if (comptime !build_options.wayland) return null;
@ -66,6 +71,32 @@ pub const SurfaceState = struct {
}
pub fn deinit(self: *SurfaceState) void {
if (self.blur_token) |blur| blur.release();
}
pub fn setBlur(self: *SurfaceState, blurred: bool) !void {
log.debug("setting blur={}", .{blurred});
const mgr = self.app_state.blur_manager orelse {
log.warn("can't set blur: org_kde_kwin_blur_manager protocol unavailable", .{});
return;
};
if (self.blur_token) |blur| {
// Only release token when transitioning from blurred -> not blurred
if (!blurred) {
mgr.unset(self.surface);
blur.release();
self.blur_token = null;
}
} else {
// Only acquire token when transitioning from not blurred -> blurred
if (blurred) {
const blur_token = try mgr.create(self.surface);
blur_token.commit();
self.blur_token = blur_token;
}
}
}
};
@ -73,6 +104,10 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *Ap
switch (event) {
.global => |global| {
log.debug("got global interface={s}", .{global.interface});
if (bindInterface(org.KdeKwinBlurManager, registry, global, 1)) |iface| {
state.blur_manager = iface;
return;
}
},
.global_remove => {},
}

View File

@ -583,11 +583,27 @@ palette: Palette = .{},
@"background-opacity": f64 = 1.0,
/// A positive value enables blurring of the background when background-opacity
/// is less than 1. The value is the blur radius to apply. A value of 20
/// is less than 1.
///
/// On macOS, the value is the blur radius to apply. A value of 20
/// is reasonable for a good looking blur. Higher values will cause strange
/// rendering issues as well as performance issues.
///
/// This is only supported on macOS.
/// On KDE Plasma under Wayland, the exact value is _ignored_ the reason is
/// that KWin, the window compositor powering Plasma, only has one global blur
/// setting and does not allow applications to have individual blur settings.
///
/// To configure KWin's global blur setting, open System Settings and go to
/// "Apps & Windows" > "Window Management" > "Desktop Effects" and select the
/// "Blur" plugin. If disabled, enable it by ticking the checkbox to the left.
/// Then click on the "Configure" button and there will be two sliders that
/// allow you to set background blur and noise strengths for all apps,
/// including Ghostty.
///
/// All other Linux desktop environments are as of now unsupported. Users may
/// need to set environment-specific settings and/or install third-party plugins
/// in order to support background blur, as there isn't a unified interface for
/// doing so.
@"background-blur-radius": u8 = 0,
/// The opacity level (opposite of transparency) of an unfocused split.