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:
56
Onyx/SettingsWindowController.swift
Normal file
56
Onyx/SettingsWindowController.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
import CalendarProvider
|
||||
|
||||
/// Führt das Einstellungsfenster selbst.
|
||||
///
|
||||
/// SwiftUIs `Settings`-Szene wird über `NSApp.sendAction(showSettingsWindow:)`
|
||||
/// geöffnet — ein privater Selektor, dessen Name sich zwischen macOS-Versionen
|
||||
/// schon geändert hat (`showPreferencesWindow:` davor) und der bei
|
||||
/// `.accessory`-Apps ohne Menüleiste unzuverlässig ankommt. Genau das ist hier
|
||||
/// passiert: der Menüeintrag tat nichts.
|
||||
///
|
||||
/// Ein eigenes `NSWindow` hat diese Abhängigkeit nicht. Es kostet ein paar
|
||||
/// Zeilen mehr und funktioniert dafür.
|
||||
@MainActor
|
||||
final class SettingsWindowController: NSObject, NSWindowDelegate {
|
||||
|
||||
private var window: NSWindow?
|
||||
/// Muss außerhalb der View liegen, damit ein erneutes Öffnen den Reiter setzen kann.
|
||||
private var selectedTab: SettingsTab = .widgets
|
||||
|
||||
func show(model: AppModel, calendarModel: CalendarModel, tab: SettingsTab = .widgets) {
|
||||
selectedTab = tab
|
||||
|
||||
if window == nil {
|
||||
let hosting = NSHostingController(
|
||||
rootView: SettingsView(model: model,
|
||||
calendarModel: calendarModel,
|
||||
selectedTab: Binding(
|
||||
get: { [weak self] in self?.selectedTab ?? .widgets },
|
||||
set: { [weak self] in self?.selectedTab = $0 })))
|
||||
|
||||
let window = NSWindow(contentViewController: hosting)
|
||||
window.title = "Onyx"
|
||||
window.styleMask = [.titled, .closable, .miniaturizable]
|
||||
window.isReleasedWhenClosed = false
|
||||
window.delegate = self
|
||||
window.center()
|
||||
// Über die Position freuen sich Nutzer mit mehreren Displays:
|
||||
// beim zweiten Öffnen steht es wieder da, wo sie es hingeschoben haben.
|
||||
window.setFrameAutosaveName("onyx.settings")
|
||||
self.window = window
|
||||
}
|
||||
|
||||
// Ohne `activate` bleibt das Fenster einer .accessory-App hinter der
|
||||
// aktiven App liegen und wirkt, als wäre nichts passiert.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window?.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
// Zurück in den Hintergrund: Onyx hat sonst weiter den Fokus, obwohl
|
||||
// kein Fenster mehr offen ist.
|
||||
NSApp.hide(nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user