From 2f9660c02c04acc33a6e9d7f64a8a58a58f07fea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Jul 2025 20:58:57 -0700 Subject: [PATCH] apprt/gtk-ng: boilerplate --- src/apprt.zig | 7 ++ src/apprt/gtk-ng.zig | 5 ++ src/apprt/gtk-ng/App.zig | 38 +++++++++++ src/apprt/gtk-ng/Surface.zig | 5 ++ src/build/SharedDeps.zig | 127 +++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 src/apprt/gtk-ng.zig create mode 100644 src/apprt/gtk-ng/App.zig create mode 100644 src/apprt/gtk-ng/Surface.zig diff --git a/src/apprt.zig b/src/apprt.zig index fd81d7270..706287302 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -17,6 +17,7 @@ const structs = @import("apprt/structs.zig"); pub const action = @import("apprt/action.zig"); pub const ipc = @import("apprt/ipc.zig"); pub const gtk = @import("apprt/gtk.zig"); +pub const gtk_ng = @import("apprt/gtk-ng.zig"); pub const none = @import("apprt/none.zig"); pub const browser = @import("apprt/browser.zig"); pub const embedded = @import("apprt/embedded.zig"); @@ -43,6 +44,7 @@ pub const runtime = switch (build_config.artifact) { .exe => switch (build_config.app_runtime) { .none => none, .gtk => gtk, + .@"gtk-ng" => gtk_ng, }, .lib => embedded, .wasm_module => browser, @@ -61,6 +63,11 @@ pub const Runtime = enum { /// GTK-backed. Rich windowed application. GTK is dynamically linked. gtk, + /// GTK4. The "-ng" variant is a rewrite of the GTK backend using + /// GTK-native technologies such as full GObject classes, Blueprint + /// files, etc. + @"gtk-ng", + pub fn default(target: std.Target) Runtime { // The Linux default is GTK because it is full featured. if (target.os.tag == .linux) return .gtk; diff --git a/src/apprt/gtk-ng.zig b/src/apprt/gtk-ng.zig new file mode 100644 index 000000000..dea15a3ed --- /dev/null +++ b/src/apprt/gtk-ng.zig @@ -0,0 +1,5 @@ +const internal_os = @import("../os/main.zig"); + +pub const App = @import("gtk-ng/App.zig"); +pub const Surface = @import("gtk-ng/Surface.zig"); +pub const resourcesDir = internal_os.resourcesDir; diff --git a/src/apprt/gtk-ng/App.zig b/src/apprt/gtk-ng/App.zig new file mode 100644 index 000000000..8c12a3b54 --- /dev/null +++ b/src/apprt/gtk-ng/App.zig @@ -0,0 +1,38 @@ +const App = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const apprt = @import("../../apprt.zig"); +const CoreApp = @import("../../App.zig"); + +pub fn init( + self: *App, + core_app: *CoreApp, + opts: struct {}, +) !void { + _ = self; + _ = core_app; + _ = opts; + return; +} + +pub fn run(self: *App) !void { + _ = self; +} + +pub fn terminate(self: *App) void { + _ = self; +} + +pub fn performIpc( + alloc: Allocator, + target: apprt.ipc.Target, + comptime action: apprt.ipc.Action.Key, + value: apprt.ipc.Action.Value(action), +) !bool { + _ = alloc; + _ = target; + _ = value; + return false; +} diff --git a/src/apprt/gtk-ng/Surface.zig b/src/apprt/gtk-ng/Surface.zig new file mode 100644 index 000000000..df7b04f5f --- /dev/null +++ b/src/apprt/gtk-ng/Surface.zig @@ -0,0 +1,5 @@ +const Surface = @This(); + +pub fn deinit(self: *Surface) void { + _ = self; +} diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index ea7e696ef..f745a0633 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -553,6 +553,7 @@ pub fn add( switch (self.config.app_runtime) { .none => {}, .gtk => try self.addGTK(step), + .@"gtk-ng" => try self.addGtkNg(step), } } @@ -563,6 +564,132 @@ pub fn add( return static_libs; } +/// Setup the dependencies for the GTK apprt build. +fn addGtkNg( + self: *const SharedDeps, + step: *std.Build.Step.Compile, +) !void { + const b = step.step.owner; + const target = step.root_module.resolved_target.?; + const optimize = step.root_module.optimize.?; + + const gobject_ = b.lazyDependency("gobject", .{ + .target = target, + .optimize = optimize, + }); + if (gobject_) |gobject| { + const gobject_imports = .{ + .{ "adw", "adw1" }, + .{ "gdk", "gdk4" }, + .{ "gio", "gio2" }, + .{ "glib", "glib2" }, + .{ "gobject", "gobject2" }, + .{ "gtk", "gtk4" }, + .{ "xlib", "xlib2" }, + }; + inline for (gobject_imports) |import| { + const name, const module = import; + step.root_module.addImport(name, gobject.module(module)); + } + } + + step.linkSystemLibrary2("gtk4", dynamic_link_opts); + step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts); + + if (self.config.x11) { + step.linkSystemLibrary2("X11", dynamic_link_opts); + if (gobject_) |gobject| { + step.root_module.addImport( + "gdk_x11", + gobject.module("gdkx114"), + ); + } + } + + if (self.config.wayland) wayland: { + // These need to be all be called to note that we need them. + const wayland_dep_ = b.lazyDependency("wayland", .{}); + const wayland_protocols_dep_ = b.lazyDependency( + "wayland_protocols", + .{}, + ); + const plasma_wayland_protocols_dep_ = b.lazyDependency( + "plasma_wayland_protocols", + .{}, + ); + + // Unwrap or return, there are no more dependencies below. + const wayland_dep = wayland_dep_ orelse break :wayland; + const wayland_protocols_dep = wayland_protocols_dep_ orelse break :wayland; + const plasma_wayland_protocols_dep = plasma_wayland_protocols_dep_ orelse break :wayland; + + // Note that zig_wayland cannot be lazy because lazy dependencies + // can't be imported since they don't exist and imports are + // resolved at compile time of the build. + const zig_wayland_dep = b.dependency("zig_wayland", .{}); + const Scanner = @import("zig_wayland").Scanner; + const scanner = Scanner.create(zig_wayland_dep.builder, .{ + .wayland_xml = wayland_dep.path("protocol/wayland.xml"), + .wayland_protocols = wayland_protocols_dep.path(""), + }); + + scanner.addCustomProtocol( + plasma_wayland_protocols_dep.path("src/protocols/blur.xml"), + ); + // FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398 + scanner.addCustomProtocol( + plasma_wayland_protocols_dep.path("src/protocols/server-decoration.xml"), + ); + scanner.addCustomProtocol( + plasma_wayland_protocols_dep.path("src/protocols/slide.xml"), + ); + scanner.addSystemProtocol("staging/xdg-activation/xdg-activation-v1.xml"); + + scanner.generate("wl_compositor", 1); + scanner.generate("org_kde_kwin_blur_manager", 1); + scanner.generate("org_kde_kwin_server_decoration_manager", 1); + scanner.generate("org_kde_kwin_slide_manager", 1); + scanner.generate("xdg_activation_v1", 1); + + step.root_module.addImport("wayland", b.createModule(.{ + .root_source_file = scanner.result, + })); + if (gobject_) |gobject| step.root_module.addImport( + "gdk_wayland", + gobject.module("gdkwayland4"), + ); + + if (b.lazyDependency("gtk4_layer_shell", .{ + .target = target, + .optimize = optimize, + })) |gtk4_layer_shell| { + const layer_shell_module = gtk4_layer_shell.module("gtk4-layer-shell"); + if (gobject_) |gobject| 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-0", dynamic_link_opts); + } else { + // gtk4-layer-shell *must* be dynamically linked, + // so we don't add it as a static library + const shared_lib = gtk4_layer_shell.artifact("gtk4-layer-shell"); + b.installArtifact(shared_lib); + step.linkLibrary(shared_lib); + } + } + + step.linkSystemLibrary2("wayland-client", dynamic_link_opts); + } +} + /// Setup the dependencies for the GTK apprt build. The GTK apprt /// is particularly involved compared to others so we pull this out /// into a dedicated function.