Skip to content

Commit

Permalink
Implement loading custom css in the GTK app (#4200)
Browse files Browse the repository at this point in the history
Closes #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.
  • Loading branch information
mitchellh authored Jan 2, 2025
2 parents 764a236 + 9ce4e36 commit 602e4eb
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 6 deletions.
80 changes: 74 additions & 6 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ transient_cgroup_base: ?[]const u8 = null,
/// CSS Provider for any styles based on ghostty configuration values
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.
quit_timer: union(enum) {
off: void,
Expand Down Expand Up @@ -441,6 +444,11 @@ pub fn terminate(self: *App) void {
if (self.context_menu) |context_menu| c.g_object_unref(context_menu);
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();
}

Expand Down Expand Up @@ -893,14 +901,17 @@ fn syncConfigChanges(self: *App) !void {
try self.updateConfigErrors();
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.
self.loadRuntimeCss() catch |err| switch (err) {
error.OutOfMemory => log.warn(
"out of memory loading runtime CSS, no runtime CSS applied",
.{},
),
};
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
Expand Down Expand Up @@ -1034,11 +1045,68 @@ fn loadRuntimeCss(
}

// Clears any previously loaded CSS from this provider
c.gtk_css_provider_load_from_data(
self.css_provider,
buf.items.ptr,
@intCast(buf.items.len),
);
loadCssProviderFromData(self.css_provider, buf.items);
}

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.
Expand Down
9 changes: 9 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,15 @@ keybind: Keybinds = .{},
/// Adwaita support.
@"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
/// notifications using certain escape sequences such as OSC 9 or OSC 777.
@"desktop-notifications": bool = true,
Expand Down

0 comments on commit 602e4eb

Please sign in to comment.