diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index c91726f..96cd0d7 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -11,6 +11,8 @@ * Phase 35: Core Wayland protocol globals advertised (wl_compositor, * wl_subcompositor, wl_seat, wl_data_device_manager, xdg_wm_base). * Phase 36: wl_shm and wl_output advertised; XDG toplevel listener wired. + * Phase 37: frame callbacks driven by output frame event; XDG configure + * sent on new toplevel so clients complete their first frame. */ #ifdef HAVE_CONFIG_H @@ -26,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +64,8 @@ struct wl_compositor_state { /* Phase 36: XDG toplevel tracking */ struct wl_listener xdg_new_surface_listener; int managed_surface_count; /* surfaces seen */ + /* Phase 37: periodic timer that drives client frame callbacks */ + struct wl_event_source *frame_timer; char socket_name[64]; /* Phase 34: e.g. "wayland-0" */ int started; /* non-zero after successful display_open */ }; @@ -68,9 +73,34 @@ struct wl_compositor_state { static struct wl_compositor_state wl_state; /* ------------------------------------------------------------------ */ -/* Phase 36: XDG surface / toplevel management */ +/* Phase 36 / 37: XDG surface / toplevel management and frame loop */ /* ------------------------------------------------------------------ */ +/* + * Phase 37: Wayland event loop timer — drives client frame callbacks. + * For the headless backend we avoid the allocator/swapchain path entirely + * and call wlr_output_send_frame directly at ~60 fps. This causes + * wlroots to send wl_surface.frame callbacks to all waiting clients. + */ +static int +frame_timer_cb(void *data) +{ + (void)data; + + if (wl_state.output) + wlr_output_send_frame(wl_state.output); + + /* re-arm at ~60 fps */ + if (wl_state.frame_timer) + wl_event_source_timer_update(wl_state.frame_timer, 16); + return 0; +} + +/* + * Phase 36: new XDG surface listener. + * Sends an initial configure to toplevels so clients can commit their + * first buffer (size 0,0 = "use whatever size you want"). + */ static void handle_xdg_new_surface(struct wl_listener *listener, void *data) { @@ -86,6 +116,9 @@ handle_xdg_new_surface(struct wl_listener *listener, void *data) xdg_surface->toplevel->app_id ? xdg_surface->toplevel->app_id : "(none)", xdg_surface->toplevel->title ? xdg_surface->toplevel->title : "(none)"); + /* Phase 37: send the initial configure so the client can commit. */ + wlr_xdg_surface_schedule_configure(xdg_surface); + /* TODO: create WWindow and call policy layer (wManageWindow equivalent) * once the surface is committed (use map event on toplevel). */ } @@ -181,6 +214,18 @@ wl_display_open(const char *name) wl_signal_add(&wl_state.xdg_shell->events.new_surface, &wl_state.xdg_new_surface_listener); + /* Phase 37: periodic timer to drive client frame callbacks at ~60 fps. + * Using a timer avoids the swapchain/allocator complexity of the full + * render pipeline while still keeping Wayland clients unblocked. */ + { + struct wl_event_loop *loop = + wl_display_get_event_loop(wl_state.display); + wl_state.frame_timer = + wl_event_loop_add_timer(loop, frame_timer_cb, NULL); + if (wl_state.frame_timer) + wl_event_source_timer_update(wl_state.frame_timer, 16); + } + wl_state.started = 1; return NULL; /* no X11 Display* in the Wayland path */ } @@ -190,6 +235,10 @@ wl_display_close(void) { if (!wl_state.started) return; + if (wl_state.frame_timer) { + wl_event_source_remove(wl_state.frame_timer); + wl_state.frame_timer = NULL; + } if (wl_state.output_layout) { wlr_output_layout_destroy(wl_state.output_layout); wl_state.output_layout = NULL; diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 1647a10..0291206 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -14,6 +14,8 @@ # xdg_wm_base) — verified via weston-info if available # 6. (Phase 36) wl_shm and wl_output advertised; a real SHM client # (weston-simple-shm) can connect without crashing the compositor +# 7. (Phase 37) Frame callbacks fire; weston-simple-shm runs its render +# loop (stays alive for 1 s, compositor still alive afterwards) # # This test grows as wl_backend vtable slots are implemented. # @@ -130,12 +132,15 @@ fi # compositor is still alive after the client runs (or times out). if command -v weston-simple-shm >/dev/null 2>&1; then - CLIENT_OUT=$(WAYLAND_DISPLAY="$WL_SOCK" timeout 2s weston-simple-shm 2>&1) || true + # Run weston-simple-shm for up to 3 seconds; it loops drawing frames. + # A clean timeout (exit 124) or exit 0 both mean frame callbacks fired. + # We only fail if wl_shm is missing or the compositor crashes. + CLIENT_OUT=$(WAYLAND_DISPLAY="$WL_SOCK" timeout 3s weston-simple-shm 2>&1) || true echo "$CLIENT_OUT" | grep -q 'No wl_shm global' \ && die "weston-simple-shm: compositor missing wl_shm" kill -0 "$WM_PID" 2>/dev/null \ - || die "compositor crashed after weston-simple-shm connected" - echo " client check: weston-simple-shm connected; compositor survived" + || die "compositor crashed after weston-simple-shm ran" + echo " client check: weston-simple-shm ran 3 s; compositor survived" else echo " client check: skipped (weston-simple-shm not installed)" fi