- Instance unique : un second lancement réveille l'instance active. - Autostart et reconnexion auto activés par défaut (nouvelle installation). - Page « À propos » refaite en 5 onglets (contenu séparé dans app_info.py). - Corrige le handshake/RX-TX qui ne s'affichaient plus (wg show sans élévation). - Corrige le diagnostic sudo trompeur (sondes qui ne matchaient jamais les règles). - Corrige make setup-sudoers bloqué hors session graphique (pkexec -> sudo), idempotent. - Corrige le README : Kill Switch inexistant retiré, ordre d'élévation réel documenté.
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
"""Garantit une seule instance de WGSecure en cours d'exécution.
|
|
|
|
Avec le lancement automatique au démarrage désormais activé par défaut
|
|
([[wg-autostart-reconnect]]), un second lancement manuel (icône du menu,
|
|
raccourci double-cliqué) peut facilement se superposer à l'instance déjà
|
|
présente en systray — deux fenêtres sur le même tunnel, deux timers de
|
|
reconnexion concurrents. Le second lancement doit simplement réveiller le
|
|
premier et s'arrêter.
|
|
|
|
Implémenté via un socket local nommé (QLocalServer/QLocalSocket) : le
|
|
mécanisme Qt standard pour ça, portable Linux/Windows sans dépendance
|
|
supplémentaire. Le premier lancement écoute ; tout lancement suivant s'y
|
|
connecte, signale sa présence, puis quitte.
|
|
"""
|
|
|
|
from PyQt6.QtNetwork import QLocalServer, QLocalSocket
|
|
|
|
_SERVER_NAME = "wgsecure-single-instance"
|
|
|
|
|
|
class SingleInstanceGuard:
|
|
def __init__(self):
|
|
self._server: QLocalServer | None = None
|
|
self._on_activate = None
|
|
|
|
def try_acquire(self) -> bool:
|
|
"""True si cette instance est la première et doit continuer.
|
|
|
|
False si une autre tourne déjà : celle-ci lui a signalé sa présence
|
|
et doit se terminer sans ouvrir la moindre fenêtre.
|
|
"""
|
|
probe = QLocalSocket()
|
|
probe.connectToServer(_SERVER_NAME)
|
|
if probe.waitForConnected(200):
|
|
probe.write(b"activate")
|
|
probe.waitForBytesWritten(200)
|
|
probe.disconnectFromServer()
|
|
return False
|
|
|
|
# Un crash précédent peut laisser le socket enregistré sans qu'aucun
|
|
# serveur ne l'écoute : listen() échouerait indéfiniment sinon.
|
|
QLocalServer.removeServer(_SERVER_NAME)
|
|
self._server = QLocalServer()
|
|
self._server.newConnection.connect(self._handle_connection)
|
|
self._server.listen(_SERVER_NAME)
|
|
return True
|
|
|
|
def on_activate_requested(self, callback) -> None:
|
|
"""`callback` (sans argument) est appelé quand un second lancement
|
|
tente de démarrer — à connecter au réaffichage de la fenêtre."""
|
|
self._on_activate = callback
|
|
|
|
def _handle_connection(self) -> None:
|
|
sock = self._server.nextPendingConnection()
|
|
if sock is None:
|
|
return
|
|
sock.readyRead.connect(lambda: self._consume(sock))
|
|
|
|
def _consume(self, sock: QLocalSocket) -> None:
|
|
sock.readAll()
|
|
if self._on_activate:
|
|
self._on_activate()
|
|
sock.disconnectFromServer()
|