tray.py
Rama main · réplica de lectura del repositorio original.
"""Icone de zone de notification pour l'instance « start » : ouvrir,
mettre a jour, redemarrer, arreter et creer un raccourci sans repasser par
le terminal ni la page web.
Le menu commun aux quatre applications (blink2video, lidar2map, watch2notif,
gpxsolar), son rafraichissement sur le fil principal sous macOS (issue #31)
et le fil qui porte Redemarrer/Arreter/Mettre a jour jusqu'a leur terme
(course du 2026-09-03 : « Redemarrer » arretait sans relancer) viennent de
nico579_commons.tray, nee de ce fichier. Ne restent ici que les actions
propres a blink2video.
pystray (et donc nico579_commons.tray) choisit win32 sous Windows, AppKit
sous macOS, AppIndicator/GTK sous Linux. Sans serveur graphique (SSH,
machine headless, conteneur) ou sans ces paquets (execution depuis les
sources, ou ils ne sont pas requis), on continue sans icone, jamais en
erreur bloquante : degrader plutot que planter, comme resource_dir() ou
app_dir().
Redemarrer/Arreter passent par nettoyer(), fourni par blink_cli.py, qui
arrete directement les verbes lances ; Redemarrer relance ensuite par
« blink2video restart », le mecanisme du bouton de la page de reglages
(serve.py, /api/redemarrer).
« Mettre a jour », quand une version plus recente existe, passe par
« blink2video update », le mecanisme du bouton de mise a jour de la page
(serve.py, /api/update). `maj.disponible(reseau=False)` ne lit que le cache
deja entretenu par le thread de fond de serve.py : ouvrir le menu
n'interroge jamais GitHub soi-meme."""
import os
import subprocess
import threading
import webbrowser
import maj
import raccourci_bureau
import runtime
def disponible() -> bool:
"""Faux si nico579_commons, pystray ou Pillow ne se chargent pas ici :
paquets absents (sources), ou aucun backend de zone de notification
(Linux sans AppIndicator/GTK, session sans affichage)."""
try:
from nico579_commons import tray as commun
except Exception:
return False
return commun.disponible()
def _relancer(sans_relance: bool) -> None:
arguments = ("restart", "--sans-relance") if sans_relance else ("restart",)
runtime.demarrer(
runtime.self_command(*arguments), cwd=str(runtime.app_dir()),
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT, start_new_session=(os.name != "nt"))
def _mettre_a_jour() -> None:
# Détaché : ce processus fait partie de ce que la mise à jour va arrêter.
runtime.demarrer(
runtime.self_command("update"), cwd=str(runtime.app_dir()),
stdin=subprocess.DEVNULL,
stdout=(runtime.app_dir() / "maj.log").open("ab"),
stderr=subprocess.STDOUT, start_new_session=(os.name != "nt"))
def _version_disponible():
return (maj.disponible(reseau=False) or {}).get("version")
def executer(port: int, arret: threading.Event, nettoyer) -> None:
"""Bloque sur la boucle de l'icone, thread principal exige sous macOS.
`arret` : leve par l'appelant quand un verbe surveille meurt de
lui-meme (crash) ; l'icone se referme alors pour rendre la main au
nettoyage habituel.
`nettoyer` : arrete directement, dans ce meme processus, les verbes que
l'appelant a lances (voir nettoyer_lances(), blink_cli.py). Redemarrer
et Arreter l'appellent en synchrone plutot que de s'en remettre a un
« blink2video restart » detache : constate en reel sur Windows 7, l'icone
pouvait disparaitre sans que rien ne s'arrete derriere (bug 6, revue du
27/08). executer() ne rend la main qu'une fois ce travail fini (voir
nico579_commons.tray.Tray.executer)."""
from nico579_commons import tray as commun
adresse = f"http://127.0.0.1:{port}/"
def redemarrer():
nettoyer()
_relancer(sans_relance=False)
actions = commun.Actions(
ouvrir=lambda: webbrowser.open(adresse),
redemarrer=redemarrer,
arreter=nettoyer,
version_disponible=_version_disponible,
mettre_a_jour=_mettre_a_jour,
creer_raccourci=raccourci_bureau.creer,
langue=runtime.lire_langue,
)
icone = runtime.resource_dir() / "assets" / "blink2video.ico"
commun.Tray("blink2video", icone, actions, arret=arret).executer()