- wg-quick down échoue silencieusement si l'interface a déjà disparu (crash, kill, veille) et ne retire donc jamais l'entrée DNS posée par wg-quick up : la résolution reste pointée sur un résolveur mort. Ajout d'un nettoyage DNS inconditionnel (app/core/dns.py) déclenché à la connexion, la déconnexion, la fermeture et au démarrage suivant. - is_connected() se basait sur `wg show`, qui rend un code 0 même en cas d'échec de permission pour un utilisateur non-root : l'app se croyait déconnectée en permanence et ne démontait donc jamais le tunnel à la fermeture. Détection réécrite via sysfs (sans privilèges). - Connexion/déconnexion et ping tournaient sur le thread Qt : gel de l'UI pendant les élévations de privilèges. Déportés dans des QThread (app/ui/worker.py). - Restauration réseau garantie sur toutes les sorties (croix, tray, SIGINT/SIGTERM, atexit, exception) via app/core/shutdown.py. - run_privileged() : la détection "sudo veut un mot de passe" ratait en session non-anglophone et sur les comptes ALL=(ALL) ALL ; réécrite pour se fier au message d'erreur réel plutôt qu'à `sudo -l`. - Écriture de la config WireGuard dans /etc/wireguard (root:root 0700) passe maintenant par install -D via la chaîne de privilèges complète au lieu d'un tee non autorisé par les sudoers. - Ajout d'un diagnostic des droits (make check-privileges, bouton Admin) et mise à jour de setup-sudoers pour couvrir toutes les commandes désormais utilisées.
119 lines
4.2 KiB
Python
119 lines
4.2 KiB
Python
from PyQt6.QtWidgets import QSystemTrayIcon, QMenu, QApplication
|
|
from PyQt6.QtGui import QAction
|
|
from PyQt6.QtCore import QTimer
|
|
|
|
from app.core.config import Config
|
|
from app.core import wireguard as wg_core
|
|
from app.ui import icons
|
|
|
|
|
|
class SystemTray(QSystemTrayIcon):
|
|
def __init__(self, config: Config, main_window, parent=None):
|
|
super().__init__(icons.icon_disconnected(), parent)
|
|
self._cfg = config
|
|
self._win = main_window
|
|
self._connected = False
|
|
self._build_menu()
|
|
self.setToolTip("WGSecure — Déconnecté")
|
|
self.activated.connect(self._on_activated)
|
|
|
|
self._poll = QTimer(parent)
|
|
self._poll.timeout.connect(self._update_status)
|
|
self._poll.start(5000)
|
|
self._update_status()
|
|
|
|
def _build_menu(self):
|
|
menu = QMenu()
|
|
|
|
# Ligne de statut (désactivée)
|
|
self._action_status = QAction("● Déconnecté", self)
|
|
self._action_status.setEnabled(False)
|
|
menu.addAction(self._action_status)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Afficher la fenêtre
|
|
self._action_show = QAction(icons.icon_app(), "Afficher la fenêtre", self)
|
|
self._action_show.triggered.connect(self._show_window)
|
|
menu.addAction(self._action_show)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Connexion / déconnexion
|
|
self._action_toggle = QAction(icons.icon_disconnected(), "Se connecter", self)
|
|
self._action_toggle.triggered.connect(self._toggle_connection)
|
|
menu.addAction(self._action_toggle)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Panneau de configuration
|
|
self._action_config = QAction(icons.icon_admin(), "Configuration", self)
|
|
self._action_config.triggered.connect(self._win._open_admin)
|
|
menu.addAction(self._action_config)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Quitter
|
|
self._action_quit = QAction("Quitter", self)
|
|
self._action_quit.triggered.connect(self._quit)
|
|
menu.addAction(self._action_quit)
|
|
|
|
self.setContextMenu(menu)
|
|
|
|
def _on_activated(self, reason: QSystemTrayIcon.ActivationReason):
|
|
if reason == QSystemTrayIcon.ActivationReason.Trigger:
|
|
self._show_window()
|
|
|
|
def _show_window(self):
|
|
self._win.show()
|
|
self._win.raise_()
|
|
self._win.activateWindow()
|
|
|
|
def _update_status(self):
|
|
prev = self._connected
|
|
try:
|
|
self._connected = wg_core.is_connected(self._cfg)
|
|
except Exception:
|
|
self._connected = False
|
|
|
|
if self._connected:
|
|
self.setIcon(icons.icon_connected())
|
|
self.setToolTip("WGSecure — Connecté")
|
|
self._action_status.setText("● Connecté")
|
|
self._action_toggle.setIcon(icons.icon_connected())
|
|
self._action_toggle.setText("Se déconnecter")
|
|
if not prev:
|
|
srv = self._cfg.wg.get("server_endpoint", "")
|
|
self.showMessage(
|
|
"WGSecure — Connecté ✓",
|
|
f"Tunnel WireGuard actif{(' → ' + srv) if srv else ''}",
|
|
QSystemTrayIcon.MessageIcon.Information, 3000,
|
|
)
|
|
else:
|
|
self.setIcon(icons.icon_disconnected())
|
|
self.setToolTip("WGSecure — Déconnecté")
|
|
self._action_status.setText("● Déconnecté")
|
|
self._action_toggle.setIcon(icons.icon_disconnected())
|
|
self._action_toggle.setText("Se connecter")
|
|
if prev:
|
|
self.showMessage(
|
|
"WGSecure — Déconnecté",
|
|
"Le tunnel WireGuard a été désactivé.",
|
|
QSystemTrayIcon.MessageIcon.Warning, 3000,
|
|
)
|
|
|
|
def _toggle_connection(self):
|
|
self._win._on_connect()
|
|
# La (dé)connexion est désormais asynchrone : l'icône sera mise à jour
|
|
# par la scrutation périodique une fois l'opération terminée.
|
|
|
|
def _quit(self):
|
|
self._poll.stop()
|
|
self.hide()
|
|
# prepare_quit() arrête les minuteries, attend les threads en cours,
|
|
# démonte le tunnel et retire la configuration DNS qu'il avait posée.
|
|
# Sans cela, quitter en étant connecté laissait la machine sans
|
|
# résolution de noms.
|
|
self._win.prepare_quit()
|
|
QApplication.quit()
|