summaryrefslogtreecommitdiff
path: root/src/tty
diff options
context:
space:
mode:
Diffstat (limited to 'src/tty')
-rw-r--r--src/tty/tty.zig52
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.