mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
gtk(wayland): respect compositor SSD preferences
Compositors can actually tell us whether they want to use CSD or SSD!
This commit is contained in:
@ -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);
|
||||
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)));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user