mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Implement loading custom css in the GTK app (#4200)
Closes https://github.com/ghostty-org/ghostty/issues/4089 Gave it a shot and implemented the custom css loading. My general idea is to use a provider for each stylesheet the user wants to load and then when the config changes unload them and create new providers. A separate provider has to be used for each stylesheet the user wants to load, since when the provider loads the css it clears all the previously loaded styles, so in effect we cannot use one provider to load multiple stylesheets, but maybe there is a better way to overcome this limitation which I'm not seeing.
This commit is contained in:
@ -81,6 +81,9 @@ transient_cgroup_base: ?[]const u8 = null,
|
|||||||
/// CSS Provider for any styles based on ghostty configuration values
|
/// CSS Provider for any styles based on ghostty configuration values
|
||||||
css_provider: *c.GtkCssProvider,
|
css_provider: *c.GtkCssProvider,
|
||||||
|
|
||||||
|
/// Providers for loading custom stylesheets defined by user
|
||||||
|
custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider) = .{},
|
||||||
|
|
||||||
/// The timer used to quit the application after the last window is closed.
|
/// The timer used to quit the application after the last window is closed.
|
||||||
quit_timer: union(enum) {
|
quit_timer: union(enum) {
|
||||||
off: void,
|
off: void,
|
||||||
@ -441,6 +444,11 @@ pub fn terminate(self: *App) void {
|
|||||||
if (self.context_menu) |context_menu| c.g_object_unref(context_menu);
|
if (self.context_menu) |context_menu| c.g_object_unref(context_menu);
|
||||||
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
||||||
|
|
||||||
|
for (self.custom_css_providers.items) |provider| {
|
||||||
|
c.g_object_unref(provider);
|
||||||
|
}
|
||||||
|
self.custom_css_providers.deinit(self.core_app.alloc);
|
||||||
|
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,7 +901,7 @@ fn syncConfigChanges(self: *App) !void {
|
|||||||
try self.updateConfigErrors();
|
try self.updateConfigErrors();
|
||||||
try self.syncActionAccelerators();
|
try self.syncActionAccelerators();
|
||||||
|
|
||||||
// Load our runtime CSS. If this fails then our window is just stuck
|
// Load our runtime and custom CSS. If this fails then our window is just stuck
|
||||||
// with the old CSS but we don't want to fail the entire sync operation.
|
// with the old CSS but we don't want to fail the entire sync operation.
|
||||||
self.loadRuntimeCss() catch |err| switch (err) {
|
self.loadRuntimeCss() catch |err| switch (err) {
|
||||||
error.OutOfMemory => log.warn(
|
error.OutOfMemory => log.warn(
|
||||||
@ -901,6 +909,9 @@ fn syncConfigChanges(self: *App) !void {
|
|||||||
.{},
|
.{},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
self.loadCustomCss() catch |err| {
|
||||||
|
log.warn("Failed to load custom CSS, no custom CSS applied, err={}", .{err});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This should be called whenever the configuration changes to update
|
/// This should be called whenever the configuration changes to update
|
||||||
@ -1034,11 +1045,68 @@ fn loadRuntimeCss(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clears any previously loaded CSS from this provider
|
// Clears any previously loaded CSS from this provider
|
||||||
c.gtk_css_provider_load_from_data(
|
loadCssProviderFromData(self.css_provider, buf.items);
|
||||||
self.css_provider,
|
}
|
||||||
buf.items.ptr,
|
|
||||||
@intCast(buf.items.len),
|
fn loadCustomCss(self: *App) !void {
|
||||||
);
|
const display = c.gdk_display_get_default();
|
||||||
|
|
||||||
|
// unload the previously loaded style providers
|
||||||
|
for (self.custom_css_providers.items) |provider| {
|
||||||
|
c.gtk_style_context_remove_provider_for_display(
|
||||||
|
display,
|
||||||
|
@ptrCast(provider),
|
||||||
|
);
|
||||||
|
c.g_object_unref(provider);
|
||||||
|
}
|
||||||
|
self.custom_css_providers.clearRetainingCapacity();
|
||||||
|
|
||||||
|
for (self.config.@"gtk-custom-css".value.items) |p| {
|
||||||
|
const path, const optional = switch (p) {
|
||||||
|
.optional => |path| .{ path, true },
|
||||||
|
.required => |path| .{ path, false },
|
||||||
|
};
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
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
|
||||||
|
);
|
||||||
|
defer self.core_app.alloc.free(contents);
|
||||||
|
|
||||||
|
const provider = c.gtk_css_provider_new();
|
||||||
|
c.gtk_style_context_add_provider_for_display(
|
||||||
|
display,
|
||||||
|
@ptrCast(provider),
|
||||||
|
c.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 {
|
||||||
|
if (version.atLeast(4, 12, 0)) {
|
||||||
|
const g_bytes = c.g_bytes_new(data.ptr, data.len);
|
||||||
|
defer c.g_bytes_unref(g_bytes);
|
||||||
|
|
||||||
|
c.gtk_css_provider_load_from_bytes(provider, g_bytes);
|
||||||
|
} else {
|
||||||
|
c.gtk_css_provider_load_from_data(
|
||||||
|
provider,
|
||||||
|
data.ptr,
|
||||||
|
@intCast(data.len),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by CoreApp to wake up the event loop.
|
/// Called by CoreApp to wake up the event loop.
|
||||||
|
@ -1987,6 +1987,15 @@ keybind: Keybinds = .{},
|
|||||||
/// Adwaita support.
|
/// Adwaita support.
|
||||||
@"gtk-adwaita": bool = true,
|
@"gtk-adwaita": bool = true,
|
||||||
|
|
||||||
|
/// Custom CSS files to be loaded.
|
||||||
|
///
|
||||||
|
/// This configuration can be repeated multiple times to load multiple files.
|
||||||
|
/// Prepend a ? character to the file path to suppress errors if the file does
|
||||||
|
/// not exist. If you want to include a file that begins with a literal ?
|
||||||
|
/// character, surround the file path in double quotes (").
|
||||||
|
/// The file size limit for a single stylesheet is 5MiB.
|
||||||
|
@"gtk-custom-css": RepeatablePath = .{},
|
||||||
|
|
||||||
/// If `true` (default), applications running in the terminal can show desktop
|
/// If `true` (default), applications running in the terminal can show desktop
|
||||||
/// notifications using certain escape sequences such as OSC 9 or OSC 777.
|
/// notifications using certain escape sequences such as OSC 9 or OSC 777.
|
||||||
@"desktop-notifications": bool = true,
|
@"desktop-notifications": bool = true,
|
||||||
|
Reference in New Issue
Block a user