lib/windowing/src/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 const native_wayland = @import("wayland");
  4 const native_xkb = @import("xkb");
  5 const sys = @import("sys");
  6 
  7 const windowing = @import("root.zig");
  8 const window = @import("window.zig");
  9 const key = @import("key.zig");
 10 const mouse = @import("mouse.zig");
 11 const modifier = @import("modifier.zig");
 12 const cursor = @import("cursor.zig");
 13 const clipboard_module = @import("clipboard.zig");
 14 const gamepad_module = @import("gamepad.zig");
 15 const input_module = @import("input.zig");
 16 const config = @import("config.zig");
 17 const backend = @import("backend.zig");
 18 const wayland = @import("wayland/root.zig");
 19 const surface_module = @import("surface.zig");
 20 
 21 const event = windowing.event;
 22 const presentation = windowing.presentation;
 23 const Window = windowing.Window;
 24 const Size = windowing.Size;
 25 const Event = windowing.Event;
 26 const EventIterator = windowing.EventIterator;
 27 const KeyEvent = windowing.KeyEvent;
 28 const MouseButtonEvent = windowing.MouseButtonEvent;
 29 const MouseMoveEvent = windowing.MouseMoveEvent;
 30 const Key = windowing.Key;
 31 const MouseButton = windowing.MouseButton;
 32 const clipboard = windowing.clipboard;
 33 const input = windowing.input;
 34 const WindowConfig = windowing.WindowConfig;
 35 const default_wayland_compose_limits = windowing.default_wayland_compose_limits;
 36 const default_wayland_compose_scratch_limits = windowing.default_wayland_compose_scratch_limits;
 37 const SurfaceKind = windowing.SurfaceKind;
 38 const CocoaSurface = windowing.CocoaSurface;
 39 const ClipboardCapacity = windowing.ClipboardCapacity;
 40 const EventQueueCapacity = windowing.EventQueueCapacity;
 41 const GamepadCapacity = windowing.GamepadCapacity;
 42 const InputCapacity = windowing.InputCapacity;
 43 const PresentationCapacity = windowing.PresentationCapacity;
 44 const WaylandSessionCapacity = windowing.WaylandSessionCapacity;
 45 const WaylandPresentationCapacity = windowing.WaylandPresentationCapacity;
 46 const WaylandRuntimeCapacity = windowing.WaylandRuntimeCapacity;
 47 const WaylandComposeCapacity = windowing.WaylandComposeCapacity;
 48 const WaylandComposeScratchLimits = windowing.WaylandComposeScratchLimits;
 49 const WaylandComposeScratchCapacity = windowing.WaylandComposeScratchCapacity;
 50 const WaylandEventStorageCapacity = windowing.WaylandEventStorageCapacity;
 51 const wayland_process_environment_capacity = windowing.wayland_process_environment_capacity;
 52 
 53 test "windowing package namespace" {
 54     std.testing.refAllDecls(window);
 55     std.testing.refAllDecls(event);
 56     std.testing.refAllDecls(key);
 57     std.testing.refAllDecls(mouse);
 58     std.testing.refAllDecls(modifier);
 59     std.testing.refAllDecls(cursor);
 60     std.testing.refAllDecls(clipboard_module);
 61     std.testing.refAllDecls(gamepad_module);
 62     std.testing.refAllDecls(input_module);
 63     std.testing.refAllDecls(config);
 64     std.testing.refAllDecls(backend);
 65     if (comptime builtin.os.tag == .linux) {
 66         std.testing.refAllDecls(wayland);
 67         _ = @import("wayland/test.zig");
 68         _ = @import("wayland/toplevel/state.zig");
 69     }
 70     std.testing.refAllDecls(surface_module);
 71     std.testing.refAllDecls(presentation);
 72 }
 73 
 74 fn containsWindowClose(events: *EventIterator) bool {
 75     while (events.next()) |queued| {
 76         if (queued == .window_close) return true;
 77     }
 78     return false;
 79 }
 80 
 81 fn containsWindowFocus(events: *EventIterator, focused: bool) bool {
 82     while (events.next()) |queued| switch (queued) {
 83         .window_focus => |focus| if (focus.focused == focused) return true,
 84         else => {},
 85     };
 86     return false;
 87 }
 88 
 89 fn keyPress(events: *EventIterator, key_value: Key) ?KeyEvent {
 90     while (events.next()) |queued| switch (queued) {
 91         .key_press => |key_event| if (key_event.key == key_value) return key_event,
 92         else => {},
 93     };
 94     return null;
 95 }
 96 
 97 fn containsKeyRelease(events: *EventIterator, key_value: Key) bool {
 98     while (events.next()) |queued| switch (queued) {
 99         .key_release => |key_event| if (key_event.key == key_value) return true,
100         else => {},
101     };
102     return false;
103 }
104 
105 fn mousePress(events: *EventIterator, button: MouseButton) ?MouseButtonEvent {
106     while (events.next()) |queued| switch (queued) {
107         .mouse_press => |mouse_event| if (mouse_event.button == button) return mouse_event,
108         else => {},
109     };
110     return null;
111 }
112 
113 fn containsMouseRelease(events: *EventIterator, button: MouseButton) bool {
114     while (events.next()) |queued| switch (queued) {
115         .mouse_release => |mouse_event| if (mouse_event.button == button) return true,
116         else => {},
117     };
118     return false;
119 }
120 
121 fn mouseMove(events: *EventIterator) ?MouseMoveEvent {
122     while (events.next()) |queued| switch (queued) {
123         .mouse_move => |mouse_event| return mouse_event,
124         else => {},
125     };
126     return null;
127 }
128 
129 const CocoaMouseCase = struct {
130     down_type: sys.apple.coregraphics.EventType,
131     up_type: sys.apple.coregraphics.EventType,
132     number: u32,
133     button: MouseButton,
134 };
135 
136 const cocoa_mouse_cases = [_]CocoaMouseCase{
137     .{
138         .down_type = sys.apple.coregraphics.event_left_mouse_down,
139         .up_type = sys.apple.coregraphics.event_left_mouse_up,
140         .number = 0,
141         .button = .left,
142     },
143     .{
144         .down_type = sys.apple.coregraphics.event_right_mouse_down,
145         .up_type = sys.apple.coregraphics.event_right_mouse_up,
146         .number = 1,
147         .button = .right,
148     },
149     .{
150         .down_type = sys.apple.coregraphics.event_other_mouse_down,
151         .up_type = sys.apple.coregraphics.event_other_mouse_up,
152         .number = 2,
153         .button = .middle,
154     },
155     .{
156         .down_type = sys.apple.coregraphics.event_other_mouse_down,
157         .up_type = sys.apple.coregraphics.event_other_mouse_up,
158         .number = 3,
159         .button = .x1,
160     },
161     .{
162         .down_type = sys.apple.coregraphics.event_other_mouse_down,
163         .up_type = sys.apple.coregraphics.event_other_mouse_up,
164         .number = 4,
165         .button = .x2,
166     },
167 };
168 
169 fn postCocoaKeyEvent(
170     native: CocoaSurface,
171     keycode: u16,
172     key_down: bool,
173     flags: sys.apple.coregraphics.EventFlags,
174     repeated: bool,
175 ) !sys.apple.objc.Id {
176     const cg_event = sys.apple.coregraphics.createKeyboardEvent(keycode, key_down) orelse
177         return error.TestUnexpectedResult;
178     defer sys.apple.coregraphics.releaseEvent(cg_event);
179     sys.apple.coregraphics.setKeyboardEventFlags(cg_event, flags);
180     sys.apple.coregraphics.setKeyboardEventRepeated(cg_event, repeated);
181     return postCocoaEvent(native, cg_event);
182 }
183 
184 fn postCocoaMouseEvent(
185     native: CocoaSurface,
186     event_type: sys.apple.coregraphics.EventType,
187     button: sys.apple.coregraphics.MouseButton,
188 ) !sys.apple.objc.Id {
189     const cg_event = sys.apple.coregraphics.createMouseEvent(
190         event_type,
191         .{ .x = 16, .y = 16 },
192         button,
193     ) orelse return error.TestUnexpectedResult;
194     defer sys.apple.coregraphics.releaseEvent(cg_event);
195     return postCocoaEvent(native, cg_event);
196 }
197 
198 fn postCocoaEvent(
199     native: CocoaSurface,
200     cg_event: sys.apple.coregraphics.Event,
201 ) !sys.apple.objc.Id {
202     var pool = sys.apple.objc.AutoreleasePool.init();
203     defer pool.deinit();
204     const event_class = sys.apple.objc.class("NSEvent") orelse
205         return error.TestUnexpectedResult;
206     const ns_event = sys.apple.objc.send(
207         ?sys.apple.objc.Id,
208         event_class,
209         sys.apple.objc.selector("eventWithCGEvent:"),
210         .{cg_event},
211     ) orelse return error.TestUnexpectedResult;
212     const retained = sys.apple.objc.retain(ns_event);
213     sys.apple.objc.send(
214         void,
215         native.app,
216         sys.apple.objc.selector("postEvent:atStart:"),
217         .{ retained, sys.apple.objc.boolean(true) },
218     );
219     return retained;
220 }
221 
222 fn expectCocoaEventType(
223     ns_event: sys.apple.objc.Id,
224     expected: sys.apple.foundation.NSUInteger,
225 ) !void {
226     const actual = sys.apple.objc.send(
227         sys.apple.foundation.NSUInteger,
228         ns_event,
229         sys.apple.objc.selector("type"),
230         .{},
231     );
232     try std.testing.expectEqual(expected, actual);
233 }
234 
235 fn expectCocoaMouseButton(ns_event: sys.apple.objc.Id, expected: sys.apple.foundation.NSInteger) !void {
236     const actual = sys.apple.objc.send(
237         sys.apple.foundation.NSInteger,
238         ns_event,
239         sys.apple.objc.selector("buttonNumber"),
240         .{},
241     );
242     try std.testing.expectEqual(expected, actual);
243 }
244 
245 test "windowing Cocoa window presents through the public lifecycle" {
246     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
247     const width: u32 = 2;
248     const height: u32 = 2;
249     const frame_byte_count = width * height * 4;
250     var app_window = try Window.create(std.testing.allocator, .{
251         .width = width,
252         .height = height,
253         .visible = false,
254         .presentation = .{ .retained_frame_byte_count = frame_byte_count },
255     });
256     defer app_window.destroy();
257 
258     try std.testing.expectEqual(Size{ .width = width, .height = height }, app_window.getSize());
259     try std.testing.expectEqual(SurfaceKind.cocoa, app_window.getNativeSurface().kind());
260     const first = @as([frame_byte_count]u8, @splat(0x31));
261     const second = @as([frame_byte_count]u8, @splat(0x79));
262     try app_window.presentRgba8(&first, width, height);
263     try app_window.presentRgba8(&second, width, height);
264     try app_window.presentRgba8Region(&first, width, height, .{
265         .x = 1,
266         .y = 1,
267         .width = 1,
268         .height = 1,
269     });
270     _ = try app_window.pollEvents();
271     try std.testing.expectEqual(presentation.StorageStatus{}, app_window.presentationStorageStatus());
272 }
273 
274 test "windowing Cocoa window reports native close through public state" {
275     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
276     var app_window = try Window.create(std.testing.allocator, .{
277         .title = "windowing close event",
278         .width = 96,
279         .height = 64,
280         .visible = false,
281     });
282     defer app_window.destroy();
283 
284     const native = app_window.getNativeSurface().cocoa;
285     sys.apple.objc.send(
286         void,
287         native.window,
288         sys.apple.objc.selector("performClose:"),
289         .{sys.apple.objc.nil},
290     );
291     try std.testing.expect(app_window.shouldClose());
292     var events = try app_window.pollEvents();
293     try std.testing.expect(containsWindowClose(&events));
294 }
295 
296 test "windowing Cocoa window preserves empty title support" {
297     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
298     var app_window = try Window.create(std.testing.allocator, .{
299         .title = "",
300         .width = 96,
301         .height = 64,
302         .visible = false,
303     });
304     defer app_window.destroy();
305     app_window.setTitle("");
306 
307     const native = app_window.getNativeSurface().cocoa;
308     const title = sys.apple.objc.send(
309         sys.apple.foundation.String,
310         native.window,
311         sys.apple.objc.selector("title"),
312         .{},
313     );
314     const length = sys.apple.objc.send(
315         sys.apple.foundation.NSUInteger,
316         @ptrCast(title),
317         sys.apple.objc.selector("length"),
318         .{},
319     );
320     try std.testing.expectEqual(@as(sys.apple.foundation.NSUInteger, 0), length);
321 }
322 
323 test "windowing Cocoa delegate reports focus transitions through public events" {
324     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
325     var app_window = try Window.create(std.testing.allocator, .{
326         .title = "windowing focus events",
327         .width = 96,
328         .height = 64,
329         .visible = false,
330     });
331     defer app_window.destroy();
332 
333     const native = app_window.getNativeSurface().cocoa;
334     const delegate = sys.apple.objc.send(
335         ?sys.apple.objc.Id,
336         native.window,
337         sys.apple.objc.selector("delegate"),
338         .{},
339     ) orelse return error.TestUnexpectedResult;
340     sys.apple.objc.send(
341         void,
342         delegate,
343         sys.apple.objc.selector("windowDidBecomeKey:"),
344         .{sys.apple.objc.nil},
345     );
346     var events = try app_window.pollEvents();
347     try std.testing.expect(containsWindowFocus(&events, true));
348 
349     sys.apple.objc.send(
350         void,
351         delegate,
352         sys.apple.objc.selector("windowDidResignKey:"),
353         .{sys.apple.objc.nil},
354     );
355     events = try app_window.pollEvents();
356     try std.testing.expect(containsWindowFocus(&events, false));
357 }
358 
359 test "windowing Cocoa modifier events update public key state" {
360     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
361     var app_window = try Window.create(std.testing.allocator, .{
362         .title = "windowing modifier events",
363         .width = 96,
364         .height = 64,
365         .visible = false,
366     });
367     defer app_window.destroy();
368     const native = app_window.getNativeSurface().cocoa;
369 
370     const pressed_event = try postCocoaKeyEvent(
371         native,
372         0x38,
373         true,
374         sys.apple.cocoa.NSEventModifierFlagShift,
375         false,
376     );
377     defer sys.apple.objc.release(pressed_event);
378     try expectCocoaEventType(pressed_event, sys.apple.cocoa.NSEventTypeFlagsChanged);
379     var events = try app_window.pollEvents();
380     const pressed = keyPress(&events, .left_shift) orelse
381         return error.TestUnexpectedResult;
382     try std.testing.expect(pressed.modifiers.shift);
383     try std.testing.expect(app_window.isKeyDown(.left_shift));
384     try std.testing.expect(app_window.modifiers().shift);
385 
386     const right_pressed_event = try postCocoaKeyEvent(
387         native,
388         0x3C,
389         true,
390         sys.apple.cocoa.NSEventModifierFlagShift,
391         false,
392     );
393     defer sys.apple.objc.release(right_pressed_event);
394     events = try app_window.pollEvents();
395     const right_pressed = keyPress(&events, .right_shift) orelse
396         return error.TestUnexpectedResult;
397     try std.testing.expect(right_pressed.modifiers.shift);
398     try std.testing.expect(app_window.isKeyDown(.left_shift));
399     try std.testing.expect(app_window.isKeyDown(.right_shift));
400 
401     const left_released_event = try postCocoaKeyEvent(
402         native,
403         0x38,
404         false,
405         sys.apple.cocoa.NSEventModifierFlagShift,
406         false,
407     );
408     defer sys.apple.objc.release(left_released_event);
409     try expectCocoaEventType(left_released_event, sys.apple.cocoa.NSEventTypeFlagsChanged);
410     events = try app_window.pollEvents();
411     try std.testing.expect(containsKeyRelease(&events, .left_shift));
412     try std.testing.expect(!app_window.isKeyDown(.left_shift));
413     try std.testing.expect(app_window.isKeyDown(.right_shift));
414     try std.testing.expect(app_window.modifiers().shift);
415 
416     const right_released_event = try postCocoaKeyEvent(native, 0x3C, false, 0, false);
417     defer sys.apple.objc.release(right_released_event);
418     events = try app_window.pollEvents();
419     try std.testing.expect(containsKeyRelease(&events, .right_shift));
420     try std.testing.expect(!app_window.isKeyDown(.right_shift));
421     try std.testing.expect(!app_window.modifiers().shift);
422 }
423 
424 test "windowing Cocoa key repeats preserve native metadata" {
425     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
426     var app_window = try Window.create(std.testing.allocator, .{
427         .title = "windowing repeat events",
428         .width = 96,
429         .height = 64,
430         .visible = false,
431     });
432     defer app_window.destroy();
433     const native = app_window.getNativeSurface().cocoa;
434 
435     const repeated_event = try postCocoaKeyEvent(native, 0x00, true, 0, true);
436     defer sys.apple.objc.release(repeated_event);
437     const native_repeated = sys.apple.objc.send(
438         sys.apple.objc.BOOL,
439         repeated_event,
440         sys.apple.objc.selector("isARepeat"),
441         .{},
442     );
443     try std.testing.expect(sys.apple.objc.isTrue(native_repeated));
444     var events = try app_window.pollEvents();
445     const pressed = keyPress(&events, .a) orelse return error.TestUnexpectedResult;
446     try std.testing.expect(pressed.repeated);
447 }
448 
449 test "windowing Cocoa other mouse buttons update public state" {
450     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
451     var app_window = try Window.create(std.testing.allocator, .{
452         .title = "windowing other mouse events",
453         .width = 96,
454         .height = 64,
455         .visible = false,
456     });
457     defer app_window.destroy();
458     const native = app_window.getNativeSurface().cocoa;
459     for (cocoa_mouse_cases) |case| {
460         const pressed_event = try postCocoaMouseEvent(
461             native,
462             case.down_type,
463             case.number,
464         );
465         defer sys.apple.objc.release(pressed_event);
466         try expectCocoaEventType(pressed_event, @intCast(case.down_type));
467         try expectCocoaMouseButton(pressed_event, case.number);
468         var events = try app_window.pollEvents();
469         _ = mousePress(&events, case.button) orelse return error.TestUnexpectedResult;
470         try std.testing.expect(app_window.isMouseButtonDown(case.button));
471 
472         const released_event = try postCocoaMouseEvent(
473             native,
474             case.up_type,
475             case.number,
476         );
477         defer sys.apple.objc.release(released_event);
478         try expectCocoaEventType(released_event, @intCast(case.up_type));
479         try expectCocoaMouseButton(released_event, case.number);
480         events = try app_window.pollEvents();
481         try std.testing.expect(containsMouseRelease(&events, case.button));
482         try std.testing.expect(!app_window.isMouseButtonDown(case.button));
483     }
484 }
485 
486 test "windowing Cocoa other mouse drag updates public position" {
487     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
488     var app_window = try Window.create(std.testing.allocator, .{
489         .title = "windowing other mouse drag",
490         .width = 96,
491         .height = 64,
492         .visible = false,
493     });
494     defer app_window.destroy();
495     const native = app_window.getNativeSurface().cocoa;
496     const dragged_event = try postCocoaMouseEvent(
497         native,
498         sys.apple.coregraphics.event_other_mouse_dragged,
499         3,
500     );
501     defer sys.apple.objc.release(dragged_event);
502     try expectCocoaEventType(dragged_event, sys.apple.cocoa.NSEventTypeOtherMouseDragged);
503     try expectCocoaMouseButton(dragged_event, 3);
504     var events = try app_window.pollEvents();
505     const moved = mouseMove(&events) orelse return error.TestUnexpectedResult;
506     const position = app_window.getMousePosition();
507     try std.testing.expectEqual(position.x, moved.x);
508     try std.testing.expectEqual(position.y, moved.y);
509 }
510 
511 test "windowing Cocoa window exposes consistent live backing scale" {
512     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
513     const width: u32 = 96;
514     const height: u32 = 64;
515     var app_window = try Window.create(std.testing.allocator, .{
516         .title = "windowing scale evidence",
517         .width = width,
518         .height = height,
519         .visible = false,
520     });
521     defer app_window.destroy();
522 
523     _ = try app_window.pollEvents();
524     const size = app_window.getSize();
525     const scale = app_window.getContentScale();
526     const framebuffer = app_window.getFramebufferSize();
527     const native_surface = app_window.getNativeSurface();
528     const extent = native_surface.extent();
529     const evidence = app_window.getScaleEvidence();
530     const resolved = sys.desktop.windowScaleFromEvidence(evidence);
531 
532     try std.testing.expectEqual(Size{ .width = width, .height = height }, size);
533     try std.testing.expect(std.math.isFinite(scale.x));
534     try std.testing.expect(std.math.isFinite(scale.y));
535     try std.testing.expect(scale.x > 0);
536     try std.testing.expect(scale.y > 0);
537     try std.testing.expectApproxEqAbs(scale.x, scale.y, 0.001);
538     try std.testing.expectEqual(
539         Size{
540             .width = @intFromFloat(@round(@as(f32, @floatFromInt(size.width)) * scale.x)),
541             .height = @intFromFloat(@round(@as(f32, @floatFromInt(size.height)) * scale.y)),
542         },
543         framebuffer,
544     );
545     try std.testing.expectEqual(SurfaceKind.cocoa, native_surface.kind());
546     try std.testing.expectEqual(size.width, extent.width);
547     try std.testing.expectEqual(size.height, extent.height);
548     try std.testing.expectEqual(framebuffer.width, extent.framebuffer_width);
549     try std.testing.expectEqual(framebuffer.height, extent.framebuffer_height);
550     try std.testing.expectApproxEqAbs(scale.x, extent.scale_x, 0.001);
551     try std.testing.expectApproxEqAbs(scale.y, extent.scale_y, 0.001);
552     try std.testing.expect(evidence.has_window_content_scale);
553     try std.testing.expectApproxEqAbs(scale.x, evidence.window_display, 0.001);
554     try std.testing.expectApproxEqAbs(scale.y, evidence.display_content, 0.001);
555     try std.testing.expectApproxEqAbs(scale.x, evidence.pixel_density, 0.001);
556     try std.testing.expectApproxEqAbs(scale.x, resolved.ui, 0.001);
557 }
558 
559 test "windowing root composes Wayland presentation capacity from window limits" {
560     const config_value = WindowConfig{ .presentation = .{
561         .pending_feedback_count = 3,
562         .retained_outcome_count = 5,
563         .active_buffer_count = 2,
564         .retired_buffer_count = 7,
565         .retained_frame_byte_count = 13,
566     } };
567     const frames = try PresentationCapacity.derive(config_value.presentation);
568     const capacity = try WaylandPresentationCapacity.derive(config_value.presentation);
569     try std.testing.expectEqual(@as(usize, 13), frames.retained_frame_byte_count);
570     try std.testing.expectEqual(@as(usize, 26), frames.maximum_backend_frame_storage_bytes);
571     try std.testing.expectEqual(@as(usize, 3), capacity.pending_feedback_count);
572     try std.testing.expectEqual(@as(usize, 5), capacity.retained_outcome_count);
573     try std.testing.expectEqual(@as(usize, 2), capacity.swapchain.active_buffer_count);
574     try std.testing.expectEqual(@as(usize, 7), capacity.swapchain.retired_buffer_count);
575     try std.testing.expectEqual(@as(usize, 13), capacity.pending_frame_bytes);
576     try std.testing.expectEqual(@as(usize, 11 * 13), capacity.maximum_foreign_mapped_frame_bytes);
577 }
578 
579 test "window rejects invalid frame storage before backend effects" {
580     if (builtin.os.tag != .linux and builtin.os.tag != .macos) return error.SkipZigTest;
581     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
582         .presentation = .{ .retained_frame_byte_count = 0 },
583     }));
584 }
585 
586 test "windowing root composes Wayland output capacity from window config" {
587     const config_value = WindowConfig{ .wayland_output_count = 3 };
588     const capacity = try WaylandSessionCapacity.derive(config_value.wayland_output_count);
589     try std.testing.expectEqual(@as(usize, 3), capacity.retained_output_count);
590     try std.testing.expectEqual(
591         3 * @sizeOf(wayland.session.output.Output),
592         capacity.retained_output_bytes,
593     );
594 }
595 
596 test "windowing root composes Wayland runtime capacity" {
597     const config_value = WindowConfig{ .wayland_runtime = .{
598         .transport = .{
599             .inbound_byte_count = 13,
600             .inbound_descriptor_count = 2,
601             .outbound_byte_count = 17,
602             .outbound_descriptor_count = 3,
603         },
604         .client_object_count = 5,
605         .server_object_count = 7,
606         .retained_global_count = 3,
607         .global_interface_name_byte_count = 11,
608     } };
609     const capacity = try WaylandRuntimeCapacity.derive(config_value.wayland_runtime);
610     try std.testing.expectEqual(@as(usize, 13), capacity.transport.inbound_byte_count);
611     try std.testing.expectEqual(@as(usize, 2), capacity.transport.inbound_descriptor_count);
612     try std.testing.expectEqual(@as(usize, 17), capacity.transport.outbound_byte_count);
613     try std.testing.expectEqual(@as(usize, 3), capacity.transport.outbound_descriptor_count);
614     try std.testing.expectEqual(@as(usize, 9), capacity.request_encoder.payload_byte_count);
615     try std.testing.expectEqual(@as(usize, 3), capacity.request_encoder.descriptor_count);
616     try std.testing.expectEqual(@as(usize, 5), capacity.event_storage.payload_byte_count);
617     try std.testing.expectEqual(@as(usize, 1), capacity.event_storage.descriptor_count);
618     try std.testing.expectEqual(
619         @sizeOf(@import("sys").fd.Descriptor),
620         capacity.event_storage.descriptor_bytes,
621     );
622     try std.testing.expectEqual(
623         capacity.event_storage,
624         try WaylandEventStorageCapacity.derive(.{
625             .payload_byte_count = 5,
626             .descriptor_count = 1,
627         }),
628     );
629     try std.testing.expectEqual(
630         native_wayland.runtime.maximum_event_creation_count,
631         capacity.event_creations.event_creation_count,
632     );
633     try std.testing.expectEqual(
634         @sizeOf(native_wayland.runtime.creation.Creation),
635         capacity.event_creations.event_creation_bytes,
636     );
637     try std.testing.expectEqual(@as(usize, 5), capacity.client_ids.client_id_count);
638     try std.testing.expectEqual(@as(usize, 5), capacity.objects.client_object_count);
639     try std.testing.expectEqual(@as(usize, 7), capacity.objects.server_object_count);
640     try std.testing.expectEqual(@as(usize, 3), capacity.globals.retained_global_count);
641     try std.testing.expectEqual(
642         @as(usize, 11),
643         capacity.globals.interface_name_byte_count_per_global,
644     );
645     try std.testing.expectEqual(
646         5 * @sizeOf(native_wayland.runtime.ObjectClientSlot),
647         capacity.objects.client_slot_bytes,
648     );
649     try std.testing.expectEqual(
650         7 * @sizeOf(native_wayland.runtime.ObjectServerSlot),
651         capacity.objects.server_slot_bytes,
652     );
653     try std.testing.expectEqual(
654         3 * @sizeOf(native_wayland.runtime.GlobalStorageSlot),
655         capacity.globals.retained_global_bytes,
656     );
657     try std.testing.expectEqual(@as(usize, 33), capacity.globals.interface_name_bytes);
658 }
659 
660 test "windowing root composes Wayland Compose capacity from window limits" {
661     comptime {
662         @stardustClaim(
663             @import("alloc_phase").capacity.witness(@import("xkb").compose.Storage, "xkb_compose_windowing_root"),
664             null,
665             null,
666             null,
667             null,
668             null,
669             null,
670         );
671     }
672 
673     const config_value = WindowConfig{
674         .wayland_compose = .{
675             .sequence_symbols = 2,
676             .text_bytes = 3,
677         },
678         .wayland_compose_scratch = .{
679             .line_bytes = 32,
680             .path_bytes = 64,
681         },
682     };
683     const capacity = try WaylandComposeCapacity.derive(config_value.wayland_compose);
684     const scratch_capacity = try WaylandComposeScratchCapacity.derive(
685         config_value.wayland_compose_scratch,
686     );
687     try std.testing.expectEqual(@as(usize, 3), capacity.node_count);
688     try std.testing.expectEqual(@as(usize, 2), capacity.edge_count);
689     try std.testing.expectEqual(@as(usize, 75), capacity.storage_bytes);
690     try std.testing.expectEqual(@as(usize, 743), scratch_capacity.storage_bytes);
691     try std.testing.expectEqual(
692         default_wayland_compose_limits,
693         (WindowConfig{}).wayland_compose,
694     );
695     try std.testing.expectEqual(
696         default_wayland_compose_scratch_limits,
697         (WindowConfig{}).wayland_compose_scratch,
698     );
699     try std.testing.expectEqual(
700         @as(usize, 253_968),
701         (try WaylandComposeCapacity.derive(default_wayland_compose_limits)).storage_bytes,
702     );
703     try std.testing.expectEqual(
704         @as(usize, 34_567),
705         (try WaylandComposeScratchCapacity.derive(
706             default_wayland_compose_scratch_limits,
707         )).storage_bytes,
708     );
709 }
710 
711 test "windowing root composes Wayland Compose scratch capacity from window limits" {
712     comptime {
713         @stardustClaim(
714             @import("alloc_phase").capacity.witness(@import("xkb").compose.ScratchStorage, "xkb_compose_scratch_windowing_root"),
715             null,
716             null,
717             null,
718             null,
719             null,
720             null,
721         );
722     }
723 
724     const limits = WaylandComposeScratchLimits{
725         .line_bytes = 32,
726         .path_bytes = 64,
727     };
728     const capacity = try WaylandComposeScratchCapacity.derive(limits);
729     try std.testing.expectEqual(@as(usize, 7), capacity.line_slot_count);
730     try std.testing.expectEqual(@as(usize, 8), capacity.path_slot_count);
731     try std.testing.expectEqual(@as(usize, 743), capacity.storage_bytes);
732 }
733 
734 test "windowing root exposes the configured Wayland process environment capacity" {
735     const capacity = wayland_process_environment_capacity;
736     try std.testing.expectEqual(
737         capacity.retained_entry_count + 1,
738         capacity.source_entry_count,
739     );
740     try std.testing.expectEqual(
741         capacity.source_entry_count,
742         capacity.generation_entry_count,
743     );
744     try std.testing.expectEqual(
745         capacity.generation_entry_count * @sizeOf(?[*:0]const u8) +
746             capacity.generation_owner_bytes,
747         capacity.total_static_bytes,
748     );
749 }
750 
751 test "windowing Wayland rejects invalid transport storage before backend effects" {
752     if (builtin.os.tag != .linux) return error.SkipZigTest;
753     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
754         .backend = .wayland,
755         .wayland_runtime = .{ .transport = .{ .inbound_byte_count = 0 } },
756     }));
757 }
758 
759 test "windowing Wayland rejects invalid object storage before backend effects" {
760     if (builtin.os.tag != .linux) return error.SkipZigTest;
761     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
762         .backend = .wayland,
763         .wayland_runtime = .{
764             .server_object_count = native_wayland.runtime.maximum_server_object_count + 1,
765         },
766     }));
767 }
768 
769 test "windowing Wayland rejects invalid global storage before backend effects" {
770     if (builtin.os.tag != .linux) return error.SkipZigTest;
771     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
772         .backend = .wayland,
773         .wayland_runtime = .{
774             .retained_global_count = 2,
775             .global_interface_name_byte_count = std.math.maxInt(usize),
776         },
777     }));
778 }
779 
780 test "windowing Wayland rejects empty output storage before backend effects" {
781     if (builtin.os.tag != .linux) return error.SkipZigTest;
782     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
783         .backend = .wayland,
784         .wayland_output_count = 0,
785     }));
786 }
787 
788 test "windowing Wayland rejects invalid Compose storage before backend effects" {
789     if (builtin.os.tag != .linux) return error.SkipZigTest;
790     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
791         .backend = .wayland,
792         .wayland_compose = .{
793             .sequence_symbols = native_xkb.compose.max_nodes,
794             .text_bytes = 0,
795         },
796     }));
797 }
798 
799 test "windowing Wayland rejects invalid Compose scratch before backend effects" {
800     if (builtin.os.tag != .linux) return error.SkipZigTest;
801     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
802         .backend = .wayland,
803         .wayland_compose_scratch = .{
804             .line_bytes = 0,
805             .path_bytes = 1,
806         },
807     }));
808 }
809 
810 test "windowing Wayland rejects invalid swapchain capacity before backend effects" {
811     if (builtin.os.tag != .linux) return error.SkipZigTest;
812     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
813         .backend = .wayland,
814         .presentation = .{ .active_buffer_count = 0 },
815     }));
816 }
817 
818 test "windowing root composes clipboard capacity from window limits" {
819     const config_value = WindowConfig{ .clipboard = .{
820         .received_text_byte_count = 7,
821         .transfer_read_byte_count = 3,
822         .retained_wait_event_count = 2,
823     } };
824     const capacity = try ClipboardCapacity.derive(config_value.clipboard);
825     try std.testing.expectEqual(@as(usize, 7), capacity.received_text_byte_count);
826     try std.testing.expectEqual(@as(usize, 3), capacity.transfer_read_byte_count);
827     try std.testing.expectEqual(@as(usize, 2), capacity.retained_wait_event_count);
828 }
829 
830 test "window rejects invalid clipboard capacity before backend effects" {
831     if (builtin.os.tag != .linux and builtin.os.tag != .macos) return error.SkipZigTest;
832     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
833         .clipboard = .{ .transfer_read_byte_count = 0 },
834     }));
835 }
836 
837 test "windowing root composes event queue capacity from window limits" {
838     const config_value = WindowConfig{ .events = .{ .retained_event_count = 3 } };
839     const capacity = try EventQueueCapacity.derive(config_value.events);
840     try std.testing.expectEqual(@as(usize, 3), capacity.retained_event_count);
841     try std.testing.expectEqual(3 * @sizeOf(Event), capacity.retained_event_bytes);
842 }
843 
844 test "window rejects invalid event queue capacity before backend effects" {
845     if (builtin.os.tag != .linux and builtin.os.tag != .macos) return error.SkipZigTest;
846     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
847         .events = .{ .retained_event_count = 0 },
848     }));
849 }
850 
851 test "windowing root composes gamepad capacity from window limits" {
852     const config_value = WindowConfig{ .gamepads = .{
853         .retained_gamepad_count = 2,
854         .retained_name_byte_count_per_gamepad = 5,
855         .read_event_count = 3,
856         .scanned_source_count = 7,
857     } };
858     const capacity = try GamepadCapacity.derive(config_value.gamepads);
859     const expected_read_event_count: usize = if (builtin.os.tag == .linux) 3 else 0;
860     try std.testing.expectEqual(@as(usize, 2), capacity.retained_gamepad_count);
861     try std.testing.expectEqual(@as(usize, 5), capacity.retained_name_byte_count_per_gamepad);
862     try std.testing.expectEqual(expected_read_event_count, capacity.read_event_count);
863     try std.testing.expectEqual(@as(u32, 7), capacity.scanned_source_count);
864 }
865 
866 test "window rejects invalid gamepad capacity before backend effects" {
867     if (builtin.os.tag != .linux and builtin.os.tag != .macos) return error.SkipZigTest;
868     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
869         .gamepads = .{ .scanned_source_count = 0 },
870     }));
871 }
872 
873 test "windowing root composes input capacity from window limits" {
874     const config_value = WindowConfig{ .input = .{
875         .retained_pressed_key_count = 3,
876         .retained_text_codepoint_count = 5,
877     } };
878     const capacity = try InputCapacity.derive(config_value.input);
879     try std.testing.expectEqual(@as(usize, 3), capacity.retained_pressed_key_count);
880     try std.testing.expectEqual(@as(usize, 5), capacity.retained_text_codepoint_count);
881 }
882 
883 test "window rejects invalid input capacity before backend effects" {
884     if (builtin.os.tag != .linux and builtin.os.tag != .macos) return error.SkipZigTest;
885     try std.testing.expectError(error.InvalidCapacity, Window.create(std.testing.allocator, .{
886         .input = .{ .retained_pressed_key_count = 0 },
887     }));
888 }