summaryrefslogtreecommitdiff
path: root/src/detached
diff options
context:
space:
mode:
Diffstat (limited to 'src/detached')
-rw-r--r--src/detached/client.zig32
-rw-r--r--src/detached/server.zig25
-rw-r--r--src/detached/wire.zig143
3 files changed, 154 insertions, 46 deletions
diff --git a/src/detached/client.zig b/src/detached/client.zig
index ba533751..8c9a2560 100644
--- a/src/detached/client.zig
+++ b/src/detached/client.zig
@@ -103,8 +103,12 @@ 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,
+ /// The palette its chrome is drawn in, as its last frame gave it.
+ chrome: pardes.Chrome = .{},
in: std.ArrayListUnmanaged(u8) = .empty,
out: std.ArrayListUnmanaged(u8) = .empty,
/// Bytes of `in` belonging to the message `next` returned last. Compacted at
@@ -182,6 +186,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 +324,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, &c.chrome) catch |err| return switch (err) {
error.OutOfMemory => error.Closed,
else => @as(wire.Error, @errorCast(err)),
};
@@ -996,6 +1001,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();
@@ -1007,7 +1018,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));
@@ -1025,6 +1036,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);
@@ -1282,7 +1304,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();
diff --git a/src/detached/server.zig b/src/detached/server.zig
index 8fc7b96e..5374bf73 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -150,6 +150,8 @@ pub const Session = struct {
worker_gpa: std.mem.Allocator,
io: std.Io,
core: *pardes.Pardes,
+ /// PARDES_TEST_CLOCK's virtual time (host_io.testClock).
+ test_clock: ?u64 = null,
listener: c_int = -1,
path_buf: [sun_path_len]u8 = undefined,
path_len: usize = 0,
@@ -355,8 +357,14 @@ pub const Session = struct {
return @ptrCast(@alignCast(ctx.?));
}
+ fn clockNow(ctx: ?*anyopaque) u64 {
+ const s = of(ctx);
+ return s.test_clock orelse host_io.monotonicNs();
+ }
+
const vtable: host_io.Host.VTable = .{
.wait_input = waitInput,
+ .now = clockNow,
.present = present,
.poll_frame = pollFrame,
.spawn = spawn,
@@ -767,7 +775,7 @@ pub const Session = struct {
fn sendFrame(s: *Session, c: *Client, surface: *const pardes.Surface) void {
const cells = surface.cells;
- const want = wire.frameBound(surface.cols, surface.rows) + wire.layersBound(&surface.body_layers, &surface.tag_layers);
+ const want = wire.frameBound(surface.cols, surface.rows) + wire.layersBound(&surface.body_layers, &surface.tag_layers, surface.regionList());
s.scratch.ensureTotalCapacity(s.gpa, want) catch return s.close(c, .oom);
const prev: []const pardes.Cell = if (c.need_full or c.mirror.items.len != cells.len)
&.{}
@@ -787,6 +795,8 @@ pub const Session = struct {
prev,
&surface.body_layers,
&surface.tag_layers,
+ surface.regionList(),
+ surface.chrome,
) catch |err| {
log.debug("frame {d}x{d} not encodable: {t}", .{ surface.cols, surface.rows, err });
return;
@@ -867,8 +877,9 @@ pub const Session = struct {
pardes.turn.rest();
nap(if (timeout_ms == 0) 16 else timeout_ms);
pardes.turn.wake();
- // An animating frame's wait is the frame: tick it.
- if (timeout_ms != 0) s.core.update(.tick);
+ if (timeout_ms != 0) if (s.test_clock) |*virtual| {
+ virtual.* = @max(virtual.*, s.core.nextWake() orelse virtual.*);
+ };
return;
}
var timeout: c_int = if (timeout_ms == 0) -1 else @intCast(@min(timeout_ms, std.math.maxInt(c_int)));
@@ -878,9 +889,10 @@ pub const Session = struct {
const ready = libc.poll(&fds, @intCast(n), timeout);
pardes.turn.wake();
if (ready > 0) s.dispatch(fds[0..n], src[0..n]);
- // Animating, the frame's time is up: advance it. There is no other
- // tick in a detached session, and without it an animation never ends.
- if (timeout_ms != 0 and monotonicMs() -| now >= timeout_ms) s.core.update(.tick);
+ // The core's wake ran out; the pump advances it from `now`.
+ if (timeout_ms != 0 and monotonicMs() -| now >= timeout_ms) if (s.test_clock) |*virtual| {
+ virtual.* = @max(virtual.*, s.core.nextWake() orelse virtual.*);
+ };
_ = s.drainCompletions(true);
s.takeExits();
s.expire(monotonicMs());
@@ -1212,6 +1224,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options, name: []const u8) !void
.cols = options.cols,
.rows = options.rows,
.prompt_rcs = host_io.Shell.prepare(),
+ .test_clock = host_io.testClock(),
};
defer session.core.deinit();
defer session.deinit();
diff --git a/src/detached/wire.zig b/src/detached/wire.zig
index 0854ce3e..b1b81eda 100644
--- a/src/detached/wire.zig
+++ b/src/detached/wire.zig
@@ -7,7 +7,9 @@ const limits = @import("../memory.zig").limits;
/// 7: tag layers gained the `.notice` kind, so a frame can carry a tag value
/// an older frontend would reject mid-session. The greeting is where that has
/// to be refused, which is what this number is for.
-pub const version: u16 = 7;
+/// 8: one layer type: a tag band carries all its rows (not a layer per
+/// line), and a frame carries the core's placed regions after its layers.
+pub const version: u16 = 8;
pub const Error = error{ Truncated, Overlong, BadTag, BadValue, Trailing, NoSpace };
@@ -167,7 +169,9 @@ pub const Frame = struct {
nruns: u32,
runs: []const u8,
- pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.BodyLayer, tags: *[pardes.MAX_TAG_LAYERS]pardes.TagLayer) !void {
+ pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.Layer, tags: *[pardes.MAX_TAG_LAYERS]pardes.Layer, regions: *std.ArrayListUnmanaged(pardes.Region), chrome: *pardes.Chrome) !void {
+ regions.clearRetainingCapacity();
+ chrome.* = .{};
for (layers) |*layer| {
gpa.free(layer.cells);
layer.* = .{};
@@ -185,19 +189,22 @@ pub const Frame = struct {
gpa.free(layer.cells);
layer.* = .{};
}
+ regions.clearRetainingCapacity();
+ chrome.* = .{};
}
if (f.layers.len == 0) return;
var r: Reader = .init(f.layers);
const count = try r.getU16();
if (count > layers.len) return error.Overlong;
for (layers[0..count]) |*layer| {
- layer.pane = try r.getU16();
+ layer.kind = .body;
+ layer.id = try r.getU16();
layer.serial = try r.getU32();
layer.viewport = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() };
layer.cols = try r.getU16();
layer.rows = try r.getU16();
layer.context_rows = try r.getU16();
- if (layer.pane >= pardes.MAX_PANES or layer.cols == 0 or layer.cols > f.cols or
+ if (layer.id >= pardes.MAX_PANES or layer.cols == 0 or layer.cols > f.cols or
layer.rows == 0 or layer.rows > 2 * max_rows or layer.context_rows > layer.rows or layer.context_rows > limits.wrap_rows or
layer.viewport.x > f.cols or layer.viewport.w > f.cols - layer.viewport.x or
layer.viewport.y > f.rows or layer.viewport.h > f.rows - layer.viewport.y) return error.BadValue;
@@ -217,19 +224,49 @@ pub const Frame = struct {
layer.serial = try r.getU32();
layer.viewport = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() };
layer.cols = try r.getU16();
+ layer.rows = try r.getU16();
for (&layer.bg) |*channel| channel.* = try r.getByte();
- if (layer.cols == 0 or layer.viewport.w == 0 or layer.viewport.h != 1 or
+ if (layer.cols == 0 or layer.viewport.w == 0 or layer.rows == 0 or layer.rows > pardes.MAX_TAG_ROWS or
+ layer.viewport.h != layer.rows or
layer.viewport.x > f.cols or layer.viewport.w > f.cols - layer.viewport.x or
- layer.viewport.y >= f.rows or (switch (layer.kind) {
+ layer.viewport.y >= f.rows or layer.viewport.h > f.rows - layer.viewport.y or (switch (layer.kind) {
.workspace => layer.id != 0,
.column => layer.id >= pardes.MAX_COLS,
.pane, .notice => layer.id >= pardes.MAX_PANES,
+ .body => true,
})) return error.BadValue;
- const cursor = try getCursor(&r, layer.cols, 1);
- layer.cursor = if (cursor) |c| .{ .col = c.x, .bar = c.bar } else null;
- layer.cells = try gpa.alloc(pardes.Cell, layer.cols);
+ const cursor = try getCursor(&r, layer.cols, layer.rows);
+ layer.cursor = if (cursor) |c| .{ .x = c.x, .y = c.y, .bar = c.bar } else null;
+ layer.cells = try gpa.alloc(pardes.Cell, @as(usize, layer.cols) * layer.rows);
for (layer.cells) |*cell| cell.* = try decodeCell(&r);
}
+ const region_count = try r.getU16();
+ if (region_count > pardes.MAX_REGIONS) return error.Overlong;
+ try regions.ensureTotalCapacity(gpa, region_count);
+ for (0..region_count) |_| {
+ var region: pardes.Region = .{ .kind = try r.getTag(pardes.Region.Kind), .rect = undefined };
+ region.owner = try r.getByte();
+ region.serial = try r.getU32();
+ region.rect = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() };
+ region.active = try r.getBool();
+ region.thumb_y = try r.getU16();
+ region.thumb_h = try r.getU16();
+ region.notice = try r.getByte();
+ region.clip = try r.getU16();
+ if (region.rect.x > f.cols or region.rect.w > f.cols - region.rect.x or
+ region.rect.y > f.rows or region.rect.h > f.rows - region.rect.y or
+ region.clip > region.rect.w or region.thumb_y > region.rect.h or region.thumb_h > region.rect.h - region.thumb_y) return error.BadValue;
+ regions.appendAssumeCapacity(region);
+ }
+ inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| {
+ const rgb = &@field(chrome, field.name);
+ if (field.type == ?[3]u8) {
+ if (try r.getBool()) {
+ rgb.* = .{ 0, 0, 0 };
+ for (&rgb.*.?) |*channel| channel.* = try r.getByte();
+ }
+ } else for (rgb) |*channel| channel.* = try r.getByte();
+ }
try r.end();
}
@@ -672,21 +709,26 @@ pub fn encodeFramePointer(
cells: []const pardes.Cell,
prev: []const pardes.Cell,
) Error![]const u8 {
- return encodeFrameLayers(out, cols, rows, cursor, pointer_shape, cells, prev, &.{}, &.{});
+ return encodeFrameLayers(out, cols, rows, cursor, pointer_shape, cells, prev, &.{}, &.{}, &.{}, .{});
}
-pub fn layersBound(layers: []const pardes.BodyLayer, tags: []const pardes.TagLayer) usize {
- var n: usize = 4;
+pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region) usize {
+ var n: usize = 6;
for (layers) |layer| if (layer.rows != 0) {
n += 26 + layer.context_rows + layer.cells.len * cell_max;
};
- for (tags) |layer| if (layer.cols != 0) {
- n += 26 + layer.cells.len * cell_max;
+ for (tags) |layer| if (layer.rows != 0) {
+ n += 28 + layer.cells.len * cell_max;
};
- return n;
+ return n + regions.len * region_len + chrome_len;
}
-pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, pointer_shape: pardes.Surface.PointerShape, cells: []const pardes.Cell, prev: []const pardes.Cell, layers: []const pardes.BodyLayer, tags: []const pardes.TagLayer) Error![]const u8 {
+const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2;
+/// The palette: each colour three bytes, a theme's optional ones a flag
+/// before them.
+const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4;
+
+pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, pointer_shape: pardes.Surface.PointerShape, cells: []const pardes.Cell, prev: []const pardes.Cell, layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region, chrome: pardes.Chrome) Error![]const u8 {
// The protocol's ceiling, enforced by the ENCODER too, and BEFORE the
// assert below so a caller can be told rather than tripped. `max_payload`
// is derived from these two, so a larger grid is a frame this decoder
@@ -708,7 +750,7 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
const layers_len_at = w.n;
try w.putU32(0);
const layers_start = w.n;
- if (layers.len != 0 or tags.len != 0) {
+ if (layers.len != 0 or tags.len != 0 or regions.len != 0) {
var count: u16 = 0;
for (layers) |layer| if (layer.rows != 0) {
count += 1;
@@ -717,7 +759,7 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
for (layers) |layer| {
if (layer.rows == 0) continue;
if (layer.context_rows > limits.wrap_rows or layer.context_rows > layer.rows) return error.BadValue;
- try w.putU16(layer.pane);
+ try w.putU16(layer.id);
try w.putU32(layer.serial);
try w.putU16(layer.viewport.x);
try w.putU16(layer.viewport.y);
@@ -731,13 +773,13 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
for (layer.cells) |*cell| try putCell(&w, cell);
}
var tag_count: u16 = 0;
- for (tags) |layer| if (layer.cols != 0) {
+ for (tags) |layer| if (layer.rows != 0) {
tag_count += 1;
};
try w.putU16(tag_count);
for (tags) |layer| {
- if (layer.cols == 0) continue;
- if (layer.cells.len != layer.cols) return error.BadValue;
+ if (layer.rows == 0) continue;
+ if (layer.cells.len != @as(usize, layer.cols) * layer.rows) return error.BadValue;
try w.putByte(@intFromEnum(layer.kind));
try w.putU16(layer.id);
try w.putU32(layer.serial);
@@ -746,10 +788,33 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
try w.putU16(layer.viewport.w);
try w.putU16(layer.viewport.h);
try w.putU16(layer.cols);
+ try w.putU16(layer.rows);
for (layer.bg) |channel| try w.putByte(channel);
- try putCursor(&w, if (layer.cursor) |c| .{ .x = c.col, .y = 0, .bar = c.bar } else null, layer.cols, 1);
+ try putCursor(&w, if (layer.cursor) |c| .{ .x = c.x, .y = c.y, .bar = c.bar } else null, layer.cols, layer.rows);
for (layer.cells) |*cell| try putCell(&w, cell);
}
+ try w.putU16(@intCast(regions.len));
+ for (regions) |region| {
+ try w.putByte(@intFromEnum(region.kind));
+ try w.putByte(region.owner);
+ try w.putU32(region.serial);
+ try w.putU16(region.rect.x);
+ try w.putU16(region.rect.y);
+ try w.putU16(region.rect.w);
+ try w.putU16(region.rect.h);
+ try w.putBool(region.active);
+ try w.putU16(region.thumb_y);
+ try w.putU16(region.thumb_h);
+ try w.putByte(region.notice);
+ try w.putU16(region.clip);
+ }
+ inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| {
+ const rgb = @field(chrome, field.name);
+ if (field.type == ?[3]u8) {
+ try w.putBool(rgb != null);
+ if (rgb) |value| for (value) |channel| try w.putByte(channel);
+ } else for (rgb) |channel| try w.putByte(channel);
+ }
}
std.mem.writeInt(u32, w.buf[layers_len_at..][0..4], @intCast(w.n - layers_start), .little);
const nruns_at = w.n;
@@ -1820,22 +1885,29 @@ test "detached wire carries compact body rows and exact pointer metrics" {
var cells: [8]pardes.Cell = @splat(.{});
var logical: [12]pardes.Cell = @splat(.{});
logical[10] = .{ .text = "z".* ++ @as([6]u8, @splat(0)), .len = 1, .default = false };
- var layer: pardes.BodyLayer = .{ .pane = 0, .serial = 42, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 4 }, .cols = 2, .rows = 6, .context_rows = 2, .cells = &logical, .cursor = .{ .x = 0, .y = 5, .bar = true } };
+ var layer: pardes.Layer = .{ .id = 0, .serial = 42, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 4 }, .cols = 2, .rows = 6, .context_rows = 2, .cells = &logical, .cursor = .{ .x = 0, .y = 5, .bar = true } };
layer.context_separators.set(0);
var tag_cells: [3]pardes.Cell = @splat(.{});
tag_cells[2] = logical[10];
- const tag: pardes.TagLayer = .{ .kind = .column, .id = 1, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 1 }, .cols = 3, .bg = .{ 11, 22, 33 }, .cells = &tag_cells, .cursor = .{ .col = 2, .bar = true } };
- const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}));
+ const tag: pardes.Layer = .{ .kind = .column, .id = 1, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 1 }, .cols = 3, .rows = 1, .bg = .{ 11, 22, 33 }, .cells = &tag_cells, .cursor = .{ .x = 2, .y = 0, .bar = true } };
+ const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .thumb_y = 1, .thumb_h = 2, .clip = 1 }};
+ const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed));
defer gpa.free(out);
- const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag});
+ const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 } };
+ const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed, palette);
const packet = (try framed(encoded)).?;
const frame = (try decodeServer(packet.tag, packet.payload)).frame;
- var decoded: [pardes.MAX_PANES]pardes.BodyLayer = @splat(.{});
- var tags: [pardes.MAX_TAG_LAYERS]pardes.TagLayer = @splat(.{});
+ var decoded: [pardes.MAX_PANES]pardes.Layer = @splat(.{});
+ var tags: [pardes.MAX_TAG_LAYERS]pardes.Layer = @splat(.{});
defer for (&tags) |*item| gpa.free(item.cells);
defer for (&decoded) |*item| gpa.free(item.cells);
try frame.apply(&cells);
- try frame.applyLayers(gpa, &decoded, &tags);
+ var regions: std.ArrayListUnmanaged(pardes.Region) = .empty;
+ defer regions.deinit(gpa);
+ var chrome: pardes.Chrome = .{};
+ try frame.applyLayers(gpa, &decoded, &tags, &regions, &chrome);
+ try std.testing.expectEqualDeep(placed[0..], regions.items);
+ try std.testing.expectEqualDeep(palette, chrome);
try std.testing.expectEqual(@as(u16, 6), decoded[0].rows);
try std.testing.expect(decoded[0].context_separators.isSet(0));
try std.testing.expect(!decoded[0].context_separators.isSet(1));
@@ -1847,10 +1919,10 @@ test "detached wire carries compact body rows and exact pointer metrics" {
try std.testing.expectEqual(tag.id, tags[0].id);
try std.testing.expectEqual(tag.bg, tags[0].bg);
try std.testing.expectEqual(tag.cols, tags[0].cols);
- try std.testing.expectEqual(@as(u16, 2), tags[0].cursor.?.col);
+ try std.testing.expectEqual(@as(u16, 2), tags[0].cursor.?.x);
try std.testing.expectEqualStrings("z", tags[0].cells[2].grapheme());
- const pointer = layer.hitAt(4, 27, 8, 16, 6, 8).?;
- const tag_pointer = tag.hitAt(11, 8, 8, 16, 4, 8).?;
+ const pointer = layer.bodyHit(4, 27, 8, 16, 6, 8).?;
+ const tag_pointer = tag.tagHit(11, 8, 8, 16, 4, 8).?;
const message: ClientMsg = .{ .event = .{ .mouse = .{ .col = 0, .row = 1, .button = .left, .kind = .press, .body_hit = pointer, .tag_hit = tag_pointer } } };
var mouse_bytes: [256]u8 = undefined;
const mouse_encoded = try encodeClient(&mouse_bytes, message);
@@ -1861,19 +1933,20 @@ test "detached wire carries compact body rows and exact pointer metrics" {
try std.testing.expect(mouse_encoded.len <= clientBound(message));
var truncated = frame;
truncated.layers = frame.layers[0 .. frame.layers.len - 1];
- try std.testing.expectError(error.Truncated, truncated.applyLayers(gpa, &decoded, &tags));
+ try std.testing.expectError(error.Truncated, truncated.applyLayers(gpa, &decoded, &tags, &regions, &chrome));
for (decoded) |item| {
try std.testing.expectEqual(@as(u16, 0), item.rows);
try std.testing.expectEqual(@as(usize, 0), item.cells.len);
try std.testing.expectEqual(@as(usize, 0), item.context_separators.count());
}
for (tags) |item| {
- try std.testing.expectEqual(@as(u16, 0), item.cols);
+ try std.testing.expectEqual(@as(u16, 0), item.rows);
try std.testing.expectEqual(@as(usize, 0), item.cells.len);
}
+ try std.testing.expectEqual(@as(usize, 0), regions.items.len);
const no_layers = try encodeFramePointer(out, 2, 4, null, .arrow, &cells, &cells);
const cleared_packet = (try framed(no_layers)).?;
const cleared = (try decodeServer(cleared_packet.tag, cleared_packet.payload)).frame;
- try cleared.applyLayers(gpa, &decoded, &tags);
+ try cleared.applyLayers(gpa, &decoded, &tags, &regions, &chrome);
try std.testing.expectEqual(@as(u16, 0), decoded[0].rows);
}