import AVFoundation import AppKit import ApplicationServices import Foundation /// Edison Voice needs two grants, and neither can be worked around: /// - **Microphone** — obviously. /// - **Accessibility** — for both the `CGEventTap` (hotkey) and the AX text insert. /// /// Accessibility has no programmatic request; the OS only shows the prompt, and the user /// must toggle it in System Settings. TCC also keys on the code signature, so re-signing /// the app resets the grant. @MainActor public enum Permissions { public static var hasAccessibility: Bool { AXIsProcessTrusted() } public static var hasMicrophone: Bool { AVCaptureDevice.authorizationStatus(for: .audio) == .authorized } /// Shows the system Accessibility prompt if the app isn't yet trusted. @discardableResult public static func promptForAccessibility() -> Bool { // Spelled out rather than using `kAXTrustedCheckOptionPrompt`, which imports as a // mutable global and so isn't usable from concurrency-checked code. let options = ["AXTrustedCheckOptionPrompt": true] as CFDictionary return AXIsProcessTrustedWithOptions(options) } public static func requestMicrophone() async -> Bool { switch AVCaptureDevice.authorizationStatus(for: .audio) { case .authorized: return true case .notDetermined: return await AVCaptureDevice.requestAccess(for: .audio) default: return false } } public static func openAccessibilitySettings() { let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")! NSWorkspace.shared.open(url) } public static func openMicrophoneSettings() { let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone")! NSWorkspace.shared.open(url) } }