diff --git a/include/ghostty.h b/include/ghostty.h index 00c43b532..96f7de272 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -243,6 +243,7 @@ int ghostty_init(void); ghostty_config_t ghostty_config_new(); void ghostty_config_free(ghostty_config_t); +void ghostty_config_load_cli_args(ghostty_config_t); 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); diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 2307aa082..ab2fa605d 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -12,12 +12,25 @@ extension Ghostty { /// The readiness value of the state. @Published var readiness: AppReadiness = .loading - /// The ghostty global configuration. - var config: ghostty_config_t? = nil + /// The ghostty global configuration. This should only be changed when it is definitely + /// safe to change. It is definite safe to change only when the embedded app runtime + /// in Ghostty says so (usually, only in a reload configuration callback). + var config: ghostty_config_t? = nil { + didSet { + // Free the old value whenever we change + guard let old = oldValue else { return } + ghostty_config_free(old) + } + } /// The ghostty app instance. We only have one of these for the entire app, although I guess /// in theory you can have multiple... I don't know why you would... - var app: ghostty_app_t? = nil + var app: ghostty_app_t? = nil { + didSet { + guard let old = oldValue else { return } + ghostty_app_free(old) + } + } /// Cached clipboard string for `read_clipboard` callback. private var cached_clipboard_string: String? = nil @@ -31,24 +44,12 @@ extension Ghostty { } // Initialize the global configuration. - guard let cfg = ghostty_config_new() else { - GhosttyApp.logger.critical("ghostty_config_new failed") + guard let cfg = Self.reloadConfig() else { readiness = .error return } self.config = cfg; - // Load our configuration files from the home directory. - ghostty_config_load_default_files(cfg); - ghostty_config_load_recursive_files(cfg); - - // TODO: we'd probably do some config loading here... for now we'd - // have to do this synchronously. When we support config updating we can do - // this async and update later. - - // Finalize will make our defaults available. - ghostty_config_finalize(cfg) - // Create our "runtime" config. The "runtime" is the configuration that ghostty // uses to interface with the application runtime environment. var runtime_cfg = ghostty_runtime_config_s( @@ -75,8 +76,32 @@ extension Ghostty { } deinit { - ghostty_app_free(app) - ghostty_config_free(config) + // This will force the didSet callbacks to run which free. + self.app = nil + self.config = nil + } + + /// Initializes a new configuration and loads all the values. + static func reloadConfig() -> ghostty_config_t? { + // Initialize the global configuration. + guard let cfg = ghostty_config_new() else { + GhosttyApp.logger.critical("ghostty_config_new failed") + return nil + } + + // Load our configuration files from the home directory. + ghostty_config_load_default_files(cfg); + ghostty_config_load_cli_args(cfg); + ghostty_config_load_recursive_files(cfg); + + // TODO: we'd probably do some config loading here... for now we'd + // have to do this synchronously. When we support config updating we can do + // this async and update later. + + // Finalize will make our defaults available. + ghostty_config_finalize(cfg) + + return cfg } func appTick() { @@ -142,10 +167,17 @@ extension Ghostty { } static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { - // TODO: implement config reloading in the mac app + guard let newConfig = AppState.reloadConfig() else { + GhosttyApp.logger.warning("failed to reload configuration") + return nil + } + + // Assign the new config. This will automatically free the old config. + // It is safe to free the old config from within this function call. let state = Unmanaged.fromOpaque(userdata!).takeUnretainedValue() - _ = state - return nil + state.config = newConfig + + return newConfig } static func wakeup(_ userdata: UnsafeMutableRawPointer?) { diff --git a/src/config.zig b/src/config.zig index c30355f55..3f9e6edee 100644 --- a/src/config.zig +++ b/src/config.zig @@ -217,11 +217,7 @@ pub const Config = struct { try result.loadDefaultFiles(alloc_gpa); // Parse the config from the CLI args - { - var iter = try std.process.argsWithAllocator(alloc_gpa); - defer iter.deinit(); - try cli_args.parse(Config, alloc_gpa, &result, &iter); - } + try result.loadCliArgs(alloc_gpa); // Parse the config files that were added from our file and CLI args. try result.loadRecursiveFiles(alloc_gpa); @@ -564,6 +560,14 @@ pub const Config = struct { } } + /// Load and parse the CLI args. + pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { + // Parse the config from the CLI args + var iter = try std.process.argsWithAllocator(alloc_gpa); + defer iter.deinit(); + try cli_args.parse(Config, alloc_gpa, self, &iter); + } + /// Load and parse the config files that were added in the "config-file" key. pub fn loadRecursiveFiles(self: *Config, alloc: Allocator) !void { // TODO(mitchellh): we should parse the files form the homedir first @@ -1190,6 +1194,13 @@ pub const CAPI = struct { } } + /// 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(