53 lines
1.9 KiB
Swift
53 lines
1.9 KiB
Swift
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)
|
|
}
|
|
}
|