Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gtk(wayland): respect compositor SSD preferences #5124

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now. Ideally we can add the fd to the GTK event loop so the event loop wakes up when we have data to process.

// 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