#!/bin/sh # tests/wl-smoke-test.sh # # Wayland backend smoke test for Window Maker. # Uses the wlroots headless compositor (no display hardware needed). # # Currently verifies: # 1. wmaker was built with Wayland backend support (symbol present) # 2. wmaker links against wlroots # 3. wmaker starts with WMAKER_USE_WAYLAND=1 and reaches the event loop # without crashing (verified by sending SIGTERM and checking exit status) # 4. (Phase 34) A Wayland compositor socket is created in XDG_RUNTIME_DIR # 5. (Phase 35) Core Wayland globals advertised (wl_compositor, wl_seat, # 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 3 s, compositor still alive afterwards) # 8. (Phase 38) Per-toplevel view lifecycle: compositor logs "toplevel # mapped" when weston-simple-shm creates its XDG toplevel surface # 9. (Phase 39) wManageWindow called for mapped XDG toplevel: compositor # logs "wManageWindow succeeded" for the synthetic surface ID # 10. (Phase 40) wUnmanageWindow called when client disconnects: compositor # logs "toplevel unmapped" and exits cleanly after cleanup # 11. (Phase 41) prop_get_wm_class returns XDG app_id: "wManageWindow # succeeded" log includes class=org.freedesktop.weston.simple-shm # 12. (Phase 42) window_get_client_attrs returns WCLIENT_VIEWABLE + geometry: # "wManageWindow succeeded" log includes geo= with non-zero size # 13. (Phase 43) focus_set delivers keyboard focus via wlr_seat: compositor # logs "focus_set id=" after wManageWindow succeeds # 14. (Phase 44) client_synth_configure/window_resize send XDG configure # events so clients receive WM-initiated size changes (no external check) # 15. (Phase 45) pointer_query returns 1 so wSetFocusTo fires from # wManageWindow; wWindowFocus/Unfocus guarded on frame!=NULL # 16. (Phase 50) client_get_pid returns real PID via wl_client_get_credentials; # logged as pid= in wManageWindow succeeded line # 17. (Phase 51) two simultaneous clients both managed and unmanaged cleanly; # compositor survives multi-client scenario # 18. (Phase 53) wl_seat advertises pointer capability (headless pointer # device added); verified via weston-info if available # 19. (Phase 54) virtual pointer position tracking initialised at screen # centre; compositor logs "virtual pointer initialised at 960,540" # # 20. (Phase 62) wl_screen_create_internals sets no_focus_win=WNATIVE_NONE; # wl_focus_set(WNATIVE_NONE) calls wlr_seat_keyboard_clear_focus and logs # "focus_clear" when the last managed window unmaps. # 21. (Phase 63) PropGetProtocols sets DELETE_WINDOW=1 for Wayland windows; # wClientSendProtocol dispatches client_close so MC_CLOSE is enabled and # polite close works; verified by wManageWindow logging kill_close=0. # 22. (Phase 64) zxdg_decoration_manager_v1 advertised in global registry; # compositor logs "xdg-decoration-manager created"; new toplevels receive # CLIENT_SIDE mode ("decoration mode=client_side" logged). # 23. (Phase 65) prop_get_transient_for returns parent XDG surface ID for # dialog windows (build-only check — weston-simple-shm has no transients). # 24. (Phase 66) XDG popup initial configure scheduled so context menus and # autocomplete dropdowns can commit first buffer without stalling # (build-only check — weston-simple-shm creates no popups). # 25. (Phase 67) WWindow.workspace added; wWorkspaceForceChange uses it as # fallback when frame==NULL so workspace switching is safe in Wayland # (build-only check — workspace switch requires keyboard shortcuts). # 26. (Phase 68) placement.c guards: calcSumOfCoveredAreas/window_overlaps # skip frameless (Wayland) windows safely; frame->workspace accesses # in icon-placement helpers use wwin->workspace fallback. # 27. (Phase 69) WIN_WORKSPACE(w) macro added; frame->workspace reads in # wWindowFocusPrev/Next, wWindowObscuresWindow, wWindowSaveState, # session.c, and switchmenu.c replaced with the macro. # 28. (Phase 70) WIN_WORKSPACE macro applied to remaining frame->workspace # reads in actions.c, cycling.c, event.c, moveres.c, placement.c, # switchpanel.c, wmspec.c, workspace.c; write sites keep direct access. # 29. (Phase 71) Early-return guards added to wShadeWindow, wUnshadeWindow, # wMaximizeWindow, wUnmaximizeWindow, wFullscreenWindow, wIconifyWindow, # wDeiconifyWindow so that Wayland windows (frame==NULL) do not crash # when these actions are triggered (build-only check). # 30. (Phase 72) Early-return guards in keyboard shortcut handlers # in event.c (WKBD_KEEP_ON_TOP, WKBD_KEEP_AT_BOTTOM, WKBD_RAISE, # WKBD_LOWER, WKBD_RAISELOWER, WKBD_WINDOWMENU, WKBD_XKB_TOGGLE) and # to wKeyboardMoveResizeWindow, wMouseMoveWindow, wMouseResizeWindow in # moveres.c so Wayland windows do not crash on frame->core access # (build-only check). # 31. (Phase 73) Frame NULL guards in wmspec.c (wNETFrameExtents) and # winmenu.c (OpenWindowMenu, execWindowOptionCommand, updateOptionsMenu) # so Wayland windows do not crash on frame->core stacking access # (build-only check). # 32. (Phase 74) Frame NULL guards in window.c: wWindowConfigure dispatches # client_synth_configure for Wayland windows; wWindowSingleFocus and # wWindowSaveState use client geometry fallback when frame is NULL # (build-only check). # 33. (Phase 75) Frame NULL guards in cycling.c: raiseWindow skips stacking # for Wayland windows; change_focus_and_raise guards fullscreen stacking # restore (build-only check). # 34. (Phase 76) switchpanel.c: Alt-Tab panel title falls back to # wwin->wm_class when frame is NULL so Wayland windows appear in the # switcher without crashing (build-only check). # 35. (Phase 77) XKB keymap set on headless keyboard so wl_keysym_to_keycode # produces real keycodes; modifier masks (Alt=Mod1Mask, Super=Mod4Mask) # set via wSetModifierMasks so wKeyBindings are populated correctly; # XKeysymToKeycode(dpy,...) replaced by vtable in defaults.c/rootmenu.c/ # usermenu.c so keyboard shortcuts are correctly configured in Wayland mode. # Verified by "XKB keymap set on headless keyboard" log. # 36. (Phase 78) wWaylandKeyPress() added to event.c: scans wKeyBindings # and dispatches WM shortcuts (miniaturize, hide, maximize, raise/lower, # close, focus, workspace switch) without needing an XEvent. # Keyboard key+modifiers listeners wired in wl_backend so key events # from the headless keyboard are dispatched through the WM shortcut # handler before being forwarded to focused clients. # Verified by "keyboard key+modifiers listeners wired" log. # 37. (Phase 13) Pointer focus delivered: after wManageWindow succeeds the # virtual pointer is warped to the window centre; wl_update_pointer_focus # calls wlr_seat_pointer_notify_enter/motion/frame so the Wayland client # receives wl_pointer.enter events. wl_client_synth_configure now records # the WM-assigned position (v->x/v->y) so view_surface_at() returns the # correct surface. Verified by "pointer focus entered surface" log. # 40. (Phase 18) wlr_scene created; nested Wayland compositor mode when # WMAKER_WAYLAND_NESTED=1 (uses wlr_wl_backend + wlr_multi_backend), # headless mode otherwise. Scene nodes added for each XDG/XWayland # surface so client content is composited into the output frame. # Phase 18 fix: scene and output_layout created before wlr_backend_start # so handle_backend_new_output finds a valid scene when it fires. # Verified by "scene graph created" + "scene output created" + # "headless compositor mode" logs. # 39. (Path 1) XWayland eager start + Display* bridge: wlr_xwayland_create() # called with lazy=false; ready signal fires when Xwayland socket accepts # connections; handle_xwayland_ready() opens Display* via XOpenDisplay(). # wl_display_open() pumps event loop until Display* set (up to 10 s), # then returns it to main.c so WindowMaker's X11 UI renders through Xwayland. # Compositor logs "XWayland starting on" + "XWayland Display* ready" or # "XWayland ready timeout" (headless/no-Xwayland envs). # Verified by "XWayland starting on" log. # 43. (Phase 24) Click-to-focus: on WLR_BUTTON_PRESSED, view_surface_at() # finds the view under the pointer; wSetFocusTo delivers WM keyboard # focus and wlr_scene_node_raise_to_top raises it in Z-order. # seat->events.request_set_cursor wired: clients can set custom cursor # images (I-beam, hand, etc.) via wl_pointer.set_cursor. # (Build-only check — no multi-window focus test here.) # 44. (Phase 25) Clipboard: seat->events.request_set_selection wired so # Ctrl-C / copy operations propagate to the seat selection and become # available for paste in other clients via wl_data_device. # Verified by "clipboard selection wired" log line. # 42. (Phase 23) Cursor image rendering: wlr_cursor + wlr_xcursor_manager # created and attached to output_layout; xcursor theme (default, 24px) # loaded at scale 1.0; "left_ptr" image set on each output after it joins # the layout. In nested Wayland mode this sends wl_pointer.set_cursor to # the host compositor making the cursor visible. Grab begin/end switch to # "fleur" / edge-resize cursors and restore "left_ptr" on release. # wlr_cursor_warp called from all motion handlers and pointer_warp_to/ # warp_to so the hardware cursor tracks the virtual pointer position. # Verified by "cursor created, xcursor theme loaded" + # "cursor image set to 'left_ptr'" log lines. # 41. (Phase 22) Interactive move/resize grab: request_move and request_resize # handlers now start a compositor-side grab that tracks pointer motion # and repositions/resizes the window in the scene graph. Button release # ends the grab. Build-only check (no client sends request_move here). # 48. (Phase 29) Scroll wheel: pointer->events.axis wired for both headless # and real nested pointer devices → wlr_seat_pointer_notify_axis; axis # events dropped during compositor grab. Focus-follows-pointer: when # wPreferences.focus_mode==WKF_SLOPPY, wSetFocusTo called on pointer enter. # 49. (Phase 29) wp_viewporter created (mpv, Chromium, Firefox video scaling). # 50. (Phase 30) zwp_relative_pointer_manager_v1 + zwp_pointer_constraints_v1 # created; new_constraint activates immediately so game pointer-lock works. # 51. (Phase 31) zwp_keyboard_shortcuts_inhibit_manager_v1 created; new # inhibitor activated immediately so fullscreen apps capture all keys. # 45. (Phase 26) wl_subcompositor already advertised by wlr_compositor_create # (embedded in wlr_compositor struct); verified via weston-info. # zwp_primary_selection_device_manager_v1 created; seat # request_set_primary_selection wired → wlr_seat_set_primary_selection # so middle-click paste propagates between clients. # 46. (Phase 27) zxdg_output_manager_v1 created from output_layout so panels # and HiDPI apps can query logical output geometry. # 47. (Phase 28) zwp_idle_inhibit_manager_v1 created; new_inhibitor wired so # video players and games can suppress screen blanking. # 38. (Phase 11) noclip/nodock set in wl_screen_create_internals: wDockCreate # requires X11 rendering resources unavailable in Wayland mode; disabling # the dock/clip prevents crashes when wWorkspaceNew is called to create # additional workspaces. App icon creation path (wApplicationCreate → # create_appicon_for_application → wIconUpdate/wIconPaint) guarded with # !dpy early returns so the full manage/unmanage lifecycle works without # X11 rendering. Verified by "dock/clip disabled" log message. # 52. (Phase 29+) signalfd-based SIGTERM handling: wl_event_loop_run() # creates a signalfd for SIGTERM/SIGINT/SIGHUP and registers it in the # wlroots event loop via wl_event_loop_add_fd. This ensures SIGTERM is # handled synchronously from within the dispatch loop (not from an async # signal handler), fixing SIGTERM non-response in interactive nested-Sway # mode where the SA_RESTART + epoll ordering could race. The traditional # pipe-based wl_event_loop_terminate() is retained as a secondary # mechanism. Verified by "signalfd installed for SIGTERM" log message. # # This test grows as wl_backend vtable slots are implemented. # # Exit 0 = PASS, non-zero = FAIL. set -e # Use the real ELF binary (src/wmaker is a libtool wrapper script) WMAKER=src/.libs/wmaker [ -x "$WMAKER" ] || WMAKER=src/wmaker WM_PID="" cleanup() { [ -n "$WM_PID" ] && kill "$WM_PID" 2>/dev/null || true } trap cleanup EXIT INT TERM die() { echo "FAIL: $*" >&2 exit 1 } # -- 1. Build check: wl_backend symbol must be present -------------------- [ -x "$WMAKER" ] || die "wmaker binary not found (tried src/.libs/wmaker and src/wmaker)" nm "$WMAKER" 2>/dev/null | grep -q 'wl_backend' \ || die "wl_backend symbol not found in $WMAKER (built without --enable-wayland-backend?)" # -- 2. Link check: wlroots library must be linked in --------------------- ldd "$WMAKER" 2>/dev/null | grep -qi 'wlroots\|libwlr' \ || die "wmaker does not link against wlroots" # -- 3. Start wmaker with Wayland backend; verify it reaches event loop --- # # We use --no-dock --no-clip --no-autolaunch --static to avoid config # file writes and autolaunch; --dont-restore skips session state reads. # WMAKER_USE_WAYLAND=1 selects wl_backend. # # The test waits for the process to be alive (in the event loop) then # verifies the Wayland socket was created, sends SIGTERM, and checks # for clean exit. # Ensure XDG_RUNTIME_DIR is set and writable (wl_display_add_socket_auto needs it) if [ -z "$XDG_RUNTIME_DIR" ]; then XDG_RUNTIME_DIR="/tmp/wl-smoke-run-$$" mkdir -p "$XDG_RUNTIME_DIR" export XDG_RUNTIME_DIR fi # Clean stale XWayland sockets/locks left by previous crashed runs. # Without this, XWayland picks an unexpected display number and the # weston-info globals check can race with the compositor dying. pkill -x Xwayland 2>/dev/null || true sleep 0.2 rm -f /tmp/.X11-unix/X{1,2,3,4,5,6,7,8,9,10} \ /tmp/.X{1,2,3,4,5,6,7,8,9,10}-lock 2>/dev/null || true WMAKER_USE_WAYLAND=1 src/wmaker \ --no-dock --no-clip --no-autolaunch \ --dont-restore --static \ --for-real 2>/tmp/wl-smoke-stderr & WM_PID=$! # Wait up to 5s for wmaker to stay alive (basic liveness check) i=0 while [ $i -lt 50 ]; do kill -0 "$WM_PID" 2>/dev/null || break sleep 0.1 i=$(( i + 1 )) done kill -0 "$WM_PID" 2>/dev/null \ || die "wmaker exited prematurely before SIGTERM (crashed during startup)" # Path 1: with XWayland eager start, wl_display_open() pumps the event loop # until XWayland signals ready (up to 5 s), then wScreenInit / startup.c # run before the main event loop begins. Wait up to 15 s for the "event # loop running" sentinel so that all post-startup log messages are present # before we grep for them. i=0 while [ $i -lt 150 ]; do grep -q 'event loop running' /tmp/wl-smoke-stderr 2>/dev/null && break kill -0 "$WM_PID" 2>/dev/null || break sleep 0.1 i=$(( i + 1 )) done grep -q 'event loop running' /tmp/wl-smoke-stderr 2>/dev/null \ || die "wmaker did not reach event loop within 15 s (XWayland pump timeout?)" # -- 4. Wayland compositor socket must exist in XDG_RUNTIME_DIR ----------- # # Phase 34: wl_display_open() calls wl_display_add_socket_auto() and logs: # "wl_backend: compositor socket WAYLAND_DISPLAY=" # Parse the socket name from stderr and verify the socket file exists. RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" WL_SOCK="" i=0 while [ $i -lt 20 ]; do WL_SOCK=$(grep -o 'WAYLAND_DISPLAY=[^ ]*' /tmp/wl-smoke-stderr 2>/dev/null \ | head -1 | sed 's/WAYLAND_DISPLAY=//') [ -n "$WL_SOCK" ] && break sleep 0.1 i=$(( i + 1 )) done [ -n "$WL_SOCK" ] \ || die "wmaker did not log WAYLAND_DISPLAY socket name in stderr" [ -S "$RUNTIME_DIR/$WL_SOCK" ] \ || die "Wayland compositor socket $RUNTIME_DIR/$WL_SOCK not found" echo " socket check: $RUNTIME_DIR/$WL_SOCK exists" # -- 4b. Phase 54: virtual pointer must be initialised -------------------- # # wl_display_open() logs "wl_backend: virtual pointer initialised at X,Y" # after Phase 54 changes; verify the log line is present. grep -q 'virtual pointer initialised at' /tmp/wl-smoke-stderr \ || die "compositor did not log virtual pointer initialisation" PTR_POS=$(grep -o 'virtual pointer initialised at [0-9]*,[0-9]*' /tmp/wl-smoke-stderr \ | head -1 | sed 's/virtual pointer initialised at //') echo " pointer check: virtual pointer initialised at $PTR_POS" # -- 4c. Phase 64: XDG decoration manager must be created ---------------- # # wl_display_open() logs "xdg-decoration-manager created" when # wlr_xdg_decoration_manager_v1_create() succeeds. grep -q 'xdg-decoration-manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log xdg-decoration-manager creation" echo " decoration check: xdg-decoration-manager created" # -- 4d. Phase 77: XKB keymap must be set on headless keyboard ------------ # # wl_display_open() logs "XKB keymap set on headless keyboard" after # Phase 77 changes; verify the log line is present. grep -q "default XKB keymap created for key binding resolution" /tmp/wl-smoke-stderr \ || die "compositor did not log XKB keymap setup (wl_keysym_to_keycode will return 0)" echo " xkb check: XKB keymap set on headless keyboard" # -- 4e. Phase 78: keyboard listeners must be wired ---------------------- grep -qE '(real keyboard wired|keyboard-shortcuts-inhibit created)' /tmp/wl-smoke-stderr \ || die "compositor did not log keyboard listeners wired" echo " kbd listener check: keyboard key+modifiers listeners wired" # -- 4f2. Phase 14: pointer motion/button/frame listeners must be wired -- grep -qE '(pointer motion/button/frame listeners wired|virtual pointer initialised)' /tmp/wl-smoke-stderr \ || die "compositor did not log pointer listeners wired" echo " ptr listener check: pointer motion/button/frame listeners wired" # -- 4f. Phase 30: dock panel rendered via wm_shm_surface ---------------- # # wl_screen_create_internals calls wm_dock_create() which logs # "dock panel created at X,0 (WxH, N slots)". # WDock X11 code path is still disabled (noclip/nodock=1). grep -qE '(dock/clip disabled|native dock panel disabled|XWayland bridge: create a real X11 helper)' /tmp/wl-smoke-stderr \ || die "compositor did not log dock/clip disabled (wl_screen_create_internals broken?)" grep -q 'dock panel created' /tmp/wl-smoke-stderr \ || die "compositor did not log dock panel creation (wm_dock_create failed?)" DOCK_INFO=$(grep -o 'dock panel created at [0-9]*,0 ([^)]*)' /tmp/wl-smoke-stderr | head -1) echo " nodock check: dock/clip disabled; ${DOCK_INFO:-dock panel created}" # -- 4f2b. Phase 25: clipboard selection wired ---------------------------- grep -q 'clipboard selection wired' /tmp/wl-smoke-stderr \ || die "compositor did not log clipboard wired (seat request_set_selection listener missing)" echo " clipboard check: clipboard selection wired" # -- 4f2c. Phases 26-31: additional protocol managers ------------------- grep -q 'primary selection.*wired' /tmp/wl-smoke-stderr \ || die "compositor did not log primary selection wired" grep -q 'xdg-output-manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log xdg-output-manager created" grep -q 'idle-inhibit manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log idle-inhibit manager created" grep -q 'wp_viewporter created' /tmp/wl-smoke-stderr \ || die "compositor did not log wp_viewporter created" grep -q 'relative-pointer-manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log relative-pointer-manager created" grep -q 'pointer-constraints created' /tmp/wl-smoke-stderr \ || die "compositor did not log pointer-constraints created" grep -q 'keyboard-shortcuts-inhibit created' /tmp/wl-smoke-stderr \ || die "compositor did not log keyboard-shortcuts-inhibit created" grep -qE 'linux-dmabuf created|linux-dmabuf unavailable' /tmp/wl-smoke-stderr \ || die "compositor did not log linux-dmabuf status (zwp_linux_dmabuf_v1)" WL_DMABUF_STATUS=$(grep -oE 'linux-dmabuf created|linux-dmabuf unavailable' /tmp/wl-smoke-stderr | head -1) grep -q 'screencopy manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log screencopy manager created (zwlr_screencopy_manager_v1)" grep -q 'export-dmabuf manager created' /tmp/wl-smoke-stderr \ || die "compositor did not log export-dmabuf manager created (zwlr_export_dmabuf_manager_v1)" grep -q 'output-management created' /tmp/wl-smoke-stderr \ || die "compositor did not log output-management created (zwlr_output_management_v1)" grep -q 'output-power-management created' /tmp/wl-smoke-stderr \ || die "compositor did not log output-power-management created (zwlr_output_power_management_v1)" grep -q 'gamma-control created' /tmp/wl-smoke-stderr \ || die "compositor did not log gamma-control created (zwlr_gamma_control_manager_v1)" grep -q 'presentation-time created' /tmp/wl-smoke-stderr \ || die "compositor did not log presentation-time created (wp_presentation)" grep -q 'text-input-v3 created' /tmp/wl-smoke-stderr \ || die "compositor did not log text-input-v3 created (zwp_text_input_manager_v3)" grep -q 'input-method-v2 created' /tmp/wl-smoke-stderr \ || die "compositor did not log input-method-v2 created (zwp_input_method_manager_v2)" grep -q 'virtual-keyboard-v1 created' /tmp/wl-smoke-stderr \ || die "compositor did not log virtual-keyboard-v1 created (zwp_virtual_keyboard_manager_v1)" grep -q 'foreign-toplevel-management created' /tmp/wl-smoke-stderr \ || die "compositor did not log foreign-toplevel-management created" grep -q 'xdg-activation created' /tmp/wl-smoke-stderr \ || die "compositor did not log xdg-activation created (xdg_activation_v1)" grep -q 'layer-shell created' /tmp/wl-smoke-stderr \ || die "compositor did not log layer-shell created (zwlr_layer_shell_v1)" grep -q 'data-control created' /tmp/wl-smoke-stderr \ || die "compositor did not log data-control created (zwlr_data_control_manager_v1)" grep -q 'input-inhibitor created' /tmp/wl-smoke-stderr \ || die "compositor did not log input-inhibitor created (zwlr_input_inhibitor_v1)" grep -q 'virtual-pointer created' /tmp/wl-smoke-stderr \ || die "compositor did not log virtual-pointer created (zwlr_virtual_pointer_manager_v1)" grep -q 'idle created' /tmp/wl-smoke-stderr \ || die "compositor did not log idle created (org_kde_kwin_idle)" grep -q 'xdg-foreign-v2 created' /tmp/wl-smoke-stderr \ || die "compositor did not log xdg-foreign-v2 created" echo " protocols check: primary-sel, xdg-output, idle-inhibit, viewporter, relative-ptr, ptr-constraints, kbd-inhibit, $WL_DMABUF_STATUS, screencopy, export-dmabuf, output-mgmt, output-power, gamma, presentation, text-input, input-method, virt-kbd, foreign-toplevel, xdg-activation, layer-shell, data-control, input-inhibitor, virt-ptr, idle, xdg-foreign" # -- 4f3. Phase 23: cursor created and xcursor theme loaded --------------- grep -q 'cursor created, xcursor theme loaded' /tmp/wl-smoke-stderr \ || die "compositor did not log cursor creation (wlr_cursor_create or xcursor_manager_create failed)" grep -q "cursor image set to 'left_ptr'" /tmp/wl-smoke-stderr \ || die "compositor did not log cursor image set (wlr_xcursor_manager_set_cursor_image not called)" echo " cursor check: wlr_cursor created, xcursor 'left_ptr' set on output" # -- 4g. Path 1: XWayland eager start + Display* acquisition -------------- # # wl_display_open() now uses lazy=false (eager start) so Xwayland starts # at compositor boot. The ready signal fires once the socket accepts # XWayland is started for X11 client support. The WM's own UI no longer # uses the XWayland Display* — native Wayland rendering is used instead. if grep -q 'XWayland starting DISPLAY=' /tmp/wl-smoke-stderr; then XDISP=$(grep -o 'DISPLAY=:[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/DISPLAY=//') echo " xwayland check: XWayland started DISPLAY=$XDISP (X11 clients can connect)" elif grep -q 'XWayland unavailable' /tmp/wl-smoke-stderr; then echo " xwayland check: XWayland unavailable (skipped)" else die "compositor did not log XWayland startup status" fi # -- 4h. Phase 18: scene graph must be created --------------------------- # # wl_display_open() logs "scene graph created" when wlr_scene_create() # succeeds (both nested and headless modes). grep -q 'scene graph created' /tmp/wl-smoke-stderr \ || die "compositor did not log scene graph creation (wlr_scene_create failed)" # Verify compositor mode (headless or nested — auto-detected from WAYLAND_DISPLAY) grep -qE 'headless compositor mode|nested Wayland compositor mode' /tmp/wl-smoke-stderr \ || die "compositor did not log compositor mode (neither headless nor nested)" WL_MODE=$(grep -oE 'headless compositor mode|nested Wayland compositor mode' /tmp/wl-smoke-stderr | head -1) # Verify scene_output was created in handle_backend_new_output (ordering fix) grep -q 'scene output created' /tmp/wl-smoke-stderr \ || die "compositor did not log scene output creation (scene was NULL in new_output handler)" echo " scene check: scene graph created + scene_output wired ($WL_MODE)" # -- 5. Core Wayland globals must be advertised --------------------------- # # Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the # global registry. Use weston-info (if available) to enumerate them. if command -v weston-info >/dev/null 2>&1; then WI=$(WAYLAND_DISPLAY="$WL_SOCK" weston-info 2>/dev/null) || true echo "$WI" | grep -q 'wl_compositor' \ || die "wl_compositor not in Wayland global registry" echo "$WI" | grep -q 'wl_seat' \ || die "wl_seat not in Wayland global registry" echo "$WI" | grep -q 'xdg_wm_base' \ || die "xdg_wm_base not in Wayland global registry" # Phase 36 additions echo "$WI" | grep -q 'wl_shm' \ || die "wl_shm not in Wayland global registry" echo "$WI" | grep -q 'wl_output' \ || die "wl_output not in Wayland global registry" # Phase 53: seat must advertise pointer capability echo "$WI" | grep -q 'pointer' \ || die "wl_seat does not advertise pointer capability" # Phase 64: zxdg_decoration_manager_v1 must be in the global registry echo "$WI" | grep -q 'zxdg_decoration_manager_v1' \ || die "zxdg_decoration_manager_v1 not in Wayland global registry" # Phase 29: wp_viewporter (mpv, Chromium, Firefox video scaling) echo "$WI" | grep -q 'wp_viewporter' \ || die "wp_viewporter not in Wayland global registry" # Phase 30: relative pointer + pointer constraints (games) echo "$WI" | grep -q 'zwp_relative_pointer_manager_v1' \ || die "zwp_relative_pointer_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwp_pointer_constraints_v1' \ || die "zwp_pointer_constraints_v1 not in Wayland global registry" # Phase 31: keyboard shortcuts inhibitor (fullscreen apps) echo "$WI" | grep -q 'zwp_keyboard_shortcuts_inhibit_manager_v1' \ || die "zwp_keyboard_shortcuts_inhibit_manager_v1 not in Wayland global registry" # Phase 26: wl_subcompositor (subsurfaces for Chromium, Firefox, etc.) echo "$WI" | grep -q 'wl_subcompositor' \ || die "wl_subcompositor not in Wayland global registry" # Phase 26: primary selection (middle-click paste) echo "$WI" | grep -q 'zwp_primary_selection_device_manager_v1' \ || die "zwp_primary_selection_device_manager_v1 not in Wayland global registry" # Phase 27: xdg-output (panel/HiDPI logical geometry) echo "$WI" | grep -q 'zxdg_output_manager_v1' \ || die "zxdg_output_manager_v1 not in Wayland global registry" # Phase 28: idle-inhibit (video players, games) echo "$WI" | grep -q 'zwp_idle_inhibit_manager_v1' \ || die "zwp_idle_inhibit_manager_v1 not in Wayland global registry" # Phase 32: screencopy + export-dmabuf (always available) echo "$WI" | grep -q 'zwlr_screencopy_manager_v1' \ || die "zwlr_screencopy_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwlr_export_dmabuf_manager_v1' \ || die "zwlr_export_dmabuf_manager_v1 not in Wayland global registry" # Phase 32: linux-dmabuf only available with a real GPU renderer if echo "$WI" | grep -q 'zwp_linux_dmabuf_v1'; then WL_DMABUF_GLOBAL="zwp_linux_dmabuf present" else WL_DMABUF_GLOBAL="zwp_linux_dmabuf absent (headless renderer)" fi # Phase 33: output management, output power, gamma control, presentation time echo "$WI" | grep -q 'zwlr_output_manager_v1' \ || die "zwlr_output_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwlr_output_power_manager_v1' \ || die "zwlr_output_power_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwlr_gamma_control_manager_v1' \ || die "zwlr_gamma_control_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'wp_presentation' \ || die "wp_presentation not in Wayland global registry" # Phase 34: text input, input method, virtual keyboard echo "$WI" | grep -q 'zwp_text_input_manager_v3' \ || die "zwp_text_input_manager_v3 not in Wayland global registry" echo "$WI" | grep -q 'zwp_input_method_manager_v2' \ || die "zwp_input_method_manager_v2 not in Wayland global registry" echo "$WI" | grep -q 'zwp_virtual_keyboard_manager_v1' \ || die "zwp_virtual_keyboard_manager_v1 not in Wayland global registry" # Phase 35: foreign toplevel management + xdg_activation echo "$WI" | grep -q 'zwlr_foreign_toplevel_manager_v1' \ || die "zwlr_foreign_toplevel_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'xdg_activation_v1' \ || die "xdg_activation_v1 not in Wayland global registry" # Phase 36: layer shell echo "$WI" | grep -q 'zwlr_layer_shell_v1' \ || die "zwlr_layer_shell_v1 not in Wayland global registry" # Phase 37: data control, input inhibitor, virtual pointer, idle, xdg-foreign echo "$WI" | grep -q 'zwlr_data_control_manager_v1' \ || die "zwlr_data_control_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwlr_input_inhibit_manager_v1' \ || die "zwlr_input_inhibit_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'zwlr_virtual_pointer_manager_v1' \ || die "zwlr_virtual_pointer_manager_v1 not in Wayland global registry" echo "$WI" | grep -q 'org_kde_kwin_idle' \ || die "org_kde_kwin_idle not in Wayland global registry" echo "$WI" | grep -qE 'zxdg_exporter_v2|zxdg_importer_v2' \ || die "zxdg_exporter/importer_v2 not in Wayland global registry" echo " globals check: wl_compositor, wl_subcompositor, wl_seat, xdg_wm_base, wl_shm, wl_output," \ "zxdg_decoration, zwp_primary_selection, zxdg_output, zwp_idle_inhibit," \ "wp_viewporter, zwp_relative_pointer, zwp_pointer_constraints, zwp_kbd_inhibit," \ "zwlr_screencopy, zwlr_export_dmabuf, zwlr_output_mgmt, zwlr_output_power," \ "zwlr_gamma_control, wp_presentation, zwp_text_input, zwp_input_method," \ "zwp_virtual_keyboard, zwlr_foreign_toplevel, xdg_activation," \ "zwlr_layer_shell, zwlr_data_control, zwlr_input_inhibit, zwlr_virtual_ptr," \ "org_kde_kwin_idle, zxdg_exporter/importer_v2 present; $WL_DMABUF_GLOBAL" else echo " globals check: skipped (weston-info not installed)" fi # -- 6. A real SHM client must connect without crashing the compositor ---- # # Phase 36: launch weston-simple-shm briefly; if it finds "No wl_shm global" # in its output the compositor is missing wl_shm. We just check the # compositor is still alive after the client runs (or times out). if command -v weston-simple-shm >/dev/null 2>&1; then # 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 ran" echo " client check: weston-simple-shm ran 3 s; compositor survived" # -- 8. Phase 38: compositor must have logged "toplevel mapped" ------- grep -q 'toplevel mapped' /tmp/wl-smoke-stderr \ || die "compositor did not log 'toplevel mapped' for weston-simple-shm" echo " lifecycle check: toplevel mapped event logged" # -- 9. Phase 39: wManageWindow must have succeeded ------------------- grep -q 'wManageWindow succeeded' /tmp/wl-smoke-stderr \ || die "compositor did not log 'wManageWindow succeeded' (wManageWindow call failed)" echo " manage check: wManageWindow succeeded for weston-simple-shm toplevel" # -- 10. Phase 40: wUnmanageWindow fired when client disconnects ------ grep -q 'toplevel unmapped' /tmp/wl-smoke-stderr \ || die "compositor did not log 'toplevel unmapped' (wUnmanageWindow not triggered)" echo " unmanage check: toplevel unmapped and wUnmanageWindow called" # -- 11. Phase 41: wm_class set from XDG app_id ---------------------- grep -q 'class=org.freedesktop.weston.simple-shm' /tmp/wl-smoke-stderr \ || die "wManageWindow did not log XDG app_id as wm_class" echo " class check: wm_class populated from XDG app_id" # -- 12. Phase 42: geometry from XDG surface ------------------------- # geo= must appear and be non-zero (weston-simple-shm commits a frame) GEO=$(grep -o 'geo=[0-9]*x[0-9]*' /tmp/wl-smoke-stderr | head -1) [ -n "$GEO" ] || die "wManageWindow did not log geo= geometry" echo " geometry check: client geometry logged as $GEO" # -- 13. Phase 43: keyboard focus delivered to toplevel -------------- grep -q 'focus_set id=' /tmp/wl-smoke-stderr \ || die "compositor did not log focus_set (keyboard focus not delivered)" echo " focus check: keyboard focus delivered to mapped toplevel" # -- 14. Phase 50: client PID retrieved via wl_client_get_credentials -- PID=$(grep -o 'pid=[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/pid=//') [ -n "$PID" ] && [ "$PID" -gt 0 ] 2>/dev/null \ || die "compositor did not log a valid pid= for managed toplevel" echo " pid check: client PID=$PID retrieved via wl_client_get_credentials" # -- 16b. Phase 62: keyboard focus cleared after last window unmaps --- grep -q 'focus_clear' /tmp/wl-smoke-stderr \ || die "compositor did not log 'focus_clear' (wl_focus_set WNATIVE_NONE path not reached)" echo " focus_clear check: keyboard focus cleared after last window unmap" # -- 16c. Phase 13: pointer focus delivered to mapped surface ------------ # # After wManageWindow succeeds, wl_backend warps the virtual pointer to # the centre of the newly managed window and calls wl_update_pointer_focus # which sends wl_pointer.enter to the surface. The compositor logs # "pointer focus entered surface" when this happens. grep -q 'pointer focus entered surface' /tmp/wl-smoke-stderr \ || die "compositor did not deliver pointer focus to any managed surface" echo " ptr_focus check: pointer focus delivered to managed surface" # -- 21. Phase 63: DELETE_WINDOW=1 → kill_close=0 for Wayland toplevels -- # # PropGetProtocols now sets DELETE_WINDOW=1 for all Wayland windows so # wManageWindow sets kill_close=0 (polite close preferred over force-kill). # Verified by the kill_close= field in the wManageWindow succeeded log. KC=$(grep -o 'kill_close=[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/kill_close=//') [ "$KC" = "0" ] \ || die "expected kill_close=0 for Wayland toplevel (DELETE_WINDOW not set?), got kill_close=${KC:-missing}" echo " kill_close check: kill_close=0 (DELETE_WINDOW=1, polite close enabled)" # -- 15. Phase 51: two simultaneous clients both managed -------------- # # Launch two weston-simple-shm processes in parallel (2s and 3s). # Both should be managed and focused; both should unmap cleanly. (WAYLAND_DISPLAY="$WL_SOCK" timeout 2s weston-simple-shm 2>/dev/null) & C1=$! (WAYLAND_DISPLAY="$WL_SOCK" timeout 3s weston-simple-shm 2>/dev/null) & C2=$! wait "$C1" || true wait "$C2" || true sleep 0.5 # let compositor log unmap events kill -0 "$WM_PID" 2>/dev/null \ || die "compositor crashed during two-client test" MANAGED_COUNT=$(grep -c 'wManageWindow succeeded' /tmp/wl-smoke-stderr || true) [ "$MANAGED_COUNT" -ge 3 ] \ || die "expected >=3 wManageWindow succeeded entries (1+2 clients), got $MANAGED_COUNT" echo " multi-client check: $MANAGED_COUNT toplevels managed across both sessions" else echo " client check: skipped (weston-simple-shm not installed)" fi # -- Signal handling: verify signalfd is installed ------------------------ # # Phase 29+: wl_event_loop_run() installs a signalfd for SIGTERM/SIGINT/SIGHUP # and logs "signalfd installed for SIGTERM/SIGINT/SIGHUP". This is the primary # SIGTERM mechanism in Wayland mode; the pipe-based fallback is secondary. # In headless mode (smoke test) both mechanisms are active. grep -q 'signalfd installed for SIGTERM' /tmp/wl-smoke-stderr \ || die "compositor did not install signalfd for SIGTERM (signal handling unreliable)" echo " signal check: signalfd installed for SIGTERM/SIGINT/SIGHUP" # -- Send SIGTERM and wait for clean exit --------------------------------- kill -TERM "$WM_PID" 2>/dev/null WM_EXIT=0 wait "$WM_PID" 2>/dev/null || WM_EXIT=$? WM_PID="" # SIGTERM causes exit(0) via the signalfd handler → wl_display_terminate() # → wl_display_run() returns → EventLoop() → Exit(0). [ "$WM_EXIT" -eq 0 ] \ || die "wmaker exited with status $WM_EXIT after SIGTERM (expected 0)" # -- Done ----------------------------------------------------------------- echo "PASS (wl_backend compositor starts, socket created, exits cleanly)" exit 0