diff --git a/build.zig b/build.zig index de008af35..a03d0d8a1 100644 --- a/build.zig +++ b/build.zig @@ -1165,6 +1165,87 @@ fn addDeps( .gtk => { step.linkSystemLibrary2("gtk4", dynamic_link_opts); if (config.libadwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts); + + { + // TODO: find a way to dynamically update this from the output + // of `glib-compile-resources --generate-dependencies` + const extra_file_dependencies = &.{ + "src/apprt/gtk/gresource.xml", + "src/apprt/gtk/style.css", + "src/apprt/gtk/style-dark.css", + "src/apprt/gtk/style-hc.css", + "src/apprt/gtk/style-hc-dark.css", + "images/icons/icon_16x16@2x@2x.png", + "images/icons/icon_16x16.png", + "images/icons/icon_32x32@2x@2x.png", + "images/icons/icon_32x32.png", + "images/icons/icon_128x128@2x@2x.png", + "images/icons/icon_128x128.png", + "images/icons/icon_256x256@2x@2x.png", + "images/icons/icon_256x256.png", + "images/icons/icon_512x512.png", + }; + + const generate_resources_d = b.addSystemCommand(&.{ + "glib-compile-resources", + "--generate-dependencies", + "src/apprt/gtk/gresource.xml", + }); + + _ = generate_resources_d.captureStdOut(); + + generate_resources_d.extra_file_dependencies = &.{ + "src/apprt/gtk/gresource.xml", + }; + + const generate_resources_c = b.addSystemCommand(&.{ + "glib-compile-resources", + "--c-name", + "ghostty", + "--generate-source", + "--target", + }); + + const ghostty_resources_c = generate_resources_c.addOutputFileArg("ghostty_resources.c"); + + generate_resources_c.addArgs(&.{ + "src/apprt/gtk/gresource.xml", + }); + + generate_resources_c.step.dependOn(&generate_resources_d.step); + + // TODO: find a way to dynamically update this from the output + // of `glib-compile-resources --generate-dependencies` + generate_resources_c.extra_file_dependencies = extra_file_dependencies; + + step.addCSourceFile(.{ + .file = ghostty_resources_c, + .flags = &.{}, + }); + + const generate_resources_h = b.addSystemCommand(&.{ + "glib-compile-resources", + "--c-name", + "ghostty", + "--generate-header", + "--target", + }); + + const ghostty_resources_h = generate_resources_h.addOutputFileArg("ghostty_resources.h"); + + generate_resources_h.addArgs(&.{ + "src/apprt/gtk/gresource.xml", + }); + + generate_resources_h.step.dependOn(&generate_resources_d.step); + generate_resources_h.step.dependOn(&generate_resources_c.step); + + // TODO: find a way to dynamically update this from the output + // of `glib-compile-resources --generate-dependencies` + generate_resources_h.extra_file_dependencies = extra_file_dependencies; + + step.addIncludePath(ghostty_resources_h.dirname()); + } }, } } diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 622764d14..8ae896c3d 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -153,6 +153,18 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { }; errdefer c.g_object_unref(app); + + const gapp = @as(*c.GApplication, @ptrCast(app)); + + // force the resource path to a known value so that it doesn't depend on + // the app id + c.g_application_set_resource_base_path(gapp, "/com/mitchellh/ghostty"); + + // load compiled-in resources + c.g_resources_register(c.ghostty_get_resource()); + + // 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", @@ -169,7 +181,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { if (c.g_main_context_acquire(ctx) == 0) return error.GtkContextAcquireFailed; errdefer c.g_main_context_release(ctx); - const gapp = @as(*c.GApplication, @ptrCast(app)); var err_: ?*c.GError = null; if (c.g_application_register( gapp, diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index fe8968acd..041d3900d 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -943,32 +943,14 @@ pub fn showDesktopNotification( 0 => "Ghostty", else => title, }; + const notif = c.g_notification_new(t.ptr); defer c.g_object_unref(notif); c.g_notification_set_body(notif, body.ptr); - // Find our icon in the current icon theme. Not pretty, but the builtin GIO - // method "g_themed_icon_new" doesn't search XDG_DATA_DIRS, so any install - // not in /usr/share will be unable to find an icon - const display = c.gdk_display_get_default(); - const theme = c.gtk_icon_theme_get_for_display(display); - const icon = c.gtk_icon_theme_lookup_icon( - theme, - "com.mitchellh.ghostty", - null, - 48, - 1, // Window scale - c.GTK_TEXT_DIR_LTR, - 0, - ); + const icon = c.g_themed_icon_new("com.mitchellh.ghostty"); defer c.g_object_unref(icon); - // Get the filepath of the icon we found - const file = c.gtk_icon_paintable_get_file(icon); - defer c.g_object_unref(file); - // Create a GIO icon - const gicon = c.g_file_icon_new(file); - defer c.g_object_unref(gicon); - c.g_notification_set_icon(notif, gicon); + c.g_notification_set_icon(notif, icon); const g_app: *c.GApplication = @ptrCast(self.app.app); diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 6e24c9468..4adccac4b 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -18,7 +18,6 @@ const CoreSurface = @import("../../Surface.zig"); const App = @import("App.zig"); const Surface = @import("Surface.zig"); const Tab = @import("Tab.zig"); -const icon = @import("icon.zig"); const c = @import("c.zig"); const log = std.log.scoped(.gtk); @@ -31,10 +30,6 @@ window: *c.GtkWindow, /// The notebook (tab grouping) for this window. notebook: *c.GtkNotebook, -/// The resources directory for the icon (if any). We need to retain a -/// pointer to this because GTK can use it at any time. -icon: icon.Icon, - pub fn create(alloc: Allocator, app: *App) !*Window { // Allocate a fixed pointer for our window. We try to minimize // allocations but windows and other GUI requirements are so minimal @@ -53,7 +48,6 @@ pub fn init(self: *Window, app: *App) !void { // Set up our own state self.* = .{ .app = app, - .icon = undefined, .window = undefined, .notebook = undefined, }; @@ -70,10 +64,7 @@ pub fn init(self: *Window, app: *App) !void { // to disable this so that terminal programs can capture F10 (such as htop) c.gtk_window_set_handle_menubar_accel(gtk_window, 0); - // If we don't have the icon then we'll try to add our resources dir - // to the search path and see if we can find it there. - self.icon = try icon.appIcon(self.app, window); - c.gtk_window_set_icon_name(gtk_window, self.icon.name); + c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty"); // Apply background opacity if we have it if (app.config.@"background-opacity" < 1) { @@ -189,9 +180,7 @@ fn initActions(self: *Window) void { } } -pub fn deinit(self: *Window) void { - self.icon.deinit(self.app); -} +pub fn deinit(_: *Window) void {} /// Add a new tab to this window. pub fn newTab(self: *Window, parent: ?*CoreSurface) !void { diff --git a/src/apprt/gtk/c.zig b/src/apprt/gtk/c.zig index ffe7b1d0e..5ac8d0fe8 100644 --- a/src/apprt/gtk/c.zig +++ b/src/apprt/gtk/c.zig @@ -7,6 +7,9 @@ const c = @cImport({ @cInclude("gdk/x11/gdkx.h"); // Xkb for X11 state handling @cInclude("X11/XKBlib.h"); + + // generated header files + @cInclude("ghostty_resources.h"); }); pub usingnamespace c; diff --git a/src/apprt/gtk/gresource.xml b/src/apprt/gtk/gresource.xml new file mode 100644 index 000000000..8a3394ae7 --- /dev/null +++ b/src/apprt/gtk/gresource.xml @@ -0,0 +1,20 @@ + + + + src/apprt/gtk/style.css + src/apprt/gtk/style-dark.css + src/apprt/gtk/style-hc.css + src/apprt/gtk/style-hc-dark.css + + + images/icons/icon_16x16.png + images/icons/icon_16x16@2x@2x.png + images/icons/icon_32x32.png + images/icons/icon_32x32@2x@2x.png + images/icons/icon_128x128.png + images/icons/icon_128x128@2x@2x.png + images/icons/icon_256x256.png + images/icons/icon_256x256@2x@2x.png + images/icons/icon_512x512.png + + diff --git a/src/apprt/gtk/icon.zig b/src/apprt/gtk/icon.zig deleted file mode 100644 index 6a7ced3ea..000000000 --- a/src/apprt/gtk/icon.zig +++ /dev/null @@ -1,63 +0,0 @@ -const std = @import("std"); - -const App = @import("App.zig"); -const c = @import("c.zig"); -const global_state = &@import("../../main.zig").state; - -const log = std.log.scoped(.gtk_icon); - -/// An icon. The icon may be associated with some allocated state so when -/// the icon is no longer in use it should be deinitialized. -pub const Icon = struct { - name: [:0]const u8, - state: ?[:0]const u8 = null, - - pub fn deinit(self: *const Icon, app: *App) void { - if (self.state) |v| app.core_app.alloc.free(v); - } -}; - -/// Returns the application icon that can be used anywhere. This attempts to -/// find the icon in the theme and if it can't be found, it is loaded from -/// the resources dir. If the resources dir can't be found, we'll log a warning -/// and let GTK choose a fallback. -pub fn appIcon(app: *App, widget: *c.GtkWidget) !Icon { - const icon_name = "com.mitchellh.ghostty"; - var result: Icon = .{ .name = icon_name }; - - // If we don't have the icon then we'll try to add our resources dir - // to the search path and see if we can find it there. - const icon_theme = c.gtk_icon_theme_get_for_display(c.gtk_widget_get_display(widget)); - if (c.gtk_icon_theme_has_icon(icon_theme, icon_name) == 0) icon: { - const resources_dir = global_state.resources_dir orelse { - log.info("gtk app missing Ghostty icon and no resources dir detected", .{}); - log.info("gtk app will not have Ghostty icon", .{}); - break :icon; - }; - - // The resources dir usually is `/usr/share/ghostty` but GTK icons - // go into `/usr/share/icons`. - const base = std.fs.path.dirname(resources_dir) orelse { - log.warn( - "unexpected error getting dirname of resources dir dir={s}", - .{resources_dir}, - ); - break :icon; - }; - - // Note that this method for adding the icon search path is - // a fallback mechanism. The recommended mechanism is the - // Freedesktop Icon Theme Specification. We distribute a ".desktop" - // file in zig-out/share that should be installed to the proper - // place. - const dir = try std.fmt.allocPrintZ(app.core_app.alloc, "{s}/icons", .{base}); - errdefer app.core_app.alloc.free(dir); - result.state = dir; - c.gtk_icon_theme_add_search_path(icon_theme, dir.ptr); - if (c.gtk_icon_theme_has_icon(icon_theme, icon_name) == 0) { - log.warn("Ghostty icon for gtk app not found", .{}); - } - } - - return result; -} diff --git a/src/apprt/gtk/inspector.zig b/src/apprt/gtk/inspector.zig index 8eee7a540..ae7856df7 100644 --- a/src/apprt/gtk/inspector.zig +++ b/src/apprt/gtk/inspector.zig @@ -7,7 +7,6 @@ const Surface = @import("Surface.zig"); const TerminalWindow = @import("Window.zig"); const ImguiWidget = @import("ImguiWidget.zig"); const c = @import("c.zig"); -const icon = @import("icon.zig"); const CoreInspector = @import("../../inspector/main.zig").Inspector; const log = std.log.scoped(.inspector); @@ -125,14 +124,12 @@ pub const Inspector = struct { const Window = struct { inspector: *Inspector, window: *c.GtkWindow, - icon: icon.Icon, imgui_widget: ImguiWidget, pub fn init(self: *Window, inspector: *Inspector) !void { // Initialize to undefined self.* = .{ .inspector = inspector, - .icon = undefined, .window = undefined, .imgui_widget = undefined, }; @@ -144,8 +141,7 @@ const Window = struct { self.window = gtk_window; c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector"); c.gtk_window_set_default_size(gtk_window, 1000, 600); - self.icon = try icon.appIcon(self.inspector.surface.app, window); - c.gtk_window_set_icon_name(gtk_window, self.icon.name); + c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty"); // Initialize our imgui widget try self.imgui_widget.init(); @@ -163,7 +159,6 @@ const Window = struct { } pub fn deinit(self: *Window) void { - self.icon.deinit(self.inspector.surface.app); self.inspector.locationDidClose(); } diff --git a/src/apprt/gtk/style-dark.css b/src/apprt/gtk/style-dark.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/apprt/gtk/style-hc-dark.css b/src/apprt/gtk/style-hc-dark.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/apprt/gtk/style-hc.css b/src/apprt/gtk/style-hc.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/apprt/gtk/style.css b/src/apprt/gtk/style.css new file mode 100644 index 000000000..e69de29bb