summaryrefslogtreecommitdiff
path: root/src/nested.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 23:43:06 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit0d66575a3c888498c7929e2ec345133628c5d63d (patch)
tree3be80051fd505f9675b36f9b570c46a955ca3445 /src/nested.zig
parent4b5e793f48f6e721c39ab6ddcf755604df73b643 (diff)
downloadpardes-0d66575a3c888498c7929e2ec345133628c5d63d.tar.gz
pardes-0d66575a3c888498c7929e2ec345133628c5d63d.zip
fix tab rendering - waybe wreckless
Diffstat (limited to 'src/nested.zig')
-rw-r--r--src/nested.zig75
1 files changed, 63 insertions, 12 deletions
diff --git a/src/nested.zig b/src/nested.zig
index 0c065d1d..20c42dd0 100644
--- a/src/nested.zig
+++ b/src/nested.zig
@@ -59,17 +59,43 @@ pub fn socketPath(buf: *[108]u8, pid: libc.pid_t) ?[:0]const u8 {
return std.fmt.bufPrintSentinel(buf, "{s}/pardes-{d}.sock", .{ dir, @as(u32, @intCast(pid)) }, 0) catch null;
}
-/// A `/proc/<pid>/exe` readlink with the kernel's `" (deleted)"` suffix taken
-/// off. `zig build` replaces the binary under a running pardes — that is the
-/// daily loop in this repo — and from that moment the OUTER instance's exe
-/// link reads `/path/to/pardes (deleted)` while the freshly built child's
-/// reads `/path/to/pardes`. Comparing them raw made every nested launch after
-/// a rebuild open a second full-screen UI inside the pane.
+/// Normalize the kernel suffix left on a running executable after its file is
+/// replaced. `zig build` does this routinely while an outer session is live.
fn stripDeleted(link: []const u8) []const u8 {
const suffix = " (deleted)";
return if (std.mem.endsWith(u8, link, suffix)) link[0 .. link.len - suffix.len] else link;
}
+/// The tty and SDL builds are sibling frontends of the same program. Their
+/// installed names differ only by `-gui` (and, for cross builds, share the
+/// same `-os-arch` tail), so either one must recognise the other as an outer
+/// pardes. Requiring the same directory retains the executable-identity check:
+/// an unrelated ancestor merely named `pardes` is not enough.
+fn samePardesExecutable(a_raw: []const u8, b_raw: []const u8) bool {
+ const a = stripDeleted(a_raw);
+ const b = stripDeleted(b_raw);
+ if (std.mem.eql(u8, a, b)) return true;
+ const a_dir = std.fs.path.dirname(a) orelse return false;
+ const b_dir = std.fs.path.dirname(b) orelse return false;
+ if (!std.mem.eql(u8, a_dir, b_dir)) return false;
+
+ const a_name = std.fs.path.basename(a);
+ const b_name = std.fs.path.basename(b);
+ const gui = "pardes-gui";
+ const tty = "pardes";
+ const a_gui = std.mem.startsWith(u8, a_name, gui);
+ const b_gui = std.mem.startsWith(u8, b_name, gui);
+ if (a_gui == b_gui) return false;
+ const gui_name = if (a_gui) a_name else b_name;
+ const tty_name = if (a_gui) b_name else a_name;
+ if (!std.mem.startsWith(u8, tty_name, tty)) return false;
+ const gui_tail = gui_name[gui.len..];
+ const tty_tail = tty_name[tty.len..];
+ if ((gui_tail.len != 0 and gui_tail[0] != '-') or
+ (tty_tail.len != 0 and tty_tail[0] != '-')) return false;
+ return std.mem.eql(u8, gui_tail, tty_tail);
+}
+
/// The `PPid:` field of a /proc/<pid>/status blob. Deliberately NOT field 4 of
/// /proc/<pid>/stat: that field is positional after `comm`, and a comm may
/// contain spaces and parentheses — a process named `sh (a b)` shifts every
@@ -94,11 +120,13 @@ fn sweepPid(name: []const u8) ?libc.pid_t {
return std.fmt.parseInt(libc.pid_t, digits, 10) catch null;
}
-/// The pid of the nearest ancestor running THIS executable, or null. Identity
-/// is `readlink("/proc/<pid>/exe")` against our own, not a name: a name match
-/// would call every `vim pardes.zig` an outer pardes. The hop cap is not for
-/// /proc, which cannot loop, but because the walk is driven by numbers read
-/// out of files and should not be able to spin on a surprising one.
+/// The pid of the nearest ancestor running a pardes executable, or null.
+/// Identity is `readlink("/proc/<pid>/exe")` against our own; the tty `pardes`
+/// and SDL `pardes-gui` siblings also match when they live in the same
+/// directory. A name alone would call every unrelated `pardes` ancestor an
+/// outer instance. The hop cap is not for /proc, which cannot loop, but because
+/// the walk is driven by numbers read out of files and should not be able to
+/// spin on a surprising one.
pub fn outer() ?libc.pid_t {
if (comptime builtin.os.tag != .linux) return null;
var self_buf: [4096]u8 = undefined;
@@ -112,7 +140,7 @@ pub fn outer() ?libc.pid_t {
var buf: [4096]u8 = undefined;
const exe = std.fmt.bufPrintSentinel(&name, "/proc/{d}/exe", .{@as(u32, @intCast(pid))}, 0) catch return null;
const n = libc.readlink(exe, &buf, buf.len);
- if (n > 0 and std.mem.eql(u8, stripDeleted(buf[0..@intCast(n)]), self_exe)) return pid;
+ if (n > 0 and samePardesExecutable(buf[0..@intCast(n)], self_exe)) return pid;
const status = std.fmt.bufPrintSentinel(&name, "/proc/{d}/status", .{@as(u32, @intCast(pid))}, 0) catch return null;
const fd = libc.open(status, .{ .ACCMODE = .RDONLY });
if (fd < 0) return null;
@@ -356,6 +384,29 @@ test "a rebuilt binary still matches its own running instance" {
try std.testing.expectEqualStrings("/x (delete)", stripDeleted("/x (delete)"));
}
+test "tty and GUI sibling executables recognise each other" {
+ try std.testing.expect(samePardesExecutable(
+ "/work/zig-out/bin/pardes",
+ "/work/zig-out/bin/pardes-gui",
+ ));
+ try std.testing.expect(samePardesExecutable(
+ "/work/zig-out/bin/pardes-linux-aarch64",
+ "/work/zig-out/bin/pardes-gui-linux-aarch64 (deleted)",
+ ));
+ try std.testing.expect(!samePardesExecutable(
+ "/usr/bin/pardes",
+ "/work/zig-out/bin/pardes-gui",
+ ));
+ try std.testing.expect(!samePardesExecutable(
+ "/work/zig-out/bin/pardes-linux-aarch64",
+ "/work/zig-out/bin/pardes-gui-linux-x86_64",
+ ));
+ try std.testing.expect(!samePardesExecutable(
+ "/work/zig-out/bin/not-pardes",
+ "/work/zig-out/bin/not-pardes-gui",
+ ));
+}
+
test "the sweep only recognises its own socket names" {
try std.testing.expectEqual(@as(libc.pid_t, 7), sweepPid("pardes-7.sock").?);
try std.testing.expectEqual(@as(libc.pid_t, 4194304), sweepPid("pardes-4194304.sock").?);