From d51e1cd8d7344738ca04578beb8731ab72235a07 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 13:08:29 -0300 Subject: A tty editor killed with SIGTERM or SIGHUP puts its terminal back before it dies A nested pardes killed in a pane died with the default action. That left the pane's terminal on the alternate screen, in raw mode, with mouse and paste reporting on, so the shell under it was unusable until a reset. The tty frontend now handles both signals: it writes the resets a clean exit writes (keyboard protocol, mouse modes, focus, bracketed paste, SGR, cursor, main screen) and restores the cooked termios, all async-signal-safe. Then it dies of the same signal, since SA_RESETHAND put the default back. fs.py kills a tty session with each signal and checks for the main-screen switch and cooked mode; it fails without the handler. Co-Authored-By: Claude Opus 5.5 --- src/tty/tty.zig | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'src/tty/tty.zig') diff --git a/src/tty/tty.zig b/src/tty/tty.zig index ef7f2761..1d7ff415 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -1083,6 +1083,8 @@ pub fn run(init: std.process.Init, opts: pardes.Options, attach: ?[]const u8) !v defer vx.exitAltScreen(tty.writer()) catch {}; try vx.setMouseMode(tty.writer(), true); try vx.setBracketedPaste(tty.writer(), true); + Killed.arm(tty.fd.handle, tty.termios); + defer Killed.disarm(); if (attach != null) { attach_end = attachSession(init, attach_name, &tty, &vx); @@ -1119,6 +1121,40 @@ pub fn run(init: std.process.Init, opts: pardes.Options, attach: ?[]const u8) !v } } +/// SIGTERM or SIGHUP: the terminal put back as a clean exit leaves it -- +/// the main screen, no mouse, paste or keyboard modes, the cursor shown, +/// cooked termios -- and then the signal's own death. A nested editor +/// killed in a pane must not leave that pane's terminal wedged. +/// Async-signal-safe: write, tcsetattr, sigaction, raise. +const Killed = struct { + var fd: posix.fd_t = -1; + var cooked: posix.termios = undefined; + const reset = "\x1b[= 0) { + _ = std.c.write(fd, reset, reset.len); + _ = std.c.tcsetattr(fd, .FLUSH, &cooked); + } + // SA_RESETHAND put the default back: the same signal, now fatal. + _ = std.c.raise(sig); + } +}; + fn localSession( init: std.process.Init, opts: pardes.Options, -- cgit v1.3