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.
This commit is contained in:
ske087
2026-09-13 21:57:49 +03:00
parent f437aba1fc
commit 3ac7f836c4
68 changed files with 5604 additions and 9326 deletions
+25 -18
View File
@@ -25,12 +25,16 @@ wheels/
venv/
ENV/
env/
windows/venv312/
# Kivy
*.pyc
# Media files (optional - remove if you want to track media)
#
# Coverage matters here: `media/*.mp4` matches files directly in media/, but NOT
# media/edited_media/1/foo.jpg. Playlist content is downloaded per-site and must
# never be committed, so the whole tree is ignored and only the directory
# structure (via .gitkeep) is tracked.
media/*.jpg
media/*.jpeg
media/*.png
@@ -41,9 +45,20 @@ media/*.avi
media/*.mkv
media/*.mov
media/*.webm
media/**/*.jpg
media/**/*.jpeg
media/**/*.png
media/**/*.gif
media/**/*.bmp
media/**/*.mp4
media/**/*.avi
media/**/*.mkv
media/**/*.mov
media/**/*.webm
# Playlists cache (auto-generated)
# Playlist cache (auto-generated from the server per device)
playlists/server_playlist_*.json
playlists/server_playlist.json
# Logs
*.log
@@ -72,9 +87,6 @@ logs/startup_crash.log
logs/console_out.txt
logs/console_err.txt
logs/watchdog_test_*.txt
logs/.webview2_install_attempt
windows/venv_build/
# Player credentials — live auth_code/player_id/server_url. Never commit these:
# a bundled copy made fresh builds boot "already authenticated" against an old
@@ -82,20 +94,15 @@ windows/venv_build/
player_auth.json
src/player_auth.json
working_files/player_auth.json
windows/dist/*/player_auth.json
# Runtime web-engine profiles (browser cache, not source)
# Runtime browser profile (cache, not source). The player launches Chromium with
# a dedicated --user-data-dir so it never touches the operator's own profile.
# The wildcard covers probe/test profiles (e.g. .kiosk-profile-probe), which
# otherwise get picked up as untracked files and are several MB of cache each.
.kiosk-profile/
.kiosk-profile-*/
.webview2-profile/
# NOTE: windows/webview2_sdk/ IS tracked on purpose — build.spec bundles those
# DLLs, so a fresh clone must have them or web links silently fall back to the
# Chrome/Edge subprocess engine. Only ~860 KB (2 files).
# The small WebView2 Runtime bootstrapper (~1.7 MB) IS tracked: build.spec
# bundles it so a machine without the Runtime can install it on first start.
# The ~203 MB offline standalone installer is NOT tracked — fetch it with
# windows/webview2_runtime/download_runtime_installers.ps1 when you need to
# build for machines that have no internet.
windows/webview2_runtime/MicrosoftEdgeWebView2RuntimeInstaller*.exe
!windows/webview2_runtime/MicrosoftEdgeWebview2Setup.exe
# Monitoring output (generated per run, not source)
logs/monitor*.csv
logs/monitor*.log