diff --git a/.display-keepalive.sh b/.display-keepalive.sh deleted file mode 100755 index 7101ae3..0000000 --- a/.display-keepalive.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -# Aggressive display keep-alive for Raspberry Pi -# Supports both X11 and Wayland environments - -DISPLAY_TIMEOUT=30 - -# Detect display server type -detect_display_server() { - if [ -n "$WAYLAND_DISPLAY" ]; then - echo "wayland" - elif [ -n "$DISPLAY" ]; then - echo "x11" - else - echo "unknown" - fi -} - -DISPLAY_SERVER=$(detect_display_server) - -while true; do - # Keep HDMI powered on (works for both X11 and Wayland) - if command -v tvservice &> /dev/null; then - /usr/bin/tvservice -p 2>/dev/null - fi - - if [ "$DISPLAY_SERVER" = "wayland" ]; then - # Wayland-specific power management - - # Method 1: Use wlr-randr for Wayland compositors (if available) - if command -v wlr-randr &> /dev/null; then - wlr-randr --output HDMI-A-1 --on 2>/dev/null || true - fi - - # Method 2: Prevent idle using systemd-inhibit - if command -v systemd-inhibit &> /dev/null; then - # This is already running, but refresh the lock - systemctl --user restart plasma-ksmserver.service 2>/dev/null || true - fi - - # Method 3: Use wlopm (Wayland output power management) - if command -v wlopm &> /dev/null; then - wlopm --on \* 2>/dev/null || true - fi - - # Method 4: Simulate activity via input (works on Wayland) - if command -v ydotool &> /dev/null; then - ydotool mousemove -x 1 -y 1 2>/dev/null || true - ydotool mousemove -x -1 -y -1 2>/dev/null || true - fi - - # Method 5: GNOME/KDE Wayland idle inhibit - if command -v gnome-session-inhibit &> /dev/null; then - # Already inhibited by running process - true - fi - - else - # X11-specific power management (original code) - if command -v xset &> /dev/null; then - DISPLAY=:0 xset s off 2>/dev/null - DISPLAY=:0 xset -dpms 2>/dev/null - DISPLAY=:0 xset dpms force on 2>/dev/null - DISPLAY=:0 xset s reset 2>/dev/null - fi - - # Move mouse to trigger activity - if command -v xdotool &> /dev/null; then - DISPLAY=:0 xdotool mousemove_relative 1 1 2>/dev/null - DISPLAY=:0 xdotool mousemove_relative -1 -1 2>/dev/null - fi - - # Disable monitor power saving - if command -v xrandr &> /dev/null; then - DISPLAY=:0 xrandr --output HDMI-1 --power-profile performance 2>/dev/null || true - fi - fi - - sleep $DISPLAY_TIMEOUT -done diff --git a/.gitignore b/.gitignore index 578997f..7004922 100644 --- a/.gitignore +++ b/.gitignore @@ -30,24 +30,48 @@ env/ *.pyc # Media files (optional - remove if you want to track media) -media/*.jpg -media/*.jpeg -media/*.png -media/*.gif -media/*.bmp -media/*.mp4 -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) -playlists/server_playlist_*.json +# Downloaded / on-device edited media (server-managed runtime content) +media/edited_media/ + +# Playlists cache (auto-generated by the player) +playlists/server_playlist*.json + +# Player configuration (machine-specific; install.sh writes a template if absent) +config/app_config.json # Logs *.log .kivy/ +# Runtime state (generated by the player / watchdog) +.player_heartbeat +.player_stop_requested +.player_watchdog.lock +.post-clone-setup.done +.post-clone-backups/ + +# Installer-generated launchers (recreated by install.sh) +.run-background.sh +.start-player-cron.sh + +# Secrets / machine-specific auth tokens (never commit) +player_auth.json + +# Local backups / editor leftovers +*.bak +*~ + # IDE .vscode/ .idea/ diff --git a/.keep-screen-alive.sh b/.keep-screen-alive.sh deleted file mode 100755 index 3616090..0000000 --- a/.keep-screen-alive.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# Keep-screen-alive wrapper for player -# Prevents screen from locking/turning off while player is running - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Function to keep screen awake -keep_screen_awake() { - while true; do - # Move mouse slightly to prevent idle - if command -v xdotool &> /dev/null; then - xdotool mousemove_relative 1 1 - xdotool mousemove_relative -1 -1 - fi - - # Disable DPMS and screensaver periodically - if command -v xset &> /dev/null; then - xset s reset - xset dpms force on - fi - - sleep 30 - done -} - -# Function to inhibit systemd sleep (if available) -inhibit_sleep() { - if command -v systemd-inhibit &> /dev/null; then - # Run player under systemd inhibit to prevent sleep - systemd-inhibit --what=sleep --why="Signage player running" \ - bash "$SCRIPT_DIR/start.sh" - return $? - fi - return 1 -} - -# Try systemd inhibit first (most reliable) -if inhibit_sleep; then - exit 0 -fi - -# Fallback: Start keep-alive in background -keep_screen_awake & -KEEPALIVE_PID=$! - -# Start the player -cd "$SCRIPT_DIR" -bash start.sh -PLAYER_EXIT=$? - -# Kill keep-alive when player exits -kill $KEEPALIVE_PID 2>/dev/null || true - -exit $PLAYER_EXIT diff --git a/.run-background.sh b/.run-background.sh deleted file mode 100755 index a910bac..0000000 --- a/.run-background.sh +++ /dev/null @@ -1,11 +0,0 @@ -#\!/bin/bash -SCRIPT_DIR="/home/pi/kiwy-signage" -LOG_FILE="/home/pi/kiwy-signage/logs/player-background.log" -mkdir -p "." -echo "[2026-07-17 10:54:53] Starting player in background..." >> "" -cd "/home/pi/kiwy-signage" || exit 1 -nohup bash start.sh >> "" 2>&1 & -PLAYER_PID=$\! -echo "[2026-07-17 10:54:53] Player started with PID: $PLAYER_PID" >> "" -echo "Player started in background (PID: $PLAYER_PID)" -echo "Logs: $LOG_FILE" diff --git a/.start-player-cron.sh b/.start-player-cron.sh deleted file mode 100755 index 0fa7745..0000000 --- a/.start-player-cron.sh +++ /dev/null @@ -1,3 +0,0 @@ -#\!/bin/bash -sleep 15 -cd /home/pi/kiwy-signage && bash start.sh diff --git a/.wait-for-display.sh b/.wait-for-display.sh deleted file mode 100755 index f460f64..0000000 --- a/.wait-for-display.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/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 deleted file mode 100644 index 30d809e..0000000 --- a/New.txt +++ /dev/null @@ -1,43 +0,0 @@ -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! -[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] 34) -[INFO ] [📊 Playlist versions - Server] v34, Local: v34 -[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 -^C[2026-01-17 22:09:12] 🛑 Watchdog received stop signal -pi@rpi-tvcanba1:~/Desktop/Kiwy-Signage $ - diff --git a/config/app_config.json b/config/app_config.json deleted file mode 100644 index e09c3f5..0000000 --- a/config/app_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "server_ip": "192.168.0.109", - "port": "8080", - "screen_name": "Birou_IT", - "quickconnect_key": "8887779", - "orientation": "Landscape", - "touch": "True", - "max_resolution": "1920x1080", - "edit_feature_enabled": true, - "use_https": false, - "verify_ssl": false -} \ No newline at end of file diff --git a/working_files/analyze_playlist.py b/dev/analyze_playlist.py similarity index 100% rename from working_files/analyze_playlist.py rename to dev/analyze_playlist.py diff --git a/working_files/check_video_codecs.py b/dev/check_video_codecs.py similarity index 100% rename from working_files/check_video_codecs.py rename to dev/check_video_codecs.py diff --git a/working_files/convert_video_for_rpi.sh b/dev/convert_video_for_rpi.sh similarity index 100% rename from working_files/convert_video_for_rpi.sh rename to dev/convert_video_for_rpi.sh diff --git a/working_files/diagnose_playlist.py b/dev/diagnose_playlist.py similarity index 100% rename from working_files/diagnose_playlist.py rename to dev/diagnose_playlist.py diff --git a/working_files/force_update.py b/dev/force_update.py similarity index 100% rename from working_files/force_update.py rename to dev/force_update.py diff --git a/working_files/get_playlists.py b/dev/get_playlists.py.v1 similarity index 100% rename from working_files/get_playlists.py rename to dev/get_playlists.py.v1 diff --git a/working_files/server_response_debug.json b/dev/server_response_debug.json similarity index 100% rename from working_files/server_response_debug.json rename to dev/server_response_debug.json diff --git a/working_files/test_authentication.py b/dev/tests/test_authentication.py similarity index 100% rename from working_files/test_authentication.py rename to dev/tests/test_authentication.py diff --git a/working_files/test_card_reader.py b/dev/tests/test_card_reader.py similarity index 100% rename from working_files/test_card_reader.py rename to dev/tests/test_card_reader.py diff --git a/working_files/test_connection.py b/dev/tests/test_connection.py similarity index 100% rename from working_files/test_connection.py rename to dev/tests/test_connection.py diff --git a/working_files/test_direct_api.py b/dev/tests/test_direct_api.py similarity index 100% rename from working_files/test_direct_api.py rename to dev/tests/test_direct_api.py diff --git a/test_edited_media_upload.py b/dev/tests/test_edited_media_upload.py similarity index 100% rename from test_edited_media_upload.py rename to dev/tests/test_edited_media_upload.py diff --git a/working_files/test_playback_logging.py b/dev/tests/test_playback_logging.py similarity index 100% rename from working_files/test_playback_logging.py rename to dev/tests/test_playback_logging.py diff --git a/working_files/test_server_playlist.py b/dev/tests/test_server_playlist.py similarity index 100% rename from working_files/test_server_playlist.py rename to dev/tests/test_server_playlist.py diff --git a/PLAYER_WEBLINK_INTEGRATION.md b/documentation/PLAYER_WEBLINK_INTEGRATION.md similarity index 100% rename from PLAYER_WEBLINK_INTEGRATION.md rename to documentation/PLAYER_WEBLINK_INTEGRATION.md diff --git a/working_files/CARD_READER_AUTHENTICATION.md b/documentation/dev-notes/CARD_READER_AUTHENTICATION.md similarity index 98% rename from working_files/CARD_READER_AUTHENTICATION.md rename to documentation/dev-notes/CARD_READER_AUTHENTICATION.md index d33653b..5361ff5 100644 --- a/working_files/CARD_READER_AUTHENTICATION.md +++ b/documentation/dev-notes/CARD_READER_AUTHENTICATION.md @@ -75,7 +75,7 @@ If no card is swiped (timeout), `user_card_data` will be `null`. A test utility is provided to verify card reader functionality: ```bash -cd /home/pi/Desktop/Kiwy-Signage/working_files +cd /home/pi/Desktop/Kiwy-Signage/dev/tests python3 test_card_reader.py ``` @@ -178,5 +178,5 @@ Potential improvements for the card reader system: - `/src/main.py` - Main implementation (CardReader class, authentication flow) - `/src/edit_drowing.py` - Drawing/editing interface (uses authenticated user) -- `/working_files/test_card_reader.py` - Card reader test utility +- `/dev/tests/test_card_reader.py` - Card reader test utility - `/requirements.txt` - Dependencies (includes evdev) diff --git a/working_files/CARD_READER_FIX.md b/documentation/dev-notes/CARD_READER_FIX.md similarity index 98% rename from working_files/CARD_READER_FIX.md rename to documentation/dev-notes/CARD_READER_FIX.md index 5ca9a12..1b9521f 100644 --- a/working_files/CARD_READER_FIX.md +++ b/documentation/dev-notes/CARD_READER_FIX.md @@ -54,7 +54,7 @@ The system now logs: Run the enhanced test script to identify your card reader: ```bash -cd /home/pi/Desktop/Kiwy-Signage/working_files +cd /home/pi/Desktop/Kiwy-Signage/dev/tests python3 test_card_reader.py ``` @@ -147,7 +147,7 @@ sudo usermod -a -G input $USER - Implemented priority-based search - Enhanced logging -2. **working_files/test_card_reader.py** +2. **dev/tests/test_card_reader.py** - Updated `list_input_devices()` to show device classifications - Updated `test_card_reader()` to use same logic as main app - Added visual indicators for device types diff --git a/working_files/DEBUGGING_MEDIA_SKIPS.md b/documentation/dev-notes/DEBUGGING_MEDIA_SKIPS.md similarity index 100% rename from working_files/DEBUGGING_MEDIA_SKIPS.md rename to documentation/dev-notes/DEBUGGING_MEDIA_SKIPS.md diff --git a/working_files/IMPLEMENTATION_SUMMARY.md b/documentation/dev-notes/IMPLEMENTATION_SUMMARY.md similarity index 100% rename from working_files/IMPLEMENTATION_SUMMARY.md rename to documentation/dev-notes/IMPLEMENTATION_SUMMARY.md diff --git a/working_files/INVESTIGATION_RESULTS.md b/documentation/dev-notes/INVESTIGATION_RESULTS.md similarity index 100% rename from working_files/INVESTIGATION_RESULTS.md rename to documentation/dev-notes/INVESTIGATION_RESULTS.md diff --git a/working_files/MIGRATION_GUIDE.md b/documentation/dev-notes/MIGRATION_GUIDE.md similarity index 100% rename from working_files/MIGRATION_GUIDE.md rename to documentation/dev-notes/MIGRATION_GUIDE.md diff --git a/working_files/OFFLINE_INSTALLATION.md b/documentation/dev-notes/OFFLINE_INSTALLATION.md similarity index 91% rename from working_files/OFFLINE_INSTALLATION.md rename to documentation/dev-notes/OFFLINE_INSTALLATION.md index c1d3d52..1607df3 100644 --- a/working_files/OFFLINE_INSTALLATION.md +++ b/documentation/dev-notes/OFFLINE_INSTALLATION.md @@ -16,8 +16,9 @@ Kiwy-Signage/ │ │ ├── apt-packages.txt # List of required apt packages │ │ └── debs/ # Downloaded .deb files (optional) │ └── README.md -├── download_offline_packages.sh # Download Python packages -├── download_deb_packages.sh # Download system .deb packages +├── scripts/ +│ ├── download_offline_packages.sh # Download Python packages +│ └── download_deb_packages.sh # Download system .deb packages └── install.sh # Smart installer (online/offline) ``` @@ -33,10 +34,10 @@ git clone cd Kiwy-Signage # Download Python packages -bash download_offline_packages.sh +bash scripts/download_offline_packages.sh # (Optional) Download system .deb packages -bash download_deb_packages.sh +bash scripts/download_deb_packages.sh ``` This will populate the `repo/` folder with all necessary packages. @@ -104,7 +105,7 @@ To update the offline package cache: ```bash # On a connected system -bash download_offline_packages.sh +bash scripts/download_offline_packages.sh ``` This will download the latest versions of all packages. @@ -117,7 +118,7 @@ If installation fails due to missing dependencies: ```bash # Download .deb packages with dependencies -bash download_deb_packages.sh +bash scripts/download_deb_packages.sh # Install with dependency resolution sudo apt install -f diff --git a/working_files/OFFLINE_QUICKSTART.md b/documentation/dev-notes/OFFLINE_QUICKSTART.md similarity index 74% rename from working_files/OFFLINE_QUICKSTART.md rename to documentation/dev-notes/OFFLINE_QUICKSTART.md index ad5db5b..721617f 100644 --- a/working_files/OFFLINE_QUICKSTART.md +++ b/documentation/dev-notes/OFFLINE_QUICKSTART.md @@ -4,10 +4,10 @@ ```bash # 1. Download Python packages (required) -bash download_offline_packages.sh +bash scripts/download_offline_packages.sh # 2. Download system .deb packages (optional, for fully offline) -bash download_deb_packages.sh +bash scripts/download_deb_packages.sh ``` ## For Offline System (Installation) @@ -22,12 +22,13 @@ bash install.sh --offline ## What's Included -### Python Packages (18 wheels) -✅ Kivy 2.1.0 -✅ Requests 2.32.4 +### Python Packages +✅ Kivy 2.3.1 +✅ Requests 2.34.2 ✅ Bcrypt 4.2.1 -✅ Aiohttp 3.9.1 (async HTTP) -✅ Asyncio 3.4.3 (async framework) +✅ Aiohttp 3.11.16 (async HTTP) +✅ ffpyplayer 4.5.3 (video) +✅ evdev 1.9.1 (card reader) ✅ All dependencies ### System Packages diff --git a/working_files/QUICK_START.md b/documentation/dev-notes/QUICK_START.md similarity index 100% rename from working_files/QUICK_START.md rename to documentation/dev-notes/QUICK_START.md diff --git a/working_files/README.md b/documentation/dev-notes/README.md similarity index 100% rename from working_files/README.md rename to documentation/dev-notes/README.md diff --git a/install.sh b/install.sh index 8367d01..1991f95 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ -#\!/bin/bash +#!/bin/bash # Kivy Signage Player Installation Script # Supports both online and offline installation using a Python virtual environment @@ -77,6 +77,48 @@ detect_raspberry_pi() { fi } +# ============================================================ +# Helper: Internet reachability probe (dependency-free) +# ============================================================ +# Returns 0 if the public internet (PyPI) is reachable, 1 otherwise. +# Uses only the Python stdlib so it works on a bare, offline host. +has_internet() { + python3 - << 'PYEOF' >/dev/null 2>&1 +import socket +try: + socket.setdefaulttimeout(3) + socket.create_connection(("pypi.org", 443), timeout=3).close() + raise SystemExit(0) +except Exception: + raise SystemExit(1) +PYEOF + return $? +} + +# ============================================================ +# Helper: Fetch a URL using curl, wget, or the Python stdlib +# ============================================================ +# Works on minimal/offline images where curl/wget may be absent. +fetch_url() { + local url="$1" + if command -v curl &>/dev/null; then + curl -sSf --max-time 20 "$url" + elif command -v wget &>/dev/null; then + wget -qO- --timeout=20 "$url" + else + python3 - "$url" << 'PYEOF' +import sys, urllib.request +url = sys.argv[1] +try: + with urllib.request.urlopen(url, timeout=20) as r: + sys.stdout.write(r.read().decode("utf-8", "replace")) +except Exception as e: + sys.stderr.write("fetch failed: %s\n" % e) + raise SystemExit(1) +PYEOF + fi +} + # ============================================================ # CLI Argument Parsing # ============================================================ @@ -92,6 +134,7 @@ MAX_RESOLUTION="" EDIT_ENABLED="" PROVISION_URL="" CONFIG_FILE_PATH="" +NO_REBOOT=false usage() { echo "Usage: $0 [OPTIONS]" @@ -119,6 +162,7 @@ usage() { echo "" echo "Installation options:" echo " --offline Force offline installation from local packages" + echo " --no-reboot Do not reboot automatically at the end" echo " -q, --quiet Silent mode (minimal output)" echo " -h, --help Show this help" exit 0 @@ -186,6 +230,10 @@ while [ $# -gt 0 ]; do OFFLINE_MODE=true shift ;; + --no-reboot) + NO_REBOOT=true + shift + ;; -q|--quiet) exec > >(tee -a /tmp/kivy-install.log) 2>&1 shift @@ -205,6 +253,12 @@ if [ "$OFFLINE_MODE" = false ] && [ -d "$WHEELS_DIR" ] && [ "$(ls -A "$WHEELS_DI OFFLINE_MODE=true fi +# Probe internet reachability once (guards every network step below) +HAS_INTERNET=false +if has_internet; then + HAS_INTERNET=true +fi + # ============================================================ # Mode Banner # ============================================================ @@ -216,6 +270,11 @@ if [ "$OFFLINE_MODE" = true ]; then else echo "Mode: Online Installation" fi +if [ "$HAS_INTERNET" = true ]; then + echo "Internet: reachable" +else + echo "Internet: NOT available (local-network / isolated install)" +fi if [ -n "$SUDO_USERNAME" ]; then echo "Sudo user: $SUDO_USERNAME" fi @@ -228,27 +287,54 @@ echo "Step 1/6: Installing system dependencies..." echo "--------------------" echo "Updating package lists..." -run_sudo apt update +if [ "$HAS_INTERNET" = true ]; then + run_sudo apt update 2>/dev/null || echo " WARNING: 'apt update' failed; continuing with existing lists" +else + echo " Offline / no internet — skipping 'apt update'" + run_sudo apt update 2>/dev/null || true +fi echo "Installing system packages..." -if [ "$OFFLINE_MODE" = true ] && [ -d "$DEB_DIR" ] && [ "$(ls -A $DEB_DIR/*.deb 2>/dev/null)" ]; then - echo "Installing from offline .deb packages..." +# System dependencies required for Kivy + video playback +APT_PACKAGES="python3-pip python3-setuptools python3-dev python3-venv \ +libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ +libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev \ +zlib1g-dev ffmpeg libavcodec-extra \ +gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ +curl wget" + +# Prefer the offline .deb bundle when present +USED_DEB_BUNDLE=false +if [ -d "$DEB_DIR" ] && ls "$DEB_DIR"/*.deb >/dev/null 2>&1; then + echo "Installing from offline .deb packages in $DEB_DIR..." cd "$DEB_DIR" run_sudo dpkg -i *.deb 2>/dev/null || true - run_sudo apt-get install -f -y || true + # Resolve dependencies from the local bundle only (no-op if already satisfied) + run_sudo apt-get install -f -y 2>/dev/null || true cd "$SCRIPT_DIR" + USED_DEB_BUNDLE=true +elif [ "$OFFLINE_MODE" = true ]; then + echo "WARNING: Offline mode but no .deb bundle found in $DEB_DIR" + echo " System packages will still be attempted via apt (may fail without internet)" fi -run_sudo apt install -y \ - python3-pip python3-setuptools python3-dev \ - python3-venv \ - libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ - libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev \ - zlib1g-dev ffmpeg libavcodec-extra \ - gstreamer1.0-plugins-base gstreamer1.0-plugins-good +# Install/complete system packages. Never abort the installer: +# offline, some packages may already be present (fine) or simply unavailable. +if run_sudo apt install -y $APT_PACKAGES 2>/dev/null; then + echo "System dependencies installed" +else + echo "WARNING: bulk system package install did not fully succeed." + echo " Installing available packages individually..." + for pkg in $APT_PACKAGES; do + run_sudo apt install -y "$pkg" 2>/dev/null || echo " - unavailable (skipped): $pkg" + done + if [ "$HAS_INTERNET" != true ] && [ "$USED_DEB_BUNDLE" != true ]; then + echo " NOTE: Host is offline and no .deb bundle was found." + echo " Add .deb files to $DEB_DIR to satisfy missing system packages." + fi +fi -echo "System dependencies installed" echo "" # ============================================================ @@ -268,8 +354,12 @@ echo "Virtual environment created at $VENV_DIR" source "$VENV_DIR/bin/activate" -echo "Upgrading pip..." -pip install --upgrade pip --quiet +if [ "$OFFLINE_MODE" = true ] && [ "$HAS_INTERNET" != true ]; then + echo "Skipping pip self-upgrade (offline, no internet)" +else + echo "Upgrading pip..." + pip install --upgrade pip --quiet 2>/dev/null || echo " WARNING: pip upgrade failed; continuing" +fi echo "" @@ -293,9 +383,13 @@ if [ -f "$REQUIREMENTS_FILE" ]; then else echo "" echo "Offline installation failed (some wheels may be missing or incompatible)" - echo " Falling back to online installation (requires internet)..." - echo "" - pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true + if [ "$HAS_INTERNET" = true ]; then + echo " Falling back to online installation (requires internet)..." + pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true + else + echo " No internet available — cannot fall back to online installation." + echo " Add the missing/compatible wheels to: $WHEELS_DIR" + fi fi else pip install -r "$REQUIREMENTS_FILE" --find-links="$WHEELS_DIR" 2>&1 || true @@ -306,16 +400,20 @@ if [ -f "$REQUIREMENTS_FILE" ]; then echo "Verifying installed packages..." pip list --format=columns 2>/dev/null | grep -iE "kivy|ffpyplayer" || true - # If kivy is still missing after pip install, try system-wide fallback - python3 -c "import kivy" 2>/dev/null && echo " kivy OK" || { - echo "" - echo " kivy not found in venv — installing system-wide via pip (break-system-packages)..." - pip install kivy --break-system-packages 2>/dev/null || pip install --user kivy 2>/dev/null || true - } - python3 -c "import ffpyplayer" 2>/dev/null && echo " ffpyplayer OK" || { - echo " ffpyplayer not found — installing system-wide..." - pip install ffpyplayer --break-system-packages 2>/dev/null || pip install --user ffpyplayer 2>/dev/null || true - } + # If a critical package is missing, fall back to online install only when reachable + for pkg in kivy ffpyplayer; do + if python3 -c "import $pkg" 2>/dev/null; then + echo " $pkg OK" + else + echo " WARNING: $pkg could not be imported in the virtual environment" + if [ "$HAS_INTERNET" = true ]; then + echo " Attempting online fallback install for $pkg..." + pip install "$pkg" 2>/dev/null || echo " - $pkg online install failed (continuing)" + else + echo " Offline: ensure a compatible '$pkg' wheel exists in $WHEELS_DIR" + fi + fi + done else echo "requirements.txt not found - skipping pip install" fi @@ -358,24 +456,32 @@ run_sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/kivy-signage-player.d chmod 644 "$AUTOSTART_DIR/kivy-signage-player.desktop" 2>/dev/null || true echo "XDG autostart entry created" -# Cron fallback +# Cron fallback (secondary to XDG autostart; start.sh enforces single-instance) if command -v crontab &>/dev/null; then CRON_WRAPPER="$SCRIPT_DIR/.start-player-cron.sh" + CRON_MARKER_TAG=".start-player-cron.sh" cat > "$CRON_WRAPPER" << EOF -#\!/bin/bash +#!/bin/bash +# Kiwy signage player cron fallback (managed by install.sh) sleep 15 cd $SCRIPT_DIR && bash start.sh EOF chmod +x "$CRON_WRAPPER" - - CRON_ENTRY="@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1" - # Use sudo -u which works when running with sudo (no password prompt) - if sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null | grep -q "kivy-signage-player"; then - true - else - (sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null || true; echo "$CRON_ENTRY") | sudo -u "$ACTUAL_USER" crontab - 2>/dev/null || true - fi - echo "Cron fallback configured" + + # Idempotent: strip ALL previous Kiwy cron lines, then add exactly one. + # (The old guard grepped for a string that never appeared in the entry, + # so every re-install appended another @reboot line.) + CURRENT_CRON="$(sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null || true)" + CLEANED_CRON="$(printf '%s\n' "$CURRENT_CRON" \ + | grep -v -F "$CRON_MARKER_TAG" \ + | grep -v -F "$CRON_WRAPPER" \ + | grep -v -F "# Kiwy signage player" || true)" + { + printf '%s\n' "$CLEANED_CRON" | grep -v '^$' + echo "# Kiwy signage player autostart fallback (managed by install.sh)" + echo "@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1" + } | sudo -u "$ACTUAL_USER" crontab - 2>/dev/null || true + echo "Cron fallback configured (single entry, deduplicated)" fi echo "" @@ -442,7 +548,7 @@ write_player_config() { if [ -n "$PROVISION_URL" ]; then echo "Fetching provisioning data from: $PROVISION_URL" local response - response=$(curl -sSf "$PROVISION_URL" 2>&1 || true) + response=$(fetch_url "$PROVISION_URL" 2>/dev/null || true) if [ -n "$response" ]; then echo "$response" > "$config_file" echo "Provisioning data saved" @@ -509,7 +615,7 @@ with open(config_file, 'w') as f: print("Configuration written") PYEOF - elif [ \! -f "$config_file" ]; then + elif [ ! -f "$config_file" ]; then cat > "$config_file" << 'EOF' { "server_ip": "", @@ -568,18 +674,18 @@ fi # Create background runner NOHUP_WRAPPER="$SCRIPT_DIR/.run-background.sh" -cat > "$NOHUP_WRAPPER" << EOF -#\!/bin/bash +cat > "$NOHUP_WRAPPER" << 'EOF' +#!/bin/bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$SCRIPT_DIR/logs/player-background.log" mkdir -p "$(dirname "$LOG_FILE")" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting player in background..." >> "$LOG_FILE" cd "$SCRIPT_DIR" || exit 1 nohup bash start.sh >> "$LOG_FILE" 2>&1 & -PLAYER_PID=\$\! -echo "[$(date '+%Y-%m-%d %H:%M:%S')] Player started with PID: \$PLAYER_PID" >> "$LOG_FILE" -echo "Player started in background (PID: \$PLAYER_PID)" -echo "Logs: \$LOG_FILE" +PLAYER_PID=$! +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Player started with PID: $PLAYER_PID" >> "$LOG_FILE" +echo "Player started in background (PID: $PLAYER_PID)" +echo "Logs: $LOG_FILE" EOF chmod +x "$NOHUP_WRAPPER" echo "Background runner created" @@ -589,7 +695,7 @@ echo "Background runner created" # ============================================================ echo "" echo "==========================================" -echo "Installation Complete\!" +echo "Installation Complete!" echo "==========================================" echo "" echo "Installation: $SCRIPT_DIR" @@ -636,7 +742,15 @@ if [ -f "$PI_BOOT_PATCH_SCRIPT" ]; then echo "" fi -if [ -z "$SUDO_PASSWORD" ]; then +if [ "$NO_REBOOT" = true ]; then + echo "==========================================" + echo "Reboot Skipped (--no-reboot)" + echo "==========================================" + echo "" + echo "Some changes (HDMI settings, GPU memory) may require a reboot." + echo "Reboot manually when convenient: sudo reboot" + echo "" +elif [ -z "$SUDO_PASSWORD" ]; then echo "==========================================" echo "System Reboot Recommended" echo "==========================================" diff --git a/install.sh.bak b/install.sh.bak deleted file mode 100755 index 6ae7924..0000000 --- a/install.sh.bak +++ /dev/null @@ -1,1056 +0,0 @@ -#!/bin/bash - -# Kivy Signage Player Installation Script -# Supports both online and offline installation - -set -e - -# Cleanup handler for Ctrl+C / unexpected exit -cleanup() { - echo "" - echo "⚠️ Installation interrupted. Cleaning up..." - # Nothing critical to clean, but inform the user - echo "If the script was modifying system files, you may need to re-run." - exit 1 -} -trap cleanup SIGINT SIGTERM - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$SCRIPT_DIR/repo" -WHEELS_DIR="$REPO_DIR/python-wheels" -SYSTEM_DIR="$REPO_DIR/system-packages" -DEB_DIR="$SYSTEM_DIR/debs" - -# Helper to find the correct boot config file (Raspberry Pi) -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 -} - -# Function to detect Raspberry Pi -detect_raspberry_pi() { - BOOT_CONFIG=$(find_boot_config) - if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null || [ -n "$BOOT_CONFIG" ]; then - return 0 # Is Raspberry Pi - else - return 1 # Not Raspberry Pi - fi -} - -# Function to setup autostart workflow -setup_autostart() { - echo "" - echo "==========================================" - echo "Setting up Autostart Workflow" - echo "==========================================" - - # Determine the actual user (if running with sudo) - ACTUAL_USER="${SUDO_USER:-$(whoami)}" - ACTUAL_HOME=$(eval echo ~"$ACTUAL_USER") - - AUTOSTART_DIR="$ACTUAL_HOME/.config/autostart" - SYSTEMD_DIR="$ACTUAL_HOME/.config/systemd/user" - LXDE_AUTOSTART="$ACTUAL_HOME/.config/lxsession/LXDE-pi/autostart" - - # 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=/bin/bash -c "cd $SCRIPT_DIR && exec bash start.sh" -Icon=media-video-display -Categories=Utility; -NoDisplay=false -Terminal=false -StartupNotify=false -Hidden=false -X-GNOME-Autostart-enabled=true -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 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 - echo "Configuring LXDE autostart..." - if ! grep -q "kivy-signage-player" "$LXDE_AUTOSTART"; then - echo "" >> "$LXDE_AUTOSTART" - echo "# Kivy Signage Player autostart" >> "$LXDE_AUTOSTART" - echo "@bash -c 'cd $SCRIPT_DIR && bash start.sh'" >> "$LXDE_AUTOSTART" - chown "$ACTUAL_USER:$ACTUAL_USER" "$LXDE_AUTOSTART" - echo "✓ Added to LXDE autostart" - fi - fi - - # 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" - - # 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..." - # Create a wrapper script that waits for desktop to be ready - CRON_WRAPPER="$SCRIPT_DIR/.start-player-cron.sh" - cat > "$CRON_WRAPPER" << 'EOF' -#!/bin/bash -# Wait for desktop environment to be ready -sleep 15 - -# Start the player -cd "$SCRIPT_DIR" && bash start.sh -EOF - sed -i "s|\$SCRIPT_DIR|$SCRIPT_DIR|g" "$CRON_WRAPPER" - chmod +x "$CRON_WRAPPER" - - # Add to crontab for the actual user - CRON_ENTRY="@reboot sleep 20 && $CRON_WRAPPER > /tmp/kivy-player-cron.log 2>&1" - if ! sudo -u "$ACTUAL_USER" crontab -l 2>/dev/null | grep -q "kivy-signage-player"; then - sudo -u "$ACTUAL_USER" sh -c "(crontab -l 2>/dev/null || true; echo '$CRON_ENTRY') | crontab -" 2>/dev/null || true - echo "✓ Cron fallback configured" - fi - - echo "" - echo "Autostart configuration methods:" - echo " 1. ✓ XDG Autostart Entry (~/.config/autostart/)" - if [ -f "$LXDE_AUTOSTART" ]; then - echo " 2. ✓ LXDE Autostart" - fi - echo " 3. ✓ Cron Fallback (@reboot)" - echo "" - echo "The player will start automatically when the desktop loads via XDG autostart." - echo "To verify: ls -la ~/.config/autostart/kivy-signage-player.desktop" - echo "" -} - -# Function to disable power-saving mode on Raspberry Pi -setup_raspberry_pi_power_management() { - echo "" - echo "==========================================" - echo "Raspberry Pi Detected - Configuring Power Management" - echo "==========================================" - - # Determine the actual user (if running with sudo) - ACTUAL_USER="${SUDO_USER:-$(whoami)}" - ACTUAL_HOME=$(eval echo ~"$ACTUAL_USER") - - # Disable HDMI power-saving - echo "Disabling HDMI screen power-saving..." - BOOT_CONFIG=$(find_boot_config) - if [ -n "$BOOT_CONFIG" ]; then - # Check if hdmi_blanking is already configured - if grep -q "hdmi_blanking" "$BOOT_CONFIG"; then - sudo sed -i 's/^hdmi_blanking=.*/hdmi_blanking=0/' "$BOOT_CONFIG" - else - echo "hdmi_blanking=0" | sudo tee -a "$BOOT_CONFIG" > /dev/null - fi - - # Disable HDMI CEC (can interfere with power management) - if grep -q "hdmi_ignore_cec_init" "$BOOT_CONFIG"; then - sudo sed -i 's/^hdmi_ignore_cec_init=.*/hdmi_ignore_cec_init=1/' "$BOOT_CONFIG" - else - echo "hdmi_ignore_cec_init=1" | sudo tee -a "$BOOT_CONFIG" > /dev/null - fi - - # Force HDMI mode to always be on - if grep -q "hdmi_force_hotplug" "$BOOT_CONFIG"; then - sudo sed -i 's/^hdmi_force_hotplug=.*/hdmi_force_hotplug=1/' "$BOOT_CONFIG" - else - echo "hdmi_force_hotplug=1" | sudo tee -a "$BOOT_CONFIG" > /dev/null - fi - - # Disable HDMI sleep mode - if grep -q "hdmi_ignore_edid" "$BOOT_CONFIG"; then - sudo sed -i 's/^hdmi_ignore_edid=.*/hdmi_ignore_edid=0xa5000080/' "$BOOT_CONFIG" - else - echo "hdmi_ignore_edid=0xa5000080" | sudo tee -a "$BOOT_CONFIG" > /dev/null - fi - - echo "✓ HDMI configuration locked in $BOOT_CONFIG" - echo " - HDMI blanking disabled" - echo " - HDMI CEC disabled" - echo " - HDMI hotplug forced" - echo " - HDMI sleep mode disabled" - fi - - # Disable screensaver and screen blanking in X11 - echo "Configuring display blanking settings..." - - # Create/update display configuration in home directory - XORG_DIR="/etc/X11/xorg.conf.d" - if [ -d "$XORG_DIR" ]; then - # Create display power management configuration - sudo tee "$XORG_DIR/99-no-blanking.conf" > /dev/null << 'EOF' -Section "ServerFlags" - Option "BlankTime" "0" - Option "StandbyTime" "0" - Option "SuspendTime" "0" - Option "OffTime" "0" -EndSection - -Section "InputClass" - Identifier "Disable DPMS" - MatchClass "DPMS" - Option "DPMS" "false" -EndSection -EOF - echo "✓ X11 display blanking disabled" - fi - - # Disable CPU power scaling (keep at max performance) - echo "Configuring CPU power management..." - - # Create systemd service to keep CPU at max frequency - sudo tee /etc/systemd/system/disable-cpu-powersave.service > /dev/null << 'EOF' -[Unit] -Description=Disable CPU Power Saving for Signage Player -After=network.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/bash -c 'for cpu in /sys/devices/system/cpu/cpu[0-9]*; do echo performance > "$cpu/cpufreq/scaling_governor" 2>/dev/null; done' -RemainAfterExit=yes - -[Install] -WantedBy=multi-user.target -EOF - - # Enable and start the service - sudo systemctl daemon-reload - sudo systemctl enable disable-cpu-powersave.service - sudo systemctl start disable-cpu-powersave.service - echo "✓ CPU power saving disabled (performance mode enabled)" - - # Disable sleep/suspend - echo "Disabling system sleep and suspend..." - sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null || true - echo "✓ System sleep/suspend disabled" - - # Disable screensaver via xset (if X11 is running) - if command -v xset &> /dev/null; then - # Create .xinitrc if it doesn't exist to disable screensaver - if [ ! -f "$ACTUAL_HOME/.xinitrc" ]; then - cat >> "$ACTUAL_HOME/.xinitrc" << 'EOF' -# Disable screen blanking and screensaver -xset s off -xset -dpms -xset s noblank -EOF - else - # Update existing .xinitrc if needed - if ! grep -q "xset s off" "$ACTUAL_HOME/.xinitrc"; then - cat >> "$ACTUAL_HOME/.xinitrc" << 'EOF' - -# Disable screen blanking and screensaver -xset s off -xset -dpms -xset s noblank -EOF - fi - fi - chown "$ACTUAL_USER:$ACTUAL_USER" "$ACTUAL_HOME/.xinitrc" 2>/dev/null || true - echo "✓ X11 screensaver disabled in $ACTUAL_HOME/.xinitrc" - fi - - # Create screen keep-alive wrapper script - echo "Creating screen keep-alive service..." - KEEPALIVE_SCRIPT="$SCRIPT_DIR/.keep-screen-alive.sh" - cat > "$KEEPALIVE_SCRIPT" << 'EOF' -#!/bin/bash -# Keep-screen-alive wrapper for player -# Prevents screen from locking/turning off while player is running - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Function to keep screen awake -keep_screen_awake() { - while true; do - # Move mouse slightly to prevent idle - if command -v xdotool &> /dev/null; then - xdotool mousemove_relative 1 1 - xdotool mousemove_relative -1 -1 - fi - - # Disable DPMS and screensaver periodically - if command -v xset &> /dev/null; then - xset s reset - xset dpms force on - fi - - sleep 30 - done -} - -# Function to inhibit systemd sleep (if available) -inhibit_sleep() { - if command -v systemd-inhibit &> /dev/null; then - # Run player under systemd inhibit to prevent sleep - systemd-inhibit --what=sleep --why="Signage player running" \ - bash "$SCRIPT_DIR/start.sh" - return $? - fi - return 1 -} - -# Try systemd inhibit first (most reliable) -if inhibit_sleep; then - exit 0 -fi - -# Fallback: Start keep-alive in background -keep_screen_awake & -KEEPALIVE_PID=$! - -# Start the player -cd "$SCRIPT_DIR" -bash start.sh -PLAYER_EXIT=$? - -# Kill keep-alive when player exits -kill $KEEPALIVE_PID 2>/dev/null || true - -exit $PLAYER_EXIT -EOF - - chmod +x "$KEEPALIVE_SCRIPT" - echo "✓ Screen keep-alive service created" - - # Create systemd service to keep HDMI powered on - # Note: Using 5-minute interval to avoid flickering on some displays - echo "Creating HDMI power control service..." - sudo tee /etc/systemd/system/hdmi-poweron.service > /dev/null << 'EOF' -[Unit] -Description=Keep HDMI Display Powered On -After=multi-user.target display-manager.service - -[Service] -Type=simple -ExecStart=/bin/bash -c 'while true; do /usr/bin/tvservice -p 2>/dev/null; sleep 300; done' -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -EOF - - sudo systemctl daemon-reload - sudo systemctl enable hdmi-poweron.service - sudo systemctl start hdmi-poweron.service - echo "✓ HDMI power control service enabled" - - # Create aggressive display keep-alive script - echo "Creating aggressive display keep-alive script..." - DISPLAY_KEEPALIVE="$SCRIPT_DIR/.display-keepalive.sh" - cat > "$DISPLAY_KEEPALIVE" << 'EOF' -#!/bin/bash -# Aggressive display keep-alive for Raspberry Pi -# Supports both X11 and Wayland environments - -DISPLAY_TIMEOUT=30 - -# Detect display server type -detect_display_server() { - if [ -n "$WAYLAND_DISPLAY" ]; then - echo "wayland" - elif [ -n "$DISPLAY" ]; then - echo "x11" - else - echo "unknown" - fi -} - -DISPLAY_SERVER=$(detect_display_server) - -while true; do - # Keep HDMI powered on (works for both X11 and Wayland) - if command -v tvservice &> /dev/null; then - /usr/bin/tvservice -p 2>/dev/null - fi - - if [ "$DISPLAY_SERVER" = "wayland" ]; then - # Wayland-specific power management - - # Method 1: Use wlr-randr for Wayland compositors (if available) - if command -v wlr-randr &> /dev/null; then - wlr-randr --output HDMI-A-1 --on 2>/dev/null || true - fi - - # Method 2: Prevent idle using systemd-inhibit - if command -v systemd-inhibit &> /dev/null; then - # This is already running, but refresh the lock - systemctl --user restart plasma-ksmserver.service 2>/dev/null || true - fi - - # Method 3: Use wlopm (Wayland output power management) - if command -v wlopm &> /dev/null; then - wlopm --on \* 2>/dev/null || true - fi - - # Method 4: Simulate activity via input (works on Wayland) - if command -v ydotool &> /dev/null; then - ydotool mousemove -x 1 -y 1 2>/dev/null || true - ydotool mousemove -x -1 -y -1 2>/dev/null || true - fi - - # Method 5: GNOME/KDE Wayland idle inhibit - if command -v gnome-session-inhibit &> /dev/null; then - # Already inhibited by running process - true - fi - - else - # X11-specific power management (original code) - if command -v xset &> /dev/null; then - DISPLAY=:0 xset s off 2>/dev/null - DISPLAY=:0 xset -dpms 2>/dev/null - DISPLAY=:0 xset dpms force on 2>/dev/null - DISPLAY=:0 xset s reset 2>/dev/null - fi - - # Move mouse to trigger activity - if command -v xdotool &> /dev/null; then - DISPLAY=:0 xdotool mousemove_relative 1 1 2>/dev/null - DISPLAY=:0 xdotool mousemove_relative -1 -1 2>/dev/null - fi - - # Disable monitor power saving - if command -v xrandr &> /dev/null; then - DISPLAY=:0 xrandr --output HDMI-1 --power-profile performance 2>/dev/null || true - fi - fi - - sleep $DISPLAY_TIMEOUT -done -EOF - - chmod +x "$DISPLAY_KEEPALIVE" - echo "✓ Display keep-alive script created" - - # Install Wayland tools if using Wayland - echo "Checking for Wayland and installing necessary tools..." - if [ -n "$WAYLAND_DISPLAY" ] || pgrep -x "wayfire\|sway\|weston\|mutter" > /dev/null 2>/dev/null; then - echo "Wayland detected - installing Wayland power management tools..." - - # List of optional Wayland tools (best-effort install) - WAYLAND_TOOLS="wlopm wlr-randr ydotool" - for tool in $WAYLAND_TOOLS; do - if ! command -v "$tool" &> /dev/null; then - echo "Installing $tool (if available)..." - sudo apt-get install -y "$tool" 2>/dev/null || echo "Note: $tool not available in package manager - skipping" - fi - done - - echo "✓ Wayland tools installation attempted" - else - echo "X11 detected - using xset/xdotool/xrandr tools" - fi - - # Update systemd screen keep-alive service to use this script - sudo tee /etc/systemd/system/screen-keepalive.service > /dev/null << EOF -[Unit] -Description=Keep Screen Awake During Signage Player -After=display-manager.service -PartOf=graphical-session.target - -[Service] -Type=simple -User=$ACTUAL_USER -ExecStart=/bin/bash $DISPLAY_KEEPALIVE -Restart=always -RestartSec=5 -Environment="DISPLAY=:0" -Environment="XAUTHORITY=%h/.Xauthority" - -[Install] -WantedBy=graphical-session.target -EOF - - sudo systemctl daemon-reload - sudo systemctl enable screen-keepalive.service - echo "✓ Aggressive screen keep-alive service enabled" - - echo "" - echo "⚠️ Note: Some changes require a system reboot to take effect." - echo "Please rerun this script after rebooting if power management issues persist." - echo "" -} - - -# ============================================================ -# Config & Deployment Functions -# ============================================================ - -# Function to write player configuration from CLI parameters -write_player_config() { - local config_dir="$SCRIPT_DIR/config" - local config_file="$config_dir/app_config.json" - - echo "" - echo "==========================================" - echo "Configuring Player Settings" - echo "==========================================" - - mkdir -p "$config_dir" - - # If --provision was given, fetch config from server - if [ -n "$PROVISION_URL" ]; then - echo "Fetching provisioning data from: $PROVISION_URL" - local response - response=$(curl -sSf "$PROVISION_URL" 2>&1 || true) - if [ -z "$response" ]; then - echo "⚠️ WARNING: Failed to fetch provisioning data from $PROVISION_URL" - echo " The player will start with setup screen on first boot." - echo " You can configure it manually via the touchscreen UI." - return 0 - fi - - # Write fetched config - echo "$response" > "$config_file" - echo "✓ Provisioning data saved to $config_file" - - # Extract key fields from response for display - local fetched_name - fetched_name=$(echo "$response" | python3 -c "import sys,json; print(json.load(sys.stdin).get('screen_name',''))" 2>/dev/null || echo "unknown") - echo " Provisioned screen: $fetched_name" - return 0 - fi - - # If --config was given, copy from file - if [ -n "$CONFIG_FILE_PATH" ]; then - if [ -f "$CONFIG_FILE_PATH" ]; then - cp "$CONFIG_FILE_PATH" "$config_file" - echo "✓ Configuration copied from $CONFIG_FILE_PATH" - return 0 - else - echo "⚠️ WARNING: Config file not found: $CONFIG_FILE_PATH" - fi - fi - - # Build config from individual CLI params if any were provided - if [ -n "$SERVER_IP" ] || [ -n "$SCREEN_NAME" ] || [ -n "$QUICKCONNECT_KEY" ]; then - # Export vars so Python subprocess can read them safely - export SERVER_IP SERVER_PORT SCREEN_NAME QUICKCONNECT_KEY - export ORIENTATION TOUCH_ENABLED MAX_RESOLUTION - export USE_HTTPS VERIFY_SSL EDIT_ENABLED - - # Use Python to merge existing config with CLI params (avoids bash quoting issues) - python3 << PYEOF -import json, os - -config_file = "$config_file" - -# Load existing config or start fresh -if os.path.exists(config_file): - with open(config_file) as f: - config = json.load(f) -else: - config = {} - -# CLI parameter values (passed via environment for safety) -cli_params = { - 'server_ip': os.environ.get('SERVER_IP', ''), - 'port': os.environ.get('SERVER_PORT', ''), - 'screen_name': os.environ.get('SCREEN_NAME', ''), - 'quickconnect_key': os.environ.get('QUICKCONNECT_KEY', ''), - 'orientation': os.environ.get('ORIENTATION', ''), - 'touch': os.environ.get('TOUCH_ENABLED', ''), - 'max_resolution': os.environ.get('MAX_RESOLUTION', ''), -} - -# Boolean flags need special handling -https_val = os.environ.get('USE_HTTPS', '') -verify_val = os.environ.get('VERIFY_SSL', '') -edit_val = os.environ.get('EDIT_ENABLED', '') - -for key, val in cli_params.items(): - if val: # Only override if a value was provided - config[key] = val - -# Handle boolean fields -if https_val: - config['use_https'] = https_val.lower() == 'true' -if verify_val: - config['verify_ssl'] = verify_val.lower() == 'false' # --no-verify-ssl sets to "false" -if edit_val: - config['edit_feature_enabled'] = edit_val.lower() == 'true' - -# Ensure defaults for any missing fields -config.setdefault('port', '8080') -config.setdefault('orientation', 'Landscape') -config.setdefault('touch', 'True') -config.setdefault('max_resolution', '1920x1080') -config.setdefault('edit_feature_enabled', False) -config.setdefault('use_https', False) -config.setdefault('verify_ssl', True) - -with open(config_file, 'w') as f: - json.dump(config, f, indent=2) - -print("✓ Configuration written to " + config_file) -PYEOF - fi - - # If no config at all, create a minimal placeholder - if [ ! -f "$config_file" ]; then - cat > "$config_file" << 'EOF' -{ - "server_ip": "", - "port": "8080", - "screen_name": "", - "quickconnect_key": "", - "orientation": "Landscape", - "touch": "True", - "max_resolution": "1920x1080", - "edit_feature_enabled": false, - "use_https": false, - "verify_ssl": true -} -EOF - echo "✓ Default config template created — please configure via touchscreen UI" - fi -} - -# Function to create required player directories -setup_player_directories() { - echo "" - echo "==========================================" - echo "Setting Up Player Directories" - echo "==========================================" - - mkdir -p "$SCRIPT_DIR/media" - mkdir -p "$SCRIPT_DIR/playlists" - mkdir -p "$SCRIPT_DIR/config/resources" - mkdir -p "$SCRIPT_DIR/logs" - - # Create a log rotation mechanism placeholder - touch "$SCRIPT_DIR/logs/.gitkeep" 2>/dev/null || true - - echo "✓ Player directories created:" - echo " - media/ (downloaded media files)" - echo " - playlists/ (synced playlists)" - echo " - config/resources/ (resource files)" - echo " - logs/ (player log output)" -} - -# Function to initialize player authentication with Digiserver -init_player_auth() { - echo "" - echo "==========================================" - echo "Initializing Player Authentication" - echo "==========================================" - - local config_file="$SCRIPT_DIR/config/app_config.json" - - if [ ! -f "$config_file" ]; then - echo "⚠️ No config file found — skipping authentication" - echo " Authentication will happen on first player run." - return 0 - fi - - # Check if we have enough config to authenticate - local server_ip - local screen_name - local quickconnect_key - server_ip=$(python3 -c "import json; print(json.load(open('$config_file')).get('server_ip',''))" 2>/dev/null || echo "") - screen_name=$(python3 -c "import json; print(json.load(open('$config_file')).get('screen_name',''))" 2>/dev/null || echo "") - quickconnect_key=$(python3 -c "import json; print(json.load(open('$config_file')).get('quickconnect_key',''))" 2>/dev/null || echo "") - - if [ -z "$server_ip" ] || [ -z "$screen_name" ] || [ -z "$quickconnect_key" ]; then - echo "⏸️ Incomplete config (server, name, or key missing)" - echo " Authentication will happen on first player run via the UI." - return 0 - fi - - echo "Attempting to register player with Digiserver..." - echo " Server: $server_ip" - echo " Screen: $screen_name" - - # Run auth using system Python - ( - cd "$SCRIPT_DIR/src" - python3 -c " -import json, sys, asyncio -sys.path.insert(0, '.') -from get_playlists_v2 import ensure_authenticated - -config_file = '$config_file' -with open(config_file) as f: - config = json.load(f) - -auth = ensure_authenticated(config) -if auth: - print('✅ Player registered with server successfully') - sys.exit(0) -else: - print('⚠️ Authentication pending — will retry on player startup') - sys.exit(1) -" 2>&1 || echo "⚠️ Auth initialization deferred to first player run" - ) -} - -# Function to optimize for background execution -optimize_background_execution() { - echo "" - echo "==========================================" - echo "Optimizing Background Execution" - echo "==========================================" - - # Create a nohup wrapper that ensures the player runs in background - # even if the terminal session ends - NOHUP_WRAPPER="$SCRIPT_DIR/.run-background.sh" - cat > "$NOHUP_WRAPPER" << 'EOF' -#!/bin/bash -# Background runner for Kiwy Signage Player -# Ensures the player keeps running even after SSH logout - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LOG_FILE="$SCRIPT_DIR/logs/player-background.log" - -# Ensure log directory exists -mkdir -p "$(dirname "$LOG_FILE")" - -# Log startup -echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting player in background..." >> "$LOG_FILE" - -# Navigate to project directory -cd "$SCRIPT_DIR" || exit 1 - -# Use nohup to ignore HUP signals -nohup bash start.sh >> "$LOG_FILE" 2>&1 & -PLAYER_PID=$! - -echo "[$(date '+%Y-%m-%d %H:%M:%S')] Player started with PID: $PLAYER_PID" >> "$LOG_FILE" -echo "Player started in background (PID: $PLAYER_PID)" -echo "Logs: $LOG_FILE" -EOF - - chmod +x "$NOHUP_WRAPPER" - echo "✓ Background runner created: .run-background.sh" - echo " Usage: bash .run-background.sh" - - # Verify autostart entry exists - local autostart_file="$ACTUAL_HOME/.config/autostart/kivy-signage-player.desktop" - if [ -f "$autostart_file" ]; then - echo "✓ Autostart entry already configured for background execution" - fi - - # Create a status-check alias for convenience - echo "" - echo "Background management commands:" - echo " bash .run-background.sh Start player in background" - echo " bash check_player_status.sh Check if player is running" - echo " bash stop_player.sh Stop the player" - echo "" -} - - -# ============================================================ -# CLI Argument Parsing -# ============================================================ -OFFLINE_MODE=false -SERVER_IP="" -SERVER_PORT="" -SCREEN_NAME="" -QUICKCONNECT_KEY="" -USE_HTTPS="" -VERIFY_SSL="" -ORIENTATION="" -TOUCH_ENABLED="" -MAX_RESOLUTION="" -EDIT_ENABLED="" -PROVISION_URL="" -CONFIG_FILE_PATH="" - -usage() { - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Installation modes (choose one):" - echo " --provision URL One-time provisioning URL from Digiserver (fetches all config)" - echo " --config FILE Path to pre-made JSON config file" - echo " (individual params) Set each config value manually" - echo "" - echo "Configuration options:" - echo " -s, --server IP Digiserver IP or hostname" - echo " -p, --port PORT Server port (default: 8080)" - echo " -n, --name NAME Screen/player name" - echo " -k, --key KEY Quick-connect / auth key" - echo " --https Enable HTTPS for server communication" - echo " --no-verify-ssl Disable SSL certificate verification" - echo " -o, --orientation Landscape or Portrait (default: Landscape)" - echo " --touch Enable touch input" - echo " -r, --resolution WxH Max resolution (default: 1920x1080)" - echo " --edit Enable on-device edit feature" - echo "" - echo "Installation options:" - echo " --offline Force offline installation from local packages" - echo " -q, --quiet Silent mode (minimal output)" - echo " -h, --help Show this help" - exit 0 -} - -while [ $# -gt 0 ]; do - case "$1" in - --provision) - PROVISION_URL="$2" - shift 2 - ;; - --config) - CONFIG_FILE_PATH="$2" - shift 2 - ;; - -s|--server) - SERVER_IP="$2" - shift 2 - ;; - -p|--port) - SERVER_PORT="$2" - shift 2 - ;; - -n|--name) - SCREEN_NAME="$2" - shift 2 - ;; - -k|--key) - QUICKCONNECT_KEY="$2" - shift 2 - ;; - --https) - USE_HTTPS="true" - shift - ;; - --no-verify-ssl) - VERIFY_SSL="false" - shift - ;; - -o|--orientation) - ORIENTATION="$2" - shift 2 - ;; - --touch) - TOUCH_ENABLED="True" - shift - ;; - -r|--resolution) - MAX_RESOLUTION="$2" - shift 2 - ;; - --edit) - EDIT_ENABLED="true" - shift - ;; - --offline) - OFFLINE_MODE=true - shift - ;; - -q|--quiet) - # Silent mode - redirect stdout to log file - exec > >(tee -a /tmp/kivy-install.log) 2>&1 - shift - ;; - -h|--help) - usage - ;; - *) - echo "Unknown option: $1" - usage - ;; - esac -done - -# Auto-detect offline mode if wheels exist -if [ "$OFFLINE_MODE" = false ] && [ -d "$WHEELS_DIR" ] && [ "$(ls -A "$WHEELS_DIR"/*.whl 2>/dev/null)" ]; then - OFFLINE_MODE=true -fi - -# ============================================================ -# Mode Banner -# ============================================================ -if [ "$OFFLINE_MODE" = true ]; then - echo "==========================================" - echo "📦 Offline Installation Mode" - echo "==========================================" -else - echo "==========================================" - echo "🌐 Online Installation Mode" - echo "==========================================" -fi - -echo "" -echo "Installing Kivy Signage Player dependencies..." -echo "" - -# Install system dependencies -echo "Step 1: Installing system dependencies..." -echo "--------------------" - -if [ "$OFFLINE_MODE" = true ] && [ -d "$DEB_DIR" ] && [ "$(ls -A $DEB_DIR/*.deb 2>/dev/null)" ]; then - # Offline: Install from local .deb files - echo "Installing from offline .deb packages..." - cd "$DEB_DIR" - - # Install all .deb files with dependencies - sudo dpkg -i *.deb 2>/dev/null || true - - # Fix any broken dependencies - sudo apt-get install -f -y || true - - # Also install Python packages via apt (may pull from cache) - sudo apt install -y python3-kivy python3-aiohttp python3-bcrypt python3-requests python3-evdev 2>/dev/null || true - - echo "System packages installed from offline repository" -else - # Online: Use apt-get - echo "Updating package lists..." - sudo apt update - - echo "Installing system packages..." - - # Read packages from file if available, otherwise use default list - if [ -f "$SYSTEM_DIR/apt-packages.txt" ]; then - PACKAGES=$(grep -v '^#' "$SYSTEM_DIR/apt-packages.txt" | grep -v '^$' | tr '\n' ' ') - sudo apt install -y $PACKAGES - else - # Default package list - sudo apt install -y python3-pip python3-setuptools python3-dev - sudo apt install -y libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev - sudo apt install -y libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev - sudo apt install -y zlib1g-dev ffmpeg libavcodec-extra - sudo apt install -y gstreamer1.0-plugins-base gstreamer1.0-plugins-good - - # Install Python packages system-wide via apt (pre-compiled for system Python) - echo "Installing Python packages via apt..." - sudo apt install -y \ - python3-kivy \ - python3-aiohttp \ - python3-bcrypt \ - python3-requests \ - python3-evdev - echo "✓ Python packages installed via apt" - - # Install ffpyplayer via pip (not available in apt) - echo "Installing ffpyplayer via pip..." - pip3 install --user ffpyplayer 2>/dev/null || pip3 install --user ffpyplayer --break-system-packages 2>/dev/null || echo "⚠️ ffpyplayer not available - video may use fallback" - fi - - echo "System packages installed successfully" -fi - -# Setup autostart workflow -setup_autostart - -# Check if running on Raspberry Pi and setup power management -if detect_raspberry_pi; then - setup_raspberry_pi_power_management -fi - -# Optimize video playback for Raspberry Pi -if detect_raspberry_pi; then - echo "" - echo "==========================================" - echo "Optimizing for Video Playback" - echo "==========================================" - - # Run video optimization script - if [ -f "$SCRIPT_DIR/.video-optimization.sh" ]; then - bash "$SCRIPT_DIR/.video-optimization.sh" - fi -fi - -# Create required player directories -setup_player_directories - -# Write player configuration -write_player_config - -# Initialize authentication with Digiserver -init_player_auth - -# Optimize for background execution -optimize_background_execution - -# ============================================================ -# Final Summary -# ============================================================ -echo "" -echo "==========================================" -echo "✅ Kivy Signage Player — Installation Complete!" -echo "==========================================" -echo "" -echo "📁 Installation directory: $SCRIPT_DIR" -echo "" - -# Read back the config to show applied settings -if [ -f "$SCRIPT_DIR/config/app_config.json" ]; then - echo "📋 Player Configuration:" - echo "------------------------" - python3 -c " -import json -with open('$SCRIPT_DIR/config/app_config.json') as f: - c = json.load(f) -print(f' Server: {c.get(\"server_ip\",\"not set\")}') -print(f' Port: {c.get(\"port\",\"8080\")}') -print(f' Screen: {c.get(\"screen_name\",\"not set\")}') -print(f' HTTPS: {c.get(\"use_https\",False)}') -print(f' Resolution: {c.get(\"max_resolution\",\"auto\")}') -" 2>/dev/null || echo " (config present)" - echo "" -fi - -echo "🚀 Quick Start Commands:" -echo "------------------------" -echo " bash start.sh Start with watchdog (auto-restart)" -echo " bash run_player.sh Run once (no auto-restart)" -echo " bash .run-background.sh Run in background (survives SSH logout)" -echo " bash check_player_status.sh Check if player is running" -echo " bash stop_player.sh Stop the player" -echo "" -echo "🔄 Autostart: The player will launch automatically on next desktop login." -echo "" -echo "📝 Logs: $SCRIPT_DIR/logs/" -echo "" - -# ============================================================ -# Reboot Prompt -# ============================================================ -echo "" -echo "==========================================" -echo "🔄 System Reboot Required" -echo "==========================================" -echo "" -echo "Autostart and power-saving settings are configured." -echo "A reboot is required to apply all changes." -echo "" - -# Auto-reboot with 5-second window to cancel -echo "" -echo "🔄 Rebooting in 5 seconds... Press Ctrl+C to cancel." -sleep 5 -echo "Rebooting now..." -sudo reboot diff --git a/media/edited_media/5/eye_e_v2.jpg b/media/edited_media/5/eye_e_v2.jpg deleted file mode 100644 index 0d0363e..0000000 Binary files a/media/edited_media/5/eye_e_v2.jpg and /dev/null differ diff --git a/playlists/server_playlist.json b/playlists/server_playlist.json deleted file mode 100644 index 830f531..0000000 --- a/playlists/server_playlist.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "count": 4, - "player_id": 1, - "player_name": "Test_player1", - "playlist": [ - { - "file_name": "sean-oulashin-KMn4VEeEPR8-unsplash.jpg", - "type": "image", - "url": "media/sean-oulashin-KMn4VEeEPR8-unsplash.jpg", - "duration": 14, - "edit_on_player": true - }, - { - "file_name": "weblink-1e4a4d6d885a", - "type": "weblink", - "url": "https://moto-adv.com/", - "duration": 30, - "edit_on_player": false - }, - { - "file_name": "sample-30s.mp4", - "type": "video", - "url": "media/sample-30s.mp4", - "duration": 31, - "edit_on_player": false - }, - { - "file_name": "edited_media/5/eye_e_v2.jpg", - "type": "image", - "url": "media/edited_media/5/eye_e_v2.jpg", - "duration": 50, - "edit_on_player": true - } - ], - "playlist_id": 1, - "playlist_version": 14 -} \ No newline at end of file diff --git a/repo/.gitignore b/repo/.gitignore index f6cf238..0660892 100644 --- a/repo/.gitignore +++ b/repo/.gitignore @@ -1,6 +1,10 @@ -# Ignore downloaded packages but keep directory structure -repo/python-wheels/*.whl -repo/python-wheels/*.tar.gz -repo/system-packages/debs/ -!repo/python-wheels/.gitkeep -!repo/system-packages/.gitkeep +# Patterns are relative to THIS file (the repo/ folder), so no "repo/" prefix. +# (The previous patterns used a "repo/" prefix and therefore never matched.) + +# Downloaded system .deb bundles are large and optional. +# Fetch on demand with: bash scripts/download_deb_packages.sh +# They are intentionally NOT committed. +system-packages/debs/ + +# NOTE: Python wheels under python-wheels/ ARE committed on purpose, so that a +# plain `git clone` ships a complete offline install bundle. diff --git a/repo/README.md b/repo/README.md index 2ee4f7c..37110ec 100644 --- a/repo/README.md +++ b/repo/README.md @@ -13,21 +13,23 @@ The installer script (`install.sh`) will automatically use this repo folder if a ### To populate this folder for offline installation: -Run the download script from the project root: +Run the download scripts from the project root: ```bash -bash download_offline_packages.sh +bash scripts/download_offline_packages.sh +bash scripts/download_deb_packages.sh ``` -This will download all necessary Python wheels and document system packages. +This will download all necessary Python wheels and system .deb packages. ## Contents -### Python Packages -- kivy==2.1.0 -- requests==2.32.4 +### Python Packages (see requirements.txt for the source of truth) +- kivy==2.3.1 +- requests==2.34.2 - bcrypt==4.2.1 -- aiohttp==3.9.1 -- asyncio==3.4.3 +- aiohttp==3.11.16 +- ffpyplayer==4.5.3 +- evdev==1.9.1 - All dependencies ### System Packages (APT) diff --git a/repo/python-wheels/attrs-25.4.0-py3-none-any.whl b/repo/python-wheels/attrs-25.4.0-py3-none-any.whl deleted file mode 100644 index 32a167a..0000000 Binary files a/repo/python-wheels/attrs-25.4.0-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/certifi-2025.10.5-py3-none-any.whl b/repo/python-wheels/certifi-2025.10.5-py3-none-any.whl deleted file mode 100644 index 00366e1..0000000 Binary files a/repo/python-wheels/certifi-2025.10.5-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/certifi-2025.11.12-py3-none-any.whl b/repo/python-wheels/certifi-2025.11.12-py3-none-any.whl deleted file mode 100644 index f7543ae..0000000 Binary files a/repo/python-wheels/certifi-2025.11.12-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl b/repo/python-wheels/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl deleted file mode 100644 index 9e96513..0000000 Binary files a/repo/python-wheels/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl and /dev/null differ diff --git a/repo/python-wheels/docutils-0.22.2-py3-none-any.whl b/repo/python-wheels/docutils-0.22.2-py3-none-any.whl deleted file mode 100644 index 8eb008a..0000000 Binary files a/repo/python-wheels/docutils-0.22.2-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/docutils-0.22.3-py3-none-any.whl b/repo/python-wheels/docutils-0.22.3-py3-none-any.whl deleted file mode 100644 index 477622e..0000000 Binary files a/repo/python-wheels/docutils-0.22.3-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/idna-3.11-py3-none-any.whl b/repo/python-wheels/idna-3.11-py3-none-any.whl deleted file mode 100644 index 28f2c10..0000000 Binary files a/repo/python-wheels/idna-3.11-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/pygments-2.19.2-py3-none-any.whl b/repo/python-wheels/pygments-2.19.2-py3-none-any.whl deleted file mode 100644 index 3ec3f10..0000000 Binary files a/repo/python-wheels/pygments-2.19.2-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/requests-2.32.4-py3-none-any.whl b/repo/python-wheels/requests-2.32.4-py3-none-any.whl deleted file mode 100644 index 11af60e..0000000 Binary files a/repo/python-wheels/requests-2.32.4-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/requests-2.32.5-py3-none-any.whl b/repo/python-wheels/requests-2.32.5-py3-none-any.whl deleted file mode 100644 index 2931062..0000000 Binary files a/repo/python-wheels/requests-2.32.5-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/typing_extensions-4.15.0-py3-none-any.whl b/repo/python-wheels/typing_extensions-4.15.0-py3-none-any.whl deleted file mode 100644 index 5fec9ca..0000000 Binary files a/repo/python-wheels/typing_extensions-4.15.0-py3-none-any.whl and /dev/null differ diff --git a/repo/python-wheels/urllib3-2.5.0-py3-none-any.whl b/repo/python-wheels/urllib3-2.5.0-py3-none-any.whl deleted file mode 100644 index b064a10..0000000 Binary files a/repo/python-wheels/urllib3-2.5.0-py3-none-any.whl and /dev/null differ diff --git a/run_player-afterclone.sh b/run_player-afterclone.sh new file mode 100755 index 0000000..10fe3fd --- /dev/null +++ b/run_player-afterclone.sh @@ -0,0 +1,347 @@ +#!/bin/bash +# +# run_player-afterclone.sh +# ============================================================ +# ONE-TIME post-clone setup for a cloned Raspberry Pi SD card. +# +# Problem it solves: +# The player identifies itself to DigiServer using the "screen_name" +# value in config/app_config.json (NOT the OS hostname). A cloned SD +# card carries the ORIGINAL card's screen_name + a cached auth token +# (src/player_auth.json). Two devices then fight over the same player +# session, so the cloned device never connects. +# +# What this script does: +# 1. Stops the running player/watchdog (so the token is not re-written) +# 2. Backs up the current identity/config files +# 3. Deletes the cached authentication token(s) -> forces re-auth +# 4. Sets a NEW unique screen_name and (optionally) server details +# 5. Leaves the player ready to boot and register as a BRAND-NEW player +# +# Usage: +# bash run_player-afterclone.sh [OPTIONS] +# +# Options: +# -n, --name NAME New unique screen/player name +# -s, --server IP Server IP / hostname +# -p, --port PORT Server port +# -k, --key KEY Quick-connect / auth key +# --https Enable HTTPS +# --no-https Disable HTTPS +# --verify-ssl Enable SSL certificate verification +# --no-verify-ssl Disable SSL certificate verification +# --set-hostname Also set the OS hostname to the new screen name +# -y, --yes Non-interactive; use args/defaults, no prompts +# --force Run even if this card was already set up +# -h, --help Show this help +# + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFIG_DIR="$SCRIPT_DIR/config" +CONFIG_FILE="$CONFIG_DIR/app_config.json" +VENV_DIR="$SCRIPT_DIR/.venv" +MARKER_FILE="$SCRIPT_DIR/.post-clone-setup.done" +BACKUP_ROOT="$SCRIPT_DIR/.post-clone-backups" +STAMP="$(date '+%Y%m%d-%H%M%S')" + +# ---- Defaults / CLI state ---- +NEW_NAME="" +NEW_SERVER="" +NEW_PORT="" +NEW_KEY="" +NEW_USE_HTTPS="" +NEW_VERIFY_SSL="" +SET_HOSTNAME=false +ASSUME_YES=false +FORCE=false + +# ---- Pretty printing ---- +info() { echo " $1"; } +ok() { echo " ✓ $1"; } +warn() { echo " ⚠️ $1"; } +err() { echo " ✗ $1"; } + +usage() { + cat <<'EOF' +run_player-afterclone.sh - one-time post-clone setup for a cloned SD card. + +Deletes the cached authentication token and configures a NEW unique +player identity so the card registers on the server as a new player. + +Usage: + bash run_player-afterclone.sh [OPTIONS] + +Options: + -n, --name NAME New unique screen/player name + -s, --server IP Server IP / hostname + -p, --port PORT Server port + -k, --key KEY Quick-connect / auth key + --https Enable HTTPS + --no-https Disable HTTPS + --verify-ssl Enable SSL certificate verification + --no-verify-ssl Disable SSL certificate verification + --set-hostname Also set the OS hostname to the new screen name + -y, --yes Non-interactive; use args/defaults, no prompts + --force Run even if this card was already set up + -h, --help Show this help +EOF + exit 0 +} + +# ---- Pick a python interpreter (prefer the venv, fall back to system) ---- +pick_python() { + if [ -x "$VENV_DIR/bin/python3" ]; then + echo "$VENV_DIR/bin/python3" + elif command -v python3 >/dev/null 2>&1; then + echo "python3" + else + echo "" + fi +} + +# ---- Parse arguments ---- +while [ $# -gt 0 ]; do + case "$1" in + -n|--name) NEW_NAME="$2"; shift 2 ;; + -s|--server) NEW_SERVER="$2"; shift 2 ;; + -p|--port) NEW_PORT="$2"; shift 2 ;; + -k|--key) NEW_KEY="$2"; shift 2 ;; + --https) NEW_USE_HTTPS="true"; shift ;; + --no-https) NEW_USE_HTTPS="false"; shift ;; + --verify-ssl) NEW_VERIFY_SSL="true"; shift ;; + --no-verify-ssl) NEW_VERIFY_SSL="false"; shift ;; + --set-hostname) SET_HOSTNAME=true; shift ;; + -y|--yes) ASSUME_YES=true; shift ;; + --force) FORCE=true; shift ;; + -h|--help) usage ;; + *) err "Unknown option: $1"; echo ""; usage ;; + esac +done + +echo "==========================================" +echo "Kiwy Signage - Post-Clone Setup" +echo "==========================================" +echo "" + +# ---- Safety: only run once per card (unless --force) ---- +if [ -f "$MARKER_FILE" ] && [ "$FORCE" != true ]; then + warn "This card looks already set up (marker: $(basename "$MARKER_FILE"))." + warn "Re-run with --force if you really want to reset it again." + echo "" + exit 1 +fi + +PYTHON_BIN="$(pick_python)" +if [ -z "$PYTHON_BIN" ]; then + err "python3 not found. Run install.sh first." + exit 1 +fi + +# ============================================================ +# 1) Stop the running player + watchdog so nothing re-writes the token +# ============================================================ +echo "▶ Stopping any running player/watchdog..." +pkill -f "bash.*start.sh" 2>/dev/null || true +pkill -f "python3 main.py" 2>/dev/null || true +sleep 2 +pkill -9 -f "bash.*start.sh" 2>/dev/null || true +pkill -9 -f "python3 main.py" 2>/dev/null || true +ok "Player stopped" +echo "" + +# ============================================================ +# 2) Read current config (for defaults / display) +# ============================================================ +CUR_NAME="" +CUR_SERVER="" +CUR_PORT="" +CUR_KEY="" +if [ -f "$CONFIG_FILE" ]; then + eval "$("$PYTHON_BIN" - "$CONFIG_FILE" <<'PYEOF' +import json, sys +try: + c = json.load(open(sys.argv[1])) +except Exception: + c = {} +def q(k): + v = c.get(k, "") + v = "" if v is None else str(v) + return v.replace("'", "'\\''") +print(f"CUR_NAME='{q('screen_name')}'") +print(f"CUR_SERVER='{q('server_ip')}'") +print(f"CUR_PORT='{q('port')}'") +print(f"CUR_KEY='{q('quickconnect_key')}'") +PYEOF +)" +fi + +OS_HOSTNAME="$(hostname 2>/dev/null || echo '')" + +# Sensible defaults: prefer an explicit new name, else the (changed) OS hostname, +# else the previous screen name. +DEF_NAME="$NEW_NAME" +[ -z "$DEF_NAME" ] && DEF_NAME="$OS_HOSTNAME" +[ -z "$DEF_NAME" ] && DEF_NAME="$CUR_NAME" +[ -z "$DEF_NAME" ] && DEF_NAME="player-$(date +%s | tail -c 5)" + +# ============================================================ +# 3) Prompt (unless -y / non-interactive) for the new identity +# ============================================================ +prompt_value() { + # $1 = prompt text, $2 = default + local answer + if [ "$ASSUME_YES" = true ] || [ ! -t 0 ]; then + echo "$2" + return + fi + read -r -p " $1 [$2]: " answer + echo "${answer:-$2}" +} + +echo "▶ New player identity" +info "Note: the server identifies this player by 'screen_name', not the OS hostname." +if [ -z "$NEW_NAME" ]; then + NEW_NAME="$(prompt_value "New unique screen/player name" "$DEF_NAME")" +fi +if [ -z "$NEW_SERVER" ]; then + NEW_SERVER="$(prompt_value "Server IP/hostname" "$CUR_SERVER")" +fi +if [ -z "$NEW_PORT" ]; then + NEW_PORT="$(prompt_value "Server port" "${CUR_PORT:-8080}")" +fi +if [ -z "$NEW_KEY" ]; then + NEW_KEY="$(prompt_value "Quick-connect key" "$CUR_KEY")" +fi +echo "" + +# ============================================================ +# 4) Back up current identity files +# ============================================================ +BACKUP_DIR="$BACKUP_ROOT/$STAMP" +mkdir -p "$BACKUP_DIR" +echo "▶ Backing up current identity files to:" +info "$BACKUP_DIR" +[ -f "$CONFIG_FILE" ] && cp -p "$CONFIG_FILE" "$BACKUP_DIR/" 2>/dev/null || true +[ -f "$SCRIPT_DIR/src/player_auth.json" ] && cp -p "$SCRIPT_DIR/src/player_auth.json" "$BACKUP_DIR/" 2>/dev/null || true +ok "Backup complete" +echo "" + +# ============================================================ +# 5) Delete cached authentication tokens (force re-authentication) +# ============================================================ +echo "▶ Removing cached authentication token(s)..." +REMOVED=0 +while IFS= read -r token; do + [ -z "$token" ] && continue + rm -f "$token" && REMOVED=$((REMOVED + 1)) && info "deleted: ${token#$SCRIPT_DIR/}" +done < <(find "$SCRIPT_DIR" -name 'player_auth.json' -not -path '*/.venv/*' -not -path "$BACKUP_ROOT/*" 2>/dev/null) +rm -f /tmp/temp_auth_test.json 2>/dev/null || true +rm -f "$SCRIPT_DIR/.player_heartbeat" 2>/dev/null || true +rm -f "$SCRIPT_DIR/.player_stop_requested" 2>/dev/null || true +if [ "$REMOVED" -eq 0 ]; then + ok "No cached token found (already clean)" +else + ok "Removed $REMOVED token file(s)" +fi +echo "" + +# ============================================================ +# 6) Write the new config (screen_name + server details) +# ============================================================ +echo "▶ Writing new player configuration..." +mkdir -p "$CONFIG_DIR" +CFG_PATH="$CONFIG_FILE" \ +NEW_SCREEN_NAME="$NEW_NAME" \ +NEW_SERVER_IP="$NEW_SERVER" \ +NEW_PORT="$NEW_PORT" \ +NEW_KEY="$NEW_KEY" \ +NEW_USE_HTTPS="$NEW_USE_HTTPS" \ +NEW_VERIFY_SSL="$NEW_VERIFY_SSL" \ +"$PYTHON_BIN" <<'PYEOF' +import json, os + +cfg_path = os.environ['CFG_PATH'] +try: + with open(cfg_path) as f: + cfg = json.load(f) +except Exception: + cfg = {} + +updates = { + 'screen_name': os.environ.get('NEW_SCREEN_NAME', ''), + 'server_ip': os.environ.get('NEW_SERVER_IP', ''), + 'port': os.environ.get('NEW_PORT', ''), + 'quickconnect_key': os.environ.get('NEW_KEY', ''), +} +for key, val in updates.items(): + if val != '': + cfg[key] = val + +if os.environ.get('NEW_USE_HTTPS', '') != '': + cfg['use_https'] = os.environ['NEW_USE_HTTPS'].lower() == 'true' +if os.environ.get('NEW_VERIFY_SSL', '') != '': + cfg['verify_ssl'] = os.environ['NEW_VERIFY_SSL'].lower() == 'true' + +# Safe defaults for anything missing +cfg.setdefault('port', '8080') +cfg.setdefault('orientation', 'Landscape') +cfg.setdefault('touch', 'True') +cfg.setdefault('max_resolution', '1920x1080') +cfg.setdefault('edit_feature_enabled', True) +cfg.setdefault('use_https', False) +cfg.setdefault('verify_ssl', False) + +with open(cfg_path, 'w') as f: + json.dump(cfg, f, indent=2) + f.write('\n') + +print(json.dumps(cfg, indent=2)) +PYEOF +ok "Configuration updated" +echo "" + +# ============================================================ +# 7) Optionally align the OS hostname with the screen name +# ============================================================ +if [ "$SET_HOSTNAME" = true ] && [ -n "$NEW_NAME" ]; then + SAFE_HOST="$(echo "$NEW_NAME" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed 's/-\+$//')" + echo "▶ Setting OS hostname to '$SAFE_HOST' (player identity is still 'screen_name')..." + if command -v hostnamectl >/dev/null 2>&1; then + sudo hostnamectl set-hostname "$SAFE_HOST" 2>/dev/null \ + && ok "OS hostname set to $SAFE_HOST" \ + || warn "Could not set OS hostname (needs sudo)" + else + echo "$SAFE_HOST" | sudo tee /etc/hostname >/dev/null 2>&1 \ + && ok "OS hostname file updated" \ + || warn "Could not write /etc/hostname (needs sudo)" + fi + warn "A reboot is recommended for the hostname change to fully apply." + echo "" +fi + +# ============================================================ +# 8) Mark the card as set up + summary +# ============================================================ +{ + echo "Post-clone setup completed: $(date '+%Y-%m-%d %H:%M:%S')" + echo "screen_name: $NEW_NAME" + echo "server_ip: $NEW_SERVER" + echo "port: $NEW_PORT" + echo "backup: $BACKUP_DIR" +} > "$MARKER_FILE" + +echo "==========================================" +echo "✓ Card is ready to connect to the server" +echo "==========================================" +echo "" +echo " Player name : $NEW_NAME" +echo " Server : $NEW_SERVER:$NEW_PORT" +echo " Backup : $BACKUP_DIR" +echo "" +echo " Next steps:" +echo " 1. (Optional) reboot: sudo reboot" +echo " 2. Start the player: ./start.sh" +echo "" +echo " The player will authenticate as a NEW player '$NEW_NAME'" +echo " and fetch its own playlist from the server." +echo "" diff --git a/run_player.sh b/run_player.sh index 3991e4f..f525df3 100644 --- a/run_player.sh +++ b/run_player.sh @@ -1,5 +1,16 @@ #!/bin/bash -# Start Kivy Signage Player -cd "$(dirname "$0")/src" -python3 main.py \ No newline at end of file +# Run Kivy Signage Player once (no watchdog / auto-restart) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Activate the virtual environment created by install.sh (holds Kivy + deps). +if [ -f "$SCRIPT_DIR/.venv/bin/activate" ]; then + # shellcheck disable=SC1091 + source "$SCRIPT_DIR/.venv/bin/activate" +else + echo "WARNING: virtual environment not found at $SCRIPT_DIR/.venv" + echo " Run 'bash install.sh' first for a fully offline-capable setup." +fi + +cd "$SCRIPT_DIR/src" +exec python3 main.py \ No newline at end of file diff --git a/working_files/download_deb_packages.sh b/scripts/download_deb_packages.sh similarity index 97% rename from working_files/download_deb_packages.sh rename to scripts/download_deb_packages.sh index 4a7abf1..1555456 100755 --- a/working_files/download_deb_packages.sh +++ b/scripts/download_deb_packages.sh @@ -6,7 +6,8 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SYSTEM_DIR="$SCRIPT_DIR/repo/system-packages" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +SYSTEM_DIR="$PROJECT_ROOT/repo/system-packages" DEB_DIR="$SYSTEM_DIR/debs" echo "==========================================" diff --git a/working_files/download_offline_packages.sh b/scripts/download_offline_packages.sh similarity index 95% rename from working_files/download_offline_packages.sh rename to scripts/download_offline_packages.sh index 26ac215..80e7040 100755 --- a/working_files/download_offline_packages.sh +++ b/scripts/download_offline_packages.sh @@ -6,7 +6,8 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$SCRIPT_DIR/repo" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +REPO_DIR="$PROJECT_ROOT/repo" WHEELS_DIR="$REPO_DIR/python-wheels" SYSTEM_DIR="$REPO_DIR/system-packages" @@ -26,7 +27,7 @@ fi # Download Python packages echo "Step 1: Downloading Python wheels..." echo "--------------------" -cd "$SCRIPT_DIR" +cd "$PROJECT_ROOT" # Check if pip is installed if ! command -v pip3 &> /dev/null; then diff --git a/src/__pycache__/get_playlists.cpython-311.pyc b/src/__pycache__/get_playlists.cpython-311.pyc deleted file mode 100644 index 7ab71b6..0000000 Binary files a/src/__pycache__/get_playlists.cpython-311.pyc and /dev/null differ diff --git a/src/player_auth.json b/src/player_auth.json deleted file mode 100644 index 78fed64..0000000 --- a/src/player_auth.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "hostname": "Birou_IT", - "auth_code": "CZncd_2dlTZGieBEdqAbUTjf3qNyEUPDXr8jLVx7NLs", - "player_id": 1, - "player_name": "Test_player1", - "playlist_id": 1, - "orientation": "Landscape", - "authenticated": true, - "server_url": "http://192.168.0.109:8080" -} \ No newline at end of file diff --git a/start.sh b/start.sh index 05e6d97..ca4953f 100755 --- a/start.sh +++ b/start.sh @@ -59,6 +59,13 @@ load_user_environment() { # Fallback: detect display manually log_message "Falling back to manual display detection..." + # Ensure XDG_RUNTIME_DIR is set. Wayland clients (and Kivy) need it, and a + # cron-launched instance may not have it. Default to the standard location. + if [ -z "$XDG_RUNTIME_DIR" ]; then + export XDG_RUNTIME_DIR="/run/user/$(id -u)" + log_message "Set XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" + fi + if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then # Try to detect Wayland display if [ -S "/run/user/$(id -u)/wayland-0" ]; then @@ -96,8 +103,27 @@ load_user_environment() { return 0 } -# Load the user environment early -load_user_environment +# Load the user environment early. If there is no usable display, exit WITHOUT +# taking the lock — otherwise a headless instance (e.g. cron before login) would +# block the real desktop-autostart instance from ever running. +if ! load_user_environment; then + log_message "No display server available — exiting so another instance can start" + exit 0 +fi + +# ============================================================ +# Single-instance guard (prevents parallel watchdogs) +# ============================================================ +# Only ONE watchdog may run at a time. If XDG autostart, the cron fallback, and +# a manual start all fire, the first one to reach this point wins; every other +# instance exits immediately instead of spawning a second player. +LOCK_FILE="$SCRIPT_DIR/.player_watchdog.lock" +exec 200>"$LOCK_FILE" +if ! flock -n 200; then + log_message "Another watchdog is already running (lock: $LOCK_FILE) — exiting" + exit 0 +fi +log_message "Single-instance lock acquired: $LOCK_FILE" # Function to configure display output to Full HD (1920x1080) configure_display_resolution() { diff --git a/stop_player.sh b/stop_player.sh index 4e94414..bedb53f 100755 --- a/stop_player.sh +++ b/stop_player.sh @@ -28,6 +28,7 @@ pkill -9 -f "python3 main.py" 2>/dev/null # Clean up heartbeat and stop flag files rm -f "$SCRIPT_DIR/.player_heartbeat" rm -f "$SCRIPT_DIR/.player_stop_requested" +rm -f "$SCRIPT_DIR/.player_watchdog.lock" echo "" echo "✓ Player and watchdog stopped" diff --git a/working_files/player_auth.json b/working_files/player_auth.json deleted file mode 100644 index 6ee814a..0000000 --- a/working_files/player_auth.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "hostname": "tv-terasa", - "auth_code": "iiSyZDLWGyqNIxeRt54XYREgvAio11RwwU1_oJev6WI", - "player_id": 1, - "player_name": "TV-acasa 1", - "playlist_id": 1, - "orientation": "Landscape", - "authenticated": true, - "server_url": "http://digi-signage.moto-adv.com" -} \ No newline at end of file