// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! What is on the other side of the terminal: a program on a pseudo-terminal, //! a serial port, or a message saying why there is neither. //! //! Everything above this — the session, its IO thread, the window — sees a //! byte stream with something to wait on and a size it can be told about, //! and does not care which. A tagged union rather than a vtable, because the //! set is closed and known at compile time. //! //! What is waited on differs. On Linux it is one file descriptor, polled for //! being readable, or writable. On Windows, where nothing waits for a handle //! to become writable, writes are made whole, and reads are waited for on //! the handles `waitHandles` gives — or, for a backend with nothing to wait //! on, looked for every `pollInterval` milliseconds. The pseudo-terminal //! is a pty on Linux and a pseudoconsole on Windows, under one name. const std = @import("std"); const builtin = @import("builtin"); pub const Handle = @import("../os/handle.zig").Handle; pub const Pty = switch (builtin.os.tag) { .windows => @import("ConPty.zig"), else => @import("Pty.zig"), }; pub const Serial = @import("Serial.zig"); pub const Message = @import("Message.zig"); /// The most handles `waitHandles` gives. pub const max_wait_handles = 2; pub const Backend = union(enum) { pty: Pty, serial: Serial, message: Message, /// What an event loop polls for readability, on Linux. pub fn handle(b: *const Backend) Handle { return switch (b.*) { inline else => |*v| v.handle(), }; } /// What the IO thread waits on to be signaled before reading, on /// Windows; none, for a backend that is polled instead. pub fn waitHandles(b: *const Backend, buf: *[max_wait_handles]Handle) []const Handle { return switch (b.*) { inline else => |*v| v.waitHandles(buf), }; } /// How often, in milliseconds, to look for something to read from a /// backend with nothing to wait on; null for one with handles. pub fn pollInterval(b: *const Backend) ?u32 { return switch (b.*) { .serial => Serial.poll_interval_ms, else => null, }; } /// Reads what has arrived. Zero means the other side is gone for good: /// the program exited, or the port's device went away. pub fn read(b: *Backend, buf: []u8) ReadError!usize { return switch (b.*) { inline else => |*v| v.read(buf), }; } pub fn write(b: *Backend, bytes: []const u8) WriteError!usize { return switch (b.*) { inline else => |*v| v.write(bytes), }; } /// Tells the other side the grid changed size. A serial port has no /// way to be told, so this does nothing there. pub fn resize(b: *Backend, size: Size) void { switch (b.*) { inline else => |*v| v.resize(size), } } /// A line for the window title before the program sets one. pub fn describe(b: *const Backend, w: *std.Io.Writer) std.Io.Writer.Error!void { return switch (b.*) { inline else => |*v| v.describe(w), }; } pub fn deinit(b: *Backend) void { switch (b.*) { inline else => |*v| v.deinit(), } b.* = undefined; } }; /// How a program ended. pub const ExitStatus = union(enum) { /// It exited, with this status. exited: u32, /// A signal killed it. signaled: u32, /// Neither, as far as can be told. other, pub fn successful(s: ExitStatus) bool { return s == .exited and s.exited == 0; } }; /// The grid's size, in cells and in pixels. pub const Size = struct { cols: u16, rows: u16, width_px: u16 = 0, height_px: u16 = 0, }; pub const ReadError = error{ /// Nothing has arrived since the last read. WouldBlock, Unexpected, }; pub const WriteError = error{ /// The other side is not taking bytes yet; try again when the handle is /// writable. WouldBlock, /// The other side is gone. Closed, Unexpected, }; test { _ = Message; _ = Pty; _ = Serial; }