fix: icône barre de menu sur Tahoe — stub libpython + abandon de rumps
Sur macOS Tahoe (26.5), le NSStatusItem n'est jamais placé (fenêtre h=0) si : 1. l'exécutable du processus n'appartient pas au bundle lancé (le python framework se ré-exécute → identité dépareillée) → stub C qui dlopen libpython et exécute app.py DANS le processus du bundle ; 2. l'item est créé par rumps (cause interne non isolée, l'API native fonctionne) → app.py réécrit en pyobjc pur (NSStatusItem + NSMenu). Le stub transmet argv à python quand il y en a (workers multiprocessing de MLX) — sinon bombe à fork (9 instances observées). PYTHONUNBUFFERED pour que les print atteignent ~/Library/Logs/Zonza.log. Auto-diagnostic du placement de l'icône au démarrage. rumps retiré des dépendances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/* Stub de lancement Zonza — embarque libpython DANS le processus du bundle.
|
||||
*
|
||||
* Pourquoi 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é par LaunchServices
|
||||
* (le python framework se ré-exécute dans son Python.app interne → identité
|
||||
* dépareillée → icône créée mais jamais affichée, fenêtre de hauteur 0).
|
||||
* Vérifié par tests A/B/C/D le 2026-06-13 : seul un exécutable du bundle qui
|
||||
* garde son image (dlopen de libpython, pas d'exec) obtient un emplacement.
|
||||
*
|
||||
* Compilé par build_app.sh avec les chemins injectés en -D :
|
||||
* PYTHON_DYLIB — dylib du framework Python (Homebrew)
|
||||
* APP_PY — chemin absolu de app.py dans le dépôt
|
||||
* VENV_SITE — site-packages du venv du dépôt
|
||||
*/
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int (*py_bytes_main_t)(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* logs de l'app → ~/Library/Logs/Zonza.log (le bundle n'a pas de terminal) */
|
||||
const char *home = getenv("HOME");
|
||||
if (home) {
|
||||
char logpath[1024];
|
||||
snprintf(logpath, sizeof logpath, "%s/Library/Logs/Zonza.log", home);
|
||||
freopen(logpath, "a", stdout);
|
||||
freopen(logpath, "a", stderr);
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
setvbuf(stderr, NULL, _IOLBF, 0);
|
||||
}
|
||||
|
||||
/* le venv n'est pas détecté (pas de pyvenv.cfg à côté de notre binaire) :
|
||||
* on ajoute ses site-packages via PYTHONPATH avant l'init de python. */
|
||||
setenv("PYTHONPATH", VENV_SITE, 1);
|
||||
/* sans tty, python bufferise stdout → les print n'atteindraient le log qu'à la
|
||||
* sortie du processus. */
|
||||
setenv("PYTHONUNBUFFERED", "1", 1);
|
||||
|
||||
void *lib = dlopen(PYTHON_DYLIB, RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!lib) {
|
||||
fprintf(stderr, "zonza: dlopen libpython a échoué : %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
py_bytes_main_t py_main = (py_bytes_main_t)dlsym(lib, "Py_BytesMain");
|
||||
if (!py_main) {
|
||||
fprintf(stderr, "zonza: Py_BytesMain introuvable : %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
/* CRITIQUE anti bombe-à-fork : multiprocessing (MLX/numpy) relance
|
||||
* sys.executable — c'est-à-dire CE stub — avec ses propres arguments
|
||||
* (-c … --multiprocessing-fork). Si on forçait app.py inconditionnellement,
|
||||
* chaque worker relancerait l'app entière (vérifié : 9 instances).
|
||||
* Donc : des arguments présents → on les transmet tels quels à python ;
|
||||
* aucun argument (lancement normal par l'utilisateur) → on lance app.py. */
|
||||
if (argc > 1) {
|
||||
return py_main(argc, argv);
|
||||
}
|
||||
char *py_argv[] = {argv[0], APP_PY, NULL};
|
||||
return py_main(2, py_argv);
|
||||
}
|
||||
Reference in New Issue
Block a user