From 0d24c8af265bb9a6d69d9362f6fed3bd74bb7eef Mon Sep 17 00:00:00 2001 From: Jules Date: Thu, 9 Apr 2026 07:46:39 +0200 Subject: [PATCH] Kiosk PyQt6 WebEngine : alternative navigateur, process unique, auto-reload --- scripts/install-kiosk-qt.sh | 34 ++++++++++++++ scripts/kiosk-qt.py | 91 +++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 scripts/install-kiosk-qt.sh create mode 100644 scripts/kiosk-qt.py diff --git a/scripts/install-kiosk-qt.sh b/scripts/install-kiosk-qt.sh new file mode 100644 index 0000000..f7b5823 --- /dev/null +++ b/scripts/install-kiosk-qt.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Installe PyQt6 WebEngine et configure le service kiosk Qt +set -e + +echo "=== Installation PyQt6 WebEngine ===" +pip3 install PyQt6 PyQt6-WebEngine --break-system-packages + +echo "=== Creation service systemd ===" +cat > /etc/systemd/system/photobooth-kiosk-qt.service << EOF +[Unit] +Description=Photobooth Kiosk Qt +After=graphical-session.target photobooth.service +Wants=photobooth.service + +[Service] +Type=simple +User=$(logname) +Environment=DISPLAY=:0 +Environment=XAUTHORITY=/home/$(logname)/.Xauthority +ExecStart=/usr/bin/python3 /home/$(logname)/photobooth/scripts/kiosk-qt.py +Restart=always +RestartSec=3 + +[Install] +WantedBy=graphical-session.target +EOF + +systemctl daemon-reload +systemctl enable photobooth-kiosk-qt + +echo "=== Pour basculer sur le kiosk Qt ===" +echo " sudo systemctl stop photobooth-kiosk" +echo " sudo systemctl disable photobooth-kiosk" +echo " sudo systemctl start photobooth-kiosk-qt" diff --git a/scripts/kiosk-qt.py b/scripts/kiosk-qt.py new file mode 100644 index 0000000..c7b7f70 --- /dev/null +++ b/scripts/kiosk-qt.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +""" +Kiosk PyQt6 WebEngine pour photobooth. +Alternative au browser : process unique, pas de "already running", restart propre. +Usage : python3 scripts/kiosk-qt.py +""" + +import sys +import time +import subprocess +from PyQt6.QtCore import Qt, QUrl, QTimer +from PyQt6.QtWidgets import QApplication +from PyQt6.QtWebEngineWidgets import QWebEngineView +from PyQt6.QtWebEngineCore import QWebEngineSettings + + +BACKEND_URL = "http://localhost:8080" +RETRY_INTERVAL_MS = 3000 + + +def wait_backend(url: str, timeout: int = 30) -> bool: + """Attend que le backend soit dispo avant d'afficher.""" + import urllib.request + for _ in range(timeout): + try: + urllib.request.urlopen(url, timeout=1) + return True + except Exception: + time.sleep(1) + return False + + +class KioskWindow(QWebEngineView): + def __init__(self): + super().__init__() + + # Plein ecran sans decoration + self.setWindowFlags(Qt.WindowType.FramelessWindowHint) + self.showFullScreen() + + # Parametres WebEngine + settings = self.settings() + settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True) + settings.setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, True) + settings.setAttribute(QWebEngineSettings.WebAttribute.TouchIconsEnabled, True) + settings.setAttribute(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, True) + + # Cacher le curseur + self.setCursor(Qt.CursorShape.BlankCursor) + + self.setUrl(QUrl(BACKEND_URL)) + + # Watchdog : recharge si la page ne repond plus (WebSocket coupe) + self._watchdog = QTimer(self) + self._watchdog.setInterval(10000) + self._watchdog.timeout.connect(self._check_backend) + self._watchdog.start() + + def _check_backend(self): + import urllib.request + try: + urllib.request.urlopen(BACKEND_URL, timeout=2) + except Exception: + self.reload() + + def keyPressEvent(self, event): + # F5 = reload, F11 = fullscreen toggle, Echap ignoré + if event.key() == Qt.Key.Key_F5: + self.reload() + elif event.key() == Qt.Key.Key_F11: + if self.isFullScreen(): + self.showNormal() + else: + self.showFullScreen() + + +def main(): + app = QApplication(sys.argv) + app.setApplicationName("Photobooth Kiosk") + + # Attendre que le backend soit pret + if not wait_backend(BACKEND_URL): + print("Backend non disponible apres 30s", file=sys.stderr) + sys.exit(1) + + window = KioskWindow() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main()