mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
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.
This commit is contained in:
81
build.zig
81
build.zig
@ -1165,6 +1165,87 @@ fn addDeps(
|
|||||||
.gtk => {
|
.gtk => {
|
||||||
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
||||||
if (config.libadwaita) step.linkSystemLibrary2("adwaita-1", 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());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,18 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
errdefer c.g_object_unref(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(
|
_ = c.g_signal_connect_data(
|
||||||
app,
|
app,
|
||||||
"activate",
|
"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;
|
if (c.g_main_context_acquire(ctx) == 0) return error.GtkContextAcquireFailed;
|
||||||
errdefer c.g_main_context_release(ctx);
|
errdefer c.g_main_context_release(ctx);
|
||||||
|
|
||||||
const gapp = @as(*c.GApplication, @ptrCast(app));
|
|
||||||
var err_: ?*c.GError = null;
|
var err_: ?*c.GError = null;
|
||||||
if (c.g_application_register(
|
if (c.g_application_register(
|
||||||
gapp,
|
gapp,
|
||||||
|
@ -943,32 +943,14 @@ pub fn showDesktopNotification(
|
|||||||
0 => "Ghostty",
|
0 => "Ghostty",
|
||||||
else => title,
|
else => title,
|
||||||
};
|
};
|
||||||
|
|
||||||
const notif = c.g_notification_new(t.ptr);
|
const notif = c.g_notification_new(t.ptr);
|
||||||
defer c.g_object_unref(notif);
|
defer c.g_object_unref(notif);
|
||||||
c.g_notification_set_body(notif, body.ptr);
|
c.g_notification_set_body(notif, body.ptr);
|
||||||
|
|
||||||
// Find our icon in the current icon theme. Not pretty, but the builtin GIO
|
const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
|
||||||
// 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,
|
|
||||||
);
|
|
||||||
defer c.g_object_unref(icon);
|
defer c.g_object_unref(icon);
|
||||||
// Get the filepath of the icon we found
|
c.g_notification_set_icon(notif, icon);
|
||||||
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);
|
|
||||||
|
|
||||||
const g_app: *c.GApplication = @ptrCast(self.app.app);
|
const g_app: *c.GApplication = @ptrCast(self.app.app);
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ const CoreSurface = @import("../../Surface.zig");
|
|||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const icon = @import("icon.zig");
|
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
@ -31,10 +30,6 @@ window: *c.GtkWindow,
|
|||||||
/// The notebook (tab grouping) for this window.
|
/// The notebook (tab grouping) for this window.
|
||||||
notebook: *c.GtkNotebook,
|
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 {
|
pub fn create(alloc: Allocator, app: *App) !*Window {
|
||||||
// Allocate a fixed pointer for our window. We try to minimize
|
// Allocate a fixed pointer for our window. We try to minimize
|
||||||
// allocations but windows and other GUI requirements are so minimal
|
// 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
|
// Set up our own state
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.icon = undefined,
|
|
||||||
.window = undefined,
|
.window = undefined,
|
||||||
.notebook = 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)
|
// to disable this so that terminal programs can capture F10 (such as htop)
|
||||||
c.gtk_window_set_handle_menubar_accel(gtk_window, 0);
|
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
|
c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty");
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Apply background opacity if we have it
|
// Apply background opacity if we have it
|
||||||
if (app.config.@"background-opacity" < 1) {
|
if (app.config.@"background-opacity" < 1) {
|
||||||
@ -189,9 +180,7 @@ fn initActions(self: *Window) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Window) void {
|
pub fn deinit(_: *Window) void {}
|
||||||
self.icon.deinit(self.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a new tab to this window.
|
/// Add a new tab to this window.
|
||||||
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
|
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
|
||||||
|
@ -7,6 +7,9 @@ const c = @cImport({
|
|||||||
@cInclude("gdk/x11/gdkx.h");
|
@cInclude("gdk/x11/gdkx.h");
|
||||||
// Xkb for X11 state handling
|
// Xkb for X11 state handling
|
||||||
@cInclude("X11/XKBlib.h");
|
@cInclude("X11/XKBlib.h");
|
||||||
|
|
||||||
|
// generated header files
|
||||||
|
@cInclude("ghostty_resources.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
pub usingnamespace c;
|
pub usingnamespace c;
|
||||||
|
20
src/apprt/gtk/gresource.xml
Normal file
20
src/apprt/gtk/gresource.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/com/mitchellh/ghostty">
|
||||||
|
<file compressed="true" alias="style.css">src/apprt/gtk/style.css</file>
|
||||||
|
<file compressed="true" alias="style-dark.css">src/apprt/gtk/style-dark.css</file>
|
||||||
|
<file compressed="true" alias="style-hc.css">src/apprt/gtk/style-hc.css</file>
|
||||||
|
<file compressed="true" alias="style-hc-dark.css">src/apprt/gtk/style-hc-dark.css</file>
|
||||||
|
</gresource>
|
||||||
|
<gresource prefix="/com/mitchellh/ghostty/icons">
|
||||||
|
<file preprocess="to-pixdata" alias="16x16/apps/com.mitchellh.ghostty.png">images/icons/icon_16x16.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="16x16@2/apps/com.mitchellh.ghostty.png">images/icons/icon_16x16@2x@2x.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="32x32/apps/com.mitchellh.ghostty.png">images/icons/icon_32x32.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="32x32@2/apps/com.mitchellh.ghostty.png">images/icons/icon_32x32@2x@2x.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="128x128/apps/com.mitchellh.ghostty.png">images/icons/icon_128x128.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="128x128@2/apps/com.mitchellh.ghostty.png">images/icons/icon_128x128@2x@2x.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="256x256/apps/com.mitchellh.ghostty.png">images/icons/icon_256x256.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="256x256@2/apps/com.mitchellh.ghostty.png">images/icons/icon_256x256@2x@2x.png</file>
|
||||||
|
<file preprocess="to-pixdata" alias="512x512/apps/com.mitchellh.ghostty.png">images/icons/icon_512x512.png</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
@ -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;
|
|
||||||
}
|
|
@ -7,7 +7,6 @@ const Surface = @import("Surface.zig");
|
|||||||
const TerminalWindow = @import("Window.zig");
|
const TerminalWindow = @import("Window.zig");
|
||||||
const ImguiWidget = @import("ImguiWidget.zig");
|
const ImguiWidget = @import("ImguiWidget.zig");
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
const icon = @import("icon.zig");
|
|
||||||
const CoreInspector = @import("../../inspector/main.zig").Inspector;
|
const CoreInspector = @import("../../inspector/main.zig").Inspector;
|
||||||
|
|
||||||
const log = std.log.scoped(.inspector);
|
const log = std.log.scoped(.inspector);
|
||||||
@ -125,14 +124,12 @@ pub const Inspector = struct {
|
|||||||
const Window = struct {
|
const Window = struct {
|
||||||
inspector: *Inspector,
|
inspector: *Inspector,
|
||||||
window: *c.GtkWindow,
|
window: *c.GtkWindow,
|
||||||
icon: icon.Icon,
|
|
||||||
imgui_widget: ImguiWidget,
|
imgui_widget: ImguiWidget,
|
||||||
|
|
||||||
pub fn init(self: *Window, inspector: *Inspector) !void {
|
pub fn init(self: *Window, inspector: *Inspector) !void {
|
||||||
// Initialize to undefined
|
// Initialize to undefined
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.inspector = inspector,
|
.inspector = inspector,
|
||||||
.icon = undefined,
|
|
||||||
.window = undefined,
|
.window = undefined,
|
||||||
.imgui_widget = undefined,
|
.imgui_widget = undefined,
|
||||||
};
|
};
|
||||||
@ -144,8 +141,7 @@ const Window = struct {
|
|||||||
self.window = gtk_window;
|
self.window = gtk_window;
|
||||||
c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector");
|
c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector");
|
||||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
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, "com.mitchellh.ghostty");
|
||||||
c.gtk_window_set_icon_name(gtk_window, self.icon.name);
|
|
||||||
|
|
||||||
// Initialize our imgui widget
|
// Initialize our imgui widget
|
||||||
try self.imgui_widget.init();
|
try self.imgui_widget.init();
|
||||||
@ -163,7 +159,6 @@ const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Window) void {
|
pub fn deinit(self: *Window) void {
|
||||||
self.icon.deinit(self.inspector.surface.app);
|
|
||||||
self.inspector.locationDidClose();
|
self.inspector.locationDidClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0
src/apprt/gtk/style-dark.css
Normal file
0
src/apprt/gtk/style-dark.css
Normal file
0
src/apprt/gtk/style-hc-dark.css
Normal file
0
src/apprt/gtk/style-hc-dark.css
Normal file
0
src/apprt/gtk/style-hc.css
Normal file
0
src/apprt/gtk/style-hc.css
Normal file
0
src/apprt/gtk/style.css
Normal file
0
src/apprt/gtk/style.css
Normal file
Reference in New Issue
Block a user