835bd46d
feat(pty): spawn child shell via forkpty
a73x 2026-04-08 06:01
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -40,8 +40,31 @@ pub fn build(b: *std.Build) void { | |||
| 40 | const run_step = b.step("run", "Run waystty"); | 40 | const run_step = b.step("run", "Run waystty"); |
| 41 | run_step.dependOn(&run_cmd.step); | 41 | run_step.dependOn(&run_cmd.step); |
| 42 | 42 | ||
| 43 | // pty module — forkpty-based PTY spawn | ||
| 44 | const pty_mod = b.createModule(.{ | ||
| 45 | .root_source_file = b.path("src/pty.zig"), | ||
| 46 | .target = target, | ||
| 47 | .optimize = optimize, | ||
| 48 | .link_libc = true, | ||
| 49 | }); | ||
| 50 | pty_mod.linkSystemLibrary("util", .{}); | ||
| 51 | exe_mod.addImport("pty", pty_mod); | ||
| 52 | |||
| 43 | const test_step = b.step("test", "Run unit tests"); | 53 | const test_step = b.step("test", "Run unit tests"); |
| 44 | 54 | ||
| 55 | // Test pty.zig | ||
| 56 | const pty_test_mod = b.createModule(.{ | ||
| 57 | .root_source_file = b.path("src/pty.zig"), | ||
| 58 | .target = target, | ||
| 59 | .optimize = optimize, | ||
| 60 | .link_libc = true, | ||
| 61 | }); | ||
| 62 | pty_test_mod.linkSystemLibrary("util", .{}); | ||
| 63 | const pty_tests = b.addTest(.{ | ||
| 64 | .root_module = pty_test_mod, | ||
| 65 | }); | ||
| 66 | test_step.dependOn(&b.addRunArtifact(pty_tests).step); | ||
| 67 | |||
| 45 | // Test main.zig (and transitively vt.zig via its import) | 68 | // Test main.zig (and transitively vt.zig via its import) |
| 46 | const main_test_mod = b.createModule(.{ | 69 | const main_test_mod = b.createModule(.{ |
| 47 | .root_source_file = b.path("src/main.zig"), | 70 | .root_source_file = b.path("src/main.zig"), |
src/pty.zig
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,71 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | const c = @cImport({ | ||
| 4 | @cInclude("pty.h"); | ||
| 5 | @cInclude("termios.h"); | ||
| 6 | @cInclude("unistd.h"); | ||
| 7 | @cInclude("sys/ioctl.h"); | ||
| 8 | @cInclude("fcntl.h"); | ||
| 9 | @cInclude("sys/wait.h"); | ||
| 10 | @cInclude("stdlib.h"); | ||
| 11 | }); | ||
| 12 | |||
| 13 | pub const Pty = struct { | ||
| 14 | master_fd: std.posix.fd_t, | ||
| 15 | child_pid: std.posix.pid_t, | ||
| 16 | |||
| 17 | pub const SpawnOptions = struct { | ||
| 18 | cols: u16, | ||
| 19 | rows: u16, | ||
| 20 | shell: [:0]const u8, | ||
| 21 | }; | ||
| 22 | |||
| 23 | pub fn spawn(opts: SpawnOptions) !Pty { | ||
| 24 | var master: c_int = undefined; | ||
| 25 | var winsize = c.struct_winsize{ | ||
| 26 | .ws_row = opts.rows, | ||
| 27 | .ws_col = opts.cols, | ||
| 28 | .ws_xpixel = 0, | ||
| 29 | .ws_ypixel = 0, | ||
| 30 | }; | ||
| 31 | |||
| 32 | const pid = c.forkpty(&master, null, null, &winsize); | ||
| 33 | if (pid < 0) return error.ForkptyFailed; | ||
| 34 | |||
| 35 | if (pid == 0) { | ||
| 36 | // Child process | ||
| 37 | _ = c.setenv("TERM", "xterm-256color", 1); | ||
| 38 | |||
| 39 | var argv = [_:null]?[*:0]const u8{ opts.shell.ptr, null }; | ||
| 40 | std.posix.execveZ(opts.shell.ptr, &argv, std.c.environ) catch {}; | ||
| 41 | std.process.exit(1); | ||
| 42 | } | ||
| 43 | |||
| 44 | // Parent: set master fd non-blocking | ||
| 45 | const flags = try std.posix.fcntl(master, std.posix.F.GETFL, 0); | ||
| 46 | const nonblock_bit: usize = @as(u32, @bitCast(std.posix.O{ .NONBLOCK = true })); | ||
| 47 | _ = try std.posix.fcntl(master, std.posix.F.SETFL, flags | nonblock_bit); | ||
| 48 | |||
| 49 | return .{ | ||
| 50 | .master_fd = master, | ||
| 51 | .child_pid = pid, | ||
| 52 | }; | ||
| 53 | } | ||
| 54 | |||
| 55 | pub fn deinit(self: *Pty) void { | ||
| 56 | std.posix.close(self.master_fd); | ||
| 57 | _ = std.c.kill(self.child_pid, std.c.SIG.TERM); | ||
| 58 | _ = std.c.waitpid(self.child_pid, null, 0); | ||
| 59 | } | ||
| 60 | }; | ||
| 61 | |||
| 62 | test "Pty.spawn launches /bin/sh and returns valid fd" { | ||
| 63 | var pty = try Pty.spawn(.{ | ||
| 64 | .cols = 80, | ||
| 65 | .rows = 24, | ||
| 66 | .shell = "/bin/sh", | ||
| 67 | }); | ||
| 68 | defer pty.deinit(); | ||
| 69 | try std.testing.expect(pty.master_fd >= 0); | ||
| 70 | try std.testing.expect(pty.child_pid > 0); | ||
| 71 | } | ||