Dispatch the Mail permission prompt via DispatchQueue.main.async, not implicit MainActor isolation
Previous fix assumed being MainActor-isolated inside an async method was equivalent to a classic synchronous AppKit call — it wasn't; Rune confirmed live it still did nothing (no dialog, no state change, even after a clean tccutil reset). AEDeterminePermissionToAutomateTarget is a blocking, modal-dialog-presenting legacy API and needs to run from a genuine DispatchQueue.main.async dispatch to correctly nest its own run loop, not from inside a suspended Task continuation frame. Added extra Log.mail checkpoints (call received / about to call AE / result) so the next attempt has real data to diagnose from either way.
This commit is contained in:
@@ -77,18 +77,27 @@ final class AppleMailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if
|
/// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if
|
||||||
/// the user hasn't been asked yet (a no-op if already granted or denied). Deliberately NOT
|
/// the user hasn't been asked yet (a no-op if already granted or denied).
|
||||||
/// dispatched to the background `queue` used for AppleScript execution — the system consent
|
///
|
||||||
/// prompt needs to be raised from the main thread/run loop to actually display (same as this
|
/// First attempt just called this synchronously from an `async` method, reasoning that
|
||||||
/// codebase's existing blocking `NSAlert.runModal()` calls); calling it off-main appears to
|
/// `-default-isolation=MainActor` makes it run on the main actor anyway — it didn't work live
|
||||||
/// silently fail (no dialog, no error, no state change — the exact symptom Rune hit live).
|
/// (button flashed, no dialog, no state change, confirmed still broken even after a
|
||||||
/// `AppleMailService` has no explicit actor annotation, so under this project's
|
/// `tccutil reset`). Being "MainActor-isolated" inside Swift's structured-concurrency/Task
|
||||||
/// `-default-isolation=MainActor` build setting this method already runs on the main actor.
|
/// execution model is NOT the same as a classic synchronous AppKit call — this blocking,
|
||||||
|
/// modal-dialog-presenting legacy C API needs to run from a genuine `DispatchQueue.main.async`
|
||||||
|
/// dispatch (the same execution shape as a normal button target-action), not from inside a
|
||||||
|
/// suspended `Task` continuation frame, to correctly nest its own run loop for the prompt.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func requestAccess() async -> Bool {
|
func requestAccess() async -> Bool {
|
||||||
|
Log.mail.info("requestAccess: called")
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Log.mail.info("requestAccess: about to call AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true)")
|
||||||
let status = Self.checkMailAutomationPermission(askUserIfNeeded: true)
|
let status = Self.checkMailAutomationPermission(askUserIfNeeded: true)
|
||||||
Log.mail.info("requestAccess: AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true) returned \(status)")
|
Log.mail.info("requestAccess: AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true) returned \(status)")
|
||||||
return status == 0
|
continuation.resume(returning: status == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func checkMailAutomationPermission(askUserIfNeeded: Bool) -> OSStatus {
|
private static func checkMailAutomationPermission(askUserIfNeeded: Bool) -> OSStatus {
|
||||||
|
|||||||
Reference in New Issue
Block a user