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.
This commit is contained in:
Mitchell Hashimoto
2025-08-02 12:57:01 -07:00
committed by GitHub
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_fullscreen => Action.toggleFullscreen(target),
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
.toggle_tab_overview => return Action.toggleTabOverview(target),
// Unimplemented but todo on gtk-ng branch
@@ -562,7 +563,6 @@ pub const Application = extern struct {
.goto_split,
.toggle_split_zoom,
// TODO: winproto
.toggle_quick_terminal,
.toggle_window_decorations,
=> {
log.warn("unimplemented action={}", .{action});
@@ -1477,7 +1477,14 @@ const Action = struct {
parent: ?*CoreSurface,
) !void {
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
// window. There's never a time when the window config should be out
// 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 {
switch (target) {
.app => {},

View File

@@ -26,6 +26,7 @@ const Config = @import("config.zig").Config;
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
const Window = @import("window.zig").Window;
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 {
_ = self;
const alloc = Application.default().allocator();
var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit();
@@ -1099,6 +1098,14 @@ pub const Surface = extern struct {
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;
}

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const build_config = @import("../../../build_config.zig");
const assert = std.debug.assert;
const adw = @import("adw");
const gdk = @import("gdk");
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
@@ -15,6 +16,7 @@ const ext = @import("../ext.zig");
const gtk_version = @import("../gtk_version.zig");
const adw_version = @import("../adw_version.zig");
const gresource = @import("../build/gresource.zig");
const winprotopkg = @import("../winproto.zig");
const Common = @import("../class.zig").Common;
const Config = @import("config.zig").Config;
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 name = "tabs-autohide";
const impl = gobject.ext.defineProperty(
@@ -205,12 +227,19 @@ pub const Window = extern 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.
tab_bindings: *gobject.BindingGroup,
/// The configuration that this surface is using.
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
/// initialized any single surface yet. We need this because we
/// 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();
}
// 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.
if (comptime build_config.is_debug) {
self.as(gtk.Widget).addCssClass("devel");
@@ -270,6 +303,24 @@ pub const Window = extern struct {
// Initialize our actions
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
// config and such can affect our bindings which ar setup initially
// 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
/// at the position dictated by the `window-new-tab-position` config.
/// The new tab will be selected.
@@ -466,12 +522,28 @@ pub const Window = extern struct {
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
/// to call multiple times. This should be called whenever a change
/// happens that might affect how the window appears (config change,
/// fullscreen, etc.).
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.
inline for (&.{
@@ -488,15 +560,28 @@ pub const Window = extern struct {
}
// Remainder uses the config
const priv = self.private();
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.
priv.toolbar.remove(priv.tab_bar.as(gtk.Widget));
switch (config.@"gtk-tabs-location") {
.top => priv.toolbar.addTopBar(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 {
@@ -535,6 +620,11 @@ pub const Window = extern struct {
//---------------------------------------------------------------
// 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.
/// This does not ref the value.
fn getActiveSurface(self: *Self) ?*Surface {
@@ -542,6 +632,12 @@ pub const Window = extern struct {
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.
fn getSelectedTab(self: *Self) ?*Tab {
const priv = self.private();
@@ -571,8 +667,14 @@ pub const Window = extern struct {
}
fn getHeaderbarVisible(self: *Self) bool {
// TODO: CSD/SSD
// TODO: QuickTerminal
const priv = self.private();
// 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 (self.as(gtk.Window).isFullscreen() != 0) return false;
@@ -648,6 +750,36 @@ pub const Window = extern struct {
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(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
@@ -696,6 +828,26 @@ pub const Window = extern struct {
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
/// should be opaque.
fn propBackgroundOpaque(
@@ -706,6 +858,24 @@ pub const Window = extern struct {
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
@@ -731,6 +901,7 @@ pub const Window = extern struct {
fn finalize(self: *Self) callconv(.C) void {
const priv = self.private();
priv.tab_bindings.unref();
priv.winproto.deinit(Application.default().allocator());
gobject.Object.virtual_methods.finalize.call(
Class.parent,
@@ -741,6 +912,26 @@ pub const Window = extern struct {
//---------------------------------------------------------------
// 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 {
self.performBindingAction(.new_tab);
}
@@ -1362,6 +1553,7 @@ pub const Window = extern struct {
properties.config.impl,
properties.debug.impl,
properties.@"headerbar-visible".impl,
properties.@"quick-terminal".impl,
properties.@"tabs-autohide".impl,
properties.@"tabs-visible".impl,
properties.@"tabs-wide".impl,
@@ -1376,6 +1568,7 @@ pub const Window = extern struct {
class.bindTemplateChildPrivate("toast_overlay", .{});
// Template Callbacks
class.bindTemplateCallback("realize", &windowRealize);
class.bindTemplateCallback("new_tab", &btnNewTab);
class.bindTemplateCallback("overview_create_tab", &tabOverviewCreateTab);
class.bindTemplateCallback("overview_notify_open", &tabOverviewOpen);
@@ -1386,11 +1579,13 @@ pub const Window = extern struct {
class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow);
class.bindTemplateCallback("notify_n_pages", &tabViewNPages);
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
class.bindTemplateCallback("notify_config", &propConfig);
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
class.bindTemplateCallback("notify_maximized", &propMaximized);
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
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
*/
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
*/

View File

@@ -6,6 +6,7 @@
const std = @import("std");
const assert = std.debug.assert;
const glib = @import("glib");
const gobject = @import("gobject");
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
/// of the given type `T`, or null if it doesn't exist.
pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T {

View File

@@ -7,10 +7,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
]
close-request => $close_request();
realize => $realize();
notify::background-opaque => $notify_background_opaque();
notify::config => $notify_config();
notify::fullscreened => $notify_fullscreened();
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-height: 600;
// 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 {
create-tab => $overview_create_tab();
notify::open => $overview_notify_open();
enable-new-tab: true;
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 {
top-bar-style: bind template.toolbar-style;

View File

@@ -7,9 +7,7 @@ const gdk = @import("gdk");
const Config = @import("../../config.zig").Config;
const input = @import("../../input.zig");
const key = @import("key.zig");
// TODO: As we get to these APIs the compiler should tell us
const ApprtWindow = void;
const ApprtWindow = @import("class/window.zig").Window;
pub const noop = @import("winproto/noop.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 input = @import("../../../input.zig");
const ApprtWindow = void; // TODO: fix
const ApprtWindow = @import("../class/window.zig").Window;
const log = std.log.scoped(.winproto_noop);

View File

@@ -12,7 +12,7 @@ const wayland = @import("wayland");
const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig");
const ApprtWindow = void; // TODO: fix
const ApprtWindow = @import("../class/window.zig").Window;
const wl = wayland.client.wl;
const org = wayland.client.org;
@@ -127,7 +127,7 @@ pub const App = struct {
}
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.setLayer(window, .top);
@@ -257,7 +257,7 @@ pub const Window = struct {
) !Window {
_ = 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;
// 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.
fn syncBlur(self: *Window) !void {
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| {
// Only release token when transitioning from blurred -> not blurred
@@ -392,7 +396,12 @@ pub const Window = struct {
}
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,
.client => .Client,
.server => .Server,
@@ -401,12 +410,15 @@ pub const Window = struct {
}
fn syncQuickTerminal(self: *Window) !void {
const window = self.apprt_window.window.as(gtk.Window);
const config = &self.apprt_window.config;
const window = self.apprt_window.as(gtk.Window);
const config = if (self.apprt_window.getConfig()) |v|
v.get()
else
return;
layer_shell.setKeyboardMode(
window,
switch (config.quick_terminal_keyboard_interactivity) {
switch (config.@"quick-terminal-keyboard-interactivity") {
.none => .none,
.@"on-demand" => on_demand: {
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,
.right => .right,
.top => .top,
@@ -470,14 +482,14 @@ pub const Window = struct {
monitor: *gdk.Monitor,
apprt_window: *ApprtWindow,
) callconv(.c) void {
const window = apprt_window.window.as(gtk.Window);
const config = &apprt_window.config;
const window = apprt_window.as(gtk.Window);
const config = if (apprt_window.getConfig()) |v| v.get() else return;
var monitor_size: gdk.Rectangle = undefined;
monitor.getGeometry(&monitor_size);
const dims = config.quick_terminal_size.calculate(
config.quick_terminal_position,
const dims = config.@"quick-terminal-size".calculate(
config.@"quick-terminal-position",
.{
.width = @intCast(monitor_size.f_width),
.height = @intCast(monitor_size.f_height),

View File

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