Something went wrong. Try again.
A GPU terminal emulator for Wayland and Windows on libghostty-vt, drawing with Vulkan, running a program on a pty or a serial port.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! A serial port, through zig-serial.//!//! The far end of a serial line has no idea how big the window is and no way//! to be told, so `resize` does nothing; full-screen programs on the far end//! are on their own, as they are with any serial terminal.
const std = @import("std");const serial = @import("serial");
const backend = @import("Backend.zig");
const Serial = @This();
port: serial.Port,/// For `describe`. Owned.path: []const u8,settings: serial.Settings,gpa: std.mem.Allocator,
pub const Options = struct { path: []const u8, settings: serial.Settings = .{},};
pub fn open(gpa: std.mem.Allocator, options: Options) (serial.OpenError || std.mem.Allocator.Error)!Serial { const path = try gpa.dupe(u8, options.path); errdefer gpa.free(path); var port = try serial.Port.open(options.path, options.settings, .{}); errdefer port.close(); // What the driver chose, which is what the title should say. const actual = port.current() catch options.settings; return .{ .port = port, .path = path, .settings = actual, .gpa = gpa };}
/// Windows has nothing to wait on for a port opened without overlapped/// IO, so it is looked at this often instead.pub const poll_interval_ms = 10;
pub fn handle(s: *const Serial) backend.Handle { return s.port.handle;}
pub fn waitHandles(s: *const Serial, buf: *[backend.max_wait_handles]backend.Handle) []const backend.Handle { _ = s; return buf[0..0];}
pub fn read(s: *Serial, buf: []u8) backend.ReadError!usize { return s.port.read(buf) catch |err| switch (err) { error.WouldBlock => error.WouldBlock, // The device went away; a USB adapter pulled out reads as this. error.InputOutput => 0, error.Unexpected => error.Unexpected, };}
pub fn write(s: *Serial, bytes: []const u8) backend.WriteError!usize { return s.port.write(bytes) catch |err| switch (err) { error.WouldBlock => error.WouldBlock, error.InputOutput => error.Closed, error.Unexpected => error.Unexpected, };}
pub fn resize(s: *Serial, size: backend.Size) void { _ = s; _ = size;}
/// `/dev/ttyUSB0 115200 8N1`, the way every serial program has said it.pub fn describe(s: *const Serial, w: *std.Io.Writer) std.Io.Writer.Error!void { const parity: u8 = switch (s.settings.parity) { .none => 'N', .even => 'E', .odd => 'O', .mark => 'M', .space => 'S', }; const stop: []const u8 = switch (s.settings.stop_bits) { .one => "1", .one_and_half => "1.5", .two => "2", }; try w.print("{s} {d} {d}{c}{s}", .{ s.path, s.settings.baud, @backingInt(s.settings.data_bits), parity, stop, });}
pub fn deinit(s: *Serial) void { s.port.close(); s.gpa.free(s.path);}
test "describe says rate and framing" { var s: Serial = .{ .port = undefined, .path = "/dev/ttyS0", .settings = .{ .baud = 9600, .data_bits = .seven, .parity = .even, .stop_bits = .two }, .gpa = std.testing.allocator, }; var buf: [64]u8 = undefined; var w: std.Io.Writer = .fixed(&buf); try s.describe(&w); try std.testing.expectEqualStrings("/dev/ttyS0 9600 7E2", w.buffered());}