zonza/app.py
Ralph Mayola 2b0d81796b fix: icône de barre de menu absente sur macOS Tahoe
setActivationPolicy_(Accessory) appelé avant rumps.run() empêche la
création du NSStatusItem sur Tahoe (vérifié par test A/B). Retiré :
LSUIElement dans l'Info.plist suffit pour masquer l'icône Dock.
Le lanceur redirige aussi stdout/stderr vers ~/Library/Logs/Zonza.log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 00:14:15 +02:00

86 lines
2.9 KiB
Python

"""Point d'entrée : application macOS de barre de menu (rumps).
Possède la boucle d'événements Cocoa. Enregistre la hotkey globale, gère l'état du menu,
lance le warmup du modèle en fond, et instancie le contrôleur + la fenêtre de pulsation.
Zonza vit dans la barre de menu, jamais dans le Dock : c'est le LSUIElement de
l'Info.plist (posé par build_app.sh) qui s'en charge.
"""
import threading
import rumps
from PyObjCTools import AppHelper
from pynput import keyboard
from controller import ZonzaController
from core import CONFIG, validate_config
from pulse_window import PulseWindow
_STATE_TITLES = {
"idle": "🎙️",
"recording": "🔴",
"transcribing": "",
}
class ZonzaApp(rumps.App):
def __init__(self):
super().__init__("🎙️", quit_button=None)
self.pulse = PulseWindow.alloc().init()
self.controller = ZonzaController(
CONFIG, self.pulse, on_state=self._set_state,
run_on_main=AppHelper.callAfter,
)
self.menu = [
rumps.MenuItem("Sons", callback=self._toggle_sounds),
None,
rumps.MenuItem("Quitter", callback=self._quit),
]
self.menu["Sons"].state = CONFIG["sounds"]
self._start_hotkey()
self._warmup_async()
def _set_state(self, state):
# appelé depuis des threads de fond (transcription) ; toucher le titre du menu
# (AppKit) doit se faire sur le thread principal → on marshale via callAfter.
AppHelper.callAfter(self._apply_title, state)
def _apply_title(self, state):
self.title = _STATE_TITLES.get(state, "🎙️")
def _toggle_sounds(self, sender):
CONFIG["sounds"] = not CONFIG["sounds"]
sender.state = CONFIG["sounds"]
def _quit(self, _):
rumps.quit_application()
def _start_hotkey(self):
# pynput exécute ce callback sur SON thread d'écoute, pas le thread principal.
# toggle() touche NSWindow/NSTimer (Cocoa) → on marshale sur le thread principal.
def on_activate():
AppHelper.callAfter(self.controller.toggle)
self._listener = keyboard.GlobalHotKeys({CONFIG["hotkey"]: on_activate})
self._listener.start()
def _warmup_async(self):
def run():
try:
self.controller.transcriber.warmup()
except Exception as e:
print(f"[erreur] warmup modèle : {e}")
rumps.notification("Zonza", "Modèle", f"Échec du chargement : {e}")
threading.Thread(target=run, daemon=True).start()
def main():
validate_config(CONFIG)
# PAS de setActivationPolicy_ ici : sur macOS Tahoe, forcer la politique avant
# run() empêche rumps de créer l'icône de barre de menu. Le LSUIElement de
# l'Info.plist suffit à masquer l'icône Dock pour l'app installée.
ZonzaApp().run()
if __name__ == "__main__":
main()