summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 03:14:31 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commita7960117dfb83ad65a0466a96affbbb524936b7f (patch)
treea824bff6e91e0e0e3cf84e9ac88a12201d708f81 /src/ninep
parent6dfc6838c1c02978c026b650d69f87f23e3ff81c (diff)
downloadpardes-a7960117dfb83ad65a0466a96affbbb524936b7f.tar.gz
pardes-a7960117dfb83ad65a0466a96affbbb524936b7f.zip
A detached session with no frontend takes its size from the root ctl
A --detach session nobody is attached to stayed 80x24, too small for an agent's panes, and nothing could change it. The root ctl takes size <cols> <rows> while no frontend is attached (refused while one owns the size), and a client that attaches later still sets its own (the server compares with the core's size). Documented with the 80x24 default. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/ctl.zig30
1 files changed, 30 insertions, 0 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 378dd844..26a18a12 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -472,6 +472,14 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply {
var it = Messages.init(p, req.data);
while (it.next()) |line| {
if (line.len == 0) continue;
+ if (std.mem.startsWith(u8, line, "size ") or std.mem.eql(u8, line, "size")) {
+ const size = sizeOf(line) orelse return refuse(p, req, "size takes <cols> <rows>", line);
+ // A frontend's window is the size; only a session none is
+ // attached to (--detach, unattended) takes one written.
+ if (!p.unattended) return refuse(p, req, "size: a frontend is attached and owns the size", line);
+ if (apply) p.update(.{ .resize = .{ .cols = size[0], .rows = size[1] } });
+ continue;
+ }
if (!apply) {
if (checkBuiltin(p, req, line, .session)) |refusal| return refusal;
continue;
@@ -485,6 +493,15 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply {
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
+/// `size <cols> <rows>`, each at least a few cells and at most 4096.
+fn sizeOf(line: []const u8) ?[2]u16 {
+ var it = std.mem.tokenizeAny(u8, line["size".len..], " \t");
+ const cols = std.fmt.parseInt(u16, it.next() orelse return null, 10) catch return null;
+ const rows = std.fmt.parseInt(u16, it.next() orelse return null, 10) catch return null;
+ if (it.next() != null or cols < 20 or rows < 4 or cols > 4096 or rows > 4096) return null;
+ return .{ cols, rows };
+}
+
// ---- /commands ----
/// Every builtin the registry holds, one a line: its word, `arg` when it
@@ -1460,3 +1477,16 @@ test "Edit's p and = never fail for want of room for +Errors: the text is logged
for (p.panes) |slot| count += @intFromBool(slot != null);
try testing.expectEqual(@as(usize, 1), count); // the +Errors that had no room is gone
}
+
+test "size sets the screen of a session no frontend is attached to, and only of one" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const root_ctl = @intFromEnum(tree.TopFile.ctl);
+ try testing.expectEqual(E.INVAL, wr(p, root_ctl, "size 120 40\n").errno()); // a frontend's
+ p.unattended = true;
+ try testing.expectEqual(Status.ok, wr(p, root_ctl, "size 120 40\n").reply.status);
+ try testing.expectEqual(@as(u16, 120), p.screen_w);
+ try testing.expectEqual(@as(u16, 40), p.screen_h);
+ try testing.expectEqual(E.INVAL, wr(p, root_ctl, "size 120\n").errno());
+ try testing.expectEqual(E.INVAL, wr(p, root_ctl, "size 5 2\n").errno());
+}