repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

commit
5389d30
parent
b79c8c7
author
Eric Bower
date
2026-04-26 22:48:18 -0400 EDT
feat: cross-compile from linux to mac

Latest version of libghostty now supports cross-compiling without needing
Apple sdk tools.
3 files changed,  +32, -36
M build.zig
+17, -21
 1@@ -36,15 +36,14 @@ pub fn build(b: *std.Build) void {
 2     });
 3     exe_mod.addOptions("build_options", options);
 4 
 5-    if (b.lazyDependency("ghostty", .{
 6+    const dep = b.dependency("ghostty", .{
 7         .target = target,
 8         .optimize = optimize,
 9-    })) |dep| {
10-        exe_mod.addImport(
11-            "ghostty-vt",
12-            dep.module("ghostty-vt"),
13-        );
14-    }
15+    });
16+    exe_mod.addImport(
17+        "ghostty-vt",
18+        dep.module("ghostty-vt"),
19+    );
20 
21     // Run
22     {
23@@ -69,15 +68,14 @@ pub fn build(b: *std.Build) void {
24             .target = target,
25             .optimize = optimize,
26         });
27-        if (b.lazyDependency("ghostty", .{
28+        const test_dep = b.dependency("ghostty", .{
29             .target = target,
30             .optimize = optimize,
31-        })) |dep| {
32-            test_module.addImport(
33-                "ghostty-vt",
34-                dep.module("ghostty-vt"),
35-            );
36-        }
37+        });
38+        test_module.addImport(
39+            "ghostty-vt",
40+            test_dep.module("ghostty-vt"),
41+        );
42         const exe_unit_tests = b.addTest(.{
43             .root_module = test_module,
44         });
45@@ -108,15 +106,13 @@ pub fn build(b: *std.Build) void {
46         check.dependOn(&exe_check.step);
47     }
48 
49-    // Release step - macOS can cross-compile to Linux,
50-    // but Linux cannot cross-compile to macOS (needs SDK)
51+    // Release step - cross-compile to all targets from any host
52     {
53         const release_step = b.step(
54             "release",
55-            "Build release binaries (macOS builds all, Linux builds Linux only)",
56+            "Build release binaries for all platforms",
57         );
58-        const native_os = @import("builtin").os.tag;
59-        const release_targets = if (native_os == .macos) linux_targets ++ macos_targets else linux_targets;
60+        const release_targets = linux_targets ++ macos_targets;
61         for (release_targets) |release_target| {
62             const resolved = b.resolveTargetQuery(release_target);
63             const release_mod = b.createModule(.{
64@@ -129,8 +125,8 @@ pub fn build(b: *std.Build) void {
65             if (b.lazyDependency("ghostty", .{
66                 .target = resolved,
67                 .optimize = .ReleaseSafe,
68-            })) |dep| {
69-                release_mod.addImport("ghostty-vt", dep.module("ghostty-vt"));
70+            })) |release_dep| {
71+                release_mod.addImport("ghostty-vt", release_dep.module("ghostty-vt"));
72             }
73 
74             const release_exe = b.addExecutable(.{
M src/main.zig
+2, -2
 1@@ -1135,7 +1135,7 @@ const Daemon = struct {
 2     }
 3 
 4     pub fn handleOutput(self: *Daemon, payload: []const u8, vt_stream: anytype) !void {
 5-        try vt_stream.nextSlice(payload);
 6+        vt_stream.nextSlice(payload);
 7         self.has_pty_output = true;
 8         for (self.clients.items) |client| {
 9             try ipc.appendMessage(self.alloc, &client.write_buf, .Output, payload);
10@@ -2417,7 +2417,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
11                     break :daemon_loop;
12                 } else {
13                     // Feed PTY output to terminal emulator for state tracking
14-                    try vt_stream.nextSlice(buf[0..n]);
15+                    vt_stream.nextSlice(buf[0..n]);
16                     daemon.has_pty_output = true;
17 
18                     // When no real terminal client has attached yet, respond to
M src/util.zig
+13, -13
 1@@ -980,9 +980,9 @@ test "serializeTerminalState excludes synchronized output replay" {
 2     var stream = term.vtStream();
 3     defer stream.deinit();
 4 
 5-    try stream.nextSlice("\x1b[?2004h"); // Bracketed paste
 6-    try stream.nextSlice("\x1b[?2026h"); // Synchronized output
 7-    try stream.nextSlice("hello");
 8+    stream.nextSlice("\x1b[?2004h"); // Bracketed paste
 9+    stream.nextSlice("\x1b[?2026h"); // Synchronized output
10+    stream.nextSlice("hello");
11 
12     try testing.expect(term.modes.get(.bracketed_paste));
13     try testing.expect(term.modes.get(.synchronized_output));
14@@ -1005,7 +1005,7 @@ fn testCreateTerminal(alloc: std.mem.Allocator, cols: u16, rows: u16, vt_data: [
15     if (vt_data.len > 0) {
16         var stream = term.vtStream();
17         defer stream.deinit();
18-        try stream.nextSlice(vt_data);
19+        stream.nextSlice(vt_data);
20     }
21     return term;
22 }
23@@ -1036,7 +1036,7 @@ fn serializeRoundtrip(alloc: std.mem.Allocator, source: *ghostty_vt.Terminal) !g
24     });
25     var stream = dest.vtStream();
26     defer stream.deinit();
27-    try stream.nextSlice(serialized);
28+    stream.nextSlice(serialized);
29     return dest;
30 }
31 
32@@ -1107,11 +1107,11 @@ test "serializeTerminalState with scrollback preserves visible content" {
33     var buf: [32]u8 = undefined;
34     for (0..80) |i| {
35         const line = std.fmt.bufPrint(&buf, "SCROLL_{d}\r\n", .{i}) catch unreachable;
36-        try stream.nextSlice(line);
37+        stream.nextSlice(line);
38     }
39 
40     // Clear screen and place markers at specific positions
41-    try stream.nextSlice("\x1b[2J" ++
42+    stream.nextSlice("\x1b[2J" ++
43         "\x1b[2;5HMARK_A" ++
44         "\x1b[6;15HMARK_B" ++
45         "\x1b[10;30HMARK_C" ++
46@@ -1149,9 +1149,9 @@ test "serializeTerminalState nested roundtrip preserves content" {
47         var buf: [32]u8 = undefined;
48         for (0..60) |i| {
49             const line = std.fmt.bufPrint(&buf, "SCROLL_{d}\r\n", .{i}) catch unreachable;
50-            try inner_stream.nextSlice(line);
51+            inner_stream.nextSlice(line);
52         }
53-        try inner_stream.nextSlice("\x1b[2J" ++
54+        inner_stream.nextSlice("\x1b[2J" ++
55             "\x1b[3;10HINNER_A" ++
56             "\x1b[12;25HINNER_B" ++
57             "\x1b[20;5H");
58@@ -1173,7 +1173,7 @@ test "serializeTerminalState nested roundtrip preserves content" {
59     {
60         var outer_stream = outer.vtStream();
61         defer outer_stream.deinit();
62-        try outer_stream.nextSlice(inner_serialized);
63+        outer_stream.nextSlice(inner_serialized);
64     }
65 
66     // Serialize outer (simulates outer daemon re-attach after detach)
67@@ -1240,9 +1240,9 @@ test "serializeTerminalState scrollback + size mismatch nested roundtrip" {
68         var buf: [32]u8 = undefined;
69         for (0..80) |i| {
70             const line = std.fmt.bufPrint(&buf, "LINE_{d}\r\n", .{i}) catch unreachable;
71-            try inner_stream.nextSlice(line);
72+            inner_stream.nextSlice(line);
73         }
74-        try inner_stream.nextSlice("\x1b[2J" ++
75+        inner_stream.nextSlice("\x1b[2J" ++
76             "\x1b[3;10HSTRESS_A" ++
77             "\x1b[12;25HSTRESS_B" ++
78             "\x1b[16;20H");
79@@ -1264,7 +1264,7 @@ test "serializeTerminalState scrollback + size mismatch nested roundtrip" {
80     {
81         var outer_stream = outer.vtStream();
82         defer outer_stream.deinit();
83-        try outer_stream.nextSlice(inner_ser);
84+        outer_stream.nextSlice(inner_ser);
85     }
86 
87     var client = try serializeRoundtrip(alloc, &outer);