import ApplicationServices import Foundation import os /// Requests and polls for the Accessibility (TCC) grant FocusFixer needs /// to observe events and drive the AX API. final class PermissionManager { private let logger = Logger(subsystem: "com.local.focusfixer", category: "PermissionManager") private var pollTimer: Timer? var isTrusted: Bool { AXIsProcessTrusted() } /// Shows the system Accessibility prompt (if not already trusted) and /// polls until the grant appears, then calls `onGranted` on the main /// queue. Safe to call repeatedly; a prior poll is cancelled first. func waitUntilTrusted(pollInterval: TimeInterval = 1.0, onGranted: @escaping () -> Void) { stopPolling() if isTrusted { onGranted() return } let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary _ = AXIsProcessTrustedWithOptions(options) logger.info("Accessibility permission not granted yet; prompting and polling") pollTimer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] timer in guard let self else { timer.invalidate() return } guard self.isTrusted else { return } timer.invalidate() self.pollTimer = nil self.logger.info("Accessibility permission granted") onGranted() } } func stopPolling() { pollTimer?.invalidate() pollTimer = nil } }