diff --git a/backend/main.py b/backend/main.py
index b4ce74f..55625c9 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -774,6 +774,39 @@ async def api_supprimer_animation(nom: str):
return JSONResponse({"erreur": "Fichier introuvable"}, status_code=404)
+# --- API Photostation ---
+
+FLAG_PHOTOSTATION = Path("/tmp/photostation_running")
+PHOTOSTATION_DIR = Path("/opt/photostation")
+
+
+@app.get("/api/photostation/disponible")
+async def api_photostation_disponible():
+ disponible = (PHOTOSTATION_DIR / "src" / "main.py").exists()
+ return {"disponible": disponible}
+
+
+@app.post("/api/photostation/lancer")
+async def api_photostation_lancer():
+ if not (PHOTOSTATION_DIR / "src" / "main.py").exists():
+ return JSONResponse({"erreur": "Photostation non installée"}, status_code=404)
+ asyncio.create_task(_lancer_photostation())
+ return {"succes": True}
+
+
+async def _lancer_photostation():
+ FLAG_PHOTOSTATION.touch()
+ await asyncio.sleep(1)
+ # Fermer Chromium (la boucle kiosk attend le flag avant de le relancer)
+ await asyncio.create_subprocess_exec("pkill", "chromium")
+ await asyncio.sleep(2)
+ env = {**__import__("os").environ, "DISPLAY": ":0"}
+ cmd = f"source {PHOTOSTATION_DIR}/venv/bin/activate && python {PHOTOSTATION_DIR}/src/main.py --kiosk"
+ proc = await asyncio.create_subprocess_exec("bash", "-c", cmd, env=env)
+ await proc.wait()
+ FLAG_PHOTOSTATION.unlink(missing_ok=True)
+
+
# --- API Systeme ---
@app.post("/api/systeme/redemarrer")
diff --git a/frontend/css/style.css b/frontend/css/style.css
index bd6b011..4e462ea 100644
--- a/frontend/css/style.css
+++ b/frontend/css/style.css
@@ -135,6 +135,26 @@ html, body {
color: rgba(255,255,255,0.4);
}
+.btn-photostation {
+ position: absolute;
+ bottom: 12px;
+ left: 12px;
+ width: 56px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5rem;
+ color: rgba(255,255,255,0.15);
+ cursor: pointer;
+ border-radius: 50%;
+ transition: color 0.3s;
+}
+
+.btn-photostation:hover, .btn-photostation:active {
+ color: rgba(255,255,255,0.4);
+}
+
/* Popup mot de passe */
.popup-overlay {
position: fixed;
diff --git a/frontend/index.html b/frontend/index.html
index 977515a..c89fbd1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -42,8 +42,34 @@
⚙
+
+ 🖼
+
+
+
Choisissez votre mode
diff --git a/frontend/js/app.js b/frontend/js/app.js
index e606052..e934dae 100644
--- a/frontend/js/app.js
+++ b/frontend/js/app.js
@@ -15,6 +15,7 @@ async function init() {
setupModes();
setupOnglets();
chargerBoothInfo();
+ verifierPhotostationDisponible();
}
function appliquerConfig() {
@@ -137,6 +138,8 @@ function setupEcranAccueil() {
// --- Admin : icone + mot de passe ---
const MDP_ADMIN = '1234';
+const PIN_PHOTOSTATION = '1234';
+let _pinPhotostationSaisi = '';
function ouvrirAdmin() {
const popup = document.getElementById('popup-mdp');
@@ -161,6 +164,59 @@ function paveEffacer() {
input.value = input.value.slice(0, -1);
}
+// --- Photostation ---
+
+async function verifierPhotostationDisponible() {
+ const data = await apiGet('/api/photostation/disponible').catch(() => null);
+ if (data?.disponible) {
+ document.getElementById('btn-photostation').classList.remove('cache');
+ }
+}
+
+function ouvrirPhotostation() {
+ _pinPhotostationSaisi = '';
+ _majAffichagePin();
+ document.getElementById('pin-photostation-erreur').classList.add('cache');
+ document.getElementById('popup-pin-photostation').classList.remove('cache');
+}
+
+function fermerPopupPhotostation() {
+ document.getElementById('popup-pin-photostation').classList.add('cache');
+ _pinPhotostationSaisi = '';
+}
+
+function pinPhotostationTouche(c) {
+ if (_pinPhotostationSaisi.length >= 4) return;
+ _pinPhotostationSaisi += c;
+ _majAffichagePin();
+ if (_pinPhotostationSaisi.length === 4) {
+ setTimeout(_validerPinPhotostation, 150);
+ }
+}
+
+function pinPhotostationEffacer() {
+ _pinPhotostationSaisi = _pinPhotostationSaisi.slice(0, -1);
+ _majAffichagePin();
+}
+
+function _majAffichagePin() {
+ const n = _pinPhotostationSaisi.length;
+ const el = document.getElementById('pin-photostation-affichage');
+ el.textContent = '●'.repeat(n) + '○'.repeat(4 - n);
+}
+
+async function _validerPinPhotostation() {
+ if (_pinPhotostationSaisi !== PIN_PHOTOSTATION) {
+ document.getElementById('pin-photostation-erreur').classList.remove('cache');
+ _pinPhotostationSaisi = '';
+ _majAffichagePin();
+ return;
+ }
+ fermerPopupPhotostation();
+ afficherStatut('Lancement de la station d\'impression...', 'succes');
+ await apiPost('/api/photostation/lancer', {});
+}
+
function validerMdpAdmin() {
const input = document.getElementById('input-mdp-admin');
if (input.value === MDP_ADMIN) {
diff --git a/scripts/install-photostation.sh b/scripts/install-photostation.sh
new file mode 100644
index 0000000..0f6280a
--- /dev/null
+++ b/scripts/install-photostation.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Installe la photostation sur la Surface pour intégration photobooth
+set -e
+
+DEST="/opt/photostation"
+REPO="https://git.copydev.fr/jules/photostation.git"
+
+echo "=== Installation Photostation ==="
+
+# Dépendances système
+sudo apt-get install -y \
+ python3-pyqt6 python3-pyqt6.qtsvg \
+ python3-pillow python3-flask \
+ python3-qrcode python3-cups \
+ libgl1 libglib2.0-0 2>&1 | grep -E 'Install|Upgrade|already'
+
+# Cloner ou mettre à jour
+if [ -d "$DEST/.git" ]; then
+ echo "Mise à jour du repo..."
+ git -C "$DEST" pull
+else
+ echo "Clonage du repo..."
+ sudo git clone "$REPO" "$DEST"
+ sudo chown -R jules:jules "$DEST"
+fi
+
+# Venv avec accès aux paquets système (PyQt6 est en paquet système)
+if [ ! -d "$DEST/venv" ]; then
+ python3 -m venv --system-site-packages "$DEST/venv"
+fi
+
+# Dépendances Python supplémentaires
+"$DEST/venv/bin/pip" install --quiet qrcode[pil] 2>/dev/null || true
+
+echo ""
+echo "=== Installation terminée ==="
+echo "Test : DISPLAY=:0 bash -c 'source $DEST/venv/bin/activate && python $DEST/src/main.py'"
diff --git a/scripts/kiosk-session.sh b/scripts/kiosk-session.sh
index 3de7372..cfdd69b 100644
--- a/scripts/kiosk-session.sh
+++ b/scripts/kiosk-session.sh
@@ -22,7 +22,9 @@ for i in $(seq 1 15); do
done
# Chromium en boucle : redémarre automatiquement s'il plante ou se ferme
+# Attend si la photostation est en cours d'exécution
while true; do
+ while [ -f /tmp/photostation_running ]; do sleep 1; done
chromium \
--kiosk \
--noerrdialogs \