Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
57 changes: 45 additions & 12 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig");
const Version = @import("src/build/Version.zig");
const Command = @import("src/Command.zig");

const Scanner = @import("zig_wayland").Scanner;

comptime {
// This is the required Zig version for building this project. We allow
// any patch version but the major and minor must match exactly.
Expand Down Expand Up @@ -105,19 +107,19 @@ pub fn build(b: *std.Build) !void {
"Enables the use of Adwaita when using the GTK rendering backend.",
) orelse true;

config.x11 = b.option(
bool,
"gtk-x11",
"Enables linking against X11 libraries when using the GTK rendering backend.",
) orelse x11: {
if (target.result.os.tag != .linux) break :x11 false;
var x11 = false;
var wayland = false;

if (target.result.os.tag == .linux) pkgconfig: {
var pkgconfig = std.process.Child.init(&.{ "pkg-config", "--variable=targets", "gtk4" }, b.allocator);

pkgconfig.stdout_behavior = .Pipe;
pkgconfig.stderr_behavior = .Pipe;

try pkgconfig.spawn();
pkgconfig.spawn() catch |err| {
std.log.warn("failed to spawn pkg-config - disabling X11 and Wayland integrations: {}", .{err});
break :pkgconfig;
};

const output_max_size = 50 * 1024;

Expand All @@ -139,18 +141,31 @@ pub fn build(b: *std.Build) !void {
switch (term) {
.Exited => |code| {
if (code == 0) {
if (std.mem.indexOf(u8, stdout.items, "x11")) |_| break :x11 true;
break :x11 false;
if (std.mem.indexOf(u8, stdout.items, "x11")) |_| x11 = true;
if (std.mem.indexOf(u8, stdout.items, "wayland")) |_| wayland = true;
} else {
std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code });
return error.Unexpected;
pluiedev marked this conversation as resolved.
Show resolved Hide resolved
}
std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code });
break :x11 false;
},
inline else => |code| {
std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code });
return error.Unexpected;
},
}
};
}

config.x11 = b.option(
bool,
"gtk-x11",
"Enables linking against X11 libraries when using the GTK rendering backend.",
) orelse x11;

config.wayland = b.option(
bool,
"gtk-wayland",
"Enables linking against Wayland libraries when using the GTK rendering backend.",
) orelse wayland;

config.sentry = b.option(
bool,
Expand Down Expand Up @@ -1459,6 +1474,24 @@ fn addDeps(
if (config.adwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts);
if (config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts);

if (config.wayland) {
const scanner = Scanner.create(b, .{});

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);
}

{
const gresource = @import("src/apprt/gtk/gresource.zig");

Expand Down
8 changes: 8 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
.url = "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz",
.hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
},
.zig_wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/a5e2e9b6a6d7fba638ace4d4b24a3b576a02685b.tar.gz",
.hash = "1220d41b23ae70e93355bb29dac1c07aa6aeb92427a2dffc4375e94b4de18111248c",
},

// C libs
.cimgui = .{ .path = "./pkg/cimgui" },
Expand Down Expand Up @@ -64,5 +68,9 @@
.url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
.hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
},
.plasma_wayland_protocols = .{
.url = "git+https://invent.kde.org/libraries/plasma-wayland-protocols.git?ref=master#db525e8f9da548cffa2ac77618dd0fbe7f511b86",
.hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
},
},
}
7 changes: 7 additions & 0 deletions nix/devShell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
pandoc,
hyperfine,
typos,
wayland,
wayland-scanner,
wayland-protocols,
}: let
# See package.nix. Keep in sync.
rpathLibs =
Expand Down Expand Up @@ -80,6 +83,7 @@
libadwaita
gtk4
glib
wayland
];
in
mkShell {
Expand Down Expand Up @@ -153,6 +157,9 @@ in
libadwaita
gtk4
glib
wayland
wayland-scanner
wayland-protocols
];

# This should be set onto the rpath of the ghostty binary if you want
Expand Down
44 changes: 29 additions & 15 deletions nix/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
oniguruma,
zlib,
libGL,
libX11,
libXcursor,
libXi,
libXrandr,
glib,
gtk4,
libadwaita,
Expand All @@ -26,7 +22,17 @@
pandoc,
revision ? "dirty",
optimize ? "Debug",
x11 ? true,

enableX11 ? true,
libX11,
libXcursor,
libXi,
libXrandr,

enableWayland ? true,
pluiedev marked this conversation as resolved.
Show resolved Hide resolved
wayland,
wayland-protocols,
wayland-scanner,
}: let
# The Zig hook has no way to select the release type without actual
# overriding of the default flags.
Expand Down Expand Up @@ -114,14 +120,19 @@ in
version = "1.0.2";
inherit src;

nativeBuildInputs = [
git
ncurses
pandoc
pkg-config
zig_hook
wrapGAppsHook4
];
nativeBuildInputs =
[
git
ncurses
pandoc
pkg-config
zig_hook
wrapGAppsHook4
]
++ lib.optionals enableWayland [
wayland-scanner
wayland-protocols
];

buildInputs =
[
Expand All @@ -142,16 +153,19 @@ in
glib
gsettings-desktop-schemas
]
++ lib.optionals x11 [
++ lib.optionals enableX11 [
libX11
libXcursor
libXi
libXrandr
]
++ lib.optionals enableWayland [
wayland
];

dontConfigure = true;

zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix -Dgtk-x11=${lib.boolToString x11}";
zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix -Dgtk-x11=${lib.boolToString enableX11} -Dgtk-wayland=${lib.boolToString enableWayland}";

preBuild = ''
rm -rf $ZIG_GLOBAL_CACHE_DIR
Expand Down
2 changes: 1 addition & 1 deletion nix/zigCacheHash.nix
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"sha256-l+tZVL18qhm8BoBsQVbKfYmXQVObD0QMzQe6VBM/8Oo="
"sha256-eUY6MS3//r6pA/w9b+E4+YqmqUbzpUfL3afJJlnMhLY="
2 changes: 1 addition & 1 deletion src/apprt/embedded.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1953,7 +1953,7 @@ pub const CAPI = struct {
_ = CGSSetWindowBackgroundBlurRadius(
CGSDefaultConnectionForThread(),
nswindow.msgSend(usize, objc.sel("windowNumber"), .{}),
@intCast(config.@"background-blur-radius"),
@intCast(config.@"background-blur-radius".cval()),
);
}

Expand Down
17 changes: 14 additions & 3 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const version = @import("version.zig");
const inspector = @import("inspector.zig");
const key = @import("key.zig");
const x11 = @import("x11.zig");
const wayland = @import("wayland.zig");
const testing = std.testing;

const log = std.log.scoped(.gtk);
Expand Down Expand Up @@ -73,6 +74,9 @@ running: bool = true,
/// Xkb state (X11 only). Will be null on Wayland.
x11_xkb: ?x11.Xkb = null,

/// Wayland app state. Will be null on X11.
wayland: ?wayland.AppState = null,

/// The base path of the transient cgroup used to put all surfaces
/// into their own cgroup. This is only set if cgroups are enabled
/// and initialization was successful.
Expand Down Expand Up @@ -397,6 +401,10 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
break :x11_xkb try x11.Xkb.init(display);
};

// Initialize Wayland state
var wl = wayland.AppState.init(display);
if (wl) |*w| try w.register();

// This just calls the `activate` signal but its part of the normal startup
// routine so we just call it, but only if the config allows it (this allows
// for launching Ghostty in the "background" without immediately opening
Expand All @@ -422,6 +430,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
.ctx = ctx,
.cursor_none = cursor_none,
.x11_xkb = x11_xkb,
.wayland = wl,
.single_instance = single_instance,
// If we are NOT the primary instance, then we never want to run.
// This means that another instance of the GTK app is running and
Expand Down Expand Up @@ -838,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.
Expand Down
48 changes: 43 additions & 5 deletions src/apprt/gtk/Window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const gtk_key = @import("key.zig");
const Notebook = @import("notebook.zig").Notebook;
const HeaderBar = @import("headerbar.zig").HeaderBar;
const version = @import("version.zig");
const wayland = @import("wayland.zig");

const log = std.log.scoped(.gtk);

Expand Down Expand Up @@ -55,6 +56,8 @@ toast_overlay: ?*c.GtkWidget,
/// See adwTabOverviewOpen for why we have this.
adw_tab_overview_focus_timer: ?c.guint = null,

wayland: ?wayland.SurfaceState,

pub fn create(alloc: Allocator, app: *App) !*Window {
// Allocate a fixed pointer for our window. We try to minimize
// allocations but windows and other GUI requirements are so minimal
Expand All @@ -79,6 +82,7 @@ pub fn init(self: *Window, app: *App) !void {
.notebook = undefined,
.context_menu = undefined,
.toast_overlay = undefined,
.wayland = null,
};

// Create the window
Expand Down Expand Up @@ -115,11 +119,6 @@ pub fn init(self: *Window, app: *App) !void {
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window-theme-ghostty");
}

// Remove the window's background if any of the widgets need to be transparent
if (app.config.@"background-opacity" < 1) {
c.gtk_widget_remove_css_class(@ptrCast(window), "background");
}

// Create our box which will hold our widgets in the main content area.
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);

Expand Down Expand Up @@ -290,6 +289,7 @@ pub fn init(self: *Window, app: *App) !void {

// All of our events
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(&gtkRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(window, "realize", c.G_CALLBACK(&gtkRealize), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(window, "close-request", c.G_CALLBACK(&gtkCloseRequest), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(&gtkDestroy), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(&gtkKeyPressed), self, null, c.G_CONNECT_DEFAULT);
Expand Down Expand Up @@ -387,6 +387,28 @@ 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 (config.@"background-opacity" < 1) {
c.gtk_widget_remove_css_class(@ptrCast(self.window), "background");
} else {
c.gtk_widget_add_css_class(@ptrCast(self.window), "background");
}

if (self.wayland) |*wl| {
const blurred = switch (config.@"background-blur-radius") {
.false => false,
.true => true,
.radius => |v| v > 0,
};
try wl.setBlur(blurred);
}
}

/// 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.
Expand Down Expand Up @@ -424,6 +446,8 @@ fn initActions(self: *Window) void {
pub fn deinit(self: *Window) void {
c.gtk_widget_unparent(@ptrCast(self.context_menu));

if (self.wayland) |*wl| wl.deinit();

if (self.adw_tab_overview_focus_timer) |timer| {
_ = c.g_source_remove(timer);
}
Expand Down Expand Up @@ -551,6 +575,20 @@ pub fn sendToast(self: *Window, title: [:0]const u8) void {
c.adw_toast_overlay_add_toast(@ptrCast(toast_overlay), toast);
}

fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
const self = userdataSelf(ud.?);

if (self.app.wayland) |*wl| {
self.wayland = wayland.SurfaceState.init(v, wl);
}

self.syncAppearance(&self.app.config) catch |err| {
log.err("failed to initialize appearance={}", .{err});
};

return true;
}

// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
// sends an undefined value.
fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
Expand Down
Loading
Loading