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:
- Add the
com.apple.security.temporary-exception.apple-events
entitlement forSystem Events
- Put the script in the
Application Scripts
folder of the application and run it from there withNSUserAppleScriptTask
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 enterUsers/$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!).
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.
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 theNSXPCConnection
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
How to Create Array of Unique Object List in Swift
Nsuserdefaults Not Working on Xcode Beta With Watch Os2
Binary Operator * Cannot Be Applied to Operands of Type Int and Double
Check Password String Strength Criteria in Swift
Choosing Coredata Entities from Form Picker
Checking If an Object Is a Given Type in Swift
How to Detect If an Skspritenode Has Been Touched
How to Create a Segue That Can Be Called from a Button That Is Created Programmatically
Programmatically Detect Tab Bar or Tabview Height in Swiftui
Forced to Cast, Even If Protocol Requires Given Type
Why Are Emoji Characters Like 👩👩👧👦 Treated So Strangely in Swift Strings
How to Set Layer Cornerradius For Only Bottom-Left, Bottom-Right, and Top-Left Corner
Getting Keyboard Size from Userinfo in Swift