mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +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,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;
|
||||||
}
|
}
|
||||||
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
@ -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)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
Reference in New Issue
Block a user