diff --git a/src/App.zig b/src/App.zig index 02089ae5b..edd7b4863 100644 --- a/src/App.zig +++ b/src/App.zig @@ -20,6 +20,7 @@ const font = @import("font/main.zig"); const internal_os = @import("os/main.zig"); const macos = @import("macos"); const objc = @import("objc"); +const Signals = @import("Signals.zig"); const log = std.log.scoped(.app); @@ -74,6 +75,9 @@ config_conditional_state: configpkg.ConditionalState, /// if they are the first surface. first: bool = true, +/// Structure for managing POSIX signal handlers. +signals: Signals = .init, + pub const CreateError = Allocator.Error || font.SharedGridSet.InitError; /// Create a new app instance. This returns a stable pointer to the app @@ -104,7 +108,11 @@ pub fn init( .mailbox = .{}, .font_grid_set = font_grid_set, .config_conditional_state = .{}, + .signals = .init, }; + + // Start handling POSIX signals (this is a no-op on non-POSIX systems). + self.signals.start(self); } pub fn deinit(self: *App) void { @@ -257,6 +265,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void { log.debug("mailbox message={s}", .{@tagName(message)}); switch (message) { .open_config => try self.performAction(rt_app, .open_config), + .reload_config => try self.performAction(rt_app, .reload_config), .new_window => |msg| try self.newWindow(rt_app, msg), .close => |surface| self.closeSurface(surface), .surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message), @@ -518,6 +527,9 @@ pub const Message = union(enum) { // Open the configuration file open_config: void, + // Reload the configuration file + reload_config: void, + /// Create a new terminal window. new_window: NewWindow, diff --git a/src/Signals.zig b/src/Signals.zig new file mode 100644 index 000000000..c98b35bec --- /dev/null +++ b/src/Signals.zig @@ -0,0 +1,52 @@ +//! Structure for managing POSIX signal handlers. +const Signals = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const posix = std.posix; + +const App = @import("./App.zig"); + +const log = std.log.scoped(.signals); + +/// Global variable to store core app. This needs to be +/// a global since it needs to be accessed from a POSIX +/// signal handler. +var _app: ?*App = null; + +pub const init: Signals = .{}; + +// Start handling POSIX signals. This _should not_ modify the handler for +// SIGPIPE as that's handled in the global state initialization. +pub fn start(_: *Signals, app: *App) void { + // Only posix systems. + if (comptime builtin.os.tag == .windows) return; + + _app = app; + + var sa: posix.Sigaction = .{ + .handler = .{ .handler = handler }, + .mask = posix.empty_sigset, + .flags = 0, + }; + + // SIGUSR2 => reload config + posix.sigaction(posix.SIG.USR2, &sa, null); +} + +/// POSIX signal handler. This must follow all the rules for POSIX signal +/// handlers. In general it's best to send a message that's handled by other +/// threads. +fn handler(signal: c_int) callconv(.c) void { + // Failsafe in case we get called on a non-POSIX system. + const app = _app orelse return; + + log.info("POSIX signal received: {d}", .{signal}); + + switch (signal) { + posix.SIG.USR2 => { + _ = app.mailbox.push(.reload_config, .instant); + }, + else => {}, + } +}