I'm writing an AppKit macOS application that will use AppleScript / Apple events to automate another application. I need to know ahead of time whether or not my app has permission to do this for the specific other application it wants to automate. I also need to trigger the "Foo.app wants access to control Bar.app" permission dialog if it hasn't already been shown.
The one way I can think of to do this is to just run an AppleScript that attempts to automate the other app in some rudimentary way and see what happens. However, if the permission dialog appears, that script will block until the dialog has been dismissed.
Is there a way to check this without blocking?
I was able to adapt the answer in this thread to something that works for my purposes. The key issue is that AEDeterminePermissionToAutomateTarget
can block under two circumstances:
askUserIfNeeded
parameter is true, and it prompts the user for permissionGiven that, the following will check if the current application has permission to send Apple events to the application with the given bundle ID, and do so in a non-blocking manner, prompting the user as necessary:
static _Atomic bool promptingUser = false;
static void promptUserForAppleEventPermissions(NSString *appId)
{
if (promptingUser) {
return;
}
promptingUser = true;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
NSAppleEventDescriptor *targetAppEventDescriptor;
targetAppEventDescriptor = [NSAppleEventDescriptor descriptorWithBundleIdentifier:appId];
AEDeterminePermissionToAutomateTarget(targetAppEventDescriptor.aeDesc, typeWildCard, typeWildCard, true);
promptingUser = false;
}
});
}
bool checkAppleEventPermissionsForApplicationId(NSString *appId)
{
if (promptingUser) {
NSLog(@"Still waiting for user prompt");
return false;
}
OSStatus status;
NSAppleEventDescriptor *targetAppEventDescriptor;
targetAppEventDescriptor = [NSAppleEventDescriptor descriptorWithBundleIdentifier:appId];
status = AEDeterminePermissionToAutomateTarget(targetAppEventDescriptor.aeDesc, typeWildCard, typeWildCard, false);
switch (status) {
case 0: // noErr
return true;
case -600: //procNotFound
NSLog(@"App not running: %@", appId);
return false;
case -1744: // errAEEventWouldRequireUserConsent
NSLog(@"App requires user consent: %@", appId);
promptUserForAppleEventPermissions(appId);
return false;
case -1743: //errAEEventNotPermitted
NSLog(@"User denied permission to control app: %@", appId);
// Prompt user to manually grant permission in System Settings here
return false;
default:
NSLog(@"Received unexpected error code %d:", status, appId);
return false;
}
}
Note that checkAppleEventPermissionsForApplicationId
is not thread-safe, since I don't need a thread-safe version for my purposes.