summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 09:32:02 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit0ee8d748c028ad5257f95e15db38646aecd2851b (patch)
tree48b34e5604cd55f64bd9b323fdb9a2a6d0d60588 /src/ninep/tree.zig
parentcc5323772d5acd07376c561e8e08eaf365b7d22c (diff)
downloadpardes-0ee8d748c028ad5257f95e15db38646aecd2851b.tar.gz
pardes-0ee8d748c028ad5257f95e15db38646aecd2851b.zip
A root focus file names the pane with the keyboard and moves it; a pane's ctl says whether it is current
Scripts had no way to ask which pane has the keyboard or to give it one. rio answers both through a window's wctl: its read ends with current or notcurrent, and a write of current takes the keyboard (rio(4)). A pane's ctl line now ends the same way, and since there is one keyboard for the whole tree, /focus reads the focused pane's serial and takes a serial to move the keyboard there, off any header that had it; a serial no pane has fails with "no such window". fs-bench's stale readdir check is fixed too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig15
1 files changed, 14 insertions, 1 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index f93ab203..b0de446e 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -174,6 +174,7 @@ pub const TopFile = enum(u4) {
listeners,
pane,
new,
+ focus,
pub fn fileName(f: TopFile) []const u8 {
return if (f == .root) "/" else @tagName(f);
@@ -182,7 +183,7 @@ pub const TopFile = enum(u4) {
pub fn mode(f: TopFile) u16 {
return switch (f) {
.root, .pane => 0o755,
- .look, .exec, .log => 0o666,
+ .look, .exec, .log, .focus => 0o666,
.README, .index, .status, .screen, .listeners, .new => 0o444,
};
}
@@ -444,6 +445,11 @@ fn topSize(p: *Pardes, f: TopFile) u64 {
.look, .exec => ctl.resultsLen(p),
.log => events.logLen(&p.fs.log),
.listeners => screen.listenersLen(p),
+ .focus => focus: {
+ const pn = p.panes[p.active] orelse break :focus 0;
+ var digits: [16]u8 = undefined;
+ break :focus (std.fmt.bufPrint(&digits, "{d}\n", .{pn.serial}) catch unreachable).len;
+ },
};
}
@@ -516,6 +522,10 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, @intFromEnum(t), t.dir(), t.fileName());
}
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, fs.os_root, true, "os");
+ // After the first ten, which older clients list by position.
+ inline for (.{TopFile.focus}) |t| {
+ if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, @intFromEnum(t), t.dir(), t.fileName());
+ }
sources.stage(p, out, "", &skip);
},
.pane => {
@@ -705,6 +715,7 @@ fn read(p: *Pardes, req: Req, target: Target) Reply {
.log => events.readLog(p, req),
.screen => screen.readSnapshot(p, req, null),
.listeners => screen.readListeners(p, req),
+ .focus => ctl.readFocus(p, req),
// The serial the open handed this fid, so that two reads of one
// fid answer the same pane: the read observes, the open acted.
.new => serial: {
@@ -737,6 +748,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
.top => |f| return switch (f) {
.look, .exec => ctl.command(p, req, null, f == .exec),
.log => events.writeLog(p, req),
+ .focus => ctl.writeFocus(p, req),
else => Reply.fail(req.tag, E.PERM),
},
.pane => |t| {
@@ -894,6 +906,7 @@ test "readdir lists the root and a pane directory without creating anything" {
}
try testing.expectEqual(@intFromEnum(TopFile.pane), top[8].node);
try testing.expectEqual(fs.os_root, top[9].node);
+ try testing.expectEqualStrings("focus", top[10].name);
var idbuf: [16]u8 = undefined;
try testing.expect(th.nameAt(top, try std.fmt.bufPrint(&idbuf, "{d}", .{serial})) == null);
try testing.expect(th.nameAt(top, "self") == null);