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 }