Files
tuxgyverandClaude Opus 5 996bbe240d fix(ui): rendre la saisie du code d'accès atteignable à tout moment (v0.8.2)
La fenêtre de saisie ne s'ouvrait qu'une fois, juste après une connexion
manuelle réussie. Un refus, une fenêtre fermée, une reconnexion automatique
— qui n'ouvre volontairement aucune modale — ou l'expiration de
l'autorisation au bout de 12 h laissaient un tunnel monté, un réseau muet
et aucun élément d'interface pour ressaisir un code.

Un bouton sous l'action de connexion et une entrée de systray apparaissent
désormais dès qu'une saisie a un sens : identifiant VPN configuré, tunnel
monté, accès non ouvert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:44:06 +02:00

133 lines
5.1 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)
# Saisie du code d'accès : le tunnel monte en quarantaine et
# l'autorisation expire d'elle-même, il faut donc pouvoir y revenir
# sans passer par une déconnexion. Masquée quand elle n'a pas lieu
# d'être — pas d'identifiant VPN, tunnel à l'arrêt, accès déjà ouvert.
self._action_access = QAction("🔐 Saisir le code d'accès", self)
self._action_access.triggered.connect(self._win.request_access_code)
self._action_access.setVisible(False)
menu.addAction(self._action_access)
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
# La fenêtre principale est seule à savoir si le serveur a ouvert
# l'accès : elle tient cet état, qu'aucune commande WireGuard ne
# rapporte — le tunnel est identique, ouvert ou en quarantaine.
self._action_access.setVisible(self._win.access_code_needed())
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()