- commit
- acfa1ec
- parent
- 8560c39
- author
- Eric Bower
- date
- 2025-10-13 11:31:13 -0400 EDT
fix: respond to Device Attribute queries to prevent fish shell warning Fish shell sends a Primary Device Attribute (DA1) query (ESC [ c) when it starts to detect terminal capabilities. Previously, zmx forwarded this query to the PTY but never sent a response back to the client, causing fish to wait 2 seconds and display a terminal compatibility warning. This commit intercepts DA queries in handlePtyInput() before they reach the PTY and responds directly to the client with VT220 capabilities: - Primary DA (ESC [ c): VT220 with color (22) and clipboard (52) - Secondary DA (ESC [ > c): Terminal version info The response format matches ghostty's deviceAttributes implementation. Fixes the "fish could not read response to Primary Device Attribute query" warning on first attach.
1 files changed,
+32,
-0
+32,
-0
1@@ -613,6 +613,25 @@ fn handlePtyInput(client: *Client, text: []const u8) !void {
2
3 std.debug.print("Writing {d} bytes to PTY fd={d}\n", .{ text.len, session.pty_master_fd });
4
5+ // Intercept Device Attribute queries and respond directly
6+ if (respondToDeviceAttributes(client, text)) |response| {
7+ // Send response back to client instead of PTY
8+ const header = protocol.FrameHeader{
9+ .length = @intCast(response.len),
10+ .frame_type = @intFromEnum(protocol.FrameType.pty_binary),
11+ };
12+
13+ var frame_buf = std.ArrayList(u8).initCapacity(session.allocator, @sizeOf(protocol.FrameHeader) + response.len) catch return;
14+ defer frame_buf.deinit(session.allocator);
15+
16+ const header_bytes = std.mem.asBytes(&header);
17+ frame_buf.appendSlice(session.allocator, header_bytes) catch return;
18+ frame_buf.appendSlice(session.allocator, response) catch return;
19+
20+ _ = posix.write(client.fd, frame_buf.items) catch {};
21+ return; // Don't forward to PTY
22+ }
23+
24 // Write input to PTY master fd
25 const written = posix.write(session.pty_master_fd, text) catch |err| {
26 std.debug.print("Error writing to PTY: {s}\n", .{@errorName(err)});
27@@ -621,6 +640,19 @@ fn handlePtyInput(client: *Client, text: []const u8) !void {
28 _ = written;
29 }
30
31+fn respondToDeviceAttributes(_: *Client, text: []const u8) ?[]const u8 {
32+ // Primary Device Attributes: CSI c or ESC [ c
33+ if (std.mem.eql(u8, text, "\x1b[c")) {
34+ // VT220 with color and clipboard support
35+ return "\x1b[?62;22;52c";
36+ }
37+ // Secondary Device Attributes: CSI > c or ESC [ > c
38+ if (std.mem.eql(u8, text, "\x1b[>c")) {
39+ return "\x1b[>1;10;0c";
40+ }
41+ return null;
42+}
43+
44 fn handleWindowResize(client: *Client, rows: u16, cols: u16) !void {
45 const session_name = client.attached_session orelse {
46 std.debug.print("Client fd={d} not attached to any session\n", .{client.fd});