From 0cde1a7537cd9f88b2f97f3c51b54669d909b597 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 17:03:07 -0300 Subject: A focus moved over 9P tells the programs that asked for focus reports, as a click does A program that set DECSET 1004 heard `CSI O` and `CSI I` when the keyboard moved by a click, a key or the window's focus, because update() checks after each event. A /focus write went through serveFs, which never did, so the program kept believing it had focus until the next input event. serveFs now runs the same check once a request has settled. Co-Authored-By: Claude Opus 5.5 --- src/ninep/ctl.zig | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/ninep/ctl.zig') diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 66fa22ab..aebd07e2 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2563,6 +2563,32 @@ test "a 9P look keeps its leading blanks, a blank-only look included, and exec s try testing.expect(th.logHas(p, "blanks trimmed")); } +test "a focus moved over 9P tells the programs that asked for focus reports, as a click does" { + const p = try Pardes.init(testing.allocator, .{ .cols = 90, .rows = 30, .shells = 2 }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const first = for (p.panes) |slot| { + if (slot) |pane| if (pane.isTerminal()) break pane.serial; + } else return error.NoShell; + try testing.expectEqual(Status.ok, wr(p, Node.of(first, .exec), "Tty\n").reply.status); + var shells: [2]usize = undefined; + var n: usize = 0; + for (p.panes, 0..) |slot, id| if (slot) |pane| if (pane.isTerminal() and n < 2) { + shells[n] = id; + n += 1; + }; + try testing.expectEqual(@as(usize, 2), n); + p.active = shells[0]; + p.update(.{ .output = .{ .pane = @intCast(shells[0]), .bytes = "\x1b[?1004h" } }); + p.update(.{ .output = .{ .pane = @intCast(shells[1]), .bytes = "\x1b[?1004h" } }); + while (p.nextEffect()) |_| {} + var digits: [16]u8 = undefined; + const moved = wr(p, @intFromEnum(tree.TopFile.focus), try std.fmt.bufPrint(&digits, "{d}\n", .{p.panes[shells[1]].?.serial})); + try testing.expectEqual(Status.ok, moved.reply.status); + try testing.expectEqual(shells[1], p.active); + try testing.expectEqualStrings("\x1b[O\x1b[I", moved.pty_buf[0..moved.pty_len]); +} + test "a line starting with # runs as nothing at an exec or a ctl, silently" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); -- cgit v1.3