Tentative pour rendre l'Ui moins bloquante
This commit is contained in:
@@ -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._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
@@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
+177
-93
@@ -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:
|
||||
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."
|
||||
)
|
||||
return
|
||||
"""Appelée lors du clic sur le bouton connexion."""
|
||||
|
||||
self._ensure_server_session()
|
||||
# 1. Sécurités de base
|
||||
if not self.stored_user_id or self.stored_user_id.isspace():
|
||||
show_qt_error(self, "Connexion impossible", "Identifiant Discord absent.")
|
||||
return
|
||||
|
||||
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,
|
||||
)
|
||||
# 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 is None:
|
||||
self.start_queue()
|
||||
return
|
||||
# 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
|
||||
|
||||
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é.
|
||||
self.ui.connexion_btn.setEnabled(False)
|
||||
|
||||
# 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,43 +204,76 @@ 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)
|
||||
super().mousePressEvent(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)
|
||||
super().mouseMoveEvent(event)
|
||||
if not event.isAccepted():
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
self._dragger.mouse_release(event)
|
||||
super().mouseReleaseEvent(event)
|
||||
if not event.isAccepted():
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Close
|
||||
@@ -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:
|
||||
self.close_timer.stop()
|
||||
self.close_timer.deleteLater()
|
||||
# 3. On stop les timers s'ils tournaient déjà (évite les doublons)
|
||||
self.close_timer.stop()
|
||||
self.countdown_timer.stop()
|
||||
|
||||
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()
|
||||
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}")
|
||||
|
||||
GetServerTokenForDiscord.register_discord_user(
|
||||
self.stored_user_id,
|
||||
PlayerServerInfo.session_id,
|
||||
)
|
||||
class QueueThread(QThread):
|
||||
update = Signal(str)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user