add multimonitor

This commit is contained in:
2026-08-18 21:30:51 +02:00
parent 7d117f4ae5
commit f60dd06e7e
10 changed files with 470 additions and 34 deletions
+72
View File
@@ -244,6 +244,78 @@ test "bits above the tag range do not affect the slot" {
try testing.expectEqual(act.tag_count, act.tagSlot(@as(u32, 1) << (act.tag_count - 1)));
}
// ─── splitting the tag set across screens ────────────────────────────────────
test "a lone screen owns every tag" {
try testing.expectEqual(act.all_tags, act.tagsForOutput(0, 1));
// No screens at all is not a case callers should have to think about.
try testing.expectEqual(act.all_tags, act.tagsForOutput(0, 0));
}
test "two screens split the tags into contiguous halves" {
// Nine tags do not halve evenly; the left screen takes the extra one.
try testing.expectEqual(@as(u32, 0b000011111), act.tagsForOutput(0, 2));
try testing.expectEqual(@as(u32, 0b111100000), act.tagsForOutput(1, 2));
}
test "three screens split the tags into thirds" {
try testing.expectEqual(@as(u32, 0b000000111), act.tagsForOutput(0, 3));
try testing.expectEqual(@as(u32, 0b000111000), act.tagsForOutput(1, 3));
try testing.expectEqual(@as(u32, 0b111000000), act.tagsForOutput(2, 3));
}
test "every tag lands on exactly one screen" {
// The whole point of the split: no tag is shared and none goes missing,
// whatever the screen count.
for (1..act.tag_count + 1) |count| {
var seen: u32 = 0;
for (0..count) |i| {
const mask = act.tagsForOutput(i, count);
try testing.expect(mask != 0);
try testing.expectEqual(@as(u32, 0), seen & mask);
seen |= mask;
}
try testing.expectEqual(act.all_tags, seen);
}
}
test "ranges are contiguous and in screen order" {
for (1..act.tag_count + 1) |count| {
var next: u32 = 0;
for (0..count) |i| {
const mask = act.tagsForOutput(i, count);
// Contiguous: a run of ones, starting where the last range ended.
const lowest = @ctz(mask);
const past_highest = 32 - @clz(mask);
try testing.expectEqual(next, lowest);
try testing.expectEqual(past_highest - lowest, @popCount(mask));
next = past_highest;
}
}
}
test "more screens than tags leaves no screen without one" {
// Twelve monitors and nine tags: the ones past the end share the last tag
// rather than being handed an empty mask, which could show nothing at all.
const count = act.tag_count + 3;
for (0..count) |i| {
try testing.expect(act.tagsForOutput(i, count) != 0);
}
try testing.expectEqual(act.tagsForOutput(act.tag_count - 1, count), act.tagsForOutput(count - 1, count));
}
test "a screen index past the end owns everything rather than nothing" {
try testing.expectEqual(act.all_tags, act.tagsForOutput(2, 2));
}
test "the lowest tag is what a stranded view falls back to" {
try testing.expectEqual(@as(u32, 0b100000), act.lowestTag(0b111100000));
try testing.expectEqual(@as(u32, 1), act.lowestTag(act.all_tags));
try testing.expectEqual(@as(u32, 0), act.lowestTag(0));
// Junk above the tag range is not a tag to fall back to.
try testing.expectEqual(@as(u32, 0), act.lowestTag(~act.all_tags));
}
// ─── command parsing ─────────────────────────────────────────────────────────
fn parseOk(argv: []const []const u8) act.Action {