2
votes

I'm trying to run an AppleScript from my macOS app (Mojave, Xcode 10.3). However, when the AppleScript is run from the app, I get this error:

/Users/my_username/Desktop/test_apple_script.scpt: execution error: System Events got an error: osascript is not allowed to send keystrokes. (1002)

Based on other posts, I have made sure that my app can control my computer (System Preferences > Security & Privacy > Privacy > Accessibility), and it also has control over System Events (System Preferences > Security & Privacy > Privacy > Automation).

Here is my AppleScript:

tell application "System Events"
    key code 48 using {command down}
end tell

The script works when run in Script Editor (I only had to give Script Editor the access in System Preferences > Security & Privacy > Privacy > Accessibility).

This is the code I am using to run the Apple Script from my app (via osascript):

let proc = Process()
proc.launchPath = "/usr/bin/env"
proc.arguments = ["/usr/bin/osascript","/Users/my_username/Desktop/test_apple_script.scpt"]
proc.launch()

This code works perfectly when I run a simple AppleScript that just displays a notification:

tell application "System Events"
    display dialog "Test"
end tell

How can I make it so that my AppleScript runs from my macOS app just as it does when I run it in Script Editor?

Edit: For some more context, the command tab case here is just an example. I am trying to find a generalizable approach to simulating keyboard shortcuts by clicking buttons in my app (e.g., the user clicks on the "cut" button and the app acts as if the user has just pressed command + x). There may be a better approach to this than using an AppleScript, but I know that it is possible to do what I want using AppleScripts.

Edit: In case it wasn't clear from my original question, the simple AppleScript that displays a notification was already working from within my app. The problem arises when I try to run an AppleScript that simulates user input from the keyboard.

Edit: This code here worked for me temporarily, but it no longer works. I did not change the code at all. I simply ran my app again.

let PlayPauseScript = "tell application \"Spotify\"\n playpause\n end tell"\"test\"\n end tell"
if let scriptObject = NSAppleScript(source: PlayPauseScript) {
    scriptObject.executeAndReturnError(nil)
}

Edit: Here is where the problem currently stands. Based on suggestions, I added this to my info.plist, and now the Spotify AppleScript consistently works (when called via NSAppleScript):

<key>NSAppleEventsUsageDescription</key> 
<string>...this is why I need permission...</string>

It seems my problem (now) is just with AppleScripts that use System Events. Despite making sure that both my app and osascript are allowed to control my computer (via security & privacy > privacy > accessibility), I now get the same error (1002) when using NEAppleScript and osascript. My app is also allowed to control System Events (security & privacy > privacy > automation). The osascript error prints to the console in Xcode, and I uncovered the error code for NEAppleScript by using this code (in the @IBAction of the button):

let script =
"""
try
    tell application "System Events"
        keystroke "c" using {command down}
    end tell
on error errMsg number errorNumber
    display dialog "An unknown error occurred:  " & errorNumber as text
end try
"""

if let scriptObject = NSAppleScript(source: script) {
    scriptObject.executeAndReturnError(nil)
}

The above code caused this to appear in the dialog box: An unknown error occurred: 1002

When my app is run, it does prompt me to give it control over System Events, which I always do. My app is not sandboxed. I believe that both NSAppleScript and oascript did work for me once, but stopped working when I ran the app again (despite my not having made any changes to my code, I'm almost positive).

I don't know if this is informative, but the only other clue I have as to what might be going on is that I keep having to check and uncheck Script Editor's permission in security & privacy > privacy > accessibility to get it to run these AppleScripts because it tells me that Script Editor is not allowed to send keystrokes.

3
If the app is sandboxed you cannot run scripts with Process.vadian
Good point. However, my app is not sandboxed.Jeremy
Did you add the NSAppleEventsUsageDescription in Info.plist? By the way why don't you run the script with NSAppleScript rather than performing the shell detour.vadian
Can you provide more context, please. Why do you need to simulate command-tab? it seems to me there are simpler and more efficient ways of doing that from swift than running an AppleScript through osascript. (and there are better ways of running applescripts, anyway, like NSAppleScript). But until I know context I can't really tell.Ted Wrigley
@vadian, I just threw this into Info.plist: <key>NSAppleEventsUsageDescription</key> <string>NSApplication</string>. Is that the correct way to add that? (I'm still really new to this.) And I'm looking into using NSAppleScript.Jeremy

3 Answers

2
votes

To fix osascript 1002 permission issue.

Got to Security & Privacy -> Privacy tab -> Accessibility -> Add osascript (/usr/bin/osascript)

Or Use NSAppleScript

1
votes
Security & Privacy -> Privacy tab -> Accessibility

Add /usr/bin/osascript

NOTE: You may also need to add your terminal (iTerm in my case)

0
votes

This is a couple of things for you to consider, more than a real answer to your question. If you'd like more details, I'll happily edit them in on request. (also, I'll put code examples in objective-C, because I'm still learning swift)

First, if you're going to be doing a lot of scripting in your app, it may be worth your while to switch over to Scripting Bridge. Scripting Bridge is a cocoa interface for applescripting other apps, so (to steal an example I found elsewhere) if you want to get the name of the current track in iTunes, you'd do something like:

iTuneApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
currentTrack = iTunes.currentTrack.name;

If you want specific code for Spotify I'll have to install that app; let me know.

Second, if you want to do keystrokes, you should look into Quartz Event Services. QES allows you to create, send, and capture input events. So if you want to send a ⌘C, you might do something like:

// '8' is the key code for 'c'
comCDownEvent = CGEventCreateKeyboardEvent(nil, 8, true); 
CGEventSetFlags(comCDownEvent, kCGEventFlagMaskCommand);
comCUpEvent = CGEventCreateKeyboardEvent(nil, 8, false); 
CGEventSetFlags(comCUpEvent, kCGEventFlagMaskCommand);
CGEventPostToPSN(targetAppProcessSerialNumber, comCDownEvent);
CGEventPostToPSN(targetAppProcessSerialNumber, comCUpEvent);

You can't run this code from sandboxed apps, but I think you'll have that problem any way you go.

The main advantage of these approaches is that they avoid switching contexts: it's all done from cocoa, so you don't need to invoke a shell or create an AppleScript environment, and avoid all the translation problems that involves.