import webbrowser from sys import platform from os import environ from PySide6 import QtGui from PySide6.QtCore import Qt, QTimer from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QMainWindow, QSizePolicy from PySide6.QtCore import QThread, Signal from config.config_manager import ConfigManager from config.constants import PlayerServerInfo, Urls from tools.http_client import ApiError from ui.error_dialog import show_qt_error from ui.hazard_stripes import HazardButton from controllers.audio_controller import AudioController from controllers.glow_animator import GlowAnimator from controllers.window_dragger import WindowDragger from discord import discord_oauth from fivemserver.whitelistmanager import WhiteList from fivemserver.fivemlauncher import FiveMLauncher from fivemserver.queuemanager import QueueManager from fivemserver.get_server_token import GetServerTokenForDiscord from fake_patch_notes import patch_note # For Linux Wayland to authorize moving window if platform.startswith('linux'): environ["QT_QPA_PLATFORM"] = "xcb" class MainWindow(QMainWindow): #update = Signal(str) # Reçoit les callbacks de QueueManager def __init__(self, bundle_dir: str, config_manager: ConfigManager): super().__init__() self.config = config_manager self.stored_user_id = self.config.get_discord_user() self.queue_thread = None self.queue_position_value = None # UI self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow_vertical_pager.ui", self) self.setCentralWidget(self.ui.centralWidget()) self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) # Par défaut on affiche la page normal pour la connexion au serveur self.ui.stackedWidget.setCurrentIndex(0) # On cache par défaut les infos liste d'attente self.ui.queue_lbl.hide() self.ui.queue_position.hide() # Si l'id discord = "" ou des espace, alors on affiche la page comme quoi faut être connecté à discord. if self.stored_user_id == "" or self.stored_user_id.isspace(): self.ui.stackedWidget.setCurrentIndex(1) else: try: # on vérifie si le joueur est whitelisté WhiteList.check_whitelist(Urls.API_URL.value, self.stored_user_id) except ApiError as exc: show_qt_error(self, "La Tanière", f"Impossible de vérifier la whitelist.\n\n{exc}") PlayerServerInfo.is_whitelist = False PlayerServerInfo.is_staff = False # si on est whitelisté, on démarre la file d'attente if PlayerServerInfo.is_whitelist: self.start_queue() self.ui.queue_lbl.show() self.ui.queue_position.show() else: self.ui.stackedWidget.setCurrentIndex(2) # Test bouton en contruction en_chantier = False # on set la css du bouton en fonction de la valeur de la variable en_chantier self.set_en_chantier(en_chantier) if en_chantier: old_btn = self.ui.connexion_btn parent_layout = self.ui.verticalLayout_6 # layout direct du bouton dans le .ui index = parent_layout.indexOf(old_btn) new_btn = HazardButton(old_btn.parentWidget()) new_btn.setObjectName("connexion_btn") new_btn.setText("EN MAINTENANCE") new_btn.setIcon(old_btn.icon()) new_btn.setIconSize(old_btn.iconSize()) new_btn.setMinimumSize(old_btn.minimumSize()) new_btn.set_hazard(True) parent_layout.takeAt(index) old_btn.deleteLater() parent_layout.insertWidget(index, new_btn) self.ui.connexion_btn = new_btn self.ui.connexion_btn.clicked.connect(self._on_connexion) # centrage vertical du bouton connexion if not PlayerServerInfo.is_staff: self.ui.staff_btn.hide() layout = self.ui.verticalLayout_6 # Trouver et modifier le spacer item for i in range(layout.count()): item = layout.itemAt(i) if item.spacerItem(): # C'est un spacer item.spacerItem().changeSize(20, 15, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) layout.invalidate() # Forcer le recalcul du layout break self.ui.info_text.setMarkdown(patch_note) # Sous-systèmes self._audio = AudioController(self.config, self.ui.audio_volume_adjust, self.ui.mute_btn) self._glow = GlowAnimator(self.ui.connexion_btn) self._dragger = WindowDragger(self) self._connect_signals() self._center_window() self.show() # ------------------------------------------------------------------ # Setup # ------------------------------------------------------------------ def _connect_signals(self) -> None: self.ui.close_btn.clicked.connect(self.close) self.ui.minimize_btn.clicked.connect(self.showMinimized) self.ui.connexion_btn.clicked.connect(self._on_connexion) self.ui.discord_btn.clicked.connect(self._on_discord) self.ui.intranet_btn.clicked.connect(self._on_intranet) self.ui.discord_auth_btn.clicked.connect(self._on_discord_auth_btn) self.ui.no_whitelist_btn.clicked.connect(self.close) def _center_window(self) -> None: self.adjustSize() screen = ( QtGui.QGuiApplication.screenAt(QtGui.QCursor.pos()) or QtGui.QGuiApplication.primaryScreen() ) rect = self.frameGeometry() rect.moveCenter(screen.availableGeometry().center()) self.move(rect.topLeft()) # ------------------------------------------------------------------ # Button handlers # ------------------------------------------------------------------ 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 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: show_qt_error( self, "Connexion en attente", "La position dans la file d'attente n'est pas encore connue." ) 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é. self.ui.connexion_btn.setEnabled(False) # fermer l'application après 60 secondes (60000 ms) QTimer.singleShot(60000, self.close) except ApiError as exc: show_qt_error(self, "Connexion impossible", f"Erreur lors de la connexion.\n\n{exc}") @staticmethod def _on_discord() -> None: webbrowser.open(Urls.DISCORD.value) def _on_intranet(self) -> None: webbrowser.open(Urls.INTRANET.value) self._glow.start() def _on_discord_auth_btn(self) -> None: try: test = discord_oauth.get_discord_user_id() self.config.set_discord_user(test[0]) PlayerServerInfo.session_id = test[1] self.config.save() self.ui.stackedWidget.setCurrentIndex(0) 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: QtGui.QMouseEvent) -> None: self._dragger.mouse_press(event) super().mousePressEvent(event) def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None: self._dragger.mouse_move(event) super().mouseMoveEvent(event) def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None: self._dragger.mouse_release(event) super().mouseReleaseEvent(event) # ------------------------------------------------------------------ # Close # ------------------------------------------------------------------ def closeEvent(self, event) -> None: if self.queue_thread and self.queue_thread.isRunning(): self.queue_thread.stop() self.queue_thread.wait() # Attend que le thread se termine proprement self.config.save() super().closeEvent(event) # ------------------------------------------------------------------ # Change ui on runtime # ------------------------------------------------------------------ def set_en_chantier(self, valeur: bool): self.en_chantier = valeur # ta variable Python self.ui.connexion_btn.setProperty("en_chantier", valeur) # propriété Qt self.ui.connexion_btn.style().unpolish(self.ui.connexion_btn) self.ui.connexion_btn.style().polish(self.ui.connexion_btn) # ------------------------------------------------------------------ # 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() # 🧪 TEMP - Simule une position en queue pour tester l'UI # self.handle_update("position:3:10") def handle_update(self, message: str): if message == "ok": self.ui.queue_lbl.setVisible(True) self.ui.queue_position.setVisible(False) self.ui.queue_lbl.setText("🚀 C'est votre tour !") self.queue_position_value = 0 elif message == "ready": self.ui.queue_lbl.setVisible(True) self.ui.queue_position.setVisible(False) self.ui.queue_lbl.setText("🚀 C'est votre tour !") self.queue_position_value = 0 elif message.startswith("position:"): _, pos, total = message.split(":") self.queue_position_value = int(pos) self.ui.queue_lbl.setVisible(True) self.ui.queue_position.setVisible(True) self.ui.queue_position.setText(f"{pos}") def launch_fivem(self): pass class QueueThread(QThread): update = Signal(str) def __init__(self, user_id: str, parent=None): # ← parent=None super().__init__(parent) # ← passé à QThread self.manager = QueueManager( user_id=user_id, on_update=self.update.emit ) def run(self): self.manager.start() def stop(self): self.manager.stop()