summaryrefslogtreecommitdiff
path: root/src/detached/client.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 14:37:29 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitd88e381793c6505bf3f2af43705bd2fa32f55ef6 (patch)
treed4f3966c4eacf7fee43b6f18fc83f136676f835d /src/detached/client.zig
parent7284b72396398213356577345151bd79e497d27c (diff)
downloadpardes-d88e381793c6505bf3f2af43705bd2fa32f55ef6.tar.gz
pardes-d88e381793c6505bf3f2af43705bd2fa32f55ef6.zip
One Layer for tags, notices, headers and bodies; a taller tag is one layer of rows; wire v8
src/Layer.zig merges TagLayer and BodyLayer. `rows` (0 = no layer) and a cursor at {x, y}. A tag of N rows is ONE layer of N grid rows: tagHit answers the row as `line`, bodyHit keeps its meaning, and the per-line layer bases (TAG_LINE_LAYER_BASE, HEADER_LINE_LAYER_BASE) are gone, not aliased. Wire v8, the one bump: tag layers carry rows and cursor y, and the frame carries the placed region list. v7 and v9 peers are refused in both directions (server test over both, a new frontend test over both). web: tag_layer_value 11 = rows, 12 = cursor y; app.mjs lays every row. macOS: the Zig side compiles against Layer; pardes.h still sees one row per tag layer (accepted, the macOS shell is ignored for now). No visual change: snapshot goldens and the 17 GUI goldens byte-identical. Shared files touched: pardes.zig, Messages.zig, gui.zig, macos.zig, detached/client.zig, detached/server.zig, detached/wire.zig (plus web.zig, app.mjs, edit.zig, look.zig). Not touched: mouse.zig, tagline.zig, colors.zig, tty.zig, dump.zig, exec.zig, host_io.zig, panes.zig.
Diffstat (limited to 'src/detached/client.zig')
-rw-r--r--src/detached/client.zig30
1 files changed, 25 insertions, 5 deletions
diff --git a/src/detached/client.zig b/src/detached/client.zig
index 4468c961..3347f609 100644
--- a/src/detached/client.zig
+++ b/src/detached/client.zig
@@ -103,8 +103,10 @@ pub const Client = struct {
grid: std.ArrayListUnmanaged(pardes.Cell) = .empty,
cursor: ?wire.Cursor = null,
pointer_shape: pardes.Surface.PointerShape = .arrow,
- body_layers: [pardes.MAX_PANES]pardes.BodyLayer = @splat(.{}),
- tag_layers: [pardes.MAX_TAG_LAYERS]pardes.TagLayer = @splat(.{}),
+ body_layers: [pardes.MAX_PANES]pardes.Layer = @splat(.{}),
+ tag_layers: [pardes.MAX_TAG_LAYERS]pardes.Layer = @splat(.{}),
+ /// The session's placed regions, as its last frame gave them.
+ regions: std.ArrayListUnmanaged(pardes.Region) = .empty,
in: std.ArrayListUnmanaged(u8) = .empty,
out: std.ArrayListUnmanaged(u8) = .empty,
/// Bytes of `in` belonging to the message `next` returned last. Compacted at
@@ -182,6 +184,7 @@ pub const Client = struct {
layer.* = .{};
}
for (&c.tag_layers) |*layer| c.gpa.free(layer.cells);
+ c.regions.deinit(c.gpa);
c.in.deinit(c.gpa);
c.out.deinit(c.gpa);
}
@@ -319,7 +322,7 @@ pub const Client = struct {
try c.reshape(f.cols, f.rows);
}
try f.apply(c.grid.items);
- f.applyLayers(c.gpa, &c.body_layers, &c.tag_layers) catch |err| return switch (err) {
+ f.applyLayers(c.gpa, &c.body_layers, &c.tag_layers, &c.regions) catch |err| return switch (err) {
error.OutOfMemory => error.Closed,
else => @as(wire.Error, @errorCast(err)),
};
@@ -995,6 +998,12 @@ test "detached session: a frontend that dies takes nothing with it" {
}
test "detached session: a frontend speaking another protocol is refused, loudly" {
+ // The protocol before this one (v7 carried one-row tags and no regions)
+ // and the one after.
+ for ([_]u16{ wire.version - 1, wire.version + 1 }) |claimed| try refusedHello(claimed);
+}
+
+fn refusedHello(claimed: u16) !void {
var h: Harness = undefined;
try h.init(60, 16);
defer h.deinit();
@@ -1006,7 +1015,7 @@ test "detached session: a frontend speaking another protocol is refused, loudly"
defer _ = libc.close(fd);
var buf: [64]u8 = undefined;
const hello = try wire.encodeClient(&buf, .{
- .hello = .{ .version = wire.version + 1, .cols = 60, .rows = 16 },
+ .hello = .{ .version = claimed, .cols = 60, .rows = 16 },
});
try testing.expectEqual(@as(isize, @intCast(hello.len)), libc.send(fd, hello.ptr, hello.len, nosignal));
@@ -1024,6 +1033,17 @@ test "detached session: a frontend speaking another protocol is refused, loudly"
try testing.expect(!h.core.quit);
}
+test "detached frontend: a session speaking another protocol is not attached to" {
+ for ([_]u16{ wire.version - 1, wire.version + 1 }) |claimed| {
+ var c: Client = .{ .gpa = testing.allocator };
+ defer c.deinit();
+ var buf: [64]u8 = undefined;
+ try c.in.appendSlice(testing.allocator, try wire.encodeServer(&buf, .{ .welcome = .{ .version = claimed, .slot = 0, .cols = 60, .rows = 16 } }));
+ try testing.expectError(error.Ungreeted, c.next());
+ try testing.expect(!c.attached());
+ }
+}
+
test "detached session: a peer that sends garbage is dropped, not obeyed" {
var h: Harness = undefined;
try h.init(60, 16);
@@ -1281,7 +1301,7 @@ fn pumpUntilNotice(h: *Harness, c: *Client, want: []const u8) !bool {
try c.wait(5);
while (try c.next()) |_| {}
for (&c.tag_layers) |*layer| {
- if (layer.cols == 0 or layer.kind != .notice) continue;
+ if (layer.rows == 0 or layer.kind != .notice) continue;
var n: usize = 0;
for (layer.cells[0..layer.cols]) |cell| {
const g = cell.grapheme();