From 3e1f975551c2258b82dbc4bb747fef3a5d5d1910 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Mar 2023 21:44:45 -0700 Subject: [PATCH] move config loading into apprt to prep for reloading --- src/App.zig | 35 +++++++++++++++++++++++++++-------- src/Surface.zig | 20 ++++++++++++++++++++ src/apprt/glfw.zig | 15 +++++++++++---- src/apprt/gtk.zig | 13 +++++++++++-- src/apprt/surface.zig | 6 ++++++ src/main.zig | 9 +-------- 6 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/App.zig b/src/App.zig index f06fe1443..e9358b3a1 100644 --- a/src/App.zig +++ b/src/App.zig @@ -30,11 +30,6 @@ alloc: Allocator, /// The list of surfaces that are currently active. surfaces: SurfaceList, -// The configuration for the app. This may change (app runtimes are notified -// via the callback), but the change will only ever happen during tick() -// so app runtimes can ensure there are no data races in reading this. -config: *const Config, - /// The mailbox that can be used to send this thread messages. Note /// this is a blocking queue so if it is full you will get errors (or block). mailbox: Mailbox.Queue, @@ -47,17 +42,15 @@ quit: bool, /// "startup" logic. pub fn create( alloc: Allocator, - config: *const Config, ) !*App { // If we have DevMode on, store the config so we can show it - if (DevMode.enabled) DevMode.instance.config = config; + //if (DevMode.enabled) DevMode.instance.config = config; var app = try alloc.create(App); errdefer alloc.destroy(app); app.* = .{ .alloc = alloc, .surfaces = .{}, - .config = config, .mailbox = .{}, .quit = false, }; @@ -99,6 +92,21 @@ pub fn tick(self: *App, rt_app: *apprt.App) !bool { return self.quit or self.surfaces.items.len == 0; } +/// Update the configuration associated with the app. This can only be +/// called from the main thread. +/// +/// The caller owns the config memory. The prior config must not be freed +/// until this function returns successfully. +pub fn updateConfig(self: *App, config: *const Config) !void { + // Update our config + self.config = config; + + // Go through and update all of the surface configurations. + for (self.surfaces.items) |surface| { + try surface.handleMessage(.{ .change_config = config }); + } +} + /// Add an initialized surface. This is really only for the runtime /// implementations to call and should NOT be called by general app users. /// The surface must be from the pool. @@ -125,6 +133,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void { while (self.mailbox.pop()) |message| { log.debug("mailbox message={s}", .{@tagName(message)}); switch (message) { + .reload_config => try self.reloadConfig(rt_app), .new_window => |msg| try self.newWindow(rt_app, msg), .close => |surface| try self.closeSurface(rt_app, surface), .quit => try self.setQuit(), @@ -134,6 +143,12 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void { } } +fn reloadConfig(self: *App, rt_app: *apprt.App) !void { + _ = rt_app; + _ = self; + //try rt_app.reloadConfig(); +} + fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void { if (!self.hasSurface(surface)) return; rt_app.closeSurface(surface.rt_surface); @@ -195,6 +210,10 @@ fn hasSurface(self: *App, surface: *Surface) bool { /// The message types that can be sent to the app thread. pub const Message = union(enum) { + /// Reload the configuration for the entire app and propagate it to + /// all the active surfaces. + reload_config: void, + /// Create a new terminal window. new_window: NewWindow, diff --git a/src/Surface.zig b/src/Surface.zig index 806529af6..b34eabaa2 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -528,6 +528,8 @@ pub fn close(self: *Surface) void { /// surface. pub fn handleMessage(self: *Surface, msg: Message) !void { switch (msg) { + .change_config => |config| try self.changeConfig(config), + .set_title => |*v| { // The ptrCast just gets sliceTo to return the proper type. // We know that our title should end in 0. @@ -553,6 +555,24 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { } } +/// Update our configuration at runtime. +fn changeConfig(self: *Surface, config: *const configpkg.Config) !void { + // Update our new derived config immediately + const derived = DerivedConfig.init(self.alloc, config) catch |err| { + // If the derivation fails then we just log and return. We don't + // hard fail in this case because we don't want to error the surface + // when config fails we just want to keep using the old config. + log.err("error updating configuration err={}", .{err}); + return; + }; + self.config.deinit(); + self.config = derived; + + // Update our derived configurations for the renderer and termio, + // then send them a message to update. + // TODO +} + /// Returns the x/y coordinate of where the IME (Input Method Editor) /// keyboard should be rendered. pub fn imePoint(self: *const Surface) apprt.IMEPos { diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 1995f03cd..c86031602 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -19,6 +19,7 @@ const Renderer = renderer.Renderer; const apprt = @import("../apprt.zig"); const CoreApp = @import("../App.zig"); const CoreSurface = @import("../Surface.zig"); +const Config = @import("../config.zig").Config; // Get native API access on certain platforms so we can do more customization. const glfwNative = glfw.Native(.{ @@ -29,6 +30,7 @@ const log = std.log.scoped(.glfw); pub const App = struct { app: *CoreApp, + config: Config, /// Mac-specific state. darwin: if (Darwin.enabled) Darwin else void, @@ -53,14 +55,19 @@ pub const App = struct { var darwin = if (Darwin.enabled) try Darwin.init() else {}; errdefer if (Darwin.enabled) darwin.deinit(); + // Load our configuration + var config = try Config.load(core_app.alloc); + errdefer config.deinit(); + return .{ .app = core_app, + .config = config, .darwin = darwin, }; } - pub fn terminate(self: App) void { - _ = self; + pub fn terminate(self: *App) void { + self.config.deinit(); glfw.terminate(); } @@ -139,7 +146,7 @@ pub const App = struct { errdefer surface.deinit(); // If we have a parent, inherit some properties - if (self.app.config.@"window-inherit-font-size") { + if (self.config.@"window-inherit-font-size") { if (parent_) |parent| { surface.core_surface.setFontSize(parent.font_size); } @@ -313,7 +320,7 @@ pub const Surface = struct { // Initialize our surface now that we have the stable pointer. try self.core_surface.init( app.app.alloc, - app.app.config, + &app.config, .{ .rt_app = app, .mailbox = &app.app.mailbox }, self, ); diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 218866a33..c6556e49d 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -9,6 +9,7 @@ const apprt = @import("../apprt.zig"); const input = @import("../input.zig"); const CoreApp = @import("../App.zig"); const CoreSurface = @import("../Surface.zig"); +const Config = @import("../config.zig").Config; pub const c = @cImport({ @cInclude("gtk/gtk.h"); @@ -37,6 +38,7 @@ pub const App = struct { }; core_app: *CoreApp, + config: Config, app: *c.GtkApplication, ctx: *c.GMainContext, @@ -53,6 +55,10 @@ pub const App = struct { // rid of this dep. if (!glfw.init(.{})) return error.GlfwInitFailed; + // Load our configuration + var config = try Config.load(core_app.alloc); + errdefer config.deinit(); + // Create our GTK Application which encapsulates our process. const app = @ptrCast(?*c.GtkApplication, c.gtk_application_new( null, @@ -108,6 +114,7 @@ pub const App = struct { return .{ .core_app = core_app, .app = app, + .config = config, .ctx = ctx, .cursor_default = cursor_default, .cursor_ibeam = cursor_ibeam, @@ -116,7 +123,7 @@ pub const App = struct { // Terminate the application. The application will not be restarted after // this so all global state can be cleaned up. - pub fn terminate(self: App) void { + pub fn terminate(self: *App) void { c.g_settings_sync(); while (c.g_main_context_iteration(self.ctx, 0) != 0) {} c.g_main_context_release(self.ctx); @@ -125,6 +132,8 @@ pub const App = struct { c.g_object_unref(self.cursor_ibeam); c.g_object_unref(self.cursor_default); + self.config.deinit(); + glfw.terminate(); } @@ -575,7 +584,7 @@ pub const Surface = struct { // Initialize our surface now that we have the stable pointer. try self.core_surface.init( self.app.core_app.alloc, - self.app.core_app.config, + &self.app.config, .{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox }, self, ); diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index 3f409efb7..d5ebc0083 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -2,6 +2,7 @@ const App = @import("../App.zig"); const Surface = @import("../Surface.zig"); const renderer = @import("../renderer.zig"); const termio = @import("../termio.zig"); +const Config = @import("../config.zig").Config; /// The message types that can be sent to a single surface. pub const Message = union(enum) { @@ -24,6 +25,11 @@ pub const Message = union(enum) { /// Write the clipboard contents. clipboard_write: WriteReq, + /// Change the configuration to the given configuration. The pointer is + /// not valid after receiving this message so any config must be used + /// and derived immediately. + change_config: *const Config, + /// Close the surface. This will only close the current surface that /// receives this, not the full application. close: void, diff --git a/src/main.zig b/src/main.zig index 899f0494a..9600a4a66 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,8 +14,6 @@ const xdg = @import("xdg.zig"); const apprt = @import("apprt.zig"); const App = @import("App.zig"); -const cli_args = @import("cli_args.zig"); -const Config = @import("config.zig").Config; const Ghostty = @import("main_c.zig").Ghostty; /// Global process state. This is initialized in main() for exe artifacts @@ -29,13 +27,8 @@ pub fn main() !void { defer state.deinit(); const alloc = state.alloc; - // Try reading our config - var config = try Config.load(alloc); - defer config.deinit(); - //std.log.debug("config={}", .{config}); - // Create our app state - var app = try App.create(alloc, &config); + var app = try App.create(alloc); defer app.destroy(); // Create our runtime app