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

View File

@ -584,8 +584,8 @@ pub fn toggleFullscreen(self: *Window) void {
/// 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.app.config.@"window-decoration" = switch (self.app.config.@"window-decoration") {
.client, .server => .none, .auto, .client, .server => .none,
.none => .server, .none => .client,
}; };
self.updateConfig(&self.app.config) catch {}; 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 // FIXME: replace with `zxdg_decoration_v1` once GTK merges
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398 // https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null, kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
}; };
pub fn init( pub fn init(
@ -57,6 +59,12 @@ pub const App = struct {
registry.setListener(*Context, registryListener, context); registry.setListener(*Context, registryListener, context);
if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; 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 .{ return .{
.display = display, .display = display,
.context = context, .context = context,
@ -82,25 +90,22 @@ pub const App = struct {
) void { ) void {
switch (event) { switch (event) {
// https://wayland.app/protocols/wayland#wl_registry:event:global // https://wayland.app/protocols/wayland#wl_registry:event:global
.global => |global| global: { .global => |global| {
log.debug("wl_registry.global: interface={s}", .{global.interface}); log.debug("wl_registry.global: interface={s}", .{global.interface});
if (registryBind( if (registryBind(
org.KdeKwinBlurManager, org.KdeKwinBlurManager,
registry, registry,
global, global,
1,
)) |blur_manager| { )) |blur_manager| {
context.kde_blur_manager = blur_manager; context.kde_blur_manager = blur_manager;
break :global;
} else if (registryBind( } else if (registryBind(
org.KdeKwinServerDecorationManager, org.KdeKwinServerDecorationManager,
registry, registry,
global, global,
1,
)) |deco_manager| { )) |deco_manager| {
context.kde_decoration_manager = 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, comptime T: type,
registry: *wl.Registry, registry: *wl.Registry,
global: anytype, global: anytype,
version: u32,
) ?*T { ) ?*T {
if (std.mem.orderZ( if (std.mem.orderZ(
u8, u8,
@ -127,7 +131,7 @@ pub const App = struct {
T.interface.name, T.interface.name,
) != .eq) return null; ) != .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={}", .{ log.warn("error binding interface {s} error={}", .{
global.interface, global.interface,
err, err,
@ -135,6 +139,18 @@ pub const App = struct {
return null; 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. /// Per-window (wl_surface) state for the Wayland protocol.
@ -235,13 +251,14 @@ pub const Window = struct {
} }
pub fn clientSideDecorationEnabled(self: Window) bool { pub fn clientSideDecorationEnabled(self: Window) bool {
// Note: we should change this to being the actual mode // Compositor doesn't support the SSD protocol
// state emitted by the decoration manager. if (self.decoration == null) return true;
// We are CSD if we don't support the SSD Wayland protocol return switch (self.getDecorationMode()) {
// or if we do but we're in CSD mode. .Client => true,
return self.decoration == null or .Server, .None => false,
self.config.window_decoration.isCSD(); else => unreachable,
};
} }
/// Update the blur state of the window. /// Update the blur state of the window.
@ -269,14 +286,17 @@ pub const Window = struct {
fn syncDecoration(self: *Window) !void { fn syncDecoration(self: *Window) !void {
const deco = self.decoration orelse return; 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, .client => .Client,
.server => .Server, .server => .Server,
.none => .None, .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(), .blur = config.@"background-blur-radius".enabled(),
.has_decoration = switch (config.@"window-decoration") { .has_decoration = switch (config.@"window-decoration") {
.none => false, .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 /// borders, etc. will not be shown. On macOS, this will also disable
/// tabs (enforced by the system). /// 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 /// * `server` - Prefer server-side decorations. This is only relevant
/// on Linux with GTK. This currently only works on Linux with Wayland /// on Linux with GTK. This currently only works on Linux with Wayland
/// and the `org_kde_kwin_server_decoration` protocol available (e.g. /// 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 /// If `server` is set but the environment doesn't support server-side
/// decorations, client-side decorations will be used instead. /// 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`, /// For the sake of backwards compatibility and convenience, this setting also
/// this is equivalent to `client`. If set to `false`, this is equivalent to /// accepts boolean true and false values. If set to `true`, this is equivalent
/// `none`. This is a convenience for users who live primarily on systems /// to `auto`. If set to `false`, this is equivalent to `none`.
/// that don't differentiate between client and server-side decorations /// This is convenient for users who live primarily on systems that don't
/// (e.g. macOS and Windows). /// differentiate between client and server-side decorations (e.g. macOS and
/// Windows).
/// ///
/// The "toggle_window_decorations" keybind action can be used to create /// The "toggle_window_decorations" keybind action can be used to create
/// a keybinding to toggle this setting at runtime. This will always toggle /// 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). /// that will be fixed in the future).
/// ///
/// Changing this configuration in your configuration and reloading will /// 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 /// macOS: To hide the titlebar without removing the native window borders
/// or rounded corners, use `macos-titlebar-style = hidden` instead. /// 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. /// 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 /// See window-decoration
pub const WindowDecoration = enum { pub const WindowDecoration = enum {
auto,
client, client,
server, server,
none, none,
pub fn parseCLI(input_: ?[]const u8) !WindowDecoration { 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| 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| else |_| if (std.meta.stringToEnum(WindowDecoration, input)) |v|
v v
else else
error.InvalidValue; 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" { test "parse WindowDecoration" {
const testing = std.testing; const testing = std.testing;
{ {
const v = try WindowDecoration.parseCLI(null); const v = try WindowDecoration.parseCLI(null);
try testing.expectEqual(WindowDecoration.client, v); try testing.expectEqual(WindowDecoration.auto, v);
} }
{ {
const v = try WindowDecoration.parseCLI("true"); const v = try WindowDecoration.parseCLI("true");
try testing.expectEqual(WindowDecoration.client, v); try testing.expectEqual(WindowDecoration.auto, v);
} }
{ {
const v = try WindowDecoration.parseCLI("false"); const v = try WindowDecoration.parseCLI("false");
@ -5968,6 +5962,10 @@ pub const WindowDecoration = enum {
const v = try WindowDecoration.parseCLI("client"); const v = try WindowDecoration.parseCLI("client");
try testing.expectEqual(WindowDecoration.client, v); try testing.expectEqual(WindowDecoration.client, v);
} }
{
const v = try WindowDecoration.parseCLI("auto");
try testing.expectEqual(WindowDecoration.auto, v);
}
{ {
const v = try WindowDecoration.parseCLI("none"); const v = try WindowDecoration.parseCLI("none");
try testing.expectEqual(WindowDecoration.none, v); try testing.expectEqual(WindowDecoration.none, v);