c884437a
feat: scaffold muxd; prove ghostty-vt drives headless
a73x 2026-08-07 06:52
Commit message
Makefile
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,17 @@ | |||
| 1 | # ghostty (pinned in build.zig.zon) hard-requires Zig 0.15.x; the system | ||
| 2 | # default zig is 0.17-dev. Override with ZIG=... if yours lives elsewhere. | ||
| 3 | ZIG ?= $(HOME)/Downloads/zig-x86_64-linux-0.15.2/zig | ||
| 4 | |||
| 5 | .PHONY: build test e2e clean | ||
| 6 | |||
| 7 | build: | ||
| 8 | $(ZIG) build | ||
| 9 | |||
| 10 | test: | ||
| 11 | $(ZIG) build test | ||
| 12 | |||
| 13 | e2e: | ||
| 14 | $(ZIG) build e2e | ||
| 15 | |||
| 16 | clean: | ||
| 17 | rm -rf zig-out .zig-cache | ||
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,64 @@ | |||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn build(b: *std.Build) void { | ||
| 4 | const target = b.standardTargetOptions(.{}); | ||
| 5 | const optimize = b.standardOptimizeOption(.{}); | ||
| 6 | |||
| 7 | const ghostty_dep = b.lazyDependency("ghostty", .{ | ||
| 8 | .target = target, | ||
| 9 | .optimize = optimize, | ||
| 10 | }); | ||
| 11 | |||
| 12 | const engine_mod = b.createModule(.{ | ||
| 13 | .root_source_file = b.path("src/engine.zig"), | ||
| 14 | .target = target, | ||
| 15 | .optimize = optimize, | ||
| 16 | }); | ||
| 17 | if (ghostty_dep) |dep| { | ||
| 18 | engine_mod.addImport("ghostty-vt", dep.module("ghostty-vt")); | ||
| 19 | } | ||
| 20 | |||
| 21 | const pty_mod = b.createModule(.{ | ||
| 22 | .root_source_file = b.path("src/pty.zig"), | ||
| 23 | .target = target, | ||
| 24 | .optimize = optimize, | ||
| 25 | .link_libc = true, | ||
| 26 | }); | ||
| 27 | |||
| 28 | const debug_mod = b.createModule(.{ | ||
| 29 | .root_source_file = b.path("src/debug.zig"), | ||
| 30 | .target = target, | ||
| 31 | .optimize = optimize, | ||
| 32 | }); | ||
| 33 | debug_mod.addImport("engine", engine_mod); | ||
| 34 | |||
| 35 | const exe_mod = b.createModule(.{ | ||
| 36 | .root_source_file = b.path("src/main.zig"), | ||
| 37 | .target = target, | ||
| 38 | .optimize = optimize, | ||
| 39 | .link_libc = true, | ||
| 40 | }); | ||
| 41 | exe_mod.addImport("engine", engine_mod); | ||
| 42 | exe_mod.addImport("pty", pty_mod); | ||
| 43 | exe_mod.addImport("debug", debug_mod); | ||
| 44 | |||
| 45 | const exe = b.addExecutable(.{ .name = "muxd", .root_module = exe_mod }); | ||
| 46 | // Zig 0.15's self-hosted x86_64 linker can't handle the .sframe | ||
| 47 | // sections emitted by gcc >= 16 crt1.o on this system; LLD can. | ||
| 48 | exe.use_llvm = true; | ||
| 49 | exe.use_lld = true; | ||
| 50 | b.installArtifact(exe); | ||
| 51 | |||
| 52 | const test_step = b.step("test", "Run unit tests"); | ||
| 53 | for ([_]*std.Build.Module{ engine_mod, pty_mod, debug_mod }) |mod| { | ||
| 54 | const t = b.addTest(.{ .root_module = mod }); | ||
| 55 | t.use_llvm = true; | ||
| 56 | t.use_lld = true; | ||
| 57 | test_step.dependOn(&b.addRunArtifact(t).step); | ||
| 58 | } | ||
| 59 | |||
| 60 | const e2e = b.addSystemCommand(&.{"test/e2e.sh"}); | ||
| 61 | e2e.addArtifactArg(exe); | ||
| 62 | const e2e_step = b.step("e2e", "Run end-to-end test"); | ||
| 63 | e2e_step.dependOn(&e2e.step); | ||
| 64 | } | ||
build.zig.zon
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,18 @@ | |||
| 1 | .{ | ||
| 2 | .name = .mux, | ||
| 3 | .version = "0.0.1", | ||
| 4 | .fingerprint = 0xb3d8f5dcb89714b5, | ||
| 5 | .minimum_zig_version = "0.15.2", | ||
| 6 | .paths = .{ | ||
| 7 | "build.zig", | ||
| 8 | "build.zig.zon", | ||
| 9 | "src", | ||
| 10 | }, | ||
| 11 | .dependencies = .{ | ||
| 12 | .ghostty = .{ | ||
| 13 | .url = "git+https://github.com/ghostty-org/ghostty#853183e911b70ff7b61057f52fc7b47ea4934238", | ||
| 14 | .hash = "ghostty-1.3.2-dev-5UdBC7VOBgVv0iA-qLRtBnau_zLIv7iGGLdnEiW6fUYU", | ||
| 15 | .lazy = true, | ||
| 16 | }, | ||
| 17 | }, | ||
| 18 | } | ||
src/debug.zig
No textual changes available.
src/engine.zig
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,19 @@ | |||
| 1 | //! Authoritative headless terminal engine. Wraps ghostty-vt's Terminal | ||
| 2 | //! and TerminalStream behind the small surface muxd needs. | ||
| 3 | const std = @import("std"); | ||
| 4 | const vt = @import("ghostty-vt"); | ||
| 5 | |||
| 6 | test "ghostty-vt boots headless and text lands in the grid" { | ||
| 7 | const alloc = std.testing.allocator; | ||
| 8 | var term = try vt.Terminal.init(alloc, .{ .cols = 80, .rows = 24 }); | ||
| 9 | defer term.deinit(alloc); | ||
| 10 | |||
| 11 | var stream: vt.TerminalStream = .initAlloc(alloc, .{ .terminal = &term }); | ||
| 12 | defer stream.deinit(); | ||
| 13 | |||
| 14 | stream.nextSlice("hello"); | ||
| 15 | |||
| 16 | const s = try term.plainString(alloc); | ||
| 17 | defer alloc.free(s); | ||
| 18 | try std.testing.expectEqualStrings("hello", s); | ||
| 19 | } | ||
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1 @@ | |||
| 1 | pub fn main() !void {} | ||
src/pty.zig
No textual changes available.