a73x

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 {