XOJO GUI’s for AppleScript & Python – chapter 3 : Shell scripts, AppleEvents and Python

When OSAScript is used within an XOJO App to invoke an Applescript, the Applescript will return its result as in the “I’ve beeped” in the previous example. If, however, we use a XOJO shell to invoke a Python script in the Terminal using OSAScript, Python has no built in way to return a result to the calling app, so all that gets returned is the window reference of the Terminal App. By installing the OSAScript Package from PyPi your Python scripts can call Applescripts using osascript.run Because the calling app is now Python and no longer XOJO, we need to send the result of the ‘Python called’ Apple scripts back to XOJO instead. We can do this by making our XOJO app able to read custom AppleEvents, and then send those custom AppleEvents from the Apple scripts invoked through Python. We now have a closed control loop. XOJO acts as the command and control centre with a user interface and can call Applescripts and Python scripts and get feedback from those scripts on order to handle flow control and determine real time status.

( The resources for this project can be downloaded at the foot of the page )

integrating XOJO, AppleScript and Python with AppleEvents

In order for the Applescripts invoked by Python to feed back to XOJO we need to create a scripting definitions file (.sdef) using Sdef Editor for our Xojo App. This will contain the AppleScript Dictionary command for the call to return a value. Here we add a single command named ‘output’ with a Æ code called AEtsOutp of type text. Once the sdef file is defined and created we have to include this file in our Xojo App as a Script by first saving the sdef into a ‘Resources’ subfolder of our Xojo project location.

Finally we have to add a script to the Xojo App that modifies its plist at ‘build-time’ by adding some xml that marks it as an Applescriptable application. The file is called info-addon.plist and also should reside in the Resources subfolder of the Xojo code folder for our App. Edit its XML such that the name of the scripting definition file is the same as that created above, and also that the ‘Scriptable Application’ value is set to YES.

Now the code of the script that merges our info-addon.plist into the Apps plist on build is as follows:

dim cmd, s as String

dim p1 as String = DoShellCommand ("echo ""$PROJECT_PATH/Resources/Info-addon.plist""").Trim
p1 = ReplaceAll (p1, "'", "\'")

cmd = "/usr/libexec/PlistBuddy -c ""Merge \"""+p1+"\"""" "+CurrentBuildLocation+"/"""+CurrentBuildAppName+".app""/Contents/Info.plist"

s = DoShellCommand (cmd)
if s <> "" then
print "Updating Info.plist failed: "+s

To confirm that all is well and good, run the App and then display its AppleScript Dicitionary from within Script Editor or Script Debugger.

Integrating Python

We can modify our previous OSAScriptRunner app to select a Python file to run instead using the same OSAScript code and a little fiddling with the Terminal command:

The get.py button action contains the following:

dim f as folderitem
dim fileString as string
dim pathString as string
dim scr1 as string
dim scr2 as string
dim result as string
dim n as integer

f = GetOpenFolderItem("")

//then test for nil to see if the user clicked cancel
if f <> nil then
//its a file!
runButton.enabled = true
pathString = f.ShellPath
fileString = f.Name
pathStringField.text = f.ShellPath
fileNameField.text = f.Name

result = Replace(pathString,fileString, "")

n = Len(result)
scr1 = mid(result,1,n-1)

scr1Field.text = scr1
scr2 = " && python3 "
scr2Field.text = scr2
pyCmd = scr1 + scr2 + fileString
cmdField.text = pyCmd
Window1.aEventField.text = ""
end if

The run button contains the following code:

Dim pythonString as string
Dim str1 as string
Dim str2 as string
Dim str3 as string

Dim sh As New Shell

//build a shell command that includes the chosen file's path and name (from the dialog box) as variables
str1 = "osascript -e ' tell application ""Terminal""' -e activate -e ' do script ""cd "
str2 = pyCmd
str3 = """' -e 'end tell'"
pythonString = str1 + str2 + str3
osaField.text = pythonString

//execute shell command

//put result into rb
dim result as string = sh.result
resultField.text = result

From the XOJO app (which has been built and named ‘PyRun3’ we can now select the Python file to run. In this case I select the Loopy.py file from the ‘chooser dialog’:

#!/usr/bin/env python3

import osascript

while True:
print('Who are you?')
name = input()
if name != 'Paul':
print('Hello Paul. What is the Password?')
password = input()
if password == '1':
osascript.run('/users/pauljvallance/Documents/XOJOPython/password1.scpt', background=True)
if password == '2':
osascript.run('/users/pauljvallance/Documents/XOJOPython/password2.scpt', background=True)
print('Access granted.')

code,out,err = osascript.run('display dialog "Finished"')

osascript.run('tell application "Terminal" to quit', background=True)

# osascript.run('tell application "PyRun3" to quit')

The applescripts that are called from within Python depending on program logic feedback are as follows:


tell application "PyRun3"
set res1 to output "Password 1 was selected"
end tell


tell application "PyRun3"
set res1 to output "Password 2 was selected"
end tell

Here we are using the output command to send back strings of text to our XOJO app as shown in the image below:


Thanks to Thomas Tempelmann for pointing me in the right direction with much info and with his ArbieTest demo. Thanks also to Thomas Read for his PDF document ‘Developing AppleScriptable XOJO Applications’ which is linked for download.


PyRun3 XOJO App and project files