From c5ad7563f92656ec02bd08856b46431f2e222e69 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 10 Oct 2025 15:41:58 -0500 Subject: [PATCH] gtk: better reporting for CSS parsing problems (#9129) Log messages will include the problematic CSS, simplifying debugging. Especially helpful since some of our CSS is generated at runtime so it could be difficult to examine the CSS "source". ``` info(gtk_ghostty_application): loading gtk-custom-css path=/home/ghostty/dev/ghostty/x.css warning(gtk_ghostty_application): css parsing failed at :2:3-14: gtk-css-parser-error-quark 4 No property named "border-poop" * { border-poop: 0; warning(gtk_ghostty_application): css parsing failed at :1:3-3:1: gtk-css-parser-warning-quark 1 Unterminated block at end of document * { border-poop: 0; ``` vs: ``` info(gtk_ghostty_application): loading gtk-custom-css path=/home/ghostty/dev/ghostty/x.css warning(glib): WARNING: Gtk: Theme parser error: :2:3-14: No property named "border-poop" warning(glib): WARNING: Gtk: Theme parser warning: :1:3-3:1: Unterminated block at end of document ``` --- src/apprt/gtk/class/application.zig | 107 ++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig index a35cd5b3f..d75a0ef7f 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -394,6 +394,14 @@ pub const Application = extern struct { .{ .detail = "config" }, ); + _ = gtk.CssProvider.signals.parsing_error.connect( + css_provider, + *Self, + signalCssParsingError, + self, + .{}, + ); + // Trigger initial config changes self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec); @@ -812,8 +820,8 @@ pub const Application = extern struct { fn loadRuntimeCss(self: *Self) (Allocator.Error || std.Io.Writer.Error)!void { const alloc = self.allocator(); - - const config = self.private().config.get(); + const priv: *Private = self.private(); + const config = priv.config.get(); var buf: std.Io.Writer.Allocating = try .initCapacity(alloc, 2048); defer buf.deinit(); @@ -862,19 +870,15 @@ pub const Application = extern struct { , .{ .font_family = font_family }); } - // ensure that we have a sentinel - try writer.writeByte(0); + const contents = buf.written(); - const data_ = buf.written(); - const data = data_[0 .. data_.len - 1 :0]; + log.debug("runtime CSS is {d} bytes", .{contents.len}); - log.debug("runtime CSS is {d} bytes", .{data.len + 1}); + const bytes = glib.Bytes.new(contents.ptr, contents.len); + defer bytes.unref(); // Clears any previously loaded CSS from this provider - loadCssProviderFromData( - self.private().css_provider, - data, - ); + priv.css_provider.loadFromBytes(bytes); } /// Load runtime CSS for older than GTK 4.16 @@ -1013,8 +1017,8 @@ pub const Application = extern struct { } } - fn loadCustomCss(self: *Self) !void { - const priv = self.private(); + fn loadCustomCss(self: *Self) (std.fs.File.ReadError || Allocator.Error)!void { + const priv: *Private = self.private(); const alloc = self.allocator(); const display = gdk.Display.getDefault() orelse { log.warn("unable to get display", .{}); @@ -1031,7 +1035,7 @@ pub const Application = extern struct { } priv.custom_css_providers.clearRetainingCapacity(); - const config = priv.config.getMut(); + const config = priv.config.get(); for (config.@"gtk-custom-css".value.items) |p| { const path, const optional = switch (p) { .optional => |path| .{ path, true }, @@ -1048,23 +1052,42 @@ pub const Application = extern struct { }; defer file.close(); + const css_file_size_limit = 5 * 1024 * 1024; // 5MB + log.info("loading gtk-custom-css path={s}", .{path}); - const contents = try file.readToEndAlloc( + const contents = file.readToEndAlloc( alloc, - 5 * 1024 * 1024, // 5MB, - ); + css_file_size_limit, + ) catch |err| switch (err) { + error.FileTooBig => { + log.warn("gtk-custom-css file {s} was larger than {Bi}", .{ path, css_file_size_limit }); + continue; + }, + else => |e| return e, + }; defer alloc.free(contents); - const data = try alloc.dupeZ(u8, contents); - defer alloc.free(data); + const bytes = glib.Bytes.new(contents.ptr, contents.len); + defer bytes.unref(); + + const css_provider = gtk.CssProvider.new(); + errdefer css_provider.unref(); + + _ = gtk.CssProvider.signals.parsing_error.connect( + css_provider, + *Self, + signalCssParsingError, + self, + .{}, + ); + + try priv.custom_css_providers.append(alloc, css_provider); + + css_provider.loadFromBytes(bytes); - const provider = gtk.CssProvider.new(); - errdefer provider.unref(); - try priv.custom_css_providers.append(alloc, provider); - loadCssProviderFromData(provider, data); gtk.StyleContext.addProviderForDisplay( display, - provider.as(gtk.StyleProvider), + css_provider.as(gtk.StyleProvider), gtk.STYLE_PROVIDER_PRIORITY_USER, ); } @@ -1180,6 +1203,37 @@ pub const Application = extern struct { }; } + /// Log CSS parsing error + fn signalCssParsingError( + _: *gtk.CssProvider, + css_section: *gtk.CssSection, + err: *glib.Error, + _: *Self, + ) callconv(.c) void { + const location = css_section.toString(); + defer glib.free(location); + if (comptime gtk_version.atLeast(4, 16, 0)) bytes: { + const bytes = css_section.getBytes() orelse break :bytes; + var len: usize = undefined; + const ptr = bytes.getData(&len) orelse break :bytes; + const data = ptr[0..len]; + log.warn("css parsing failed at {s}: {s} {d} {s}\n{s}", .{ + location, + glib.quarkToString(err.f_domain), + err.f_code, + err.f_message orelse "«unknown»", + data, + }); + return; + } + log.warn("css parsing failed at {s}: {s} {d} {s}", .{ + location, + glib.quarkToString(err.f_domain), + err.f_code, + err.f_message orelse "«unknown»", + }); + } + //--------------------------------------------------------------- // Libghostty Callbacks @@ -2583,8 +2637,3 @@ fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) // Abusing integers to be enums and booleans is a terrible idea, C. return if (window.isActive() != 0) 0 else -1; } - -fn loadCssProviderFromData(provider: *gtk.CssProvider, data: [:0]const u8) void { - assert(gtk_version.runtimeAtLeast(4, 12, 0)); - provider.loadFromString(data); -}