summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig27
-rw-r--r--src/gui.zig89
-rw-r--r--src/look.zig64
-rw-r--r--src/tty.zig66
-rw-r--r--test/e2e_harness.zig37
-rw-r--r--test/snapshot.zig63
6 files changed, 184 insertions, 162 deletions
diff --git a/build.zig b/build.zig
index 22dd7158..f9aa1070 100644
--- a/build.zig
+++ b/build.zig
@@ -59,6 +59,10 @@ pub fn build(b: *std.Build) void {
// static archives the emcc link needs beyond the zig object (grammars, SDL)
var wasm_libs: std.ArrayList(std.Build.LazyPath) = .empty;
+ // forkpty: in libc proper on glibc>=2.34 and darwin; the BSDs keep it in libutil
+ if (target.result.os.tag == .freebsd or target.result.os.tag == .netbsd or target.result.os.tag == .openbsd)
+ root_mod.linkSystemLibrary("util", .{});
+
const opts = b.addOptions();
opts.addOption(Platform, "platform", platform);
opts.addOption(bool, "syntax_highlighting", tree_sitter_grammars != .disabled);
@@ -171,7 +175,26 @@ pub fn build(b: *std.Build) void {
}).module("vaxis");
var ghostty_vt_for_snap: ?*std.Build.Module = null;
- if (b.lazyDependency("ghostty", .{ .simd = target.result.os.tag != .emscripten })) |dep| {
+ // ghostty's simd libs (simdutf/highway, C++) locate the Apple SDK via
+ // xcrun on darwin targets, so cross-compiling to macOS from elsewhere
+ // uses the scalar fallback (the same configuration the web shell ships).
+ // Native/cross builds hand ghostty the real target (it defaults to the
+ // host otherwise); the web path keeps its original no-target fetch, whose
+ // zig object never uses ghostty's artifacts.
+ // NOTE: darwin targets also need two one-line zig-0.16 fixes in the
+ // pinned ghostty (applied in the zig-pkg cache; re-apply after a fresh
+ // fetch, or bump the pin once upstream carries them):
+ // src/os/mach.zig — std.heap.next_mmap_addr_hint is gone (make the hint
+ // var module-local) and posix.mmap prot is now a packed struct
+ // (.{ .READ = true, .WRITE = true });
+ // src/terminal/kitty/graphics_image.zig:185 — shm_open's variadic mode
+ // literal 0 must be @as(std.c.mode_t, 0).
+ const ghostty_simd = !target.result.os.tag.isDarwin() or b.graph.host.result.os.tag.isDarwin();
+ const ghostty_dep = if (is_emscripten)
+ b.lazyDependency("ghostty", .{ .simd = false })
+ else
+ b.lazyDependency("ghostty", .{ .target = target, .optimize = optimize, .simd = ghostty_simd });
+ if (ghostty_dep) |dep| {
const ghostty_vt = dep.module("ghostty-vt");
ghostty_vt_for_snap = ghostty_vt;
ghostty_vt.addImport("uucode", uucode_mod);
@@ -309,6 +332,8 @@ pub fn build(b: *std.Build) void {
}),
});
if (ghostty_vt_for_snap) |vt| snap.root_module.addImport("ghostty-vt", vt);
+ if (target.result.os.tag == .freebsd or target.result.os.tag == .netbsd or target.result.os.tag == .openbsd)
+ snap.root_module.linkSystemLibrary("util", .{});
b.installArtifact(snap);
const run_snap = b.addRunArtifact(snap);
run_snap.addArtifactArg(exe);
diff --git a/src/gui.zig b/src/gui.zig
index 516a6b79..c1881c7c 100644
--- a/src/gui.zig
+++ b/src/gui.zig
@@ -1,6 +1,6 @@
//! The SDL3 GPU shell: owns an SDL window + event loop, translates SDL input
//! into core events, performs the core's effects (fork ptys, write them,
-//! resize them — same duties as tty.zig, this is also native linux), and
+//! resize them — same duties as tty.zig, this is also native), and
//! rasterizes the core's Surface: one instanced quad per cell, glyphs from an
//! stb_truetype R8 atlas. Test modes: PARDES_TEST_GRID=1 is headless (no SDL,
//! stdin escape sequences in, text grid frames out); PARDES_TEST=1 keeps the
@@ -13,10 +13,11 @@
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"); // test modes only: the stdin escape-seq parser
const ghostty_vt = @import("ghostty-vt"); // 256-color palette for .index cells
const pardes = @import("pardes.zig");
+const look = @import("look.zig");
const is_emscripten = builtin.os.tag == .emscripten;
@@ -34,6 +35,12 @@ extern "c" fn _exit(status: c_int) noreturn;
extern "c" fn emscripten_get_element_css_size(target: [*:0]const u8, width: *f64, height: *f64) c_int;
const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0;
+// 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));
+
const log = std.log.scoped(.gui);
const font_ttf = @embedFile("AdwaitaMono-Regular.ttf");
@@ -271,7 +278,7 @@ fn normCell(norm: f32, win: f32, cell: f32) u16 {
// ---- pty plumbing: reader threads feed a mutex-protected queue ----
-const Pty = struct { fd: c_int, pid: linux.pid_t };
+const Pty = struct { fd: c_int, pid: libc.pid_t };
const Msg = union(enum) { output: struct { pane: u8, bytes: []u8 }, eof: u8 };
@@ -338,10 +345,9 @@ const Queue = struct {
fn readPtyThread(gpa: std.mem.Allocator, fd: c_int, pane: u8, q: *Queue) void {
var buf: [0x10000]u8 = undefined;
while (true) {
- const rc = linux.read(fd, &buf, buf.len);
- const n: isize = @bitCast(rc);
+ const n = libc.read(fd, &buf, buf.len);
if (n < 0) {
- if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue;
+ if (libc.errno(n) == .INTR) continue;
break; // EIO when the child exits: treat as EOF
}
if (n == 0) break;
@@ -622,7 +628,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options) !void {
var ptys: [pardes.MAX_PANES]?Pty = @splat(null);
defer for (&ptys) |*slot| if (slot.*) |pt| {
- _ = linux.close(pt.fd);
+ _ = libc.close(pt.fd);
slot.* = null;
};
var queue: Queue = .{ .gpa = gpa, .sdl_wake = true };
@@ -662,7 +668,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options) !void {
},
.eof => |id| {
if (ptys[id]) |pt| {
- _ = linux.close(pt.fd);
+ _ = libc.close(pt.fd);
ptys[id] = null;
}
core.update(.{ .eof = .{ .pane = id } });
@@ -895,7 +901,7 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void {
var ptys: [pardes.MAX_PANES]?Pty = @splat(null);
defer for (&ptys) |*slot| if (slot.*) |pt| {
- _ = linux.close(pt.fd);
+ _ = libc.close(pt.fd);
slot.* = null;
};
var queue: Queue = .{ .gpa = gpa, .sdl_wake = false };
@@ -927,7 +933,7 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void {
},
.eof => |id| {
if (ptys[id]) |pt| {
- _ = linux.close(pt.fd);
+ _ = libc.close(pt.fd);
ptys[id] = null;
}
core.update(.{ .eof = .{ .pane = id } });
@@ -981,8 +987,7 @@ const StdinFeed = struct {
if ((fds[0].revents & (posix.POLL.HUP | posix.POLL.ERR)) != 0) out.eof = true;
return out;
}
- const rc = linux.read(0, f.buf[f.fill..].ptr, f.buf.len - f.fill);
- const rn: isize = @bitCast(rc);
+ const rn = libc.read(0, f.buf[f.fill..].ptr, f.buf.len - f.fill);
if (rn < 0) return out;
if (rn == 0) {
out.eof = true;
@@ -1433,12 +1438,7 @@ fn drainEffects(
ptys[sp.pane] = pt;
// 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", .{pt.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(pt.pid, &lbuf)) |wd| core.setCwd(sp.pane, wd);
if (threads_ok) spawnReader(gpa, pt, sp.pane, queue);
},
.write => |w| {
@@ -1447,7 +1447,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.fd, posix.T.IOCSWINSZ, @intFromPtr(&ws));
+ _ = posix.system.ioctl(pt.fd, TIOCSWINSZ, @intFromPtr(&ws));
}
},
.open_link => {}, // native: look opens panes instead; nothing to launch
@@ -1458,21 +1458,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 => {
if (g == null) continue;
@@ -1500,8 +1496,8 @@ fn forkShell(cwd: ?[*:0]const u8, rows: u16, cols: u16) Pty {
const pid = forkpty(&master, null, null, &ws);
if (pid == 0) {
if (cwd) |cd| _ = chdir(cd);
- 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 .{ .fd = master, .pid = pid };
@@ -1510,11 +1506,7 @@ fn forkShell(cwd: ?[*:0]const u8, rows: u16, cols: u16) Pty {
fn pollCwds(core: *pardes.Pardes, ptys: *[pardes.MAX_PANES]?Pty) void {
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);
};
}
@@ -2073,11 +2065,9 @@ fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u
const tmp_path = std.fmt.bufPrintSentinel(&tmp_buf, "{s}/latest.ppm.tmp", .{g.capture_dir}, 0) catch return error.CapturePathTooLong;
const final_path = std.fmt.bufPrintSentinel(&final_buf, "{s}/latest.ppm", .{g.capture_dir}, 0) catch return error.CapturePathTooLong;
- const orc = linux.open(tmp_path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644);
- const sfd: isize = @bitCast(orc);
- if (sfd < 0) return error.CaptureWriteFailed;
- const fd: c_int = @intCast(sfd);
- defer _ = linux.close(fd);
+ const fd = libc.open(tmp_path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) return error.CaptureWriteFailed;
+ defer _ = libc.close(fd);
var header: [64]u8 = undefined;
const hdr = std.fmt.bufPrint(&header, "P6\n{d} {d}\n255\n", .{ width, height }) catch return error.CaptureWriteFailed;
@@ -2096,9 +2086,7 @@ fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u
}
writeFd(fd, row_rgb);
}
- const rrc = linux.rename(tmp_path, final_path);
- const rs: isize = @bitCast(rrc);
- if (rs < 0) return error.CaptureWriteFailed;
+ if (libc.rename(tmp_path, final_path) != 0) return error.CaptureWriteFailed;
}
// =====================================================================
@@ -2377,21 +2365,18 @@ fn envU16(env: *std.process.Environ.Map, name: []const u8) ?u16 {
}
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);
diff --git a/src/look.zig b/src/look.zig
index 67fbf8c5..8744da24 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -8,7 +8,8 @@
//! web — there is no filesystem; only URLs mean anything, and a URL
//! opens in a new tab (the open_link effect).
const std = @import("std");
-const linux = std.os.linux;
+const builtin = @import("builtin");
+const libc = std.c;
const pardes = @import("pardes.zig");
extern "c" fn realpath(path: [*:0]const u8, resolved: [*]u8) ?[*:0]u8;
@@ -88,10 +89,9 @@ const platform_has_fs = switch (pardes.platform) {
/// true if `path` exists and is a directory (open(O_DIRECTORY), no stat needed)
fn isDir(path: [*:0]const u8) bool {
- const rc = linux.open(path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true, .CLOEXEC = true }, 0);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) return false;
- _ = linux.close(@intCast(sfd));
+ const fd = libc.open(path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true, .CLOEXEC = true });
+ if (fd < 0) return false;
+ _ = libc.close(fd);
return true;
}
@@ -101,19 +101,16 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
if (!platform_has_fs) return error.NoFilesystem;
var pathbuf: [4096]u8 = undefined;
const path_z = std.fmt.bufPrintSentinel(&pathbuf, "{s}", .{path}, 0) catch return error.PathTooLong;
- const rc = linux.open(path_z, .{ .ACCMODE = .RDONLY }, 0);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) return error.OpenFailed;
- const fd: i32 = @intCast(sfd);
- defer _ = linux.close(fd);
+ const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY });
+ if (fd < 0) return error.OpenFailed;
+ defer _ = libc.close(fd);
var buf: std.ArrayList(u8) = .empty;
errdefer buf.deinit(gpa);
var chunk: [16384]u8 = undefined;
while (true) {
- const r = linux.read(fd, &chunk, chunk.len);
- const n: isize = @bitCast(r);
+ const n = libc.read(fd, &chunk, chunk.len);
if (n < 0) {
- if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue;
+ if (libc.errno(n) == .INTR) continue;
return error.ReadFailed;
}
if (n == 0) break;
@@ -121,3 +118,44 @@ pub fn readFile(gpa: std.mem.Allocator, path: []const u8) ![]u8 {
}
return buf.toOwnedSlice(gpa);
}
+
+// ---- shell cwd: what directory a pane's looks resolve against ----
+
+// macOS has no /proc; libproc's proc_pidinfo(PROC_PIDVNODEPATHINFO) yields the
+// cwd vnode path. Not in std.c — layout from xnu's sys/proc_info.h.
+const vnode_info_path = extern struct {
+ vi: [152]u8 align(8), // struct vnode_info: vinfo_stat + type + pad + fsid
+ path: [1024]u8, // MAXPATHLEN
+};
+const proc_vnodepathinfo = extern struct {
+ cdir: vnode_info_path,
+ rdir: vnode_info_path,
+};
+const PROC_PIDVNODEPATHINFO: c_int = 9;
+extern "c" fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *anyopaque, buffersize: c_int) c_int;
+
+/// Live cwd of a shell process (pane tags, look resolution). linux reads
+/// /proc/<pid>/cwd, darwin asks libproc; other POSIX systems have no cheap
+/// answer — return null and panes keep their spawn-time cwd (callers already
+/// tolerate failure: dead shells have no cwd either).
+pub fn shellCwd(pid: libc.pid_t, buf: *[1024]u8) ?[]const u8 {
+ switch (builtin.os.tag) {
+ .linux => {
+ var pbuf: [64]u8 = undefined;
+ const path = std.fmt.bufPrintSentinel(&pbuf, "/proc/{d}/cwd", .{pid}, 0) catch return null;
+ const n = libc.readlink(path, buf, buf.len);
+ if (n <= 0) return null;
+ return buf[0..@intCast(n)];
+ },
+ .macos, .ios, .tvos, .watchos, .visionos => {
+ var info: proc_vnodepathinfo = undefined;
+ const n = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &info, @sizeOf(proc_vnodepathinfo));
+ if (n < @as(c_int, @sizeOf(proc_vnodepathinfo))) return null;
+ const path = std.mem.sliceTo(&info.cdir.path, 0);
+ if (path.len == 0) return null;
+ @memcpy(buf[0..path.len], path);
+ return buf[0..path.len];
+ },
+ else => return null,
+ }
+}
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);
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig
index 6354aa4b..9099f13e 100644
--- a/test/e2e_harness.zig
+++ b/test/e2e_harness.zig
@@ -1,8 +1,11 @@
const std = @import("std");
const posix = std.posix;
-const linux = std.os.linux;
+const libc = std.c;
const ghostty_vt = @import("ghostty-vt");
+// 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));
+
extern "c" fn forkpty(
amaster: *c_int,
name: ?[*:0]u8,
@@ -13,9 +16,9 @@ extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_
extern "c" fn _exit(status: c_int) noreturn;
fn nowMs() i64 {
- var ts: linux.timespec = undefined;
- _ = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts);
- return ts.sec * 1000 + @divFloor(@as(i64, ts.nsec), 1_000_000);
+ var ts: libc.timespec = undefined;
+ _ = libc.clock_gettime(.MONOTONIC, &ts);
+ return @as(i64, @intCast(ts.sec)) * 1000 + @divFloor(@as(i64, @intCast(ts.nsec)), 1_000_000);
}
/// E2E test harness: forks the app in a pty and parses its output with a
@@ -92,7 +95,7 @@ pub const Harness = struct {
pub fn deinit(self: *Harness) void {
posix.kill(self.pid, posix.SIG.KILL) catch {};
- _ = linux.close(self.master);
+ _ = libc.close(self.master);
self.stream.deinit();
self.raw.deinit(self.gpa);
self.term.deinit(self.gpa);
@@ -103,10 +106,9 @@ pub const Harness = struct {
pub fn send(self: *Harness, bytes: []const u8) !void {
var off: usize = 0;
while (off < bytes.len) {
- const rc = linux.write(self.master, bytes[off..].ptr, bytes.len - off);
- const n: isize = @bitCast(rc);
+ const n = libc.write(self.master, bytes[off..].ptr, bytes.len - off);
if (n < 0) {
- if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue;
+ if (libc.errno(n) == .INTR) continue;
return error.WriteFailed;
}
off += @intCast(n);
@@ -139,7 +141,7 @@ pub const Harness = struct {
self.cols = cols;
self.term.resize(self.gpa, cols, rows) catch {};
const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 };
- _ = posix.system.ioctl(self.master, posix.T.IOCSWINSZ, @intFromPtr(&ws));
+ _ = posix.system.ioctl(self.master, TIOCSWINSZ, @intFromPtr(&ws));
}
/// The full viewport text (plainString), newline-joined rows. Caller frees.
@@ -261,18 +263,15 @@ pub const Harness = struct {
}
};
-/// Write a file (helper for test setup, raw linux syscalls).
+/// Write a file (helper for test setup, raw libc).
pub fn writeFile(path: [*:0]const u8, data: []const u8) !void {
- const rc = linux.open(path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, 0o644);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) return error.OpenFailed;
- const fd: c_int = @intCast(sfd);
- defer _ = linux.close(fd);
+ const fd = libc.open(path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) return error.OpenFailed;
+ defer _ = libc.close(fd);
var off: usize = 0;
while (off < data.len) {
- const w = linux.write(fd, data[off..].ptr, data.len - off);
- const sw: isize = @bitCast(w);
- if (sw <= 0) break;
- off += @intCast(sw);
+ const w = libc.write(fd, data[off..].ptr, data.len - off);
+ if (w <= 0) break;
+ off += @intCast(w);
}
}
diff --git a/test/snapshot.zig b/test/snapshot.zig
index c1bf03c0..10aa0c86 100644
--- a/test/snapshot.zig
+++ b/test/snapshot.zig
@@ -28,8 +28,7 @@
// snap <label> capture grid text + cursor
// snapstyle <label> capture per-cell style runs (fg,bg,attrs)
const std = @import("std");
-const posix = std.posix;
-const linux = std.os.linux;
+const libc = std.c;
const ghostty_vt = @import("ghostty-vt");
const eh = @import("e2e_harness.zig");
@@ -48,9 +47,8 @@ pub fn main(init: std.process.Init) !void {
// argv: exe-under-test, then flags/scripts. All paths made absolute up
// front because runScript chdirs into a per-script workdir.
var cwd_buf: [4096]u8 = undefined;
- const cwd_rc: isize = @bitCast(linux.getcwd(&cwd_buf, cwd_buf.len));
- if (cwd_rc <= 0) fatal("getcwd failed", .{});
- const orig_cwd = cwd_buf[0..@intCast(cwd_rc - 1)];
+ const cwd_p = libc.getcwd(&cwd_buf, cwd_buf.len) orelse fatal("getcwd failed", .{});
+ const orig_cwd = std.mem.span(@as([*:0]u8, @ptrCast(cwd_p)));
var exe: ?[]const u8 = null;
var update = false;
@@ -461,41 +459,26 @@ fn absPath(arena: std.mem.Allocator, cwd: []const u8, p: []const u8) ![]const u8
fn listSnaps(arena: std.mem.Allocator, dir_path: []const u8, scripts: *std.ArrayList([]const u8)) !void {
const dir_z = try arena.dupeZ(u8, dir_path);
- const rc = linux.open(dir_z, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, 0);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) fatal("no snapshots/ dir at {s}", .{dir_path});
- const fd: i32 = @intCast(sfd);
- defer _ = linux.close(fd);
- var buf: [8192]u8 align(@alignOf(linux.dirent64)) = undefined;
- while (true) {
- const r = linux.getdents64(fd, &buf, buf.len);
- const n: isize = @bitCast(r);
- if (n <= 0) break;
- var off: usize = 0;
- while (off < @as(usize, @intCast(n))) {
- const d: *align(1) linux.dirent64 = @ptrCast(&buf[off]);
- const name = std.mem.span(@as([*:0]const u8, @ptrCast(&d.name)));
- if (std.mem.endsWith(u8, name, ".snap"))
- try scripts.append(arena, try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name }));
- off += d.reclen;
- }
+ const dir = libc.opendir(dir_z) orelse fatal("no snapshots/ dir at {s}", .{dir_path});
+ defer _ = libc.closedir(dir);
+ while (libc.readdir(dir)) |d| {
+ const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&d.name)), 0);
+ if (std.mem.endsWith(u8, name, ".snap"))
+ try scripts.append(arena, try std.fmt.allocPrint(arena, "{s}/{s}", .{ dir_path, name }));
}
}
fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 {
const path_z = try arena.dupeZ(u8, path);
- const rc = linux.open(path_z, .{ .ACCMODE = .RDONLY }, 0);
- const sfd: isize = @bitCast(rc);
- if (sfd < 0) return error.OpenFailed;
- const fd: i32 = @intCast(sfd);
- defer _ = linux.close(fd);
+ const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY });
+ if (fd < 0) return error.OpenFailed;
+ defer _ = libc.close(fd);
var buf: std.ArrayList(u8) = .empty;
var chunk: [16384]u8 = undefined;
while (true) {
- const r = linux.read(fd, &chunk, chunk.len);
- const n: isize = @bitCast(r);
+ const n = libc.read(fd, &chunk, chunk.len);
if (n < 0) {
- if (n == -@as(isize, @intFromEnum(linux.E.INTR))) continue;
+ if (libc.errno(n) == .INTR) continue;
return error.ReadFailed;
}
if (n == 0) break;
@@ -506,15 +489,15 @@ fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 {
fn mkdir(arena: std.mem.Allocator, path: []const u8, exist_ok: bool) !void {
const path_z = try arena.dupeZ(u8, path);
- const n: isize = @bitCast(linux.mkdir(path_z, 0o755));
- if (n < 0) {
- if (exist_ok and n == -@as(isize, @intFromEnum(linux.E.EXIST))) return;
+ const rc = libc.mkdir(path_z, 0o755);
+ if (rc != 0) {
+ if (exist_ok and libc.errno(rc) == .EXIST) return;
return error.MkdirFailed;
}
}
-fn chdirRc(path_z: [:0]const u8) isize {
- return @bitCast(linux.chdir(path_z.ptr));
+fn chdirRc(path_z: [:0]const u8) c_int {
+ return libc.chdir(path_z.ptr);
}
fn rmrf(path: []const u8) void {
@@ -522,15 +505,15 @@ fn rmrf(path: []const u8) void {
if (path.len >= path_buf.len) return;
@memcpy(path_buf[0..path.len], path);
path_buf[path.len] = 0;
- const pid: isize = @bitCast(linux.fork());
+ const pid = libc.fork();
if (pid == 0) {
const argv: [4:null]?[*:0]const u8 = .{ "rm", "-rf", @as([*:0]const u8, @ptrCast(&path_buf)), null };
_ = execvp("rm", &argv);
- linux.exit_group(127);
+ libc._exit(127);
}
if (pid < 0) return;
- var status: u32 = 0;
- _ = linux.wait4(@intCast(pid), &status, 0, null);
+ var status: c_int = 0;
+ _ = libc.waitpid(pid, &status, 0);
}
fn printFirstDiff(golden: []const u8, actual: []const u8) void {