From 9184395cbaf7c5db4c87dba2493328173eececa5 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Thu, 2 Jan 2025 21:44:16 +0800 Subject: [PATCH] gtk(wayland): add support for background blur on KDE Plasma --- build.zig | 7 +++++++ src/apprt/gtk/App.zig | 8 +++++--- src/apprt/gtk/Window.zig | 15 +++++++++++++++ src/apprt/gtk/wayland.zig | 35 +++++++++++++++++++++++++++++++++++ src/config/Config.zig | 20 ++++++++++++++++++-- 5 files changed, 80 insertions(+), 5 deletions(-) diff --git a/build.zig b/build.zig index 6c030a9c5..ceb7ed381 100644 --- a/build.zig +++ b/build.zig @@ -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); diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index b43f79274..3cc1782c8 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -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. diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index d41fda138..26598d03a 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -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; } diff --git a/src/apprt/gtk/wayland.zig b/src/apprt/gtk/wayland.zig index 034309812..92446cc46 100644 --- a/src/apprt/gtk/wayland.zig +++ b/src/apprt/gtk/wayland.zig @@ -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 => {}, } diff --git a/src/config/Config.zig b/src/config/Config.zig index e31b9c5e9..7c25d5095 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -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.