- commit
- f15dee4
- parent
- 24420e4
- author
- Eric Bower
- date
- 2025-12-19 14:03:23 -0500 EST
refactor: daemonLoop and terminal serialization Code cleanup to make the daemonLoop and ghostty interaction easier to understand
1 files changed,
+124,
-85
+124,
-85
1@@ -157,6 +157,92 @@ const Daemon = struct {
2 }
3 return false;
4 }
5+
6+ pub fn handleInput(self: *Daemon, pty_fd: i32, payload: []const u8) !void {
7+ _ = self;
8+ if (payload.len > 0) {
9+ _ = try posix.write(pty_fd, payload);
10+ }
11+ }
12+
13+ pub fn handleInit(
14+ self: *Daemon,
15+ client: *Client,
16+ pty_fd: i32,
17+ term: *ghostty_vt.Terminal,
18+ payload: []const u8,
19+ ) !void {
20+ if (payload.len != @sizeOf(ipc.Resize)) return;
21+
22+ const resize = std.mem.bytesToValue(ipc.Resize, payload);
23+ var ws: c.struct_winsize = .{
24+ .ws_row = resize.rows,
25+ .ws_col = resize.cols,
26+ .ws_xpixel = 0,
27+ .ws_ypixel = 0,
28+ };
29+ _ = c.ioctl(pty_fd, c.TIOCSWINSZ, &ws);
30+ try term.resize(self.alloc, resize.cols, resize.rows);
31+
32+ std.log.debug("init resize rows={d} cols={d}", .{ resize.rows, resize.cols });
33+
34+ if (self.has_pty_output) {
35+ if (serializeTerminalState(self.alloc, term)) |term_output| {
36+ defer self.alloc.free(term_output);
37+ ipc.appendMessage(self.alloc, &client.write_buf, .Output, term_output) catch |err| {
38+ std.log.warn("failed to buffer terminal state for client err={s}", .{@errorName(err)});
39+ };
40+ client.has_pending_output = true;
41+ }
42+ }
43+ }
44+
45+ pub fn handleResize(self: *Daemon, pty_fd: i32, term: *ghostty_vt.Terminal, payload: []const u8) !void {
46+ if (payload.len != @sizeOf(ipc.Resize)) return;
47+
48+ const resize = std.mem.bytesToValue(ipc.Resize, payload);
49+ var ws: c.struct_winsize = .{
50+ .ws_row = resize.rows,
51+ .ws_col = resize.cols,
52+ .ws_xpixel = 0,
53+ .ws_ypixel = 0,
54+ };
55+ _ = c.ioctl(pty_fd, c.TIOCSWINSZ, &ws);
56+ try term.resize(self.alloc, resize.cols, resize.rows);
57+ std.log.debug("resize rows={d} cols={d}", .{ resize.rows, resize.cols });
58+ }
59+
60+ pub fn handleDetach(self: *Daemon, client: *Client, i: usize) void {
61+ std.log.info("client detach fd={d}", .{client.socket_fd});
62+ _ = self.closeClient(client, i, false);
63+ }
64+
65+ pub fn handleDetachAll(self: *Daemon) void {
66+ std.log.info("detach all clients={d}", .{self.clients.items.len});
67+ for (self.clients.items) |client_to_close| {
68+ client_to_close.deinit();
69+ self.alloc.destroy(client_to_close);
70+ }
71+ self.clients.clearRetainingCapacity();
72+ }
73+
74+ pub fn handleKill(self: *Daemon) void {
75+ std.log.info("kill received session={s}", .{self.session_name});
76+ posix.kill(self.pid, posix.SIG.TERM) catch |err| {
77+ std.log.warn("failed to send SIGTERM to pty child err={s}", .{@errorName(err)});
78+ };
79+ self.shutdown();
80+ }
81+
82+ pub fn handleInfo(self: *Daemon, client: *Client) !void {
83+ const clients_len = self.clients.items.len - 1;
84+ const info = ipc.Info{
85+ .clients_len = clients_len,
86+ .pid = self.pid,
87+ };
88+ try ipc.appendMessage(self.alloc, &client.write_buf, .Info, std.mem.asBytes(&info));
89+ client.has_pending_output = true;
90+ }
91 };
92
93 pub fn main() !void {
94@@ -832,101 +918,24 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
95
96 while (client.read_buf.next()) |msg| {
97 switch (msg.header.tag) {
98- .Input => {
99- if (msg.payload.len > 0) {
100- _ = try posix.write(pty_fd, msg.payload);
101- }
102- },
103- .Init => {
104- if (msg.payload.len == @sizeOf(ipc.Resize)) {
105- const resize = std.mem.bytesToValue(ipc.Resize, msg.payload);
106- var ws: c.struct_winsize = .{
107- .ws_row = resize.rows,
108- .ws_col = resize.cols,
109- .ws_xpixel = 0,
110- .ws_ypixel = 0,
111- };
112- _ = c.ioctl(pty_fd, c.TIOCSWINSZ, &ws);
113- try term.resize(daemon.alloc, resize.cols, resize.rows);
114-
115- std.log.debug("init resize rows={d} cols={d}", .{ resize.rows, resize.cols });
116-
117- // Only send terminal state if there's been PTY output (skip on first attach)
118- if (daemon.has_pty_output) {
119- var builder: std.Io.Writer.Allocating = .init(daemon.alloc);
120- defer builder.deinit();
121- var term_formatter = ghostty_vt.formatter.TerminalFormatter.init(&term, .vt);
122- term_formatter.content = .{ .selection = null };
123- term_formatter.extra = .{
124- .palette = false, // Don't override host terminal's palette
125- .modes = true,
126- .scrolling_region = true,
127- .tabstops = true,
128- .pwd = true,
129- .keyboard = true,
130- .screen = .all,
131- };
132- term_formatter.format(&builder.writer) catch |err| {
133- std.log.warn("failed to format terminal state err={s}", .{@errorName(err)});
134- };
135- const term_output = builder.writer.buffered();
136- if (term_output.len > 0) {
137- ipc.appendMessage(daemon.alloc, &client.write_buf, .Output, term_output) catch |err| {
138- std.log.warn("failed to buffer terminal state for client err={s}", .{@errorName(err)});
139- };
140- client.has_pending_output = true;
141- }
142- }
143- }
144- },
145- .Resize => {
146- if (msg.payload.len == @sizeOf(ipc.Resize)) {
147- const resize = std.mem.bytesToValue(ipc.Resize, msg.payload);
148- var ws: c.struct_winsize = .{
149- .ws_row = resize.rows,
150- .ws_col = resize.cols,
151- .ws_xpixel = 0,
152- .ws_ypixel = 0,
153- };
154- _ = c.ioctl(pty_fd, c.TIOCSWINSZ, &ws);
155- try term.resize(daemon.alloc, resize.cols, resize.rows);
156- std.log.debug("resize rows={d} cols={d}", .{ resize.rows, resize.cols });
157- }
158- },
159+ .Input => try daemon.handleInput(pty_fd, msg.payload),
160+ .Init => try daemon.handleInit(client, pty_fd, &term, msg.payload),
161+ .Resize => try daemon.handleResize(pty_fd, &term, msg.payload),
162 .Detach => {
163- std.log.info("client detach fd={d}", .{client.socket_fd});
164- _ = daemon.closeClient(client, i, false);
165+ daemon.handleDetach(client, i);
166 break :clients_loop;
167 },
168 .DetachAll => {
169- std.log.info("detach all clients={d}", .{daemon.clients.items.len});
170- for (daemon.clients.items) |client_to_close| {
171- client_to_close.deinit();
172- daemon.alloc.destroy(client_to_close);
173- }
174- daemon.clients.clearRetainingCapacity();
175+ daemon.handleDetachAll();
176 break :clients_loop;
177 },
178 .Kill => {
179- std.log.info("kill received session={s}", .{daemon.session_name});
180- posix.kill(daemon.pid, posix.SIG.TERM) catch |err| {
181- std.log.warn("failed to send SIGTERM to pty child err={s}", .{@errorName(err)});
182- };
183- daemon.shutdown();
184+ daemon.handleKill();
185 should_exit = true;
186 break :clients_loop;
187 },
188- .Info => {
189- // subtract current client since it's just fetching info
190- const clients_len = daemon.clients.items.len - 1;
191- const info = ipc.Info{
192- .clients_len = clients_len,
193- .pid = daemon.pid,
194- };
195- try ipc.appendMessage(daemon.alloc, &client.write_buf, .Info, std.mem.asBytes(&info));
196- client.has_pending_output = true;
197- },
198- .Output => {}, // Clients shouldn't send output
199+ .Info => try daemon.handleInfo(client),
200+ .Output => {},
201 }
202 }
203 }
204@@ -1138,6 +1147,36 @@ test "isKittyCtrlBackslash" {
205 try std.testing.expect(!isKittyCtrlBackslash("garbage"));
206 }
207
208+fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Terminal) ?[]const u8 {
209+ var builder: std.Io.Writer.Allocating = .init(alloc);
210+ defer builder.deinit();
211+
212+ var term_formatter = ghostty_vt.formatter.TerminalFormatter.init(term, .vt);
213+ term_formatter.content = .{ .selection = null };
214+ term_formatter.extra = .{
215+ .palette = false,
216+ .modes = true,
217+ .scrolling_region = true,
218+ .tabstops = true,
219+ .pwd = true,
220+ .keyboard = true,
221+ .screen = .all,
222+ };
223+
224+ term_formatter.format(&builder.writer) catch |err| {
225+ std.log.warn("failed to format terminal state err={s}", .{@errorName(err)});
226+ return null;
227+ };
228+
229+ const output = builder.writer.buffered();
230+ if (output.len == 0) return null;
231+
232+ return alloc.dupe(u8, output) catch |err| {
233+ std.log.warn("failed to allocate terminal state err={s}", .{@errorName(err)});
234+ return null;
235+ };
236+}
237+
238 fn isKittyCtrlB(buf: []const u8) bool {
239 return std.mem.indexOf(u8, buf, "\x1b[98;5u") != null or
240 std.mem.indexOf(u8, buf, "\x1b[98;133u") != null;