From f943a4cf873061771eed097fb6faea2fc30e3931 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 11 Feb 2024 15:06:22 -0600 Subject: [PATCH] GTK: Add compiled-in GTK resources and use them for icons. Use glib-compile-resources to compile CSS style sheets and icons into the Ghostty GTK binary. Makes for simpler access to icons and sets things up for customizing the look of Ghostty with CSS in the future. The CSS style sheets are blank for now so there will be no visual changes. --- build.zig | 81 +++++++++++++++++++++++++++++++++ src/apprt/gtk/App.zig | 13 +++++- src/apprt/gtk/Surface.zig | 24 ++-------- src/apprt/gtk/Window.zig | 15 +----- src/apprt/gtk/c.zig | 3 ++ src/apprt/gtk/gresource.xml | 20 ++++++++ src/apprt/gtk/icon.zig | 63 ------------------------- src/apprt/gtk/inspector.zig | 7 +-- src/apprt/gtk/style-dark.css | 0 src/apprt/gtk/style-hc-dark.css | 0 src/apprt/gtk/style-hc.css | 0 src/apprt/gtk/style.css | 0 12 files changed, 122 insertions(+), 104 deletions(-) create mode 100644 src/apprt/gtk/gresource.xml delete mode 100644 src/apprt/gtk/icon.zig create mode 100644 src/apprt/gtk/style-dark.css create mode 100644 src/apprt/gtk/style-hc-dark.css create mode 100644 src/apprt/gtk/style-hc.css create mode 100644 src/apprt/gtk/style.css 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