Berechtigungsdialog und Einstellungsfenster reparieren

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.
This commit is contained in:
Guido Schmit
2026-08-10 18:20:21 +02:00
parent 70d4d67cca
commit 40bd83e49e
11 changed files with 774 additions and 141 deletions

View File

@@ -1,7 +1,10 @@
import Foundation
import OSLog
import EventKit
import AppKit
private let log = Logger(subsystem: "com.scarriffleservices.onyx", category: "EventKit")
/// Termine aus dem Apple-Kalender. Ausschließlich lesend.
public final class EventKitSource: CalendarSource, @unchecked Sendable {
@@ -13,6 +16,10 @@ public final class EventKitSource: CalendarSource, @unchecked Sendable {
public init() {}
public var isAuthorized: Bool {
EKEventStore.authorizationStatus(for: .event) == .fullAccess
}
public func requestAccess() async -> Bool {
switch EKEventStore.authorizationStatus(for: .event) {
case .fullAccess:
@@ -22,7 +29,15 @@ public final class EventKitSource: CalendarSource, @unchecked Sendable {
case .notDetermined, .writeOnly:
// `writeOnly` ist für uns wertlos Onyx zeigt nur an. Der Nutzer
// muss den Vollzugriff geben, sonst bleibt das Widget leer.
return (try? await store.requestFullAccessToEvents()) ?? false
do {
return try await store.requestFullAccessToEvents()
} catch {
// Nicht verschlucken: wenn EventKit hier wirft, erscheint gar
// kein Dialog und der Zustand bleibt `notDetermined` von außen
// sieht das aus, als hätte die App nie gefragt.
log.error("requestFullAccessToEvents warf: \(error.localizedDescription, privacy: .public)\(String(describing: error), privacy: .public)")
return false
}
@unknown default:
return false
}