gtk: build gtk4-layer-shell ourselves

As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed, etc.)
and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41, etc.)
This is inconvenient for our users and severely limits where the quick
terminal can be used. As a result we then build gtk4-layer-shell ourselves
by default unless `--system` or `-fsys=gtk4-layer-shell` are specified.
This also allows me to add an idiomatic Zig API on top of the library
and avoiding adding even more raw C code in the GTK apprt.

Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as
I don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
This commit is contained in:
Leah Amelia Chen
2025-03-07 17:03:23 +01:00
parent e07b6fdf6b
commit cd442eb9e2
15 changed files with 241 additions and 68 deletions

View File

@@ -781,10 +781,7 @@ fn toggleQuickTerminal(self: *App) !bool {
return true;
}
if (!self.winproto.supportsQuickTerminal()) {
log.err("quick terminal not supported on current platform", .{});
return false;
}
if (!self.winproto.supportsQuickTerminal()) return false;
const qt = Window.create(self.core_app.alloc, self) catch |err| {
log.err("failed to initialize quick terminal={}", .{err});

View File

@@ -15,7 +15,6 @@ pub const c = @cImport({
@cInclude("X11/XKBlib.h");
}
if (build_options.wayland) {
if (build_options.layer_shell) @cInclude("gtk4-layer-shell/gtk4-layer-shell.h");
@cInclude("gdk/wayland/gdkwayland.h");
}

View File

@@ -4,6 +4,8 @@ const Allocator = std.mem.Allocator;
const build_options = @import("build_options");
const wayland = @import("wayland");
const gtk = @import("gtk");
const gtk4_layer_shell = @import("gtk4-layer-shell");
const c = @import("../c.zig").c;
const Config = @import("../../../config.zig").Config;
@@ -90,17 +92,19 @@ pub const App = struct {
}
pub fn supportsQuickTerminal(_: App) bool {
if (comptime !build_options.layer_shell) return false;
return c.gtk_layer_is_supported() != 0;
if (!gtk4_layer_shell.isSupported()) {
log.warn("your compositor does not support the wlr-layer-shell protocol; disabling quick terminal", .{});
return false;
}
return true;
}
pub fn initQuickTerminal(_: *App, apprt_window: *ApprtWindow) !void {
if (comptime !build_options.layer_shell) unreachable;
const window: *gtk.Window = @ptrCast(apprt_window.window);
c.gtk_layer_init_for_window(apprt_window.window);
c.gtk_layer_set_layer(apprt_window.window, c.GTK_LAYER_SHELL_LAYER_TOP);
c.gtk_layer_set_keyboard_mode(apprt_window.window, c.GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND);
gtk4_layer_shell.initForWindow(window);
gtk4_layer_shell.setLayer(window, .top);
gtk4_layer_shell.setKeyboardMode(window, .on_demand);
}
fn registryListener(
@@ -336,11 +340,10 @@ pub const Window = struct {
}
fn syncQuickTerminal(self: *Window) !void {
if (comptime !build_options.layer_shell) return;
const window: *gtk.Window = @ptrCast(self.apprt_window.window);
const position = self.apprt_window.config.quick_terminal_position;
const window = self.apprt_window.window;
const anchored_edge: ?LayerShellEdge = switch (self.apprt_window.config.quick_terminal_position) {
const anchored_edge: ?gtk4_layer_shell.ShellEdge = switch (position) {
.left => .left,
.right => .right,
.top => .top,
@@ -348,23 +351,23 @@ pub const Window = struct {
.center => null,
};
for (std.meta.tags(LayerShellEdge)) |edge| {
for (std.meta.tags(gtk4_layer_shell.ShellEdge)) |edge| {
if (anchored_edge) |anchored| {
if (edge == anchored) {
c.gtk_layer_set_margin(window, @intFromEnum(edge), 0);
c.gtk_layer_set_anchor(window, @intFromEnum(edge), @intFromBool(true));
gtk4_layer_shell.setMargin(window, edge, 0);
gtk4_layer_shell.setAnchor(window, edge, true);
continue;
}
}
// Arbitrary margin - could be made customizable?
c.gtk_layer_set_margin(window, @intFromEnum(edge), 20);
c.gtk_layer_set_anchor(window, @intFromEnum(edge), @intFromBool(false));
gtk4_layer_shell.setMargin(window, edge, 20);
gtk4_layer_shell.setAnchor(window, edge, false);
}
switch (self.apprt_window.config.quick_terminal_position) {
.top, .bottom, .center => c.gtk_window_set_default_size(window, 800, 400),
.left, .right => c.gtk_window_set_default_size(window, 400, 800),
switch (position) {
.top, .bottom, .center => window.setDefaultSize(800, 400),
.left, .right => window.setDefaultSize(400, 800),
}
if (self.apprt_window.isQuickTerminal()) {
@@ -377,26 +380,18 @@ pub const Window = struct {
log.warn("could not create slide object={}", .{err});
break :slide null;
};
slide.setLocation(@intCast(@intFromEnum(anchored.toKdeSlideLocation())));
const slide_location: org.KdeKwinSlide.Location = switch (anchored) {
.top => .top,
.bottom => .bottom,
.left => .left,
.right => .right,
};
slide.setLocation(@intCast(@intFromEnum(slide_location)));
slide.commit();
break :slide slide;
} else null;
}
}
};
const LayerShellEdge = enum(c_uint) {
left = c.GTK_LAYER_SHELL_EDGE_LEFT,
right = c.GTK_LAYER_SHELL_EDGE_RIGHT,
top = c.GTK_LAYER_SHELL_EDGE_TOP,
bottom = c.GTK_LAYER_SHELL_EDGE_BOTTOM,
fn toKdeSlideLocation(self: LayerShellEdge) org.KdeKwinSlide.Location {
return switch (self) {
.left => .left,
.top => .top,
.right => .right,
.bottom => .bottom,
};
}
};

View File

@@ -150,6 +150,7 @@ pub const App = struct {
}
pub fn supportsQuickTerminal(_: App) bool {
log.warn("quick terminal is not yet supported on X11", .{});
return false;
}

View File

@@ -34,7 +34,6 @@ font_backend: font.Backend = .freetype,
/// Feature flags
x11: bool = false,
wayland: bool = false,
layer_shell: bool = false,
sentry: bool = true,
wasm_shared: bool = true,
@@ -163,12 +162,6 @@ pub fn init(b: *std.Build) !Config {
"Enables linking against X11 libraries when using the GTK rendering backend.",
) orelse gtk_targets.x11;
config.layer_shell = b.option(
bool,
"gtk-layer-shell",
"Enables linking against the gtk4-layer-shell library for quick terminal support. Requires Wayland.",
) orelse gtk_targets.layer_shell;
//---------------------------------------------------------------
// Ghostty Exe Properties
@@ -366,6 +359,7 @@ pub fn init(b: *std.Build) !Config {
"libpng",
"zlib",
"oniguruma",
"gtk4-layer-shell",
}) |dep| {
_ = b.systemIntegrationOption(
dep,
@@ -398,7 +392,6 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
step.addOption(bool, "flatpak", self.flatpak);
step.addOption(bool, "x11", self.x11);
step.addOption(bool, "wayland", self.wayland);
step.addOption(bool, "layer_shell", self.layer_shell);
step.addOption(bool, "sentry", self.sentry);
step.addOption(apprt.Runtime, "app_runtime", self.app_runtime);
step.addOption(font.Backend, "font_backend", self.font_backend);

View File

@@ -484,7 +484,24 @@ pub fn add(
step.root_module.addImport("wayland", wayland);
step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));
if (self.config.layer_shell) step.linkSystemLibrary2("gtk4-layer-shell", dynamic_link_opts);
const gtk4_layer_shell = b.dependency("gtk4_layer_shell", .{
.target = target,
.optimize = optimize,
});
const layer_shell_module = gtk4_layer_shell.module("gtk4-layer-shell");
layer_shell_module.addImport("gtk", gobject.module("gtk4"));
step.root_module.addImport("gtk4-layer-shell", layer_shell_module);
// IMPORTANT: gtk4-layer-shell must be linked BEFORE
// wayland-client, as it relies on shimming libwayland's APIs.
if (b.systemIntegrationOption("gtk4-layer-shell", .{})) {
step.linkSystemLibrary2("gtk4-layer-shell", dynamic_link_opts);
} else {
// gtk4-layer-shell *must* be dynamically linked,
// so we don't add it as a static library
step.linkLibrary(gtk4_layer_shell.artifact("gtk4-layer-shell"));
}
step.linkSystemLibrary2("wayland-client", dynamic_link_opts);
}

View File

@@ -51,5 +51,6 @@ RUN ZIG_GLOBAL_CACHE_DIR=/zig/global-cache ./nix/build-support/fetch-zig-cache.s
COPY ./src /src/src
RUN zig build -Doptimize=Debug -Dcpu=baseline -Dapp-runtime=gtk --system /zig/global-cache/p
# Debian 12 doesn't have gtk4-layer-shell, so we have to manually compile it ourselves
RUN zig build -Doptimize=Debug -Dcpu=baseline -Dapp-runtime=gtk -fno-sys=gtk4-layer-shell --system /zig/global-cache/p

View File

@@ -3,7 +3,6 @@ const std = @import("std");
pub const Targets = packed struct {
x11: bool = false,
wayland: bool = false,
layer_shell: bool = false,
};
/// Returns the targets that GTK4 was compiled with.
@@ -21,21 +20,8 @@ pub fn targets(b: *std.Build) Targets {
const x11 = std.mem.indexOf(u8, output, "x11") != null;
const wayland = std.mem.indexOf(u8, output, "wayland") != null;
const layer_shell = layer_shell: {
if (!wayland) break :layer_shell false;
_ = b.runAllowFail(
&.{ "pkg-config", "--exists", "gtk4-layer-shell-0" },
&code,
.Ignore,
) catch break :layer_shell false;
break :layer_shell true;
};
return .{
.x11 = x11,
.wayland = wayland,
.layer_shell = layer_shell,
};
}