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:
@@ -15,15 +15,10 @@ struct OnyxApp: App {
|
||||
// Onyx hat kein Hauptfenster — alles lebt in der Notch und in der
|
||||
// Menüleiste. Die Einstellungen sind die einzige Ausnahme; im Panel
|
||||
// wäre dafür zu wenig Platz.
|
||||
Settings {
|
||||
if let model = delegate.model {
|
||||
SettingsView(model: model)
|
||||
} else {
|
||||
// Nur sichtbar, wenn die Einzelinstanz-Prüfung uns abgewiesen
|
||||
// hat und die App gerade wieder verschwindet.
|
||||
ProgressView().frame(width: 520, height: 420)
|
||||
}
|
||||
}
|
||||
// Eine Szene ist Pflicht, benutzt wird sie nicht: das
|
||||
// Einstellungsfenster führt SettingsWindowController selbst.
|
||||
// Siehe die Begründung dort.
|
||||
Settings { EmptyView() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +30,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private var menuBar: MenuBarController?
|
||||
private var onyxItem: NSStatusItem?
|
||||
private var calendarModel: CalendarModel?
|
||||
private let settingsWindow = SettingsWindowController()
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
@@ -78,6 +74,38 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
installOnyxStatusItem()
|
||||
observeMenuBarPreference()
|
||||
requestPermissions()
|
||||
}
|
||||
|
||||
/// Berechtigungen beim Start anfragen, nicht erst wenn ein Widget sichtbar
|
||||
/// wird.
|
||||
///
|
||||
/// Das Panel ist beim Start zu. Eine an die View gebundene Anfrage kommt
|
||||
/// deshalb erst, wenn jemand die Notch berührt — und bis dahin taucht Onyx
|
||||
/// in den Systemeinstellungen gar nicht auf, es gibt also nichts, das man
|
||||
/// dort freischalten könnte. Genau dieser Zustand ist beim Testen
|
||||
/// aufgetreten.
|
||||
private func requestPermissions() {
|
||||
guard let calendarModel else { return }
|
||||
// Immer einmal laden: mit Berechtigung kommen Termine, ohne sie zeigt
|
||||
// das Widget den Berechtigungshinweis statt einer leeren Liste.
|
||||
calendarModel.refresh()
|
||||
guard !calendarModel.hasAccess else { return }
|
||||
|
||||
// Nicht im Hintergrund anfragen.
|
||||
//
|
||||
// Onyx ist eine App ohne Fenster. TCC bereitet den Dialog zwar vor
|
||||
// (promptType 1), entscheidet sich dann aber dagegen (promptPolicy 0),
|
||||
// weil keine App im Vordergrund ist, an die er gehören könnte. Die
|
||||
// Anfrage kommt kommentarlos mit `false` zurück, der Status bleibt
|
||||
// `notDetermined` — und in den Systemeinstellungen taucht Onyx nie auf.
|
||||
// `NSApp.activate` davor hilft nicht: Aktivierung wirkt asynchron und
|
||||
// ist beim Aufruf noch nicht durch.
|
||||
//
|
||||
// Deshalb der ehrliche Weg: beim ersten Start das Einstellungsfenster
|
||||
// öffnen. Dort ist ein echtes Fenster im Vordergrund, der Dialog
|
||||
// erscheint zuverlässig — und der Nutzer weiß, warum gefragt wird.
|
||||
showSettings(tab: .permissions)
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ notification: Notification) {
|
||||
@@ -140,9 +168,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@objc private func togglePanel() { coordinator?.togglePanelUnderPointer() }
|
||||
|
||||
@objc private func openSettings() {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
|
||||
@objc private func openSettings() { showSettings(tab: .widgets) }
|
||||
|
||||
private func showSettings(tab: SettingsTab) {
|
||||
guard let model, let calendarModel else { return }
|
||||
settingsWindow.show(model: model, calendarModel: calendarModel, tab: tab)
|
||||
}
|
||||
|
||||
@objc private func quit() { NSApp.terminate(nil) }
|
||||
|
||||
Reference in New Issue
Block a user