Tentative pour rendre l'Ui moins bloquante

This commit is contained in:
2026-03-26 20:42:59 +01:00
parent b56ac5d996
commit 1ba0ea6bd6
3 changed files with 211 additions and 104 deletions
+8 -8
View File
@@ -4,22 +4,22 @@ from PySide6.QtWidgets import QMainWindow
class WindowDragger:
# Permet de déplacer une fenêtre sans barre de titre.
def __init__(self, window: QMainWindow):
self._window = window
self._drag_pos = None
def mouse_press(self, event: QtGui.QMouseEvent) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self._drag_pos = (
event.globalPosition().toPoint()
- self._window.frameGeometry().topLeft()
)
# On stocke le vecteur entre le clic et le coin haut-gauche de la fenêtre
self._drag_pos = event.globalPosition().toPoint() - self._window.frameGeometry().topLeft()
event.accept() # On informe Qt que l'event est géré
def mouse_move(self, event: QtGui.QMouseEvent) -> None:
if event.buttons() & Qt.MouseButton.LeftButton and self._drag_pos is not None:
# Vérification stricte du bouton gauche ET de l'existence du point d'ancrage
if (event.buttons() & Qt.MouseButton.LeftButton) and self._drag_pos is not None:
self._window.move(event.globalPosition().toPoint() - self._drag_pos)
event.accept()
def mouse_release(self, _event) -> None:
def mouse_release(self, event: QtGui.QMouseEvent) -> None:
self._drag_pos = None
event.accept()
+25 -2
View File
@@ -3,9 +3,10 @@ import sys
import os
import tempfile
from pathlib import Path
from tools.utils import get_internal_dir
from PySide6.QtCore import QResource
from PySide6.QtGui import QFontDatabase, QFont, QIcon
from PySide6.QtWidgets import QApplication
@@ -30,7 +31,6 @@ if getattr(sys, 'frozen', False):
# Bundle path resolution
# ---------------------------------------------------------------------------
bundle_dir = get_internal_dir()
QResource.registerResource(f"{bundle_dir}/resources.py")
# ---------------------------------------------------------------------------
# Fix barre des tâches Windows
@@ -38,6 +38,29 @@ QResource.registerResource(f"{bundle_dir}/resources.py")
if sys.platform.startswith("win"):
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("LaTaniere.Launcher.1")
# ---------------------------------------------------------------------------
# Setup
# ---------------------------------------------------------------------------
def setup_environment(app, bundle_dir):
# Utilisation de pathlib pour la robustesse
style_path = Path(bundle_dir) / "styles" / "styles.qss"
# Tentative de chargement du style
if style_path.exists():
try:
app.setStyleSheet(style_path.read_text(encoding="utf-8"))
except Exception as e:
print(f"⚠️ Impossible de lire le fichier de style: {e}")
# Tentative de chargement de la police
try:
font_family = load_custom_font()
app.setFont(QFont(font_family, 16))
except Exception as e:
print(f"⚠️ Police personnalisée non chargée, repli sur la police système: {e}")
app.setFont(QFont("Segoe UI", 11)) # Fallback standard
# ---------------------------------------------------------------------------
# Font helper
# ---------------------------------------------------------------------------
+164 -80
View File
@@ -22,6 +22,7 @@ from fivemserver.whitelistmanager import WhiteList
from fivemserver.fivemlauncher import FiveMLauncher
from fivemserver.queuemanager import QueueManager
from fivemserver.get_server_token import GetServerTokenForDiscord
from fivemserver.auth_worker import AuthWorker
from fake_patch_notes import patch_note
# For Linux Wayland to authorize moving window
@@ -37,6 +38,8 @@ class MainWindow(QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.auth_worker = None # <--- Pour stocker l'instance du thread
self.config = config_manager
self.stored_user_id = self.config.get_discord_user()
self.queue_thread = None
@@ -45,6 +48,15 @@ class MainWindow(QMainWindow):
self.countdown_timer = None
self.remaining_time = 0 # en secondes
# Préparation du timer de fermeture finale
self.close_timer = QTimer(self)
self.close_timer.setSingleShot(True)
self.close_timer.timeout.connect(self.close)
# Préparation du timer de mise à jour visuelle (1s)
self.countdown_timer = QTimer(self)
self.countdown_timer.timeout.connect(self._update_countdown)
# UI
# self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow_vertical_pager.ui", self)
#self.setCentralWidget(self.ui.centralWidget())
@@ -158,46 +170,29 @@ class MainWindow(QMainWindow):
# ------------------------------------------------------------------
def _on_connexion(self) -> None:
try:
"""Appelée lors du clic sur le bouton connexion."""
# 1. Sécurités de base
if not self.stored_user_id or self.stored_user_id.isspace():
show_qt_error(
self,
"Connexion impossible",
"Aucun identifiant Discord n'est disponible. Merci de passer par la connexion Discord."
)
show_qt_error(self, "Connexion impossible", "Identifiant Discord absent.")
return
self._ensure_server_session()
if not PlayerServerInfo.session_id:
PlayerServerInfo.session_id = GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
GetServerTokenForDiscord.register_discord_user(
self.stored_user_id,
PlayerServerInfo.session_id,
)
if self.queue_position_value is None:
self.start_queue()
# 2. Si on a déjà une session valide, on passe directement à la suite
if PlayerServerInfo.session_id:
self._proceed_to_queue_or_launch()
return
if self.queue_position_value != 0:
show_qt_error(
self,
"Connexion en attente",
f"Tu dois attendre ton tour.\n\nPosition actuelle : {self.queue_position_value}"
)
return
FiveMLauncher.launch()
# on disable le bouton connexion une fois fivem lancé.
# 3. Sinon, on lance l'authentification en arrière-plan
# Verrouillage de l'UI pour éviter le double-clic
self.ui.connexion_btn.setEnabled(False)
self.ui.connexion_btn.setText("Authentification...")
QGuiApplication.setOverrideCursor(Qt.WaitCursor) # Curseur de chargement
# fermer l'application après 60 secondes (60000 ms)
self.schedule_close()
except ApiError as exc:
show_qt_error(self, "Connexion impossible", f"Erreur lors de la connexion.\n\n{exc}")
# Création et lancement du worker
self.auth_worker = AuthWorker(self.stored_user_id)
self.auth_worker.finished.connect(self._on_auth_finished)
self.auth_worker.finished.connect(self.auth_worker.deleteLater) # Nettoyage automatique
self.auth_worker.start()
@staticmethod
def _on_discord() -> None:
@@ -209,42 +204,75 @@ class MainWindow(QMainWindow):
def _on_discord_auth_btn(self) -> None:
try:
# 1. Récupération OAuth (souvent via navigateur, assez rapide)
test = discord_oauth.get_discord_user_id()
self.config.set_discord_user(test[0])
PlayerServerInfo.session_id = test[1]
self.stored_user_id = test[0]
user_id = test[0]
token = test[1]
# 2. Mise à jour locale immédiate
self.config.set_discord_user(user_id)
self.stored_user_id = user_id
PlayerServerInfo.session_id = token
self.config.save()
self._ensure_server_session()
# 3. Lancement du Worker pour la partie réseau (Enregistrement serveur)
# On verrouille l'UI pour éviter les clics multiples
self.ui.discord_auth_btn.setEnabled(False)
QGuiApplication.setOverrideCursor(Qt.WaitCursor)
self.auth_worker = AuthWorker(self.stored_user_id)
# On connecte le signal de fin à une nouvelle méthode dédiée
self.auth_worker.finished.connect(self._on_discord_auth_finished)
self.auth_worker.finished.connect(self.auth_worker.deleteLater)
self.auth_worker.start()
except ApiError as exc:
show_qt_error(self, "Connexion Discord", f"Impossible de récupérer ton compte Discord.\n\n{exc}")
except Exception as exc:
show_qt_error(self, "Erreur", f"Une erreur inattendue est survenue : {exc}")
def _on_discord_auth_finished(self, success: bool, session_id: str, error_message: str):
"""Callback après l'enregistrement suite à une auth Discord."""
QGuiApplication.restoreOverrideCursor()
self.ui.discord_auth_btn.setEnabled(True)
if success:
try:
# Maintenant que la session est OK côté serveur, on vérifie la Whitelist
WhiteList.check_whitelist(Urls.API_URL.value, self.stored_user_id)
if PlayerServerInfo.is_whitelist:
self.start_queue()
self.ui.queue_lbl.show()
self.ui.queue_position.show()
self.ui.stackedWidget.setCurrentIndex(0)
self.ui.stackedWidget.setCurrentIndex(0) # Retour page principale
else:
self.ui.stackedWidget.setCurrentIndex(2)
self.ui.stackedWidget.setCurrentIndex(2) # Page non-whitelisté
except ApiError as exc:
show_qt_error(self, "La Tanière", f"Impossible de vérifier la whitelist.\n\n{exc}")
else:
show_qt_error(self, "Erreur Serveur",
f"L'authentification a réussi mais l'enregistrement a échoué.\n\n{error_message}")
except ApiError as exc:
show_qt_error(self, "Connexion Discord", f"Impossible de récupérer ton compte Discord.\n\n{exc}")
# ------------------------------------------------------------------
# Mouse events → délégués au WindowDragger
# ------------------------------------------------------------------
def mousePressEvent(self, event: QMouseEvent) -> None:
# On délègue au dragger
self._dragger.mouse_press(event)
# On ne remonte pas au parent si on a déjà "accepté" l'event
if not event.isAccepted():
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
self._dragger.mouse_move(event)
if not event.isAccepted():
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
self._dragger.mouse_release(event)
if not event.isAccepted():
super().mouseReleaseEvent(event)
# ------------------------------------------------------------------
@@ -261,6 +289,12 @@ class MainWindow(QMainWindow):
def cleanup(self):
"""À appeler lors de la fermeture de la fenêtre principale"""
if self.close_timer:
self.close_timer.stop()
if self.countdown_timer:
self.countdown_timer.stop()
if hasattr(self, '_sound'):
self._sound.stop()
if hasattr(self, '_temp_mp3'):
@@ -281,55 +315,46 @@ class MainWindow(QMainWindow):
# ------------------------------------------------------------------
# Schedule de fermeture du launcher
# ------------------------------------------------------------------
def schedule_close(self, delay: int = 60000):
# Normalisation du délai
if not isinstance(delay, int) or delay <= 0:
delay = 60000
def schedule_close(self, delay_ms: int = 60000):
"""Lance ou redémarre le compte à rebours de fermeture."""
# 1. Sécurité sur les entrées
if not isinstance(delay_ms, int) or delay_ms <= 0:
delay_ms = 60000
# Conversion en secondes
self.remaining_time = delay // 1000
# 2. Reset du temps restant (conversion ms -> s)
self.remaining_time = delay_ms // 1000
# 🔁 Annule les timers existants
if self.close_timer:
# 3. On stop les timers s'ils tournaient déjà (évite les doublons)
self.close_timer.stop()
self.close_timer.deleteLater()
if self.countdown_timer:
self.countdown_timer.stop()
self.countdown_timer.deleteLater()
# ⏱ Timer de fermeture
self.close_timer = QTimer(self)
self.close_timer.setSingleShot(True)
self.close_timer.timeout.connect(self.close)
self.close_timer.start(delay)
# ⏳ Timer de countdown (1 seconde)
self.countdown_timer = QTimer(self)
self.countdown_timer.timeout.connect(self._update_countdown)
# 4. On lance
self.close_timer.start(delay_ms)
self.countdown_timer.start(1000)
# Affichage initial
self._update_countdown()
# 5. Mise à jour immédiate de l'affichage (évite d'attendre 1s)
self._update_countdown_display()
# on affiche un compteur avant fermeture automatique
def _update_countdown(self):
"""Appelée toutes les secondes par le countdown_timer."""
self.remaining_time -= 1
if self.remaining_time <= 0:
if self.countdown_timer:
self.countdown_timer.stop()
return
minutes = self.remaining_time // 60
seconds = self.remaining_time % 60
self.ui.queue_lbl.setText(f"⏳ Fermeture dans {minutes:02d}:{seconds:02d}")
self._update_countdown_display()
self.remaining_time -= 1
def _update_countdown_display(self):
"""Met à jour le texte dans l'interface."""
minutes = max(0, self.remaining_time // 60)
seconds = max(0, self.remaining_time % 60)
self.ui.queue_lbl.setText(f"⏳ Fermeture dans {minutes:02d}:{seconds:02d}")
# ------------------------------------------------------------------
# Queue managment
# ------------------------------------------------------------------
def start_queue(self):
self.queue_thread = QueueThread(self.stored_user_id)
self.queue_thread = QueueThread(self.stored_user_id, parent=self) # ← parent=self
self.queue_thread.update.connect(self.handle_update)
self.queue_thread.start()
@@ -360,22 +385,81 @@ class MainWindow(QMainWindow):
def launch_fivem(self):
pass
def _ensure_server_session(self) -> None:
"""
Garantit que le serveur connaît bien le session_id courant.
Le fait d'avoir une valeur locale ne suffit pas : il faut aussi
la resynchroniser côté serveur.
"""
def _ensure_server_session(self) -> bool:
if not self.stored_user_id or self.stored_user_id.isspace():
raise ApiError("Aucun identifiant Discord disponible.")
return False
if not PlayerServerInfo.session_id:
PlayerServerInfo.session_id = GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
try:
# 1. Si on a déjà la session, on ne fait rien
if PlayerServerInfo.session_id:
return True
# 2. Authentification (avec protection timeout)
# On suppose que authenticate renvoie le token
token = GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
if token:
PlayerServerInfo.session_id = token
# 3. L'enregistrement est souvent la partie qui bloque (SSL handshake)
# On l'entoure d'un try spécifique
try:
GetServerTokenForDiscord.register_discord_user(
self.stored_user_id,
PlayerServerInfo.session_id,
)
return True
except Exception as reg_err:
print(f"Erreur lors du register: {reg_err}")
return False
return False
except Exception as e:
print(f"Erreur globale session: {e}")
return False
def _on_auth_finished(self, success: bool, session_id: str, error_message: str):
"""Appelée quand le AuthWorker a terminé."""
# 1. Restauration de l'UI
QGuiApplication.restoreOverrideCursor()
self.ui.connexion_btn.setEnabled(True)
self.ui.connexion_btn.setText("SE CONNECTER") # Remettre le texte par défaut
# Nettoyage du worker
if self.auth_worker:
self.auth_worker.deleteLater()
self.auth_worker = None
if success:
# 2. Mise à jour des infos globales
PlayerServerInfo.session_id = session_id
# 3. On continue la logique normale
self._proceed_to_queue_or_launch()
else:
# 4. Affichage de l'erreur propre (le handshake SSL a sûrement timeout)
show_qt_error(self, "Erreur d'Authentification",
f"Impossible de se connecter au serveur.\n\n{error_message}")
def _proceed_to_queue_or_launch(self):
"""Continuation de la logique après une session valide."""
try:
# Si on n'a pas encore de position en file d'attente, on la lance
if self.queue_position_value is None:
self.start_queue()
return
# Si on est en file d'attente mais pas au début
if self.queue_position_value != 0:
show_qt_error(self, "Attente", f"Position actuelle : {self.queue_position_value}")
return
# Si tout est OK (Position 0)
FiveMLauncher.launch()
self.ui.connexion_btn.setEnabled(False)
self.schedule_close()
except Exception as exc:
show_qt_error(self, "Erreur de Lancement", f"Détails : {exc}")
class QueueThread(QThread):
update = Signal(str)