From a7960117dfb83ad65a0466a96affbbb524936b7f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 03:14:31 -0300 Subject: 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 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 --- src/ninep/ctl.zig | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/ninep/ctl.zig') 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 ", 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 `, 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()); +} -- cgit v1.3