fix(tray): handlers de menu à 2 args (icon,item) — le lambda 1-arg capturait l'icône au lieu du nom du profil; Icon.run() sur le thread principal (exigence pystray); logs DEBUG par défaut + formateur enrichi
This commit is contained in:
@@ -40,7 +40,9 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
setup_logging(level=logging.DEBUG if args.debug else logging.INFO)
|
||||
# Journalisation très verbeuse par défaut pour faciliter le diagnostic.
|
||||
setup_logging(level=logging.DEBUG)
|
||||
LOG.debug("Démarrage : args=%s", args)
|
||||
|
||||
try:
|
||||
store = load_config(args.config)
|
||||
@@ -69,12 +71,10 @@ def run_tray(store: ConfigStore) -> int:
|
||||
from .tray import TrayApp
|
||||
|
||||
app = TrayApp(store)
|
||||
app.start()
|
||||
|
||||
# Maintient le processus en vie ; pystray run() tourne déjà dans un thread.
|
||||
# On attend l'arrêt déclenché par l'élément « Quitter » (via un Event).
|
||||
try:
|
||||
app.wait()
|
||||
# start() bloque (Icon.run() sur le thread principal) et retourne
|
||||
# quand l'utilisateur clique sur « Quitter ».
|
||||
app.start()
|
||||
except KeyboardInterrupt:
|
||||
app.stop()
|
||||
return 0
|
||||
|
||||
@@ -73,12 +73,16 @@ def setup_file_logging(level: int = logging.INFO) -> Path:
|
||||
handler = logging.handlers.RotatingFileHandler(
|
||||
path, maxBytes=2 * 1024 * 1024, backupCount=3, encoding="utf-8"
|
||||
)
|
||||
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
||||
handler.setFormatter(
|
||||
logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(name)s [tid:%(thread)d:%(threadName)s] %(message)s"
|
||||
)
|
||||
)
|
||||
logging.getLogger().addHandler(handler)
|
||||
return path
|
||||
|
||||
|
||||
def setup_logging(level: int = logging.INFO) -> Path:
|
||||
def setup_logging(level: int = logging.DEBUG) -> Path:
|
||||
broadcaster().install(level)
|
||||
return setup_file_logging(level)
|
||||
|
||||
|
||||
@@ -110,14 +110,21 @@ class TrayApp:
|
||||
return img
|
||||
|
||||
menu_items = []
|
||||
menu_items.append(self._menu_item("Ouvrir les logs", self.show_logs))
|
||||
menu_items.append(self._menu_item("Ajouter un profil", self.add_profile))
|
||||
menu_items.append(
|
||||
self._menu_item("Ouvrir les logs", self._guard("Ouvrir les logs", self.show_logs))
|
||||
)
|
||||
menu_items.append(
|
||||
self._menu_item("Ajouter un profil", self._guard("Ajouter un profil", self.add_profile))
|
||||
)
|
||||
# Sous-menu des profils
|
||||
profiles_sub = pystray.Menu(
|
||||
*[
|
||||
self._menu_item(
|
||||
p.name + (" ✓" if p.name == self.store.active_name else ""),
|
||||
lambda n=p.name: self.set_active(n),
|
||||
self._guard(
|
||||
f"sélection du profil « {p.name} »",
|
||||
self._select_profile_action(p),
|
||||
),
|
||||
)
|
||||
for p in self.store.get_all()
|
||||
]
|
||||
@@ -125,9 +132,14 @@ class TrayApp:
|
||||
menu_items.append(self._menu_item("Modifier le profil", None, submenu=profiles_sub))
|
||||
|
||||
menu_items.append(pystray.Menu.SEPARATOR)
|
||||
menu_items.append(self._menu_item("Gérer l'authentification", self.manage_auth))
|
||||
menu_items.append(
|
||||
self._menu_item(
|
||||
"Gérer l'authentification",
|
||||
self._guard("Gérer l'authentification", self.manage_auth),
|
||||
)
|
||||
)
|
||||
menu_items.append(pystray.Menu.SEPARATOR)
|
||||
menu_items.append(self._menu_item("Quitter", self.stop))
|
||||
menu_items.append(self._menu_item("Quitter", self._guard("Quitter", self.stop)))
|
||||
|
||||
if self._menu_factory:
|
||||
return self._menu_factory(_image, menu_items)
|
||||
@@ -143,12 +155,47 @@ class TrayApp:
|
||||
|
||||
if submenu is not None:
|
||||
return pystray.MenuItem(text, submenu)
|
||||
return pystray.MenuItem(text, action or (lambda icon, item: None))
|
||||
if action is None:
|
||||
action = lambda icon, item: None
|
||||
return pystray.MenuItem(text, action)
|
||||
|
||||
def _guard(self, label: str, action: Callable) -> Callable:
|
||||
"""Enveloppe une action de menu (sans argument requis par l'appelant).
|
||||
|
||||
pystray invoque les actions de menu avec ``(icon, item)`` et examine
|
||||
le nombre de paramètres : un handler à exactement 2 params est appelé
|
||||
tel quel (aucun reshufflage d'arguments). Ce wrapper journalise le
|
||||
déclenchement et capture toute exception avec traceback pour rendre
|
||||
chaque clic traçable.
|
||||
"""
|
||||
|
||||
def handler(icon, item) -> None:
|
||||
LOG.debug("Clic menu → %s", label)
|
||||
try:
|
||||
action()
|
||||
except Exception:
|
||||
LOG.exception("Erreur dans l'action de menu : %s", label)
|
||||
|
||||
return handler
|
||||
|
||||
def _select_profile_action(self, profile) -> Callable:
|
||||
def action() -> None:
|
||||
LOG.debug("Sélection du profil « %s » demandée", profile.name)
|
||||
self.set_active(profile.name)
|
||||
|
||||
return action
|
||||
|
||||
# -- cycle de vie -----------------------------------------------------------
|
||||
|
||||
def start(self) -> None:
|
||||
"""Attache le raccourci global et lance l'icône dans un thread."""
|
||||
"""Attache le raccourci global puis lance l'icône — BLoquant, sur le
|
||||
thread principal.
|
||||
|
||||
pystray exige que ``Icon.run()`` soit appelé depuis le thread principal
|
||||
; les callbacks de menu s'exécutent alors sur ce thread, ce qui est
|
||||
indispensable pour créer des fenêtres Tk. Retourne quand « Quitter »
|
||||
est demandé.
|
||||
"""
|
||||
hotkey = self.store.hotkey
|
||||
try:
|
||||
bind_hotkey(self.engine, hotkey)
|
||||
@@ -158,9 +205,9 @@ class TrayApp:
|
||||
|
||||
icon = self._icon if self._icon is not None else self.build_icon()
|
||||
self._icon = icon
|
||||
self._icon_thread = threading.Thread(target=icon.run, daemon=True)
|
||||
self._icon_thread.start()
|
||||
LOG.info("Application lancée en arrière-plan (icône zone de notification).")
|
||||
icon.run() # bloque sur le thread principal
|
||||
LOG.info("Sortie : la boucle de l'icône s'est terminée.")
|
||||
|
||||
def stop(self, icon=None, item=None) -> None:
|
||||
LOG.info("Arrêt de l'application.")
|
||||
|
||||
Reference in New Issue
Block a user