a73x

f743ae41

Decouple rendering from visibility; fix v5 compositor bootstrap

a73x   2026-04-17 06:20

Commit message
Decouple rendering from visibility; fix v5 compositor bootstrap

Always render when there is pending work — only gate the frame-callback
commit (flush) on canRender(), so the surface gets its first buffer
before wl_surface.enter arrives. Fixes the bootstrap deadlock where
visible() required enteredCount > 0 but the compositor won't send enter
until a buffer is attached.

Also drops xdg_wm_base bind from v6 to v5 for compositors (e.g. sway)
that don't yet advertise v6.

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

src/main.zig
Old New
@@ -270,7 +270,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
270 270
271 while (!window.should_close and p.isChildAlive()) { 271 while (!window.should_close and p.isChildAlive()) {
272 const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs()); 272 const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs());
273 const timeout = computePollTimeoutMs(repeat_timeout_ms, render_pending and frame_loop.canRender()); 273 const timeout = computePollTimeoutMs(repeat_timeout_ms, render_pending);
274 try frame_loop.waitForWork(&pollfds_extra, timeout); 274 try frame_loop.waitForWork(&pollfds_extra, timeout);
275 275
276 // PTY output 276 // PTY output
@@ -350,7 +350,6 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
350 } 350 }
351 351
352 if (!shouldRenderFrame(render_pending, false, false)) continue; 352 if (!shouldRenderFrame(render_pending, false, false)) continue;
353 if (!bench_unthrottled and !frame_loop.canRender()) continue; // hidden — no Vulkan at all
354 353
355 // applyPendingScale — Vulkan work, gated on canRender(). 354 // applyPendingScale — Vulkan work, gated on canRender().
356 if (scale_pending) { 355 if (scale_pending) {
@@ -625,7 +624,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
625 frame_ring.push(frame_timing); 624 frame_ring.push(frame_timing);
626 625
627 clearConsumedDirtyFlags(&term.render_state.dirty, dirty_rows, refresh_plan); 626 clearConsumedDirtyFlags(&term.render_state.dirty, dirty_rows, refresh_plan);
628 if (!bench_unthrottled) try frame_loop.commitRender(); 627 if (!bench_unthrottled and frame_loop.canRender()) try frame_loop.commitRender();
629 render_pending = false; 628 render_pending = false;
630 } 629 }
631 630
src/wayland.zig
Old New
@@ -1220,7 +1220,7 @@ fn registryListener(
1220 } else if (std.mem.eql(u8, iface, std.mem.span(wl.DataDeviceManager.interface.name))) { 1220 } else if (std.mem.eql(u8, iface, std.mem.span(wl.DataDeviceManager.interface.name))) {
1221 conn.globals.data_device_manager = registry.bind(g.name, wl.DataDeviceManager, 3) catch return; 1221 conn.globals.data_device_manager = registry.bind(g.name, wl.DataDeviceManager, 3) catch return;
1222 } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) { 1222 } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) {
1223 conn.globals.wm_base = registry.bind(g.name, xdg.WmBase, 6) catch return; 1223 conn.globals.wm_base = registry.bind(g.name, xdg.WmBase, 5) catch return;
1224 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) { 1224 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) {
1225 conn.globals.seat = registry.bind(g.name, wl.Seat, 9) catch return; 1225 conn.globals.seat = registry.bind(g.name, wl.Seat, 9) catch return;
1226 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Output.interface.name))) { 1226 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Output.interface.name))) {