diff options
Diffstat (limited to 'src/tty')
| -rw-r--r-- | src/tty/tty.zig | 52 |
1 files changed, 52 insertions, 0 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig index 22449719..91213d9f 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -15,6 +15,7 @@ const temp_file = @import("../temp_file.zig"); const shell_bin = @import("../shell_bin.zig"); const message = @import("../message.zig"); const selection_pipe = @import("../selection_pipe.zig"); +const nested = @import("../nested.zig"); extern "c" fn forkpty(amaster: *c_int, name: ?[*:0]u8, termp: ?*const anyopaque, winp: ?*const posix.winsize) c_int; extern "c" fn execv(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int; @@ -42,6 +43,9 @@ pub const Command = struct { pipe_done: selection_pipe.Response, /// something happened in a watched directory (see watchFiles) files_changed, + /// a pardes launched inside this one sent us a builtin command line + /// (see lookServer); gpa-owned, like pty_read + command: []u8, } = .nop; }; const Loop = vaxis.Loop(@TypeOf(Command.value)); @@ -436,6 +440,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { } while (loop.tryEvent() catch null) |ev| switch (ev) { .pty_read => |pr| gpa.free(pr.bytes), + .command => |line| gpa.free(line), .paste => |b| gpa.free(@constCast(b)), .lsp_done => |d| gpa.free(d.rows), .pipe_done => |response_value| { @@ -446,6 +451,22 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { }; } + // The socket a pardes launched inside this one connects to (nested.zig). + // Declared AFTER the drain above so its teardown runs BEFORE it — the + // listener thread must be out of the way before the queue is emptied. + // --nested opted out of the whole mechanism, including being an outer + // instance; so does any failure to bind, and then children simply open + // their own session. + var sock_buf: [108]u8 = undefined; + const sock_path: ?[:0]const u8 = if (opts.nested) null else nested.socketPath(&sock_buf, libc.getpid()); + const sock_fd: c_int = if (sock_path) |sp| nested.listenAt(sp) else -1; + // only on the fd, so a bind that FAILED cannot unlink a path this process + // never created + defer if (sock_fd >= 0) { + _ = libc.close(sock_fd); + _ = libc.unlink(sock_path.?); + }; + // Perform the initial spawns BEFORE any worker thread exists: forkpty from // a multithreaded process can wedge the child before exec. drainEffects(core, &ptys, &gens, io, gpa, &loop, &vx, &tty, &lsp_task, &pipe_tasks, inotify_fd, &watches, false); @@ -455,6 +476,10 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { // resize watcher: plain detached thread (not io.concurrent — teardown // joins those, and sigwait never returns); dies with the process (try std.Thread.spawn(.{}, winchWatch, .{ &loop, &vx, &tty })).detach(); + // ...and the nested-instance listener, detached for the same reason: a + // blocking accept(2) never returns either, so an io.concurrent task would + // hang the teardown that joins it. + if (sock_fd >= 0) (try std.Thread.spawn(.{}, lookServer, .{ gpa, sock_fd, &loop })).detach(); // Capability handshake — SEND the probes, do not wait on them. This was // queryTerminal(2ms), which blocks on a futex until DA1 comes back. The // number has to beat one terminal round trip: a local terminal answers in @@ -593,6 +618,10 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { core.update(.{ .paste = bytes }); gpa.free(@constCast(bytes)); }, + .command => |line| { + core.update(.{ .command = line }); + gpa.free(line); + }, // Coalesced on purpose: a burst of writes (a formatter, a // build, a `git checkout`) collapses into ONE pass below, so // it cannot queue a reload — or an undo entry — per write. @@ -1089,6 +1118,29 @@ fn watchFiles(io: std.Io, fd: c_int, loop: *Loop) anyerror!void { } } +/// Block on the nested-instance socket and hand the loop each command line a +/// pardes started inside this one sends. Same shape as winchWatch: a plain +/// detached thread around a call that never returns, posting into the vaxis +/// loop from ordinary thread context. +/// +/// Nothing here is woken by teardown — close(2) does NOT release a thread +/// parked in accept4 on linux — so this dies with the process, exactly as +/// winchWatch dies inside sigwait. The window that leaves is one connection +/// accepted between the last drain and process exit posting into a queue whose +/// owner has returned; same shape and same bound as every other detached +/// worker here, and a self-pipe to close it would be more machinery than the +/// window is worth. +fn lookServer(gpa: std.mem.Allocator, fd: c_int, loop: *Loop) void { + var buf: [nested.max_line]u8 = undefined; + while (nested.acceptLine(fd, &buf)) |line| { + const owned = gpa.dupe(u8, line) catch continue; + loop.postEvent(.{ .command = owned }) catch { + gpa.free(owned); + break; + }; + } +} + /// Consume SIGWINCH synchronously (it is blocked in every thread) and post /// the new size as a winsize event from normal thread context — the one place /// vaxis's Io-backed queue is safe to touch on a resize. |
