gtk(wayland): respect compositor SSD preferences (#5124)

Compositors can actually tell us whether they want to use CSD or SSD!

(Ignore the context menu changes — they will most likely be unified
after #4952 anyway)
This commit is contained in:
Mitchell Hashimoto
2025-01-16 13:01:31 -08:00
committed by GitHub
5 changed files with 73 additions and 57 deletions

View File

@ -1881,7 +1881,6 @@ fn initContextMenu(self: *App) void {
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
}
if (!self.config.@"window-decoration".isCSD()) {
const section = c.g_menu_new();
defer c.g_object_unref(section);
const submenu = c.g_menu_new();
@ -1890,7 +1889,6 @@ fn initContextMenu(self: *App) void {
initMenuContent(@ptrCast(submenu));
c.g_menu_append_submenu(section, "Menu", @ptrCast(@alignCast(submenu)));
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
}
self.context_menu = menu;
}

View File

@ -584,8 +584,8 @@ pub fn toggleFullscreen(self: *Window) void {
/// Toggle the window decorations for this window.
pub fn toggleWindowDecorations(self: *Window) void {
self.app.config.@"window-decoration" = switch (self.app.config.@"window-decoration") {
.client, .server => .none,
.none => .server,
.auto, .client, .server => .none,
.none => .client,
};
self.updateConfig(&self.app.config) catch {};
}

View File

@ -22,6 +22,8 @@ pub const App = struct {
// FIXME: replace with `zxdg_decoration_v1` once GTK merges
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
};
pub fn init(
@ -57,6 +59,12 @@ pub const App = struct {
registry.setListener(*Context, registryListener, context);
if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
if (context.kde_decoration_manager != null) {
// FIXME: Roundtrip again because we have to wait for the decoration
// manager to respond with the preferred default mode. Ew.
if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
}
return .{
.display = display,
.context = context,
@ -82,25 +90,22 @@ pub const App = struct {
) void {
switch (event) {
// https://wayland.app/protocols/wayland#wl_registry:event:global
.global => |global| global: {
.global => |global| {
log.debug("wl_registry.global: interface={s}", .{global.interface});
if (registryBind(
org.KdeKwinBlurManager,
registry,
global,
1,
)) |blur_manager| {
context.kde_blur_manager = blur_manager;
break :global;
} else if (registryBind(
org.KdeKwinServerDecorationManager,
registry,
global,
1,
)) |deco_manager| {
context.kde_decoration_manager = deco_manager;
break :global;
deco_manager.setListener(*Context, decoManagerListener, context);
}
},
@ -119,7 +124,6 @@ pub const App = struct {
comptime T: type,
registry: *wl.Registry,
global: anytype,
version: u32,
) ?*T {
if (std.mem.orderZ(
u8,
@ -127,7 +131,7 @@ pub const App = struct {
T.interface.name,
) != .eq) return null;
return registry.bind(global.name, T, version) catch |err| {
return registry.bind(global.name, T, T.generated_version) catch |err| {
log.warn("error binding interface {s} error={}", .{
global.interface,
err,
@ -135,6 +139,18 @@ pub const App = struct {
return null;
};
}
fn decoManagerListener(
_: *org.KdeKwinServerDecorationManager,
event: org.KdeKwinServerDecorationManager.Event,
context: *Context,
) void {
switch (event) {
.default_mode => |mode| {
context.default_deco_mode = @enumFromInt(mode.mode);
},
}
}
};
/// Per-window (wl_surface) state for the Wayland protocol.
@ -235,13 +251,14 @@ pub const Window = struct {
}
pub fn clientSideDecorationEnabled(self: Window) bool {
// Note: we should change this to being the actual mode
// state emitted by the decoration manager.
// Compositor doesn't support the SSD protocol
if (self.decoration == null) return true;
// We are CSD if we don't support the SSD Wayland protocol
// or if we do but we're in CSD mode.
return self.decoration == null or
self.config.window_decoration.isCSD();
return switch (self.getDecorationMode()) {
.Client => true,
.Server, .None => false,
else => unreachable,
};
}
/// Update the blur state of the window.
@ -269,14 +286,17 @@ pub const Window = struct {
fn syncDecoration(self: *Window) !void {
const deco = self.decoration orelse return;
const mode: org.KdeKwinServerDecoration.Mode = switch (self.config.window_decoration) {
// The protocol requests uint instead of enum so we have
// to convert it.
deco.requestMode(@intCast(@intFromEnum(self.getDecorationMode())));
}
fn getDecorationMode(self: Window) org.KdeKwinServerDecorationManager.Mode {
return switch (self.config.window_decoration) {
.auto => self.app_context.default_deco_mode orelse .Client,
.client => .Client,
.server => .Server,
.none => .None,
};
// The protocol requests uint instead of enum so we have
// to convert it.
deco.requestMode(@intCast(@intFromEnum(mode)));
}
};

View File

@ -168,7 +168,7 @@ pub const Window = struct {
.blur = config.@"background-blur-radius".enabled(),
.has_decoration = switch (config.@"window-decoration") {
.none => false,
.client, .server => true,
.auto, .client, .server => true,
},
};
}

View File

@ -1138,27 +1138,33 @@ keybind: Keybinds = .{},
/// borders, etc. will not be shown. On macOS, this will also disable
/// tabs (enforced by the system).
///
/// * `client` - Prefer client-side decorations. This is the default.
/// * `auto` - Automatically decide to use either client-side or server-side
/// decorations based on the detected preferences of the current OS and
/// desktop environment. This option usually makes Ghostty look the most
/// "native" for your desktop.
///
/// * `client` - Prefer client-side decorations.
///
/// * `server` - Prefer server-side decorations. This is only relevant
/// on Linux with GTK. This currently only works on Linux with Wayland
/// and the `org_kde_kwin_server_decoration` protocol available (e.g.
/// KDE Plasma, but almost any non-Gnome desktop supports this protocol).
/// KDE Plasma, but almost any non-GNOME desktop supports this protocol).
///
/// If `server` is set but the environment doesn't support server-side
/// decorations, client-side decorations will be used instead.
///
/// The default value is `client`.
/// The default value is `auto`.
///
/// This setting also accepts boolean true and false values. If set to `true`,
/// this is equivalent to `client`. If set to `false`, this is equivalent to
/// `none`. This is a convenience for users who live primarily on systems
/// that don't differentiate between client and server-side decorations
/// (e.g. macOS and Windows).
/// For the sake of backwards compatibility and convenience, this setting also
/// accepts boolean true and false values. If set to `true`, this is equivalent
/// to `auto`. If set to `false`, this is equivalent to `none`.
/// This is convenient for users who live primarily on systems that don't
/// differentiate between client and server-side decorations (e.g. macOS and
/// Windows).
///
/// The "toggle_window_decorations" keybind action can be used to create
/// a keybinding to toggle this setting at runtime. This will always toggle
/// back to "server" if the current value is "none" (this is an issue
/// back to "auto" if the current value is "none" (this is an issue
/// that will be fixed in the future).
///
/// Changing this configuration in your configuration and reloading will
@ -1166,7 +1172,7 @@ keybind: Keybinds = .{},
///
/// macOS: To hide the titlebar without removing the native window borders
/// or rounded corners, use `macos-titlebar-style = hidden` instead.
@"window-decoration": WindowDecoration = .client,
@"window-decoration": WindowDecoration = .auto,
/// The font that will be used for the application's window and tab titles.
///
@ -5917,44 +5923,32 @@ pub const BackgroundBlur = union(enum) {
/// See window-decoration
pub const WindowDecoration = enum {
auto,
client,
server,
none,
pub fn parseCLI(input_: ?[]const u8) !WindowDecoration {
const input = input_ orelse return .client;
const input = input_ orelse return .auto;
return if (cli.args.parseBool(input)) |b|
if (b) .client else .none
if (b) .auto else .none
else |_| if (std.meta.stringToEnum(WindowDecoration, input)) |v|
v
else
error.InvalidValue;
}
/// Returns true if the window decoration setting results in
/// CSD (client-side decorations). Note that this only returns the
/// user requested behavior. Depending on available APIs (e.g.
/// Wayland protocols), the actual behavior may differ and the apprt
/// should rely on actual windowing APIs to determine the actual
/// status.
pub fn isCSD(self: WindowDecoration) bool {
return switch (self) {
.client => true,
.server, .none => false,
};
}
test "parse WindowDecoration" {
const testing = std.testing;
{
const v = try WindowDecoration.parseCLI(null);
try testing.expectEqual(WindowDecoration.client, v);
try testing.expectEqual(WindowDecoration.auto, v);
}
{
const v = try WindowDecoration.parseCLI("true");
try testing.expectEqual(WindowDecoration.client, v);
try testing.expectEqual(WindowDecoration.auto, v);
}
{
const v = try WindowDecoration.parseCLI("false");
@ -5968,6 +5962,10 @@ pub const WindowDecoration = enum {
const v = try WindowDecoration.parseCLI("client");
try testing.expectEqual(WindowDecoration.client, v);
}
{
const v = try WindowDecoration.parseCLI("auto");
try testing.expectEqual(WindowDecoration.auto, v);
}
{
const v = try WindowDecoration.parseCLI("none");
try testing.expectEqual(WindowDecoration.none, v);