summaryrefslogtreecommitdiff
path: root/src/tty.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-06 18:00:54 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit7a83d1548493b043e2e77022c1c87d348377ac7b (patch)
tree75bcac77800b13695a953a3398b5bb8a95e017dd /src/tty.zig
parentde4def3548a6729b0dfd2120495a61beef8c8c2c (diff)
downloadpardes-7a83d1548493b043e2e77022c1c87d348377ac7b.tar.gz
pardes-7a83d1548493b043e2e77022c1c87d348377ac7b.zip
posix portability: linux syscalls -> libc (std.c) in one shared path. All raw std.os.linux.* call sites converted with the errno-convention flip (libc returns -1 + thread-local errno, not -errno): open/close/read/write/rename in tty.zig, gui.zig, look.zig; harness too (snapshot.zig: getcwd/mkdir/chdir/fork+waitpid/getdents64->opendir-readdir, e2e_harness.zig: clock_gettime/write/close), so the parity suite itself is POSIX-portable. Per-OS branches kept only where platforms genuinely diverge: shellCwd in look.zig (linux /proc/<pid>/cwd readlink; darwin libproc proc_pidinfo PROC_PIDVNODEPATHINFO with a hand-declared extern; other POSIX degrades to null), bash path (/usr/bin/bash on linux, /bin/bash elsewhere), TIOCSWINSZ constant (absent from std.c.T on darwin). forkpty/execv/chdir externs were already libc and are portable (fork-safety preserved: absolute-path execv, no malloc between fork and exec, initial spawns before worker threads). build.zig: forward target/optimize to the ghostty dep for native/cross (web keeps its no-target fetch), disable ghostty simd for darwin cross-builds (apple_sdk needs xcrun; scalar fallback = web config), link libutil on the BSDs. Pinned ghostty needs two one-line zig-0.16 darwin fixes, applied in the zig-pkg cache and documented in build.zig. Verified: aarch64-macos + x86_64-macos + x86_64-freebsd cross-compiles green; linux native tty + gui + web green; snapshot suite 18/18 with goldens untouched. Out of scope: gui-on-macos needs Metal shaders (SDL GPU rejects our SPIR-V there).
Diffstat (limited to 'src/tty.zig')
-rw-r--r--src/tty.zig66
1 files changed, 29 insertions, 37 deletions
diff --git a/src/tty.zig b/src/tty.zig
index d9821343..9e2da26b 100644
--- a/src/tty.zig
+++ b/src/tty.zig
@@ -3,16 +3,24 @@
//! them, resize them), and hands the core's Surface to vaxis cell-for-cell —
//! the canonical interface rendered with no interpretation.
const std = @import("std");
+const builtin = @import("builtin");
const posix = std.posix;
-const linux = std.os.linux;
+const libc = std.c;
const vaxis = @import("vaxis");
const pardes = @import("pardes.zig");
+const look = @import("look.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;
extern "c" fn chdir(path: [*:0]const u8) c_int;
extern "c" fn _exit(status: c_int) noreturn;
+// absolute shell path per OS: execv must not search PATH (no allocation
+// between fork and exec)
+const bash_path: [*:0]const u8 = if (builtin.os.tag == .linux) "/usr/bin/bash" else "/bin/bash";
+// TIOCSWINSZ: absent from std.c.T on darwin — _IOW('t', 103, winsize)
+const TIOCSWINSZ: c_int = @bitCast(@as(u32, if (@hasDecl(posix.T, "IOCSWINSZ")) posix.T.IOCSWINSZ else 0x80087467));
+
pub const Command = struct {
pub var value: union(enum) {
nop,
@@ -54,7 +62,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
defer pardes.image.stop();
const core = if (opts.load_path) |lp| blk: {
- const bytes = try @import("look.zig").readFile(gpa, lp);
+ const bytes = try look.readFile(gpa, lp);
defer gpa.free(bytes);
break :blk try pardes.Pardes.initFromDump(gpa, opts, bytes);
} else try pardes.Pardes.init(gpa, opts);
@@ -66,7 +74,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var kitty_handles: [pardes.MAX_PANES]?vaxis.Image = @splat(null);
var ptys: [pardes.MAX_PANES]?Pty = @splat(null);
defer for (&ptys) |*slot| if (slot.*) |*pt| {
- _ = linux.close(pt.file.handle);
+ _ = libc.close(pt.file.handle);
slot.* = null;
};
@@ -104,7 +112,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
},
.pty_eof => |id| {
if (ptys[id]) |*pt| {
- _ = linux.close(pt.file.handle);
+ _ = libc.close(pt.file.handle);
ptys[id] = null;
}
core.update(.{ .eof = .{ .pane = @intCast(id) } });
@@ -146,14 +154,10 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
drainEffects(core, &ptys, io, gpa, &loop, &vx, &tty, true);
if (core.quit) break;
- // live cwd for tags/look: cheap /proc readlink per pane, per frame
+ // live cwd for tags/look: cheap per-pane lookup, per frame
for (&ptys, 0..) |*slot, id| if (slot.*) |pt| {
var lbuf: [1024]u8 = undefined;
- var pbuf: [64]u8 = undefined;
- const path = std.fmt.bufPrintSentinel(&pbuf, "/proc/{d}/cwd", .{pt.pid}, 0) catch continue;
- const rc = linux.readlinkat(linux.AT.FDCWD, path, &lbuf, lbuf.len);
- const n: isize = @bitCast(rc);
- if (n > 0) core.setCwd(id, lbuf[0..@intCast(n)]);
+ if (look.shellCwd(pt.pid, &lbuf)) |cwd| core.setCwd(id, cwd);
};
// ---- render: surface -> vaxis, cell for cell ----
@@ -220,12 +224,7 @@ fn drainEffects(
ptys[sp.pane] = .{ .file = child.file, .pid = child.pid, .reader = .{ .any_future = null, .result = {} } };
// report the pane's starting directory back to the core (tags)
var lbuf: [1024]u8 = undefined;
- var pbuf: [64]u8 = undefined;
- if (std.fmt.bufPrintSentinel(&pbuf, "/proc/{d}/cwd", .{child.pid}, 0) catch null) |path| {
- const rc = linux.readlinkat(linux.AT.FDCWD, path, &lbuf, lbuf.len);
- const n: isize = @bitCast(rc);
- if (n > 0) core.setCwd(sp.pane, lbuf[0..@intCast(n)]);
- }
+ if (look.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(sp.pane, wd);
if (threads_ok) {
if (ptys[sp.pane]) |*pt| {
pt.reader = io.concurrent(readPty, .{ io, gpa, pt.file, @as(usize, sp.pane), loop }) catch pt.reader;
@@ -238,7 +237,7 @@ fn drainEffects(
.resize_pty => |rs| {
if (ptys[rs.pane]) |pt| {
const ws: posix.winsize = .{ .row = rs.rows, .col = rs.cols, .xpixel = 0, .ypixel = 0 };
- _ = posix.system.ioctl(pt.file.handle, posix.T.IOCSWINSZ, @intFromPtr(&ws));
+ _ = posix.system.ioctl(pt.file.handle, TIOCSWINSZ, @intFromPtr(&ws));
}
},
.open_link => {}, // tty: no link handling (look opens panes instead)
@@ -249,21 +248,17 @@ fn drainEffects(
if (f.path.len >= pathbuf.len) continue;
@memcpy(pathbuf[0..f.path.len], f.path);
pathbuf[f.path.len] = 0;
- const rc = linux.open(pathbuf[0..f.path.len :0], .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) continue;
- const fd: c_int = @intCast(sfd);
+ const fd = libc.open(pathbuf[0..f.path.len :0], .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) continue;
writeFd(fd, f.content);
- _ = linux.close(fd);
+ _ = libc.close(fd);
},
.write_dump => {
const out = core.dump_out orelse continue;
- const rc = linux.open(pardes.dump.default_path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) continue;
- const fd: c_int = @intCast(sfd);
+ const fd = libc.open(pardes.dump.default_path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) continue;
writeFd(fd, out);
- _ = linux.close(fd);
+ _ = libc.close(fd);
},
.set_clipboard => {
// mirror the core's yank register out via OSC 52
@@ -280,8 +275,8 @@ fn forkShell(cwd: ?[*:0]const u8, rows: u16, cols: u16) struct { file: std.Io.Fi
const pid = forkpty(&master, null, null, &ws);
if (pid == 0) {
if (cwd) |c| _ = chdir(c);
- const argv: [4:null]?[*:0]const u8 = .{ "/usr/bin/bash", "--rcfile", "/tmp/pardes-osc133.bash", null };
- _ = execv("/usr/bin/bash", &argv);
+ const argv: [4:null]?[*:0]const u8 = .{ bash_path, "--rcfile", "/tmp/pardes-osc133.bash", null };
+ _ = execv(bash_path, &argv);
_exit(127);
}
return .{ .file = .{ .handle = master, .flags = .{ .nonblocking = false } }, .pid = pid };
@@ -364,21 +359,18 @@ fn vaxisColor(c: pardes.Color) vaxis.Color {
}
fn writeFile(data: []const u8) void {
- const rc = linux.open("/tmp/pardes-osc133.bash", .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) return;
- const fd: c_int = @intCast(sfd);
- defer _ = linux.close(fd);
+ const fd = libc.open("/tmp/pardes-osc133.bash", .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) return;
+ defer _ = libc.close(fd);
writeFd(fd, data);
}
fn writeFd(fd: c_int, data: []const u8) void {
var off: usize = 0;
while (off < data.len) {
- const rc = linux.write(fd, data[off..].ptr, data.len - off);
- const n: isize = @bitCast(rc);
+ const n = libc.write(fd, data[off..].ptr, data.len - off);
if (n < 0) {
- if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue;
+ if (libc.errno(n) == .INTR) continue;
return;
}
off += @intCast(n);