gtk: apply all window appearance changes in syncAppearance

The GTK side of appearance code is kind of a mess with several different
functions all having the responsibility of interacting with each other
and setting the appropriate window appearance. It should solely be the
responsibility of the `syncAppearance` function to apply appearance
changes, with other callbacks/functions calling it instead: much like
what we already do for the macOS apprt.
This commit is contained in:
Leah Amelia Chen
2025-01-24 16:26:15 +01:00
parent da32534e8a
commit aa2dbe2919
5 changed files with 182 additions and 191 deletions

View File

@ -31,6 +31,8 @@ const log = std.log.scoped(.gtk);
app: *App, app: *App,
config: DerivedConfig,
/// Our window /// Our window
window: *c.GtkWindow, window: *c.GtkWindow,
@ -55,6 +57,38 @@ adw_tab_overview_focus_timer: ?c.guint = null,
/// State and logic for windowing protocol for a window. /// State and logic for windowing protocol for a window.
winproto: winproto.Window, winproto: winproto.Window,
pub const DerivedConfig = struct {
background_opacity: f64,
background_blur: configpkg.Config.BackgroundBlur,
window_theme: configpkg.Config.WindowTheme,
gtk_titlebar: bool,
gtk_titlebar_hide_when_maximized: bool,
gtk_tabs_location: configpkg.Config.GtkTabsLocation,
gtk_wide_tabs: bool,
gtk_toolbar_style: configpkg.Config.GtkToolbarStyle,
maximize: bool,
fullscreen: bool,
window_decoration: configpkg.Config.WindowDecoration,
pub fn init(config: *const configpkg.Config) DerivedConfig {
return .{
.background_opacity = config.@"background-opacity",
.background_blur = config.@"background-blur",
.window_theme = config.@"window-theme",
.gtk_titlebar = config.@"gtk-titlebar",
.gtk_titlebar_hide_when_maximized = config.@"gtk-titlebar-hide-when-maximized",
.gtk_tabs_location = config.@"gtk-tabs-location",
.gtk_wide_tabs = config.@"gtk-wide-tabs",
.gtk_toolbar_style = config.@"gtk-toolbar-style",
.maximize = config.maximize,
.fullscreen = config.fullscreen,
.window_decoration = config.@"window-decoration",
};
}
};
pub fn create(alloc: Allocator, app: *App) !*Window { pub fn create(alloc: Allocator, app: *App) !*Window {
// Allocate a fixed pointer for our window. We try to minimize // Allocate a fixed pointer for our window. We try to minimize
// allocations but windows and other GUI requirements are so minimal // allocations but windows and other GUI requirements are so minimal
@ -73,6 +107,7 @@ pub fn init(self: *Window, app: *App) !void {
// Set up our own state // Set up our own state
self.* = .{ self.* = .{
.app = app, .app = app,
.config = DerivedConfig.init(&app.config),
.window = undefined, .window = undefined,
.headerbar = undefined, .headerbar = undefined,
.tab_overview = null, .tab_overview = null,
@ -99,13 +134,6 @@ pub fn init(self: *Window, app: *App) !void {
c.gtk_window_set_icon_name(self.window, build_config.bundle_id); c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
// Apply class to color headerbar if window-theme is set to `ghostty` and
// GTK version is before 4.16. The conditional is because above 4.16
// we use GTK CSS color variables.
if (!version.atLeast(4, 16, 0) and app.config.@"window-theme" == .ghostty) {
c.gtk_widget_add_css_class(gtk_widget, "window-theme-ghostty");
}
// Create our box which will hold our widgets in the main content area. // Create our box which will hold our widgets in the main content area.
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
@ -153,7 +181,7 @@ pub fn init(self: *Window, app: *App) !void {
// If we're using an AdwWindow then we can support the tab overview. // If we're using an AdwWindow then we can support the tab overview.
if (self.tab_overview) |tab_overview| { if (self.tab_overview) |tab_overview| {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
const btn = switch (app.config.@"gtk-tabs-location") { const btn = switch (self.config.gtk_tabs_location) {
.top, .bottom => btn: { .top, .bottom => btn: {
const btn = c.gtk_toggle_button_new(); const btn = c.gtk_toggle_button_new();
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs"); c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
@ -188,7 +216,6 @@ pub fn init(self: *Window, app: *App) !void {
self.headerbar.packStart(btn); self.headerbar.packStart(btn);
} }
_ = c.g_signal_connect_data(self.window, "notify::decorated", c.G_CALLBACK(&gtkWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(&gtkWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(&gtkWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(&gtkWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(&gtkWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
@ -237,12 +264,6 @@ pub fn init(self: *Window, app: *App) !void {
c.gtk_popover_set_has_arrow(@ptrCast(@alignCast(self.context_menu)), 0); c.gtk_popover_set_has_arrow(@ptrCast(@alignCast(self.context_menu)), 0);
c.gtk_widget_set_halign(self.context_menu, c.GTK_ALIGN_START); c.gtk_widget_set_halign(self.context_menu, c.GTK_ALIGN_START);
// If we want the window to be maximized, we do that here.
if (app.config.maximize) c.gtk_window_maximize(self.window);
// If we are in fullscreen mode, new windows start fullscreen.
if (app.config.fullscreen) c.gtk_window_fullscreen(self.window);
// We register a key event controller with the window so // We register a key event controller with the window so
// we can catch key events when our surface may not be // we can catch key events when our surface may not be
// focused (i.e. when the libadw tab overview is shown). // focused (i.e. when the libadw tab overview is shown).
@ -265,14 +286,14 @@ pub fn init(self: *Window, app: *App) !void {
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget()); c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
if (self.app.config.@"gtk-tabs-location" != .hidden) { if (self.config.gtk_tabs_location != .hidden) {
const tab_bar = c.adw_tab_bar_new(); const tab_bar = c.adw_tab_bar_new();
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view))); c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0); if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar)); const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
switch (self.app.config.@"gtk-tabs-location") { switch (self.config.gtk_tabs_location) {
.top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget), .top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
.bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget), .bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget),
.hidden => unreachable, .hidden => unreachable,
@ -280,7 +301,7 @@ pub fn init(self: *Window, app: *App) !void {
} }
c.adw_toolbar_view_set_content(toolbar_view, box); c.adw_toolbar_view_set_content(toolbar_view, box);
const toolbar_style: c.AdwToolbarStyle = switch (self.app.config.@"gtk-toolbar-style") { const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) {
.flat => c.ADW_TOOLBAR_FLAT, .flat => c.ADW_TOOLBAR_FLAT,
.raised => c.ADW_TOOLBAR_RAISED, .raised => c.ADW_TOOLBAR_RAISED,
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER, .@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
@ -298,12 +319,12 @@ pub fn init(self: *Window, app: *App) !void {
@ptrCast(@alignCast(self.tab_overview)), @ptrCast(@alignCast(self.tab_overview)),
); );
} else tab_bar: { } else tab_bar: {
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar; if (self.config.gtk_tabs_location == .hidden) break :tab_bar;
// In earlier adwaita versions, we need to add the tabbar manually since we do not use // In earlier adwaita versions, we need to add the tabbar manually since we do not use
// an AdwToolbarView. // an AdwToolbarView.
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?; const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline"); c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
switch (app.config.@"gtk-tabs-location") { switch (self.config.gtk_tabs_location) {
.top => c.gtk_box_insert_child_after( .top => c.gtk_box_insert_child_after(
@ptrCast(box), @ptrCast(box),
@ptrCast(@alignCast(tab_bar)), @ptrCast(@alignCast(tab_bar)),
@ -317,9 +338,15 @@ pub fn init(self: *Window, app: *App) !void {
} }
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view))); c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0); 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);
// Show the window // Show the window
c.gtk_widget_show(gtk_widget); c.gtk_widget_show(gtk_widget);
} }
@ -328,22 +355,80 @@ pub fn updateConfig(
self: *Window, self: *Window,
config: *const configpkg.Config, config: *const configpkg.Config,
) !void { ) !void {
self.winproto.updateConfigEvent(config) catch |err| { self.config = DerivedConfig.init(config);
// We want to continue attempting to make the other config
// changes necessary so we just log the error and continue.
log.warn("failed to update window protocol config error={}", .{err});
};
// We always resync our appearance whenever the config changes. // We always resync our appearance whenever the config changes.
try self.syncAppearance(config); try self.syncAppearance();
} }
/// Updates appearance based on config settings. Will be called once upon window /// Updates appearance based on config settings. Will be called once upon window
/// realization, and every time the config is reloaded. /// realization, every time the config is reloaded, and every time a window state
/// is toggled (un-/maximized, un-/fullscreened, window decorations toggled, etc.)
/// ///
/// TODO: Many of the initial style settings in `create` could possibly be made /// TODO: Many of the initial style settings in `create` could possibly be made
/// reactive by moving them here. /// reactive by moving them here.
pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void { pub fn syncAppearance(self: *Window) !void {
const csd_enabled = self.winproto.clientSideDecorationEnabled();
c.gtk_window_set_decorated(self.window, @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);
self.headerbar.setVisible(visible: {
// Never display the header bar when CSDs are disabled.
if (!csd_enabled) break :visible false;
// Unconditionally disable the header bar when fullscreened.
if (self.config.fullscreen) break :visible false;
// *Conditionally* disable the header bar when maximized,
// and gtk-titlebar-hide-when-maximized is set
if (self.config.maximize and self.config.gtk_titlebar_hide_when_maximized)
break :visible false;
break :visible self.config.gtk_titlebar;
});
toggleCssClass(
@ptrCast(self.window),
"background",
self.config.background_opacity >= 1,
);
// Apply class to color headerbar if window-theme is set to `ghostty` and
// GTK version is before 4.16. The conditional is because above 4.16
// we use GTK CSS color variables.
toggleCssClass(
@ptrCast(self.window),
"window-theme-ghostty",
!version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty,
);
if (self.tab_overview) |tab_overview| {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
// Disable the title buttons (close, maximize, minimize, ...)
// *inside* the tab overview if CSDs are disabled.
// We do spare the search button, though.
c.adw_tab_overview_set_show_start_title_buttons(@ptrCast(tab_overview), @intFromBool(csd_enabled));
c.adw_tab_overview_set_show_end_title_buttons(@ptrCast(tab_overview), @intFromBool(csd_enabled));
// Update toolbar view style
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_tab_overview_get_child(@ptrCast(tab_overview)));
const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) {
.flat => c.ADW_TOOLBAR_FLAT,
.raised => c.ADW_TOOLBAR_RAISED,
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
};
c.adw_toolbar_view_set_top_bar_style(toolbar_view, toolbar_style);
c.adw_toolbar_view_set_bottom_bar_style(toolbar_view, toolbar_style);
}
self.winproto.syncAppearance() catch |err| { self.winproto.syncAppearance() catch |err| {
log.warn("failed to sync winproto appearance error={}", .{err}); log.warn("failed to sync winproto appearance error={}", .{err});
}; };
@ -351,30 +436,8 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
toggleCssClass( toggleCssClass(
@ptrCast(self.window), @ptrCast(self.window),
"background", "background",
config.@"background-opacity" >= 1, self.config.background_opacity >= 1,
); );
// If we are disabling CSDs then disable them right away.
const csd_enabled = self.winproto.clientSideDecorationEnabled();
c.gtk_window_set_decorated(self.window, @intFromBool(csd_enabled));
// If we are not decorated then we hide the titlebar.
self.headerbar.setVisible(config.@"gtk-titlebar" and csd_enabled);
// Disable the title buttons (close, maximize, minimize, ...)
// *inside* the tab overview if CSDs are disabled.
// We do spare the search button, though.
if (self.tab_overview) |tab_overview| {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
c.adw_tab_overview_set_show_start_title_buttons(
@ptrCast(tab_overview),
@intFromBool(csd_enabled),
);
c.adw_tab_overview_set_show_end_title_buttons(
@ptrCast(tab_overview),
@intFromBool(csd_enabled),
);
}
} }
fn toggleCssClass( fn toggleCssClass(
@ -518,30 +581,42 @@ pub fn toggleTabOverview(self: *Window) void {
/// Toggle the maximized state for this window. /// Toggle the maximized state for this window.
pub fn toggleMaximize(self: *Window) void { pub fn toggleMaximize(self: *Window) void {
if (c.gtk_window_is_maximized(self.window) == 0) { if (self.config.maximize) {
c.gtk_window_maximize(self.window);
} else {
c.gtk_window_unmaximize(self.window); c.gtk_window_unmaximize(self.window);
} else {
c.gtk_window_maximize(self.window);
} }
// We update the config and call syncAppearance
// in the gtkWindowNotifyMaximized callback
} }
/// Toggle fullscreen for this window. /// Toggle fullscreen for this window.
pub fn toggleFullscreen(self: *Window) void { pub fn toggleFullscreen(self: *Window) void {
const is_fullscreen = c.gtk_window_is_fullscreen(self.window); if (self.config.fullscreen) {
if (is_fullscreen == 0) {
c.gtk_window_fullscreen(self.window);
} else {
c.gtk_window_unfullscreen(self.window); c.gtk_window_unfullscreen(self.window);
} else {
c.gtk_window_fullscreen(self.window);
} }
// We update the config and call syncAppearance
// in the gtkWindowNotifyFullscreened callback
} }
/// Toggle the window decorations for this window. /// Toggle the window decorations for this window.
pub fn toggleWindowDecorations(self: *Window) void { pub fn toggleWindowDecorations(self: *Window) void {
self.app.config.@"window-decoration" = switch (self.app.config.@"window-decoration") { self.config.window_decoration = switch (self.config.window_decoration) {
.none => switch (self.app.config.@"window-decoration") {
// If we started as none, then we switch to auto
.none => .auto,
// Switch back
.auto, .client, .server => |v| v,
},
// Always set to none
.auto, .client, .server => .none, .auto, .client, .server => .none,
.none => .client,
}; };
self.updateConfig(&self.app.config) catch {};
self.syncAppearance() catch |err| {
log.err("failed to sync appearance={}", .{err});
};
} }
/// Grabs focus on the currently selected tab. /// Grabs focus on the currently selected tab.
@ -562,23 +637,22 @@ pub fn sendToast(self: *Window, title: [:0]const u8) void {
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast); c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
} }
fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { fn gtkRealize(_: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
const self = userdataSelf(ud.?); const self = userdataSelf(ud.?);
// Initialize our window protocol logic // Initialize our window protocol logic
if (winproto.Window.init( if (winproto.Window.init(
self.app.core_app.alloc, self.app.core_app.alloc,
&self.app.winproto, &self.app.winproto,
v, self,
&self.app.config, )) |wp| {
)) |winproto_win| { self.winproto = wp;
self.winproto = winproto_win;
} else |err| { } else |err| {
log.warn("failed to initialize window protocol error={}", .{err}); log.warn("failed to initialize window protocol error={}", .{err});
} }
// When we are realized we always setup our appearance // When we are realized we always setup our appearance
self.syncAppearance(&self.app.config) catch |err| { self.syncAppearance() catch |err| {
log.err("failed to initialize appearance={}", .{err}); log.err("failed to initialize appearance={}", .{err});
}; };
@ -591,61 +665,22 @@ fn gtkWindowNotifyMaximized(
ud: ?*anyopaque, ud: ?*anyopaque,
) callconv(.C) void { ) callconv(.C) void {
const self = userdataSelf(ud orelse return); const self = userdataSelf(ud orelse return);
self.config.maximize = c.gtk_window_is_maximized(self.window) != 0;
// Only toggle visibility of the header bar when we're using CSDs, self.syncAppearance() catch |err| {
// and actually intend on displaying the header bar log.err("failed to sync appearance={}", .{err});
if (!self.winproto.clientSideDecorationEnabled()) return; };
// If we aren't maximized, we should show the headerbar again
// if it was originally visible.
const maximized = c.gtk_window_is_maximized(self.window) != 0;
if (!maximized) {
self.headerbar.setVisible(self.app.config.@"gtk-titlebar");
return;
}
// If we are maximized, we should hide the headerbar if requested.
if (self.app.config.@"gtk-titlebar-hide-when-maximized") {
self.headerbar.setVisible(false);
}
}
fn gtkWindowNotifyDecorated(
object: *c.GObject,
_: *c.GParamSpec,
ud: ?*anyopaque,
) callconv(.C) void {
const self = userdataSelf(ud orelse return);
const is_decorated = c.gtk_window_get_decorated(@ptrCast(object)) == 1;
// 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(object), "csd", is_decorated);
toggleCssClass(@ptrCast(object), "ssd", !is_decorated);
toggleCssClass(@ptrCast(object), "no-border-radius", !is_decorated);
// FIXME: This is to update the blur region offset on X11.
// Remove this when we move everything related to window appearance
// to `syncAppearance` for Ghostty 1.2.
self.winproto.syncAppearance() catch {};
} }
fn gtkWindowNotifyFullscreened( fn gtkWindowNotifyFullscreened(
object: *c.GObject, _: *c.GObject,
_: *c.GParamSpec, _: *c.GParamSpec,
ud: ?*anyopaque, ud: ?*anyopaque,
) callconv(.C) void { ) callconv(.C) void {
const self = userdataSelf(ud orelse return); const self = userdataSelf(ud orelse return);
const fullscreened = c.gtk_window_is_fullscreen(@ptrCast(object)) != 0; self.config.fullscreen = c.gtk_window_is_fullscreen(self.window) != 0;
if (!fullscreened) { self.syncAppearance() catch |err| {
const csd_enabled = self.winproto.clientSideDecorationEnabled(); log.err("failed to sync appearance={}", .{err});
self.headerbar.setVisible(self.app.config.@"gtk-titlebar" and csd_enabled); };
return;
}
self.headerbar.setVisible(false);
} }
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab // Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab

View File

@ -5,6 +5,7 @@ const c = @import("c.zig").c;
const Config = @import("../../config.zig").Config; const Config = @import("../../config.zig").Config;
const input = @import("../../input.zig"); const input = @import("../../input.zig");
const key = @import("key.zig"); const key = @import("key.zig");
const ApprtWindow = @import("Window.zig");
pub const noop = @import("winproto/noop.zig"); pub const noop = @import("winproto/noop.zig");
pub const x11 = @import("winproto/x11.zig"); pub const x11 = @import("winproto/x11.zig");
@ -74,8 +75,7 @@ pub const Window = union(Protocol) {
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
app: *App, app: *App,
window: *c.GtkWindow, apprt_window: *ApprtWindow,
config: *const Config,
) !Window { ) !Window {
return switch (app.*) { return switch (app.*) {
inline else => |*v, tag| { inline else => |*v, tag| {
@ -90,8 +90,7 @@ pub const Window = union(Protocol) {
try field.type.init( try field.type.init(
alloc, alloc,
v, v,
window, apprt_window,
config,
), ),
); );
} }
@ -111,15 +110,6 @@ pub const Window = union(Protocol) {
} }
} }
pub fn updateConfigEvent(
self: *Window,
config: *const Config,
) !void {
switch (self.*) {
inline else => |*v| try v.updateConfigEvent(config),
}
}
pub fn syncAppearance(self: *Window) !void { pub fn syncAppearance(self: *Window) !void {
switch (self.*) { switch (self.*) {
inline else => |*v| try v.syncAppearance(), inline else => |*v| try v.syncAppearance(),

View File

@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const c = @import("../c.zig").c; const c = @import("../c.zig").c;
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = @import("../Window.zig");
const log = std.log.scoped(.winproto_noop); const log = std.log.scoped(.winproto_noop);
@ -34,8 +35,7 @@ pub const Window = struct {
pub fn init( pub fn init(
_: Allocator, _: Allocator,
_: *App, _: *App,
_: *c.GtkWindow, _: *ApprtWindow,
_: *const Config,
) !Window { ) !Window {
return .{}; return .{};
} }
@ -47,7 +47,7 @@ pub const Window = struct {
pub fn updateConfigEvent( pub fn updateConfigEvent(
_: *Window, _: *Window,
_: *const Config, _: *const ApprtWindow.DerivedConfig,
) !void {} ) !void {}
pub fn resizeEvent(_: *Window) !void {} pub fn resizeEvent(_: *Window) !void {}

View File

@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
const c = @import("../c.zig").c; const c = @import("../c.zig").c;
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = @import("../Window.zig");
const wl = wayland.client.wl; const wl = wayland.client.wl;
const org = wayland.client.org; const org = wayland.client.org;
@ -155,7 +156,7 @@ pub const App = struct {
/// Per-window (wl_surface) state for the Wayland protocol. /// Per-window (wl_surface) state for the Wayland protocol.
pub const Window = struct { pub const Window = struct {
config: DerivedConfig, config: *const ApprtWindow.DerivedConfig,
/// The Wayland surface for this window. /// The Wayland surface for this window.
surface: *wl.Surface, surface: *wl.Surface,
@ -170,28 +171,15 @@ pub const Window = struct {
/// of the window. /// of the window.
decoration: ?*org.KdeKwinServerDecoration, decoration: ?*org.KdeKwinServerDecoration,
const DerivedConfig = struct {
blur: bool,
window_decoration: Config.WindowDecoration,
pub fn init(config: *const Config) DerivedConfig {
return .{
.blur = config.@"background-blur".enabled(),
.window_decoration = config.@"window-decoration",
};
}
};
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
app: *App, app: *App,
gtk_window: *c.GtkWindow, apprt_window: *ApprtWindow,
config: *const Config,
) !Window { ) !Window {
_ = alloc; _ = alloc;
const gdk_surface = c.gtk_native_get_surface( const gdk_surface = c.gtk_native_get_surface(
@ptrCast(gtk_window), @ptrCast(apprt_window.window),
) orelse return error.NotWaylandSurface; ) orelse return error.NotWaylandSurface;
// This should never fail, because if we're being called at this point // This should never fail, because if we're being called at this point
@ -222,7 +210,7 @@ pub const Window = struct {
}; };
return .{ return .{
.config = DerivedConfig.init(config), .config = &apprt_window.config,
.surface = wl_surface, .surface = wl_surface,
.app_context = app.context, .app_context = app.context,
.blur_token = null, .blur_token = null,
@ -236,18 +224,15 @@ pub const Window = struct {
if (self.decoration) |deco| deco.release(); if (self.decoration) |deco| deco.release();
} }
pub fn updateConfigEvent(
self: *Window,
config: *const Config,
) !void {
self.config = DerivedConfig.init(config);
}
pub fn resizeEvent(_: *Window) !void {} pub fn resizeEvent(_: *Window) !void {}
pub fn syncAppearance(self: *Window) !void { pub fn syncAppearance(self: *Window) !void {
try self.syncBlur(); self.syncBlur() catch |err| {
try self.syncDecoration(); log.err("failed to sync blur={}", .{err});
};
self.syncDecoration() catch |err| {
log.err("failed to sync blur={}", .{err});
};
} }
pub fn clientSideDecorationEnabled(self: Window) bool { pub fn clientSideDecorationEnabled(self: Window) bool {
@ -270,18 +255,18 @@ pub const Window = struct {
/// Update the blur state of the window. /// Update the blur state of the window.
fn syncBlur(self: *Window) !void { fn syncBlur(self: *Window) !void {
const manager = self.app_context.kde_blur_manager orelse return; const manager = self.app_context.kde_blur_manager orelse return;
const blur = self.config.blur; const blur = self.config.background_blur;
if (self.blur_token) |tok| { if (self.blur_token) |tok| {
// Only release token when transitioning from blurred -> not blurred // Only release token when transitioning from blurred -> not blurred
if (!blur) { if (!blur.enabled()) {
manager.unset(self.surface); manager.unset(self.surface);
tok.release(); tok.release();
self.blur_token = null; self.blur_token = null;
} }
} else { } else {
// Only acquire token when transitioning from not blurred -> blurred // Only acquire token when transitioning from not blurred -> blurred
if (blur) { if (blur.enabled()) {
const tok = try manager.create(self.surface); const tok = try manager.create(self.surface);
tok.commit(); tok.commit();
self.blur_token = tok; self.blur_token = tok;

View File

@ -7,6 +7,7 @@ const c = @import("../c.zig").c;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const adwaita = @import("../adwaita.zig"); const adwaita = @import("../adwaita.zig");
const ApprtWindow = @import("../Window.zig");
const log = std.log.scoped(.gtk_x11); const log = std.log.scoped(.gtk_x11);
@ -151,33 +152,21 @@ pub const App = struct {
pub const Window = struct { pub const Window = struct {
app: *App, app: *App,
alloc: Allocator, config: *const ApprtWindow.DerivedConfig,
config: DerivedConfig,
window: c.Window, window: c.Window,
gtk_window: *c.GtkWindow, gtk_window: *c.GtkWindow,
blur_region: Region = .{}, blur_region: Region = .{},
const DerivedConfig = struct {
blur: bool,
window_decoration: Config.WindowDecoration,
pub fn init(config: *const Config) DerivedConfig {
return .{
.blur = config.@"background-blur".enabled(),
.window_decoration = config.@"window-decoration",
};
}
};
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
app: *App, app: *App,
gtk_window: *c.GtkWindow, apprt_window: *ApprtWindow,
config: *const Config,
) !Window { ) !Window {
_ = alloc;
const surface = c.gtk_native_get_surface( const surface = c.gtk_native_get_surface(
@ptrCast(gtk_window), @ptrCast(apprt_window.window),
) orelse return error.NotX11Surface; ) orelse return error.NotX11Surface;
// Check if we're actually on X11 // Check if we're actually on X11
@ -188,10 +177,9 @@ pub const Window = struct {
return .{ return .{
.app = app, .app = app,
.alloc = alloc, .config = &apprt_window.config,
.config = DerivedConfig.init(config),
.window = c.gdk_x11_surface_get_xid(surface), .window = c.gdk_x11_surface_get_xid(surface),
.gtk_window = gtk_window, .gtk_window = apprt_window.window,
}; };
} }
@ -200,13 +188,6 @@ pub const Window = struct {
_ = alloc; _ = alloc;
} }
pub fn updateConfigEvent(
self: *Window,
config: *const Config,
) !void {
self.config = DerivedConfig.init(config);
}
pub fn resizeEvent(self: *Window) !void { pub fn resizeEvent(self: *Window) !void {
// The blur region must update with window resizes // The blur region must update with window resizes
self.blur_region.width = c.gtk_widget_get_width(@ptrCast(self.gtk_window)); self.blur_region.width = c.gtk_widget_get_width(@ptrCast(self.gtk_window));
@ -257,14 +238,14 @@ pub const Window = struct {
// and I think it's not really noticeable enough to justify the effort. // and I think it's not really noticeable enough to justify the effort.
// (Wayland also has this visual artifact anyway...) // (Wayland also has this visual artifact anyway...)
const blur = self.config.blur; const blur = self.config.background_blur;
log.debug("set blur={}, window xid={}, region={}", .{ log.debug("set blur={}, window xid={}, region={}", .{
blur, blur,
self.window, self.window,
self.blur_region, self.blur_region,
}); });
if (blur) { if (blur.enabled()) {
try self.changeProperty( try self.changeProperty(
Region, Region,
self.app.atoms.kde_blur, self.app.atoms.kde_blur,