mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
gtk(wayland): implement server-sided decorations (#4724)
Fixes #4630 fully unless there exists an X11 API I haven't found yet :p ~~Depends on first commit of #4723, which is duplicated here for now~~
This commit is contained in:
@ -165,11 +165,14 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
var windowDecorations: Bool {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let defaultValue = true
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-decoration"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v;
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return WindowDecoration(rawValue: str)?.enabled() ?? defaultValue
|
||||
}
|
||||
|
||||
var windowTheme: String? {
|
||||
@ -554,4 +557,17 @@ extension Ghostty.Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum WindowDecoration: String {
|
||||
case none
|
||||
case client
|
||||
case server
|
||||
|
||||
func enabled() -> Bool {
|
||||
switch self {
|
||||
case .client, .server: return true
|
||||
case .none: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1881,7 +1881,7 @@ fn initContextMenu(self: *App) void {
|
||||
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
|
||||
}
|
||||
|
||||
if (!self.config.@"window-decoration") {
|
||||
if (!self.config.@"window-decoration".isCSD()) {
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
const submenu = c.g_menu_new();
|
||||
|
@ -1384,11 +1384,9 @@ fn gtkResize(area: *c.GtkGLArea, width: c.gint, height: c.gint, ud: ?*anyopaque)
|
||||
};
|
||||
|
||||
if (self.container.window()) |window| {
|
||||
if (window.winproto) |*winproto| {
|
||||
winproto.resizeEvent() catch |err| {
|
||||
log.warn("failed to notify window protocol of resize={}", .{err});
|
||||
};
|
||||
}
|
||||
window.winproto.resizeEvent() catch |err| {
|
||||
log.warn("failed to notify window protocol of resize={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
self.resize_overlay.maybeShow();
|
||||
|
@ -57,7 +57,7 @@ toast_overlay: ?*c.GtkWidget,
|
||||
adw_tab_overview_focus_timer: ?c.guint = null,
|
||||
|
||||
/// State and logic for windowing protocol for a window.
|
||||
winproto: ?winproto.Window,
|
||||
winproto: winproto.Window,
|
||||
|
||||
pub fn create(alloc: Allocator, app: *App) !*Window {
|
||||
// Allocate a fixed pointer for our window. We try to minimize
|
||||
@ -83,7 +83,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
.notebook = undefined,
|
||||
.context_menu = undefined,
|
||||
.toast_overlay = undefined,
|
||||
.winproto = null,
|
||||
.winproto = .none,
|
||||
};
|
||||
|
||||
// Create the window
|
||||
@ -207,11 +207,6 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
_ = c.g_signal_connect_data(gtk_window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// If we are disabling decorations then disable them right away.
|
||||
if (!app.config.@"window-decoration") {
|
||||
c.gtk_window_set_decorated(gtk_window, 0);
|
||||
}
|
||||
|
||||
// If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
|
||||
// need to stick the headerbar into the content box.
|
||||
if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
||||
@ -379,7 +374,11 @@ pub fn updateConfig(
|
||||
self: *Window,
|
||||
config: *const configpkg.Config,
|
||||
) !void {
|
||||
if (self.winproto) |*v| try v.updateConfigEvent(config);
|
||||
self.winproto.updateConfigEvent(config) catch |err| {
|
||||
// We want to continue attempting to make the other config
|
||||
// changes necessary so we just log the error and continue.
|
||||
log.warn("failed to update window protocol config error={}", .{err});
|
||||
};
|
||||
|
||||
// We always resync our appearance whenever the config changes.
|
||||
try self.syncAppearance(config);
|
||||
@ -391,16 +390,52 @@ pub fn updateConfig(
|
||||
/// TODO: Many of the initial style settings in `create` could possibly be made
|
||||
/// reactive by moving them here.
|
||||
pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
|
||||
if (config.@"background-opacity" < 1) {
|
||||
c.gtk_widget_remove_css_class(@ptrCast(self.window), "background");
|
||||
} else {
|
||||
c.gtk_widget_add_css_class(@ptrCast(self.window), "background");
|
||||
}
|
||||
|
||||
// Window protocol specific appearance updates
|
||||
if (self.winproto) |*v| v.syncAppearance() catch |err| {
|
||||
log.warn("failed to sync window protocol appearance error={}", .{err});
|
||||
self.winproto.syncAppearance() catch |err| {
|
||||
log.warn("failed to sync winproto appearance error={}", .{err});
|
||||
};
|
||||
|
||||
toggleCssClass(
|
||||
@ptrCast(self.window),
|
||||
"background",
|
||||
config.@"background-opacity" >= 1,
|
||||
);
|
||||
|
||||
// If we are disabling CSDs then disable them right away.
|
||||
const csd_enabled = self.winproto.clientSideDecorationEnabled();
|
||||
c.gtk_window_set_decorated(self.window, @intFromBool(csd_enabled));
|
||||
|
||||
// If we are not decorated then we hide the titlebar.
|
||||
self.headerbar.setVisible(config.@"gtk-titlebar" and csd_enabled);
|
||||
|
||||
// Disable the title buttons (close, maximize, minimize, ...)
|
||||
// *inside* the tab overview if CSDs are disabled.
|
||||
// We do spare the search button, though.
|
||||
if ((comptime adwaita.versionAtLeast(0, 0, 0)) and
|
||||
adwaita.enabled(&self.app.config))
|
||||
{
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
c.adw_tab_overview_set_show_start_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
c.adw_tab_overview_set_show_end_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleCssClass(
|
||||
widget: *c.GtkWidget,
|
||||
class: [:0]const u8,
|
||||
v: bool,
|
||||
) void {
|
||||
if (v) {
|
||||
c.gtk_widget_add_css_class(widget, class);
|
||||
} else {
|
||||
c.gtk_widget_remove_css_class(widget, class);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the GTK actions for the window scope. Actions are how GTK handles
|
||||
@ -440,7 +475,7 @@ fn initActions(self: *Window) void {
|
||||
pub fn deinit(self: *Window) void {
|
||||
c.gtk_widget_unparent(@ptrCast(self.context_menu));
|
||||
|
||||
if (self.winproto) |*v| v.deinit(self.app.core_app.alloc);
|
||||
self.winproto.deinit(self.app.core_app.alloc);
|
||||
|
||||
if (self.adw_tab_overview_focus_timer) |timer| {
|
||||
_ = c.g_source_remove(timer);
|
||||
@ -548,15 +583,11 @@ pub fn toggleFullscreen(self: *Window) void {
|
||||
|
||||
/// Toggle the window decorations for this window.
|
||||
pub fn toggleWindowDecorations(self: *Window) void {
|
||||
const old_decorated = c.gtk_window_get_decorated(self.window) == 1;
|
||||
const new_decorated = !old_decorated;
|
||||
c.gtk_window_set_decorated(self.window, @intFromBool(new_decorated));
|
||||
|
||||
// If we have a titlebar, then we also show/hide it depending on the
|
||||
// decorated state. GTK tends to consider the titlebar part of the frame
|
||||
// and hides it with decorations, but libadwaita doesn't. This makes it
|
||||
// explicit.
|
||||
self.headerbar.setVisible(new_decorated);
|
||||
self.app.config.@"window-decoration" = switch (self.app.config.@"window-decoration") {
|
||||
.client, .server => .none,
|
||||
.none => .server,
|
||||
};
|
||||
self.updateConfig(&self.app.config) catch {};
|
||||
}
|
||||
|
||||
/// Grabs focus on the currently selected tab.
|
||||
@ -623,17 +654,14 @@ fn gtkWindowNotifyDecorated(
|
||||
_: *c.GParamSpec,
|
||||
_: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
if (c.gtk_window_get_decorated(@ptrCast(object)) == 1) {
|
||||
c.gtk_widget_remove_css_class(@ptrCast(object), "ssd");
|
||||
c.gtk_widget_remove_css_class(@ptrCast(object), "no-border-radius");
|
||||
} else {
|
||||
// Fix any artifacting that may occur in window corners. The .ssd CSS
|
||||
// class is defined in the GtkWindow documentation:
|
||||
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
|
||||
// for .ssd is provided by GTK and Adwaita.
|
||||
c.gtk_widget_add_css_class(@ptrCast(object), "ssd");
|
||||
c.gtk_widget_add_css_class(@ptrCast(object), "no-border-radius");
|
||||
}
|
||||
const is_decorated = c.gtk_window_get_decorated(@ptrCast(object)) == 1;
|
||||
|
||||
// Fix any artifacting that may occur in window corners. The .ssd CSS
|
||||
// class is defined in the GtkWindow documentation:
|
||||
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
|
||||
// for .ssd is provided by GTK and Adwaita.
|
||||
toggleCssClass(@ptrCast(object), "ssd", !is_decorated);
|
||||
toggleCssClass(@ptrCast(object), "no-border-radius", !is_decorated);
|
||||
}
|
||||
|
||||
fn gtkWindowNotifyFullscreened(
|
||||
|
@ -18,9 +18,6 @@ pub const HeaderBar = union(enum) {
|
||||
} else {
|
||||
HeaderBarGtk.init(self);
|
||||
}
|
||||
|
||||
if (!window.app.config.@"gtk-titlebar" or !window.app.config.@"window-decoration")
|
||||
self.setVisible(false);
|
||||
}
|
||||
|
||||
pub fn setVisible(self: HeaderBar, visible: bool) void {
|
||||
|
@ -62,7 +62,7 @@ pub const App = union(Protocol) {
|
||||
|
||||
/// Per-Window state for the underlying windowing protocol.
|
||||
///
|
||||
/// In both X and Wayland, the terminology used is "Surface" and this is
|
||||
/// In Wayland, the terminology used is "Surface" and for it, this is
|
||||
/// really "Surface"-specific state. But Ghostty uses the term "Surface"
|
||||
/// heavily to mean something completely different, so we use "Window" here
|
||||
/// to better match what it generally maps to in the Ghostty codebase.
|
||||
@ -125,4 +125,10 @@ pub const Window = union(Protocol) {
|
||||
inline else => |*v| try v.syncAppearance(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return switch (self) {
|
||||
inline else => |v| v.clientSideDecorationEnabled(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -53,4 +53,12 @@ pub const Window = struct {
|
||||
pub fn resizeEvent(_: *Window) !void {}
|
||||
|
||||
pub fn syncAppearance(_: *Window) !void {}
|
||||
|
||||
/// This returns true if CSD is enabled for this window. This
|
||||
/// should be the actual present state of the window, not the
|
||||
/// desired state.
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
_ = self;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
@ -18,6 +18,10 @@ pub const App = struct {
|
||||
|
||||
const Context = struct {
|
||||
kde_blur_manager: ?*org.KdeKwinBlurManager = null,
|
||||
|
||||
// FIXME: replace with `zxdg_decoration_v1` once GTK merges
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
||||
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
@ -89,6 +93,14 @@ pub const App = struct {
|
||||
)) |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;
|
||||
}
|
||||
},
|
||||
|
||||
@ -97,6 +109,12 @@ pub const App = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind a Wayland interface to a global object. Returns non-null
|
||||
/// if the binding was successful, otherwise null.
|
||||
///
|
||||
/// The type T is the Wayland interface type that we're requesting.
|
||||
/// This function will verify that the global object is the correct
|
||||
/// interface and version before binding.
|
||||
fn registryBind(
|
||||
comptime T: type,
|
||||
registry: *wl.Registry,
|
||||
@ -130,14 +148,20 @@ pub const Window = struct {
|
||||
app_context: *App.Context,
|
||||
|
||||
/// A token that, when present, indicates that the window is blurred.
|
||||
blur_token: ?*org.KdeKwinBlur = null,
|
||||
blur_token: ?*org.KdeKwinBlur,
|
||||
|
||||
/// Object that controls the decoration mode (client/server/auto)
|
||||
/// of the window.
|
||||
decoration: ?*org.KdeKwinServerDecoration,
|
||||
|
||||
const DerivedConfig = struct {
|
||||
blur: bool,
|
||||
window_decoration: Config.WindowDecoration,
|
||||
|
||||
pub fn init(config: *const Config) DerivedConfig {
|
||||
return .{
|
||||
.blur = config.@"background-blur-radius".enabled(),
|
||||
.window_decoration = config.@"window-decoration",
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -165,19 +189,41 @@ pub const Window = struct {
|
||||
gdk_surface,
|
||||
) orelse return error.NoWaylandSurface);
|
||||
|
||||
// Get our decoration object so we can control the
|
||||
// CSD vs SSD status of this surface.
|
||||
const deco: ?*org.KdeKwinServerDecoration = deco: {
|
||||
const mgr = app.context.kde_decoration_manager orelse
|
||||
break :deco null;
|
||||
|
||||
const deco: *org.KdeKwinServerDecoration = mgr.create(
|
||||
wl_surface,
|
||||
) catch |err| {
|
||||
log.warn("could not create decoration object={}", .{err});
|
||||
break :deco null;
|
||||
};
|
||||
|
||||
break :deco deco;
|
||||
};
|
||||
|
||||
return .{
|
||||
.config = DerivedConfig.init(config),
|
||||
.surface = wl_surface,
|
||||
.app_context = app.context,
|
||||
.blur_token = null,
|
||||
.decoration = deco,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Window, alloc: Allocator) void {
|
||||
_ = alloc;
|
||||
if (self.blur_token) |blur| blur.release();
|
||||
if (self.decoration) |deco| deco.release();
|
||||
}
|
||||
|
||||
pub fn updateConfigEvent(self: *Window, config: *const Config) !void {
|
||||
pub fn updateConfigEvent(
|
||||
self: *Window,
|
||||
config: *const Config,
|
||||
) !void {
|
||||
self.config = DerivedConfig.init(config);
|
||||
}
|
||||
|
||||
@ -185,6 +231,17 @@ pub const Window = struct {
|
||||
|
||||
pub fn syncAppearance(self: *Window) !void {
|
||||
try self.syncBlur();
|
||||
try self.syncDecoration();
|
||||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
// Note: we should change this to being the actual mode
|
||||
// state emitted by the decoration manager.
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// Update the blur state of the window.
|
||||
@ -208,4 +265,18 @@ 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) {
|
||||
.client => .Client,
|
||||
.server => .Server,
|
||||
.none => .None,
|
||||
};
|
||||
|
||||
// The protocol requests uint instead of enum so we have
|
||||
// to convert it.
|
||||
deco.requestMode(@intCast(@intFromEnum(mode)));
|
||||
}
|
||||
};
|
||||
|
@ -161,10 +161,15 @@ pub const Window = struct {
|
||||
|
||||
const DerivedConfig = struct {
|
||||
blur: bool,
|
||||
has_decoration: bool,
|
||||
|
||||
pub fn init(config: *const Config) DerivedConfig {
|
||||
return .{
|
||||
.blur = config.@"background-blur-radius".enabled(),
|
||||
.has_decoration = switch (config.@"window-decoration") {
|
||||
.none => false,
|
||||
.client, .server => true,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -239,6 +244,10 @@ pub const Window = struct {
|
||||
try self.syncBlur();
|
||||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return self.config.has_decoration;
|
||||
}
|
||||
|
||||
fn syncBlur(self: *Window) !void {
|
||||
// FIXME: This doesn't currently factor in rounded corners on Adwaita,
|
||||
// which means that the blur region will grow slightly outside of the
|
||||
|
@ -450,10 +450,14 @@ pub fn add(
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml"));
|
||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/server-decoration.xml"));
|
||||
|
||||
scanner.generate("wl_compositor", 1);
|
||||
scanner.generate("org_kde_kwin_blur_manager", 1);
|
||||
scanner.generate("org_kde_kwin_server_decoration_manager", 1);
|
||||
|
||||
step.root_module.addImport("wayland", wayland);
|
||||
step.linkSystemLibrary2("wayland-client", dynamic_link_opts);
|
||||
|
@ -282,7 +282,7 @@ const c = @cImport({
|
||||
/// For example, a value of `1` increases the value by 1; it does not set it to
|
||||
/// literally 1. A value of `20%` increases the value by 20%. And so on.
|
||||
///
|
||||
/// There is little to no validation on these values so the wrong values (i.e.
|
||||
/// There is little to no validation on these values so the wrong values (e.g.
|
||||
/// `-100%`) can cause the terminal to be unusable. Use with caution and reason.
|
||||
///
|
||||
/// Some values are clamped to minimum or maximum values. This can make it
|
||||
@ -467,7 +467,7 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
||||
|
||||
/// The minimum contrast ratio between the foreground and background colors.
|
||||
/// The contrast ratio is a value between 1 and 21. A value of 1 allows for no
|
||||
/// contrast (i.e. black on black). This value is the contrast ratio as defined
|
||||
/// contrast (e.g. black on black). This value is the contrast ratio as defined
|
||||
/// by the [WCAG 2.0 specification](https://www.w3.org/TR/WCAG20/).
|
||||
///
|
||||
/// If you want to avoid invisible text (same color as background), a value of
|
||||
@ -722,7 +722,7 @@ command: ?[]const u8 = null,
|
||||
/// injecting any configured shell integration into the command's
|
||||
/// environment. With `-e` its highly unlikely that you're executing a
|
||||
/// shell and forced shell integration is likely to cause problems
|
||||
/// (i.e. by wrapping your command in a shell, setting env vars, etc.).
|
||||
/// (e.g. by wrapping your command in a shell, setting env vars, etc.).
|
||||
/// This is a safety measure to prevent unexpected behavior. If you want
|
||||
/// shell integration with a `-e`-executed command, you must either
|
||||
/// name your binary appropriately or source the shell integration script
|
||||
@ -770,7 +770,7 @@ command: ?[]const u8 = null,
|
||||
|
||||
/// Match a regular expression against the terminal text and associate clicking
|
||||
/// it with an action. This can be used to match URLs, file paths, etc. Actions
|
||||
/// can be opening using the system opener (i.e. `open` or `xdg-open`) or
|
||||
/// can be opening using the system opener (e.g. `open` or `xdg-open`) or
|
||||
/// executing any arbitrary binding action.
|
||||
///
|
||||
/// Links that are configured earlier take precedence over links that are
|
||||
@ -876,7 +876,7 @@ class: ?[:0]const u8 = null,
|
||||
/// Valid keys are currently only listed in the
|
||||
/// [Ghostty source code](https://github.com/ghostty-org/ghostty/blob/d6e76858164d52cff460fedc61ddf2e560912d71/src/input/key.zig#L255).
|
||||
/// This is a documentation limitation and we will improve this in the future.
|
||||
/// A common gotcha is that numeric keys are written as words: i.e. `one`,
|
||||
/// A common gotcha is that numeric keys are written as words: e.g. `one`,
|
||||
/// `two`, `three`, etc. and not `1`, `2`, `3`. This will also be improved in
|
||||
/// the future.
|
||||
///
|
||||
@ -919,7 +919,7 @@ class: ?[:0]const u8 = null,
|
||||
/// * Ghostty will wait an indefinite amount of time for the next key in
|
||||
/// the sequence. There is no way to specify a timeout. The only way to
|
||||
/// force the output of a prefix key is to assign another keybind to
|
||||
/// specifically output that key (i.e. `ctrl+a>ctrl+a=text:foo`) or
|
||||
/// specifically output that key (e.g. `ctrl+a>ctrl+a=text:foo`) or
|
||||
/// press an unbound key which will send both keys to the program.
|
||||
///
|
||||
/// * If a prefix in a sequence is previously bound, the sequence will
|
||||
@ -949,13 +949,13 @@ class: ?[:0]const u8 = null,
|
||||
/// including `physical:`-prefixed triggers without specifying the
|
||||
/// prefix.
|
||||
///
|
||||
/// * `csi:text` - Send a CSI sequence. i.e. `csi:A` sends "cursor up".
|
||||
/// * `csi:text` - Send a CSI sequence. e.g. `csi:A` sends "cursor up".
|
||||
///
|
||||
/// * `esc:text` - Send an escape sequence. i.e. `esc:d` deletes to the
|
||||
/// * `esc:text` - Send an escape sequence. e.g. `esc:d` deletes to the
|
||||
/// end of the word to the right.
|
||||
///
|
||||
/// * `text:text` - Send a string. Uses Zig string literal syntax.
|
||||
/// i.e. `text:\x15` sends Ctrl-U.
|
||||
/// e.g. `text:\x15` sends Ctrl-U.
|
||||
///
|
||||
/// * All other actions can be found in the documentation or by using the
|
||||
/// `ghostty +list-actions` command.
|
||||
@ -981,12 +981,12 @@ class: ?[:0]const u8 = null,
|
||||
/// keybinds only apply to the focused terminal surface. If this is true,
|
||||
/// then the keybind will be sent to all terminal surfaces. This only
|
||||
/// applies to actions that are surface-specific. For actions that
|
||||
/// are already global (i.e. `quit`), this prefix has no effect.
|
||||
/// are already global (e.g. `quit`), this prefix has no effect.
|
||||
///
|
||||
/// * `global:` - Make the keybind global. By default, keybinds only work
|
||||
/// within Ghostty and under the right conditions (application focused,
|
||||
/// sometimes terminal focused, etc.). If you want a keybind to work
|
||||
/// globally across your system (i.e. even when Ghostty is not focused),
|
||||
/// globally across your system (e.g. even when Ghostty is not focused),
|
||||
/// specify this prefix. This prefix implies `all:`. Note: this does not
|
||||
/// work in all environments; see the additional notes below for more
|
||||
/// information.
|
||||
@ -1087,7 +1087,7 @@ keybind: Keybinds = .{},
|
||||
/// any of the heuristics that disable extending noted below.
|
||||
///
|
||||
/// The "extend" value will be disabled in certain scenarios. On primary
|
||||
/// screen applications (i.e. not something like Neovim), the color will not
|
||||
/// screen applications (e.g. not something like Neovim), the color will not
|
||||
/// be extended vertically if any of the following are true:
|
||||
///
|
||||
/// * The nearest row has any cells that have the default background color.
|
||||
@ -1127,21 +1127,46 @@ keybind: Keybinds = .{},
|
||||
/// configuration `font-size` will be used.
|
||||
@"window-inherit-font-size": bool = true,
|
||||
|
||||
/// Configure a preference for window decorations. This setting specifies
|
||||
/// a _preference_; the actual OS, desktop environment, window manager, etc.
|
||||
/// may override this preference. Ghostty will do its best to respect this
|
||||
/// preference but it may not always be possible.
|
||||
///
|
||||
/// Valid values:
|
||||
///
|
||||
/// * `true`
|
||||
/// * `false` - windows won't have native decorations, i.e. titlebar and
|
||||
/// borders. On macOS this also disables tabs and tab overview.
|
||||
/// * `none` - All window decorations will be disabled. Titlebar,
|
||||
/// 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.
|
||||
///
|
||||
/// * `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).
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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).
|
||||
///
|
||||
/// The "toggle_window_decorations" keybind action can be used to create
|
||||
/// a keybinding to toggle this setting at runtime.
|
||||
/// 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
|
||||
/// 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": bool = true,
|
||||
@"window-decoration": WindowDecoration = .client,
|
||||
|
||||
/// The font that will be used for the application's window and tab titles.
|
||||
///
|
||||
@ -1364,7 +1389,7 @@ keybind: Keybinds = .{},
|
||||
@"resize-overlay-duration": Duration = .{ .duration = 750 * std.time.ns_per_ms },
|
||||
|
||||
/// If true, when there are multiple split panes, the mouse selects the pane
|
||||
/// that is focused. This only applies to the currently focused window; i.e.
|
||||
/// that is focused. This only applies to the currently focused window; e.g.
|
||||
/// mousing over a split in an unfocused window will not focus that split
|
||||
/// and bring the window to front.
|
||||
///
|
||||
@ -1408,7 +1433,7 @@ keybind: Keybinds = .{},
|
||||
/// and a minor amount of user interaction).
|
||||
@"title-report": bool = false,
|
||||
|
||||
/// The total amount of bytes that can be used for image data (i.e. the Kitty
|
||||
/// The total amount of bytes that can be used for image data (e.g. the Kitty
|
||||
/// image protocol) per terminal screen. The maximum value is 4,294,967,295
|
||||
/// (4GiB). The default is 320MB. If this is set to zero, then all image
|
||||
/// protocols will be disabled.
|
||||
@ -1668,7 +1693,7 @@ keybind: Keybinds = .{},
|
||||
///
|
||||
/// * `none` - OSC 4/10/11 queries receive no reply
|
||||
///
|
||||
/// * `8-bit` - Color components are return unscaled, i.e. `rr/gg/bb`
|
||||
/// * `8-bit` - Color components are return unscaled, e.g. `rr/gg/bb`
|
||||
///
|
||||
/// * `16-bit` - Color components are returned scaled, e.g. `rrrr/gggg/bbbb`
|
||||
///
|
||||
@ -1767,7 +1792,7 @@ keybind: Keybinds = .{},
|
||||
/// typical for a macOS application and may not work well with all themes.
|
||||
///
|
||||
/// The "transparent" style will also update in real-time to dynamic
|
||||
/// changes to the window background color, i.e. via OSC 11. To make this
|
||||
/// changes to the window background color, e.g. via OSC 11. To make this
|
||||
/// more aesthetically pleasing, this only happens if the terminal is
|
||||
/// a window, tab, or split that borders the top of the window. This
|
||||
/// avoids a disjointed appearance where the titlebar color changes
|
||||
@ -1834,7 +1859,7 @@ keybind: Keybinds = .{},
|
||||
/// - U.S. International
|
||||
///
|
||||
/// Note that if an *Option*-sequence doesn't produce a printable character, it
|
||||
/// will be treated as *Alt* regardless of this setting. (i.e. `alt+ctrl+a`).
|
||||
/// will be treated as *Alt* regardless of this setting. (e.g. `alt+ctrl+a`).
|
||||
///
|
||||
/// Explicit values that can be set:
|
||||
///
|
||||
@ -5890,6 +5915,62 @@ pub const BackgroundBlur = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
/// See window-decoration
|
||||
pub const WindowDecoration = enum {
|
||||
client,
|
||||
server,
|
||||
none,
|
||||
|
||||
pub fn parseCLI(input: ?[]const u8) !WindowDecoration {
|
||||
const input_ = input orelse return .client;
|
||||
|
||||
return if (cli.args.parseBool(input_)) |b|
|
||||
if (b) .client else .none
|
||||
else |_| if (std.mem.eql(u8, input_, "server"))
|
||||
.server
|
||||
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);
|
||||
}
|
||||
{
|
||||
const v = try WindowDecoration.parseCLI("true");
|
||||
try testing.expectEqual(WindowDecoration.client, v);
|
||||
}
|
||||
{
|
||||
const v = try WindowDecoration.parseCLI("false");
|
||||
try testing.expectEqual(WindowDecoration.none, v);
|
||||
}
|
||||
{
|
||||
const v = try WindowDecoration.parseCLI("server");
|
||||
try testing.expectEqual(WindowDecoration.server, v);
|
||||
}
|
||||
{
|
||||
try testing.expectError(error.InvalidValue, WindowDecoration.parseCLI(""));
|
||||
try testing.expectError(error.InvalidValue, WindowDecoration.parseCLI("aaaa"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// See theme
|
||||
pub const Theme = struct {
|
||||
light: []const u8,
|
||||
|
Reference in New Issue
Block a user