macoscocoaapplescriptappkit

How can an application check if it has permission to control another application through AppleScript without blocking?


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?


Solution

  • 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:

    Given 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.