Can You Execute an Applescript Script from a Swift Application

Can you execute an Applescript script from a Swift Application

Tested: one can do something like this (arbitrary script path added):

import Foundation

let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]

task.launch()

Running AppleScript from Swift not working

This error occurs if the application is sandboxed.

You have two options:

  1. Add the com.apple.security.temporary-exception.apple-events entitlement for System Events
  2. Put the script in the Application Scripts folder of the application and run it from there with NSUserAppleScriptTask

How to run a system event AppleScript from a hardened macOS app?

After much gathering at hints all over the internet, I've come to the conclusion that the problem lies in having different binaries.

It makes sense that macOS does some binary check to make sure the application has not been swaped. I had therefore two binaries in my machine a "release" one, which was DeveloperID signed. And the XCode debug one, which uses a development certificate. So both are different and therefore do not pass the binary check.

The brute force solution is not to have a release version installed in my machine. Another solution would be to have the debug binary have a different bundle ID (com.ospfranco.sol), so that macOS allows both the release binary and the debug binary to have accessibility access.

How to run AppleScript from inside a SwiftUI View?

I have played around quite a bit with this manner and after having numerous discussions and trial & errors on that subject, I want to present 2 solutions (until now it seems that these are the only possible solutions for a sandboxed application).

First of all, if you don't consider to distribute your app on the App Store, you can forget about the following, since you just have to deactivate the Sandbox and you are basically free to do whatever you want!

In case your planning to distribute a Sandboxed App, the only way of running a script and interacting with other apps on the user's system is to run a script file from the Application Script folder. This folder is a designated folder in the user library structure: /Users/thisUser/Library/Application Scripts/com.developerName.appName/. Whatever script goes in here you have the right to run from your application appName.

You basically have two options to get your script file into that folder:

Option 1 - Install the Script File

This is (in my opinion) clearly the option you should go for if your script is static (does not require any additional user data from your application). All you have to do is

  • Select the project (1)
  • click on Build Phases (2)
  • add the Copy Files setting (if not already present)
  • choose the script file location in your app (singing might be a good option when distributing via AppStore)
  • add the Application Script folder of your application to the destination. Therefore, choose Absolute Path and enter Users/$USER/Library/Application Scripts/$PRODUCT_BUNDLE_IDENTIFIER in the Path field.

You can also select the Copy only when installing option if your script is entirely static but in case you have changes on the script when the application is closed and reopened and you want to update the script, leave this option blank (I have not tested this!).

Sample Image

After this is done you can execute the script via NSUserScriptTask. A more detailed description of how you could implement this is given here.

Option 2 - Giving access to the folder and copy the file on demand

This is certainly the solution when your script file updates dynamically according to e.g. user inputs. Unfortunately, this is a bit of a hassle and does not have (in my opinion) satisfying solutions. To do so, you will have to grant access to the folder (in this case Application Scripts/). This is done via NSOpenPanel. A really good tutorial how to implement this is given here.

Per default you will have read permission to that folder only. Since you are trying to copy a file into that folder you will have to change that to read/write in your Capabilities as well.

Sample Image

I hope this will help some people to "shine some light into the dark"! For me this was quite a bit of a journey since there is only very little information out there.

How to implement AppleScript support in a Swift MacOS app

So, I managed to do exactly what I wanted after 4 hours of tedious research.

Here's all the code:

Scriptable.sdef:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">

<dictionary title="App Scripting Terminology">

<suite name="Scriptable App Suite" code="NoSu" description="Scriptable App suite.">

<command name="send" code="NoOnSend">
<cocoa class="App.ScriptableApplicationCommand"/>
<access-group identifier="*"/>
<parameter code="NoCm" name="command" description="Command to send" type="text">
<cocoa key="commandFlag"/>
</parameter>
<result type="text" description="ACK"/>
</command>

</suite>

</dictionary>

ScriptableCommand.swift:

import Foundation
import Cocoa

class ScriptableApplicationCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
let text = self.evaluatedArguments!["commandFlag"] as! String
print(text)
return text
}
}

Test AppleScript:

tell application "Noonecares"
send command "s"
end tell

Turns out App.ScriptableApplicationCommand is essential. ScriptableApplicationCommand alone doesn't do a thing.

At least today I learned something new.

AppleScript execution sometimes crashes. Can I separate it from the main app?

You can do this using an XPC service. In fact "stability" is one of the two key reasons Apple created XPC services. So if this instability is unavoidable, then creating an XPC service is a good way to go about doing this.

Apple provides two main ways to communicate with an XPC service: their XPC C framework and their
Objective-C framework which is often referred to as the
NSXPCConnection API. While both can be used from Swift with its built in language bridging functionality, neither are particularly pleasant to use from Swift. I've created SecureXPC which is written from the ground up for Swift and I think is quite a bit easier and nicer to use.



Related Topics



Leave a reply



Submit