summaryrefslogtreecommitdiff
path: root/src/detached
diff options
context:
space:
mode:
Diffstat (limited to 'src/detached')
-rw-r--r--src/detached/client.zig4
-rw-r--r--src/detached/server.zig3
-rw-r--r--src/detached/wire.zig131
3 files changed, 121 insertions, 17 deletions
diff --git a/src/detached/client.zig b/src/detached/client.zig
index 42404bca..9481cd54 100644
--- a/src/detached/client.zig
+++ b/src/detached/client.zig
@@ -103,6 +103,7 @@ pub const Client = struct {
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(.{}),
in: std.ArrayListUnmanaged(u8) = .empty,
out: std.ArrayListUnmanaged(u8) = .empty,
/// Bytes of `in` belonging to the message `next` returned last. Compacted at
@@ -179,6 +180,7 @@ pub const Client = struct {
c.gpa.free(layer.cells);
layer.* = .{};
}
+ for (&c.tag_layers) |*layer| c.gpa.free(layer.cells);
c.in.deinit(c.gpa);
c.out.deinit(c.gpa);
}
@@ -316,7 +318,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) catch |err| return switch (err) {
+ f.applyLayers(c.gpa, &c.body_layers, &c.tag_layers) catch |err| return switch (err) {
error.OutOfMemory => error.Closed,
else => @as(wire.Error, @errorCast(err)),
};
diff --git a/src/detached/server.zig b/src/detached/server.zig
index 82abc432..6b82cd7b 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -707,7 +707,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);
+ const want = wire.frameBound(surface.cols, surface.rows) + wire.layersBound(&surface.body_layers, &surface.tag_layers);
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)
&.{}
@@ -726,6 +726,7 @@ pub const Session = struct {
cells,
prev,
&surface.body_layers,
+ &surface.tag_layers,
) catch |err| {
log.debug("frame {d}x{d} not encodable: {t}", .{ surface.cols, surface.rows, err });
return;
diff --git a/src/detached/wire.zig b/src/detached/wire.zig
index 416330e8..245bc9f4 100644
--- a/src/detached/wire.zig
+++ b/src/detached/wire.zig
@@ -4,7 +4,7 @@ const std = @import("std");
const pardes = @import("../pardes.zig");
const limits = @import("../memory.zig").limits;
-pub const version: u16 = 5;
+pub const version: u16 = 6;
pub const Error = error{ Truncated, Overlong, BadTag, BadValue, Trailing, NoSpace };
@@ -164,15 +164,25 @@ pub const Frame = struct {
nruns: u32,
runs: []const u8,
- pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.BodyLayer) !void {
+ pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.BodyLayer, tags: *[pardes.MAX_TAG_LAYERS]pardes.TagLayer) !void {
for (layers) |*layer| {
gpa.free(layer.cells);
layer.* = .{};
}
- errdefer for (layers) |*layer| {
+ for (tags) |*layer| {
gpa.free(layer.cells);
layer.* = .{};
- };
+ }
+ errdefer {
+ for (layers) |*layer| {
+ gpa.free(layer.cells);
+ layer.* = .{};
+ }
+ for (tags) |*layer| {
+ gpa.free(layer.cells);
+ layer.* = .{};
+ }
+ }
if (f.layers.len == 0) return;
var r: Reader = .init(f.layers);
const count = try r.getU16();
@@ -196,6 +206,27 @@ pub const Frame = struct {
layer.cells = try gpa.alloc(pardes.Cell, @as(usize, layer.cols) * layer.rows);
for (layer.cells) |*cell| cell.* = try decodeCell(&r);
}
+ const tag_count = try r.getU16();
+ if (tag_count > tags.len) return error.Overlong;
+ for (tags[0..tag_count]) |*layer| {
+ layer.kind = try r.getTag(pardes.TagKind);
+ 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();
+ for (&layer.bg) |*channel| channel.* = try r.getByte();
+ if (layer.cols == 0 or layer.viewport.w == 0 or layer.viewport.h != 1 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) {
+ .workspace => layer.id != 0,
+ .column => layer.id >= pardes.MAX_COLS,
+ .pane => layer.id >= pardes.MAX_PANES,
+ })) 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);
+ for (layer.cells) |*cell| cell.* = try decodeCell(&r);
+ }
try r.end();
}
@@ -638,18 +669,21 @@ 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) usize {
- var n: usize = 2;
+pub fn layersBound(layers: []const pardes.BodyLayer, tags: []const pardes.TagLayer) usize {
+ var n: usize = 4;
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;
+ };
return n;
}
-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) Error![]const u8 {
+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 {
// 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
@@ -671,7 +705,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) {
+ if (layers.len != 0 or tags.len != 0) {
var count: u16 = 0;
for (layers) |layer| if (layer.rows != 0) {
count += 1;
@@ -693,6 +727,26 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
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);
}
+ var tag_count: u16 = 0;
+ for (tags) |layer| if (layer.cols != 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;
+ try w.putByte(@intFromEnum(layer.kind));
+ try w.putU16(layer.id);
+ try w.putU32(layer.serial);
+ try w.putU16(layer.viewport.x);
+ try w.putU16(layer.viewport.y);
+ try w.putU16(layer.viewport.w);
+ try w.putU16(layer.viewport.h);
+ try w.putU16(layer.cols);
+ 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);
+ for (layer.cells) |*cell| try putCell(&w, cell);
+ }
}
std.mem.writeInt(u32, w.buf[layers_len_at..][0..4], @intCast(w.n - layers_start), .little);
const nruns_at = w.n;
@@ -810,6 +864,34 @@ fn getBodyHit(r: *Reader) Error!?pardes.Mouse.BodyHit {
return hit;
}
+fn putTagHit(w: *Writer, hit: ?pardes.TagHit) Error!void {
+ try w.putBool(hit != null);
+ if (hit) |h| {
+ try w.putByte(@intFromEnum(h.kind));
+ try w.putU16(h.id);
+ try w.putU32(h.serial);
+ try w.putU16(h.col);
+ try w.putF32(h.pixel_x);
+ try w.putF32(h.pixel_y);
+ try putMetrics(w, h.metrics);
+ }
+}
+
+fn getTagHit(r: *Reader) Error!?pardes.TagHit {
+ if (!try r.getBool()) return null;
+ const hit: pardes.TagHit = .{
+ .kind = try r.getTag(pardes.TagKind),
+ .id = try r.getU16(),
+ .serial = try r.getU32(),
+ .col = try r.getU16(),
+ .pixel_x = try r.getF32(),
+ .pixel_y = try r.getF32(),
+ .metrics = (try getMetrics(r)) orelse return error.BadValue,
+ };
+ if (!std.math.isFinite(hit.pixel_x) or !std.math.isFinite(hit.pixel_y)) return error.BadValue;
+ return hit;
+}
+
pub fn encodeClient(out: []u8, msg: ClientMsg) Error![]const u8 {
var w: Writer = .init(out);
const at = try beginMessage(&w, @intFromEnum(clientTag(msg)));
@@ -839,6 +921,7 @@ pub fn encodeClient(out: []u8, msg: ClientMsg) Error![]const u8 {
try w.putU16(m.row);
try w.putBool(m.ctrl);
try putBodyHit(&w, m.body_hit);
+ try putTagHit(&w, m.tag_hit);
},
.resize => |rs| {
try w.putU16(rs.cols);
@@ -901,6 +984,7 @@ pub fn decodeClient(tag: u8, payload: []const u8) Error!ClientMsg {
.row = try r.getU16(),
.ctrl = try r.getBool(),
.body_hit = try getBodyHit(&r),
+ .tag_hit = try getTagHit(&r),
} } },
.resize => blk: {
var ev: pardes.Event = .{ .resize = .{ .cols = try r.getCols(), .rows = try r.getRows() } };
@@ -1735,15 +1819,20 @@ test "detached wire carries compact body rows and exact pointer metrics" {
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 } };
layer.context_separators.set(0);
- const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}));
+ 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}));
defer gpa.free(out);
- const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer});
+ const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag});
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(.{});
+ 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);
+ try frame.applyLayers(gpa, &decoded, &tags);
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));
@@ -1751,25 +1840,37 @@ test "detached wire carries compact body rows and exact pointer metrics" {
try std.testing.expect(decoded[0].hasContextBorderAfter(1));
try std.testing.expectEqualStrings("z", decoded[0].cells[10].grapheme());
try std.testing.expectEqual(@as(u16, 5), decoded[0].cursor.?.y);
+ try std.testing.expectEqual(tag.kind, tags[0].kind);
+ 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.expectEqualStrings("z", tags[0].cells[2].grapheme());
const pointer = layer.hitAt(4, 27, 8, 16, 6, 8).?;
- const message: ClientMsg = .{ .event = .{ .mouse = .{ .col = 0, .row = 1, .button = .left, .kind = .press, .body_hit = pointer } } };
+ const tag_pointer = tag.hitAt(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);
const mouse_packet = (try framed(mouse_encoded)).?;
const mouse = (try decodeClient(mouse_packet.tag, mouse_packet.payload)).event.mouse;
try std.testing.expectEqualDeep(pointer, mouse.body_hit.?);
+ try std.testing.expectEqualDeep(tag_pointer, mouse.tag_hit.?);
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));
+ try std.testing.expectError(error.Truncated, truncated.applyLayers(gpa, &decoded, &tags));
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(usize, 0), item.cells.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);
+ try cleared.applyLayers(gpa, &decoded, &tags);
try std.testing.expectEqual(@as(u16, 0), decoded[0].rows);
}