From 5a02bfc173c01111849c41b6efc924e31f6b4767 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 1 Jul 2025 12:38:04 -0500 Subject: [PATCH] core: handle SIGUSR2 as a signal to reload the config This PR adds the infrastructure to handle POSIX signals. The only signal currently handled is SIGUSR2, which will reload the config. SIGPIPE is set up to be ignored in the global state and should not be changed as macOS relies on SIGPIPE being ignored for proper operation. Note that any signal not configured by Ghostty will generally result in termination of the process. Fixes #7747 --- src/App.zig | 12 ++++++++++++ src/Signals.zig | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/Signals.zig 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 => {}, + } +}