#!/usr/bin/env bash # fix_kivy_sdl2.sh — make Kivy use the system SDL2 (Wayland-capable). # # THE PROBLEM # ----------- # Kivy's PyPI wheel bundles a private SDL2 in ``Kivy.libs/`` that is compiled # WITHOUT the Wayland video driver. Verified on this Pi: # # Kivy.libs/libSDL2-2-87637523.0.so.0.3000.7 -> x11, KMSDRM, offscreen, dummy, evdev # /usr/lib/aarch64-linux-gnu/libSDL2-2.0.so.0 -> x11, wayland, KMSDRM, offscreen, dummy, evdev # # On Raspberry Pi OS "Trixie" the desktop is Wayland/labwc and there is no # X server running, so the bundled build cannot create a window at all: # # [CRITICAL] Unable to find any valuable Window provider. # sdl2 - RuntimeError: b'wayland,x11,dummy not available' # # Substituting the system library under Kivy's bundled filename resolves it # (verified: "WINDOW OK size=(800, 600)"). # # WHY A SYMLINK AND NOT A COPY # ---------------------------- # The symlink survives ``pip install --upgrade kivy`` overwriting the file, is # reversible, and keeps the distro's security updates in effect. A copy would # silently become stale. # # Usage: # bash linux/fix_kivy_sdl2.sh # apply (idempotent) # bash linux/fix_kivy_sdl2.sh --check # report only, change nothing # bash linux/fix_kivy_sdl2.sh --revert # remove the symlink set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" VENV_DIR="${KIWY_VENV:-$ROOT_DIR/.venv}" BACKUP_DIR="$VENV_DIR/.kivy-sdl2-backup" SYSTEM_SDL2="/usr/lib/aarch64-linux-gnu/libSDL2-2.0.so.0" MODE="apply" case "${1:-}" in --check) MODE="check" ;; --revert) MODE="revert" ;; "") MODE="apply" ;; *) echo "Unknown option: $1" >&2; exit 2 ;; esac if [ ! -d "$VENV_DIR" ]; then echo "ERROR: virtualenv not found at $VENV_DIR" >&2 echo " Set KIWY_VENV=/path/to/venv if it lives elsewhere." >&2 exit 1 fi KIVY_LIBS="$(find "$VENV_DIR" -maxdepth 5 -type d -name 'Kivy.libs' 2>/dev/null | head -1)" if [ -z "$KIVY_LIBS" ]; then echo "No Kivy.libs directory found under $VENV_DIR — nothing to do." echo "(Kivy is probably installed from apt, which uses the system SDL2 already.)" exit 0 fi # Kivy names the bundled library with a hash, so locate it by pattern rather # than hard-coding the version. Symlinks must be matched too: once the fix is # applied the original file IS a symlink, and skipping it would make this # script wrongly conclude that nothing is bundled. BUNDLED="$(find "$KIVY_LIBS" -maxdepth 1 -name 'libSDL2-2-*.so*' 2>/dev/null | head -1)" # Fallback: ask the compiled extension which SDL2 it actually loads. This is # authoritative and also covers a future Kivy layout change. if [ -z "$BUNDLED" ]; then EXT="$(find "$VENV_DIR" -maxdepth 6 -name '_window_sdl2*.so' 2>/dev/null | head -1)" if [ -n "$EXT" ]; then NEEDED="$(ldd "$EXT" 2>/dev/null | awk '/libSDL2-2-/ {print $3; exit}')" [ -n "$NEEDED" ] && BUNDLED="$NEEDED" fi fi echo "Kivy.libs : $KIVY_LIBS" echo "bundled : ${BUNDLED:-}" echo "system : $SYSTEM_SDL2" if [ ! -e "$SYSTEM_SDL2" ]; then echo "ERROR: system SDL2 not found at $SYSTEM_SDL2" >&2 echo " Install it with: sudo apt install libsdl2-2.0-0" >&2 exit 1 fi # Report the video drivers compiled into a library, so the fix is verifiable. report_drivers() { local lib="$1" local py="${VENV_DIR}/bin/python" [ -x "$py" ] || py="$(command -v python3)" "$py" - "$lib" <<'PY' 2>/dev/null || echo " (could not inspect drivers)" import ctypes, sys try: s = ctypes.CDLL(sys.argv[1]) s.SDL_GetNumVideoDrivers.restype = ctypes.c_int s.SDL_GetVideoDriver.restype = ctypes.c_char_p s.SDL_GetVideoDriver.argtypes = [ctypes.c_int] n = s.SDL_GetNumVideoDrivers() print(' drivers:', ', '.join(s.SDL_GetVideoDriver(i).decode() for i in range(n))) except Exception as exc: print(' (inspection failed:', exc, ')') PY } case "$MODE" in revert) if [ -L "$BUNDLED" ]; then rm -f "$BUNDLED" if [ -f "$BACKUP_DIR/$(basename "$BUNDLED")" ]; then cp -a "$BACKUP_DIR/$(basename "$BUNDLED")" "$BUNDLED" echo "Reverted to the original bundled SDL2." else echo "Removed the symlink. Reinstall Kivy to restore the bundled library:" echo " $VENV_DIR/bin/pip install --force-reinstall kivy" fi else echo "Nothing to revert (no symlink in place)." fi exit 0 ;; check) echo if [ -L "$BUNDLED" ]; then echo "STATUS: fixed (bundled name points at the system SDL2)" report_drivers "$BUNDLED" else echo "STATUS: NOT fixed — Kivy is using its own SDL2" report_drivers "$BUNDLED" fi echo echo "system SDL2:" report_drivers "$SYSTEM_SDL2" exit 0 ;; apply) if [ -z "$BUNDLED" ]; then echo "No bundled libSDL2 found — Kivy already uses the system SDL2." exit 0 fi if [ -L "$BUNDLED" ]; then echo "Already fixed (symlink in place). Verifying..." report_drivers "$BUNDLED" exit 0 fi # Keep a copy of the original so --revert works without a reinstall. mkdir -p "$BACKUP_DIR" if [ ! -f "$BACKUP_DIR/$(basename "$BUNDLED")" ]; then cp -a "$BUNDLED" "$BACKUP_DIR/" echo "Backed up original -> $BACKUP_DIR/$(basename "$BUNDLED")" fi ln -sf "$SYSTEM_SDL2" "$BUNDLED" echo "Symlinked $BUNDLED -> $SYSTEM_SDL2" echo echo "Resulting drivers:" report_drivers "$BUNDLED" echo echo "Done. Kivy can now create a Wayland window." echo "NOTE: re-run this script after any 'pip install --upgrade kivy'." ;; esac