- commit
- f6b9539
- parent
- fa0955b
- author
- Eric Bower
- date
- 2025-10-15 16:14:51 -0400 EDT
fix: reattach rendering
2 files changed,
+77,
-28
+22,
-3
1@@ -203,6 +203,7 @@ fn readCallback(
2 const data = read_buffer.slice[0..len];
3
4 // Check if this is a binary frame (starts with FrameHeader)
5+ var remaining_data = data;
6 if (data.len >= @sizeOf(protocol.FrameHeader)) {
7 const potential_header = data[0..@sizeOf(protocol.FrameHeader)];
8 const header: *const protocol.FrameHeader = @ptrCast(@alignCast(potential_header));
9@@ -214,7 +215,14 @@ fn readCallback(
10 // We have the complete frame
11 const payload = data[@sizeOf(protocol.FrameHeader)..expected_total];
12 writeToStdout(ctx, payload);
13- return .rearm;
14+
15+ // Check if there's more data after this frame (e.g., JSON response)
16+ if (data.len > expected_total) {
17+ remaining_data = data[expected_total..];
18+ // Continue processing remaining data as JSON below
19+ } else {
20+ return .rearm;
21+ }
22 } else {
23 // Partial frame, buffer it
24 ctx.frame_buffer.appendSlice(ctx.allocator, data) catch {};
25@@ -243,11 +251,11 @@ fn readCallback(
26 }
27
28 // Otherwise parse as JSON control message
29- const newline_idx = std.mem.indexOf(u8, data, "\n") orelse {
30+ const newline_idx = std.mem.indexOf(u8, remaining_data, "\n") orelse {
31 return .rearm;
32 };
33
34- const msg_line = data[0..newline_idx];
35+ const msg_line = remaining_data[0..newline_idx];
36
37 const msg_type_parsed = protocol.parseMessageType(ctx.allocator, msg_line) catch |err| {
38 std.debug.print("JSON parse error: {s}\r\n", .{@errorName(err)});
39@@ -262,13 +270,17 @@ fn readCallback(
40
41 switch (msg_type) {
42 .attach_session_response => {
43+ std.debug.print("Received attach_session_response\r\n", .{});
44 const parsed = protocol.parseMessage(protocol.AttachSessionResponse, ctx.allocator, msg_line) catch |err| {
45 std.debug.print("Failed to parse attach response: {s}\r\n", .{@errorName(err)});
46+ std.debug.print("Message line: {s}\r\n", .{msg_line});
47 return .rearm;
48 };
49 defer parsed.deinit();
50
51+ std.debug.print("Parsed response: status={s}, client_fd={?d}\r\n", .{ parsed.value.payload.status, parsed.value.payload.client_fd });
52 if (std.mem.eql(u8, parsed.value.payload.status, "ok")) {
53+ std.debug.print("Status is OK, processing...\r\n", .{});
54 const client_fd = parsed.value.payload.client_fd orelse {
55 std.debug.print("Missing client_fd in response\r\n", .{});
56 return .rearm;
57@@ -347,6 +359,12 @@ fn readCallback(
58 }
59
60 fn startStdinReading(ctx: *Context) void {
61+ // Don't start if already reading
62+ if (ctx.stdin_completion != null) {
63+ std.debug.print("Stdin reading already started, skipping\r\n", .{});
64+ return;
65+ }
66+
67 const stdin_ctx = ctx.allocator.create(StdinContext) catch @panic("failed to create stdin context");
68 stdin_ctx.* = .{
69 .ctx = ctx,
70@@ -359,6 +377,7 @@ fn startStdinReading(ctx: *Context) void {
71 ctx.stdin_completion = stdin_completion;
72 ctx.stdin_ctx = stdin_ctx;
73
74+ std.debug.print("Starting stdin reading\r\n", .{});
75 ctx.stdin_stream.read(ctx.loop, stdin_completion, .{ .slice = &stdin_ctx.buffer }, StdinContext, stdin_ctx, stdinReadCallback);
76 }
77
+55,
-25
1@@ -245,6 +245,7 @@ const Session = struct {
2 vt_stream: ghostty.Stream(*VTHandler),
3 vt_handler: VTHandler,
4 attached_clients: std.AutoHashMap(std.posix.fd_t, void),
5+ pty_reading: bool = false, // Track if PTY reads are active
6
7 fn deinit(self: *Session) void {
8 self.allocator.free(self.name);
9@@ -762,23 +763,6 @@ fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []con
10 break :blk new_session;
11 };
12
13- // Update libghostty-vt terminal size and PTY window size
14- // Only resize if we have valid dimensions
15- if (rows > 0 and cols > 0) {
16- try session.vt.resize(session.allocator, cols, rows);
17-
18- var ws = c.struct_winsize{
19- .ws_row = rows,
20- .ws_col = cols,
21- .ws_xpixel = 0,
22- .ws_ypixel = 0,
23- };
24- const result = c.ioctl(session.pty_master_fd, c.TIOCSWINSZ, &ws);
25- if (result < 0) {
26- return error.IoctlFailed;
27- }
28- }
29-
30 // Mark client as attached
31 client.attached_session = session.name;
32
33@@ -791,21 +775,63 @@ fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []con
34
35 // Start reading from PTY if not already started (first client)
36 const is_first_client = session.attached_clients.count() == 1;
37+ std.debug.print("is_reattach={}, is_first_client={}, attached_clients.count={}\n", .{ is_reattach, is_first_client, session.attached_clients.count() });
38
39- // For reattaching clients, send snapshot BEFORE starting PTY reads to prevent interleaving
40+ // For reattaching clients, resize VT BEFORE snapshot so snapshot matches client size
41+ // But defer TIOCSWINSZ until after snapshot to prevent SIGWINCH during send
42 if (is_reattach) {
43+ if (rows > 0 and cols > 0) {
44+ // Only resize VT if geometry changed
45+ if (session.vt.cols != cols or session.vt.rows != rows) {
46+ try session.vt.resize(session.allocator, cols, rows);
47+ std.debug.print("Resized VT to {d}x{d} before snapshot\n", .{ cols, rows });
48+ }
49+ }
50+
51+ // Render snapshot at correct client size
52 const buffer_slice = try terminal_snapshot.render(&session.vt, client.allocator);
53 defer client.allocator.free(buffer_slice);
54
55 try protocol.writeBinaryFrame(client.fd, .pty_binary, buffer_slice);
56 std.debug.print("Sent scrollback buffer to client fd={d} ({d} bytes)\n", .{ client.fd, buffer_slice.len });
57
58- // Unmute client now that snapshot is sent
59+ // Unmute client before TIOCSWINSZ so client can receive the redraw
60 client.muted = false;
61+
62+ // Now send TIOCSWINSZ to trigger app (vim) redraw - client will receive it
63+ if (rows > 0 and cols > 0) {
64+ var ws = c.struct_winsize{
65+ .ws_row = rows,
66+ .ws_col = cols,
67+ .ws_xpixel = 0,
68+ .ws_ypixel = 0,
69+ };
70+ const result = c.ioctl(session.pty_master_fd, c.TIOCSWINSZ, &ws);
71+ if (result < 0) {
72+ return error.IoctlFailed;
73+ }
74+ }
75+ } else if (!is_reattach and rows > 0 and cols > 0) {
76+ // New session: just resize normally
77+ try session.vt.resize(session.allocator, cols, rows);
78+
79+ var ws = c.struct_winsize{
80+ .ws_row = rows,
81+ .ws_col = cols,
82+ .ws_xpixel = 0,
83+ .ws_ypixel = 0,
84+ };
85+ const result = c.ioctl(session.pty_master_fd, c.TIOCSWINSZ, &ws);
86+ if (result < 0) {
87+ return error.IoctlFailed;
88+ }
89 }
90
91- if (is_first_client) {
92- // Start PTY reads AFTER snapshot is sent
93+ // Only start PTY reading if not already started
94+ if (!session.pty_reading) {
95+ session.pty_reading = true;
96+ std.debug.print("Starting PTY reads for session {s}\n", .{session.name});
97+ // Start PTY reads AFTER snapshot is sent (readFromPty sends attach response)
98 try readFromPty(ctx, client, session);
99
100 // For first attach to new session, clear the client's terminal
101@@ -813,11 +839,15 @@ fn handleAttachSession(ctx: *ServerContext, client: *Client, session_name: []con
102 try protocol.writeBinaryFrame(client.fd, .pty_binary, "\x1b[2J\x1b[H");
103 }
104 } else {
105- // Send attach success response for additional clients
106- try protocol.writeJson(ctx.allocator, client.fd, .attach_session_response, protocol.AttachSessionResponse{
107+ // PTY already reading - just send attach response
108+ std.debug.print("PTY already reading for session {s}, sending attach response to client fd={d}\n", .{ session.name, client.fd });
109+ const response = protocol.AttachSessionResponse{
110 .status = "ok",
111 .client_fd = client.fd,
112- });
113+ };
114+ std.debug.print("Response payload: status={s}, client_fd={?d}\n", .{ response.status, response.client_fd });
115+ try protocol.writeJson(ctx.allocator, client.fd, .attach_session_response, response);
116+ std.debug.print("Attach response sent successfully\n", .{});
117 }
118 }
119
120@@ -832,7 +862,7 @@ fn handlePtyInput(client: *Client, text: []const u8) !void {
121 return error.SessionNotFound;
122 };
123
124- std.debug.print("Writing {d} bytes to PTY fd={d}\n", .{ text.len, session.pty_master_fd });
125+ std.debug.print("Client fd={d}: Writing {d} bytes to PTY fd={d} (first byte: {d})\n", .{ client.fd, text.len, session.pty_master_fd, if (text.len > 0) text[0] else 0 });
126
127 // Write input to PTY master fd
128 const written = posix.write(session.pty_master_fd, text) catch |err| {