Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit 32b8282

Browse files
Harden microphone permission resolution
Resolve macOS microphone status without letting undetermined states mask denied access, and retry both app-level and capture-device request paths when permission APIs disagree.
1 parent 7a2a66c commit 32b8282

1 file changed

Lines changed: 76 additions & 41 deletions

File tree

src-tauri/swift-permissions/src/lib.swift

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -41,59 +41,42 @@ private func mapAudioApplicationPermission(_ permission: AVAudioApplication.reco
4141
}
4242
}
4343

44-
private func currentMicrophonePermissionStatus() -> Int {
45-
let captureStatus = mapCaptureMicrophoneStatus(AVCaptureDevice.authorizationStatus(for: .audio))
46-
47-
if #available(macOS 14.0, *) {
48-
let appStatus = mapAudioApplicationPermission(AVAudioApplication.shared.recordPermission)
44+
private func captureMicrophonePermissionStatus() -> Int {
45+
mapCaptureMicrophoneStatus(AVCaptureDevice.authorizationStatus(for: .audio))
46+
}
4947

50-
if appStatus == grantedValue || captureStatus == grantedValue {
51-
return grantedValue
52-
}
48+
private func combinedMicrophonePermissionStatus(appStatus: Int, captureStatus: Int) -> Int {
49+
if appStatus == grantedValue || captureStatus == grantedValue {
50+
return grantedValue
51+
}
5352

54-
if appStatus == neverRequestedValue || captureStatus == neverRequestedValue {
55-
return neverRequestedValue
56-
}
53+
if appStatus == errorValue {
54+
return captureStatus
55+
}
5756

58-
return appStatus == errorValue ? captureStatus : appStatus
57+
if captureStatus == errorValue {
58+
return appStatus
5959
}
6060

61-
return captureStatus
62-
}
61+
if appStatus == deniedValue || captureStatus == deniedValue {
62+
return deniedValue
63+
}
6364

64-
@_cdecl("_microphone_permission_status")
65-
public func _microphone_permission_status() -> Int {
66-
currentMicrophonePermissionStatus()
65+
return neverRequestedValue
6766
}
6867

69-
@_cdecl("_request_microphone_permission")
70-
public func _request_microphone_permission() -> Bool {
71-
if currentMicrophonePermissionStatus() == grantedValue {
72-
return true
73-
}
68+
private func currentMicrophonePermissionStatus() -> Int {
69+
let captureStatus = captureMicrophonePermissionStatus()
7470

7571
if #available(macOS 14.0, *) {
76-
switch AVAudioApplication.shared.recordPermission {
77-
case .granted:
78-
return true
79-
case .denied:
80-
return currentMicrophonePermissionStatus() == grantedValue
81-
case .undetermined:
82-
let semaphore = DispatchSemaphore(value: 0)
83-
var granted = false
84-
85-
AVAudioApplication.requestRecordPermission { allowed in
86-
granted = allowed
87-
semaphore.signal()
88-
}
89-
90-
_ = semaphore.wait(timeout: .now() + .seconds(60))
91-
return granted || currentMicrophonePermissionStatus() == grantedValue
92-
@unknown default:
93-
return currentMicrophonePermissionStatus() == grantedValue
94-
}
72+
let appStatus = mapAudioApplicationPermission(AVAudioApplication.shared.recordPermission)
73+
return combinedMicrophonePermissionStatus(appStatus: appStatus, captureStatus: captureStatus)
9574
}
9675

76+
return captureStatus
77+
}
78+
79+
private func requestCaptureMicrophonePermission() -> Bool {
9780
let status = AVCaptureDevice.authorizationStatus(for: .audio)
9881

9982
switch status {
@@ -117,6 +100,58 @@ public func _request_microphone_permission() -> Bool {
117100
}
118101
}
119102

103+
@available(macOS 14.0, *)
104+
private func requestAppMicrophonePermission() -> Bool {
105+
switch AVAudioApplication.shared.recordPermission {
106+
case .granted:
107+
return true
108+
case .denied:
109+
return false
110+
case .undetermined:
111+
let semaphore = DispatchSemaphore(value: 0)
112+
var granted = false
113+
114+
AVAudioApplication.requestRecordPermission { allowed in
115+
granted = allowed
116+
semaphore.signal()
117+
}
118+
119+
_ = semaphore.wait(timeout: .now() + .seconds(60))
120+
return granted
121+
@unknown default:
122+
return false
123+
}
124+
}
125+
126+
@_cdecl("_microphone_permission_status")
127+
public func _microphone_permission_status() -> Int {
128+
currentMicrophonePermissionStatus()
129+
}
130+
131+
@_cdecl("_request_microphone_permission")
132+
public func _request_microphone_permission() -> Bool {
133+
if currentMicrophonePermissionStatus() == grantedValue {
134+
return true
135+
}
136+
137+
if #available(macOS 14.0, *) {
138+
let appStatus = mapAudioApplicationPermission(AVAudioApplication.shared.recordPermission)
139+
if appStatus == neverRequestedValue && requestAppMicrophonePermission() {
140+
return true
141+
}
142+
143+
if currentMicrophonePermissionStatus() == grantedValue {
144+
return true
145+
}
146+
}
147+
148+
if requestCaptureMicrophonePermission() {
149+
return true
150+
}
151+
152+
return currentMicrophonePermissionStatus() == grantedValue
153+
}
154+
120155
@_cdecl("_audio_capture_permission_status")
121156
public func _audio_capture_permission_status() -> Int {
122157
guard let tccHandle,

0 commit comments

Comments
 (0)