mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-03 20:58:36 +03:00
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:
@ -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(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), 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
|
||||||
|
@ -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(),
|
||||||
|
@ -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 {}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user