Reputation: 73
I am trying to use AppleScript to control the Apple Photos
app from within a QT C++ app that must run in the sandbox environment required for the macOS App Store.
I have tried to run the AppleScript via a QProcess launching osascript
, like this :
const QString aScript = QString(
"tell application \"Photos\"\n"
" set selMedia to (get media items whose id contains \"%1\")\n"
" if not (album \"Trash from %2\" exists) then\n"
" make new album named \"Trash from %2\"\n"
" end if\n"
" add selMedia to album \"Trash from %2\"\n"
"end tell").arg(fileNameNoExt, APP_NAME);
QProcess script;
script.setProgram("osascript");
script.setArguments(QStringList() <<
"-e" << aScript);
script.start();
script.waitForFinished();
int exitCode = script.exitCode();
if(exitCode!=0){
QString outMsg = script.readAllStandardOutput();
QString errorOut = script.readAllStandardError();
// Warn user and output message...
}
This works fine outside the sandbox, adding the desired items from the library to the desired album. However, inside the sandbox, I get an errAEPrivilegeError
. Looking in the Console
for the error event, it states :
AppleEvents/sandbox: Returning errAEPrivilegeError/-10004 and denying dispatch of event core/getd from process '<private>'/0x0-0x4d84d8, pid=31654, because it is not entitled to send an AppleEvent to this process.
Here is my entitlements file when signing the app :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.scripting-targets</key>
<dict>
<key>com.apple.Photos</key>
<array>
<string>com.apple.Photos.library.read-write</string>
</array>
</dict>
</dict>
</plist>
When the app is first run (sandboxed) and the AppleScript is started, I get the system permission box, saying that the app is asking to control the Photos app. I click OK of course, but then I get the error. And upon next runs, as the permission is already granted, no system permission box comes up, but it fails nonetheless. When I reset the permissions with the terminal command tccutil reset All [my app bundle id]
, then upon the next execution of the app and the AppleScript, I get the permission box again.
If I delete the com.apple.security.scripting-targets
entitlement, I get no system permission request and the error is different : from my app the error output states Photos isn't running, from the console it says it was denied due to sandboxing. If I keep the scripting-targets and com.apple.Photos but delete library.read-write I get the errAEPrivilegeError again.
I have tried to only test the make new album named "Trash [...]"
but this still fails with errAEPrivilegeError. Even if only telling Photos to quit
with AppleScript (which according to Photos.app/Contents/Resources/Photos.sdef
doesn't require any specific permissions appart from scripting, I also get errAEPrivilegeError. The only command that seems to work is activate
.
Also, if I try to run the qprocess not as a separate process, with QProcess::execute
(if maybe the sandbox problem is because it is not the main app running the script ?), I still get the same errAEPrivilegeError.
What am I missing for it to work, or what other method should I use to run an applescript from C++/QT when in a sandbox ?
Upvotes: 4
Views: 714
Reputation: 186
Did you include NSAppleEventsUsageDescription
in your app’s Info.plist?
[ETA]
OK, after further digging I can confirm the behavior you’re seeing. With the correct app entitlements an in-process NSAppleScript
can send AEs to Photos; however, the same process is unable to send AEs from a child osascript
process.
According to Apple docs (which are lousy), a child process must have exactly 2 entitlements to inherit its parent’s: com.apple.security.app-sandbox
and com.apple.security.inherit
. However, running codesign --display --entitlements - /usr/bin/osascript
shows osascript
has neither of these but a bunch of unrelated entitlements. So this looks like Apple’s screwup in how they’ve built osascript
.
When running AppleScripts that are part of a Swift/ObjC app, I generally recommend using the AppleScript-ObjC bridge to load and call your AppleScript handlers directly; no subprocess required. I have no idea how to do that in a Qt app though.
The simplest solution may be to write your own command-line executable/XPC service in ObjC which runs your AppleScript code via NSAppleScript
, giving it the com.apple.security.app-sandbox
and com.apple.security.inherit
entitlements, and embed it in your Qt app’s main bundle.
(You may also need to add com.apple.security.automation.apple-events
in the main app’s entitlements if the runtime is hardened.)
…
One more thing: avoid generating AppleScript code via string interpolation. If you want to parameterize a script, pack your values as parameters in an Apple event and call the corresponding AS handler via -executeAppleEvent:error:
.
Upvotes: 1