94e7b0ed
feat(pty): add resize
a73x 2026-04-08 06:03
Implements Pty.resize(cols, rows) via ioctl(TIOCSWINSZ). Includes test verifying the new window size is readable back via TIOCGWINSZ.
diff --git a/src/pty.zig b/src/pty.zig index 2185a54..4afda9c 100644 --- a/src/pty.zig +++ b/src/pty.zig @@ -13,6 +13,7 @@ const c = @cImport({ pub const Pty = struct { master_fd: std.posix.fd_t, child_pid: std.posix.pid_t, child_reaped: bool = false, pub const SpawnOptions = struct { cols: u16, @@ -60,10 +61,36 @@ pub const Pty = struct { return std.posix.write(self.master_fd, data); } pub fn resize(self: *Pty, cols: u16, rows: u16) !void { var ws = c.struct_winsize{ .ws_row = rows, .ws_col = cols, .ws_xpixel = 0, .ws_ypixel = 0, }; if (c.ioctl(self.master_fd, c.TIOCSWINSZ, &ws) < 0) { return error.IoctlFailed; } } pub fn isChildAlive(self: *Pty) bool { if (self.child_reaped) return false; var status: c_int = 0; const rc = c.waitpid(self.child_pid, &status, c.WNOHANG); if (rc == 0) return true; if (rc == self.child_pid) { self.child_reaped = true; return false; } return true; } pub fn deinit(self: *Pty) void { std.posix.close(self.master_fd); _ = std.c.kill(self.child_pid, std.c.SIG.TERM); _ = std.c.waitpid(self.child_pid, null, 0); if (!self.child_reaped) { _ = std.c.kill(self.child_pid, std.c.SIG.TERM); _ = std.c.waitpid(self.child_pid, null, 0); } } }; @@ -112,3 +139,46 @@ test "Pty.spawn launches /bin/sh and returns valid fd" { try std.testing.expect(pty.master_fd >= 0); try std.testing.expect(pty.child_pid > 0); } test "Pty.resize sets winsize via ioctl" { var pty = try Pty.spawn(.{ .cols = 80, .rows = 24, .shell = "/bin/sh", }); defer pty.deinit(); try pty.resize(120, 40); var ws: c.struct_winsize = undefined; const rc = c.ioctl(pty.master_fd, c.TIOCGWINSZ, &ws); try std.testing.expectEqual(@as(c_int, 0), rc); try std.testing.expectEqual(@as(c_ushort, 120), ws.ws_col); try std.testing.expectEqual(@as(c_ushort, 40), ws.ws_row); } test "Pty.isChildAlive returns true while shell runs, false after exit" { var pty = try Pty.spawn(.{ .cols = 80, .rows = 24, .shell = "/bin/sh", }); defer pty.deinit(); try std.testing.expect(pty.isChildAlive()); // Tell the shell to exit _ = try pty.write("exit\n"); // Wait up to 1 second for it to actually exit const deadline = std.time.nanoTimestamp() + 1 * std.time.ns_per_s; var saw_dead = false; while (std.time.nanoTimestamp() < deadline) { if (!pty.isChildAlive()) { saw_dead = true; break; } std.Thread.sleep(20 * std.time.ns_per_ms); } try std.testing.expect(saw_dead); }