repos / zmx

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

commit
ea6a7e5
parent
ddf78a8
author
Eric Bower
date
2025-10-10 22:50:30 -0400 EDT
fix: detach shortcut
1 files changed,  +28, -2
M src/daemon.zig
+28, -2
 1@@ -80,6 +80,7 @@ const Client = struct {
 2     allocator: std.mem.Allocator,
 3     attached_session: ?[]const u8,
 4     server_ctx: *ServerContext,
 5+    message_buffer: std.ArrayList(u8),
 6 };
 7 
 8 // Main daemon server state that manages the event loop, Unix socket server,
 9@@ -175,6 +176,7 @@ fn acceptCallback(
10             };
11 
12             const client = ctx.allocator.create(Client) catch @panic("failed to create client");
13+            const message_buffer = std.ArrayList(u8).initCapacity(ctx.allocator, 512) catch @panic("failed to create message buffer");
14             client.* = .{
15                 .fd = client_fd,
16                 .stream = xev.Stream.initFd(client_fd),
17@@ -182,6 +184,7 @@ fn acceptCallback(
18                 .allocator = ctx.allocator,
19                 .attached_session = null,
20                 .server_ctx = ctx,
21+                .message_buffer = message_buffer,
22             };
23 
24             ctx.clients.put(client_fd, client) catch @panic("failed to add client");
25@@ -213,11 +216,33 @@ fn readCallback(
26             return closeClient(client, completion);
27         }
28         const data = read_buffer.slice[0..len];
29-        handleMessage(client, data) catch |err| {
30-            std.debug.print("handleMessage failed: {s}\n", .{@errorName(err)});
31+
32+        // Append to message buffer
33+        client.message_buffer.appendSlice(client.allocator, data) catch {
34             return closeClient(client, completion);
35         };
36 
37+        // Process complete messages (delimited by newline)
38+        while (std.mem.indexOf(u8, client.message_buffer.items, "\n")) |newline_pos| {
39+            const message = client.message_buffer.items[0..newline_pos];
40+            handleMessage(client, message) catch |err| {
41+                std.debug.print("handleMessage failed: {s}\n", .{@errorName(err)});
42+                return closeClient(client, completion);
43+            };
44+
45+            // Remove processed message from buffer (including newline)
46+            const remaining = client.message_buffer.items[newline_pos + 1 ..];
47+            const remaining_copy = client.allocator.dupe(u8, remaining) catch {
48+                return closeClient(client, completion);
49+            };
50+            client.message_buffer.clearRetainingCapacity();
51+            client.message_buffer.appendSlice(client.allocator, remaining_copy) catch {
52+                client.allocator.free(remaining_copy);
53+                return closeClient(client, completion);
54+            };
55+            client.allocator.free(remaining_copy);
56+        }
57+
58         return .rearm;
59     } else |err| {
60         if (err == error.EndOfStream or err == error.EOF) {
61@@ -1038,6 +1063,7 @@ fn closeCallback(
62         std.debug.print("close failed: {s}\n", .{@errorName(err)});
63     }
64     std.debug.print("client disconnected fd={d}\n", .{client.fd});
65+    client.message_buffer.deinit(client.allocator);
66     client.allocator.destroy(completion);
67     client.allocator.destroy(client);
68     return .disarm;