repos / zmx

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

commit
230c637
parent
ac62850
author
Eric Bower
date
2025-10-14 12:20:47 -0400 EDT
feat: restore terminal modes
1 files changed,  +47, -9
M src/daemon.zig
+47, -9
 1@@ -36,6 +36,11 @@ const VTHandler = struct {
 2         try self.terminal.print(cp);
 3     }
 4 
 5+    pub fn setMode(self: *VTHandler, mode: ghostty.Mode, enabled: bool) !void {
 6+        self.terminal.modes.set(mode, enabled);
 7+        std.debug.print("Mode changed: {s} = {}\n", .{ @tagName(mode), enabled });
 8+    }
 9+
10     pub fn deviceAttributes(
11         self: *VTHandler,
12         req: ghostty.DeviceAttributeReq,
13@@ -733,19 +738,52 @@ fn renderTerminalSnapshot(session: *Session, allocator: std.mem.Allocator) ![]u8
14     // Clear screen and move to home
15     try output.appendSlice(allocator, "\x1b[2J\x1b[H");
16 
17+    const vt = &session.vt;
18     const screen = &session.vt.screen;
19+
20+    // Debug: Print all enabled modes
21+    std.debug.print("Terminal modes for session {s}:\n", .{session.name});
22+    inline for (@typeInfo(ghostty.Mode).@"enum".fields) |field| {
23+        const mode: ghostty.Mode = @field(ghostty.Mode, field.name);
24+        const value = vt.modes.get(mode);
25+        const tag: ghostty.modes.ModeTag = @bitCast(@as(ghostty.modes.ModeTag.Backing, field.value));
26+        // const mode_default = @field(vt.modes.default, field.name);
27+
28+        // if (value != mode_default) {
29+        std.debug.print("  {s}{d} {s} = {}\n", .{
30+            if (tag.ansi) "" else "?",
31+            tag.value,
32+            field.name,
33+            value,
34+        });
35+        // }
36+    }
37+
38+    // Restore terminal modes by sending appropriate escape sequences
39+    // Iterate over all modes and generate the correct escape sequence for each
40+    inline for (@typeInfo(ghostty.Mode).@"enum".fields) |field| {
41+        @setEvalBranchQuota(6000);
42+        const mode: ghostty.Mode = @field(ghostty.Mode, field.name);
43+        const value = vt.modes.get(mode);
44+        const tag: ghostty.modes.ModeTag = @bitCast(@as(ghostty.modes.ModeTag.Backing, field.value));
45+        const mode_default = @field(vt.modes.default, field.name);
46+
47+        // Only send escape sequences for modes that differ from their defaults
48+        if (value != mode_default) {
49+            const suffix: u8 = if (value) 'h' else 'l';
50+            if (tag.ansi) {
51+                // ANSI mode: CSI <n> h/l
52+                try output.writer(allocator).print("\x1b[{d}{c}", .{ tag.value, suffix });
53+            } else {
54+                // DEC mode: CSI ? <n> h/l
55+                try output.writer(allocator).print("\x1b[?{d}{c}", .{ tag.value, suffix });
56+            }
57+        }
58+    }
59+
60     const content = try session.vt.plainStringUnwrapped(allocator);
61     defer allocator.free(content);
62     try output.appendSlice(allocator, content);
63-    // Get the active screen from the terminal
64-    // var it = screen.pages.pageIterator(.right_down, .{ .screen = .{} }, null);
65-    // var blank_rows: usize = 0;
66-    // var blank_cells: usize = 0;
67-    // while (it.next()) |chunk| {
68-    //     const page: *const ghostty.Page = &chunk.node.data;
69-    //     const start_y = chunk.start;
70-    //     const end_y = chunk.end;
71-    // }
72 
73     // Position cursor at correct location (ANSI is 1-based)
74     const cursor = screen.cursor;