#!/bin/bash # Construit Zonza.app — un stub compilé qui embarque libpython dans le processus. # # Pourquoi un stub C (et pas un script bash qui exec python) : sur macOS Tahoe (26), # le gestionnaire de barre de menu refuse de placer le NSStatusItem d'un processus # dont l'exécutable principal n'appartient pas au bundle lancé (le python framework # se ré-exécute → identité dépareillée → icône invisible, fenêtre de hauteur 0). # Le stub dlopen libpython et exécute app.py DANS le processus du bundle : l'identité # est cohérente et l'icône s'affiche. (Validé par tests le 2026-06-13.) # # Pourquoi pas PyInstaller/py2app : empaqueter MLX (shaders Metal) échoue ; le code # tourne donc depuis le dépôt + son venv (ne pas les supprimer). # # RÈGLE DE PROPRETÉ : ce script est l'UNIQUE chemin de build. Il construit DIRECTEMENT # dans /Applications — jamais dans /tmp ni dans le dépôt (l'historique de builds # éparpillés est ce qui avait créé des icônes en double avec l'ancienne app Dictée). # # Usage : ./build_app.sh → installe directement /Applications/Zonza.app set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" APP="/Applications/Zonza.app" CONTENTS="$APP/Contents" PYBIN="$HERE/.venv/bin/python3" [ -x "$PYBIN" ] || { echo "Erreur : venv introuvable ($PYBIN). Lance : python3 -m venv .venv && .venv/bin/pip install -r requirements.txt"; exit 1; } # chemins python injectés dans le stub PYDYLIB="$("$PYBIN" -c "import sys; print(sys.base_prefix)")/../../Python" PYDYLIB="$(cd "$(dirname "$PYDYLIB")" && pwd)/Python" SITE="$("$PYBIN" -c "import sysconfig; print(sysconfig.get_paths()['purelib'])")" [ -f "$PYDYLIB" ] || { echo "Erreur : dylib python introuvable ($PYDYLIB)"; exit 1; } # icône (générée si absente) [ -f "$HERE/assets/icon.icns" ] || { "$PYBIN" "$HERE/assets/make_icon.py" && "$HERE/assets/make_icns.sh"; } echo "→ construction de $APP …" rm -rf "$APP" mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources" # stub compilé : libpython embarqué, exécutable = celui du bundle clang -O2 \ -DPYTHON_DYLIB="\"$PYDYLIB\"" \ -DAPP_PY="\"$HERE/app.py\"" \ -DVENV_SITE="\"$SITE\"" \ "$HERE/launcher/stub.c" -o "$CONTENTS/MacOS/zonza" # icône cp "$HERE/assets/icon.icns" "$CONTENTS/Resources/icon.icns" # Info.plist : app de barre de menu (pas d'icône Dock) + permission micro + icône cat > "$CONTENTS/Info.plist" <<'PLIST' CFBundleNameZonza CFBundleDisplayNameZonza CFBundleIdentifiercloud.mrtechlab.zonza CFBundleVersion1.1 CFBundleShortVersionString1.1 CFBundlePackageTypeAPPL CFBundleExecutablezonza CFBundleIconFileicon LSUIElement NSMicrophoneUsageDescription Zonza a besoin du micro pour transcrire votre voix localement. PLIST # signature ad-hoc avec identifiant stable : les permissions TCC (Accessibilité, # Surveillance de l'entrée, Micro) restent acquises d'un rebuild à l'autre. codesign --force --sign - --identifier "cloud.mrtechlab.zonza" "$APP" # enregistrement propre dans LaunchServices LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister "$LSREGISTER" -f "$APP" 2>/dev/null || true touch "$APP" echo "✓ $APP installé." echo " Au premier lancement, accorde à « Zonza » : Accessibilité + Surveillance de" echo " l'entrée (Réglages > Confidentialité). Logs : ~/Library/Logs/Zonza.log"