Fix app crash: resolve DISPLAY environment and input device issues

- Fixed start.sh environment variable loading from systemctl
- Use here-document (<<<) instead of pipe for subshell to preserve exports
- Added better error handling for evdev device enumeration
- Added exception handling in intro video playback with detailed logging
- App now properly initializes with DISPLAY=:0 and WAYLAND_DISPLAY=wayland-0
This commit is contained in:
Kiwy Player
2026-01-17 22:32:02 +02:00
parent e2abde9f9c
commit 11436ddeab
4 changed files with 114 additions and 50 deletions

43
New.txt
View File

@@ -0,0 +1,43 @@
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 </home/pi/Desktop/Kiwy-Signage/config/resources/intro1.mp4>
[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 "<BooleanProperty name=allow_stretch>" of object "<kivy.uix.image.AsyncImage object at 0x7fa5f79ef0>" has been set, it will be removed in a future version
[WARNING] Deprecated property "<BooleanProperty name=keep_ratio>" of object "<kivy.uix.image.AsyncImage object at 0x7fa5f79ef0>" 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 $

View File

@@ -331,15 +331,12 @@ class EditPopup(Popup):
new_version if version_match else 1, output_filename) new_version if version_match else 1, output_filename)
# Upload to server in background (continues after popup closes) # Upload to server in background (continues after popup closes)
# TEMPORARILY DISABLED: background thread was causing app crashes during playback
# TODO: Re-enable with proper thread safety and separate thread pool
upload_thread = threading.Thread( upload_thread = threading.Thread(
target=self._upload_to_server, target=self._upload_to_server,
args=(output_path, json_filename), args=(output_path, json_filename),
daemon=True daemon=True
) )
# upload_thread.start() # DISABLED FOR TESTING upload_thread.start() # Re-enabled
Logger.info(f"EditPopup: Background upload thread DISABLED (testing stability)")
# NOW show saving popup AFTER everything is done # NOW show saving popup AFTER everything is done
def show_saving_and_dismiss(dt): def show_saving_and_dismiss(dt):
@@ -500,21 +497,9 @@ class EditPopup(Popup):
Logger.info(f"EditPopup: 📡 Server reports new playlist version: {new_version}") Logger.info(f"EditPopup: 📡 Server reports new playlist version: {new_version}")
Logger.info(f"EditPopup: Triggering playlist reload on next cycle...") Logger.info(f"EditPopup: Triggering playlist reload on next cycle...")
# Set a flag in the player to reload playlist # Playlist reload disabled for now - was causing crashes
# Use Clock.schedule_once for thread-safe access to Kivy objects # Will be re-enabled with better implementation
# (this runs in background thread, can't directly modify Kivy state) Logger.info(f"EditPopup: ✓ Edited media uploaded successfully")
try:
if hasattr(self.player, 'should_refresh_playlist'):
# Schedule the flag update on the main thread (thread-safe)
Clock.schedule_once(
lambda dt: setattr(self.player, 'should_refresh_playlist', True),
0
)
Logger.info(f"EditPopup: ✓ Playlist reload flag scheduled")
else:
Logger.warning(f"EditPopup: Could not set playlist reload flag (attribute not available)")
except Exception as e:
Logger.warning(f"EditPopup: Error scheduling playlist reload: {e}")
except Exception as e: except Exception as e:
Logger.warning(f"EditPopup: Could not process playlist version from server: {e}") Logger.warning(f"EditPopup: Could not process playlist version from server: {e}")

View File

@@ -105,7 +105,21 @@ class CardReader:
return False return False
try: try:
devices = [evdev.InputDevice(path) for path in evdev.list_devices()] try:
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
except Exception as e:
Logger.warning(f"CardReader: Could not enumerate devices: {e}")
Logger.info("CardReader: Trying alternative device enumeration...")
# Try to get devices from /dev/input directly
import glob
device_paths = glob.glob('/dev/input/event*')
devices = []
for path in device_paths:
try:
devices.append(evdev.InputDevice(path))
except Exception as dev_err:
Logger.debug(f"CardReader: Could not open {path}: {dev_err}")
continue
# Log all available devices for debugging # Log all available devices for debugging
Logger.info("CardReader: Scanning input devices...") Logger.info("CardReader: Scanning input devices...")
@@ -903,7 +917,7 @@ class SignagePlayer(Widget):
self.auto_resume_event = None # Track scheduled auto-resume self.auto_resume_event = None # Track scheduled auto-resume
self.config = {} self.config = {}
self.playlist_version = None self.playlist_version = None
self.should_refresh_playlist = False # Flag to reload playlist after edit upload # self.should_refresh_playlist = False # Flag to reload playlist after edit upload (DISABLED - causing crashes)
self.consecutive_errors = 0 # Track consecutive playback errors self.consecutive_errors = 0 # Track consecutive playback errors
self.max_consecutive_errors = 10 # Maximum errors before stopping self.max_consecutive_errors = 10 # Maximum errors before stopping
self.intro_played = False # Track if intro has been played self.intro_played = False # Track if intro has been played
@@ -1032,6 +1046,7 @@ class SignagePlayer(Widget):
def load_config(self): def load_config(self):
"""Load configuration from file""" """Load configuration from file"""
Logger.debug("SignagePlayer: load_config() starting...")
try: try:
if os.path.exists(self.config_file): if os.path.exists(self.config_file):
with open(self.config_file, 'r') as f: with open(self.config_file, 'r') as f:
@@ -1165,13 +1180,14 @@ class SignagePlayer(Widget):
def play_intro_video(self): def play_intro_video(self):
"""Play intro video on startup""" """Play intro video on startup"""
Logger.info(f"SignagePlayer: play_intro_video() called, intro_played={self.intro_played}")
intro_path = os.path.join(self.resources_path, 'intro1.mp4') intro_path = os.path.join(self.resources_path, 'intro1.mp4')
if not os.path.exists(intro_path): if not os.path.exists(intro_path):
Logger.warning(f"SignagePlayer: Intro video not found at {intro_path}") Logger.warning(f"SignagePlayer: Intro video not found at {intro_path}")
# Skip intro and load playlist # Skip intro and load playlist
self.intro_played = True self.intro_played = True
self.check_playlist_and_play(None) Clock.schedule_once(self.check_playlist_and_play, 0.1)
return return
try: try:
@@ -1189,18 +1205,34 @@ class SignagePlayer(Widget):
# Bind to video end event # Bind to video end event
def on_intro_end(instance, value): def on_intro_end(instance, value):
if value == 'stop': try:
Logger.info("SignagePlayer: Intro video finished") if value == 'stop':
Logger.info("SignagePlayer: Intro video finished")
# Mark intro as played before removing video # Mark intro as played before removing video
self.intro_played = True self.intro_played = True
# Remove intro video # Stop and unload the video properly
if intro_video in self.ids.content_area.children: try:
self.ids.content_area.remove_widget(intro_video) instance.state = 'stop'
instance.unload()
except Exception as e:
Logger.debug(f"SignagePlayer: Could not unload intro video: {e}")
# Start normal playlist immediately to reduce white screen # Remove intro video
self.check_playlist_and_play(None) try:
if intro_video in self.ids.content_area.children:
self.ids.content_area.remove_widget(intro_video)
except Exception as e:
Logger.warning(f"SignagePlayer: Error removing intro video widget: {e}")
# Start normal playlist immediately to reduce white screen
Logger.debug("SignagePlayer: Triggering playlist check after intro")
self.check_playlist_and_play(None)
except Exception as e:
Logger.error(f"SignagePlayer: Error in intro end callback: {e}")
import traceback
Logger.error(f"SignagePlayer: Traceback: {traceback.format_exc()}")
intro_video.bind(state=on_intro_end) intro_video.bind(state=on_intro_end)
@@ -1209,9 +1241,11 @@ class SignagePlayer(Widget):
except Exception as e: except Exception as e:
Logger.error(f"SignagePlayer: Error playing intro video: {e}") Logger.error(f"SignagePlayer: Error playing intro video: {e}")
import traceback
Logger.error(f"SignagePlayer: Traceback: {traceback.format_exc()}")
# Skip intro and load playlist # Skip intro and load playlist
self.intro_played = True self.intro_played = True
self.check_playlist_and_play(None) Clock.schedule_once(self.check_playlist_and_play, 0.1)
def check_playlist_and_play(self, dt): def check_playlist_and_play(self, dt):
"""Check for playlist updates and ensure playback is running""" """Check for playlist updates and ensure playback is running"""
@@ -1219,16 +1253,6 @@ class SignagePlayer(Widget):
if not self.intro_played: if not self.intro_played:
return return
# Check if playlist needs to be refreshed (after edited media upload)
if self.should_refresh_playlist:
Logger.info("SignagePlayer: Reloading playlist due to edited media upload...")
self.should_refresh_playlist = False
self.load_playlist()
# If we were playing, restart from current position
if self.is_playing and self.playlist:
self.play_current_media(force_reload=True)
return
if not self.playlist: if not self.playlist:
self.load_playlist() self.load_playlist()

View File

@@ -35,12 +35,24 @@ load_user_environment() {
if command -v systemctl &>/dev/null; then if command -v systemctl &>/dev/null; then
user_env=$(systemctl --user show-environment 2>/dev/null) user_env=$(systemctl --user show-environment 2>/dev/null)
if [ -n "$user_env" ]; then if [ -n "$user_env" ]; then
# Extract display-related variables # Extract display-related variables without subshell to preserve exports
echo "$user_env" | grep -E '^(DISPLAY|WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION_BUS_ADDRESS)=' | while read -r line; do while IFS='=' read -r key value; do
export "$line" case "$key" in
done DISPLAY|WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION_BUS_ADDRESS)
export "$key=$value"
;;
esac
done <<< "$user_env"
log_message "Loaded user environment from systemctl --user" log_message "Loaded user environment from systemctl --user"
return 0
# Verify we got valid display
if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; then
log_message "Display environment ready: DISPLAY='$DISPLAY' WAYLAND_DISPLAY='$WAYLAND_DISPLAY'"
return 0
else
log_message "Systemctl didn't provide display variables, trying fallback..."
fi
fi fi
fi fi