this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

vt example compiles but does not work correctly

authored by

Jeffrey C. Ollie and committed by
Tim Culverhouse
c6bba620 e905abb9

+142 -127
+13 -22
examples/vt.zig
··· 9 9 10 10 pub const panic = vaxis.panic_handler; 11 11 12 - pub fn main() !void { 13 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 14 - defer { 15 - const deinit_status = gpa.deinit(); 16 - //fail test; can't try in defer as defer is executed after we return 17 - if (deinit_status == .leak) { 18 - std.log.err("memory leak", .{}); 19 - } 20 - } 21 - const alloc = gpa.allocator(); 12 + pub fn main(init: std.process.Init) !void { 13 + const io = init.io; 14 + const alloc = init.gpa; 22 15 23 16 var buffer: [1024]u8 = undefined; 24 - var tty = try vaxis.Tty.init(&buffer); 17 + var tty: vaxis.Tty = try .init(io, &buffer); 25 18 const writer = tty.writer(); 26 - var vx = try vaxis.init(alloc, .{}); 19 + var vx = try vaxis.init(io, alloc, init.environ_map, .{}); 27 20 defer vx.deinit(alloc, writer); 28 21 29 - var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 30 - try loop.init(); 22 + var loop: vaxis.Loop(Event) = .init(io, &tty, &vx); 31 23 32 24 try loop.start(); 33 25 defer loop.stop(); 34 26 35 27 try vx.enterAltScreen(writer); 36 28 try vx.queryTerminal(writer, 1 * std.time.ns_per_s); 37 - var env = try std.process.getEnvMap(alloc); 38 - defer env.deinit(); 39 29 40 30 const vt_opts: vaxis.widgets.Terminal.Options = .{ 41 31 .winsize = .{ ··· 45 35 .y_pixel = 0, 46 36 }, 47 37 .scrollback_size = 0, 48 - .initial_working_directory = env.get("HOME") orelse @panic("no $HOME"), 38 + .initial_working_directory = init.environ_map.get("HOME") orelse @panic("no $HOME"), 49 39 }; 50 - const shell = env.get("SHELL") orelse "bash"; 40 + const shell = init.environ_map.get("SHELL") orelse "bash"; 51 41 const argv = [_][]const u8{shell}; 52 42 var write_buf: [4096]u8 = undefined; 53 43 var vt = try vaxis.widgets.Terminal.init( 44 + io, 54 45 alloc, 55 46 &argv, 56 - &env, 47 + init.environ_map, 57 48 vt_opts, 58 49 &write_buf, 59 50 ); ··· 62 53 63 54 var redraw: bool = false; 64 55 while (true) { 65 - std.Thread.sleep(8 * std.time.ns_per_ms); 56 + try io.sleep(.fromMilliseconds(8), .real); 66 57 // try vt events first 67 - while (vt.tryEvent()) |event| { 58 + while (try vt.tryEvent()) |event| { 68 59 redraw = true; 69 60 switch (event) { 70 61 .bell => {}, ··· 74 65 .pwd_change => {}, 75 66 } 76 67 } 77 - while (loop.tryEvent()) |event| { 68 + while (try loop.tryEvent()) |event| { 78 69 redraw = true; 79 70 switch (event) { 80 71 .key_press => |key| {
+53 -45
src/widgets/terminal/Command.zig
··· 5 5 const Pty = @import("Pty.zig"); 6 6 const Terminal = @import("Terminal.zig"); 7 7 8 + const linux = std.os.linux; 8 9 const posix = std.posix; 9 10 10 11 argv: []const []const u8, ··· 14 15 // Set after spawn() 15 16 pid: ?std.posix.pid_t = null, 16 17 17 - env_map: *const std.process.EnvMap, 18 + env_map: *const std.process.Environ.Map, 18 19 19 20 pty: Pty, 20 21 21 - pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void { 22 + pub fn spawn(self: *Command, io: std.Io, allocator: std.mem.Allocator) !void { 22 23 var arena_allocator = std.heap.ArenaAllocator.init(allocator); 23 24 defer arena_allocator.deinit(); 24 25 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(); 26 + const pid = pid: { 27 + const rc = linux.fork(); 28 + break :pid switch (linux.errno(rc)) { 29 + .SUCCESS => rc, 30 + else => return error.ForkError, 31 + }; 32 + }; 33 33 if (pid == 0) { 34 34 // we are the child 35 35 _ = std.os.linux.setsid(); ··· 39 39 if (posix.system.ioctl(self.pty.tty.handle, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError; 40 40 41 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); 42 + { 43 + const rc = linux.dup2(self.pty.tty.handle, std.posix.STDIN_FILENO); 44 + switch (linux.errno(rc)) { 45 + .SUCCESS => {}, 46 + else => return error.Dup2Failed, 47 + } 48 + } 49 + { 50 + const rc = linux.dup2(self.pty.tty.handle, std.posix.STDOUT_FILENO); 51 + switch (linux.errno(rc)) { 52 + .SUCCESS => {}, 53 + else => return error.Dup2Failed, 54 + } 55 + } 56 + { 57 + const rc = linux.dup2(self.pty.tty.handle, std.posix.STDERR_FILENO); 58 + switch (linux.errno(rc)) { 59 + .SUCCESS => {}, 60 + else => return error.Dup2Failed, 61 + } 51 62 } 63 + self.pty.tty.close(io); 64 + if (self.pty.pty.handle > 2) self.pty.pty.close(io); 65 + 66 + // if (self.working_directory) |wd| { 67 + // try std.posix.chdir(wd); 68 + // } 52 69 53 70 // exec 54 - const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp); 55 - _ = err catch {}; 71 + std.process.replace(io, .{ 72 + .argv = self.argv, 73 + .environ_map = self.env_map, 74 + }) catch {}; 56 75 } 57 76 58 77 // we are the parent ··· 75 94 return; 76 95 } 77 96 78 - fn handleSigChild(_: c_int) callconv(.c) void { 79 - const result = std.posix.waitpid(-1, 0); 97 + fn handleSigChild(_: posix.SIG) callconv(.c) void { 98 + var status: u32 = undefined; 99 + const rc = linux.waitpid(-1, &status, 0); 100 + const pid: i32 = switch (linux.errno(rc)) { 101 + .SUCCESS => @intCast(rc), 102 + else => return, 103 + }; 80 104 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 - } 105 + Terminal.global_vt_mutex.lock(Terminal.global_io) catch return; 106 + defer Terminal.global_vt_mutex.unlock(Terminal.global_io); 107 + var vt = Terminal.global_vts.get(pid) orelse return; 108 + vt.event_queue.push(.exited) catch {}; 87 109 } 88 110 89 111 pub fn kill(self: *Command) void { ··· 97 119 /// hash map plus options. 98 120 fn createEnvironFromMap( 99 121 arena: std.mem.Allocator, 100 - map: *const std.process.EnvMap, 122 + map: *const std.process.Environ.Map, 101 123 ) ![: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; 124 + return try map.createPosixBlock(arena, .{}); 117 125 }
+22 -15
src/widgets/terminal/Pty.zig
··· 5 5 const builtin = @import("builtin"); 6 6 const Winsize = @import("../../main.zig").Winsize; 7 7 8 + const linux = std.os.linux; 8 9 const posix = std.posix; 9 10 10 - pty: std.fs.File, 11 - tty: std.fs.File, 11 + pty: std.Io.File, 12 + tty: std.Io.File, 12 13 13 14 /// opens a new tty/pty pair 14 - pub fn init() !Pty { 15 + pub fn init(io: std.Io) !Pty { 15 16 switch (builtin.os.tag) { 16 - .linux => return openPtyLinux(), 17 + .linux => return openPtyLinux(io), 17 18 else => @compileError("unsupported os"), 18 19 } 19 20 } 20 21 21 22 /// closes the tty and pty 22 - pub fn deinit(self: Pty) void { 23 - self.pty.close(); 24 - self.tty.close(); 23 + pub fn deinit(self: Pty, io: std.Io) void { 24 + self.pty.close(io); 25 + self.tty.close(io); 25 26 } 26 27 27 28 /// sets the size of the pty ··· 36 37 return error.SetWinsizeError; 37 38 } 38 39 39 - fn openPtyLinux() !Pty { 40 - const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0); 41 - errdefer posix.close(p); 40 + fn openPtyLinux(io: std.Io) !Pty { 41 + const pty = try std.Io.Dir.openFileAbsolute(io, "/dev/ptmx", .{ 42 + .mode = .read_write, 43 + .allow_ctty = false, 44 + }); 45 + errdefer pty.close(io); 42 46 43 47 // unlockpt 44 48 var n: c_uint = 0; 45 - if (posix.system.ioctl(p, posix.T.IOCSPTLCK, @intFromPtr(&n)) != 0) return error.IoctlError; 49 + if (posix.system.ioctl(pty.handle, posix.T.IOCSPTLCK, @intFromPtr(&n)) != 0) return error.IoctlError; 46 50 47 51 // ptsname 48 - if (posix.system.ioctl(p, posix.T.IOCGPTN, @intFromPtr(&n)) != 0) return error.IoctlError; 52 + if (posix.system.ioctl(pty.handle, posix.T.IOCGPTN, @intFromPtr(&n)) != 0) return error.IoctlError; 49 53 var buf: [16]u8 = undefined; 50 54 const sname = try std.fmt.bufPrint(&buf, "/dev/pts/{d}", .{n}); 51 55 std.log.debug("pts: {s}", .{sname}); 52 56 53 - const t = try posix.open(sname, .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0); 57 + const tty = try std.Io.Dir.openFileAbsolute(io, sname, .{ 58 + .mode = .read_write, 59 + .allow_ctty = false, 60 + }); 54 61 55 62 return .{ 56 - .pty = .{ .handle = p }, 57 - .tty = .{ .handle = t }, 63 + .pty = pty, 64 + .tty = tty, 58 65 }; 59 66 }
+54 -45
src/widgets/terminal/Terminal.zig
··· 43 43 key_press: vaxis.Key, 44 44 }; 45 45 46 - pub var global_vt_mutex: std.Thread.Mutex = .{}; 47 - pub var global_vts: ?std.AutoHashMap(i32, *Terminal) = null; 46 + pub var global_io: std.Io = undefined; 47 + pub var global_io_initialized: bool = false; 48 + pub var global_vt_mutex: std.Io.Mutex = .init; 49 + pub var global_vts: std.AutoHashMapUnmanaged(i32, *Terminal) = .empty; 48 50 pub var global_sigchild_installed: bool = false; 49 51 52 + io: std.Io, 50 53 allocator: std.mem.Allocator, 51 54 scrollback_size: u16, 52 55 53 56 pty: Pty, 54 - pty_writer: std.fs.File.Writer, 57 + pty_writer: std.Io.File.Writer, 55 58 cmd: Command, 56 - thread: ?std.Thread = null, 59 + thread: ?std.Io.Future(void) = null, 57 60 58 61 /// the screen we draw from 59 62 front_screen: Screen, 60 - front_mutex: std.Thread.Mutex = .{}, 63 + front_mutex: std.Io.Mutex = .init, 61 64 62 65 /// the back screens 63 66 back_screen: *Screen = undefined, ··· 65 68 back_screen_alt: Screen, 66 69 // only applies to primary screen 67 70 scroll_offset: usize = 0, 68 - back_mutex: std.Thread.Mutex = .{}, 71 + back_mutex: std.Io.Mutex = .init, 69 72 // dirty is protected by back_mutex. Only access this field when you hold that mutex 70 73 dirty: bool = false, 71 74 ··· 79 82 80 83 last_printed: []const u8 = "", 81 84 82 - event_queue: Queue = .{}, 85 + event_queue: Queue, 83 86 84 87 /// initialize a Terminal. This sets the size of the underlying pty and allocates the sizes of the 85 88 /// screen 86 89 pub fn init( 90 + io: std.Io, 87 91 allocator: std.mem.Allocator, 88 92 argv: []const []const u8, 89 - env: *const std.process.EnvMap, 93 + env: *const std.process.Environ.Map, 90 94 opts: Options, 91 95 write_buf: []u8, 92 96 ) !Terminal { 97 + if (!global_io_initialized) { 98 + global_io = io; 99 + global_io_initialized = true; 100 + } 93 101 // Verify we have an absolute path 94 102 if (opts.initial_working_directory) |pwd| { 95 103 if (!std.fs.path.isAbsolute(pwd)) return error.InvalidWorkingDirectory; 96 104 } 97 - const pty = try Pty.init(); 105 + const pty = try Pty.init(io); 98 106 try pty.setSize(opts.winsize); 99 107 const cmd: Command = .{ 100 108 .argv = argv, ··· 108 116 try tabs.append(allocator, col); 109 117 } 110 118 return .{ 119 + .io = io, 111 120 .allocator = allocator, 112 121 .pty = pty, 113 - .pty_writer = pty.pty.writerStreaming(write_buf), 122 + .pty_writer = pty.pty.writerStreaming(io, write_buf), 114 123 .cmd = cmd, 115 124 .scrollback_size = opts.scrollback_size, 116 125 .front_screen = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows), 117 126 .back_screen_pri = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows + opts.scrollback_size), 118 127 .back_screen_alt = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows), 119 128 .tab_stops = tabs, 129 + .event_queue = .init(io), 120 130 }; 121 131 } 122 132 ··· 125 135 self.should_quit = true; 126 136 127 137 pid: { 128 - global_vt_mutex.lock(); 129 - defer global_vt_mutex.unlock(); 130 - var vts = global_vts orelse break :pid; 138 + global_vt_mutex.lock(self.io) catch break :pid; 139 + defer global_vt_mutex.unlock(self.io); 131 140 if (self.cmd.pid) |pid| 132 - _ = vts.remove(pid); 133 - if (vts.count() == 0) { 134 - vts.deinit(); 135 - global_vts = null; 141 + _ = global_vts.remove(pid); 142 + if (global_vts.count() == 0) { 143 + global_vts.deinit(self.allocator); 136 144 } 137 145 } 138 146 self.cmd.kill(); 139 - if (self.thread) |thread| { 147 + if (self.thread) |*thread| { 140 148 // write an EOT into the tty to trigger a read on our thread 141 149 const EOT = "\x04"; 142 - _ = self.pty.tty.write(EOT) catch {}; 143 - thread.join(); 150 + self.pty.tty.writeStreamingAll(self.io, EOT) catch {}; 151 + thread.await(self.io); 144 152 self.thread = null; 145 153 } 146 - self.pty.deinit(); 154 + self.pty.deinit(self.io); 147 155 self.front_screen.deinit(self.allocator); 148 156 self.back_screen_pri.deinit(self.allocator); 149 157 self.back_screen_alt.deinit(self.allocator); ··· 156 164 if (self.thread != null) return; 157 165 self.back_screen = &self.back_screen_pri; 158 166 159 - try self.cmd.spawn(self.allocator); 167 + try self.cmd.spawn(self.io, self.allocator); 160 168 161 169 self.working_directory.clearRetainingCapacity(); 162 170 if (self.cmd.working_directory) |pwd| { 163 171 try self.working_directory.appendSlice(self.allocator, pwd); 164 172 } else { 165 - const pwd = std.fs.cwd(); 166 - var buffer: [std.fs.max_path_bytes]u8 = undefined; 167 - const out_path = try std.os.getFdPath(pwd.fd, &buffer); 173 + const pwd: std.Io.Dir = .cwd(); 174 + const out_path = try pwd.realPathFileAlloc(self.io, ".", self.allocator); 168 175 try self.working_directory.appendSlice(self.allocator, out_path); 169 176 } 170 177 171 178 { 172 179 // add to our global list 173 - global_vt_mutex.lock(); 174 - defer global_vt_mutex.unlock(); 175 - if (global_vts == null) 176 - global_vts = std.AutoHashMap(i32, *Terminal).init(self.allocator); 180 + try global_vt_mutex.lock(self.io); 181 + defer global_vt_mutex.unlock(self.io); 177 182 if (self.cmd.pid) |pid| 178 - try global_vts.?.put(pid, self); 183 + try global_vts.put(self.allocator, pid, self); 179 184 } 180 185 181 - self.thread = try std.Thread.spawn(.{}, Terminal.run, .{self}); 186 + self.thread = try self.io.concurrent(Terminal.run, .{self}); 182 187 } 183 188 184 189 /// resize the screen. Locks access to the back screen. Should only be called from the main thread. ··· 190 195 ws.rows == self.front_screen.height) 191 196 return; 192 197 193 - self.back_mutex.lock(); 194 - defer self.back_mutex.unlock(); 198 + try self.back_mutex.lock(self.io); 199 + defer self.back_mutex.unlock(self.io); 195 200 196 201 self.front_screen.deinit(self.allocator); 197 202 self.front_screen = try Screen.init(self.allocator, ws.cols, ws.rows); ··· 206 211 207 212 pub fn draw(self: *Terminal, allocator: std.mem.Allocator, win: vaxis.Window) !void { 208 213 if (self.back_mutex.tryLock()) { 209 - defer self.back_mutex.unlock(); 214 + defer self.back_mutex.unlock(self.io); 210 215 // We keep this as a separate condition so we don't deadlock by obtaining the lock but not 211 216 // having sync 212 217 if (!self.mode.sync) { ··· 231 236 } 232 237 } 233 238 234 - pub fn tryEvent(self: *Terminal) ?Event { 235 - return self.event_queue.tryPop(); 239 + pub fn tryEvent(self: *Terminal) !?Event { 240 + return try self.event_queue.tryPop(); 236 241 } 237 242 238 243 pub fn update(self: *Terminal, event: InputEvent) !void { ··· 249 254 return &self.pty_writer.interface; 250 255 } 251 256 252 - fn reader(self: *const Terminal, buf: []u8) std.fs.File.Reader { 253 - return self.pty.pty.readerStreaming(buf); 257 + fn reader(self: *const Terminal, buf: []u8) std.Io.File.Reader { 258 + return self.pty.pty.readerStreaming(self.io, buf); 254 259 } 255 260 256 261 /// process the output from the command on the pty 257 - fn run(self: *Terminal) !void { 262 + fn run(self: *Terminal) void { 263 + self._run() catch {}; 264 + } 265 + 266 + fn _run(self: *Terminal) !void { 258 267 var parser: Parser = .{ 259 268 .buf = try .initCapacity(self.allocator, 128), 260 269 }; ··· 265 274 266 275 while (!self.should_quit) { 267 276 const event = try parser.parseReader(&reader_.interface); 268 - self.back_mutex.lock(); 269 - defer self.back_mutex.unlock(); 277 + try self.back_mutex.lock(self.io); 278 + defer self.back_mutex.unlock(self.io); 270 279 271 - if (!self.dirty and self.event_queue.tryPush(.redraw)) 280 + if (!self.dirty and try self.event_queue.tryPush(.redraw)) 272 281 self.dirty = true; 273 282 274 283 switch (event) { ··· 673 682 0 => { 674 683 self.title.clearRetainingCapacity(); 675 684 try self.title.appendSlice(self.allocator, osc[semicolon + 1 ..]); 676 - self.event_queue.push(.{ .title_change = self.title.items }); 685 + try self.event_queue.push(.{ .title_change = self.title.items }); 677 686 }, 678 687 7 => { 679 688 // OSC 7 ; file:// <hostname> <pwd> ··· 693 702 } else enc[i]; 694 703 try self.working_directory.append(self.allocator, b); 695 704 } 696 - self.event_queue.push(.{ .pwd_change = self.working_directory.items }); 705 + try self.event_queue.push(.{ .pwd_change = self.working_directory.items }); 697 706 }, 698 707 else => log.info("unhandled osc: {s}", .{osc}), 699 708 } ··· 708 717 .NUL, .SOH, .STX => {}, 709 718 .EOT => {}, // we send EOT to quit the read thread 710 719 .ENQ => {}, 711 - .BEL => self.event_queue.push(.bell), 720 + .BEL => try self.event_queue.push(.bell), 712 721 .BS => self.back_screen.cursorLeft(1), 713 722 .HT => self.horizontalTab(1), 714 723 .LF, .VT, .FF => try self.back_screen.index(),