gtk: apply initial appearance settings before presenting

Fixes #5934 (only on the GTK side), #5960
This commit is contained in:
Leah Amelia Chen
2025-02-24 15:27:12 +01:00
parent 61f41e5c01
commit 4703fc74b4

View File

@ -7,6 +7,10 @@ const Window = @This();
const std = @import("std");
const builtin = @import("builtin");
const gtk = @import("gtk");
const gobject = @import("gobject");
const build_config = @import("../../build_config.zig");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
@ -73,11 +77,12 @@ pub const DerivedConfig = struct {
gtk_wide_tabs: bool,
gtk_toolbar_style: configpkg.Config.GtkToolbarStyle,
title: ?[:0]const u8,
maximize: bool,
fullscreen: bool,
window_decoration: configpkg.Config.WindowDecoration,
pub fn init(config: *const configpkg.Config) DerivedConfig {
pub fn init(config: *const configpkg.Config, alloc: Allocator) !DerivedConfig {
return .{
.background_opacity = config.@"background-opacity",
.background_blur = config.@"background-blur",
@ -88,11 +93,16 @@ pub const DerivedConfig = struct {
.gtk_wide_tabs = config.@"gtk-wide-tabs",
.gtk_toolbar_style = config.@"gtk-toolbar-style",
.title = if (config.title) |t| try alloc.dupe(u8, t) else null,
.maximize = config.maximize,
.fullscreen = config.fullscreen,
.window_decoration = config.@"window-decoration",
};
}
pub fn deinit(self: *DerivedConfig, alloc: Allocator) void {
if (self.title) |t| alloc.free(t);
}
};
pub fn create(alloc: Allocator, app: *App) !*Window {
@ -114,7 +124,7 @@ pub fn init(self: *Window, app: *App) !void {
self.* = .{
.app = app,
.last_config = @intFromPtr(&app.config),
.config = DerivedConfig.init(&app.config),
.config = try DerivedConfig.init(&app.config, app.core_app.alloc),
.window = undefined,
.headerbar = undefined,
.tab_overview = null,
@ -130,17 +140,10 @@ pub fn init(self: *Window, app: *App) !void {
self.window = @ptrCast(@alignCast(gtk_widget));
c.gtk_window_set_title(self.window, "Ghostty");
c.gtk_window_set_default_size(self.window, 1000, 600);
c.gtk_widget_add_css_class(gtk_widget, "window");
c.gtk_widget_add_css_class(gtk_widget, "terminal-window");
// GTK4 grabs F10 input by default to focus the menubar icon. We want
// to disable this so that terminal programs can capture F10 (such as htop)
c.gtk_window_set_handle_menubar_accel(self.window, 0);
c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
// Create our box which will hold our widgets in the main content area.
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
@ -353,11 +356,7 @@ pub fn init(self: *Window, app: *App) !void {
if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
}
// If we want the window to be maximized, we do that here.
if (self.config.maximize) c.gtk_window_maximize(self.window);
// If we are in fullscreen mode, new windows start fullscreen.
if (self.config.fullscreen) c.gtk_window_fullscreen(self.window);
try self.initAppearance();
// Show the window
c.gtk_widget_show(gtk_widget);
@ -367,19 +366,44 @@ pub fn updateConfig(
self: *Window,
config: *const configpkg.Config,
) !void {
// avoid multiple reconfigs when we have many surfaces contained in this
// window using the integer value of config as a simple marker to know if
// we've "seen" this particular config before
// HACK:
// Avoid multiple reconfigs when we have many surfaces contained in this
// window using the integer value of the config pointer as a simple marker
// to know if we've "seen" this particular config before.
//
// For the long run we should try to make the flow of config updates saner
// and avoid manual deduplication. See #5947 for more information.
const this_config = @intFromPtr(config);
if (self.last_config == this_config) return;
self.last_config = this_config;
self.config = DerivedConfig.init(config);
self.config = try DerivedConfig.init(config, self.app.core_app.alloc);
// We always resync our appearance whenever the config changes.
try self.syncAppearance();
}
/// Initializes the appearance of the window.
///
/// Not all appearance changes *should* be applied when a config option is
/// reloaded (for instance, the maximize and fullscreen settings should only
/// apply to new windows), so we apply them here.
pub fn initAppearance(self: *Window) !void {
// FIXME: Remove once self.window has been migrated to use zig-gobject
const window: *gtk.Window = @ptrCast(self.window);
window.setTitle(self.config.title orelse "Ghostty");
window.setDefaultSize(1000, 600);
window.as(gtk.Widget).addCssClass("window");
window.as(gtk.Widget).addCssClass("terminal-window");
window.setIconName(build_config.bundle_id);
if (self.config.maximize) window.maximize();
if (self.config.fullscreen) window.fullscreen();
try self.syncAppearance();
}
/// Updates appearance based on config settings. Will be called once upon window
/// realization, every time the config is reloaded, and every time a window state
/// is toggled (un-/maximized, un-/fullscreened, window decorations toggled, etc.)
@ -387,16 +411,19 @@ pub fn updateConfig(
/// TODO: Many of the initial style settings in `create` could possibly be made
/// reactive by moving them here.
pub fn syncAppearance(self: *Window) !void {
// FIXME: Remove once self.window has been migrated to use zig-gobject
const window: *gtk.Window = @ptrCast(self.window);
const csd_enabled = self.winproto.clientSideDecorationEnabled();
c.gtk_window_set_decorated(self.window, @intFromBool(csd_enabled));
window.setDecorated(@intFromBool(csd_enabled));
// Fix any artifacting that may occur in window corners. The .ssd CSS
// class is defined in the GtkWindow documentation:
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
// for .ssd is provided by GTK and Adwaita.
toggleCssClass(@ptrCast(self.window), "csd", csd_enabled);
toggleCssClass(@ptrCast(self.window), "ssd", !csd_enabled);
toggleCssClass(@ptrCast(self.window), "no-border-radius", !csd_enabled);
toggleCssClass(window, "csd", csd_enabled);
toggleCssClass(window, "ssd", !csd_enabled);
toggleCssClass(window, "no-border-radius", !csd_enabled);
self.headerbar.setVisible(visible: {
// Never display the header bar when CSDs are disabled.
@ -414,7 +441,7 @@ pub fn syncAppearance(self: *Window) !void {
});
toggleCssClass(
@ptrCast(self.window),
window,
"background",
self.config.background_opacity >= 1,
);
@ -423,7 +450,7 @@ pub fn syncAppearance(self: *Window) !void {
// GTK version is before 4.16. The conditional is because above 4.16
// we use GTK CSS color variables.
toggleCssClass(
@ptrCast(self.window),
window,
"window-theme-ghostty",
!version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty,
);
@ -451,23 +478,17 @@ pub fn syncAppearance(self: *Window) !void {
self.winproto.syncAppearance() catch |err| {
log.warn("failed to sync winproto appearance error={}", .{err});
};
toggleCssClass(
@ptrCast(self.window),
"background",
self.config.background_opacity >= 1,
);
}
fn toggleCssClass(
widget: *c.GtkWidget,
widget: anytype,
class: [:0]const u8,
v: bool,
) void {
if (v) {
c.gtk_widget_add_css_class(widget, class);
widget.as(gtk.Widget).addCssClass(class);
} else {
c.gtk_widget_remove_css_class(widget, class);
widget.as(gtk.Widget).removeCssClass(class);
}
}
@ -510,6 +531,7 @@ fn initActions(self: *Window) void {
pub fn deinit(self: *Window) void {
self.winproto.deinit(self.app.core_app.alloc);
self.config.deinit(self.app.core_app.alloc);
if (self.adw_tab_overview_focus_timer) |timer| {
_ = c.g_source_remove(timer);