From 8ecb11a602d2adc526c403590ea1e8fe46209072 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 28 Dec 2024 13:40:17 -0600 Subject: [PATCH 1/5] gtk: add option to not link against libX11 --- .github/workflows/test.yml | 9 ++++++++ build.zig | 47 ++++++++++++++++++++++++++++++++++++++ nix/package.nix | 26 ++++++++++++++------- src/apprt/gtk/App.zig | 2 ++ src/apprt/gtk/Surface.zig | 5 ++-- src/apprt/gtk/Window.zig | 2 +- src/apprt/gtk/c.zig | 16 ++++++++----- src/apprt/gtk/key.zig | 27 +++++++++++++--------- src/apprt/gtk/x11.zig | 7 ++++-- src/build_config.zig | 2 ++ src/cli/version.zig | 5 ++++ 11 files changed, 118 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2cbb76a50f..c9b515d467 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -332,6 +332,15 @@ jobs: - name: Test GTK Build (No Libadwaita) run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=false -Demit-docs + - name: Test GTK build with explicit X11 + run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs -Dgtk-x11=true + + - name: Test GTK build with no X11 + run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs -Dgtk-x11=false + + - name: Test GTK build with no Adwaita and no X11 + run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=false -Demit-docs -Dgtk-x11=false + - name: Test GLFW Build run: nix develop -c zig build -Dapp-runtime=glfw diff --git a/build.zig b/build.zig index 0e183efd10..6140265726 100644 --- a/build.zig +++ b/build.zig @@ -105,6 +105,53 @@ 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 pkgconfig = std.process.Child.init(&.{ "pkg-config", "--variable=targets", "gtk4" }, b.allocator); + + pkgconfig.stdout_behavior = .Pipe; + pkgconfig.stderr_behavior = .Pipe; + + try pkgconfig.spawn(); + + const output_max_size = 50 * 1024; + + var stdout = std.ArrayList(u8).init(b.allocator); + var stderr = std.ArrayList(u8).init(b.allocator); + defer { + stdout.deinit(); + stderr.deinit(); + } + + try pkgconfig.collectOutput(&stdout, &stderr, output_max_size); + + const term = try pkgconfig.wait(); + + if (stderr.items.len > 0) { + std.log.warn("pkg-config had errors:\n{s}", .{stderr.items}); + } + + switch (term) { + .Exited => |code| { + if (code == 0) { + if (std.mem.indexOf(u8, stdout.items, "x11")) |_| break :x11 true; + break :x11 false; + } + std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code }); + return error.Unexpected; + }, + inline else => |code| { + std.log.warn("pkg-config: {s} with code {d}", .{ @tagName(term), code }); + return error.Unexpected; + }, + } + }; + const pie = b.option( bool, "pie", diff --git a/nix/package.nix b/nix/package.nix index bfc1e47dee..55205f4591 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -26,6 +26,7 @@ pandoc, revision ? "dirty", optimize ? "Debug", + x11 ? false, }: let # The Zig hook has no way to select the release type without actual # overriding of the default flags. @@ -136,15 +137,16 @@ in oniguruma zlib - libX11 - libXcursor - libXi - libXrandr - libadwaita gtk4 glib gsettings-desktop-schemas + ] + ++ lib.optionals x11 [ + libX11 + libXcursor + libXi + libXrandr ]; dontConfigure = true; @@ -157,7 +159,12 @@ in chmod u+rwX -R $ZIG_GLOBAL_CACHE_DIR ''; - outputs = ["out" "terminfo" "shell_integration" "vim"]; + outputs = [ + "out" + "terminfo" + "shell_integration" + "vim" + ]; postInstall = '' terminfo_src=${ @@ -183,14 +190,17 @@ in echo "$vim" >> "$out/nix-support/propagated-user-env-packages" ''; - postFixup = '' + postFixup = lib.optionalString x11 '' patchelf --add-rpath "${lib.makeLibraryPath [libX11]}" "$out/bin/.ghostty-wrapped" ''; meta = { homepage = "https://github.com/ghostty-org/ghostty"; license = lib.licenses.mit; - platforms = ["x86_64-linux" "aarch64-linux"]; + platforms = [ + "x86_64-linux" + "aarch64-linux" + ]; mainProgram = "ghostty"; }; }) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 47ef512546..9128c8b108 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -15,6 +15,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const build_config = @import("../../build_config.zig"); +const build_options = @import("build_options"); const apprt = @import("../../apprt.zig"); const configpkg = @import("../../config.zig"); const input = @import("../../input.zig"); @@ -360,6 +361,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // keyboard state but the block does more than that (i.e. setting up // WM_CLASS). const x11_xkb: ?x11.Xkb = x11_xkb: { + if (comptime !build_options.x11) break :x11_xkb null; if (!x11.is_display(display)) break :x11_xkb null; // Set the X11 window class property (WM_CLASS) if are are on an X11 diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 3d80d92595..bed90c95f4 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -6,6 +6,7 @@ const Surface = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const build_config = @import("../../build_config.zig"); +const build_options = @import("build_options"); const configpkg = @import("../../config.zig"); const apprt = @import("../../apprt.zig"); const font = @import("../../font/main.zig"); @@ -1183,7 +1184,7 @@ fn showContextMenu(self: *Surface, x: f32, y: f32) void { @ptrCast(window.window), &c.GRAPHENE_POINT_INIT(point.x, point.y), @ptrCast(&point), - ) == c.False) { + ) == 0) { log.warn("failed computing point for context menu", .{}); return; } @@ -1899,7 +1900,7 @@ pub fn dimSurface(self: *Surface) void { // Don't dim surface if context menu is open. // This means we got unfocused due to it opening. const context_menu_open = c.gtk_widget_get_visible(window.context_menu); - if (context_menu_open == c.True) return; + if (context_menu_open == 1) return; if (self.unfocused_widget != null) return; self.unfocused_widget = c.gtk_drawing_area_new(); diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 48e88e4912..0ad09ab74f 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -257,7 +257,7 @@ pub fn init(self: *Window, app: *App) !void { self.context_menu = c.gtk_popover_menu_new_from_model(@ptrCast(@alignCast(self.app.context_menu))); c.gtk_widget_set_parent(self.context_menu, window); - c.gtk_popover_set_has_arrow(@ptrCast(@alignCast(self.context_menu)), c.False); + c.gtk_popover_set_has_arrow(@ptrCast(@alignCast(self.context_menu)), 0); c.gtk_widget_set_halign(self.context_menu, c.GTK_ALIGN_START); // If we are in fullscreen mode, new windows start fullscreen. diff --git a/src/apprt/gtk/c.zig b/src/apprt/gtk/c.zig index 63801250e2..abd4821d36 100644 --- a/src/apprt/gtk/c.zig +++ b/src/apprt/gtk/c.zig @@ -1,15 +1,19 @@ +const build_options = @import("build_options"); + /// Imported C API directly from header files pub const c = @cImport({ @cInclude("gtk/gtk.h"); - if (@import("build_options").adwaita) { + if (build_options.adwaita) { @cInclude("libadwaita-1/adwaita.h"); } - // Add in X11-specific GDK backend which we use for specific things - // (e.g. X11 window class). - @cInclude("gdk/x11/gdkx.h"); - // Xkb for X11 state handling - @cInclude("X11/XKBlib.h"); + if (build_options.x11) { + // Add in X11-specific GDK backend which we use for specific things + // (e.g. X11 window class). + @cInclude("gdk/x11/gdkx.h"); + // Xkb for X11 state handling + @cInclude("X11/XKBlib.h"); + } // generated header files @cInclude("ghostty_resources.h"); diff --git a/src/apprt/gtk/key.zig b/src/apprt/gtk/key.zig index f8458bd3bc..cd5d483d98 100644 --- a/src/apprt/gtk/key.zig +++ b/src/apprt/gtk/key.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build_options = @import("build_options"); const input = @import("../../input.zig"); const c = @import("c.zig").c; const x11 = @import("x11.zig"); @@ -111,21 +112,25 @@ pub fn eventMods( x11_xkb: ?*x11.Xkb, ) input.Mods { const device = c.gdk_event_get_device(event); - const display = c.gtk_widget_get_display(widget); - var mods = if (x11_xkb) |xkb| - // Add any modifier state events from Xkb if we have them (X11 - // only). Null back from the Xkb call means there was no modifier - // event to read. This likely means that the key event did not - // result in a modifier change and we can safely rely on the GDK - // state. - xkb.modifier_state_from_notify(display) orelse - translateMods(gtk_mods) - else + var mods = mods: { + if (comptime build_options.x11) { + const display = c.gtk_widget_get_display(widget); + if (x11_xkb) |xkb| { + // Add any modifier state events from Xkb if we have them (X11 + // only). Null back from the Xkb call means there was no modifier + // event to read. This likely means that the key event did not + // result in a modifier change and we can safely rely on the GDK + // state. + if (xkb.modifier_state_from_notify(display)) |x11_mods| break :mods x11_mods; + break :mods translateMods(gtk_mods); + } + } // On Wayland, we have to use the GDK device because the mods sent // to this event do not have the modifier key applied if it was // presssed (i.e. left control). - translateMods(c.gdk_device_get_modifier_state(device)); + break :mods translateMods(c.gdk_device_get_modifier_state(device)); + }; mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1; diff --git a/src/apprt/gtk/x11.zig b/src/apprt/gtk/x11.zig index 0b116c59f6..078305904c 100644 --- a/src/apprt/gtk/x11.zig +++ b/src/apprt/gtk/x11.zig @@ -1,5 +1,6 @@ /// Utility functions for X11 handling. const std = @import("std"); +const build_options = @import("build_options"); const c = @import("c.zig").c; const input = @import("../../input.zig"); @@ -7,6 +8,7 @@ const log = std.log.scoped(.gtk_x11); /// Returns true if the passed in display is an X11 display. pub fn is_display(display: ?*c.GdkDisplay) bool { + if (comptime !build_options.x11) return false; return c.g_type_check_instance_is_a( @ptrCast(@alignCast(display orelse return false)), c.gdk_x11_display_get_type(), @@ -15,11 +17,12 @@ pub fn is_display(display: ?*c.GdkDisplay) bool { /// Returns true if the app is running on X11 pub fn is_current_display_server() bool { + if (comptime !build_options.x11) return false; const display = c.gdk_display_get_default(); return is_display(display); } -pub const Xkb = struct { +pub const Xkb = if (build_options.x11) struct { base_event_code: c_int, funcs: Funcs, @@ -111,7 +114,7 @@ pub const Xkb = struct { return mods; } -}; +} else struct {}; /// The functions that we load dynamically from libX11.so. const Funcs = struct { diff --git a/src/build_config.zig b/src/build_config.zig index 1f3b35e037..35c4295640 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -22,6 +22,7 @@ pub const BuildConfig = struct { version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, flatpak: bool = false, adwaita: bool = false, + x11: bool = false, app_runtime: apprt.Runtime = .none, renderer: rendererpkg.Impl = .opengl, font_backend: font.Backend = .freetype, @@ -41,6 +42,7 @@ pub const BuildConfig = struct { // support all types. step.addOption(bool, "flatpak", self.flatpak); step.addOption(bool, "adwaita", self.adwaita); + step.addOption(bool, "x11", self.x11); step.addOption(apprt.Runtime, "app_runtime", self.app_runtime); step.addOption(font.Backend, "font_backend", self.font_backend); step.addOption(rendererpkg.Impl, "renderer", self.renderer); diff --git a/src/cli/version.zig b/src/cli/version.zig index b781398f26..29ab7f63fa 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -61,6 +61,11 @@ pub fn run(alloc: Allocator) !u8 { } else { try stdout.print(" - libadwaita : disabled\n", .{}); } + if (comptime build_options.x11) { + try stdout.print(" - libX11 : enabled\n", .{}); + } else { + try stdout.print(" - libX11 : disabled\n", .{}); + } } return 0; } From c4ff8737267029aa5079bf0ad4ee424c3d2c7c59 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Dec 2024 14:25:09 -0800 Subject: [PATCH 2/5] ci: test gtk via a matrix --- .github/workflows/test.yml | 52 +++++++++++++++++++++++++++++--------- src/apprt/gtk/key.zig | 11 ++++---- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9b515d467..a9369b66e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -329,18 +329,6 @@ jobs: - name: Test GTK Build run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs - - name: Test GTK Build (No Libadwaita) - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=false -Demit-docs - - - name: Test GTK build with explicit X11 - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs -Dgtk-x11=true - - - name: Test GTK build with no X11 - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs -Dgtk-x11=false - - - name: Test GTK build with no Adwaita and no X11 - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=false -Demit-docs -Dgtk-x11=false - - name: Test GLFW Build run: nix develop -c zig build -Dapp-runtime=glfw @@ -348,6 +336,46 @@ jobs: - name: Test System Build run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p + test-gtk: + strategy: + fail-fast: false + matrix: + adwaita: ["true", "false"] + x11: ["true", "false"] + runs-on: namespace-profile-ghostty-md + needs: test + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@v1.2.0 + with: + path: | + /nix + /zig + + - name: Test GTK Build + run: | + nix develop -c \ + zig build \ + -Dapp-runtime=gtk \ + -Dgtk-adwaita=${{ matrix.adwaita }} \ + -Dgtk-x11=${{ matrix.x11 }} + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v15 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + test-macos: runs-on: namespace-profile-ghostty-macos needs: test diff --git a/src/apprt/gtk/key.zig b/src/apprt/gtk/key.zig index cd5d483d98..80191b7f5d 100644 --- a/src/apprt/gtk/key.zig +++ b/src/apprt/gtk/key.zig @@ -114,18 +114,19 @@ pub fn eventMods( const device = c.gdk_event_get_device(event); var mods = mods: { + // Add any modifier state events from Xkb if we have them (X11 + // only). Null back from the Xkb call means there was no modifier + // event to read. This likely means that the key event did not + // result in a modifier change and we can safely rely on the GDK + // state. if (comptime build_options.x11) { const display = c.gtk_widget_get_display(widget); if (x11_xkb) |xkb| { - // Add any modifier state events from Xkb if we have them (X11 - // only). Null back from the Xkb call means there was no modifier - // event to read. This likely means that the key event did not - // result in a modifier change and we can safely rely on the GDK - // state. if (xkb.modifier_state_from_notify(display)) |x11_mods| break :mods x11_mods; break :mods translateMods(gtk_mods); } } + // On Wayland, we have to use the GDK device because the mods sent // to this event do not have the modifier key applied if it was // presssed (i.e. left control). From 63dad2fb1055ef52d6b09b4c74bd3031f4e3795e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Dec 2024 14:31:18 -0800 Subject: [PATCH 3/5] prettier --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9369b66e0..042531cf05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -375,7 +375,6 @@ jobs: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - test-macos: runs-on: namespace-profile-ghostty-macos needs: test From f95aa3296532f4efdefdecd2cf3c44ec6f1908f2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Dec 2024 14:32:07 -0800 Subject: [PATCH 4/5] run gtk matrix on small instances --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 042531cf05..1b648f79a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -342,7 +342,7 @@ jobs: matrix: adwaita: ["true", "false"] x11: ["true", "false"] - runs-on: namespace-profile-ghostty-md + runs-on: namespace-profile-ghostty-sm needs: test env: ZIG_LOCAL_CACHE_DIR: /zig/local-cache From a55bea3491c1bd586efc8ab96b679eb4feae3704 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Dec 2024 14:38:21 -0800 Subject: [PATCH 5/5] ci: install nix --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b648f79a1..f1ea32a9c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -342,6 +342,7 @@ jobs: matrix: adwaita: ["true", "false"] x11: ["true", "false"] + name: GTK adwaita=${{ matrix.adwaita }} x11=${{ matrix.x11 }} runs-on: namespace-profile-ghostty-sm needs: test env: @@ -358,6 +359,15 @@ jobs: /nix /zig + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v15 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Test GTK Build run: | nix develop -c \