From 27c3382a6a6b1d87262fe2f21d68242093c6950d Mon Sep 17 00:00:00 2001 From: Maciej Bartczak <39600846+maciekbartczak@users.noreply.github.com> Date: Tue, 31 Dec 2024 15:53:10 +0100 Subject: [PATCH 1/4] Implement loading custom css in the GTK app --- src/apprt/gtk/App.zig | 55 ++++++++++++++++++++++++++++++++++++++++++- src/config/Config.zig | 8 +++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index c10ba7993..ee663485f 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -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.ArrayList(*c.GtkCssProvider), + /// The timer used to quit the application after the last window is closed. quit_timer: union(enum) { off: void, @@ -425,6 +428,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // our "activate" call above will open a window. .running = c.g_application_get_is_remote(gapp) == 0, .css_provider = css_provider, + .custom_css_providers = std.ArrayList(*c.GtkCssProvider).init(core_app.alloc), }; } @@ -441,6 +445,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.config.deinit(); } @@ -892,7 +901,7 @@ 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( @@ -900,6 +909,12 @@ fn syncConfigChanges(self: *App) !void { .{}, ), }; + self.loadCustomCss() catch |err| switch (err) { + error.OutOfMemory => log.warn( + "out of memory loading custom CSS, no custom CSS applied", + .{}, + ), + }; } /// This should be called whenever the configuration changes to update @@ -1040,6 +1055,44 @@ fn loadRuntimeCss( ); } +fn loadCustomCss(self: *App) Allocator.Error!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 }, + }; + std.fs.accessAbsolute(path, .{}) catch |err| { + if (err != error.FileNotFound or !optional) { + log.err("error opening gtk-custom-css file {s}: {}", .{ path, err }); + } + continue; + }; + + const provider = c.gtk_css_provider_new(); + + c.gtk_style_context_add_provider_for_display( + display, + @ptrCast(provider), + c.GTK_STYLE_PROVIDER_PRIORITY_USER, + ); + c.gtk_css_provider_load_from_path(provider, path); + + try self.custom_css_providers.append(provider); + } +} + /// Called by CoreApp to wake up the event loop. pub fn wakeup(self: App) void { _ = self; diff --git a/src/config/Config.zig b/src/config/Config.zig index 91c07cc78..a136017d1 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1925,6 +1925,14 @@ 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 ("). +@"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, From 973467b1caaa3c77ad6fea2362892f1e61b34b1a Mon Sep 17 00:00:00 2001 From: Maciej Bartczak <39600846+maciekbartczak@users.noreply.github.com> Date: Tue, 31 Dec 2024 20:08:12 +0100 Subject: [PATCH 2/4] code review: - use ArrayListUnmanaged - read the stylesheet file using zig api - use proper css_provider_load_ function depending on the GTK version --- src/apprt/gtk/App.zig | 56 +++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index ee663485f..60c029330 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -82,7 +82,7 @@ transient_cgroup_base: ?[]const u8 = null, css_provider: *c.GtkCssProvider, /// Providers for loading custom stylesheets defined by user -custom_css_providers: std.ArrayList(*c.GtkCssProvider), +custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider), /// The timer used to quit the application after the last window is closed. quit_timer: union(enum) { @@ -428,7 +428,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // our "activate" call above will open a window. .running = c.g_application_get_is_remote(gapp) == 0, .css_provider = css_provider, - .custom_css_providers = std.ArrayList(*c.GtkCssProvider).init(core_app.alloc), + .custom_css_providers = .{}, }; } @@ -448,7 +448,7 @@ pub fn terminate(self: *App) void { for (self.custom_css_providers.items) |provider| { c.g_object_unref(provider); } - self.custom_css_providers.deinit(); + self.custom_css_providers.deinit(self.core_app.alloc); self.config.deinit(); } @@ -909,11 +909,8 @@ fn syncConfigChanges(self: *App) !void { .{}, ), }; - self.loadCustomCss() catch |err| switch (err) { - error.OutOfMemory => log.warn( - "out of memory loading custom CSS, no custom CSS applied", - .{}, - ), + self.loadCustomCss() catch |err| { + log.warn("Failed to load custom CSS, no custom CSS applied, err={}", .{err}); }; } @@ -1048,14 +1045,10 @@ 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) Allocator.Error!void { +fn loadCustomCss(self: *App) !void { const display = c.gdk_display_get_default(); // unload the previously loaded style providers @@ -1073,23 +1066,50 @@ fn loadCustomCss(self: *App) Allocator.Error!void { .optional => |path| .{ path, true }, .required => |path| .{ path, false }, }; - std.fs.accessAbsolute(path, .{}) catch |err| { + 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}); + var buf_reader = std.io.bufferedReader(file.reader()); + const contents = try buf_reader.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, ); - c.gtk_css_provider_load_from_path(provider, path); - try self.custom_css_providers.append(provider); + 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), + ); } } From 4ccd56484949a3c9b20e49f3f5b9a920b4fecb9e Mon Sep 17 00:00:00 2001 From: Maciej Bartczak <39600846+maciekbartczak@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:21:44 +0100 Subject: [PATCH 3/4] code review: - initialize custom_css_providers using a default value - remove usage of buffered reader - document maximum file size - handle exceptions explicitly --- src/apprt/gtk/App.zig | 40 ++++++++++++++++++++++++++++++---------- src/config/Config.zig | 1 + 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 60c029330..ee1b8d41b 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -82,7 +82,7 @@ transient_cgroup_base: ?[]const u8 = null, css_provider: *c.GtkCssProvider, /// Providers for loading custom stylesheets defined by user -custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider), +custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider) = .{}, /// The timer used to quit the application after the last window is closed. quit_timer: union(enum) { @@ -428,7 +428,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // our "activate" call above will open a window. .running = c.g_application_get_is_remote(gapp) == 0, .css_provider = css_provider, - .custom_css_providers = .{}, }; } @@ -909,8 +908,33 @@ fn syncConfigChanges(self: *App) !void { .{}, ), }; - self.loadCustomCss() catch |err| { - log.warn("Failed to load custom CSS, no custom CSS applied, err={}", .{err}); + self.loadCustomCss() catch |err| switch (err) { + error.OutOfMemory => log.warn( + "out of memory loading custom CSS, no custom CSS applied", + .{}, + ), + error.StreamTooLong => log.warn( + "failed to load custom CSS, no custom CSS applied - encountered stream too long error: {}", + .{err}, + ), + error.Unexpected => log.warn( + "failed to load custom CSS, no custom CSS applied - encountered unexpected error: {}", + .{err}, + ), + std.fs.File.Reader.Error.InputOutput, + std.fs.File.Reader.Error.SystemResources, + std.fs.File.Reader.Error.IsDir, + std.fs.File.Reader.Error.OperationAborted, + std.fs.File.Reader.Error.BrokenPipe, + std.fs.File.Reader.Error.ConnectionResetByPeer, + std.fs.File.Reader.Error.ConnectionTimedOut, + std.fs.File.Reader.Error.NotOpenForReading, + std.fs.File.Reader.Error.SocketNotConnected, + std.fs.File.Reader.Error.WouldBlock, + std.fs.File.Reader.Error.AccessDenied => log.warn( + "failed to load custom CSS, no custom CSS applied - encountered error while reading file: {}", + .{err}, + ), }; } @@ -1075,8 +1099,7 @@ fn loadCustomCss(self: *App) !void { defer file.close(); log.info("loading gtk-custom-css path={s}", .{path}); - var buf_reader = std.io.bufferedReader(file.reader()); - const contents = try buf_reader.reader().readAllAlloc( + const contents = try file.reader().readAllAlloc( self.core_app.alloc, 5 * 1024 * 1024 // 5MB ); @@ -1100,10 +1123,7 @@ fn loadCssProviderFromData(provider: *c.GtkCssProvider, data: []const u8) void { 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 - ); + c.gtk_css_provider_load_from_bytes(provider, g_bytes); } else { c.gtk_css_provider_load_from_data( provider, diff --git a/src/config/Config.zig b/src/config/Config.zig index a136017d1..70b85790c 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1931,6 +1931,7 @@ keybind: Keybinds = .{}, /// 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 From 9ce4e36aa26549159ed089d4d77ddabad5d41bc4 Mon Sep 17 00:00:00 2001 From: Maciej Bartczak <39600846+maciekbartczak@users.noreply.github.com> Date: Wed, 1 Jan 2025 08:56:58 +0100 Subject: [PATCH 4/4] code review - revert explicit error handling --- src/apprt/gtk/App.zig | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index ee1b8d41b..4677f0da7 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -908,33 +908,8 @@ fn syncConfigChanges(self: *App) !void { .{}, ), }; - self.loadCustomCss() catch |err| switch (err) { - error.OutOfMemory => log.warn( - "out of memory loading custom CSS, no custom CSS applied", - .{}, - ), - error.StreamTooLong => log.warn( - "failed to load custom CSS, no custom CSS applied - encountered stream too long error: {}", - .{err}, - ), - error.Unexpected => log.warn( - "failed to load custom CSS, no custom CSS applied - encountered unexpected error: {}", - .{err}, - ), - std.fs.File.Reader.Error.InputOutput, - std.fs.File.Reader.Error.SystemResources, - std.fs.File.Reader.Error.IsDir, - std.fs.File.Reader.Error.OperationAborted, - std.fs.File.Reader.Error.BrokenPipe, - std.fs.File.Reader.Error.ConnectionResetByPeer, - std.fs.File.Reader.Error.ConnectionTimedOut, - std.fs.File.Reader.Error.NotOpenForReading, - std.fs.File.Reader.Error.SocketNotConnected, - std.fs.File.Reader.Error.WouldBlock, - std.fs.File.Reader.Error.AccessDenied => log.warn( - "failed to load custom CSS, no custom CSS applied - encountered error while reading file: {}", - .{err}, - ), + self.loadCustomCss() catch |err| { + log.warn("Failed to load custom CSS, no custom CSS applied, err={}", .{err}); }; }