Skip to content

Commit

Permalink
gtk(wayland): respect compositor SSD preferences (#5124)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
mitchellh authored Jan 16, 2025
2 parents 62d3786 + 7716f98 commit 6c2c436
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 58 deletions.
18 changes: 8 additions & 10 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1881,16 +1881,14 @@ 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();
defer c.g_object_unref(submenu);

initMenuContent(@ptrCast(submenu));
c.g_menu_append_submenu(section, "Menu", @ptrCast(@alignCast(submenu)));
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
}
const section = c.g_menu_new();
defer c.g_object_unref(section);
const submenu = c.g_menu_new();
defer c.g_object_unref(submenu);

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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/apprt/gtk/Window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
}
Expand Down
56 changes: 38 additions & 18 deletions src/apprt/gtk/winproto/wayland.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
},

Expand All @@ -119,22 +124,33 @@ pub const App = struct {
comptime T: type,
registry: *wl.Registry,
global: anytype,
version: u32,
) ?*T {
if (std.mem.orderZ(
u8,
global.interface,
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,
});
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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)));
}
};
2 changes: 1 addition & 1 deletion src/apprt/gtk/winproto/x11.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
}
Expand Down
52 changes: 25 additions & 27 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1138,35 +1138,41 @@ 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
/// only affect new windows. Existing windows will not be affected.
///
/// 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.
///
Expand Down Expand Up @@ -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");
Expand All @@ -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);
Expand Down

0 comments on commit 6c2c436

Please sign in to comment.