//! TTY implementation conforming to posix standards const Posix = @This(); const std = @import("std"); const builtin = @import("builtin"); const posix = std.posix; const Winsize = @import("../main.zig").Winsize; /// the original state of the terminal, prior to calling makeRaw termios: posix.termios, /// The file descriptor of the tty fd: posix.fd_t, pub const SignalHandler = struct { context: *anyopaque, callback: *const fn (context: *anyopaque) void, }; /// global signal handlers var handlers: [8]SignalHandler = undefined; var handler_mutex: std.Thread.Mutex = .{}; var handler_idx: usize = 0; /// global tty instance, used in case of a panic. Not guaranteed to work if /// for some reason there are multiple TTYs open under a single vaxis /// compilation unit - but this is better than nothing pub var global_tty: ?Posix = null; /// initializes a Tty instance by opening /dev/tty and "making it raw". A /// signal handler is installed for SIGWINCH. No callbacks are installed, be /// sure to register a callback when initializing the event loop pub fn init() !Posix { // Open our tty const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0); // Set the termios of the tty const termios = try makeRaw(fd); var act = posix.Sigaction{ .handler = .{ .handler = Posix.handleWinch }, .mask = switch (builtin.os.tag) { .macos => 0, .linux => posix.empty_sigset, else => @compileError("os not supported"), }, .flags = 0, }; try posix.sigaction(posix.SIG.WINCH, &act, null); const self: Posix = .{ .fd = fd, .termios = termios, }; global_tty = self; return self; } /// release resources associated with the Tty return it to its original state pub fn deinit(self: Posix) void { posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| { std.log.err("couldn't restore terminal: {}", .{err}); }; if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos posix.close(self.fd); } /// Write bytes to the tty pub fn write(self: *const Posix, bytes: []const u8) !usize { return posix.write(self.fd, bytes); } pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize { const self: *const Posix = @ptrCast(@alignCast(ptr)); return posix.write(self.fd, bytes); } pub fn anyWriter(self: *const Posix) std.io.AnyWriter { return .{ .context = self, .writeFn = Posix.opaqueWrite, }; } pub fn read(self: *const Posix, buf: []u8) !usize { return posix.read(self.fd, buf); } pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize { const self: *const Posix = @ptrCast(@alignCast(ptr)); return posix.read(self.fd, buf); } pub fn anyReader(self: *const Posix) std.io.AnyReader { return .{ .context = self, .readFn = Posix.opaqueRead, }; } /// Install a signal handler for winsize. A maximum of 8 handlers may be /// installed pub fn notifyWinsize(handler: SignalHandler) !void { handler_mutex.lock(); defer handler_mutex.unlock(); if (handler_idx == handlers.len) return error.OutOfMemory; handlers[handler_idx] = handler; handler_idx += 1; } fn handleWinch(_: c_int) callconv(.C) void { handler_mutex.lock(); defer handler_mutex.unlock(); var i: usize = 0; while (i < handler_idx) : (i += 1) { const handler = handlers[i]; handler.callback(handler.context); } } /// makeRaw enters the raw state for the terminal. pub fn makeRaw(fd: posix.fd_t) !posix.termios { const state = try posix.tcgetattr(fd); var raw = state; // see termios(3) raw.iflag.IGNBRK = false; raw.iflag.BRKINT = false; raw.iflag.PARMRK = false; raw.iflag.ISTRIP = false; raw.iflag.INLCR = false; raw.iflag.IGNCR = false; raw.iflag.ICRNL = false; raw.iflag.IXON = false; raw.oflag.OPOST = false; raw.lflag.ECHO = false; raw.lflag.ECHONL = false; raw.lflag.ICANON = false; raw.lflag.ISIG = false; raw.lflag.IEXTEN = false; raw.cflag.CSIZE = .CS8; raw.cflag.PARENB = false; raw.cc[@intFromEnum(posix.V.MIN)] = 1; raw.cc[@intFromEnum(posix.V.TIME)] = 0; try posix.tcsetattr(fd, .FLUSH, raw); return state; } /// Get the window size from the kernel pub fn getWinsize(fd: posix.fd_t) !Winsize { var winsize = posix.winsize{ .ws_row = 0, .ws_col = 0, .ws_xpixel = 0, .ws_ypixel = 0, }; const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize)); if (posix.errno(err) == .SUCCESS) return Winsize{ .rows = winsize.ws_row, .cols = winsize.ws_col, .x_pixel = winsize.ws_xpixel, .y_pixel = winsize.ws_ypixel, }; return error.IoctlError; } pub fn bufferedWriter(self: *const Posix) std.io.BufferedWriter(4096, std.io.AnyWriter) { return std.io.bufferedWriter(self.anyWriter()); }