repos / zmx

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

commit
10c1e25
parent
e463586
author
Eric Bower
date
2025-10-11 17:19:42 -0400 EDT
refactor: move detach key to Config struct
5 files changed,  +16, -9
M src/attach.zig
+8, -6
 1@@ -22,6 +22,7 @@ const Context = struct {
 2     stdin_ctx: ?*StdinContext = null,
 3     read_completion: ?*xev.Completion = null,
 4     read_ctx: ?*ReadContext = null,
 5+    config: config_mod.Config,
 6 };
 7 
 8 const params = clap.parseParamsComptime(
 9@@ -94,6 +95,7 @@ pub fn main(config: config_mod.Config, iter: *std.process.ArgIterator) !void {
10         .allocator = allocator,
11         .loop = &loop,
12         .session_name = session_name,
13+        .config = config,
14     };
15 
16     const request_payload = protocol.AttachSessionRequest{ .session_name = session_name };
17@@ -404,14 +406,14 @@ fn stdinReadCallback(
18 
19         const data = read_buffer.slice[0..len];
20 
21-        // Detect Ctrl-b (0x02) as prefix for detach command
22-        if (len == 1 and data[0] == 0x02) {
23+        // Detect prefix for detach command
24+        if (len == 1 and data[0] == ctx.config.detach_prefix) {
25             ctx.prefix_pressed = true;
26             return .rearm;
27         }
28 
29-        // If prefix was pressed and now we got 'd', detach
30-        if (ctx.prefix_pressed and len == 1 and data[0] == 'd') {
31+        // If prefix was pressed and now we got the detach key, detach
32+        if (ctx.prefix_pressed and len == 1 and data[0] == ctx.config.detach_key) {
33             ctx.prefix_pressed = false;
34             sendDetachRequest(ctx);
35             return .rearm;
36@@ -420,8 +422,8 @@ fn stdinReadCallback(
37         // If prefix was pressed but we got something else, send the prefix and the new data
38         if (ctx.prefix_pressed) {
39             ctx.prefix_pressed = false;
40-            // Send the Ctrl-b that was buffered
41-            const prefix_data = [_]u8{0x02};
42+            // Send the prefix that was buffered
43+            const prefix_data = [_]u8{ctx.config.detach_prefix};
44             sendPtyInput(ctx, &prefix_data);
45             // Fall through to send the current data
46         }
M src/config.zig
+4, -0
 1@@ -4,6 +4,8 @@ const toml = @import("toml");
 2 pub const Config = struct {
 3     socket_path: []const u8 = "/tmp/zmx.sock",
 4     socket_path_allocated: bool = false,
 5+    detach_prefix: u8 = 0x00, // Ctrl+Space
 6+    detach_key: u8 = 'd',
 7 
 8     pub fn load(allocator: std.mem.Allocator) !Config {
 9         const config_path = getConfigPath(allocator) catch |err| {
10@@ -28,6 +30,8 @@ pub const Config = struct {
11         const config = Config{
12             .socket_path = try allocator.dupe(u8, result.value.socket_path),
13             .socket_path_allocated = true,
14+            .detach_prefix = result.value.detach_prefix,
15+            .detach_key = result.value.detach_key,
16         };
17         return config;
18     }
M src/daemon.zig
+2, -1
 1@@ -798,7 +798,8 @@ fn readPtyCallback(
 2         if (total_len > 0) {
 3             // Scan backwards to find if we have a partial UTF-8 sequence
 4             var i = total_len;
 5-            while (i > 0 and i > total_len - 4) {
 6+            const scan_start = if (total_len >= 4) total_len - 4 else 0;
 7+            while (i > 0 and i > scan_start) {
 8                 i -= 1;
 9                 const byte = data[i];
10                 // Check if this is a UTF-8 start byte
M src/daemon_test.zig
+1, -1
1@@ -113,7 +113,7 @@ test "daemon lifecycle: attach, verify PTY, detach, shutdown" {
2 
3     // 4. Send detach command
4     std.debug.print("=== Detaching from session ===\n", .{});
5-    const detach_seq = [_]u8{ 0x02, 'd' }; // Ctrl+B followed by 'd'
6+    const detach_seq = [_]u8{ 0x00, 'd' }; // Ctrl+Space followed by 'd'
7     _ = try attach_process.stdin.?.write(&detach_seq);
8     std.Thread.sleep(200 * std.time.ns_per_ms);
9 
M src/detach.zig
+1, -1
1@@ -71,7 +71,7 @@ pub fn main(config: config_mod.Config, iter: *std.process.ArgIterator) !void {
2 
3     if (session_name == null) {
4         std.debug.print("Error: Not currently attached to any session\n", .{});
5-        std.debug.print("Use Ctrl-b d to detach from within an attached session\n", .{});
6+        std.debug.print("Use Ctrl-Space d to detach from within an attached session\n", .{});
7         return error.NotAttached;
8     }
9     defer if (session_name) |name| allocator.free(name);