mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
apprt/gtk-ng: runtime CSS and custom CSS
This commit is contained in:
@@ -49,9 +49,9 @@ pub const blueprints: []const Blueprint = &.{
|
||||
/// CSS files in css_path
|
||||
pub const css = [_][]const u8{
|
||||
"style.css",
|
||||
// "style-dark.css",
|
||||
// "style-hc.css",
|
||||
// "style-hc-dark.css",
|
||||
"style-dark.css",
|
||||
"style-hc.css",
|
||||
"style-hc-dark.css",
|
||||
};
|
||||
|
||||
pub const Blueprint = struct {
|
||||
|
@@ -132,6 +132,12 @@ pub const Application = extern struct {
|
||||
/// glib source for our signal handler.
|
||||
signal_source: ?c_uint = null,
|
||||
|
||||
/// CSS Provider for any styles based on Ghostty configuration values.
|
||||
css_provider: *gtk.CssProvider,
|
||||
|
||||
/// Providers for loading custom stylesheets defined by user
|
||||
custom_css_providers: std.ArrayListUnmanaged(*gtk.CssProvider) = .empty,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
@@ -267,6 +273,16 @@ pub const Application = extern struct {
|
||||
const config_obj: *Config = try .new(alloc, &config);
|
||||
errdefer config_obj.unref();
|
||||
|
||||
// Internally, GTK ensures that only one instance of this provider
|
||||
// exists in the provider list for the display.
|
||||
const css_provider = gtk.CssProvider.new();
|
||||
gtk.StyleContext.addProviderForDisplay(
|
||||
display,
|
||||
css_provider.as(gtk.StyleProvider),
|
||||
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
|
||||
);
|
||||
errdefer css_provider.unref();
|
||||
|
||||
// Initialize the app.
|
||||
const self = gobject.ext.newInstance(Self, .{
|
||||
.application_id = app_id.ptr,
|
||||
@@ -287,8 +303,22 @@ pub const Application = extern struct {
|
||||
.core_app = core_app,
|
||||
.config = config_obj,
|
||||
.winproto = wp,
|
||||
.css_provider = css_provider,
|
||||
.custom_css_providers = .empty,
|
||||
};
|
||||
|
||||
// Signals
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self,
|
||||
*Self,
|
||||
propConfig,
|
||||
self,
|
||||
.{ .detail = "config" },
|
||||
);
|
||||
|
||||
// Trigger initial config changes
|
||||
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -303,6 +333,22 @@ pub const Application = extern struct {
|
||||
priv.config.unref();
|
||||
priv.winproto.deinit(alloc);
|
||||
if (priv.transient_cgroup_base) |base| alloc.free(base);
|
||||
if (gdk.Display.getDefault()) |display| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
priv.css_provider.as(gtk.StyleProvider),
|
||||
);
|
||||
|
||||
for (priv.custom_css_providers.items) |provider| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
);
|
||||
}
|
||||
}
|
||||
priv.css_provider.unref();
|
||||
for (priv.custom_css_providers.items) |provider| provider.unref();
|
||||
priv.custom_css_providers.deinit(alloc);
|
||||
}
|
||||
|
||||
/// The global allocator that all other classes should use by
|
||||
@@ -659,6 +705,155 @@ pub const Application = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn loadRuntimeCss(
|
||||
self: *Self,
|
||||
) Allocator.Error!void {
|
||||
const alloc = self.allocator();
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
const writer = buf.writer(alloc);
|
||||
|
||||
const config = self.private().config.get();
|
||||
const window_theme = config.@"window-theme";
|
||||
const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background;
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.print(
|
||||
\\widget.unfocused-split {{
|
||||
\\ opacity: {d:.2};
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
, .{
|
||||
1.0 - config.@"unfocused-split-opacity",
|
||||
unfocused_fill.r,
|
||||
unfocused_fill.g,
|
||||
unfocused_fill.b,
|
||||
});
|
||||
|
||||
if (config.@"split-divider-color") |color| {
|
||||
try writer.print(
|
||||
\\.terminal-window .notebook separator {{
|
||||
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\}}
|
||||
, .{
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.@"window-title-font-family") |font_family| {
|
||||
try writer.print(
|
||||
\\.window headerbar {{
|
||||
\\ font-family: "{[font_family]s}";
|
||||
\\}}
|
||||
, .{ .font_family = font_family });
|
||||
}
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\:root {{
|
||||
\\ --ghostty-fg: rgb({d},{d},{d});
|
||||
\\ --ghostty-bg: rgb({d},{d},{d});
|
||||
\\ --headerbar-fg-color: var(--ghostty-fg);
|
||||
\\ --headerbar-bg-color: var(--ghostty-bg);
|
||||
\\ --headerbar-backdrop-color: oklab(from var(--headerbar-bg-color) calc(l * 0.9) a b / alpha);
|
||||
\\ --overview-fg-color: var(--ghostty-fg);
|
||||
\\ --overview-bg-color: var(--ghostty-bg);
|
||||
\\ --popover-fg-color: var(--ghostty-fg);
|
||||
\\ --popover-bg-color: var(--ghostty-bg);
|
||||
\\ --window-fg-color: var(--ghostty-fg);
|
||||
\\ --window-bg-color: var(--ghostty-bg);
|
||||
\\}}
|
||||
\\windowhandle {{
|
||||
\\ background-color: var(--headerbar-bg-color);
|
||||
\\ color: var(--headerbar-fg-color);
|
||||
\\}}
|
||||
\\windowhandle:backdrop {{
|
||||
\\ background-color: var(--headerbar-backdrop-color);
|
||||
\\}}
|
||||
, .{
|
||||
headerbar_foreground.r,
|
||||
headerbar_foreground.g,
|
||||
headerbar_foreground.b,
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
|
||||
const data = try alloc.dupeZ(u8, buf.items);
|
||||
defer alloc.free(data);
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
fn loadCustomCss(self: *Self) !void {
|
||||
const priv = self.private();
|
||||
const alloc = self.allocator();
|
||||
const display = gdk.Display.getDefault() orelse {
|
||||
log.warn("unable to get display", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
// unload the previously loaded style providers
|
||||
for (priv.custom_css_providers.items) |provider| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
);
|
||||
provider.unref();
|
||||
}
|
||||
priv.custom_css_providers.clearRetainingCapacity();
|
||||
|
||||
const config = priv.config.getMut();
|
||||
for (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.warn(
|
||||
"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(
|
||||
alloc,
|
||||
5 * 1024 * 1024, // 5MB,
|
||||
);
|
||||
defer alloc.free(contents);
|
||||
|
||||
const data = try alloc.dupeZ(u8, contents);
|
||||
defer alloc.free(data);
|
||||
|
||||
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),
|
||||
gtk.STYLE_PROVIDER_PRIORITY_USER,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
@@ -684,6 +879,28 @@ pub const Application = extern struct {
|
||||
self.showConfigErrorsDialog();
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
_: *Application,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// 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
|
||||
// config change 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},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Libghostty Callbacks
|
||||
|
||||
@@ -1902,3 +2119,8 @@ 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);
|
||||
}
|
||||
|
3
src/apprt/gtk-ng/css/style-dark.css
Normal file
3
src/apprt/gtk-ng/css/style-dark.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
3
src/apprt/gtk-ng/css/style-hc-dark.css
Normal file
3
src/apprt/gtk-ng/css/style-hc-dark.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
3
src/apprt/gtk-ng/css/style-hc.css
Normal file
3
src/apprt/gtk-ng/css/style-hc.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
@@ -13,6 +13,21 @@
|
||||
# You must gracefully exit Ghostty (do not SIGINT) by closing all windows
|
||||
# and quitting. Otherwise, we leave a number of GTK resources around.
|
||||
|
||||
{
|
||||
GTK CSS Provider Leak
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
fun:g_malloc0
|
||||
fun:gtk_css_value_alloc
|
||||
fun:_gtk_css_reference_value_new
|
||||
fun:parse_ruleset
|
||||
fun:gtk_css_provider_load_internal
|
||||
fun:gtk_css_provider_load_from_bytes
|
||||
fun:gtk_css_provider_load_from_string
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GDK SVG Loading Leaks
|
||||
Memcheck:Leak
|
||||
|
Reference in New Issue
Block a user