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(>kActivate), + _ = 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(>kWindowAdded), + _ = 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(>kWindowRemoved), + _ = 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(>kWindowIsActive), + *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 actions = .{ - .{ "quit", >kActionQuit, null }, - .{ "open-config", >kActionOpenConfig, null }, - .{ "reload-config", >kActionReloadConfig, null }, - .{ "present-surface", >kActionPresentSurface, c.G_VARIANT_TYPE("t") }, - }; + 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, 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 };