From c1c7bf0954eb7c0f9d9aff70737e84930630021e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 29 Jun 2026 16:05:52 +0900 Subject: fix: switch desktop window backend GLFW -> SDL2 (reliable drag-and-drop) The GLFW Wayland drag-and-drop crash fix (previous commit) did NOT resolve drag-and-drop on the real target (labwc), so abandon the GLFW path entirely instead of patching it further. Why SDL: raylib ships a first-class SDL backend (PLATFORM_DESKTOP_SDL) that implements file drop via SDL_DROPFILE. GLFW 3.4 (vendored in raylib 6.0) has broken Wayland drag-and-drop (wl_data_offer NULL source_actions/action listeners -> libwayland wl_abort on drag, glfw/glfw#2835) AND its X11 backend segfaults on WSLg (Mesa GLX). SDL's window/EGL/drag-drop is mature on BOTH real Wayland (labwc) and WSLg, so one backend covers both targets with no vendor patches and no X11/Wayland special-casing. Changes (desktop only; web build stays Emscripten+GLFW, untouched): - build.zig: build raylib PLATFORM=PLATFORM_DESKTOP_SDL (SDL_INCLUDE_PATH/ LIBRARY_PATH = system SDL2); link SDL2 instead of wayland-*/xkbcommon/EGL. - Revert the GLFW patch (patches/glfw-wayland-dnd-crash.patch removed; the build.zig patch-application step removed). - Docs: environment.md / study-player.md / BUILDING.md / SCREENSHOT.md updated to reflect the SDL backend + the SDL2 system dependency. The RmlUi binding is backend-agnostic (rlgl render + raylib IsKeyDown input, zero glfw* calls), so the swap is transparent to it. Prerequisite on the cyberdeck: install SDL2 dev (sudo pacman -S sdl2 or sdl2-compat) before building. Verified on the laptop (WSLg): build green; binary runs under SDL (Platform backend: DESKTOP (SDL)), window opens, no GLX segfault, RmlUi fonts load, mp3 loads from argv (132300 frames), no crash. Interactive drag-drop itself still needs user testing on the cyberdeck (SDL_DROPFILE path). --- .agents/knowledge/environment.md | 60 +++++++++++---------------- .agents/knowledge/study-player.md | 9 ++-- BUILDING.md | 14 ++++--- build.zig | 40 ++++++++---------- patches/glfw-wayland-dnd-crash.patch | 80 ------------------------------------ tools/SCREENSHOT.md | 17 ++++---- 6 files changed, 61 insertions(+), 159 deletions(-) delete mode 100644 patches/glfw-wayland-dnd-crash.patch diff --git a/.agents/knowledge/environment.md b/.agents/knowledge/environment.md index 38ae92d..a54ddca 100644 --- a/.agents/knowledge/environment.md +++ b/.agents/knowledge/environment.md @@ -9,42 +9,30 @@ rake / generator invocation (see rules/wsl-toolchain.md). The build scripts (`rebuild.sh`, `build_web.sh`) already do this and also prepend the user gem bin (`$(ruby -e 'puts Gem.user_dir')/bin`) so the Linux `rake` gem resolves. -## Graphics: use Wayland, not X11 (WSLg) -WSLg's X11/GLX path **segfaults inside Mesa** (`dri2GalliumConfigQueryb`). raylib -is therefore built with the **Wayland** GLFW backend: -`make ... GLFW_LINUX_ENABLE_WAYLAND=TRUE GLFW_LINUX_ENABLE_X11=FALSE`, and -`build.zig` links the wayland-* libs (`wayland-client/cursor/egl`, `xkbcommon`) -plus `EGL`. For a normal X11 desktop, swap those back to `X11` in both places. - -Expect harmless Mesa/EGL/zink warnings on stderr in this environment -(`libEGL warning: ... zink ...`, `Wayland: The platform does not provide the -window position`); they are not errors. - -## Wayland drag-and-drop crashes on GLFW 3.4 (patched) -raylib 6.0 vendors **GLFW 3.4** (release). Its Wayland `wl_data_offer_listener` -(`vendor/raylib/src/external/glfw/src/wl_window.c`) only sets the `offer` -(opcode 0) handler; `source_actions` (opcode 1) and `action` (opcode 2) are -NULL (`wl_data_offer` is v3; those events were added in v3). Modern compositors -(labwc, GNOME, KDE) emit source_actions/action *during a drag*, and -libwayland-client then `wl_abort("listener function for opcode N of -wl_data_offer is NULL")` — **the app crashes the moment a file is dragged over -the window**, before any drop registers (glfw/glfw#2835, #2562). This is why -drag-and-drop was "unreliable" in the rewrite but reliable in the original -`../source` app, which uses the X11/Xdnd backend (`-D_GLFW_X11`) — a separate -code path with none of these bugs. - -Fix: `patches/glfw-wayland-dnd-crash.patch` backports the upstream GLFW-master -fix (no-op source_actions/action handlers + two NULL-deref guards in the -data-device path). `build.zig` applies it **idempotently** before `make` -(marker = `dataOfferHandleAction` in the file; when newly applied it deletes the -stale `libraylib.a` so `make` actually rebuilds). The vendored file is -gitignored, so the patch file + the build.zig step ARE the committed fix — a -fresh checkout reproduces it. **Drop the patch once raylib vendors a GLFW -release containing the upstream fix.** - -Do NOT "fix" this by re-enabling X11 (`GLFW_LINUX_ENABLE_X11=TRUE`): on a real -Wayland session GLFW 3.4 still picks Wayland (so it wouldn't help), and forcing -X11 reintroduces the WSLg GLX segfault above. +## Graphics backend: raylib's SDL2 backend (not GLFW) +The desktop build uses raylib's **SDL2** backend (`build.zig` builds raylib with +`PLATFORM=PLATFORM_DESKTOP_SDL`; the binary links `SDL2`). This replaces the +earlier GLFW backend for two reasons, both WSLg/Wayland-related: + +- WSLg's **X11/GLX** path segfaults inside Mesa (`dri2GalliumConfigQueryb`). GLFW + was therefore forced to its **Wayland** backend (`GLFW_LINUX_ENABLE_WAYLAND=TRUE + GLFW_LINUX_ENABLE_X11=FALSE`), which links the wayland-* libs. +- GLFW 3.4 (vendored in raylib 6.0) has **broken drag-and-drop on Wayland**: its + `wl_data_offer_listener` leaves the `source_actions`/`action` handlers NULL, so + libwayland `wl_abort()`s when a compositor sends them during a drag → the app + crashes the moment a file is dragged over the window (glfw/glfw#2835). A + backport patch was tried and **did not** fix it on the real target (labwc), so + the whole GLFW path was abandoned. + +SDL's own window/EGL/drag-drop code is mature on **both** real Wayland (labwc) +and WSLg — one backend covers both targets, no vendor patches, no X11/Wayland +special-casing. It requires **SDL2 dev installed system-wide** (`pacman -S sdl2` +or `sdl2-compat`); the binary won't link otherwise. The web build is unaffected +(still Emscripten + GLFW; `build_web.sh` unchanged). The RmlUi binding is +backend-agnostic (renders via `rlgl`, reads input via raylib's `IsKeyDown` — no +`glfw*` calls), so the swap is transparent to it. + +Expect harmless Mesa/llvmpipe messages on stderr in WSLg (software GL); not errors. ## Toolchain versions (pinned) raylib 5.5, mruby 3.3.0, RmlUi 6.1, flecs v4.1.1, Zig 0.16.0, emcc 6.0.0 (emsdk). diff --git a/.agents/knowledge/study-player.md b/.agents/knowledge/study-player.md index e0d1831..f0da870 100644 --- a/.agents/knowledge/study-player.md +++ b/.agents/knowledge/study-player.md @@ -96,10 +96,11 @@ generated registrar runs. The Ruby `CheckFileDrop` system (`game/study_player/study_player.rb`) is correct and identical in pattern to the original C app. If drag-and-drop is unreliable, -the cause is NOT the Ruby — it is the GLFW/Wayland backend: see -`.agents/knowledge/environment.md` § "Wayland drag-and-drop crashes on GLFW 3.4" -(the `wl_data_offer` NULL-listener crash + the `patches/glfw-wayland-dnd-crash.patch` -fix). The X11 `../source` app never hits it. +the cause is NOT the Ruby — it is the window backend: see +`.agents/knowledge/environment.md` § "Graphics backend: raylib's SDL2 backend". +The rewrite switched from GLFW (whose Wayland drag-drop crashes — glfw/glfw#2835) +to raylib's SDL backend, which implements drop via `SDL_DROPFILE`. The original +`../source` app uses X11/Xdnd and never hit it. ## Non-blocking seek (skip_auto_update) After `Rl.seek_music_stream`, raylib's miniaudio stream may briefly report the diff --git a/BUILDING.md b/BUILDING.md index b6daebb..c0ece45 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -52,14 +52,16 @@ after applying — `make -C vendor/raylib/src clean`.) make -C vendor/raylib/src PLATFORM=PLATFORM_DESKTOP RAYLIB_LIBTYPE=STATIC -j4 ``` -> **WSL / WSLg note:** the default X11 backend segfaults inside Mesa's GLX driver -> (`dri2GalliumConfigQueryb`) on WSLg. Build the **Wayland** backend instead: +> **WSL / WSLg note:** the desktop build uses raylib's **SDL2** backend +> (`PLATFORM=PLATFORM_DESKTOP_SDL`), not GLFW. WSLg's X11/GLX path segfaults +> inside Mesa (`dri2GalliumConfigQueryb`), and GLFW 3.4's Wayland drag-and-drop +> crashes (glfw/glfw#2835). SDL is robust on both WSLg and real Wayland (labwc), +> so one backend covers both. It requires SDL2 dev installed system-wide: > ```sh -> make -C vendor/raylib/src PLATFORM=PLATFORM_DESKTOP RAYLIB_LIBTYPE=STATIC \ -> GLFW_LINUX_ENABLE_WAYLAND=TRUE GLFW_LINUX_ENABLE_X11=FALSE -j4 +> sudo pacman -S sdl2 # Arch (or sdl2-compat) +> # then just: zig build # build.zig handles PLATFORM=PLATFORM_DESKTOP_SDL + linking SDL2 > ``` -> `build.zig` currently links the Wayland libs to match. For a normal X11 desktop, -> swap the Wayland `linkSystemLibrary` calls in `build.zig` back to `X11`. +> The web build is separate and still uses Emscripten + GLFW (`build_web.sh`). ### 1b. Build RmlUi (static lib) diff --git a/build.zig b/build.zig index 333362a..1c3e584 100644 --- a/build.zig +++ b/build.zig @@ -10,9 +10,10 @@ const std = @import("std"); // The vendored libs (1,2) are guarded so they only build when missing; mruby (3) // runs every time (rake is itself incremental). See BUILDING.md. // -// Wayland backend is used because WSLg's X11/GLX path segfaults in Mesa; for a -// normal X11 desktop, swap the wayland-* libs for "X11" and rebuild raylib -// without the GLFW_LINUX_ENABLE_WAYLAND flags. +// Desktop window/GL backend is raylib's SDL2 backend (PLATFORM_DESKTOP_SDL), +// not GLFW: GLFW 3.4's Wayland drag-and-drop is broken (crashes on drag) and +// its X11 path segfaults on WSLg. SDL is robust on both real Wayland (labwc) +// and WSLg. Requires SDL2 dev installed system-wide. Web uses Emscripten/GLFW. pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -23,25 +24,21 @@ pub fn build(b: *std.Build) void { // across platforms, so `make clean` first to avoid picking up wasm objects // from a prior web build. Guarded on the desktop lib so it only builds once. // - // Before building, apply the GLFW Wayland drag-and-drop crash fix (see - // patches/glfw-wayland-dnd-crash.patch). GLFW 3.4 (vendored in raylib 6.0) - // leaves the wl_data_offer source_actions/action listener handlers NULL, - // so libwayland wl_abort()s when a compositor sends them during a drag -> - // the app crashes the moment a file is dragged over the window. Idempotent: - // if the marker (dataOfferHandleAction) is already in the file, skip; else - // apply the patch and delete any stale lib so `make` actually rebuilds. + // We use raylib's SDL backend (PLATFORM_DESKTOP_SDL) instead of the default + // GLFW backend. GLFW 3.4 (vendored in raylib 6.0) has broken drag-and-drop on + // Wayland (the wl_data_offer source_actions/action listeners are NULL -> + // libwayland wl_abort()s when a file is dragged over the window), and its X11 + // backend segfaults on WSLg (Mesa GLX). SDL's own window/egl/drag-drop code is + // mature on both real Wayland (labwc) and WSLg, so one backend covers both + // targets with no vendor patches. Requires SDL2 dev installed system-wide + // (`pacman -S sdl2` / `sdl2-compat`). Web still uses Emscripten/GLFW (separate). const raylib_lib = b.addSystemCommand(&.{ "sh", "-c", "r=\"$PWD\"; mkdir -p \"$r/build/desktop\"; " ++ - "wlw=vendor/raylib/src/external/glfw/src/wl_window.c; " ++ - "if ! grep -q 'dataOfferHandleAction' \"$wlw\" 2>/dev/null; then " ++ - "(cd vendor/raylib && patch -p1 --forward < \"$r/patches/glfw-wayland-dnd-crash.patch\" >/dev/null); " ++ - "rm -f \"$r/build/desktop/libraylib.a\"; " ++ - "fi; " ++ "[ -f \"$r/build/desktop/libraylib.a\" ] || (" ++ "cd vendor/raylib/src && make clean >/dev/null 2>&1; " ++ - "make PLATFORM=PLATFORM_DESKTOP RAYLIB_LIBTYPE=STATIC " ++ - "GLFW_LINUX_ENABLE_WAYLAND=TRUE GLFW_LINUX_ENABLE_X11=FALSE -j4 " ++ + "make PLATFORM=PLATFORM_DESKTOP_SDL RAYLIB_LIBTYPE=STATIC " ++ + "SDL_INCLUDE_PATH=/usr/include/SDL2 SDL_LIBRARY_PATH=/usr/lib -j4 " ++ "RAYLIB_RELEASE_PATH=\"$r/build/desktop\")", }); @@ -145,18 +142,13 @@ pub fn build(b: *std.Build) void { // (MRB_USE_CXX_EXCEPTION) because a C++ mrbgem (rmlui) is present. exe.root_module.addObjectFile(.{ .cwd_relative = "/usr/lib/libgcc_s.so.1" }); - // system libraries needed by raylib (desktop GLFW/Wayland) and mruby + // system libraries needed by raylib (desktop SDL2 backend) and mruby + exe.root_module.linkSystemLibrary("SDL2", .{}); exe.root_module.linkSystemLibrary("GL", .{}); - exe.root_module.linkSystemLibrary("EGL", .{}); exe.root_module.linkSystemLibrary("m", .{}); exe.root_module.linkSystemLibrary("pthread", .{}); exe.root_module.linkSystemLibrary("dl", .{}); exe.root_module.linkSystemLibrary("rt", .{}); - // Wayland backend (WSLg-friendly; avoids the broken Mesa GLX path) - exe.root_module.linkSystemLibrary("wayland-client", .{}); - exe.root_module.linkSystemLibrary("wayland-cursor", .{}); - exe.root_module.linkSystemLibrary("wayland-egl", .{}); - exe.root_module.linkSystemLibrary("xkbcommon", .{}); // link only after the dependency libs are built exe.step.dependOn(&raylib_lib.step); diff --git a/patches/glfw-wayland-dnd-crash.patch b/patches/glfw-wayland-dnd-crash.patch deleted file mode 100644 index 8a9bf1b..0000000 --- a/patches/glfw-wayland-dnd-crash.patch +++ /dev/null @@ -1,80 +0,0 @@ -From: study-player rewrite (backport of upstream GLFW master fix) -Subject: [Wayland] Fix drag-and-drop crash on data offer source_actions/action - -raylib 6.0 vendors GLFW 3.4 (release), which has an unfixed Wayland -drag-and-drop bug that crashes the host app the moment a file is dragged -over the window (glfw/glfw#2835, glfw/glfw#2562). Fixed in GLFW master but -NOT in the 3.4 release: - -`struct wl_data_offer_listener dataOfferListener` (wl_window.c) only -initialises the `offer` (opcode 0) handler. `source_actions` (opcode 1) -and `action` (opcode 2) are NULL (wl_data_offer is version 3; those events -were added in v3). Modern compositors (labwc, GNOME, KDE, ...) emit -source_actions/action during a drag, and libwayland-client then calls -`wl_abort("listener function for opcode N of wl_data_offer is NULL")` -> -the app dies on drag-enter, before any drop can happen. Provide no-op -handlers so the events are safely consumed. (GLFW only needs the `offer` -mime-types to decide what to accept on drop.) - -Also guard two NULL derefs in the same data-device path (both fixed in -GLFW master, changelog: "A drag entering a non-GLFW surface could cause a -segfault"): `dataDeviceHandleEnter` dereferenced `window` even when the -drag entered a non-GLFW surface, and `dataDeviceHandleDrop` passed a -possibly-NULL `dragFocus` to `_glfwInputDrop` (asserts are off in release). - -The X11/Xdnd backend (used by the original 'dev' app, -D_GLFW_X11) is a -separate code path and is unaffected, which is why drag-and-drop is -reliable there. - -Drop this patch once raylib vendors a GLFW release containing these fixes. - -diff --git a/src/external/glfw/src/wl_window.c b/src/external/glfw/src/wl_window.c -index 505166e..d68faf8 100644 ---- a/src/external/glfw/src/wl_window.c -+++ b/src/external/glfw/src/wl_window.c -@@ -1935,9 +1935,26 @@ static void dataOfferHandleOffer(void* userData, - } - } - -+// wl_data_offer v3 events. The compositor sends source_actions / action during -+// a drag-and-drop; without handlers libwayland-client wl_abort()s on the NULL -+// listener function pointer (glfw/glfw#2835). No-op handlers are sufficient. -+static void dataOfferHandleSourceActions(void* userData, -+ struct wl_data_offer* offer, -+ uint32_t source_actions) -+{ -+} -+ -+static void dataOfferHandleAction(void* userData, -+ struct wl_data_offer* offer, -+ uint32_t dnd_action) -+{ -+} -+ - static const struct wl_data_offer_listener dataOfferListener = - { -- dataOfferHandleOffer -+ dataOfferHandleOffer, -+ dataOfferHandleSourceActions, -+ dataOfferHandleAction - }; - - static void dataDeviceHandleDataOffer(void* userData, -@@ -1987,7 +2004,7 @@ static void dataDeviceHandleEnter(void* userData, - window = wl_surface_get_user_data(surface); - } - -- if (surface == window->wl.surface && _glfw.wl.offers[i].text_uri_list) -+ if (window && surface == window->wl.surface && _glfw.wl.offers[i].text_uri_list) - { - _glfw.wl.dragOffer = offer; - _glfw.wl.dragFocus = window; -@@ -2042,7 +2059,7 @@ static void dataDeviceHandleDrop(void* userData, - { - int count; - char** paths = _glfwParseUriList(string, &count); -- if (paths) -+ if (paths && _glfw.wl.dragFocus) - _glfwInputDrop(_glfw.wl.dragFocus, count, (const char**) paths); - - for (int i = 0; i < count; i++) diff --git a/tools/SCREENSHOT.md b/tools/SCREENSHOT.md index d3dacad..1d44cc8 100644 --- a/tools/SCREENSHOT.md +++ b/tools/SCREENSHOT.md @@ -137,15 +137,14 @@ The desktop target needs a **driven display** — a real, interactive window. In this WSL2/WSLg dev environment, running the desktop binary from a non-interactive shell **stalls** (verified): -- raylib is built **Wayland-only** (`GLFW_LINUX_ENABLE_WAYLAND=TRUE - GLFW_LINUX_ENABLE_X11=FALSE`; WSLg's X11/GLX path segfaults inside Mesa — see - `.agents/knowledge/environment.md`). With `XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir - WAYLAND_DISPLAY=wayland-0`, `InitWindow` connects to Wayland then **blocks in - `do_sys_poll`** (0% CPU, state `S`) — the compositor doesn't drive a - non-interactive window, so the framebuffer never renders and no PNG is produced. -- The X11 path (`DISPLAY=:0`) initializes but **segfaults at GL/FBO setup** - (the known Mesa `dri2GalliumConfigQueryb` crash); software GL (`LIBGL_ALWAYS_SOFTWARE`) - doesn't help because the X11 backend isn't compiled in. +- raylib's desktop build now uses the **SDL2** backend (`PLATFORM_DESKTOP_SDL`; + see `.agents/knowledge/environment.md`). With `XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir + WAYLAND_DISPLAY=wayland-0`, the window opens but a **non-interactive** WSLg + compositor doesn't drive it, so the framebuffer never renders and no PNG is + produced (the earlier GLFW/Wayland build blocked in `do_sys_poll`; SDL reaches + init but the same non-interactive-display problem applies). +- (Historical: the X11/GLFW path segfaulted in Mesa `dri2GalliumConfigQueryb` on + WSLg; that's why it was dropped. SDL sidesteps GLX.) So `--target desktop` here will hit its 30s timeout and fall back to `ffmpeg` (which also can't grab an unrendered window). **Use `--target web` in this -- cgit v1.2.3