summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeah Amelia Chen <hi@pluie.me>2025-03-18 09:14:19 +0100
committerGitHub <noreply@github.com>2025-03-18 09:14:19 +0100
commitd75c5ec038d20765c8c7f4b76eb736e21190a74b (patch)
tree88e8d44508a8b30ed28db19f321bb8a3ce4b2eb4
parent742bca713d2c5d2633df9ee99d358d8a104919fb (diff)
parentee95a5f3e0823a995dc65a20f58163eb9beab368 (diff)
gtk: convert App to zig-gobject (#6787)
-rw-r--r--build.zig.zon4
-rw-r--r--build.zig.zon.json6
-rw-r--r--build.zig.zon.nix6
-rw-r--r--build.zig.zon.txt2
-rw-r--r--src/apprt/gtk/App.zig422
-rw-r--r--src/apprt/gtk/Surface.zig3
-rw-r--r--src/apprt/gtk/Window.zig3
-rw-r--r--src/apprt/gtk/adwaita.zig61
-rw-r--r--src/apprt/gtk/c.zig3
-rw-r--r--src/apprt/gtk/cgroup.zig5
-rw-r--r--src/apprt/gtk/inspector.zig2
-rw-r--r--src/apprt/gtk/version.zig69
12 files changed, 304 insertions, 282 deletions
diff --git a/build.zig.zon b/build.zig.zon
index a6134c2f7..85720a931 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -55,8 +55,8 @@
.gobject = .{
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
// Temporary until we generate them at build time automatically.
- .url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz",
- .hash = "gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV",
+ .url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
+ .hash = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR",
.lazy = true,
},
diff --git a/build.zig.zon.json b/build.zig.zon.json
index 7e46a0430..288fbb6b0 100644
--- a/build.zig.zon.json
+++ b/build.zig.zon.json
@@ -29,10 +29,10 @@
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
- "gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV": {
+ "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR": {
"name": "gobject",
- "url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz",
- "hash": "sha256-eMmS9oysZheHwSCCvmOUSDJmP9zN7cAr6qqDIbz6EmY="
+ "url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
+ "hash": "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI="
},
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
"name": "gtk4_layer_shell",
diff --git a/build.zig.zon.nix b/build.zig.zon.nix
index f2a7f8828..3226a1637 100644
--- a/build.zig.zon.nix
+++ b/build.zig.zon.nix
@@ -130,11 +130,11 @@ in
};
}
{
- name = "gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV";
+ name = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR";
path = fetchZigArtifact {
name = "gobject";
- url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz";
- hash = "sha256-eMmS9oysZheHwSCCvmOUSDJmP9zN7cAr6qqDIbz6EmY=";
+ url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst";
+ hash = "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI=";
};
}
{
diff --git a/build.zig.zon.txt b/build.zig.zon.txt
index a06d15021..3f5c2ff95 100644
--- a/build.zig.zon.txt
+++ b/build.zig.zon.txt
@@ -26,7 +26,7 @@ https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21a
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip
-https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz
+https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz
https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz
https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz
diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig
index da621117d..483e7821e 100644
--- a/src/apprt/gtk/App.zig
+++ b/src/apprt/gtk/App.zig
@@ -40,7 +40,6 @@ const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
const CloseDialog = @import("CloseDialog.zig");
const Split = @import("Split.zig");
-const c = @import("c.zig").c;
const version = @import("version.zig");
const inspector = @import("inspector.zig");
const key = @import("key.zig");
@@ -48,6 +47,11 @@ const winprotopkg = @import("winproto.zig");
const testing = std.testing;
const adwaita = @import("adwaita.zig");
+pub const c = @cImport({
+ // generated header files
+ @cInclude("ghostty_resources.h");
+});
+
const log = std.log.scoped(.gtk);
pub const Options = struct {};
@@ -55,8 +59,8 @@ pub const Options = struct {};
core_app: *CoreApp,
config: Config,
-app: *c.GtkApplication,
-ctx: *c.GMainContext,
+app: *adw.Application,
+ctx: *glib.MainContext,
/// State and logic for the underlying windowing protocol.
winproto: winprotopkg.App,
@@ -86,15 +90,15 @@ running: bool = true,
transient_cgroup_base: ?[]const u8 = null,
/// CSS Provider for any styles based on ghostty configuration values
-css_provider: *c.GtkCssProvider,
+css_provider: *gtk.CssProvider,
/// Providers for loading custom stylesheets defined by user
-custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider) = .{},
+custom_css_providers: std.ArrayListUnmanaged(*gtk.CssProvider) = .{},
/// The timer used to quit the application after the last window is closed.
quit_timer: union(enum) {
off: void,
- active: c.guint,
+ active: c_uint,
expired: void,
} = .{ .off = {} },
@@ -102,22 +106,10 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
_ = opts;
// Log our GTK version
- log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
- c.GTK_MAJOR_VERSION,
- c.GTK_MINOR_VERSION,
- c.GTK_MICRO_VERSION,
- c.gtk_get_major_version(),
- c.gtk_get_minor_version(),
- c.gtk_get_micro_version(),
- });
+ version.logVersion();
// log the adwaita version
- log.info("libadwaita version build={s} runtime={}.{}.{}", .{
- c.ADW_VERSION_S,
- c.adw_get_major_version(),
- c.adw_get_minor_version(),
- c.adw_get_micro_version(),
- });
+ adwaita.logVersion();
// Set gettext global domain to be our app so that our unqualified
// translations map to our translations.
@@ -271,9 +263,9 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
}
}
- c.adw_init();
+ adw.init();
- const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
+ const display: *gdk.Display = gdk.Display.getDefault() orelse {
// I'm unsure of any scenario where this happens. Because we don't
// want to litter null checks everywhere, we just exit here.
log.warn("gdk display is null, exiting", .{});
@@ -291,9 +283,9 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
};
// Setup the flags for our application.
- const app_flags: c.GApplicationFlags = app_flags: {
- var flags: c.GApplicationFlags = c.G_APPLICATION_DEFAULT_FLAGS;
- if (!single_instance) flags |= c.G_APPLICATION_NON_UNIQUE;
+ const app_flags: gio.ApplicationFlags = app_flags: {
+ var flags: gio.ApplicationFlags = .flags_default_flags;
+ if (!single_instance) flags.non_unique = true;
break :app_flags flags;
};
@@ -321,91 +313,87 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
// Using an AdwApplication lets us use Adwaita widgets and access things
// such as the color scheme.
- const adw_app = @as(?*c.AdwApplication, @ptrCast(c.adw_application_new(
+ const adw_app = adw.Application.new(
app_id.ptr,
app_flags,
- ))) orelse return error.GtkInitFailed;
- errdefer c.g_object_unref(adw_app);
+ );
+ errdefer adw_app.unref();
- const style_manager = c.adw_application_get_style_manager(adw_app);
- c.adw_style_manager_set_color_scheme(
- style_manager,
+ const style_manager = adw_app.getStyleManager();
+ style_manager.setColorScheme(
switch (config.@"window-theme") {
.auto, .ghostty => auto: {
const lum = config.background.toTerminalRGB().perceivedLuminance();
break :auto if (lum > 0.5)
- c.ADW_COLOR_SCHEME_PREFER_LIGHT
+ .prefer_light
else
- c.ADW_COLOR_SCHEME_PREFER_DARK;
+ .prefer_dark;
},
- .system => c.ADW_COLOR_SCHEME_PREFER_LIGHT,
- .dark => c.ADW_COLOR_SCHEME_FORCE_DARK,
- .light => c.ADW_COLOR_SCHEME_FORCE_LIGHT,
+ .system => .prefer_light,
+ .dark => .prefer_dark,
+ .light => .force_dark,
},
);
- const app: *c.GtkApplication = @ptrCast(adw_app);
- const gapp: *c.GApplication = @ptrCast(app);
+ const gio_app = adw_app.as(gio.Application);
// force the resource path to a known value so that it doesn't depend on
// the app id and load in compiled resources
- c.g_application_set_resource_base_path(gapp, "/com/mitchellh/ghostty");
- c.g_resources_register(c.ghostty_get_resource());
+ gio_app.setResourceBasePath("/com/mitchellh/ghostty");
+ gio.resourcesRegister(@ptrCast(@alignCast(c.ghostty_get_resource() orelse {
+ log.err("unable to load resources", .{});
+ return error.GtkNoResources;
+ })));
// The `activate` signal is used when Ghostty is first launched and when a
// secondary Ghostty is launched and requests a new window.
- _ = c.g_signal_connect_data(
- app,
- "activate",
- c.G_CALLBACK(&gtkActivate),
+ _ = gio.Application.signals.activate.connect(
+ adw_app,
+ *CoreApp,
+ gtkActivate,
core_app,
- null,
- c.G_CONNECT_DEFAULT,
+ .{},
);
// Other signals
- _ = c.g_signal_connect_data(
- app,
- "window-added",
- c.G_CALLBACK(&gtkWindowAdded),
+ _ = gtk.Application.signals.window_added.connect(
+ adw_app,
+ *CoreApp,
+ gtkWindowAdded,
core_app,
- null,
- c.G_CONNECT_DEFAULT,
+ .{},
);
- _ = c.g_signal_connect_data(
- app,
- "window-removed",
- c.G_CALLBACK(&gtkWindowRemoved),
+ _ = gtk.Application.signals.window_removed.connect(
+ adw_app,
+ *CoreApp,
+ gtkWindowRemoved,
core_app,
- null,
- c.G_CONNECT_DEFAULT,
+ .{},
);
// We don't use g_application_run, we want to manually control the
// loop so we have to do the same things the run function does:
// https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533
- const ctx = c.g_main_context_default() orelse return error.GtkContextFailed;
- if (c.g_main_context_acquire(ctx) == 0) return error.GtkContextAcquireFailed;
- errdefer c.g_main_context_release(ctx);
+ const ctx = glib.MainContext.default();
+ if (glib.MainContext.acquire(ctx) == 0) return error.GtkContextAcquireFailed;
+ errdefer glib.MainContext.release(ctx);
- var err_: ?*c.GError = null;
- if (c.g_application_register(
- gapp,
+ var err_: ?*glib.Error = null;
+ if (gio_app.register(
null,
- @ptrCast(&err_),
+ &err_,
) == 0) {
if (err_) |err| {
- log.warn("error registering application: {s}", .{err.message});
- c.g_error_free(err);
+ log.warn("error registering application: {s}", .{err.f_message orelse "(unknown)"});
+ err.free();
}
return error.GtkApplicationRegisterFailed;
}
- // FIXME: when App.zig is converted to zig-gobject
// Setup our windowing protocol logic
var winproto_app = try winprotopkg.App.init(
core_app.alloc,
- @ptrCast(@alignCast(display)),
+ display,
app_id,
&config,
);
@@ -419,20 +407,20 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
//
// https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302
if (config.@"initial-window")
- c.g_application_activate(gapp);
+ gio_app.activate();
// Internally, GTK ensures that only one instance of this provider exists in the provider list
// for the display.
- const css_provider = c.gtk_css_provider_new();
- c.gtk_style_context_add_provider_for_display(
+ const css_provider = gtk.CssProvider.new();
+ gtk.StyleContext.addProviderForDisplay(
display,
- @ptrCast(css_provider),
- c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
+ css_provider.as(gtk.StyleProvider),
+ gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
);
return .{
.core_app = core_app,
- .app = app,
+ .app = adw_app,
.config = config,
.ctx = ctx,
.cursor_none = cursor_none,
@@ -441,7 +429,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
// 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
// our "activate" call above will open a window.
- .running = c.g_application_get_is_remote(gapp) == 0,
+ .running = gio_app.getIsRemote() == 0,
.css_provider = css_provider,
};
}
@@ -449,16 +437,16 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
// Terminate the application. The application will not be restarted after
// this so all global state can be cleaned up.
pub fn terminate(self: *App) void {
- c.g_settings_sync();
- while (c.g_main_context_iteration(self.ctx, 0) != 0) {}
- c.g_main_context_release(self.ctx);
- c.g_object_unref(self.app);
+ gio.Settings.sync();
+ while (glib.MainContext.iteration(self.ctx, 0) != 0) {}
+ glib.MainContext.release(self.ctx);
+ self.app.unref();
- if (self.cursor_none) |cursor| c.g_object_unref(cursor);
+ if (self.cursor_none) |cursor| cursor.unref();
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
for (self.custom_css_providers.items) |provider| {
- c.g_object_unref(provider);
+ provider.unref();
}
self.custom_css_providers.deinit(self.core_app.alloc);
@@ -922,29 +910,25 @@ fn showDesktopNotification(
else => n.title,
};
- const notification = c.g_notification_new(t.ptr);
- defer c.g_object_unref(notification);
- c.g_notification_set_body(notification, n.body.ptr);
+ const notification = gio.Notification.new(t);
+ defer notification.unref();
+ notification.setBody(n.body);
- const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
- defer c.g_object_unref(icon);
- c.g_notification_set_icon(notification, icon);
+ const icon = gio.ThemedIcon.new("com.mitchellh.ghostty");
+ defer icon.unref();
+ notification.setIcon(icon.as(gio.Icon));
- const pointer = c.g_variant_new_uint64(switch (target) {
+ const pointer = glib.Variant.newUint64(switch (target) {
.app => 0,
.surface => |v| @intFromPtr(v),
});
- c.g_notification_set_default_action_and_target_value(
- notification,
- "app.present-surface",
- pointer,
- );
+ notification.setDefaultActionAndTargetValue("app.present-surface", pointer);
- const g_app: *c.GApplication = @ptrCast(self.app);
+ const gio_app = self.app.as(gio.Application);
// We set the notification ID to the body content. If the content is the
// same, this notification may replace a previous notification
- c.g_application_send_notification(g_app, n.body.ptr, notification);
+ gio_app.sendNotification(n.body, notification);
}
fn configChange(
@@ -1076,29 +1060,29 @@ fn syncActionAccelerator(
gtk_action: [:0]const u8,
action: input.Binding.Action,
) !void {
+ const gtk_app = self.app.as(gtk.Application);
+
// Reset it initially
- const zero = [_]?[*:0]const u8{null};
- c.gtk_application_set_accels_for_action(@ptrCast(self.app), gtk_action.ptr, &zero);
+ const zero = [_:null]?[*:0]const u8{};
+ gtk_app.setAccelsForAction(gtk_action, &zero);
const trigger = self.config.keybind.set.getTrigger(action) orelse return;
var buf: [256]u8 = undefined;
const accel = try key.accelFromTrigger(&buf, trigger) orelse return;
- const accels = [_]?[*:0]const u8{ accel, null };
+ const accels = [_:null]?[*:0]const u8{accel};
- c.gtk_application_set_accels_for_action(
- @ptrCast(self.app),
- gtk_action.ptr,
- &accels,
- );
+ gtk_app.setAccelsForAction(gtk_action, &accels);
}
fn loadRuntimeCss(
self: *const App,
) Allocator.Error!void {
- var stack_alloc = std.heap.stackFallback(4096, self.core_app.alloc);
- var buf = std.ArrayList(u8).init(stack_alloc.get());
- defer buf.deinit();
- const writer = buf.writer();
+ const alloc = self.core_app.alloc;
+
+ var buf: std.ArrayListUnmanaged(u8) = .empty;
+ defer buf.deinit(alloc);
+
+ const writer = buf.writer(alloc);
const config: *const Config = &self.config;
const window_theme = config.@"window-theme";
@@ -1190,20 +1174,28 @@ fn loadRuntimeCss(
});
}
+ const data = try alloc.dupeZ(u8, buf.items);
+ defer alloc.free(data);
+
// Clears any previously loaded CSS from this provider
- loadCssProviderFromData(self.css_provider, buf.items);
+ loadCssProviderFromData(self.css_provider, data);
}
fn loadCustomCss(self: *App) !void {
- const display = c.gdk_display_get_default();
+ const alloc = self.core_app.alloc;
+
+ const display = gdk.Display.getDefault() orelse {
+ log.warn("unable to get display", .{});
+ return;
+ };
// unload the previously loaded style providers
for (self.custom_css_providers.items) |provider| {
- c.gtk_style_context_remove_provider_for_display(
+ gtk.StyleContext.removeProviderForDisplay(
display,
- @ptrCast(provider),
+ provider.as(gtk.StyleProvider),
);
- c.g_object_unref(provider);
+ provider.unref();
}
self.custom_css_providers.clearRetainingCapacity();
@@ -1214,49 +1206,51 @@ fn loadCustomCss(self: *App) !void {
};
const file = std.fs.openFileAbsolute(path, .{}) catch |err| {
if (err != error.FileNotFound or !optional) {
- log.err("error opening gtk-custom-css file {s}: {}", .{ path, err });
+ log.err(
+ "error opening gtk-custom-css file {s}: {}",
+ .{ path, err },
+ );
}
continue;
};
defer file.close();
log.info("loading gtk-custom-css path={s}", .{path});
- const contents = try file.reader().readAllAlloc(self.core_app.alloc, 5 * 1024 * 1024 // 5MB
+ const contents = try file.reader().readAllAlloc(
+ self.core_app.alloc,
+ 5 * 1024 * 1024, // 5MB,
);
- defer self.core_app.alloc.free(contents);
+ defer alloc.free(contents);
- const provider = c.gtk_css_provider_new();
- c.gtk_style_context_add_provider_for_display(
+ const data = try alloc.dupeZ(u8, contents);
+ defer alloc.free(data);
+
+ const provider = gtk.CssProvider.new();
+ loadCssProviderFromData(provider, data);
+ gtk.StyleContext.addProviderForDisplay(
display,
- @ptrCast(provider),
- c.GTK_STYLE_PROVIDER_PRIORITY_USER,
+ provider.as(gtk.StyleProvider),
+ gtk.STYLE_PROVIDER_PRIORITY_USER,
);
- loadCssProviderFromData(provider, contents);
-
try self.custom_css_providers.append(self.core_app.alloc, provider);
}
}
-fn loadCssProviderFromData(provider: *c.GtkCssProvider, data: []const u8) void {
+fn loadCssProviderFromData(provider: *gtk.CssProvider, data: [:0]const u8) void {
if (version.atLeast(4, 12, 0)) {
- const g_bytes = c.g_bytes_new(data.ptr, data.len);
- defer c.g_bytes_unref(g_bytes);
+ const g_bytes = glib.Bytes.new(data.ptr, data.len);
+ defer g_bytes.unref();
- c.gtk_css_provider_load_from_bytes(provider, g_bytes);
+ provider.loadFromBytes(g_bytes);
} else {
- c.gtk_css_provider_load_from_data(
- provider,
- data.ptr,
- @intCast(data.len),
- );
+ provider.loadFromData(data, @intCast(data.len));
}
}
/// Called by CoreApp to wake up the event loop.
-pub fn wakeup(self: App) void {
- _ = self;
- c.g_main_context_wakeup(null);
+pub fn wakeup(_: App) void {
+ glib.MainContext.wakeup(null);
}
/// Run the event loop. This doesn't return until the app exits.
@@ -1297,8 +1291,7 @@ pub fn run(self: *App) !void {
} else log.debug("cgroup isolation disabled config={}", .{self.config.@"linux-cgroup"});
// Setup color scheme notifications
- const adw_app: *adw.Application = @ptrCast(@alignCast(self.app));
- const style_manager: *adw.StyleManager = adw_app.getStyleManager();
+ const style_manager: *adw.StyleManager = self.app.getStyleManager();
_ = gobject.Object.signals.notify.connect(
style_manager,
*App,
@@ -1324,7 +1317,7 @@ pub fn run(self: *App) !void {
};
while (self.running) {
- _ = c.g_main_context_iteration(self.ctx, 1);
+ _ = glib.MainContext.iteration(self.ctx, 1);
// Tick the terminal app and see if we should quit.
try self.core_app.tick(self);
@@ -1363,7 +1356,13 @@ fn startQuitTimer(self: *App) void {
if (self.config.@"quit-after-last-window-closed-delay") |v| {
// If a delay is configured, set a timeout function to quit after the delay.
- self.quit_timer = .{ .active = c.g_timeout_add(v.asMilliseconds(), gtkQuitTimerExpired, self) };
+ self.quit_timer = .{
+ .active = glib.timeoutAdd(
+ v.asMilliseconds(),
+ gtkQuitTimerExpired,
+ self,
+ ),
+ };
} else {
// If no delay is configured, treat it as expired.
self.quit_timer = .{ .expired = {} };
@@ -1453,14 +1452,13 @@ fn quit(self: *App) void {
/// This immediately destroys all windows, forcing the application to quit.
pub fn quitNow(self: *App) void {
- const list = c.gtk_window_list_toplevels();
- defer c.g_list_free(list);
- c.g_list_foreach(list, struct {
- fn callback(data: c.gpointer, _: c.gpointer) callconv(.C) void {
+ const list = gtk.Window.listToplevels();
+ defer list.free();
+ list.foreach(struct {
+ fn callback(data: ?*anyopaque, _: ?*anyopaque) callconv(.c) void {
const ptr = data orelse return;
- const widget: *c.GtkWidget = @ptrCast(@alignCast(ptr));
- const window: *c.GtkWindow = @ptrCast(widget);
- c.gtk_window_destroy(window);
+ const window: *gtk.Window = @ptrCast(@alignCast(ptr));
+ window.destroy();
}
}.callback, null);
@@ -1469,11 +1467,7 @@ pub fn quitNow(self: *App) void {
/// This is called by the `activate` signal. This is sent on program startup and
/// also when a secondary instance launches and requests a new window.
-fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
- _ = app;
-
- const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
-
+fn gtkActivate(_: *adw.Application, core_app: *CoreApp) callconv(.c) void {
// Queue a new window
_ = core_app.mailbox.push(.{
.new_window = .{},
@@ -1481,46 +1475,41 @@ fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
}
fn gtkWindowAdded(
- _: *c.GtkApplication,
- window: *c.GtkWindow,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
-
+ _: *adw.Application,
+ window: *gtk.Window,
+ core_app: *CoreApp,
+) callconv(.c) void {
// Request the is-active property change so we can detect
// when our app loses focus.
- _ = c.g_signal_connect_data(
+ _ = gobject.Object.signals.notify.connect(
window,
- "notify::is-active",
- c.G_CALLBACK(&gtkWindowIsActive),
+ *CoreApp,
+ gtkWindowIsActive,
core_app,
- null,
- c.G_CONNECT_DEFAULT,
+ .{
+ .detail = "is-active",
+ },
);
}
fn gtkWindowRemoved(
- _: *c.GtkApplication,
- _: *c.GtkWindow,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
-
+ _: *adw.Application,
+ _: *gtk.Window,
+ core_app: *CoreApp,
+) callconv(.c) void {
// Recheck if we are focused
gtkWindowIsActive(null, undefined, core_app);
}
fn gtkWindowIsActive(
- window: ?*c.GtkWindow,
- _: *c.GParamSpec,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
-
+ window: ?*gtk.Window,
+ _: *gobject.ParamSpec,
+ core_app: *CoreApp,
+) callconv(.c) void {
// If our window is active, then we can tell the app
// that we are focused.
if (window) |w| {
- if (c.gtk_window_is_active(w) == 1) {
+ if (w.isActive() != 0) {
core_app.focusEvent(true);
return;
}
@@ -1529,16 +1518,17 @@ fn gtkWindowIsActive(
// If the window becomes inactive, we need to check if any
// other windows are active. If not, then we are no longer
// focused.
- if (c.gtk_window_list_toplevels()) |list| {
- defer c.g_list_free(list);
- var current: ?*c.GList = list;
- while (current) |elem| : (current = elem.next) {
+ {
+ const list = gtk.Window.listToplevels();
+ defer list.free();
+ var current: ?*glib.List = list;
+ while (current) |elem| : (current = elem.f_next) {
// If the window is active then we are still focused.
// This is another window since we did our check above.
// That window should trigger its own is-active
// callback so we don't need to call it here.
- const w: *c.GtkWindow = @alignCast(@ptrCast(elem.data));
- if (c.gtk_window_is_active(w) == 1) return;
+ const w: *gtk.Window = @alignCast(@ptrCast(elem.f_data));
+ if (w.isActive() == 1) return;
}
}
@@ -1575,33 +1565,30 @@ fn colorSchemeEvent(
}
fn gtkActionOpenConfig(
- _: *c.GSimpleAction,
- _: *c.GVariant,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const self: *App = @ptrCast(@alignCast(ud orelse return));
+ _: *gio.SimpleAction,
+ _: ?*glib.Variant,
+ self: *App,
+) callconv(.c) void {
_ = self.core_app.mailbox.push(.{
.open_config = {},
}, .{ .forever = {} });
}
fn gtkActionReloadConfig(
- _: *c.GSimpleAction,
- _: *c.GVariant,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const self: *App = @ptrCast(@alignCast(ud orelse return));
+ _: *gio.SimpleAction,
+ _: ?*glib.Variant,
+ self: *App,
+) callconv(.c) void {
self.reloadConfig(.app, .{}) catch |err| {
log.err("error reloading configuration: {}", .{err});
};
}
fn gtkActionQuit(
- _: *c.GSimpleAction,
- _: *c.GVariant,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const self: *App = @ptrCast(@alignCast(ud orelse return));
+ _: *gio.SimpleAction,
+ _: ?*glib.Variant,
+ self: *App,
+) callconv(.c) void {
self.core_app.performAction(self, .quit) catch |err| {
log.err("error quitting err={}", .{err});
};
@@ -1610,21 +1597,24 @@ fn gtkActionQuit(
/// Action sent by the window manager asking us to present a specific surface to
/// the user. Usually because the user clicked on a desktop notification.
fn gtkActionPresentSurface(
- _: *c.GSimpleAction,
- parameter: *c.GVariant,
- ud: ?*anyopaque,
-) callconv(.C) void {
- const self: *App = @ptrCast(@alignCast(ud orelse return));
+ _: *gio.SimpleAction,
+ parameter_: ?*glib.Variant,
+ self: *App,
+) callconv(.c) void {
+ const parameter = parameter_ orelse return;
+
+ const t = glib.ext.VariantType.newFor(u64);
+ defer glib.VariantType.free(t);
// Make sure that we've receiived a u64 from the system.
- if (c.g_variant_is_of_type(parameter, c.G_VARIANT_TYPE("t")) == 0) {
+ if (glib.Variant.isOfType(parameter, t) == 0) {
return;
}
// Convert that u64 to pointer to a core surface. A value of zero
// means that there was no target surface for the notification so
// we don't focus any surface.
- const ptr_int: u64 = c.g_variant_get_uint64(parameter);
+ const ptr_int = parameter.getUint64();
if (ptr_int == 0) return;
const surface: *CoreSurface = @ptrFromInt(ptr_int);
@@ -1654,25 +1644,27 @@ fn initActions(self: *App) void {
//
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
+ const t = glib.ext.VariantType.newFor(u64);
+ defer glib.VariantType.free(t);
+
const actions = .{
- .{ "quit", &gtkActionQuit, null },
- .{ "open-config", &gtkActionOpenConfig, null },
- .{ "reload-config", &gtkActionReloadConfig, null },
- .{ "present-surface", &gtkActionPresentSurface, c.G_VARIANT_TYPE("t") },
+ .{ "quit", gtkActionQuit, null },
+ .{ "open-config", gtkActionOpenConfig, null },
+ .{ "reload-config", gtkActionReloadConfig, null },
+ .{ "present-surface", gtkActionPresentSurface, t },
};
-
inline for (actions) |entry| {
- const action = c.g_simple_action_new(entry[0], entry[2]);
- defer c.g_object_unref(action);
- _ = c.g_signal_connect_data(
+ const action = gio.SimpleAction.new(entry[0], entry[2]);
+ defer action.unref();
+ _ = gio.SimpleAction.signals.activate.connect(
action,
- "activate",
- c.G_CALLBACK(entry[1]),
+ *App,
+ entry[1],
self,
- null,
- c.G_CONNECT_DEFAULT,
+ .{},
);
- c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action));
+ const action_map = self.app.as(gio.ActionMap);
+ action_map.addAction(action.as(gio.Action));
}
}
diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig
index 0b962b06c..30cf19f82 100644
--- a/src/apprt/gtk/Surface.zig
+++ b/src/apprt/gtk/Surface.zig
@@ -1341,8 +1341,7 @@ pub fn showDesktopNotification(
const pointer = glib.Variant.newUint64(@intFromPtr(&self.core_surface));
notification.setDefaultActionAndTargetValue("app.present-surface", pointer);
- // FIXME: when App.zig gets converted to zig-gobject
- const app: gio.Application = @ptrCast(@alignCast(self.app.app));
+ const app = self.app.app.as(gio.Application);
// We set the notification ID to the body content. If the content is the
// same, this notification may replace a previous notification
diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig
index a33f4a6e4..2ec77d723 100644
--- a/src/apprt/gtk/Window.zig
+++ b/src/apprt/gtk/Window.zig
@@ -141,9 +141,8 @@ pub fn init(self: *Window, app: *App) !void {
.winproto = .none,
};
- // FIXME: when App.zig is converted to zig-gobject
// Create the window
- self.window = adw.ApplicationWindow.new(@ptrCast(@alignCast(app.app)));
+ self.window = adw.ApplicationWindow.new(app.app.as(gtk.Application));
const gtk_window = self.window.as(gtk.Window);
const gtk_widget = self.window.as(gtk.Widget);
errdefer gtk_window.destroy();
diff --git a/src/apprt/gtk/adwaita.zig b/src/apprt/gtk/adwaita.zig
index 67f2dae61..1191b6fca 100644
--- a/src/apprt/gtk/adwaita.zig
+++ b/src/apprt/gtk/adwaita.zig
@@ -1,27 +1,45 @@
const std = @import("std");
-const c = @import("c.zig").c;
+
+// Until the gobject bindings are built at the same time we are building
+// Ghostty, we need to import `adwaita.h` directly to ensure that the version
+// macros match the version of `libadwaita` that we are building/linking
+// against.
+const c = @cImport({
+ @cInclude("adwaita.h");
+});
+
+const adw = @import("adw");
+
+const log = std.log.scoped(.gtk);
+
+pub fn logVersion() void {
+ log.info("libadwaita version build={s} runtime={}.{}.{}", .{
+ c.ADW_VERSION_S,
+ adw.getMajorVersion(),
+ adw.getMinorVersion(),
+ adw.getMicroVersion(),
+ });
+}
/// Verifies that the running libadwaita version is at least the given
-/// version. This will return false if Ghostty is configured to
-/// not build with libadwaita.
+/// version. This will return false if Ghostty is configured to not build with
+/// libadwaita.
///
-/// This can be run in both a comptime and runtime context. If it
-/// is run in a comptime context, it will only check the version
-/// in the headers. If it is run in a runtime context, it will
-/// check the actual version of the library we are linked against.
-/// So generally you probably want to do both checks!
+/// This can be run in both a comptime and runtime context. If it is run in a
+/// comptime context, it will only check the version in the headers. If it is
+/// run in a runtime context, it will check the actual version of the library we
+/// are linked against. So generally you probably want to do both checks!
///
-/// This is inlined so that the comptime checks will disable the
-/// runtime checks if the comptime checks fail.
+/// This is inlined so that the comptime checks will disable the runtime checks
+/// if the comptime checks fail.
pub inline fn versionAtLeast(
comptime major: u16,
comptime minor: u16,
comptime micro: u16,
) bool {
- // If our header has lower versions than the given version,
- // we can return false immediately. This prevents us from
- // compiling against unknown symbols and makes runtime checks
- // very slightly faster.
+ // If our header has lower versions than the given version, we can return
+ // false immediately. This prevents us from compiling against unknown
+ // symbols and makes runtime checks very slightly faster.
if (comptime c.ADW_MAJOR_VERSION < major or
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor) or
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION == minor and c.ADW_MICRO_VERSION < micro))
@@ -30,14 +48,13 @@ pub inline fn versionAtLeast(
// If we're in comptime then we can't check the runtime version.
if (@inComptime()) return true;
- // We use the functions instead of the constants such as
- // c.ADW_MINOR_VERSION because the function gets the actual
- // runtime version.
- if (c.adw_get_major_version() >= major) {
- if (c.adw_get_major_version() > major) return true;
- if (c.adw_get_minor_version() >= minor) {
- if (c.adw_get_minor_version() > minor) return true;
- return c.adw_get_micro_version() >= micro;
+ // We use the functions instead of the constants such as c.ADW_MINOR_VERSION
+ // because the function gets the actual runtime version.
+ if (adw.getMajorVersion() >= major) {
+ if (adw.getMajorVersion() > major) return true;
+ if (adw.getMinorVersion() >= minor) {
+ if (adw.getMinorVersion() > minor) return true;
+ return adw.getMicroVersion() >= micro;
}
}
diff --git a/src/apprt/gtk/c.zig b/src/apprt/gtk/c.zig
index 2ce11684f..058a8e25a 100644
--- a/src/apprt/gtk/c.zig
+++ b/src/apprt/gtk/c.zig
@@ -3,9 +3,6 @@ pub const c = @cImport({
@cInclude("gtk/gtk.h");
@cInclude("adwaita.h");
- // generated header files
- @cInclude("ghostty_resources.h");
-
// compatibility
@cInclude("ghostty_gtk_compat.h");
});
diff --git a/src/apprt/gtk/cgroup.zig b/src/apprt/gtk/cgroup.zig
index 81d167b2b..2f5104d09 100644
--- a/src/apprt/gtk/cgroup.zig
+++ b/src/apprt/gtk/cgroup.zig
@@ -113,9 +113,8 @@ fn enableControllers(alloc: Allocator, cgroup: []const u8) !void {
/// On success this will return the name of the transient scope
/// cgroup prefix, allocated with the given allocator.
fn createScope(app: *App, pid_: std.os.linux.pid_t) !void {
- // FIXME: when app.app gets converted to gobject
- const g_app: *gio.Application = @ptrCast(@alignCast(app.app));
- const connection = g_app.getDbusConnection() orelse
+ const gio_app = app.app.as(gio.Application);
+ const connection = gio_app.getDbusConnection() orelse
return error.DbusConnectionRequired;
const pid: u32 = @intCast(pid_);
diff --git a/src/apprt/gtk/inspector.zig b/src/apprt/gtk/inspector.zig
index 558175751..e08e98120 100644
--- a/src/apprt/gtk/inspector.zig
+++ b/src/apprt/gtk/inspector.zig
@@ -136,7 +136,7 @@ const Window = struct {
};
// Create the window
- const window = c.gtk_application_window_new(inspector.surface.app.app);
+ const window = c.gtk_application_window_new(@ptrCast(@alignCast(inspector.surface.app.app)));
const gtk_window: *c.GtkWindow = @ptrCast(window);
errdefer c.gtk_window_destroy(gtk_window);
self.window = gtk_window;
diff --git a/src/apprt/gtk/version.zig b/src/apprt/gtk/version.zig
index d8686fa28..67e769d11 100644
--- a/src/apprt/gtk/version.zig
+++ b/src/apprt/gtk/version.zig
@@ -1,19 +1,41 @@
-const c = @import("c.zig").c;
+const std = @import("std");
+
+// Until the gobject bindings are built at the same time we are building
+// Ghostty, we need to import `gtk/gtk.h` directly to ensure that the version
+// macros match the version of `gtk4` that we are building/linking against.
+const c = @cImport({
+ @cInclude("gtk/gtk.h");
+});
+
+const gtk = @import("gtk");
+
+const log = std.log.scoped(.gtk);
+
+pub fn logVersion() void {
+ log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
+ c.GTK_MAJOR_VERSION,
+ c.GTK_MINOR_VERSION,
+ c.GTK_MICRO_VERSION,
+ gtk.getMajorVersion(),
+ gtk.getMinorVersion(),
+ gtk.getMicroVersion(),
+ });
+}
/// Verifies that the GTK version is at least the given version.
///
-/// This can be run in both a comptime and runtime context. If it
-/// is run in a comptime context, it will only check the version
-/// in the headers. If it is run in a runtime context, it will
-/// check the actual version of the library we are linked against.
+/// This can be run in both a comptime and runtime context. If it is run in a
+/// comptime context, it will only check the version in the headers. If it is
+/// run in a runtime context, it will check the actual version of the library we
+/// are linked against.
///
-/// This function should be used in cases where the version check
-/// would affect code generation, such as using symbols that are
-/// only available beyond a certain version. For checks which only
-/// depend on GTK's runtime behavior, use `runtimeAtLeast`.
+/// This function should be used in cases where the version check would affect
+/// code generation, such as using symbols that are only available beyond a
+/// certain version. For checks which only depend on GTK's runtime behavior,
+/// use `runtimeAtLeast`.
///
-/// This is inlined so that the comptime checks will disable the
-/// runtime checks if the comptime checks fail.
+/// This is inlined so that the comptime checks will disable the runtime checks
+/// if the comptime checks fail.
pub inline fn atLeast(
comptime major: u16,
comptime minor: u16,
@@ -34,25 +56,23 @@ pub inline fn atLeast(
return runtimeAtLeast(major, minor, micro);
}
-/// Verifies that the GTK version at runtime is at least the given
-/// version.
+/// Verifies that the GTK version at runtime is at least the given version.
///
-/// This function should be used in cases where the only the runtime
-/// behavior is affected by the version check. For checks which would
-/// affect code generation, use `atLeast`.
+/// This function should be used in cases where the only the runtime behavior
+/// is affected by the version check. For checks which would affect code
+/// generation, use `atLeast`.
pub inline fn runtimeAtLeast(
comptime major: u16,
comptime minor: u16,
comptime micro: u16,
) bool {
- // We use the functions instead of the constants such as
- // c.GTK_MINOR_VERSION because the function gets the actual
- // runtime version.
- if (c.gtk_get_major_version() >= major) {
- if (c.gtk_get_major_version() > major) return true;
- if (c.gtk_get_minor_version() >= minor) {
- if (c.gtk_get_minor_version() > minor) return true;
- return c.gtk_get_micro_version() >= micro;
+ // We use the functions instead of the constants such as c.GTK_MINOR_VERSION
+ // because the function gets the actual runtime version.
+ if (gtk.getMajorVersion() >= major) {
+ if (gtk.getMajorVersion() > major) return true;
+ if (gtk.getMinorVersion() >= minor) {
+ if (gtk.getMinorVersion() > minor) return true;
+ return gtk.getMicroVersion() >= micro;
}
}
@@ -60,7 +80,6 @@ pub inline fn runtimeAtLeast(
}
test "atLeast" {
- const std = @import("std");
const testing = std.testing;
const funs = &.{ atLeast, runtimeAtLeast };