a modern tui library written in zig
at main 3.4 kB view raw
1const Command = @This(); 2 3const std = @import("std"); 4const builtin = @import("builtin"); 5const Pty = @import("Pty.zig"); 6const Terminal = @import("Terminal.zig"); 7 8const posix = std.posix; 9 10argv: []const []const u8, 11 12working_directory: ?[]const u8, 13 14// Set after spawn() 15pid: ?std.posix.pid_t = null, 16 17env_map: *const std.process.EnvMap, 18 19pty: Pty, 20 21pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void { 22 var arena_allocator = std.heap.ArenaAllocator.init(allocator); 23 defer arena_allocator.deinit(); 24 25 const arena = arena_allocator.allocator(); 26 27 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null); 28 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; 29 30 const envp = try createEnvironFromMap(arena, self.env_map); 31 32 const pid = try std.posix.fork(); 33 if (pid == 0) { 34 // we are the child 35 _ = std.os.linux.setsid(); 36 37 // set the controlling terminal 38 var u: c_uint = std.posix.STDIN_FILENO; 39 if (posix.system.ioctl(self.pty.tty.handle, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError; 40 41 // set up io 42 try posix.dup2(self.pty.tty.handle, std.posix.STDIN_FILENO); 43 try posix.dup2(self.pty.tty.handle, std.posix.STDOUT_FILENO); 44 try posix.dup2(self.pty.tty.handle, std.posix.STDERR_FILENO); 45 46 self.pty.tty.close(); 47 if (self.pty.pty.handle > 2) self.pty.pty.close(); 48 49 if (self.working_directory) |wd| { 50 try std.posix.chdir(wd); 51 } 52 53 // exec 54 const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp); 55 _ = err catch {}; 56 } 57 58 // we are the parent 59 self.pid = @intCast(pid); 60 61 if (!Terminal.global_sigchild_installed) { 62 Terminal.global_sigchild_installed = true; 63 var act = posix.Sigaction{ 64 .handler = .{ .handler = handleSigChild }, 65 .mask = switch (builtin.os.tag) { 66 .macos => 0, 67 .linux => posix.sigemptyset(), 68 else => @compileError("os not supported"), 69 }, 70 .flags = 0, 71 }; 72 posix.sigaction(posix.SIG.CHLD, &act, null); 73 } 74 75 return; 76} 77 78fn handleSigChild(_: c_int) callconv(.c) void { 79 const result = std.posix.waitpid(-1, 0); 80 81 Terminal.global_vt_mutex.lock(); 82 defer Terminal.global_vt_mutex.unlock(); 83 if (Terminal.global_vts) |vts| { 84 var vt = vts.get(result.pid) orelse return; 85 vt.event_queue.push(.exited); 86 } 87} 88 89pub fn kill(self: *Command) void { 90 if (self.pid) |pid| { 91 std.posix.kill(pid, std.posix.SIG.TERM) catch {}; 92 self.pid = null; 93 } 94} 95 96/// Creates a null-deliminated environment variable block in the format expected by POSIX, from a 97/// hash map plus options. 98fn createEnvironFromMap( 99 arena: std.mem.Allocator, 100 map: *const std.process.EnvMap, 101) ![:null]?[*:0]u8 { 102 const envp_count: usize = map.count(); 103 104 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); 105 var i: usize = 0; 106 107 { 108 var it = map.iterator(); 109 while (it.next()) |pair| { 110 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); 111 i += 1; 112 } 113 } 114 115 std.debug.assert(i == envp_count); 116 return envp_buf; 117}