eb6c0232
Add real-path DisplayOps adapter on Window
a73x 2026-04-16 11:08
DisplayAdapter thinly wraps wl.Display + wl.Surface and satisfies the DisplayOps vtable. frameCallbackListener receives wl_callback.done and forwards it to FrameLoop.onFrameCallbackDone, which performs the identity check.
diff --git a/src/wayland.zig b/src/wayland.zig index b77f540..e31224c 100644 --- a/src/wayland.zig +++ b/src/wayland.zig @@ -11,6 +11,12 @@ const c = @cImport({ @cInclude("unistd.h"); }); const frame_loop_mod = @import("frame_loop"); pub const FrameLoop = frame_loop_mod.FrameLoop; pub const DisplayOps = frame_loop_mod.DisplayOps; pub const SurfaceStateView = frame_loop_mod.SurfaceStateView; pub const CallbackToken = frame_loop_mod.CallbackToken; pub const Output = struct { wl_output: *wl.Output, name: u32, @@ -555,6 +561,56 @@ fn choosePreferredMime(mime_types: []const [:0]const u8) ?[:0]const u8 { return null; } pub const DisplayAdapter = struct { display: *wl.Display, surface: *wl.Surface, // NOTE: The frame listener receives `loop: *FrameLoop` directly through // zig-wayland's setListener data slot, so the adapter does not need to // carry a reference to the loop itself to dispatch .done events. fn flushThunk(ctx: *anyopaque) void { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); _ = self.display.flush(); } fn prepareReadThunk(ctx: *anyopaque) bool { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); return self.display.prepareRead(); } fn readEventsThunk(ctx: *anyopaque) void { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); _ = self.display.readEvents(); } fn dispatchPendingThunk(ctx: *anyopaque) void { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); _ = self.display.dispatchPending(); } fn getFdThunk(ctx: *anyopaque) std.posix.fd_t { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); return self.display.getFd(); } fn requestFrameThunk(ctx: *anyopaque, loop: *FrameLoop) anyerror!CallbackToken { const self: *DisplayAdapter = @ptrCast(@alignCast(ctx)); const cb = try self.surface.frame(); cb.setListener(*FrameLoop, frameCallbackListener, loop); return @ptrCast(cb); } fn destroyCallbackThunk(_: *anyopaque, token: CallbackToken) void { const cb: *wl.Callback = @constCast(@ptrCast(@alignCast(token))); cb.destroy(); } }; fn frameCallbackListener(cb: *wl.Callback, event: wl.Callback.Event, loop: *FrameLoop) void { switch (event) { .done => { const tok: CallbackToken = @ptrCast(cb); loop.onFrameCallbackDone(tok); }, } } pub const Window = struct { alloc: std.mem.Allocator, surface: *wl.Surface, @@ -568,6 +624,7 @@ pub const Window = struct { should_close: bool = false, width: u32 = 800, height: u32 = 600, display_adapter: DisplayAdapter = undefined, pub fn deinit(self: *Window) void { self.xdg_toplevel.destroy(); @@ -601,6 +658,33 @@ pub const Window = struct { } } } /// Build a DisplayOps vtable that drives the real compositor. pub fn displayOps(self: *Window, display: *wl.Display) DisplayOps { // Carry both display and surface via the owned adapter struct. self.display_adapter = .{ .display = display, .surface = self.surface, }; return .{ .ctx = &self.display_adapter, .flushFn = DisplayAdapter.flushThunk, .prepareReadFn = DisplayAdapter.prepareReadThunk, .readEventsFn = DisplayAdapter.readEventsThunk, .dispatchPendingFn = DisplayAdapter.dispatchPendingThunk, .getFdFn = DisplayAdapter.getFdThunk, .requestFrameFn = DisplayAdapter.requestFrameThunk, .destroyCallbackFn = DisplayAdapter.destroyCallbackThunk, }; } pub fn surfaceStateView(self: *const Window) SurfaceStateView { return .{ .configured_ptr = &self.state.configured, .suspended_ptr = &self.state.suspended, .tracker = self.tracker, }; } }; pub const Connection = struct {