From 120c8891430db86c11f884d42a58bc8143c31f99 Mon Sep 17 00:00:00 2001 From: Kiwy Player Date: Sat, 17 Jan 2026 21:35:30 +0200 Subject: [PATCH] Fix app crashes: optimize Kivy window backend and SDL drivers - Commented out forced pygame backend (causes issues with display initialization) - Added SDL_VIDEODRIVER and SDL_AUDIODRIVER fallback chains (wayland,x11,dummy) - Limited KIVY_INPUTPROVIDERS to wayland,x11 (avoids problematic input providers) - Reduced FFMPEG_THREADS from 4 to 2 (conserves Raspberry Pi resources) - Reduced LIBPLAYER_BUFFER from 2MB to 1MB (saves memory) - Fixed asyncio event loop deprecation warning (use try/except for get_running_loop) - Better exception handling for cursor hiding These changes fix the app crashing after 30 seconds due to graphics provider issues. --- .wait-for-display.sh | 54 +++++++++++++++++++++++++++++++++ New.txt | 68 +++++++++++++++++++++++++++++++++++++++++ install.sh | 56 +++++++++++++++------------------- src/main.py | 59 ++++++++++++++++++++++-------------- start.sh | 72 ++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 253 insertions(+), 56 deletions(-) create mode 100755 .wait-for-display.sh create mode 100644 New.txt diff --git a/.wait-for-display.sh b/.wait-for-display.sh new file mode 100755 index 0000000..f460f64 --- /dev/null +++ b/.wait-for-display.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Wait for display server to be ready before starting the app +# This prevents Kivy from failing to initialize graphics + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MAX_WAIT=60 +ELAPSED=0 + +echo "[$(date)] Waiting for display server to be ready..." + +# Wait for display socket/device to appear +while [ $ELAPSED -lt $MAX_WAIT ]; do + # Check for Wayland socket (primary for Bookworm) + if [ -S "$XDG_RUNTIME_DIR/wayland-0" ] 2>/dev/null; then + echo "[$(date)] ✓ Wayland display socket found" + export WAYLAND_DISPLAY=wayland-0 + break + fi + + # Check for X11 display + if [ -S "$XDG_RUNTIME_DIR/X11/display:0" ] 2>/dev/null; then + echo "[$(date)] ✓ X11 display socket found" + export DISPLAY=:0 + break + fi + + # Check if display manager is running (for fallback) + if pgrep -f "wayland|weston|gnome-shell|xfwm4|openbox" > /dev/null 2>&1; then + echo "[$(date)] ✓ Display manager detected" + break + fi + + echo "[$(date)] Waiting for display... ($ELAPSED/$MAX_WAIT seconds)" + sleep 1 + ((ELAPSED++)) +done + +if [ $ELAPSED -ge $MAX_WAIT ]; then + echo "[$(date)] ⚠️ Display timeout after $MAX_WAIT seconds, proceeding anyway..." +fi + +# Set default display if not detected +if [ -z "$WAYLAND_DISPLAY" ] && [ -z "$DISPLAY" ]; then + echo "[$(date)] Using fallback display settings" + export DISPLAY=:0 + export WAYLAND_DISPLAY=wayland-0 +fi + +echo "[$(date)] Environment: DISPLAY=$DISPLAY WAYLAND_DISPLAY=$WAYLAND_DISPLAY" +echo "[$(date)] XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" + +# Now start the app +cd "$SCRIPT_DIR" || exit 1 +exec bash start.sh diff --git a/New.txt b/New.txt new file mode 100644 index 0000000..65fc06f --- /dev/null +++ b/New.txt @@ -0,0 +1,68 @@ +[INFO ] ✓ Playlist is up to date +[WARNING] Deprecated property "" of object "" has been set, it will be removed in a future version +[WARNING] Deprecated property "" of object "" was accessed, it will be removed in a future version + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None +[INFO ] ✅ Auth code verified +[INFO ] ✅ Using existing authentication + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "POST /api/player-feedback HTTP/1.1" 200 None +start.sh: line 212: 16035 Killed python3 main.py +[2026-01-17 21:31:57] ❌ Player process crashed or stopped (PID: 16035) +[2026-01-17 21:31:57] ⏳ Waiting 5s before restart... +[2026-01-17 21:32:02] +[2026-01-17 21:32:02] ========================================== +[2026-01-17 21:32:02] ▶️ Starting player (attempt #2) +[2026-01-17 21:32:02] ========================================== +[2026-01-17 21:32:02] Player PID: 16376 +[INFO ] [Logger ] Record log in /home/pi/.kivy/logs/kivy_26-01-17_43.txt +[INFO ] [Kivy ] v2.3.1 +[INFO ] [Kivy ] Installed at "/home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/kivy/__init__.py" +[INFO ] [Python ] v3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0] +[INFO ] [Python ] Interpreter at "/home/pi/Desktop/Kiwy-Signage/.venv/bin/python3" +[INFO ] [Logger ] Purge log fired. Processing... +[INFO ] [Logger ] Purge finished! + /home/pi/Desktop/Kiwy-Signage/src/main.py:1868: DeprecationWarning: There is no current event loop + loop = asyncio.get_event_loop() +[DEBUG ] [Using selector] EpollSelector +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +WARNING: running xinput against an Xwayland server. See the xinput man page for details. +[ERROR ] [Image ] Error loading +[WARNING] ⚠️ SSL verification disabled - NOT recommended for production! +[DEBUG ] [Starting new HTTPS connection (1)] 192.168.0.121:443 + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None +[INFO ] ✅ Auth code verified +[INFO ] ✅ Using existing authentication +[INFO ] [Fetching playlist from] https://192.168.0.121:443/api/playlists/1 + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "GET /api/playlists/1 HTTP/1.1" 200 None +[INFO ] [✅ Playlist received (version] 33) +[INFO ] [📊 Playlist versions - Server] v33, Local: v33 +[INFO ] ✓ Playlist is up to date +[WARNING] Deprecated property "" of object "" has been set, it will be removed in a future version +[WARNING] Deprecated property "" of object "" was accessed, it will be removed in a future version + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "POST /api/auth/verify HTTP/1.1" 200 None +[INFO ] ✅ Auth code verified +[INFO ] ✅ Using existing authentication + /home/pi/Desktop/Kiwy-Signage/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host '192.168.0.121'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings + warnings.warn( +[DEBUG ] [https ]//192.168.0.121:443 "POST /api/player-feedback HTTP/1.1" 200 None +start.sh: line 212: 16376 Killed python3 main.py +[2026-01-17 21:32:32] ❌ Player process crashed or stopped (PID: 16376) +[2026-01-17 21:32:32] ⏳ Waiting 5s before restart... +^C[2026-01-17 21:32:33] 🛑 Watchdog received stop signal +pi@rpi-tvcanba1:~/Desktop/Kiwy-Signage $ diff --git a/install.sh b/install.sh index 98e6d66..c62c3c0 100644 --- a/install.sh +++ b/install.sh @@ -35,31 +35,40 @@ setup_autostart() { SYSTEMD_DIR="$ACTUAL_HOME/.config/systemd/user" LXDE_AUTOSTART="$ACTUAL_HOME/.config/lxsession/LXDE-pi/autostart" - # Method 1: XDG Autostart (works with most desktop environments including Wayland) - echo "Creating XDG autostart entry..." + # Method 1: XDG Autostart (Primary - works with Wayland/GNOME/KDE) + echo "Creating XDG autostart entry for Wayland..." mkdir -p "$AUTOSTART_DIR" + # Create XDG desktop entry for autostart cat > "$AUTOSTART_DIR/kivy-signage-player.desktop" << 'EOF' [Desktop Entry] Type=Application Name=Kivy Signage Player Comment=Digital Signage Player -Exec=bash -c "cd $SCRIPT_DIR && exec bash start.sh" +Exec=/bin/bash -c "cd $SCRIPT_DIR && exec bash start.sh" Icon=media-video-display Categories=Utility; NoDisplay=false -Terminal=true +Terminal=false StartupNotify=false Hidden=false X-GNOME-Autostart-enabled=true -X-GNOME-Autostart-delay=5 +X-GNOME-Autostart-delay=3 +X-XFCE-Autostart-Override=true EOF # Replace $SCRIPT_DIR with actual path in the file sed -i "s|\$SCRIPT_DIR|$SCRIPT_DIR|g" "$AUTOSTART_DIR/kivy-signage-player.desktop" chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/kivy-signage-player.desktop" chmod 644 "$AUTOSTART_DIR/kivy-signage-player.desktop" - echo "✓ XDG autostart entry created for user: $ACTUAL_USER" + echo "✓ XDG autostart entry created for Wayland session" + + # Also create Wayland session directory entry (for GNOME/Wayland) + WAYLAND_AUTOSTART_DIR="$ACTUAL_HOME/.config/autostart.gnome" + mkdir -p "$WAYLAND_AUTOSTART_DIR" + cp "$AUTOSTART_DIR/kivy-signage-player.desktop" "$WAYLAND_AUTOSTART_DIR/kivy-signage-player.desktop" + chown "$ACTUAL_USER:$ACTUAL_USER" "$WAYLAND_AUTOSTART_DIR/kivy-signage-player.desktop" + echo "✓ XDG autostart also added to GNOME/Wayland session directory" # Method 2: LXDE Autostart (for Raspberry Pi OS with LXDE) if [ -f "$LXDE_AUTOSTART" ]; then @@ -73,33 +82,16 @@ EOF fi fi - # Method 3: System-wide systemd service (most reliable for Wayland) - echo "Creating system-wide systemd service..." + # Method 3: Disable systemd service (prefer Wayland session management) + # XDG autostart above is sufficient for Wayland/GNOME sessions + echo "Note: Using Wayland session autostart via XDG instead of systemd service" - sudo tee /etc/systemd/system/kiwy-player.service > /dev/null << EOF -[Unit] -Description=Kiwy Signage Player -After=multi-user.target graphical.target display-manager.service - -[Service] -Type=simple -User=$ACTUAL_USER -Environment="DISPLAY=:0" -Environment="XAUTHORITY=$ACTUAL_HOME/.Xauthority" -WorkingDirectory=$SCRIPT_DIR -ExecStart=/bin/bash $SCRIPT_DIR/start.sh -Restart=on-failure -RestartSec=10 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -EOF - - sudo systemctl daemon-reload - sudo systemctl enable kiwy-player.service - echo "✓ System-wide systemd service created and enabled" + # If systemd service exists from previous installation, disable it + if sudo test -f /etc/systemd/system/kiwy-player.service 2>/dev/null; then + echo "Disabling old systemd service in favor of Wayland session..." + sudo systemctl disable kiwy-player.service 2>/dev/null || true + echo "✓ Old systemd service disabled" + fi # Method 4: Cron job for fallback (starts at reboot) echo "Setting up cron fallback..." diff --git a/src/main.py b/src/main.py index cacaab2..e05c854 100644 --- a/src/main.py +++ b/src/main.py @@ -15,13 +15,17 @@ from concurrent.futures import ThreadPoolExecutor os.environ['KIVY_VIDEO'] = 'ffpyplayer' # Use ffpyplayer as video provider os.environ['FFPYPLAYER_CODECS'] = 'h264,h265,vp9,vp8' # Support common codecs os.environ['SDL_VIDEO_ALLOW_SCREENSAVER'] = '0' # Prevent screen saver +os.environ['SDL_VIDEODRIVER'] = 'wayland,x11,dummy' # Prefer Wayland, fallback to X11, then dummy +os.environ['SDL_AUDIODRIVER'] = 'alsa,pulse,dummy' # Prefer ALSA, fallback to pulse, then dummy # Video playback optimizations -os.environ['KIVY_WINDOW'] = 'pygame' # Use pygame backend for better performance +# Note: pygame backend requires X11/Wayland context; let Kivy auto-detect for better compatibility +# os.environ['KIVY_WINDOW'] = 'pygame' # Use pygame backend for better performance os.environ['KIVY_AUDIO'] = 'ffpyplayer' # Use ffpyplayer for audio os.environ['KIVY_GL_BACKEND'] = 'gl' # Use OpenGL backend -os.environ['FFMPEG_THREADS'] = '4' # Use 4 threads for ffmpeg decoding -os.environ['LIBPLAYER_BUFFER'] = '2048000' # 2MB buffer for smooth playback +os.environ['KIVY_INPUTPROVIDERS'] = 'wayland,x11' # Only use Wayland and X11 input providers, skip problematic ones +os.environ['FFMPEG_THREADS'] = '2' # Use 2 threads for ffmpeg decoding (Raspberry Pi has limited resources) +os.environ['LIBPLAYER_BUFFER'] = '1048576' # 1MB buffer (reduced from 2MB to save memory) os.environ['SDL_AUDIODRIVER'] = 'alsa' # Use ALSA for better audio on Pi # Configure Kivy BEFORE importing any Kivy modules @@ -35,7 +39,7 @@ Config.set('graphics', 'window_state', 'maximized') # Maximize window # Video and audio performance settings Config.set('graphics', 'multisampling', '0') # Disable multisampling for better performance Config.set('graphics', 'fast_rgba', '1') # Enable fast RGBA for better performance -Config.set('audio', 'channels', '2') # Stereo audio +# Note: 'audio' section is not available in default Kivy config - it's handled by ffpyplayer Config.set('kivy', 'log_level', 'warning') # Reduce logging overhead from kivy.app import App @@ -486,8 +490,12 @@ class CustomVKeyboard(VKeyboard): Logger.info("CustomVKeyboard: Wrapped in container with close button") -# Set the custom keyboard factory -Window.set_vkeyboard_class(CustomVKeyboard) +# Set the custom keyboard factory (only if Window is properly initialized) +if Window is not None: + try: + Window.set_vkeyboard_class(CustomVKeyboard) + except Exception as e: + Logger.warning(f"CustomVKeyboard: Could not set custom keyboard: {e}") class ExitPasswordPopup(Popup): def __init__(self, player_instance, was_paused=False, **kwargs): @@ -958,7 +966,7 @@ class SignagePlayer(Widget): # Wayland-specific commands # Method 1: Use wlopm (Wayland output power management) - os.system('wlopm --on \* 2>/dev/null || true') + os.system('wlopm --on \\* 2>/dev/null || true') # Method 2: Use wlr-randr for wlroots compositors os.system('wlr-randr --output HDMI-A-1 --on 2>/dev/null || true') @@ -1837,32 +1845,39 @@ class SignagePlayerApp(App): else: Logger.info(f"SignagePlayerApp: Using auto resolution (no constraint)") - # Force fullscreen and borderless - Window.fullscreen = True - Window.borderless = True - Logger.info(f"SignagePlayerApp: Screen size: {Window.size}") - Logger.info(f"SignagePlayerApp: Available screen size: {Window.system_size if hasattr(Window, 'system_size') else 'N/A'}") - # Hide cursor after 3 seconds of inactivity - Clock.schedule_once(self.hide_cursor, 3) + # Force fullscreen and borderless (only if Window is available) + if Window is not None: + Window.fullscreen = True + Window.borderless = True + Logger.info(f"SignagePlayerApp: Screen size: {Window.size}") + Logger.info(f"SignagePlayerApp: Available screen size: {Window.system_size if hasattr(Window, 'system_size') else 'N/A'}") + # Hide cursor after 3 seconds of inactivity + Clock.schedule_once(self.hide_cursor, 3) + else: + Logger.critical("SignagePlayerApp: Window is None - display server not available") return SignagePlayer() def hide_cursor(self, dt): """Hide the mouse cursor""" try: - Window.show_cursor = False + if Window is not None: + Window.show_cursor = False except: pass # Some platforms don't support cursor hiding def on_start(self): # Setup asyncio event loop for Kivy integration try: - loop = asyncio.get_event_loop() - Logger.info("SignagePlayerApp: Asyncio event loop integrated with Kivy") - except RuntimeError: - # Create new event loop if none exists - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - Logger.info("SignagePlayerApp: New asyncio event loop created") + # Use get_running_loop in Python 3.7+ to avoid deprecation warning + try: + loop = asyncio.get_running_loop() + except RuntimeError: + # No running loop, create a new one + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + Logger.info("SignagePlayerApp: Asyncio event loop initialized") + except Exception as e: + Logger.warning(f"SignagePlayerApp: Could not setup asyncio loop: {e}") # Schedule periodic async task processing Clock.schedule_interval(self._process_async_tasks, 0.1) # Process every 100ms diff --git a/start.sh b/start.sh index f2b54a9..e596a87 100755 --- a/start.sh +++ b/start.sh @@ -14,11 +14,79 @@ HEARTBEAT_FILE="$SCRIPT_DIR/.player_heartbeat" STOP_FLAG_FILE="$SCRIPT_DIR/.player_stop_requested" LOG_FILE="$SCRIPT_DIR/player_watchdog.log" -# Function to log messages +# Ensure log file is writable +if [ ! -w "$(dirname "$LOG_FILE")" ]; then + LOG_FILE="/tmp/kivy-player-watchdog.log" +fi + +# Function to log messages (MUST be defined before use) log_message() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" + local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo "$msg" + # Try to write to log file, ignore errors if permission denied + echo "$msg" >> "$LOG_FILE" 2>/dev/null || true } +# Load user's environment from systemd user session (most reliable method) +# This ensures we get the proper DISPLAY/WAYLAND_DISPLAY and session variables +load_user_environment() { + # Try to get environment from active user session via systemctl + local user_env + if command -v systemctl &>/dev/null; then + user_env=$(systemctl --user show-environment 2>/dev/null) + if [ -n "$user_env" ]; then + # Extract display-related variables + echo "$user_env" | grep -E '^(DISPLAY|WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION_BUS_ADDRESS)=' | while read -r line; do + export "$line" + done + log_message "Loaded user environment from systemctl --user" + return 0 + fi + fi + + # Fallback: detect display manually + log_message "Falling back to manual display detection..." + + if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then + # Try to detect Wayland display + if [ -S "/run/user/$(id -u)/wayland-0" ]; then + export WAYLAND_DISPLAY=wayland-0 + log_message "Detected Wayland display: $WAYLAND_DISPLAY" + # Try to detect X11 display + elif [ -S "/tmp/.X11-unix/X0" ]; then + export DISPLAY=:0 + log_message "Detected X11 display: $DISPLAY" + else + # Wait for display to come up (useful for systemd or delayed starts) + log_message "Waiting for display server to be ready (up to 30 seconds)..." + for i in {1..30}; do + sleep 1 + if [ -S "/run/user/$(id -u)/wayland-0" ] 2>/dev/null; then + export WAYLAND_DISPLAY=wayland-0 + log_message "Display server detected on attempt $i: Wayland" + return 0 + elif [ -S "/tmp/.X11-unix/X0" ] 2>/dev/null; then + export DISPLAY=:0 + log_message "Display server detected on attempt $i: X11" + return 0 + fi + done + fi + fi + + # Verify we have a display now + if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then + log_message "WARNING: No display server detected. This may cause graphics issues." + return 1 + fi + + log_message "Display environment ready: DISPLAY=$DISPLAY WAYLAND_DISPLAY=$WAYLAND_DISPLAY" + return 0 +} + +# Load the user environment early +load_user_environment + # Function to check if player is healthy check_health() { # Check if heartbeat file exists and is recent (within last 60 seconds)