Merge branch 'main' into fix-triple-clicking-drag

This commit is contained in:
otomist
2025-01-14 14:00:25 -05:00
11 changed files with 294 additions and 76 deletions

View File

@ -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
}
}
}
}

View File

@ -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();

View File

@ -1384,12 +1384,10 @@ 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| {
window.winproto.resizeEvent() catch |err| {
log.warn("failed to notify window protocol of resize={}", .{err});
};
}
}
self.resize_overlay.maybeShow();
}

View File

@ -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(&gtkWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
_ = c.g_signal_connect_data(gtk_window, "notify::fullscreened", c.G_CALLBACK(&gtkWindowNotifyFullscreened), 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");
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),
);
}
}
}
// Window protocol specific appearance updates
if (self.winproto) |*v| v.syncAppearance() catch |err| {
log.warn("failed to sync window protocol appearance error={}", .{err});
};
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 {
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.
c.gtk_widget_add_css_class(@ptrCast(object), "ssd");
c.gtk_widget_add_css_class(@ptrCast(object), "no-border-radius");
}
toggleCssClass(@ptrCast(object), "ssd", !is_decorated);
toggleCssClass(@ptrCast(object), "no-border-radius", !is_decorated);
}
fn gtkWindowNotifyFullscreened(

View File

@ -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 {

View File

@ -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(),
};
}
};

View File

@ -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;
}
};

View File

@ -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)));
}
};

View File

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

View File

@ -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);

View File

@ -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,