TCC zeigt den Dialog nur, wenn eine App im Vordergrund ist, an die er gehören
kann. Bei einer App ohne Fenster verpufft die Anfrage kommentarlos: sie kommt
mit false zurück, der Status bleibt notDetermined, und in den
Systemeinstellungen taucht die App nie auf — dort lässt sich auch nichts von
Hand hinzufügen, Einträge entstehen ausschließlich durch eine echte Anfrage.
Deshalb fragt der Reiter Berechtigungen jetzt selbst, sobald er sichtbar ist,
mit 400 ms Vorlauf: Aktivierung wirkt asynchron, ohne die Pause fragt Onyx
während es formal noch im Hintergrund liegt.
CalendarAuthorization trennt undetermined von denied. Im ersten Fall lohnt eine
Anfrage, im zweiten zeigt macOS nie wieder einen Dialog und es hilft nur der
Weg über die Systemeinstellungen — dorthin führt der Knopf dann auch.
Korrektur einer eigenen Fehldiagnose: promptPolicy = 0 im tccd-Protokoll gehört
zu kTCCServiceReminders, nicht zum Kalender. Der bekommt durchgehend
promptPolicy = 4, Nachfragen war also die ganze Zeit erlaubt. Die tatsächlichen
Ursachen waren die ausgelagerte Debug-dylib und ein verklemmter TCC-Eintrag.
Zwei Fehler, beide durch Messen gefunden statt durch Raten — und einer davon
war meine eigene fehlerhafte Diagnose: `log show` wurde von zsh abgefangen, ich
hatte stderr umgeleitet und die Fehlermeldung nie gesehen. Meine Aussage "keine
TCC-Einträge, also wurde nie gefragt" stützte sich damit auf einen Befehl, der
gar nicht lief.
Einstellungsfenster: SwiftUIs Settings-Szene wird über den privaten Selektor
showSettingsWindow: geöffnet, dessen Name sich zwischen macOS-Versionen schon
geändert hat und der bei .accessory-Apps ohne Menüleiste unzuverlässig ankommt.
Der Menüeintrag tat schlicht nichts. Jetzt ein eigenes NSWindow.
Kalenderberechtigung, zwei Ursachen übereinander:
Xcode lagert im Debug-Build den Programmcode in eine eigene dylib aus (für
SwiftUI-Vorschauen). Das Hauptprogramm ist dann nur ein Rumpf, und TCC ordnete
die Anfrage dem Rumpf zu — kTCCServiceCalendar erreichte tccd überhaupt nicht.
ENABLE_DEBUG_DYLIB = NO behebt das; danach erscheint die Anfrage im Protokoll.
Danach immer noch kein Dialog: promptType 1, aber promptPolicy 0. TCC bereitet
den Dialog vor und entscheidet sich dagegen, weil keine App im Vordergrund ist,
an die er gehören könnte. NSApp.activate davor hilft nicht — Aktivierung wirkt
asynchron und ist beim Aufruf noch nicht durch. Deshalb wird beim ersten Start
das Einstellungsfenster auf dem Reiter Berechtigungen geöffnet: dort steht ein
echtes Fenster im Vordergrund, und der Nutzer weiß, warum gefragt wird.
Folgefehler dabei: ohne Berechtigung zeigte das Widget "Nichts mehr für heute"
— eine Aussage, die es ohne Zugriff gar nicht treffen kann. refresh() fragt
jetzt nicht mehr selbst nach und meldet ohne Berechtigung ehrlich `denied`.
Fehler aus requestFullAccessToEvents werden nicht mehr mit try? verschluckt.
Einstellungen wirken sofort statt erst nach einem Neustart. In einer App ohne
Fenster ist eine verzögerte Änderung praktisch unauffindbar — man sieht ja
nicht, dass etwas passiert ist. AppModel meldet Layout- und Anzeigeänderungen
an den Koordinator, das Panel zeichnet sich neu und die Panelgröße zieht mit.
Widgets lassen sich hinzufügen, entfernen, in der Größe ändern und per
Drag-and-Drop umsortieren. Angeboten werden nur die Größen, die ein Widget
sinnvoll ausfüllt — ein Mini-Monat in 1x1 wäre unleserlich.
Lokalisierung als String Catalog in DE und EN, Quellsprache Englisch damit die
Schlüssel im Code lesbar bleiben. Der Katalog von OnyxCore musste ausdrücklich
als Ressource deklariert werden: ohne das gibt es kein Bundle.module und die
Übersetzungen wären zur Laufzeit unauffindbar. Im gebauten Bundle liegen jetzt
de.lproj und en.lproj, auch für das Paket.
Kein Menüleisten-Tab: solange es keine Module gibt, wäre das eine Oberfläche
für ein Feature, das noch nicht existiert. Er kommt mit Phase 5.
80 Tests grün.