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:
@@ -2,21 +2,90 @@ import SwiftUI
|
||||
import OnyxDesign
|
||||
import OnyxNotch
|
||||
import OnyxWidgetKit
|
||||
import CalendarProvider
|
||||
|
||||
enum SettingsTab: Hashable {
|
||||
case widgets, display, permissions
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@Bindable var model: AppModel
|
||||
let calendarModel: CalendarModel
|
||||
@Binding var selectedTab: SettingsTab
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
TabView(selection: $selectedTab) {
|
||||
WidgetSettings(model: model)
|
||||
.tabItem { Label("settings.tab.widgets", systemImage: "square.grid.2x2") }
|
||||
.tag(SettingsTab.widgets)
|
||||
DisplaySettings(model: model)
|
||||
.tabItem { Label("settings.tab.display", systemImage: "macbook") }
|
||||
.tag(SettingsTab.display)
|
||||
PermissionSettings(calendarModel: calendarModel)
|
||||
.tabItem { Label("settings.tab.permissions", systemImage: "hand.raised") }
|
||||
.tag(SettingsTab.permissions)
|
||||
}
|
||||
.frame(width: 520, height: 420)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Berechtigungen
|
||||
|
||||
/// Zeigt den Zustand, statt ihn nur spürbar zu machen.
|
||||
///
|
||||
/// Eine verweigerte Berechtigung äußert sich sonst als leeres Widget, und der
|
||||
/// Weg zurück ist nicht offensichtlich: hat macOS die Anfrage einmal
|
||||
/// abgelehnt bekommen, fragt es nie wieder — es bleibt nur der Gang in die
|
||||
/// Systemeinstellungen.
|
||||
private struct PermissionSettings: View {
|
||||
let calendarModel: CalendarModel
|
||||
@State private var granted = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
Label("settings.permissions.calendar", systemImage: "calendar")
|
||||
Spacer()
|
||||
if granted {
|
||||
Label("settings.permissions.granted", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
.labelStyle(.titleAndIcon)
|
||||
} else {
|
||||
Button("settings.permissions.request") {
|
||||
Task {
|
||||
let ok = await calendarModel.requestAccessNow()
|
||||
granted = ok
|
||||
// macOS fragt nach einer Ablehnung nicht erneut.
|
||||
// Dann hilft nur noch der Weg über die
|
||||
// Systemeinstellungen — also dorthin führen.
|
||||
if !ok { openPrivacySettings() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("settings.permissions.calendar.why")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Section {
|
||||
Button("settings.permissions.openSystem") { openPrivacySettings() }
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.padding()
|
||||
.onAppear { granted = calendarModel.hasAccess }
|
||||
}
|
||||
|
||||
private func openPrivacySettings() {
|
||||
guard let url = URL(string:
|
||||
"x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars") else { return }
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Widgets
|
||||
|
||||
private struct WidgetSettings: View {
|
||||
|
||||
Reference in New Issue
Block a user