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:
@@ -5,11 +5,10 @@ This document describes how the **Kiwy-Signage player**
|
||||
playlist item type (display a live web page / URL instead of an uploaded media
|
||||
file).
|
||||
|
||||
> **Status: implemented.** The player supports `weblink` items on both
|
||||
> Raspberry Pi (`chromium` subprocess) and Windows (embedded **WebView2**, with
|
||||
> the CEF and Chrome/Edge subprocess engines as fallbacks). Sections 1–4
|
||||
> describe the original design plan; section 6 documents the shipped
|
||||
> architecture and the interaction model.
|
||||
> **Status: implemented.** The player supports `weblink` items on
|
||||
> Raspberry Pi / Linux via a Chromium kiosk subprocess.
|
||||
> Sections 1–4 describe the original design plan; section 6 documents the
|
||||
> shipped architecture and the interaction model.
|
||||
|
||||
---
|
||||
|
||||
@@ -207,7 +206,8 @@ Recommended options, in order of robustness:
|
||||
- On Wayland/X11 the player already sets `SDL_VIDEODRIVER`; verify Chromium
|
||||
launches on the same display/session.
|
||||
|
||||
2. **Embedded web view widget** (`kivy_garden.webview`, WebKit/GTK, or WebView2).
|
||||
2. **Embedded web view widget** (`kivy_garden.webview`, WebKit/GTK, or a
|
||||
platform WebView) — renders inside the Kivy window.
|
||||
Cleaner UX (stays inside the Kivy widget tree) but fragile and poorly
|
||||
supported on Pi/Wayland — only pursue if option 1 is unacceptable.
|
||||
|
||||
@@ -258,55 +258,59 @@ platform:
|
||||
|--------------------------|----------------|
|
||||
| `WeblinkSession` | Owns one weblink item: validate → launch → verify → watch → teardown. Generation-tokened so stale callbacks are ignored, and `atexit`-safe. |
|
||||
| `WeblinkAdapter` | The only platform-specific part: launch / wait for the window / is it alive / tear it down / pre-warm. `extra_launch_args()` lets a subclass add browser flags without copying `launch()`. |
|
||||
| `ChromiumSubprocessAdapter` | Default engine (Raspberry Pi `chromium`; on Windows the Chrome/Edge fallback). |
|
||||
| `ChromiumSubprocessAdapter` | Default engine (Raspberry Pi `chromium`). |
|
||||
| `InteractionWatcher` | Decides when the item is finished (see the interaction model below). |
|
||||
| `WebInputSources` | Reads `/dev/input/event*` (Linux) and does a pointer-position tap (Windows, needed for embedded engines). |
|
||||
| `webview2_browser.py` | **Windows, preferred**: embeds WebView2 as a child HWND of the Kivy window. |
|
||||
| `webview2_runtime.py` | **Windows**: detects the WebView2 Runtime and installs it silently when missing. |
|
||||
| `WebInputSources` | Reads `/dev/input/event*` to detect viewer interaction. |
|
||||
| `linux_browser.py` | **Raspberry Pi / Linux**: `LinuxChromiumAdapter` — Chromium kiosk on Wayland (labwc). |
|
||||
|
||||
Platform wrappers inject their engines through
|
||||
Platform entry points inject their engine through
|
||||
`SignagePlayer.weblink_adapter_factory`:
|
||||
|
||||
* **Raspberry Pi / Linux** — built-in Chromium subprocess adapter.
|
||||
* **Windows** (`windows/run_win.py`) — engines are tried in this order:
|
||||
* **Raspberry Pi / Linux** (`linux/run_linux.py`) — injects
|
||||
`LinuxChromiumAdapter`, which adds the flags Trixie needs:
|
||||
|
||||
| Order | Engine | Renders | Notes |
|
||||
|-------|--------|---------|-------|
|
||||
| 1 | **WebView2** (`webview2_browser.py`) | child window **inside** Kivy | Preferred. No subprocess, so no background/z-order/hand-off/leak problems. |
|
||||
| 2 | CEF (`cef_browser.py`) | child window inside Kivy | Dormant: `cefpython3` has no wheels past Python 3.9. |
|
||||
| 3 | Chrome/Edge subprocess | separate window | Last resort only; retains the old drawbacks. |
|
||||
| Flag | Why |
|
||||
|------|-----|
|
||||
| `--kiosk` | What actually makes labwc give the window exclusive fullscreen. Windows needed `--start-maximized` instead; that is not sufficient here. |
|
||||
| `--user-data-dir=<.kiosk-profile>` | **Mandatory.** Without it Chromium hands the URL to an already-running instance, the process we launched exits in ~2 s and the item is skipped as a failed launch. Also guarantees we never touch the operator's own browser profile. |
|
||||
| `--ozone-platform-hint=auto` | Lets Chromium pick Wayland when available and fall back to X11/XWayland. |
|
||||
| `--autoplay-policy=no-user-gesture-required` | Signage pages play media without a click. |
|
||||
|
||||
Teardown kills the **whole process group** (`os.killpg`). `proc.terminate()`
|
||||
only reaps the parent, leaving Chromium's GPU/zygote/renderer children behind;
|
||||
across a 24/7 playlist those accumulate until the Pi runs out of memory.
|
||||
|
||||
`WeblinkAdapter.extra_launch_args()` is the hook subclasses use to add flags
|
||||
without duplicating `launch()` — the Chrome adapter uses it for
|
||||
without duplicating `launch()` — the Linux adapter uses it for
|
||||
`--user-data-dir` + `--kiosk`.
|
||||
|
||||
> **Do not give the factory a class-level `None` default combined with an
|
||||
> unconditional instance assignment.** `SignagePlayer.__init__` originally set
|
||||
> `self.weblink_adapter_factory = None`, which shadowed the class attribute the
|
||||
> Windows wrapper installs — so the platform adapters were silently ignored and
|
||||
> every weblink fell back to the generic adapter and failed. It now only sets
|
||||
> platform entry point installs — so the platform adapters were silently ignored
|
||||
> and every weblink fell back to the generic adapter and failed. It now only sets
|
||||
> the instance attribute when the class attribute is absent.
|
||||
|
||||
### 6.2 Windows: WebView2 Runtime
|
||||
### 6.2 Linux: Chromium kiosk on Wayland
|
||||
|
||||
WebView2 is two separate things, and they ship differently:
|
||||
Web links on Raspberry Pi OS Trixie use a single dedicated `chromium` process
|
||||
launched in kiosk mode with its own profile directory:
|
||||
|
||||
* the **SDK** (`Microsoft.Web.WebView2.Core.dll`, `WebView2Loader.dll`) — the
|
||||
API surface, bundled in the exe from `windows\webview2_sdk\` (~860 KB);
|
||||
* the **Runtime** (`msedgewebview2.exe`) — the actual Chromium engine, shipped
|
||||
by Microsoft and **verified/installed at start-up** by
|
||||
`windows\webview2_runtime.py`.
|
||||
* `--kiosk` gives an exclusive-fullscreen window under the **labwc** compositor;
|
||||
* `--user-data-dir=<data>/.kiosk-profile` guarantees a *fresh, trackable* browser
|
||||
instead of a hand-off to a running instance;
|
||||
* `--ozone-platform-hint=auto` selects Wayland natively and falls back to X11.
|
||||
|
||||
If the Runtime is absent the player runs an installer silently
|
||||
(`/silent /install`) and **unelevated**, which produces a *per-user* install and
|
||||
therefore never raises a UAC prompt on the signage display. A ~1.7 MB online
|
||||
bootstrapper is bundled by default; a ~203 MB offline standalone installer can
|
||||
be bundled instead (see `windows\webview2_runtime\download_runtime_installers.ps1`)
|
||||
for machines with no internet.
|
||||
Because a separate process cannot be introspected portably, visibility is
|
||||
verified by combining "the process survived the health grace period" with a
|
||||
best-effort check that the PID actually owns a Wayland/X11 socket. A launch that
|
||||
dies sooner than `min_healthy_alive` is treated as a **failed launch** (hand-off,
|
||||
missing binary, instant crash) rather than a finished item, so the playlist
|
||||
never skips a weblink silently.
|
||||
|
||||
Install success is decided by **re-reading the installed version**, not by the
|
||||
installer exit code — Edge Update returns a non-zero HRESULT (e.g.
|
||||
`-2147219416`) when the Runtime is already current, which is not a failure.
|
||||
Stale `SingletonLock`/`SingletonSocket` files left by a crashed Chromium are
|
||||
cleared before each launch: our profile is private to the player, so removing
|
||||
the lock is always safe and prevents Chromium refusing to start.
|
||||
|
||||
### 6.1 Interaction model — web links are not passive media
|
||||
|
||||
|
||||
Reference in New Issue
Block a user