"""Exécution de tâches bloquantes hors du thread graphique. `wg-quick up/down` passe par sudo ou pkexec : entre l'attente du dialogue d'authentification et le montage du tunnel, l'appel peut durer plusieurs dizaines de secondes. Lancé depuis le thread Qt, il gèle toute l'interface (fenêtre grise, « ne répond pas »). Ces workers déportent l'appel. """ from __future__ import annotations from typing import Any, Callable from PyQt6.QtCore import QThread, pyqtSignal class TaskWorker(QThread): """Exécute `fn(*args)` dans un thread et émet son résultat. `done` porte (ok, message, tag) ; `tag` permet à l'appelant de savoir de quelle tâche il s'agit lorsqu'un même slot en traite plusieurs. """ done = pyqtSignal(bool, str, str) def __init__(self, fn: Callable[..., tuple[bool, str]], *args: Any, tag: str = "", parent=None): super().__init__(parent) self._fn = fn self._args = args self._tag = tag def run(self) -> None: try: ok, msg = self._fn(*self._args) except Exception as e: # noqa: BLE001 — remonté à l'UI ok, msg = False, str(e) self.done.emit(bool(ok), str(msg), self._tag) class ValueWorker(QThread): """Variante pour une fonction qui retourne une valeur quelconque.""" done = pyqtSignal(object) def __init__(self, fn: Callable[..., Any], *args: Any, parent=None): super().__init__(parent) self._fn = fn self._args = args def run(self) -> None: try: result = self._fn(*self._args) except Exception: # noqa: BLE001 result = None self.done.emit(result)