Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3330fa4c | ||
|
|
142eff5916 |
@@ -6,6 +6,23 @@ Ce projet suit le [Versionnage Sémantique](https://semver.org/lang/fr/).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [0.13.0] — 2026-09-08
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
- **Un thème clair, au choix dans Paramètres → Apparence.** Le thème sombre reste celui par défaut : rien ne change sur les postes déjà installés tant que le réglage n'est pas touché. Le choix est lu au lancement — les feuilles de style sont posées dans les constructeurs des fenêtres, et les repeindre à chaud supposerait de reconstruire celle qui porte l'état du tunnel, ses fils d'exécution et ses minuteries. Le message de confirmation l'annonce plutôt que de laisser croire à un réglage sans effet.
|
||||||
|
- Le module de thème publie désormais deux palettes derrière les mêmes noms. Les cent soixante-dix appels répartis dans l'interface lisent `theme.BG` sans rien savoir du thème actif : ajouter une palette n'a demandé de toucher aucun widget.
|
||||||
|
|
||||||
|
### Modifié
|
||||||
|
- **Les couleurs qui vivaient encore hors du module de thème l'ont rejoint.** Le graphe de bande passante peignait son fond en dur — un panneau sombre au milieu d'une page claire — et sept de ses teintes sont maintenant des jetons. Quatre couleurs de survol de la fenêtre principale, recopiées à la main, désignent les jetons de même valeur.
|
||||||
|
- **Le texte posé sur un aplat saturé ne suit plus la couleur de texte ordinaire.** Bouton plein, onglet actif, bandeau, ligne sélectionnée : ils portaient `TEXT`, blanc en thème sombre et presque noir en thème clair, où il aurait disparu sur le fond d'accent. Un jeton distinct le dit, blanc dans les deux thèmes.
|
||||||
|
- Trois variantes d'état des champs et des boutons — lecture seule, désactivé, flèches de compteur — étaient écrites en dur dans la feuille de style commune sous forme de voiles blancs translucides, c'est-à-dire invisibles sur un fond clair. Elles deviennent des jetons de palette.
|
||||||
|
|
||||||
|
### Corrigé
|
||||||
|
- **Deux endroits figeaient le thème au chargement de leur module** et auraient gardé les teintes sombres quel que soit le réglage : la feuille de style de la fenêtre d'historique, construite au niveau du module, et la table état → couleur du diagnostic, évaluée comme attribut de classe. La première devient une fonction, la seconde ne retient que la puce et demande la couleur au thème à l'affichage.
|
||||||
|
- **Le bandeau de titre du panneau d'administration s'affichait blanc sur gris pâle en thème clair.** Il prend le fond en creux de la fenêtre, sur lequel le blanc des bandeaux d'onglets — qui sont, eux, des aplats saturés — ne se voyait pas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.12.1] — 2026-09-08
|
## [0.12.1] — 2026-09-08
|
||||||
|
|
||||||
### Corrigé
|
### Corrigé
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
## ──────────────────────────────────────────────
|
## ──────────────────────────────────────────────
|
||||||
|
|
||||||
APP := wgsecure
|
APP := wgsecure
|
||||||
VERSION := 0.12.1
|
VERSION := 0.13.0
|
||||||
VENV := .venv
|
VENV := .venv
|
||||||
PYTHON := $(VENV)/bin/python3
|
PYTHON := $(VENV)/bin/python3
|
||||||
PIP := $(VENV)/bin/pip
|
PIP := $(VENV)/bin/pip
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 🛡️ WGSecure (WGS)
|
# 🛡️ WGSecure (WGS)
|
||||||
|
|
||||||
🚀 v0.12.1 · 🐍 · 🪟🐧 · 🔐 Accès validé par le serveur · 🛡️ WireGuard
|
🚀 v0.13.0 · 🐍 · 🪟🐧 · 🔐 Accès validé par le serveur · 🛡️ WireGuard
|
||||||
|
|
||||||
**WGSecure** est une interface graphique multiplateforme (Windows & Linux) pour gérer une connexion WireGuard dont l'accès aux ressources distantes est ouvert par un code à 6 chiffres validé **par le serveur**.
|
**WGSecure** est une interface graphique multiplateforme (Windows & Linux) pour gérer une connexion WireGuard dont l'accès aux ressources distantes est ouvert par un code à 6 chiffres validé **par le serveur**.
|
||||||
|
|
||||||
@@ -210,4 +210,4 @@ WGSecure/
|
|||||||
## 👤 Auteur
|
## 👤 Auteur
|
||||||
|
|
||||||
Développé par **Johnny** — [JT-Tools](https://github.com/JT-Tools)
|
Développé par **Johnny** — [JT-Tools](https://github.com/JT-Tools)
|
||||||
Version :  — Septembre 2026
|
Version :  — Septembre 2026
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
__version__ = "0.12.1"
|
__version__ = "0.13.0"
|
||||||
APP_NAME = "WGSecure"
|
APP_NAME = "WGSecure"
|
||||||
APP_SHORT = "WGS"
|
APP_SHORT = "WGS"
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ _DEFAULT: dict[str, Any] = {
|
|||||||
"auto_connect_on_startup": False,
|
"auto_connect_on_startup": False,
|
||||||
"auto_reconnect": True,
|
"auto_reconnect": True,
|
||||||
"reconnect_interval": 15,
|
"reconnect_interval": 15,
|
||||||
|
# « dark » ou « light ». Lu une seule fois, au lancement : les feuilles
|
||||||
|
# de style sont posées dans les constructeurs des fenêtres (cf.
|
||||||
|
# app/ui/theme.py). Une valeur inconnue retombe sur « dark ».
|
||||||
|
"theme": "dark",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-10
@@ -102,7 +102,7 @@ class AdminWindow(QDialog):
|
|||||||
|
|
||||||
banner = QLabel(" WGSecure — Configuration Administrateur")
|
banner = QLabel(" WGSecure — Configuration Administrateur")
|
||||||
banner.setFixedHeight(40)
|
banner.setFixedHeight(40)
|
||||||
banner.setStyleSheet(theme.banner_style(theme.BG_SUNKEN))
|
banner.setStyleSheet(theme.banner_style(theme.BG_SUNKEN, theme.TEXT))
|
||||||
layout.addWidget(banner)
|
layout.addWidget(banner)
|
||||||
|
|
||||||
# Onglets de premier niveau : config qui conditionne la connexion
|
# Onglets de premier niveau : config qui conditionne la connexion
|
||||||
@@ -941,6 +941,18 @@ class AdminWindow(QDialog):
|
|||||||
|
|
||||||
lay.addWidget(grp2)
|
lay.addWidget(grp2)
|
||||||
|
|
||||||
|
grp3 = QGroupBox("Apparence")
|
||||||
|
form3 = QFormLayout(grp3)
|
||||||
|
|
||||||
|
self._cmb_theme = QComboBox()
|
||||||
|
for cle, libelle in theme.PALETTES.items():
|
||||||
|
self._cmb_theme.addItem(libelle, cle)
|
||||||
|
actuel = self._cmb_theme.findData(theme.CURRENT)
|
||||||
|
self._cmb_theme.setCurrentIndex(actuel if actuel >= 0 else 0)
|
||||||
|
self._row(form3, "Thème :", self._cmb_theme)
|
||||||
|
|
||||||
|
lay.addWidget(grp3)
|
||||||
|
|
||||||
btn_apply = QPushButton("✅ Appliquer les paramètres")
|
btn_apply = QPushButton("✅ Appliquer les paramètres")
|
||||||
btn_apply.clicked.connect(self._apply_settings)
|
btn_apply.clicked.connect(self._apply_settings)
|
||||||
lay.addWidget(btn_apply)
|
lay.addWidget(btn_apply)
|
||||||
@@ -952,12 +964,26 @@ class AdminWindow(QDialog):
|
|||||||
self._cfg.set("ui", "auto_reconnect", self._chk_autorecon.isChecked())
|
self._cfg.set("ui", "auto_reconnect", self._chk_autorecon.isChecked())
|
||||||
self._cfg.set("ui", "reconnect_interval", self._spin_interval.value())
|
self._cfg.set("ui", "reconnect_interval", self._spin_interval.value())
|
||||||
self._cfg.set("ui", "auto_connect_on_startup", self._chk_auto_connect.isChecked())
|
self._cfg.set("ui", "auto_connect_on_startup", self._chk_auto_connect.isChecked())
|
||||||
|
# Le thème n'est lu qu'au lancement : les feuilles de style sont
|
||||||
|
# posées dans les constructeurs des fenêtres, et les repeindre
|
||||||
|
# supposerait de reconstruire celle qui porte le tunnel. Le dire plutôt
|
||||||
|
# que de laisser croire à un réglage sans effet.
|
||||||
|
choisi = self._cmb_theme.currentData()
|
||||||
|
theme_change = choisi != (self._cfg.get("ui", "theme") or "dark")
|
||||||
|
self._cfg.set("ui", "theme", choisi)
|
||||||
|
|
||||||
autostart = self._chk_autostart.isChecked()
|
autostart = self._chk_autostart.isChecked()
|
||||||
if autostart != bool(self._cfg.get("ui", "autostart")):
|
if autostart != bool(self._cfg.get("ui", "autostart")):
|
||||||
self._cfg.set_autostart(autostart)
|
self._cfg.set_autostart(autostart)
|
||||||
else:
|
else:
|
||||||
self._cfg.save()
|
self._cfg.save()
|
||||||
QMessageBox.information(self, "Paramètres", "Paramètres enregistrés.")
|
|
||||||
|
message = "Paramètres enregistrés."
|
||||||
|
if theme_change:
|
||||||
|
message += ("\n\nLe thème « "
|
||||||
|
f"{theme.PALETTES.get(choisi, choisi)} » s'appliquera "
|
||||||
|
"au prochain démarrage de WGSecure.")
|
||||||
|
QMessageBox.information(self, "Paramètres", message)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Onglet Sécurité
|
# Onglet Sécurité
|
||||||
@@ -1579,13 +1605,11 @@ class AdminWindow(QDialog):
|
|||||||
v.addWidget(btn_close)
|
v.addWidget(btn_close)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
|
|
||||||
#: Rendu d'une étape de diagnostic : puce, couleur du libellé.
|
#: Puce d'une étape de diagnostic. La couleur ne figure pas ici : évaluée
|
||||||
_STEP_STYLE = {
|
#: à l'import comme le reste d'un attribut de classe, elle aurait retenu
|
||||||
"ok": ("✅", theme.OK_TEXT),
|
#: les teintes du thème d'alors. Elle est demandée au thème à l'affichage
|
||||||
"warn": ("⚠️", theme.WARN_TEXT),
|
#: (cf. `theme.state_text`).
|
||||||
"fail": ("❌", theme.FAIL_TEXT),
|
_STEP_BULLET = {"ok": "✅", "warn": "⚠️", "fail": "❌", "skip": "⏭️"}
|
||||||
"skip": ("⏭️", theme.SKIP_TEXT),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _run_test(self):
|
def _run_test(self):
|
||||||
if self._diag_worker is not None:
|
if self._diag_worker is not None:
|
||||||
@@ -1628,7 +1652,11 @@ class AdminWindow(QDialog):
|
|||||||
# chaque cellule renvoyant à la ligne.
|
# chaque cellule renvoyant à la ligne.
|
||||||
rows = ['<table width="100%" cellspacing="0" cellpadding="0">']
|
rows = ['<table width="100%" cellspacing="0" cellpadding="0">']
|
||||||
for st in steps:
|
for st in steps:
|
||||||
bullet, color = self._STEP_STYLE.get(st["state"], ("•", theme.TEXT))
|
if st["state"] in self._STEP_BULLET:
|
||||||
|
bullet = self._STEP_BULLET[st["state"]]
|
||||||
|
color = theme.state_text(st["state"])
|
||||||
|
else:
|
||||||
|
bullet, color = "•", theme.TEXT
|
||||||
weight = "bold" if st["state"] == "fail" else "normal"
|
weight = "bold" if st["state"] == "fail" else "normal"
|
||||||
rows.append(
|
rows.append(
|
||||||
'<tr>'
|
'<tr>'
|
||||||
|
|||||||
+9
-7
@@ -5,6 +5,8 @@ from PyQt6.QtWidgets import QWidget
|
|||||||
from PyQt6.QtGui import QPainter, QColor, QPen, QBrush, QFont, QPainterPath
|
from PyQt6.QtGui import QPainter, QColor, QPen, QBrush, QFont, QPainterPath
|
||||||
from PyQt6.QtCore import Qt, QRect, QPointF
|
from PyQt6.QtCore import Qt, QRect, QPointF
|
||||||
|
|
||||||
|
from app.ui import theme
|
||||||
|
|
||||||
|
|
||||||
def _fmt(bps: float) -> str:
|
def _fmt(bps: float) -> str:
|
||||||
if bps >= 1_048_576:
|
if bps >= 1_048_576:
|
||||||
@@ -43,7 +45,7 @@ class BandwidthGraph(QWidget):
|
|||||||
pad_l, pad_r, pad_t, pad_b = 42, 8, 6, 20
|
pad_l, pad_r, pad_t, pad_b = 42, 8, 6, 20
|
||||||
|
|
||||||
# Fond
|
# Fond
|
||||||
p.fillRect(0, 0, w, h, QColor("#17202a"))
|
p.fillRect(0, 0, w, h, QColor(theme.GRAPH_BG))
|
||||||
|
|
||||||
plot_w = w - pad_l - pad_r
|
plot_w = w - pad_l - pad_r
|
||||||
plot_h = h - pad_t - pad_b
|
plot_h = h - pad_t - pad_b
|
||||||
@@ -71,18 +73,18 @@ class BandwidthGraph(QWidget):
|
|||||||
line.lineTo(QPointF(x, y))
|
line.lineTo(QPointF(x, y))
|
||||||
p.drawPath(line)
|
p.drawPath(line)
|
||||||
|
|
||||||
_path(self._rx, "#5dade2", "#1a3a52") # RX bleu
|
_path(self._rx, theme.GRAPH_RX, theme.GRAPH_RX_FILL) # RX
|
||||||
_path(self._tx, "#58d68d", "#1a3d2b") # TX vert
|
_path(self._tx, theme.GRAPH_TX, theme.GRAPH_TX_FILL) # TX
|
||||||
|
|
||||||
# Axes
|
# Axes
|
||||||
p.setPen(QPen(QColor("#2e4057")))
|
p.setPen(QPen(QColor(theme.GRAPH_AXIS)))
|
||||||
p.drawLine(pad_l, pad_t, pad_l, pad_t + plot_h)
|
p.drawLine(pad_l, pad_t, pad_l, pad_t + plot_h)
|
||||||
p.drawLine(pad_l, pad_t + plot_h, w - pad_r, pad_t + plot_h)
|
p.drawLine(pad_l, pad_t + plot_h, w - pad_r, pad_t + plot_h)
|
||||||
|
|
||||||
# Labels Y
|
# Labels Y
|
||||||
font = QFont("Arial", 8)
|
font = QFont("Arial", 8)
|
||||||
p.setFont(font)
|
p.setFont(font)
|
||||||
p.setPen(QPen(QColor("#5d6d7e")))
|
p.setPen(QPen(QColor(theme.GRAPH_LABEL)))
|
||||||
for frac, label in [(0.0, _fmt(max_val)), (0.5, _fmt(max_val / 2)), (1.0, "0")]:
|
for frac, label in [(0.0, _fmt(max_val)), (0.5, _fmt(max_val / 2)), (1.0, "0")]:
|
||||||
y = int(pad_t + frac * plot_h)
|
y = int(pad_t + frac * plot_h)
|
||||||
p.drawText(QRect(0, y - 8, pad_l - 2, 16),
|
p.drawText(QRect(0, y - 8, pad_l - 2, 16),
|
||||||
@@ -90,9 +92,9 @@ class BandwidthGraph(QWidget):
|
|||||||
label)
|
label)
|
||||||
|
|
||||||
# Légende
|
# Légende
|
||||||
p.setPen(QPen(QColor("#5dade2")))
|
p.setPen(QPen(QColor(theme.GRAPH_RX)))
|
||||||
p.drawText(pad_l + 4, pad_t + 12, f"↓ {_fmt(self._rx[-1])}")
|
p.drawText(pad_l + 4, pad_t + 12, f"↓ {_fmt(self._rx[-1])}")
|
||||||
p.setPen(QPen(QColor("#58d68d")))
|
p.setPen(QPen(QColor(theme.GRAPH_TX)))
|
||||||
p.drawText(pad_l + 90, pad_t + 12, f"↑ {_fmt(self._tx[-1])}")
|
p.drawText(pad_l + 90, pad_t + 12, f"↑ {_fmt(self._tx[-1])}")
|
||||||
|
|
||||||
p.end()
|
p.end()
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ from PyQt6.QtGui import QColor, QFont
|
|||||||
from app.core import history as hist
|
from app.core import history as hist
|
||||||
from app.ui import theme
|
from app.ui import theme
|
||||||
|
|
||||||
_CSS = f"""
|
def _css() -> str:
|
||||||
|
"""Feuille de style de la fenêtre d'historique.
|
||||||
|
|
||||||
|
Fonction et non constante : bâtie à l'import, elle retenait les couleurs
|
||||||
|
du thème chargé à ce moment-là et ignorait celui appliqué au démarrage.
|
||||||
|
"""
|
||||||
|
return f"""
|
||||||
QDialog {{ background: {theme.BG_SUNKEN}; }}
|
QDialog {{ background: {theme.BG_SUNKEN}; }}
|
||||||
QLabel {{ color: {theme.TEXT}; background: transparent; }}
|
QLabel {{ color: {theme.TEXT}; background: transparent; }}
|
||||||
QTableWidget {{
|
QTableWidget {{
|
||||||
@@ -21,12 +27,14 @@ _CSS = f"""
|
|||||||
background: {theme.BG_RAISED}; color: {theme.TEXT}; padding: 5px;
|
background: {theme.BG_RAISED}; color: {theme.TEXT}; padding: 5px;
|
||||||
border: none; font-weight: bold; font-size: 11px;
|
border: none; font-weight: bold; font-size: 11px;
|
||||||
}}
|
}}
|
||||||
QTableWidget::item:selected {{ background: {theme.ACCENT}; }}
|
QTableWidget::item:selected {{ background: {theme.ACCENT};
|
||||||
|
color: {theme.ON_SOLID}; }}
|
||||||
QPushButton {{
|
QPushButton {{
|
||||||
background: {theme.BG_RAISED}; color: {theme.TEXT}; border-radius: 5px;
|
background: {theme.BG_RAISED}; color: {theme.TEXT}; border-radius: 5px;
|
||||||
padding: 6px 14px; border: none;
|
padding: 6px 14px; border: none;
|
||||||
}}
|
}}
|
||||||
QPushButton:hover {{ background: {theme.ACCENT_HOVER}; }}
|
QPushButton:hover {{ background: {theme.ACCENT_HOVER};
|
||||||
|
color: {theme.ON_SOLID}; }}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +43,7 @@ class HistoryDialog(QDialog):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("WGSecure — Historique des sessions")
|
self.setWindowTitle("WGSecure — Historique des sessions")
|
||||||
self.setMinimumSize(680, 420)
|
self.setMinimumSize(680, 420)
|
||||||
self.setStyleSheet(_CSS)
|
self.setStyleSheet(_css())
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
@@ -64,7 +72,7 @@ class HistoryDialog(QDialog):
|
|||||||
self._table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
self._table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
||||||
self._table.setAlternatingRowColors(True)
|
self._table.setAlternatingRowColors(True)
|
||||||
self._table.setStyleSheet(
|
self._table.setStyleSheet(
|
||||||
_CSS +
|
_css() +
|
||||||
f"QTableWidget {{ alternate-background-color: {theme.BG_RAISED}; }}"
|
f"QTableWidget {{ alternate-background-color: {theme.BG_RAISED}; }}"
|
||||||
)
|
)
|
||||||
layout.addWidget(self._table)
|
layout.addWidget(self._table)
|
||||||
|
|||||||
+12
-10
@@ -553,9 +553,9 @@ class MainWindow(QMainWindow):
|
|||||||
self._btn_connect = QPushButton("Se connecter")
|
self._btn_connect = QPushButton("Se connecter")
|
||||||
self._btn_connect.setFixedHeight(44)
|
self._btn_connect.setFixedHeight(44)
|
||||||
self._btn_connect.setStyleSheet(
|
self._btn_connect.setStyleSheet(
|
||||||
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.TEXT}; font-size: 14px;"
|
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.ON_SOLID}; font-size: 14px;"
|
||||||
" font-weight: bold; border-radius: 6px; border: none; }"
|
" font-weight: bold; border-radius: 6px; border: none; }"
|
||||||
"QPushButton:hover { background: #27ae60; }"
|
f"QPushButton:hover {{ background: {theme.OK_HOVER}; }}"
|
||||||
f"QPushButton:disabled {{ background: {theme.BG_RAISED}; color: {theme.TEXT_FAINT}; }}"
|
f"QPushButton:disabled {{ background: {theme.BG_RAISED}; color: {theme.TEXT_FAINT}; }}"
|
||||||
)
|
)
|
||||||
self._btn_connect.clicked.connect(self._on_connect)
|
self._btn_connect.clicked.connect(self._on_connect)
|
||||||
@@ -690,14 +690,16 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _style_access_button(self, expected: bool):
|
def _style_access_button(self, expected: bool):
|
||||||
"""Met le bouton en avant seulement quand un code est réellement dû."""
|
"""Met le bouton en avant seulement quand un code est réellement dû."""
|
||||||
bg, hover = ((theme.WARN_SOLID, "#d68910") if expected
|
bg, hover, fg = ((theme.WARN_SOLID, theme.WARN_HOVER, theme.ON_SOLID)
|
||||||
else (theme.BG_RAISED, theme.ACCENT_HOVER))
|
if expected
|
||||||
|
else (theme.BG_RAISED, theme.ACCENT_HOVER, theme.TEXT))
|
||||||
weight = "bold" if expected else "normal"
|
weight = "bold" if expected else "normal"
|
||||||
self._btn_access.setStyleSheet(
|
self._btn_access.setStyleSheet(
|
||||||
f"QPushButton {{ background: {bg}; color: {theme.TEXT};"
|
f"QPushButton {{ background: {bg}; color: {fg};"
|
||||||
f" font-size: 12px; font-weight: {weight}; border-radius: 6px;"
|
f" font-size: 12px; font-weight: {weight}; border-radius: 6px;"
|
||||||
" border: none; }"
|
" border: none; }"
|
||||||
f"QPushButton:hover {{ background: {hover}; }}"
|
f"QPushButton:hover {{ background: {hover};"
|
||||||
|
f" color: {theme.ON_SOLID}; }}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _sync_window_height(self):
|
def _sync_window_height(self):
|
||||||
@@ -1085,9 +1087,9 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
self._btn_connect.setText("Se déconnecter")
|
self._btn_connect.setText("Se déconnecter")
|
||||||
self._btn_connect.setStyleSheet(
|
self._btn_connect.setStyleSheet(
|
||||||
f"QPushButton {{ background: {theme.FAIL_SOLID}; color: {theme.TEXT}; font-size: 14px;"
|
f"QPushButton {{ background: {theme.FAIL_SOLID}; color: {theme.ON_SOLID}; font-size: 14px;"
|
||||||
" font-weight: bold; border-radius: 6px; border: none; }"
|
" font-weight: bold; border-radius: 6px; border: none; }"
|
||||||
"QPushButton:hover { background: #c0392b; }"
|
f"QPushButton:hover {{ background: {theme.FAIL_SOLID}; }}"
|
||||||
)
|
)
|
||||||
self._info_labels["rx_tx"].setText(
|
self._info_labels["rx_tx"].setText(
|
||||||
f"↓ {info.get('rx_bytes','—')} ↑ {info.get('tx_bytes','—')}"
|
f"↓ {info.get('rx_bytes','—')} ↑ {info.get('tx_bytes','—')}"
|
||||||
@@ -1109,9 +1111,9 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
self._btn_connect.setText("Se connecter")
|
self._btn_connect.setText("Se connecter")
|
||||||
self._btn_connect.setStyleSheet(
|
self._btn_connect.setStyleSheet(
|
||||||
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.TEXT}; font-size: 14px;"
|
f"QPushButton {{ background: {theme.OK_SOLID}; color: {theme.ON_SOLID}; font-size: 14px;"
|
||||||
" font-weight: bold; border-radius: 6px; border: none; }"
|
" font-weight: bold; border-radius: 6px; border: none; }"
|
||||||
"QPushButton:hover { background: #27ae60; }"
|
f"QPushButton:hover {{ background: {theme.OK_HOVER}; }}"
|
||||||
)
|
)
|
||||||
self._info_labels["rx_tx"].setText("—")
|
self._info_labels["rx_tx"].setText("—")
|
||||||
self._info_labels["handshake"].setText("—")
|
self._info_labels["handshake"].setText("—")
|
||||||
|
|||||||
+316
-148
@@ -5,78 +5,309 @@ distinctes dans le seul panneau d'administration, cinq bandeaux d'onglets sans
|
|||||||
rapport de teinte entre eux, et des blocs de résultat dont le fond ne
|
rapport de teinte entre eux, et des blocs de résultat dont le fond ne
|
||||||
correspondait ni à celui de la page ni à celui de leurs voisins. Tout passe
|
correspondait ni à celui de la page ni à celui de leurs voisins. Tout passe
|
||||||
désormais par ce module — une couleur ne se choisit plus au cas par cas.
|
désormais par ce module — une couleur ne se choisit plus au cas par cas.
|
||||||
|
|
||||||
|
Deux palettes cohabitent. Les noms exportés — `BG`, `TEXT`, `TAB_CSS`… — ne
|
||||||
|
changent pas : `apply()` les rebranche sur la palette choisie au démarrage,
|
||||||
|
et les cent soixante-dix appels répartis dans l'interface continuent de lire
|
||||||
|
`theme.BG` sans rien savoir du thème actif. C'est ce qui permet d'ajouter une
|
||||||
|
palette sans toucher à un seul widget.
|
||||||
|
|
||||||
|
Le choix est lu une fois, au lancement (cf. `main.py`) : les feuilles de style
|
||||||
|
sont posées dans les constructeurs des fenêtres, et une bascule à chaud
|
||||||
|
demanderait de les reconstruire — celle qui porte l'état du tunnel comprise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ── Fonds ────────────────────────────────────────────────────────────────────
|
# ── Palettes ────────────────────────────────────────────────────────────────
|
||||||
BG = "#1c2833" # fond de l'application et de toutes les pages
|
# Chaque palette porte les mêmes clés ; ce sont elles que `apply()` publie
|
||||||
BG_SUNKEN = "#17202a" # zones en creux : configurations, blocs de code
|
# comme variables de module. Tout ce qui dépend du thème vit ici, y compris
|
||||||
BG_RAISED = "#253545" # éléments posés sur le fond : listes, en-têtes secondaires
|
# ce qui était auparavant écrit au fil des feuilles de style ci-dessous —
|
||||||
BORDER = "rgba(255,255,255,0.18)"
|
# une valeur en dur dans `TAB_CSS` aurait été un fond blanc translucide posé
|
||||||
BORDER_SOFT = "rgba(255,255,255,0.10)"
|
# sur un thème clair, c'est-à-dire rien du tout.
|
||||||
INPUT_BG = "rgba(255,255,255,0.10)" # fond des champs de saisie et listes
|
|
||||||
|
|
||||||
# ── Textes ───────────────────────────────────────────────────────────────────
|
_DARK = {
|
||||||
TEXT = "white"
|
# Fonds
|
||||||
TEXT_MUTED = "rgba(255,255,255,0.70)"
|
"BG": "#1c2833", # fond de l'application et de toutes les pages
|
||||||
TEXT_FAINT = "rgba(255,255,255,0.50)"
|
"BG_SUNKEN": "#17202a", # zones en creux : configurations, blocs de code
|
||||||
TEXT_CODE = "#a8d8ea" # texte à chasse fixe (aperçus de config, rapports)
|
"BG_RAISED": "#253545", # posé sur le fond : listes, en-têtes secondaires
|
||||||
|
"BORDER": "rgba(255,255,255,0.18)",
|
||||||
|
"BORDER_SOFT": "rgba(255,255,255,0.10)",
|
||||||
|
"INPUT_BG": "rgba(255,255,255,0.10)", # champs de saisie et listes
|
||||||
|
# Variantes d'état des champs et boutons, jusqu'ici en dur dans TAB_CSS.
|
||||||
|
"INPUT_BG_READONLY": "rgba(255,255,255,0.06)",
|
||||||
|
"INPUT_BG_DISABLED": "rgba(255,255,255,0.04)",
|
||||||
|
"SPIN_BUTTON_BG": "rgba(255,255,255,0.15)",
|
||||||
|
"BUTTON_DISABLED_BG": "rgba(255,255,255,0.08)",
|
||||||
|
|
||||||
# ── Accent ───────────────────────────────────────────────────────────────────
|
# Textes
|
||||||
ACCENT = "#2471a3"
|
"TEXT": "white",
|
||||||
ACCENT_HOVER = "#1a5276"
|
"TEXT_MUTED": "rgba(255,255,255,0.70)",
|
||||||
ACCENT_ACTIVE = "#154360"
|
"TEXT_FAINT": "rgba(255,255,255,0.50)",
|
||||||
ACCENT_LIGHT = "#5dade2"
|
"TEXT_CODE": "#a8d8ea", # chasse fixe (aperçus de config, rapports)
|
||||||
|
# Texte posé sur un aplat saturé — bouton plein, onglet actif, bandeau.
|
||||||
|
# Distinct de TEXT : sur un thème clair, TEXT est presque noir et
|
||||||
|
# deviendrait illisible sur un fond d'accent.
|
||||||
|
"ON_SOLID": "white",
|
||||||
|
"BANNER_TEXT": "white",
|
||||||
|
|
||||||
# ── États ────────────────────────────────────────────────────────────────────
|
# Accent
|
||||||
# Chaque état porte un trio : teinte du texte, fond translucide, bouton plein.
|
"ACCENT": "#2471a3",
|
||||||
OK_TEXT, OK_BG, OK_SOLID = "#a9dfbf", "rgba(39,174,96,0.18)", "#1e8449"
|
"ACCENT_HOVER": "#1a5276",
|
||||||
WARN_TEXT, WARN_BG, WARN_SOLID = "#f7dc6f", "rgba(241,196,15,0.15)", "#b7950b"
|
"ACCENT_ACTIVE": "#154360",
|
||||||
FAIL_TEXT, FAIL_BG, FAIL_SOLID = "#f1948a", "rgba(231,76,60,0.18)", "#c0392b"
|
"ACCENT_LIGHT": "#5dade2",
|
||||||
SKIP_TEXT, SKIP_BG = "rgba(255,255,255,0.45)", "rgba(255,255,255,0.05)"
|
# Fond de l'encart d'introduction : l'accent très dilué.
|
||||||
|
"HINT_BG": "rgba(36,113,163,0.16)",
|
||||||
|
|
||||||
# Teintes de survol des boutons pleins ok/fail — reprises telles quelles à
|
# États — chacun porte un trio : texte, fond translucide, bouton plein.
|
||||||
# plusieurs endroits (panneau admin, fenêtre principale) : un seul nom leur
|
"OK_TEXT": "#a9dfbf", "OK_BG": "rgba(39,174,96,0.18)", "OK_SOLID": "#1e8449",
|
||||||
# évite de dériver l'une de l'autre au fil des modifications.
|
"WARN_TEXT": "#f7dc6f", "WARN_BG": "rgba(241,196,15,0.15)", "WARN_SOLID": "#b7950b",
|
||||||
OK_HOVER = "#27ae60"
|
"FAIL_TEXT": "#f1948a", "FAIL_BG": "rgba(231,76,60,0.18)", "FAIL_SOLID": "#c0392b",
|
||||||
FAIL_HOVER = "#e74c3c"
|
"SKIP_TEXT": "rgba(255,255,255,0.45)",
|
||||||
|
"SKIP_BG": "rgba(255,255,255,0.05)",
|
||||||
|
# Survols des boutons pleins — nommés pour ne pas dériver l'un de l'autre.
|
||||||
|
"OK_HOVER": "#27ae60",
|
||||||
|
"WARN_HOVER": "#d68910",
|
||||||
|
"FAIL_HOVER": "#e74c3c",
|
||||||
|
|
||||||
_STATE = {
|
# Badge de ping (fenêtre principale). Teintes propres, volontairement plus
|
||||||
"ok": (OK_TEXT, OK_BG),
|
# tranchées que les aplats d'état : le badge est petit et lu d'un coup.
|
||||||
"warn": (WARN_TEXT, WARN_BG),
|
"PING_GOOD_BG": "#1e8449", "PING_GOOD_FG": "white",
|
||||||
"fail": (FAIL_TEXT, FAIL_BG),
|
"PING_MEDIUM_BG": "#d4ac0d", "PING_MEDIUM_FG": "black",
|
||||||
"skip": (SKIP_TEXT, SKIP_BG),
|
"PING_BAD_BG": "#922b21", "PING_BAD_FG": "white",
|
||||||
"idle": (TEXT_MUTED, "transparent"),
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Badge de ping (fenêtre principale) ──────────────────────────────────────
|
# Graphe de bande passante : fond, puis trait et remplissage par série.
|
||||||
PING_CSS = {
|
"GRAPH_BG": "#17202a",
|
||||||
"good": "background:#1e8449; color:white;",
|
"GRAPH_RX": "#5dade2", "GRAPH_RX_FILL": "#1a3a52",
|
||||||
"medium": "background:#d4ac0d; color:black;",
|
"GRAPH_TX": "#58d68d", "GRAPH_TX_FILL": "#1a3d2b",
|
||||||
"bad": "background:#922b21; color:white;",
|
"GRAPH_AXIS": "#2e4057", "GRAPH_LABEL": "#5d6d7e",
|
||||||
"offline": f"background:{BG_RAISED}; color:{TEXT_FAINT};",
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Entrées de journal (fenêtre principale) ─────────────────────────────────
|
# Bandeaux d'onglets. Six teintes calées sur la même luminosité et la même
|
||||||
LOG_COLORS = {
|
# saturation : les onglets restent distinguables sans que l'un paraisse
|
||||||
"success": OK_TEXT,
|
# plus clair ou plus saturé que les autres.
|
||||||
"error": FAIL_TEXT,
|
"BANNER": {
|
||||||
"warning": WARN_TEXT,
|
|
||||||
"info": ACCENT_LIGHT,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Bandeaux d'onglets ───────────────────────────────────────────────────────
|
|
||||||
# Six teintes calées sur la même luminosité et la même saturation : les
|
|
||||||
# onglets restent distinguables sans que l'un paraisse plus clair ou plus
|
|
||||||
# saturé que les autres, ce qui était le cas des valeurs choisies à la main.
|
|
||||||
BANNER = {
|
|
||||||
"wireguard": "#1a5276",
|
"wireguard": "#1a5276",
|
||||||
"keys": "#1a4a6e",
|
"keys": "#1a4a6e",
|
||||||
"test": "#1a4a7a",
|
"test": "#1a4a7a",
|
||||||
"settings": "#155e52",
|
"settings": "#155e52",
|
||||||
"security": "#7a3b1a",
|
"security": "#7a3b1a",
|
||||||
"about": "#243b53",
|
"about": "#243b53",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Le thème clair n'est pas l'inverse du sombre : inverser une luminance donne
|
||||||
|
# des aplats criards et des textes d'état illisibles. Les fonds descendent
|
||||||
|
# vers un gris très pâle plutôt que vers le blanc pur — un blanc plein fait
|
||||||
|
# vibrer les bordures et fatigue sur une fenêtre qui reste ouverte la journée.
|
||||||
|
# Les teintes d'état, elles, sont assombries : `#a9dfbf` est un vert lisible
|
||||||
|
# sur ardoise et invisible sur papier.
|
||||||
|
#
|
||||||
|
# L'accent, les bandeaux et les aplats pleins ne bougent pas : ils portent
|
||||||
|
# déjà un texte blanc dans les deux thèmes, et les garder identiques donne à
|
||||||
|
# l'application la même identité d'un thème à l'autre.
|
||||||
|
_LIGHT = {
|
||||||
|
"BG": "#f4f6f8",
|
||||||
|
"BG_SUNKEN": "#e9edf1",
|
||||||
|
"BG_RAISED": "#ffffff",
|
||||||
|
"BORDER": "rgba(28,40,51,0.22)",
|
||||||
|
"BORDER_SOFT": "rgba(28,40,51,0.12)",
|
||||||
|
"INPUT_BG": "#ffffff",
|
||||||
|
"INPUT_BG_READONLY": "rgba(28,40,51,0.05)",
|
||||||
|
"INPUT_BG_DISABLED": "rgba(28,40,51,0.03)",
|
||||||
|
"SPIN_BUTTON_BG": "rgba(28,40,51,0.10)",
|
||||||
|
"BUTTON_DISABLED_BG": "rgba(28,40,51,0.08)",
|
||||||
|
|
||||||
|
"TEXT": "#1c2833", # l'ardoise du thème sombre, passée au texte
|
||||||
|
"TEXT_MUTED": "rgba(28,40,51,0.72)",
|
||||||
|
"TEXT_FAINT": "rgba(28,40,51,0.50)",
|
||||||
|
"TEXT_CODE": "#1a5276",
|
||||||
|
"ON_SOLID": "white",
|
||||||
|
"BANNER_TEXT": "white",
|
||||||
|
|
||||||
|
"ACCENT": "#2471a3",
|
||||||
|
"ACCENT_HOVER": "#1a5276",
|
||||||
|
"ACCENT_ACTIVE": "#154360",
|
||||||
|
# Plus soutenu que dans le thème sombre : `#5dade2` sert aussi de couleur
|
||||||
|
# de texte au journal, et se perd sur un fond pâle.
|
||||||
|
"ACCENT_LIGHT": "#2e86c1",
|
||||||
|
"HINT_BG": "rgba(36,113,163,0.10)",
|
||||||
|
|
||||||
|
"OK_TEXT": "#1a6b3c", "OK_BG": "rgba(39,174,96,0.14)", "OK_SOLID": "#1e8449",
|
||||||
|
"WARN_TEXT": "#8a6d0b", "WARN_BG": "rgba(241,196,15,0.22)", "WARN_SOLID": "#b7950b",
|
||||||
|
"FAIL_TEXT": "#a5281b", "FAIL_BG": "rgba(231,76,60,0.12)", "FAIL_SOLID": "#c0392b",
|
||||||
|
"SKIP_TEXT": "rgba(28,40,51,0.45)",
|
||||||
|
"SKIP_BG": "rgba(28,40,51,0.05)",
|
||||||
|
"OK_HOVER": "#27ae60",
|
||||||
|
"WARN_HOVER": "#d68910",
|
||||||
|
"FAIL_HOVER": "#e74c3c",
|
||||||
|
|
||||||
|
"PING_GOOD_BG": "#1e8449", "PING_GOOD_FG": "white",
|
||||||
|
"PING_MEDIUM_BG": "#b7950b", "PING_MEDIUM_FG": "white",
|
||||||
|
"PING_BAD_BG": "#922b21", "PING_BAD_FG": "white",
|
||||||
|
|
||||||
|
# Le graphe garde son fond en creux, pâle comme le reste ; les deux séries
|
||||||
|
# sont assombries pour rester lisibles dessus, et leur remplissage devient
|
||||||
|
# un voile de la même teinte au lieu d'un aplat sombre.
|
||||||
|
"GRAPH_BG": "#e9edf1",
|
||||||
|
"GRAPH_RX": "#2e86c1", "GRAPH_RX_FILL": "#cfe3f2",
|
||||||
|
"GRAPH_TX": "#1e8449", "GRAPH_TX_FILL": "#d3efdd",
|
||||||
|
"GRAPH_AXIS": "#c3ccd6", "GRAPH_LABEL": "#5d6d7e",
|
||||||
|
|
||||||
|
"BANNER": {
|
||||||
|
"wireguard": "#1a5276",
|
||||||
|
"keys": "#1a4a6e",
|
||||||
|
"test": "#1a4a7a",
|
||||||
|
"settings": "#155e52",
|
||||||
|
"security": "#7a3b1a",
|
||||||
|
"about": "#243b53",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#: Palettes proposées, dans l'ordre où l'interface les présente.
|
||||||
|
PALETTES = {"dark": "Sombre", "light": "Clair"}
|
||||||
|
_PALETTES = {"dark": _DARK, "light": _LIGHT}
|
||||||
|
|
||||||
|
#: Palette active. Renseignée par `apply()`, lue par le panneau de réglages.
|
||||||
|
CURRENT = "dark"
|
||||||
|
|
||||||
|
|
||||||
|
def apply(name: str) -> str:
|
||||||
|
"""Rend `name` active et recalcule tout ce qui en dépend.
|
||||||
|
|
||||||
|
Rend le nom réellement appliqué : un réglage inconnu — configuration
|
||||||
|
écrite à la main, retour à une version antérieure — retombe sur le thème
|
||||||
|
sombre plutôt que de faire échouer le démarrage de l'interface.
|
||||||
|
|
||||||
|
À appeler avant de construire la moindre fenêtre : les feuilles de style
|
||||||
|
sont posées dans les constructeurs et ne sont pas relues ensuite.
|
||||||
|
"""
|
||||||
|
global CURRENT
|
||||||
|
CURRENT = name if name in _PALETTES else "dark"
|
||||||
|
globals().update(_PALETTES[CURRENT])
|
||||||
|
_derive()
|
||||||
|
return CURRENT
|
||||||
|
|
||||||
|
|
||||||
|
def _derive() -> None:
|
||||||
|
"""Recompose les valeurs dérivées de la palette qui vient d'être publiée.
|
||||||
|
|
||||||
|
Les fonctions du module lisent les jetons à l'appel et suivent donc
|
||||||
|
d'elles-mêmes ; seules les constantes calculées une fois à l'import —
|
||||||
|
feuilles de style complètes et tables d'état — doivent être refaites.
|
||||||
|
"""
|
||||||
|
globals().update(
|
||||||
|
_STATE={
|
||||||
|
"ok": (OK_TEXT, OK_BG),
|
||||||
|
"warn": (WARN_TEXT, WARN_BG),
|
||||||
|
"fail": (FAIL_TEXT, FAIL_BG),
|
||||||
|
"skip": (SKIP_TEXT, SKIP_BG),
|
||||||
|
"idle": (TEXT_MUTED, "transparent"),
|
||||||
|
},
|
||||||
|
PING_CSS={
|
||||||
|
"good": f"background:{PING_GOOD_BG}; color:{PING_GOOD_FG};",
|
||||||
|
"medium": f"background:{PING_MEDIUM_BG}; color:{PING_MEDIUM_FG};",
|
||||||
|
"bad": f"background:{PING_BAD_BG}; color:{PING_BAD_FG};",
|
||||||
|
"offline": f"background:{BG_RAISED}; color:{TEXT_FAINT};",
|
||||||
|
},
|
||||||
|
LOG_COLORS={
|
||||||
|
"success": OK_TEXT,
|
||||||
|
"error": FAIL_TEXT,
|
||||||
|
"warning": WARN_TEXT,
|
||||||
|
"info": ACCENT_LIGHT,
|
||||||
|
},
|
||||||
|
TAB_CSS=f"""
|
||||||
|
QWidget {{ background-color: {BG}; color: {TEXT}; }}
|
||||||
|
QLabel {{ color: {TEXT}; background: transparent; }}
|
||||||
|
QCheckBox {{ color: {TEXT}; background: transparent; }}
|
||||||
|
QGroupBox {{
|
||||||
|
color: {TEXT};
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid {BORDER};
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
QGroupBox::title {{
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
left: 10px;
|
||||||
|
padding: 0 4px;
|
||||||
|
color: {TEXT};
|
||||||
|
}}
|
||||||
|
QLineEdit {{
|
||||||
|
background: {INPUT_BG};
|
||||||
|
color: {TEXT};
|
||||||
|
border: 1px solid {BORDER};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}}
|
||||||
|
QLineEdit:read-only {{ background: {INPUT_BG_READONLY}; }}
|
||||||
|
QLineEdit:disabled {{ background: {INPUT_BG_DISABLED};
|
||||||
|
color: {TEXT_FAINT}; }}
|
||||||
|
QSpinBox {{
|
||||||
|
background: {INPUT_BG};
|
||||||
|
color: {TEXT};
|
||||||
|
border: 1px solid {BORDER};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}}
|
||||||
|
QSpinBox::up-button, QSpinBox::down-button {{
|
||||||
|
background: {SPIN_BUTTON_BG};
|
||||||
|
}}
|
||||||
|
QComboBox {{
|
||||||
|
background: {INPUT_BG};
|
||||||
|
color: {TEXT};
|
||||||
|
border: 1px solid {BORDER};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}}
|
||||||
|
QComboBox::drop-down {{ border: none; }}
|
||||||
|
QComboBox QAbstractItemView {{
|
||||||
|
background: {BG_RAISED};
|
||||||
|
color: {TEXT};
|
||||||
|
selection-background-color: {ACCENT};
|
||||||
|
selection-color: {ON_SOLID};
|
||||||
|
}}
|
||||||
|
QTextEdit {{
|
||||||
|
background: {BG_SUNKEN};
|
||||||
|
color: {TEXT_CODE};
|
||||||
|
border: 1px solid {BORDER};
|
||||||
|
border-radius: 4px;
|
||||||
|
}}
|
||||||
|
QPushButton {{
|
||||||
|
background: {ACCENT};
|
||||||
|
color: {ON_SOLID};
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: none;
|
||||||
|
}}
|
||||||
|
QPushButton:hover {{ background: {ACCENT_HOVER}; }}
|
||||||
|
QPushButton:checked {{ background: {ACCENT_ACTIVE}; }}
|
||||||
|
QPushButton:disabled {{ background: {BUTTON_DISABLED_BG};
|
||||||
|
color: {TEXT_FAINT}; }}
|
||||||
|
""",
|
||||||
|
TABBAR_CSS=f"""
|
||||||
|
QTabWidget::pane {{ border: none; background: {BG}; }}
|
||||||
|
QTabBar::tab {{
|
||||||
|
background: {BG_RAISED};
|
||||||
|
color: {TEXT_MUTED};
|
||||||
|
padding: 6px 14px;
|
||||||
|
border: none;
|
||||||
|
}}
|
||||||
|
QTabBar::tab:selected {{ background: {ACCENT}; color: {ON_SOLID}; }}
|
||||||
|
QTabBar::tab:hover:!selected {{ background: {ACCENT_HOVER};
|
||||||
|
color: {ON_SOLID}; }}
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def state_text(state: str) -> str:
|
||||||
|
"""Couleur de texte d'un état, pour un rendu qui se passe de fond.
|
||||||
|
|
||||||
|
Le panneau de diagnostic tenait sa propre table état → couleur, bâtie à
|
||||||
|
l'import de son module : elle gardait donc les teintes du thème chargé à
|
||||||
|
ce moment-là, quel que soit celui appliqué ensuite. La question revient
|
||||||
|
ici, où la réponse suit la palette active.
|
||||||
|
"""
|
||||||
|
return _STATE.get(state, _STATE["idle"])[0]
|
||||||
|
|
||||||
|
|
||||||
def result_style(state: str, font_size: int = 11) -> str:
|
def result_style(state: str, font_size: int = 11) -> str:
|
||||||
"""Feuille de style d'un bloc de résultat, selon son état.
|
"""Feuille de style d'un bloc de résultat, selon son état.
|
||||||
@@ -116,109 +347,40 @@ def scrollarea_style() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def banner_style(color: str) -> str:
|
def banner_style(color: str, fg: str = "") -> str:
|
||||||
return (f"background: {color}; color: {TEXT}; font-size: 13px;"
|
"""Bandeau de titre posé en tête d'une fenêtre ou d'une page.
|
||||||
" font-weight: bold; padding-left: 8px;")
|
|
||||||
|
|
||||||
|
`fg` vaut le blanc des bandeaux d'onglets, qui sont des aplats saturés.
|
||||||
#: CSS sombre commun à toutes les pages.
|
Le bandeau de titre du panneau d'administration, lui, prend le fond en
|
||||||
TAB_CSS = f"""
|
creux de la fenêtre : en thème clair, c'est un gris très pâle sur lequel
|
||||||
QWidget {{ background-color: {BG}; color: {TEXT}; }}
|
du blanc ne se voit pas. Il passe donc la couleur de texte ordinaire.
|
||||||
QLabel {{ color: {TEXT}; background: transparent; }}
|
|
||||||
QCheckBox {{ color: {TEXT}; background: transparent; }}
|
|
||||||
QGroupBox {{
|
|
||||||
color: {TEXT};
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid {BORDER};
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
QGroupBox::title {{
|
|
||||||
subcontrol-origin: margin;
|
|
||||||
left: 10px;
|
|
||||||
padding: 0 4px;
|
|
||||||
color: {TEXT};
|
|
||||||
}}
|
|
||||||
QLineEdit {{
|
|
||||||
background: {INPUT_BG};
|
|
||||||
color: {TEXT};
|
|
||||||
border: 1px solid {BORDER};
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 6px;
|
|
||||||
}}
|
|
||||||
QLineEdit:read-only {{ background: rgba(255,255,255,0.06); }}
|
|
||||||
QLineEdit:disabled {{ background: rgba(255,255,255,0.04);
|
|
||||||
color: {TEXT_FAINT}; }}
|
|
||||||
QSpinBox {{
|
|
||||||
background: {INPUT_BG};
|
|
||||||
color: {TEXT};
|
|
||||||
border: 1px solid {BORDER};
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 6px;
|
|
||||||
}}
|
|
||||||
QSpinBox::up-button, QSpinBox::down-button {{
|
|
||||||
background: rgba(255,255,255,0.15);
|
|
||||||
}}
|
|
||||||
QComboBox {{
|
|
||||||
background: {INPUT_BG};
|
|
||||||
color: {TEXT};
|
|
||||||
border: 1px solid {BORDER};
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 8px;
|
|
||||||
}}
|
|
||||||
QComboBox::drop-down {{ border: none; }}
|
|
||||||
QComboBox QAbstractItemView {{
|
|
||||||
background: {BG_RAISED};
|
|
||||||
color: {TEXT};
|
|
||||||
selection-background-color: {ACCENT};
|
|
||||||
}}
|
|
||||||
QTextEdit {{
|
|
||||||
background: {BG_SUNKEN};
|
|
||||||
color: {TEXT_CODE};
|
|
||||||
border: 1px solid {BORDER};
|
|
||||||
border-radius: 4px;
|
|
||||||
}}
|
|
||||||
QPushButton {{
|
|
||||||
background: {ACCENT};
|
|
||||||
color: {TEXT};
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 7px 12px;
|
|
||||||
border: none;
|
|
||||||
}}
|
|
||||||
QPushButton:hover {{ background: {ACCENT_HOVER}; }}
|
|
||||||
QPushButton:checked {{ background: {ACCENT_ACTIVE}; }}
|
|
||||||
QPushButton:disabled {{ background: rgba(255,255,255,0.08);
|
|
||||||
color: {TEXT_FAINT}; }}
|
|
||||||
"""
|
"""
|
||||||
|
return (f"background: {color}; color: {fg or BANNER_TEXT};"
|
||||||
|
" font-size: 13px; font-weight: bold; padding-left: 8px;")
|
||||||
|
|
||||||
#: Barre d'onglets, utilisée par le panneau principal et les sous-onglets.
|
|
||||||
TABBAR_CSS = f"""
|
def primary_button_style(solid: str = "", hover: str = "") -> str:
|
||||||
QTabWidget::pane {{ border: none; background: {BG}; }}
|
"""Bouton d'action principale d'une page (lancer un test, par exemple).
|
||||||
QTabBar::tab {{
|
|
||||||
background: {BG_RAISED};
|
Les valeurs par défaut sont résolues à l'appel, et non à la définition :
|
||||||
color: {TEXT_MUTED};
|
prises dans la signature, elles auraient figé les teintes de la palette
|
||||||
padding: 6px 14px;
|
active au moment de l'import — soit toujours celles du thème sombre.
|
||||||
border: none;
|
|
||||||
}}
|
|
||||||
QTabBar::tab:selected {{ background: {ACCENT}; color: {TEXT}; }}
|
|
||||||
QTabBar::tab:hover:!selected {{ background: {ACCENT_HOVER};
|
|
||||||
color: {TEXT}; }}
|
|
||||||
"""
|
"""
|
||||||
|
solid = solid or OK_SOLID
|
||||||
|
hover = hover or OK_HOVER
|
||||||
def primary_button_style(solid: str = OK_SOLID, hover: str = OK_HOVER) -> str:
|
return (f"QPushButton {{ padding: 9px; background: {solid};"
|
||||||
"""Bouton d'action principale d'une page (lancer un test, par exemple)."""
|
f" color: {ON_SOLID};"
|
||||||
return (f"QPushButton {{ padding: 9px; background: {solid}; color: {TEXT};"
|
|
||||||
f" border-radius: 5px; font-weight: bold; border: none; }}"
|
f" border-radius: 5px; font-weight: bold; border: none; }}"
|
||||||
f"QPushButton:hover {{ background: {hover}; }}")
|
f"QPushButton:hover {{ background: {hover}; }}")
|
||||||
|
|
||||||
|
|
||||||
def secondary_button_style() -> str:
|
def secondary_button_style() -> str:
|
||||||
"""Bouton d'action secondaire d'une page (à côté d'une action principale)."""
|
"""Bouton d'action secondaire d'une page (à côté d'une action principale)."""
|
||||||
return (f"QPushButton {{ padding: 9px; background: {BG_RAISED}; color: {TEXT};"
|
return (f"QPushButton {{ padding: 9px; background: {BG_RAISED};"
|
||||||
|
f" color: {TEXT};"
|
||||||
f" border-radius: 5px; border: none; }}"
|
f" border-radius: 5px; border: none; }}"
|
||||||
f"QPushButton:hover {{ background: {ACCENT_HOVER}; }}")
|
f"QPushButton:hover {{ background: {ACCENT_HOVER};"
|
||||||
|
f" color: {ON_SOLID}; }}")
|
||||||
|
|
||||||
|
|
||||||
def hint_style() -> str:
|
def hint_style() -> str:
|
||||||
@@ -227,7 +389,13 @@ def hint_style() -> str:
|
|||||||
Assez contrasté pour se détacher du fond de page, assez sobre pour ne pas
|
Assez contrasté pour se détacher du fond de page, assez sobre pour ne pas
|
||||||
concurrencer le contenu : un liseré d'accent à gauche, pas de cadre plein.
|
concurrencer le contenu : un liseré d'accent à gauche, pas de cadre plein.
|
||||||
"""
|
"""
|
||||||
return (f"background: rgba(36,113,163,0.16);"
|
return (f"background: {HINT_BG};"
|
||||||
f" border-left: 3px solid {ACCENT_LIGHT};"
|
f" border-left: 3px solid {ACCENT_LIGHT};"
|
||||||
f" border-radius: 4px; padding: 9px 11px;"
|
f" border-radius: 4px; padding: 9px 11px;"
|
||||||
f" color: {TEXT_MUTED}; font-size: 11px;")
|
f" color: {TEXT_MUTED}; font-size: 11px;")
|
||||||
|
|
||||||
|
|
||||||
|
# Palette par défaut publiée dès l'import : le module reste utilisable sans
|
||||||
|
# initialisation explicite, et `main.py` n'a qu'à rappeler `apply()` avec le
|
||||||
|
# réglage lu en configuration.
|
||||||
|
apply("dark")
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from app.core import log as conn_log
|
|||||||
from app.core import shutdown as shutdown_guard
|
from app.core import shutdown as shutdown_guard
|
||||||
from app.core.single_instance import SingleInstanceGuard
|
from app.core.single_instance import SingleInstanceGuard
|
||||||
from app.ui.main_window import MainWindow
|
from app.ui.main_window import MainWindow
|
||||||
|
from app.ui import theme
|
||||||
from app.ui.systray import SystemTray
|
from app.ui.systray import SystemTray
|
||||||
from app.ui import icons
|
from app.ui import icons
|
||||||
from app.utils.platform_utils import install_desktop_entry
|
from app.utils.platform_utils import install_desktop_entry
|
||||||
@@ -112,6 +113,13 @@ def main():
|
|||||||
|
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
|
# Avant la moindre fenêtre : les feuilles de style sont posées dans les
|
||||||
|
# constructeurs, et une palette changée après coup n'atteindrait plus
|
||||||
|
# rien. `apply` retombe seule sur le thème sombre si le réglage est
|
||||||
|
# inconnu — une configuration écrite à la main ne doit pas empêcher
|
||||||
|
# l'application de s'ouvrir.
|
||||||
|
theme.apply(config.get("ui", "theme") or "dark")
|
||||||
|
|
||||||
# Reste d'une session précédente mal terminée : DNS détourné vers un
|
# Reste d'une session précédente mal terminée : DNS détourné vers un
|
||||||
# résolveur mort. À faire avant d'ouvrir la fenêtre pour rendre l'accès
|
# résolveur mort. À faire avant d'ouvrir la fenêtre pour rendre l'accès
|
||||||
# au réseau tout de suite.
|
# au réseau tout de suite.
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
; ──────────────────────────────────────────────
|
; ──────────────────────────────────────────────
|
||||||
|
|
||||||
#define MyAppName "WGSecure"
|
#define MyAppName "WGSecure"
|
||||||
#define MyAppVersion "0.12.1"
|
#define MyAppVersion "0.13.0"
|
||||||
#define MyAppPublisher "WGSecure"
|
#define MyAppPublisher "WGSecure"
|
||||||
#define MyAppExeName "wgsecure.exe"
|
#define MyAppExeName "wgsecure.exe"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user