diff options
Diffstat (limited to 'src/macos.zig')
| -rw-r--r-- | src/macos.zig | 50 |
1 files changed, 48 insertions, 2 deletions
diff --git a/src/macos.zig b/src/macos.zig index 6cd7838f..b472e6cc 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -125,8 +125,30 @@ const Pty = struct { pid: posix.pid_t, gen: u32, reader: std.Io.Future(anyerror!void), + /// A command pane's child, watched to its exit (host_io.watchExit); + /// the pty stays open until both its end of file and its exit. + cmd: host_io.CommandWatch = .{}, }; +/// A watched child exited: wake the host, which takes it (`takeExits`). +/// Called on a watcher thread holding host_io's exit lock, which +/// pardes_deinit takes to clear the wake: safe only because the runtime's +/// wakeup never waits for the main thread (AppDelegate's is a +/// DispatchQueue.main.async); one that did would deadlock the quit. +fn wakeForExit(ctx: ?*anyopaque) void { + const st: *State = @ptrCast(@alignCast(ctx orelse return)); + wake(st); +} + +/// The command panes' exits, told once their output is in (host_io). +fn takeExits(st: *State) void { + host_io.takeExits(st.core, &st.ptys, st, closeWatched); +} + +fn closeWatched(st: *State, id: usize) void { + reap(st, @intCast(id)); +} + const WatchedFile = struct { path: []u8, serial: u32, @@ -782,6 +804,7 @@ fn initCore(runtime: ?*const Runtime, cols_arg: u16, rows_arg: u16) !void { for (&st.ptys, 0..) |*slot, id| if (slot.*) |*pt| startReader(st, pt, @intCast(id)); pardes.lsp.setStatusSink(st, lspStatusSink); + host_io.setExitWake(st, wakeForExit); // From here the core is touched only inside the entry points above, // each of which takes the turn for its own duration; between them the // 9P connections have it. @@ -801,6 +824,7 @@ export fn pardes_deinit() void { st.ninep = null; } pardes.lsp.setStatusSink(null, null); + host_io.setExitWake(null, null); if (st.lsp_task) |*t| { t.future.cancel(st.io) catch {}; st.lsp_task = null; @@ -1268,6 +1292,8 @@ export fn pardes_watch_changed(pane: u8, generation: u32) void { fn drainInbox(st: *State) bool { var batch = st.inbox.take(st.io); + // After this batch's output, which an exit is told behind. + defer takeExits(st); var did = batch.len > 0; for (batch.slice()) |msg| { defer msg.free(st.gpa); @@ -1279,6 +1305,24 @@ fn drainInbox(st: *State) bool { }, .eof => |e| { if (st.gens[e.pane] != e.gen) continue; + if (st.ptys[e.pane]) |*pt| { + // A command's pty stays open until its child has exited: + // closing it would hang up one that runs on without it. + // Its exit, if it came first, is told now its output is in. + if (pt.cmd.watched) { + host_io.commandEof(st.core, &st.ptys, e.pane, st, closeWatched); + continue; + } + // Unwatched, a command's exit is read here, as its end. + if (st.core.panes[e.pane]) |pane| if (pane.command != null) { + const status = host_io.exitStatus(pt.pid, 100); + if (status != null) pt.pid = 0; // reaped: no shell to retire + reap(st, e.pane); + st.core.update(.{ .exited = .{ .pane = e.pane, .status = status } }); + st.core.update(.{ .eof = .{ .pane = e.pane } }); + continue; + }; + } reap(st, e.pane); st.core.update(.{ .eof = .{ .pane = e.pane } }); }, @@ -1344,7 +1388,7 @@ fn restoreCore(st: *State) bool { return false; }; defer st.gpa.free(bytes); - const replacement = dump.restore(st.core, bytes) catch |err| { + const replacement = dump.restore(st.core, bytes, path) catch |err| { st.core.reportError(st.core.active, "Restore", err); return false; }; @@ -2403,6 +2447,7 @@ fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void { .pid = child.pid, .gen = gen, .reader = .{ .any_future = null, .result = {} }, + .cmd = .{ .watched = (if (core.panes[pane]) |pn| pn.command != null else false) and host_io.watchExit(child.pid), .fd = child.file.handle }, }; var lbuf: [1024]u8 = undefined; if (host_io.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(pane, wd); @@ -2423,7 +2468,8 @@ fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void { fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void { const st = hostState(ctx); - if (st.ptys[pane]) |pt| host_io.signalTty(pt.pid, pt.file.handle, sig); + // A command whose exit is recorded is reaped: its group may be another's. + if (st.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig); } fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool { |
