mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #425 from mitchellh/split-config
Configurable unfocused split opacity, C API for reading Config
This commit is contained in:
@ -301,6 +301,7 @@ void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
|
||||
void ghostty_config_load_default_files(ghostty_config_t);
|
||||
void ghostty_config_load_recursive_files(ghostty_config_t);
|
||||
void ghostty_config_finalize(ghostty_config_t);
|
||||
bool ghostty_config_get(ghostty_config_t, void *, const char *, uintptr_t);
|
||||
ghostty_input_trigger_s ghostty_config_trigger(ghostty_config_t, const char *, uintptr_t);
|
||||
|
||||
ghostty_app_t ghostty_app_new(const ghostty_runtime_config_s *, ghostty_config_t);
|
||||
|
@ -96,6 +96,7 @@ struct PrimaryView: View {
|
||||
|
||||
Ghostty.TerminalSplit(onClose: Self.closeWindow, baseConfig: self.baseConfig)
|
||||
.ghosttyApp(ghostty.app!)
|
||||
.ghosttyConfig(ghostty.config!)
|
||||
.background(WindowAccessor(window: $window))
|
||||
.onReceive(gotoTab) { onGotoTab(notification: $0) }
|
||||
.onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) }
|
||||
|
@ -350,15 +350,28 @@ private struct GhosttyAppKey: EnvironmentKey {
|
||||
static let defaultValue: ghostty_app_t? = nil
|
||||
}
|
||||
|
||||
private struct GhosttyConfigKey: EnvironmentKey {
|
||||
static let defaultValue: ghostty_config_t? = nil
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var ghosttyApp: ghostty_app_t? {
|
||||
get { self[GhosttyAppKey.self] }
|
||||
set { self[GhosttyAppKey.self] = newValue }
|
||||
}
|
||||
|
||||
var ghosttyConfig: ghostty_config_t? {
|
||||
get { self[GhosttyConfigKey.self] }
|
||||
set { self[GhosttyConfigKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func ghosttyApp(_ app: ghostty_app_t?) -> some View {
|
||||
environment(\.ghosttyApp, app)
|
||||
}
|
||||
|
||||
func ghosttyConfig(_ config: ghostty_config_t?) -> some View {
|
||||
environment(\.ghosttyConfig, config)
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +48,20 @@ extension Ghostty {
|
||||
// Maintain whether our window has focus (is key) or not
|
||||
@State private var windowFocus: Bool = true
|
||||
|
||||
@Environment(\.ghosttyConfig) private var ghostty_config
|
||||
|
||||
// This is true if the terminal is considered "focused". The terminal is focused if
|
||||
// it is both individually focused and the containing window is key.
|
||||
private var hasFocus: Bool { surfaceFocus && windowFocus }
|
||||
|
||||
// The opacity of the rectangle when unfocused.
|
||||
private var unfocusedOpacity: Double {
|
||||
var opacity: Double = 0.85
|
||||
let key = "unfocused-split-opacity"
|
||||
_ = ghostty_config_get(ghostty_config, &opacity, key, UInt(key.count))
|
||||
return 1 - opacity
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// We use a GeometryReader to get the frame bounds so that our metal surface
|
||||
@ -129,7 +139,8 @@ extension Ghostty {
|
||||
if (isSplit && !surfaceFocus) {
|
||||
Rectangle()
|
||||
.fill(.white)
|
||||
.opacity(0.15)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(unfocusedOpacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1633
src/config.zig
1633
src/config.zig
File diff suppressed because it is too large
Load Diff
110
src/config/CAPI.zig
Normal file
110
src/config/CAPI.zig
Normal file
@ -0,0 +1,110 @@
|
||||
const std = @import("std");
|
||||
const cli_args = @import("../cli_args.zig");
|
||||
const inputpkg = @import("../input.zig");
|
||||
const global = &@import("../main.zig").state;
|
||||
|
||||
const Config = @import("Config.zig");
|
||||
const c_get = @import("c_get.zig");
|
||||
const Key = @import("key.zig").Key;
|
||||
|
||||
const log = std.log.scoped(.config);
|
||||
|
||||
/// Create a new configuration filled with the initial default values.
|
||||
export fn ghostty_config_new() ?*Config {
|
||||
const result = global.alloc.create(Config) catch |err| {
|
||||
log.err("error allocating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
result.* = Config.default(global.alloc) catch |err| {
|
||||
log.err("error creating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export fn ghostty_config_free(ptr: ?*Config) void {
|
||||
if (ptr) |v| {
|
||||
v.deinit();
|
||||
global.alloc.destroy(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the configuration from the CLI args.
|
||||
export fn ghostty_config_load_cli_args(self: *Config) void {
|
||||
self.loadCliArgs(global.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Load the configuration from a string in the same format as
|
||||
/// the file-based syntax for the desktop version of the terminal.
|
||||
export fn ghostty_config_load_string(
|
||||
self: *Config,
|
||||
str: [*]const u8,
|
||||
len: usize,
|
||||
) void {
|
||||
config_load_string_(self, str[0..len]) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn config_load_string_(self: *Config, str: []const u8) !void {
|
||||
var fbs = std.io.fixedBufferStream(str);
|
||||
var iter = cli_args.lineIterator(fbs.reader());
|
||||
try cli_args.parse(Config, global.alloc, self, &iter);
|
||||
}
|
||||
|
||||
/// Load the configuration from the default file locations. This
|
||||
/// is usually done first. The default file locations are locations
|
||||
/// such as the home directory.
|
||||
export fn ghostty_config_load_default_files(self: *Config) void {
|
||||
self.loadDefaultFiles(global.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Load the configuration from the user-specified configuration
|
||||
/// file locations in the previously loaded configuration. This will
|
||||
/// recursively continue to load up to a built-in limit.
|
||||
export fn ghostty_config_load_recursive_files(self: *Config) void {
|
||||
self.loadRecursiveFiles(global.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
export fn ghostty_config_finalize(self: *Config) void {
|
||||
self.finalize() catch |err| {
|
||||
log.err("error finalizing config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
export fn ghostty_config_get(
|
||||
self: *Config,
|
||||
ptr: *anyopaque,
|
||||
key_str: [*]const u8,
|
||||
len: usize,
|
||||
) bool {
|
||||
const key = std.meta.stringToEnum(Key, key_str[0..len]) orelse return false;
|
||||
return c_get.get(self, key, ptr);
|
||||
}
|
||||
|
||||
export fn ghostty_config_trigger(
|
||||
self: *Config,
|
||||
str: [*]const u8,
|
||||
len: usize,
|
||||
) inputpkg.Binding.Trigger {
|
||||
return config_trigger_(self, str[0..len]) catch |err| err: {
|
||||
log.err("error finding trigger err={}", .{err});
|
||||
break :err .{};
|
||||
};
|
||||
}
|
||||
|
||||
fn config_trigger_(
|
||||
self: *Config,
|
||||
str: []const u8,
|
||||
) !inputpkg.Binding.Trigger {
|
||||
const action = try inputpkg.Binding.Action.parse(str);
|
||||
return self.keybind.set.getTrigger(action) orelse .{};
|
||||
}
|
1463
src/config/Config.zig
Normal file
1463
src/config/Config.zig
Normal file
File diff suppressed because it is too large
Load Diff
54
src/config/Wasm.zig
Normal file
54
src/config/Wasm.zig
Normal file
@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
const wasm = @import("../os/wasm.zig");
|
||||
const cli_args = @import("../cli_args.zig");
|
||||
const alloc = wasm.alloc;
|
||||
|
||||
const Config = @import("Config.zig");
|
||||
|
||||
const log = std.log.scoped(.config);
|
||||
|
||||
/// Create a new configuration filled with the initial default values.
|
||||
export fn config_new() ?*Config {
|
||||
const result = alloc.create(Config) catch |err| {
|
||||
log.err("error allocating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
result.* = Config.default(alloc) catch |err| {
|
||||
log.err("error creating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export fn config_free(ptr: ?*Config) void {
|
||||
if (ptr) |v| {
|
||||
v.deinit();
|
||||
alloc.destroy(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the configuration from a string in the same format as
|
||||
/// the file-based syntax for the desktop version of the terminal.
|
||||
export fn config_load_string(
|
||||
self: *Config,
|
||||
str: [*]const u8,
|
||||
len: usize,
|
||||
) void {
|
||||
config_load_string_(self, str[0..len]) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn config_load_string_(self: *Config, str: []const u8) !void {
|
||||
var fbs = std.io.fixedBufferStream(str);
|
||||
var iter = cli_args.lineIterator(fbs.reader());
|
||||
try cli_args.parse(Config, alloc, self, &iter);
|
||||
}
|
||||
|
||||
export fn config_finalize(self: *Config) void {
|
||||
self.finalize() catch |err| {
|
||||
log.err("error finalizing config err={}", .{err});
|
||||
};
|
||||
}
|
76
src/config/c_get.zig
Normal file
76
src/config/c_get.zig
Normal file
@ -0,0 +1,76 @@
|
||||
const std = @import("std");
|
||||
|
||||
const key = @import("key.zig");
|
||||
const Config = @import("Config.zig");
|
||||
const Key = key.Key;
|
||||
const Value = key.Value;
|
||||
|
||||
/// Get a value from the config by key into the given pointer. This is
|
||||
/// specifically for C-compatible APIs. If you're using Zig, just access
|
||||
/// the configuration directly.
|
||||
///
|
||||
/// The return value is false if the given key is not supported by the
|
||||
/// C API yet. This is a fixable problem so if it is important to support
|
||||
/// some key, please open an issue.
|
||||
pub fn get(config: *const Config, k: Key, ptr_raw: *anyopaque) bool {
|
||||
@setEvalBranchQuota(10_000);
|
||||
switch (k) {
|
||||
inline else => |tag| {
|
||||
const value = fieldByKey(config, tag);
|
||||
switch (@TypeOf(value)) {
|
||||
?[:0]const u8 => {
|
||||
const ptr: *[*c]const u8 = @ptrCast(@alignCast(ptr_raw));
|
||||
ptr.* = if (value) |slice| @ptrCast(slice.ptr) else null;
|
||||
},
|
||||
|
||||
bool => {
|
||||
const ptr: *bool = @ptrCast(@alignCast(ptr_raw));
|
||||
ptr.* = value;
|
||||
},
|
||||
|
||||
u8, u32 => {
|
||||
const ptr: *c_uint = @ptrCast(@alignCast(ptr_raw));
|
||||
ptr.* = @intCast(value);
|
||||
},
|
||||
|
||||
f32, f64 => {
|
||||
const ptr: *f64 = @ptrCast(@alignCast(ptr_raw));
|
||||
ptr.* = @floatCast(value);
|
||||
},
|
||||
|
||||
else => return false,
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a value from the config by key.
|
||||
fn fieldByKey(self: *const Config, comptime k: Key) Value(k) {
|
||||
const field = comptime field: {
|
||||
const fields = std.meta.fields(Config);
|
||||
for (fields) |field| {
|
||||
if (@field(Key, field.name) == k) {
|
||||
break :field field;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable;
|
||||
};
|
||||
|
||||
return @field(self, field.name);
|
||||
}
|
||||
|
||||
test "u8" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var c = try Config.default(alloc);
|
||||
defer c.deinit();
|
||||
c.@"font-size" = 24;
|
||||
|
||||
var cval: c_uint = undefined;
|
||||
try testing.expect(get(&c, .@"font-size", &cval));
|
||||
try testing.expectEqual(@as(c_uint, 24), cval);
|
||||
}
|
55
src/config/key.zig
Normal file
55
src/config/key.zig
Normal file
@ -0,0 +1,55 @@
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
|
||||
/// Key is an enum of all the available configuration keys. This is used
|
||||
/// when paired with diff to determine what fields have changed in a config,
|
||||
/// amongst other things.
|
||||
pub const Key = key: {
|
||||
const field_infos = std.meta.fields(Config);
|
||||
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
||||
var i: usize = 0;
|
||||
inline for (field_infos) |field| {
|
||||
// Ignore fields starting with "_" since they're internal and
|
||||
// not copied ever.
|
||||
if (field.name[0] == '_') continue;
|
||||
|
||||
enumFields[i] = .{
|
||||
.name = field.name,
|
||||
.value = i,
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
|
||||
var decls = [_]std.builtin.Type.Declaration{};
|
||||
break :key @Type(.{
|
||||
.Enum = .{
|
||||
.tag_type = std.math.IntFittingRange(0, field_infos.len - 1),
|
||||
.fields = enumFields[0..i],
|
||||
.decls = &decls,
|
||||
.is_exhaustive = true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/// Returns the value type for a key
|
||||
pub fn Value(comptime key: Key) type {
|
||||
const field = comptime field: {
|
||||
const fields = std.meta.fields(Config);
|
||||
for (fields) |field| {
|
||||
if (@field(Key, field.name) == key) {
|
||||
break :field field;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable;
|
||||
};
|
||||
|
||||
return field.type;
|
||||
}
|
||||
|
||||
test "Value" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expectEqual(?[:0]const u8, Value(.@"font-family"));
|
||||
try testing.expectEqual(bool, Value(.@"cursor-style-blink"));
|
||||
}
|
Reference in New Issue
Block a user