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 <data>: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 <data>: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: <data>:2:3-14: No property named "border-poop"
warning(glib): WARNING: Gtk: Theme parser warning: <data>:1:3-3:1: Unterminated block at end of document
```
pull/9139/head
Jeffrey C. Ollie 2025-10-10 15:41:58 -05:00 committed by GitHub
parent 854c8e6975
commit c5ad7563f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 78 additions and 29 deletions

View File

@ -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);
}