Files
Kiwy-Signage/linux/fix_kivy_sdl2.sh
T
ske087 3ac7f836c4 Port the player to Raspberry Pi OS Trixie 64-bit (Linux-only branch)
Replaces the Windows port with a Raspberry Pi / Linux implementation on
Raspberry Pi OS "Trixie" (Debian 13, aarch64, Wayland/labwc). The Windows
code is removed here but preserved on the Windows-Player branch.

Entry point
-----------
linux/run_linux.py replaces windows/run_win.py. src/main.py stays
platform-neutral; all Pi-specific behaviour is injected from linux/.

Five bugs that prevented the port (all measured on real hardware)
----------------------------------------------------------------
1. Kivy's PyPI wheel bundles an SDL2 built WITHOUT the wayland driver, so
   no window could be created (Trixie has no X server). linux/fix_kivy_sdl2.sh
   symlinks the system SDL2 over the bundled filename.
2. SDL2 requires WAYLAND_DISPLAY to be *set* - the socket alone is not enough,
   unlike wlopm. This broke every systemd/cron/autostart launch.
   linux_display.ensure_session_environment() detects and exports it.
3. Kivy's Clock resolves callbacks via func.__name__; a patch assigned under a
   different name crashed the player ~20s after a successful start.
4. The inherited signal_screen_activity() shelled out to tvservice, xdotool and
   ydotool - none exist on Trixie - and mis-escaped 'wlopm --on \*', so the
   display blanked after 10 minutes.
5. The launchers ran src/main.py directly, bypassing every platform patch and
   resolving the data directory one level too high.

Web links
---------
- --ozone-platform-hint=auto does NOT fall back to Wayland on Chromium 152; it
  aborts. The platform is now chosen explicitly.
- The keyring password prompt is suppressed via the ENVIRONMENT, not the flags:
  launch_env() strips DBUS_SESSION_BUS_ADDRESS for the child so Chromium cannot
  reach gnome-keyring-daemon.
- Teardown kills the whole process group (needs start_new_session=True);
  previously it silently fell back to terminate() and orphaned children.

Video normalisation
-------------------
A 4K video cannot play on a Pi 4: ffpyplayer decodes in software, measured at
0.90x realtime (1080p is 3.03x). Oversized media is downscaled to 1920x1080 at
sync time using the hardware h264_v4l2m2m encoder (~31s for an 18s clip),
triggered by resolution only so already-playable files are untouched.

src/media_state.py owns the shared on-disk contract: a .kiwy-converting marker
makes the player skip the item while it is being rebuilt, then the converted
file is played instead. If nothing is playable at all (a single-item playlist
whose only video is converting), the player loops the intro video rather than
leaving a blank screen.

Also fixed
----------
- network_monitor: replaced netsh/ifconfig/dhclient with nmcli (Trixie uses
  NetworkManager; ifconfig and dhclient are not even installed).
- Removed the Windows-only focus keeper/guardian from main.py.
- main.py: duplicate SDL_AUDIODRIVER setdefault (a silent no-op); Settings
  "Test connection" now uses tempfile.gettempdir().
- config/app_config.json: credentials blanked so a fresh clone runs the
  first-run setup flow.

Verification
------------
linux/test_media_state.py 18/18, test_linux_patches.py 21/21,
test_linux_browser_flags.py 27/27. Verified live against a real DigiServer:
image -> weblink -> image -> video with correct durations, zero leaked Chromium
processes, and no throttling over a 10 minute monitored run.
2026-09-13 21:57:49 +03:00

167 lines
5.8 KiB
Bash
Executable File

#!/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:-<none>}"
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