apprt/gtk-ng: winproto behaviors (quick terminal, csd/ssd, blur, etc.) (#8123)

This ports over the winproto behaviors to gtk-ng. The core winproto
logic is unchanged except for trivial typing changes. The interaction
with winproto is a bit different in ng due to the class separation of
logic between surfaces and windows, but functionally the same.

Ran against Valgrind and all looks good.
pull/8128/head
Mitchell Hashimoto 2025-08-02 12:57:01 -07:00 committed by GitHub
commit 7836cc8f31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 346 additions and 42 deletions

View File

@ -549,6 +549,7 @@ pub const Application = extern struct {
.toggle_maximize => Action.toggleMaximize(target), .toggle_maximize => Action.toggleMaximize(target),
.toggle_fullscreen => Action.toggleFullscreen(target), .toggle_fullscreen => Action.toggleFullscreen(target),
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
.toggle_tab_overview => return Action.toggleTabOverview(target), .toggle_tab_overview => return Action.toggleTabOverview(target),
// Unimplemented but todo on gtk-ng branch // Unimplemented but todo on gtk-ng branch
@ -562,7 +563,6 @@ pub const Application = extern struct {
.goto_split, .goto_split,
.toggle_split_zoom, .toggle_split_zoom,
// TODO: winproto // TODO: winproto
.toggle_quick_terminal,
.toggle_window_decorations, .toggle_window_decorations,
=> { => {
log.warn("unimplemented action={}", .{action}); log.warn("unimplemented action={}", .{action});
@ -1477,7 +1477,14 @@ const Action = struct {
parent: ?*CoreSurface, parent: ?*CoreSurface,
) !void { ) !void {
const win = Window.new(self); const win = Window.new(self);
initAndShowWindow(self, win, parent);
}
fn initAndShowWindow(
self: *Application,
win: *Window,
parent: ?*CoreSurface,
) void {
// Setup a binding so that whenever our config changes so does the // Setup a binding so that whenever our config changes so does the
// window. There's never a time when the window config should be out // window. There's never a time when the window config should be out
// of sync with the application config. // of sync with the application config.
@ -1694,6 +1701,48 @@ const Action = struct {
} }
} }
pub fn toggleQuickTerminal(self: *Application) bool {
// If we already have a quick terminal window, we just toggle the
// visibility of it.
if (getQuickTerminalWindow()) |win| {
win.toggleVisibility();
return true;
}
// If we don't support quick terminals then we do nothing.
const priv = self.private();
if (!priv.winproto.supportsQuickTerminal()) return false;
// Create our new window as a quick terminal
const win = gobject.ext.newInstance(Window, .{
.application = self,
.@"quick-terminal" = true,
});
assert(win.isQuickTerminal());
initAndShowWindow(self, win, null);
return true;
}
fn getQuickTerminalWindow() ?*Window {
// Find a quick terminal window.
const list = gtk.Window.listToplevels();
defer list.free();
if (ext.listFind(gtk.Window, list, struct {
fn find(gtk_win: *gtk.Window) bool {
const win = gobject.ext.cast(
Window,
gtk_win,
) orelse return false;
return win.isQuickTerminal();
}
}.find)) |w| return gobject.ext.cast(
Window,
w,
).?;
return null;
}
pub fn toggleMaximize(target: apprt.Target) void { pub fn toggleMaximize(target: apprt.Target) void {
switch (target) { switch (target) {
.app => {}, .app => {},

View File

@ -26,6 +26,7 @@ const Config = @import("config.zig").Config;
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay; const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited; const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog; const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
const Window = @import("window.zig").Window;
const log = std.log.scoped(.gtk_ghostty_surface); const log = std.log.scoped(.gtk_ghostty_surface);
@ -1061,8 +1062,6 @@ pub const Surface = extern struct {
} }
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap { pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
_ = self;
const alloc = Application.default().allocator(); const alloc = Application.default().allocator();
var env = try internal_os.getEnvMap(alloc); var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit(); errdefer env.deinit();
@ -1099,6 +1098,14 @@ pub const Surface = extern struct {
env.remove("GTK_PATH"); env.remove("GTK_PATH");
} }
// This is a hack because it ties ourselves (optionally) to the
// Window class. The right solution we should do is emit a signal
// here where the handler can modify our EnvMap, but boxing the
// EnvMap is a bit annoying so I'm punting it.
if (ext.getAncestor(Window, self.as(gtk.Widget))) |window| {
try window.winproto().addSubprocessEnv(&env);
}
return env; return env;
} }

View File

@ -2,6 +2,7 @@ const std = @import("std");
const build_config = @import("../../../build_config.zig"); const build_config = @import("../../../build_config.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const adw = @import("adw"); const adw = @import("adw");
const gdk = @import("gdk");
const gio = @import("gio"); const gio = @import("gio");
const glib = @import("glib"); const glib = @import("glib");
const gobject = @import("gobject"); const gobject = @import("gobject");
@ -15,6 +16,7 @@ const ext = @import("../ext.zig");
const gtk_version = @import("../gtk_version.zig"); const gtk_version = @import("../gtk_version.zig");
const adw_version = @import("../adw_version.zig"); const adw_version = @import("../adw_version.zig");
const gresource = @import("../build/gresource.zig"); const gresource = @import("../build/gresource.zig");
const winprotopkg = @import("../winproto.zig");
const Common = @import("../class.zig").Common; const Common = @import("../class.zig").Common;
const Config = @import("config.zig").Config; const Config = @import("config.zig").Config;
const Application = @import("application.zig").Application; const Application = @import("application.zig").Application;
@ -131,6 +133,26 @@ pub const Window = extern struct {
); );
}; };
pub const @"quick-terminal" = struct {
pub const name = "quick-terminal";
const impl = gobject.ext.defineProperty(
name,
Self,
bool,
.{
.nick = "Quick Terminal",
.blurb = "Whether this window behaves like a quick terminal.",
.default = true,
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"quick_terminal",
),
},
);
};
pub const @"tabs-autohide" = struct { pub const @"tabs-autohide" = struct {
pub const name = "tabs-autohide"; pub const name = "tabs-autohide";
const impl = gobject.ext.defineProperty( const impl = gobject.ext.defineProperty(
@ -205,12 +227,19 @@ pub const Window = extern struct {
}; };
const Private = struct { const Private = struct {
/// Whether this window is a quick terminal. If it is then it
/// behaves slightly differently under certain scenarios.
quick_terminal: bool = false,
/// Binding group for our active tab. /// Binding group for our active tab.
tab_bindings: *gobject.BindingGroup, tab_bindings: *gobject.BindingGroup,
/// The configuration that this surface is using. /// The configuration that this surface is using.
config: ?*Config = null, config: ?*Config = null,
/// State and logic for windowing protocol for a window.
winproto: winprotopkg.Window,
/// Kind of hacky to have this but this lets us know if we've /// Kind of hacky to have this but this lets us know if we've
/// initialized any single surface yet. We need this because we /// initialized any single surface yet. We need this because we
/// gate default size on this so that we don't resize the window /// gate default size on this so that we don't resize the window
@ -253,6 +282,10 @@ pub const Window = extern struct {
priv.config = app.getConfig(); priv.config = app.getConfig();
} }
// We initialize our windowing protocol to none because we can't
// actually initialize this until we get realized.
priv.winproto = .none;
// Add our dev CSS class if we're in debug mode. // Add our dev CSS class if we're in debug mode.
if (comptime build_config.is_debug) { if (comptime build_config.is_debug) {
self.as(gtk.Widget).addCssClass("devel"); self.as(gtk.Widget).addCssClass("devel");
@ -270,6 +303,24 @@ pub const Window = extern struct {
// Initialize our actions // Initialize our actions
self.initActionMap(); self.initActionMap();
// We need to setup resize notifications on our surface
if (self.as(gtk.Native).getSurface()) |gdk_surface| {
_ = gobject.Object.signals.notify.connect(
gdk_surface,
*Self,
propGdkSurfaceWidth,
self,
.{ .detail = "width" },
);
_ = gobject.Object.signals.notify.connect(
gdk_surface,
*Self,
propGdkSurfaceHeight,
self,
.{ .detail = "height" },
);
}
// We always sync our appearance at the end because loading our // We always sync our appearance at the end because loading our
// config and such can affect our bindings which ar setup initially // config and such can affect our bindings which ar setup initially
// in initTemplate. // in initTemplate.
@ -312,6 +363,11 @@ pub const Window = extern struct {
} }
} }
/// Winproto backend for this window.
pub fn winproto(self: *Self) *winprotopkg.Window {
return &self.private().winproto;
}
/// Create a new tab with the given parent. The tab will be inserted /// Create a new tab with the given parent. The tab will be inserted
/// at the position dictated by the `window-new-tab-position` config. /// at the position dictated by the `window-new-tab-position` config.
/// The new tab will be selected. /// The new tab will be selected.
@ -466,12 +522,28 @@ pub const Window = extern struct {
tab_overview.setOpen(@intFromBool(!is_open)); tab_overview.setOpen(@intFromBool(!is_open));
} }
/// Toggle the visible property.
pub fn toggleVisibility(self: *Self) void {
const widget = self.as(gtk.Widget);
widget.setVisible(@intFromBool(widget.isVisible() == 0));
}
/// Updates various appearance properties. This should always be safe /// Updates various appearance properties. This should always be safe
/// to call multiple times. This should be called whenever a change /// to call multiple times. This should be called whenever a change
/// happens that might affect how the window appears (config change, /// happens that might affect how the window appears (config change,
/// fullscreen, etc.). /// fullscreen, etc.).
fn syncAppearance(self: *Self) void { fn syncAppearance(self: *Self) void {
// TODO: CSD/SSD const priv = self.private();
const csd_enabled = priv.winproto.clientSideDecorationEnabled();
self.as(gtk.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.
self.toggleCssClass("csd", csd_enabled);
self.toggleCssClass("ssd", !csd_enabled);
self.toggleCssClass("no-border-radius", !csd_enabled);
// Trigger all our dynamic properties that depend on the config. // Trigger all our dynamic properties that depend on the config.
inline for (&.{ inline for (&.{
@ -488,15 +560,28 @@ pub const Window = extern struct {
} }
// Remainder uses the config // Remainder uses the config
const priv = self.private();
const config = if (priv.config) |v| v.get() else return; const config = if (priv.config) |v| v.get() else return;
// 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.
self.toggleCssClass(
"window-theme-ghostty",
!gtk_version.atLeast(4, 16, 0) and
config.@"window-theme" == .ghostty,
);
// Move the tab bar to the proper location. // Move the tab bar to the proper location.
priv.toolbar.remove(priv.tab_bar.as(gtk.Widget)); priv.toolbar.remove(priv.tab_bar.as(gtk.Widget));
switch (config.@"gtk-tabs-location") { switch (config.@"gtk-tabs-location") {
.top => priv.toolbar.addTopBar(priv.tab_bar.as(gtk.Widget)), .top => priv.toolbar.addTopBar(priv.tab_bar.as(gtk.Widget)),
.bottom => priv.toolbar.addBottomBar(priv.tab_bar.as(gtk.Widget)), .bottom => priv.toolbar.addBottomBar(priv.tab_bar.as(gtk.Widget)),
} }
// Do our window-protocol specific appearance sync.
priv.winproto.syncAppearance() catch |err| {
log.warn("failed to sync winproto appearance error={}", .{err});
};
} }
fn toggleCssClass(self: *Self, class: [:0]const u8, value: bool) void { fn toggleCssClass(self: *Self, class: [:0]const u8, value: bool) void {
@ -535,6 +620,11 @@ pub const Window = extern struct {
//--------------------------------------------------------------- //---------------------------------------------------------------
// Properties // Properties
/// Whether this terminal is a quick terminal or not.
pub fn isQuickTerminal(self: *Self) bool {
return self.private().quick_terminal;
}
/// Get the currently active surface. See the "active-surface" property. /// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value. /// This does not ref the value.
fn getActiveSurface(self: *Self) ?*Surface { fn getActiveSurface(self: *Self) ?*Surface {
@ -542,6 +632,12 @@ pub const Window = extern struct {
return tab.getActiveSurface(); return tab.getActiveSurface();
} }
/// Returns the configuration for this window. The reference count
/// is not increased.
pub fn getConfig(self: *Self) ?*Config {
return self.private().config;
}
/// Get the currently selected tab as a Tab object. /// Get the currently selected tab as a Tab object.
fn getSelectedTab(self: *Self) ?*Tab { fn getSelectedTab(self: *Self) ?*Tab {
const priv = self.private(); const priv = self.private();
@ -571,8 +667,14 @@ pub const Window = extern struct {
} }
fn getHeaderbarVisible(self: *Self) bool { fn getHeaderbarVisible(self: *Self) bool {
// TODO: CSD/SSD const priv = self.private();
// TODO: QuickTerminal
// Never display the header bar when CSDs are disabled.
const csd_enabled = priv.winproto.clientSideDecorationEnabled();
if (!csd_enabled) return false;
// Never display the header bar as a quick terminal.
if (priv.quick_terminal) return false;
// If we're fullscreen we never show the header bar. // If we're fullscreen we never show the header bar.
if (self.as(gtk.Window).isFullscreen() != 0) return false; if (self.as(gtk.Window).isFullscreen() != 0) return false;
@ -648,6 +750,36 @@ pub const Window = extern struct {
self.syncAppearance(); self.syncAppearance();
} }
fn propGdkSurfaceHeight(
_: *gdk.Surface,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
// X11 needs to fix blurring on resize, but winproto implementations
// could do anything.
self.private().winproto.resizeEvent() catch |err| {
log.warn(
"winproto resize event failed error={}",
.{err},
);
};
}
fn propGdkSurfaceWidth(
_: *gdk.Surface,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
// X11 needs to fix blurring on resize, but winproto implementations
// could do anything.
self.private().winproto.resizeEvent() catch |err| {
log.warn(
"winproto resize event failed error={}",
.{err},
);
};
}
fn propFullscreened( fn propFullscreened(
_: *adw.ApplicationWindow, _: *adw.ApplicationWindow,
_: *gobject.ParamSpec, _: *gobject.ParamSpec,
@ -696,6 +828,26 @@ pub const Window = extern struct {
action.setEnabled(@intFromBool(has_selection)); action.setEnabled(@intFromBool(has_selection));
} }
fn propQuickTerminal(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
const priv = self.private();
if (priv.surface_init) {
log.warn("quick terminal property can't be changed after surfaces have been initialized", .{});
return;
}
if (priv.quick_terminal) {
// Initialize the quick terminal at the app-layer
Application.default().winproto().initQuickTerminal(self) catch |err| {
log.warn("failed to initialize quick terminal error={}", .{err});
return;
};
}
}
/// Add or remove "background" CSS class depending on if the background /// Add or remove "background" CSS class depending on if the background
/// should be opaque. /// should be opaque.
fn propBackgroundOpaque( fn propBackgroundOpaque(
@ -706,6 +858,24 @@ pub const Window = extern struct {
self.toggleCssClass("background", self.getBackgroundOpaque()); self.toggleCssClass("background", self.getBackgroundOpaque());
} }
fn propScaleFactor(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
// On some platforms (namely X11) we need to refresh our appearance when
// the scale factor changes. In theory this could be more fine-grained as
// a full refresh could be expensive, but a) this *should* be rare, and
// b) quite noticeable visual bugs would occur if this is not present.
self.private().winproto.syncAppearance() catch |err| {
log.warn(
"failed to sync appearance after scale factor has been updated={}",
.{err},
);
return;
};
}
//--------------------------------------------------------------- //---------------------------------------------------------------
// Virtual methods // Virtual methods
@ -731,6 +901,7 @@ pub const Window = extern struct {
fn finalize(self: *Self) callconv(.C) void { fn finalize(self: *Self) callconv(.C) void {
const priv = self.private(); const priv = self.private();
priv.tab_bindings.unref(); priv.tab_bindings.unref();
priv.winproto.deinit(Application.default().allocator());
gobject.Object.virtual_methods.finalize.call( gobject.Object.virtual_methods.finalize.call(
Class.parent, Class.parent,
@ -741,6 +912,26 @@ pub const Window = extern struct {
//--------------------------------------------------------------- //---------------------------------------------------------------
// Signal handlers // Signal handlers
fn windowRealize(_: *gtk.Widget, self: *Window) callconv(.c) void {
const app = Application.default();
// Initialize our window protocol logic
if (winprotopkg.Window.init(
app.allocator(),
app.winproto(),
self,
)) |wp| {
self.private().winproto = wp;
} else |err| {
log.warn("failed to initialize window protocol error={}", .{err});
return;
}
// When we are realized we always setup our appearance since this
// calls some winproto functions.
self.syncAppearance();
}
fn btnNewTab(_: *adw.SplitButton, self: *Self) callconv(.c) void { fn btnNewTab(_: *adw.SplitButton, self: *Self) callconv(.c) void {
self.performBindingAction(.new_tab); self.performBindingAction(.new_tab);
} }
@ -1362,6 +1553,7 @@ pub const Window = extern struct {
properties.config.impl, properties.config.impl,
properties.debug.impl, properties.debug.impl,
properties.@"headerbar-visible".impl, properties.@"headerbar-visible".impl,
properties.@"quick-terminal".impl,
properties.@"tabs-autohide".impl, properties.@"tabs-autohide".impl,
properties.@"tabs-visible".impl, properties.@"tabs-visible".impl,
properties.@"tabs-wide".impl, properties.@"tabs-wide".impl,
@ -1376,6 +1568,7 @@ pub const Window = extern struct {
class.bindTemplateChildPrivate("toast_overlay", .{}); class.bindTemplateChildPrivate("toast_overlay", .{});
// Template Callbacks // Template Callbacks
class.bindTemplateCallback("realize", &windowRealize);
class.bindTemplateCallback("new_tab", &btnNewTab); class.bindTemplateCallback("new_tab", &btnNewTab);
class.bindTemplateCallback("overview_create_tab", &tabOverviewCreateTab); class.bindTemplateCallback("overview_create_tab", &tabOverviewCreateTab);
class.bindTemplateCallback("overview_notify_open", &tabOverviewOpen); class.bindTemplateCallback("overview_notify_open", &tabOverviewOpen);
@ -1386,11 +1579,13 @@ pub const Window = extern struct {
class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow); class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow);
class.bindTemplateCallback("notify_n_pages", &tabViewNPages); class.bindTemplateCallback("notify_n_pages", &tabViewNPages);
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage); class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
class.bindTemplateCallback("notify_config", &propConfig); class.bindTemplateCallback("notify_config", &propConfig);
class.bindTemplateCallback("notify_fullscreened", &propFullscreened); class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
class.bindTemplateCallback("notify_maximized", &propMaximized); class.bindTemplateCallback("notify_maximized", &propMaximized);
class.bindTemplateCallback("notify_menu_active", &propMenuActive); class.bindTemplateCallback("notify_menu_active", &propMenuActive);
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque); class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
// Virtual methods // Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose); gobject.Object.virtual_methods.dispose.implement(class, &dispose);

View File

@ -4,6 +4,14 @@
* https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles * https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles
*/ */
window.ssd.no-border-radius {
/* Without clearing the border radius, at least on Mutter with
* gtk-titlebar=true and gtk-adwaita=false, there is some window artifacting
* that this will mitigate.
*/
border-radius: 0 0;
}
/* /*
* GhosttySurface URL overlay * GhosttySurface URL overlay
*/ */

View File

@ -6,6 +6,7 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const glib = @import("glib");
const gobject = @import("gobject"); const gobject = @import("gobject");
const gtk = @import("gtk"); const gtk = @import("gtk");
@ -23,6 +24,24 @@ pub fn boxedFree(comptime T: type, ptr: ?*T) void {
); );
} }
/// A wrapper around `glib.List.findCustom` to find an element in the list.
/// The type `T` must be the guaranteed type of every list element.
pub fn listFind(
comptime T: type,
list: *glib.List,
comptime func: *const fn (*T) bool,
) ?*T {
const elem_: ?*glib.List = list.findCustom(null, struct {
fn callback(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int {
const ptr = data orelse return 1;
const v: *T = @ptrCast(@alignCast(@constCast(ptr)));
return if (func(v)) 0 else 1;
}
}.callback);
const elem = elem_ orelse return null;
return @ptrCast(@alignCast(elem.f_data));
}
/// Wrapper around `gtk.Widget.getAncestor` to get the widget ancestor /// Wrapper around `gtk.Widget.getAncestor` to get the widget ancestor
/// of the given type `T`, or null if it doesn't exist. /// of the given type `T`, or null if it doesn't exist.
pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T { pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T {

View File

@ -7,10 +7,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
] ]
close-request => $close_request(); close-request => $close_request();
realize => $realize();
notify::background-opaque => $notify_background_opaque();
notify::config => $notify_config(); notify::config => $notify_config();
notify::fullscreened => $notify_fullscreened(); notify::fullscreened => $notify_fullscreened();
notify::maximized => $notify_maximized(); notify::maximized => $notify_maximized();
notify::background-opaque => $notify_background_opaque(); notify::quick-terminal => $notify_quick_terminal();
notify::scale-factor => $notify_scale_factor();
default-width: 800; default-width: 800;
default-height: 600; default-height: 600;
// GTK4 grabs F10 input by default to focus the menubar icon. We want // GTK4 grabs F10 input by default to focus the menubar icon. We want
@ -20,8 +23,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
content: Adw.TabOverview tab_overview { content: Adw.TabOverview tab_overview {
create-tab => $overview_create_tab(); create-tab => $overview_create_tab();
notify::open => $overview_notify_open(); notify::open => $overview_notify_open();
enable-new-tab: true;
view: tab_view; view: tab_view;
enable-new-tab: true;
// Disable the title buttons (close, maximize, minimize, ...)
// *inside* the tab overview if CSDs are disabled.
// We do spare the search button, though.
show-start-title-buttons: bind template.decorated;
show-end-title-buttons: bind template.decorated;
Adw.ToolbarView toolbar { Adw.ToolbarView toolbar {
top-bar-style: bind template.toolbar-style; top-bar-style: bind template.toolbar-style;

View File

@ -7,9 +7,7 @@ const gdk = @import("gdk");
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("class/window.zig").Window;
// TODO: As we get to these APIs the compiler should tell us
const ApprtWindow = void;
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");

View File

@ -5,7 +5,7 @@ const gdk = @import("gdk");
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = void; // TODO: fix const ApprtWindow = @import("../class/window.zig").Window;
const log = std.log.scoped(.winproto_noop); const log = std.log.scoped(.winproto_noop);

View File

@ -12,7 +12,7 @@ const wayland = @import("wayland");
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = void; // TODO: fix const ApprtWindow = @import("../class/window.zig").Window;
const wl = wayland.client.wl; const wl = wayland.client.wl;
const org = wayland.client.org; const org = wayland.client.org;
@ -127,7 +127,7 @@ pub const App = struct {
} }
pub fn initQuickTerminal(_: *App, apprt_window: *ApprtWindow) !void { pub fn initQuickTerminal(_: *App, apprt_window: *ApprtWindow) !void {
const window = apprt_window.window.as(gtk.Window); const window = apprt_window.as(gtk.Window);
layer_shell.initForWindow(window); layer_shell.initForWindow(window);
layer_shell.setLayer(window, .top); layer_shell.setLayer(window, .top);
@ -257,7 +257,7 @@ pub const Window = struct {
) !Window { ) !Window {
_ = alloc; _ = alloc;
const gtk_native = apprt_window.window.as(gtk.Native); const gtk_native = apprt_window.as(gtk.Native);
const gdk_surface = gtk_native.getSurface() orelse return error.NotWaylandSurface; const gdk_surface = gtk_native.getSurface() 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
@ -364,7 +364,11 @@ 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.apprt_window.config.background_blur; const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return;
const blur = 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
@ -392,7 +396,12 @@ pub const Window = struct {
} }
fn getDecorationMode(self: Window) org.KdeKwinServerDecorationManager.Mode { fn getDecorationMode(self: Window) org.KdeKwinServerDecorationManager.Mode {
return switch (self.apprt_window.config.window_decoration) { const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return .Client;
return switch (config.@"window-decoration") {
.auto => self.app_context.default_deco_mode orelse .Client, .auto => self.app_context.default_deco_mode orelse .Client,
.client => .Client, .client => .Client,
.server => .Server, .server => .Server,
@ -401,12 +410,15 @@ pub const Window = struct {
} }
fn syncQuickTerminal(self: *Window) !void { fn syncQuickTerminal(self: *Window) !void {
const window = self.apprt_window.window.as(gtk.Window); const window = self.apprt_window.as(gtk.Window);
const config = &self.apprt_window.config; const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return;
layer_shell.setKeyboardMode( layer_shell.setKeyboardMode(
window, window,
switch (config.quick_terminal_keyboard_interactivity) { switch (config.@"quick-terminal-keyboard-interactivity") {
.none => .none, .none => .none,
.@"on-demand" => on_demand: { .@"on-demand" => on_demand: {
if (layer_shell.getProtocolVersion() < 4) { if (layer_shell.getProtocolVersion() < 4) {
@ -419,7 +431,7 @@ pub const Window = struct {
}, },
); );
const anchored_edge: ?layer_shell.ShellEdge = switch (config.quick_terminal_position) { const anchored_edge: ?layer_shell.ShellEdge = switch (config.@"quick-terminal-position") {
.left => .left, .left => .left,
.right => .right, .right => .right,
.top => .top, .top => .top,
@ -470,14 +482,14 @@ pub const Window = struct {
monitor: *gdk.Monitor, monitor: *gdk.Monitor,
apprt_window: *ApprtWindow, apprt_window: *ApprtWindow,
) callconv(.c) void { ) callconv(.c) void {
const window = apprt_window.window.as(gtk.Window); const window = apprt_window.as(gtk.Window);
const config = &apprt_window.config; const config = if (apprt_window.getConfig()) |v| v.get() else return;
var monitor_size: gdk.Rectangle = undefined; var monitor_size: gdk.Rectangle = undefined;
monitor.getGeometry(&monitor_size); monitor.getGeometry(&monitor_size);
const dims = config.quick_terminal_size.calculate( const dims = config.@"quick-terminal-size".calculate(
config.quick_terminal_position, config.@"quick-terminal-position",
.{ .{
.width = @intCast(monitor_size.f_width), .width = @intCast(monitor_size.f_width),
.height = @intCast(monitor_size.f_height), .height = @intCast(monitor_size.f_height),

View File

@ -20,7 +20,7 @@ pub const c = @cImport({
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const ApprtWindow = void; // TODO: fix const ApprtWindow = @import("../class/window.zig").Window;
const log = std.log.scoped(.gtk_x11); const log = std.log.scoped(.gtk_x11);
@ -170,8 +170,7 @@ pub const App = struct {
pub const Window = struct { pub const Window = struct {
app: *App, app: *App,
config: *const ApprtWindow.DerivedConfig, apprt_window: *ApprtWindow,
gtk_window: *adw.ApplicationWindow,
x11_surface: *gdk_x11.X11Surface, x11_surface: *gdk_x11.X11Surface,
blur_region: Region = .{}, blur_region: Region = .{},
@ -183,9 +182,8 @@ pub const Window = struct {
) !Window { ) !Window {
_ = alloc; _ = alloc;
const surface = apprt_window.window.as( const surface = apprt_window.as(gtk.Native).getSurface() orelse
gtk.Native, return error.NotX11Surface;
).getSurface() orelse return error.NotX11Surface;
const x11_surface = gobject.ext.cast( const x11_surface = gobject.ext.cast(
gdk_x11.X11Surface, gdk_x11.X11Surface,
@ -194,8 +192,7 @@ pub const Window = struct {
return .{ return .{
.app = app, .app = app,
.config = &apprt_window.config, .apprt_window = apprt_window,
.gtk_window = apprt_window.window,
.x11_surface = x11_surface, .x11_surface = x11_surface,
}; };
} }
@ -221,10 +218,10 @@ pub const Window = struct {
var x: f64 = 0; var x: f64 = 0;
var y: f64 = 0; var y: f64 = 0;
self.gtk_window.as(gtk.Native).getSurfaceTransform(&x, &y); self.apprt_window.as(gtk.Native).getSurfaceTransform(&x, &y);
// Transform surface coordinates to device coordinates. // Transform surface coordinates to device coordinates.
const scale: f64 = @floatFromInt(self.gtk_window.as(gtk.Widget).getScaleFactor()); const scale: f64 = @floatFromInt(self.apprt_window.as(gtk.Widget).getScaleFactor());
x *= scale; x *= scale;
y *= scale; y *= scale;
@ -242,7 +239,12 @@ pub const Window = struct {
} }
pub fn clientSideDecorationEnabled(self: Window) bool { pub fn clientSideDecorationEnabled(self: Window) bool {
return switch (self.config.window_decoration) { const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return true;
return switch (config.@"window-decoration") {
.auto, .client => true, .auto, .client => true,
.server, .none => false, .server, .none => false,
}; };
@ -257,14 +259,15 @@ 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 gtk_widget = self.gtk_window.as(gtk.Widget); const gtk_widget = self.apprt_window.as(gtk.Widget);
const config = if (self.apprt_window.getConfig()) |v| v.get() else return;
// Transform surface coordinates to device coordinates. // Transform surface coordinates to device coordinates.
const scale = self.gtk_window.as(gtk.Widget).getScaleFactor(); const scale = gtk_widget.getScaleFactor();
self.blur_region.width = gtk_widget.getWidth() * scale; self.blur_region.width = gtk_widget.getWidth() * scale;
self.blur_region.height = gtk_widget.getHeight() * scale; self.blur_region.height = gtk_widget.getHeight() * scale;
const blur = self.config.background_blur; const blur = config.@"background-blur";
log.debug("set blur={}, window xid={}, region={}", .{ log.debug("set blur={}, window xid={}, region={}", .{
blur, blur,
self.x11_surface.getXid(), self.x11_surface.getXid(),
@ -286,6 +289,11 @@ pub const Window = struct {
} }
fn syncDecorations(self: *Window) !void { fn syncDecorations(self: *Window) !void {
const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return;
var hints: MotifWMHints = .{}; var hints: MotifWMHints = .{};
self.getWindowProperty( self.getWindowProperty(
@ -306,7 +314,7 @@ pub const Window = struct {
}; };
hints.flags.decorations = true; hints.flags.decorations = true;
hints.decorations.all = switch (self.config.window_decoration) { hints.decorations.all = switch (config.@"window-decoration") {
.server => true, .server => true,
.auto, .client, .none => false, .auto, .client, .none => false,
}; };