import AudioCap's AudioRecordingPermission class
This commit is contained in:
+6
-1
@@ -9,6 +9,11 @@ let package = Package(
|
||||
.macOS("14.2")
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(name: "audiotee")
|
||||
.executableTarget(
|
||||
name: "audiotee",
|
||||
swiftSettings: [
|
||||
.define("ENABLE_TCC_SPI")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import OSLog
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
// Adapted with a huge debt of gratitude from https://github.com/insidegui/AudioCap/blob/main/AudioCap/ProcessTap/AudioRecordingPermission.swift
|
||||
|
||||
/// Uses TCC SPI in order to check/request system audio recording permission.
|
||||
@Observable
|
||||
final class AudioRecordingPermission {
|
||||
// private let logger = Logger(subsystem: kAppSubsystem, category: String(describing: AudioRecordingPermission.self))
|
||||
|
||||
enum Status: String {
|
||||
case unknown
|
||||
case denied
|
||||
case authorized
|
||||
}
|
||||
|
||||
private(set) var status: Status = .unknown
|
||||
|
||||
init() {
|
||||
#if ENABLE_TCC_SPI
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: NSApplication.didBecomeActiveNotification, object: nil, queue: .main
|
||||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.updateStatus()
|
||||
}
|
||||
|
||||
updateStatus()
|
||||
#else
|
||||
status = .authorized
|
||||
#endif // ENABLE_TCC_SPI
|
||||
}
|
||||
|
||||
func request() {
|
||||
#if ENABLE_TCC_SPI
|
||||
// logger.debug(#function)
|
||||
|
||||
guard let request = Self.requestSPI else {
|
||||
// logger.fault("Request SPI missing")
|
||||
return
|
||||
}
|
||||
|
||||
request("kTCCServiceAudioCapture" as CFString, nil) { [weak self] granted in
|
||||
guard let self else { return }
|
||||
|
||||
// self.logger.info("Request finished with result: \(granted, privacy: .public)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if granted {
|
||||
self.status = .authorized
|
||||
} else {
|
||||
self.status = .denied
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_TCC_SPI
|
||||
}
|
||||
|
||||
private func updateStatus() {
|
||||
#if ENABLE_TCC_SPI
|
||||
// logger.debug(#function)
|
||||
|
||||
guard let preflight = Self.preflightSPI else {
|
||||
// logger.fault("Preflight SPI missing")
|
||||
return
|
||||
}
|
||||
|
||||
let result = preflight("kTCCServiceAudioCapture" as CFString, nil)
|
||||
|
||||
if result == 1 {
|
||||
status = .denied
|
||||
} else if result == 0 {
|
||||
status = .authorized
|
||||
} else {
|
||||
status = .unknown
|
||||
}
|
||||
#endif // ENABLE_TCC_SPI
|
||||
}
|
||||
|
||||
#if ENABLE_TCC_SPI
|
||||
private typealias PreflightFuncType = @convention(c) (CFString, CFDictionary?) -> Int
|
||||
private typealias RequestFuncType = @convention(c) (
|
||||
CFString, CFDictionary?, @escaping (Bool) -> Void
|
||||
) -> Void
|
||||
|
||||
/// `dlopen` handle to the TCC framework.
|
||||
private static let apiHandle: UnsafeMutableRawPointer? = {
|
||||
let tccPath = "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC"
|
||||
|
||||
guard let handle = dlopen(tccPath, RTLD_NOW) else {
|
||||
assertionFailure("dlopen failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
return handle
|
||||
}()
|
||||
|
||||
/// `dlsym` function handle for `TCCAccessPreflight`.
|
||||
private static let preflightSPI: PreflightFuncType? = {
|
||||
guard let apiHandle else { return nil }
|
||||
|
||||
let fnName = "TCCAccessPreflight"
|
||||
|
||||
guard let funcSym = dlsym(apiHandle, fnName) else {
|
||||
assertionFailure("Couldn't find symbol")
|
||||
return nil
|
||||
}
|
||||
|
||||
let fn = unsafeBitCast(funcSym, to: PreflightFuncType.self)
|
||||
|
||||
return fn
|
||||
}()
|
||||
|
||||
/// `dlsym` function handle for `TCCAccessRequest`.
|
||||
private static let requestSPI: RequestFuncType? = {
|
||||
guard let apiHandle else { return nil }
|
||||
|
||||
let fnName = "TCCAccessRequest"
|
||||
|
||||
guard let funcSym = dlsym(apiHandle, fnName) else {
|
||||
assertionFailure("Couldn't find symbol")
|
||||
return nil
|
||||
}
|
||||
|
||||
let fn = unsafeBitCast(funcSym, to: RequestFuncType.self)
|
||||
|
||||
return fn
|
||||
}()
|
||||
#endif // ENABLE_TCC_SPI
|
||||
}
|
||||
Reference in New Issue
Block a user