Improve Raspberry Pi startup and Chromium transitions

This commit is contained in:
SKE087
2026-07-20 16:27:22 +03:00
parent fb9f46a94f
commit 39565c5761
3 changed files with 163 additions and 46 deletions
+12
View File
@@ -42,6 +42,7 @@ REQUIREMENTS_FILE="$SCRIPT_DIR/requirements.txt"
SUDO_USERNAME=""
SUDO_PASSWORD=""
OFFLINE_MODE=false
PI_BOOT_PATCH_SCRIPT="$SCRIPT_DIR/scripts/pi_boot_config_patch_once.sh"
# ============================================================
# Helper: Run sudo with optional password
@@ -624,6 +625,17 @@ echo ""
echo "Logs: $SCRIPT_DIR/logs/"
echo ""
if [ -f "$PI_BOOT_PATCH_SCRIPT" ]; then
echo "Applying one-time Raspberry Pi boot config patch..."
run_sudo chmod +x "$PI_BOOT_PATCH_SCRIPT" 2>/dev/null || true
if run_sudo bash "$PI_BOOT_PATCH_SCRIPT"; then
echo "One-time boot patch applied (or already present)"
else
echo "Boot patch skipped or failed; continuing installation"
fi
echo ""
fi
if [ -z "$SUDO_PASSWORD" ]; then
echo "=========================================="
echo "System Reboot Recommended"
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
MARKER_FILE="/etc/kiwy-signage/pi_boot_config_patched.marker"
is_pi() {
if [[ -f /proc/device-tree/model ]] && grep -qi "raspberry pi" /proc/device-tree/model; then
return 0
fi
if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null; then
return 0
fi
return 1
}
find_boot_config() {
if [[ -f /boot/firmware/config.txt ]]; then
echo "/boot/firmware/config.txt"
elif [[ -f /boot/config.txt ]]; then
echo "/boot/config.txt"
else
echo ""
fi
}
upsert_key() {
local key="$1"
local value="$2"
local file="$3"
if grep -Eq "^[#[:space:]]*${key}=" "$file"; then
sed -i -E "s|^[#[:space:]]*${key}=.*|${key}=${value}|" "$file"
else
echo "${key}=${value}" >> "$file"
fi
}
if [[ "${EUID}" -ne 0 ]]; then
echo "[pi-boot-patch] Please run as root."
exit 1
fi
if ! is_pi; then
echo "[pi-boot-patch] Not a Raspberry Pi. Skipping."
exit 0
fi
if [[ -f "$MARKER_FILE" ]]; then
echo "[pi-boot-patch] Patch already applied. Skipping."
exit 0
fi
BOOT_CONFIG="$(find_boot_config)"
if [[ -z "$BOOT_CONFIG" ]]; then
echo "[pi-boot-patch] Boot config not found. Skipping."
exit 0
fi
echo "[pi-boot-patch] Applying patch to ${BOOT_CONFIG}"
# Graphics and memory settings for smoother Chromium playback on Pi.
upsert_key "dtoverlay" "vc4-kms-v3d" "$BOOT_CONFIG"
upsert_key "gpu_mem" "256" "$BOOT_CONFIG"
upsert_key "disable_overscan" "1" "$BOOT_CONFIG"
# Keep HDMI and display active for kiosk use.
upsert_key "hdmi_blanking" "0" "$BOOT_CONFIG"
upsert_key "hdmi_ignore_cec_init" "1" "$BOOT_CONFIG"
upsert_key "hdmi_force_hotplug" "1" "$BOOT_CONFIG"
mkdir -p "$(dirname "$MARKER_FILE")"
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$MARKER_FILE"
echo "[pi-boot-patch] Patch complete. Marker written to ${MARKER_FILE}"
+56 -27
View File
@@ -1307,29 +1307,20 @@ class SignagePlayer(Widget):
Logger.info(f"SignagePlayer: Playing item {self.current_index + 1}/{len(self.playlist)}: {file_name} ({duration}s)")
# ── Weblink → media transition (Wayland-safe) ──────────────────
# On Wayland (Labwc) Window.raise_window() is a no-op, so we must
# ensure Chromium's fullscreen window is fully gone BEFORE we try
# to render Kivy content. We:
# 1. Stop watchdog + preload immediately
# 2. Terminate Chromium (non-blocking)
# 3. Hide the Kivy content_area so nothing stale is visible
# 4. Wait 200 ms for the compositor to remove Chromium's window
# 5. Show content_area + re-call play_current_media to render
# ── Weblink → media transition (desktop-flash safe) ──────────────
# For web→media transitions, render the next Kivy widget first,
# then close Chromium on the next frame. This prevents a brief
# desktop exposure while the compositor removes Chromium.
if not _after_weblink:
proc = getattr(self, '_weblink_proc', None)
if proc is not None:
next_is_weblink = media_item.get('type') == 'weblink'
if next_is_weblink:
# Weblink -> weblink: close current Chromium first.
self._stop_inactivity_watchdog()
self._kill_weblink_preload()
self._weblink_proc = None
# Hide content while Chromium is still closing
try:
self.ids.content_area.opacity = 0
except Exception:
pass
# Terminate Chromium
if proc.poll() is None:
try:
proc.terminate()
@@ -1337,13 +1328,28 @@ class SignagePlayer(Widget):
Logger.warning(f"SignagePlayer: weblink terminate: {exc}")
def _resume(dt):
# Force-kill if still alive after the delay
if proc.poll() is None:
try:
proc.kill()
except Exception:
pass
# Restore content area and show Kivy window
try:
Window.show()
Window.raise_window()
except Exception:
pass
self.play_current_media(
force_reload=force_reload, _after_weblink=True
)
Clock.schedule_once(_resume, 0.2)
return
# Weblink -> non-weblink: keep Chromium visible while we
# prepare next Kivy frame, then close Chromium deferred.
self._stop_inactivity_watchdog()
self._kill_weblink_preload()
self._weblink_proc = proc
try:
self.ids.content_area.opacity = 1
except Exception:
@@ -1353,14 +1359,7 @@ class SignagePlayer(Widget):
Window.raise_window()
except Exception:
pass
# Now render the actual media
self.play_current_media(
force_reload=force_reload, _after_weblink=True
)
# 200 ms gives Labwc time to remove the fullscreen surface
Clock.schedule_once(_resume, 0.2)
return
Logger.debug("SignagePlayer: Deferred Chromium close after next frame render")
# ────────────────────────────────────────────────────────────────
# Handle web links before any file/path handling (no local file exists)
@@ -1441,6 +1440,11 @@ class SignagePlayer(Widget):
self.consecutive_errors = 0
Logger.debug(f"SignagePlayer: Media started successfully")
# If we arrived here from a weblink item, close Chromium after the
# next Kivy frame so the new widget is already visible underneath.
if media_item.get('type') != 'weblink' and getattr(self, '_weblink_proc', None) is not None:
self._kill_weblink_after_frame()
except Exception as e:
Logger.error(f"SignagePlayer: Error playing media: {e}")
self.consecutive_errors += 1
@@ -1602,9 +1606,12 @@ class SignagePlayer(Widget):
self._skip_to_next_media()
return False
target_width, target_height = self._get_browser_target_size()
try:
Logger.info(f"SignagePlayer: Opening weblink in kiosk browser: {url}")
Logger.info(f"SignagePlayer: Inactivity timeout set to {duration}s (touch resets the countdown)")
Logger.info(f"SignagePlayer: Chromium target launch size: {target_width}x{target_height}")
# Kill the hidden pre-warm instance first; its work (binary in RAM,
# page in disk cache) makes the kiosk relaunch below near-instant.
self._kill_weblink_preload()
@@ -1616,6 +1623,7 @@ class SignagePlayer(Widget):
# --start-fullscreen is a normal maximised window that the
# compositor can un-stack without issues.
'--start-fullscreen',
'--start-maximized',
'--app=' + url,
'--noerrdialogs',
'--disable-infobars',
@@ -1631,6 +1639,8 @@ class SignagePlayer(Widget):
'--disable-background-networking',
'--no-default-browser-check',
'--window-position=0,0',
f'--window-size={target_width},{target_height}',
'--force-device-scale-factor=1',
])
# Unschedule any previous fixed timer — the watchdog thread takes
@@ -1653,6 +1663,23 @@ class SignagePlayer(Widget):
self._skip_to_next_media()
return False
def _get_browser_target_size(self):
"""Return a stable browser launch size to avoid 800x600 first-paint flash."""
default_width, default_height = 1920, 1080
try:
width, height = Window.size
width = int(width)
height = int(height)
except Exception:
return default_width, default_height
# Chromium sometimes first-paints at 800x600 before fullscreen;
# force at least Full HD when reported dimensions are too small.
if width < 1280 or height < 720:
return default_width, default_height
return width, height
def _start_inactivity_watchdog(self, duration):
"""Start a background thread that monitors /dev/input/* for touch/key
activity. The thread resets the idle counter on every event; when the
@@ -1893,6 +1920,8 @@ class SignagePlayer(Widget):
if not browser:
return
target_width, target_height = self._get_browser_target_size()
# Kill any stale preload first.
self._kill_weblink_preload()
@@ -1903,7 +1932,7 @@ class SignagePlayer(Widget):
'--app=' + url,
# Place window completely outside the visible display area.
'--window-position=-9999,-9999',
'--window-size=1920,1080', # pre-render at full resolution
f'--window-size={target_width},{target_height}', # pre-render at target resolution
'--noerrdialogs',
'--disable-infobars',
'--incognito',