a73x

2897c867

Add pure ScaleTracker for wl_output scale tracking

a73x   2026-04-09 10:04

Commit message
Add pure ScaleTracker for wl_output scale tracking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

build.zig
Old New
@@ -9,6 +9,12 @@ pub fn build(b: *std.Build) void {
9 .optimize = optimize, 9 .optimize = optimize,
10 }); 10 });
11 11
12 const scale_tracker_mod = b.createModule(.{
13 .root_source_file = b.path("src/scale_tracker.zig"),
14 .target = target,
15 .optimize = optimize,
16 });
17
12 // Lazy-fetch the ghostty dependency. On the first invocation this 18 // Lazy-fetch the ghostty dependency. On the first invocation this
13 // materializes the package; subsequent builds use the local cache. 19 // materializes the package; subsequent builds use the local cache.
14 const ghostty_dep = b.lazyDependency("ghostty", .{}); 20 const ghostty_dep = b.lazyDependency("ghostty", .{});
@@ -37,6 +43,7 @@ pub fn build(b: *std.Build) void {
37 .link_libc = true, 43 .link_libc = true,
38 }); 44 });
39 wayland_mod.addImport("wayland", wayland_generated_mod); 45 wayland_mod.addImport("wayland", wayland_generated_mod);
46 wayland_mod.addImport("scale_tracker", scale_tracker_mod);
40 wayland_mod.linkSystemLibrary("wayland-client", .{}); 47 wayland_mod.linkSystemLibrary("wayland-client", .{});
41 wayland_mod.linkSystemLibrary("xkbcommon", .{}); 48 wayland_mod.linkSystemLibrary("xkbcommon", .{});
42 _ = wayland_dep; // referenced via Scanner 49 _ = wayland_dep; // referenced via Scanner
@@ -101,6 +108,17 @@ pub fn build(b: *std.Build) void {
101 }); 108 });
102 test_step.dependOn(&b.addRunArtifact(pty_tests).step); 109 test_step.dependOn(&b.addRunArtifact(pty_tests).step);
103 110
111 // Test scale_tracker.zig
112 const scale_tracker_test_mod = b.createModule(.{
113 .root_source_file = b.path("src/scale_tracker.zig"),
114 .target = target,
115 .optimize = optimize,
116 });
117 const scale_tracker_tests = b.addTest(.{
118 .root_module = scale_tracker_test_mod,
119 });
120 test_step.dependOn(&b.addRunArtifact(scale_tracker_tests).step);
121
104 // Test main.zig (and transitively vt.zig via its import) 122 // Test main.zig (and transitively vt.zig via its import)
105 const main_test_mod = b.createModule(.{ 123 const main_test_mod = b.createModule(.{
106 .root_source_file = b.path("src/main.zig"), 124 .root_source_file = b.path("src/main.zig"),
src/scale_tracker.zig
Old New
@@ -0,0 +1,137 @@
1 const std = @import("std");
2
3 pub const OutputId = u32;
4
5 pub const ScaleTracker = struct {
6 alloc: std.mem.Allocator,
7 scales: std.AutoHashMapUnmanaged(OutputId, i32),
8 entered: std.AutoHashMapUnmanaged(OutputId, void),
9
10 pub fn init(alloc: std.mem.Allocator) ScaleTracker {
11 return .{
12 .alloc = alloc,
13 .scales = .empty,
14 .entered = .empty,
15 };
16 }
17
18 pub fn deinit(self: *ScaleTracker) void {
19 self.scales.deinit(self.alloc);
20 self.entered.deinit(self.alloc);
21 }
22
23 pub fn addOutput(self: *ScaleTracker, id: OutputId) !void {
24 try self.scales.put(self.alloc, id, 1);
25 }
26
27 pub fn setOutputScale(self: *ScaleTracker, id: OutputId, scale: i32) void {
28 if (self.scales.getPtr(id)) |slot| slot.* = scale;
29 }
30
31 pub fn removeOutput(self: *ScaleTracker, id: OutputId) void {
32 _ = self.scales.remove(id);
33 _ = self.entered.remove(id);
34 }
35
36 pub fn enterOutput(self: *ScaleTracker, id: OutputId) !void {
37 try self.entered.put(self.alloc, id, {});
38 }
39
40 pub fn leaveOutput(self: *ScaleTracker, id: OutputId) void {
41 _ = self.entered.remove(id);
42 }
43
44 pub fn bufferScale(self: *const ScaleTracker) i32 {
45 var max_scale: i32 = 1;
46 var it = self.entered.iterator();
47 while (it.next()) |entry| {
48 const id = entry.key_ptr.*;
49 if (self.scales.get(id)) |s| {
50 if (s > max_scale) max_scale = s;
51 }
52 }
53 return max_scale;
54 }
55 };
56
57 test "new tracker reports default scale of 1" {
58 var t = ScaleTracker.init(std.testing.allocator);
59 defer t.deinit();
60 try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
61 }
62
63 test "entered output scale is reflected in bufferScale" {
64 var t = ScaleTracker.init(std.testing.allocator);
65 defer t.deinit();
66
67 try t.addOutput(1);
68 t.setOutputScale(1, 2);
69 try t.enterOutput(1);
70 try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
71 }
72
73 test "not-yet-entered output does not change bufferScale" {
74 var t = ScaleTracker.init(std.testing.allocator);
75 defer t.deinit();
76
77 try t.addOutput(7);
78 t.setOutputScale(7, 3);
79 try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
80 }
81
82 test "bufferScale is max across entered outputs" {
83 var t = ScaleTracker.init(std.testing.allocator);
84 defer t.deinit();
85
86 try t.addOutput(1);
87 try t.addOutput(2);
88 t.setOutputScale(1, 1);
89 t.setOutputScale(2, 2);
90
91 try t.enterOutput(1);
92 try t.enterOutput(2);
93 try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
94 }
95
96 test "leaving an output drops its contribution" {
97 var t = ScaleTracker.init(std.testing.allocator);
98 defer t.deinit();
99
100 try t.addOutput(1);
101 try t.addOutput(2);
102 t.setOutputScale(1, 2);
103 t.setOutputScale(2, 3);
104 try t.enterOutput(1);
105 try t.enterOutput(2);
106 try std.testing.expectEqual(@as(i32, 3), t.bufferScale());
107
108 t.leaveOutput(2);
109 try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
110 }
111
112 test "removing an unknown output is a no-op" {
113 var t = ScaleTracker.init(std.testing.allocator);
114 defer t.deinit();
115 t.removeOutput(999);
116 try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
117 }
118
119 test "removeOutput also removes it from entered set" {
120 var t = ScaleTracker.init(std.testing.allocator);
121 defer t.deinit();
122
123 try t.addOutput(5);
124 t.setOutputScale(5, 4);
125 try t.enterOutput(5);
126 try std.testing.expectEqual(@as(i32, 4), t.bufferScale());
127
128 t.removeOutput(5);
129 try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
130 }
131
132 test "setOutputScale on unknown id is a no-op" {
133 var t = ScaleTracker.init(std.testing.allocator);
134 defer t.deinit();
135 t.setOutputScale(999, 5);
136 try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
137 }