osascript & AppleScript Runner: User interaction on the command line

Tested on Mac OS X 10.5 Leopard!

Today someone asked me if it was possible to circumvent osascript’s limitation to disallow any user interaction when executing an AppleScript from the command line. And yes, it is possible to allow user interaction :wink:

Let’s assume you have an AppleScript source file named «source.applescript» on your desktop that contains only the following line of code:


display dialog "Hello, world!"

If you open a Terminal window and enter the following command, you will instantly get an error message:

osascript /Users/yourname/Desktop/source.applescript

Error mesage:
execution error: No user interaction allowed. (-1713)

Of course, you could try to write another AppleScript that runs this source file (passed as an argument), but even this fails:


on run {sourcefile}
	run script (POSIX file sourcefile)
end run

Just save this code in a source file named «run.applescript» on your desktop and enter the following command into an open Terminal window:

osascript /Users/yourname/Desktop/run.applescript /Users/yourname/Desktop/source.applescript

Again, an error message:
execution error: No user interaction allowed. (-1713)

But as I said before, there is a solution. Just change the code in the file named «run.applescript» accordingly:


on run {sourcefile}
	tell application "AppleScript Runner"
		do script sourcefile
	end tell
end run

If you now enter the command once again into a Terminal window, you won’t get an error message:

osascript /Users/yourname/Desktop/run.applescript /Users/yourname/Desktop/source.applescript

In fact, you will even get the result from the executed AppleScript source file:
{button returned:“OK”}

Happy Scripting :smiley:

P.S.: You can find AppleScript Runner here: /System/Library/CoreServices/AppleScript Runner.app
And it also contains some hidden commands like do folder action, which you can inspect by opening the following file with a text editor:
/System/Library/CoreServices/AppleScript Runner.app/Contents/Resources/Hidden.scriptTerminology

Wow, Martin;

Hell of a complex way to run an AppleScript from the Terminal.

Wow indeed! Mighty fine, Martin. So fine it shines. :cool:

Just to add some more info on this. I sometimes embed snippets of applescript in shell scripts. I’ve discovered that using system events also allows user interaction with applescripts from the command line.

For example, the following shell script returns the “No user interaction allowed” error message:

#!/bin/bash

/usr/bin/osascript <<-EOF

	display dialog "Hello world"

EOF

But, this one runs fine:

#!/bin/bash

/usr/bin/osascript <<-EOF

tell application "System Events"
	activate
	display dialog "Hello world"
end tell

EOF

Along the same lines:

#!/bin/bash
x=/usr/bin/osascript <<EOT tell application "Finder" activate set myReply to text returned of (display dialog "Here is a question" default answer "Here is a reply") end tell EOT
echo $x

Hello!

The applescript runner solution is just what I looked for, it is great, if it works, from within Automator, as I then won’t have to maintain the script more than one place.

Then I can happily share scripts between the script menu, Quicksilver, and Automator.

I have such scripts in folders, which I have set up Quicksilver to scan!

Great! :smiley:

Ahhh… :cool:

It works like a charm!

Display dialogs (like activate commands) are not application commands even if they are in a tell block. Those special Commands are system commands and will run in the application’s context. In short: Tell application “appname” to display dialog “Hello World!” is actually (not literally) something like tell application “windowServer” to display dialog “Hello World” for process “appname”. That means that every process (that is scriptable and has an Application state) can display a dialogs. Following example code is run from OSAScript:

display dialog "Hello World!"

fails because the windowServer can’t create a window for process OSAScript because there isn’t an application for the process.

tell application "System Events" to display dialog "hello world!"

Works but it creates a new process; when done tell application “System Events” to quit. It doesn’t devour memory but for servers (or systems that never shuts down) you don’t want to keep unnecessary applications open.

tell application "Finder" to display dialog "Hello world!"

better but can be confusing for the end user; is the message from a script or finder?:confused:


set systemUIServer to "SystemUIServer" --avoids that it startup unwanted
if systemUIServer is not in every paragraph of (do shell script "ps -Ac -o comm=") then return --stop this script
tell application systemUIServer to display dialog "Hello world!"

The first two lines (i use myself) is at the beginning of the script. The display dialog line can be anywere as long as it is behind the first two lines. The pro’s against the other examples

  • No need of an extra process or borrowing a process that shows the message for you/ makes less confusing
  • Will only run when a user is logged in
  • Will only run when the WindowServer is able to draw

Hello

System Ui Server, handles all UI, so it is kind of unavoidable to have it running if you are doing any UI.
And I think it best to run it up close, that is to call it directly, that way, you get out of it scope, as quickly as possible, and there by freeing the resources as quickly as possible.

this is the way I roll:


	on alertDialog(R) -- Returns Nothing
		-- R : {aTextMessage:theMessage,aTextTitle:thetitle,timeInSecs:lenToTimeout,btnAsList:theButton,iconAsFileRef:theIcon,bundleIdOfFrontApp:frontappId}
		local res, failed, e, n
		set failed to false
		tell application "SystemUIServer"
			activate
			try
				if (iconAsFileRef of R) is null then
					set res to button returned of (display dialog (aTextMessage of R) with title (aTextTitle of R) giving up after (timeInSecs of R) buttons (btnAsList of R) default button 1)
				else
					set res to button returned of (display dialog (aTextMessage of R) with title (aTextTitle of R) giving up after (timeInSecs of R) buttons (btnAsList of R) default button 1 with icon (iconAsFileRef of R))
				end if
				if res = "" then set failed to true
			on error e number n
				set failed to true
				
			end try
		end tell
		if failed is true then
			UILib's abortNicely({bundleIdFrontApp:(bundleIdOfFrontApp of R), blnIfAFinalReturnIsNeeded:false}) -- Returns Nothing
		end if
		return
	end alertDialog



As you can seem I do minimally with stuff within that tell block, and quitting outside it and so on. securing that no resources is hanging inside there.

I’ll just toss in this, to give you the complete picture, whenever UI server has been called, the front window, looses focus, the window that was active when the script was called, so I call this, on every exit from a script, that doesn’t involve activating something. It works by pressing the key for cycle through windows which seems to work mysteriously, whether it is normal funcion keys that works , or not ( consumer keys, the volume, and so on). I just realized, that it is because keystrokes are sendt to a window! :slight_smile:


	on abortNicely(R) -- Returns Nothing
		-- R : {bundleIdFrontApp:frontappId,blnIfAFinalReturnIsNeeded:finalReturn}
		tell application "System Events" to tell application process id (bundleIdFrontApp of R)
			key down control
			key code 118
			key up control
                       if (blnIfAFinalReturnIsNeeded of R) is true then key code 36
		end tell
		error number -128
	end abortNicely

Well you would think that but the name is a bit confusing. There are several processes responsible for the UI in Mac OS X. The System UI server is responsible for the top right menu bar the windowServer is actually responsible for the windows, application’s front most etc… WindowServer keeps keeps running next to the loginWindow but API is offline when there is no user logged in and is only open for the loginWindow (to show some windows).

OSAScript is a core AS interpreter and has no direct relation with Mac OS X. That means the OSAScript command can be triggered by any process even with launchd. OSAScript can run in single-user mode or console mode or even when the system is booted but no user is logged in Mac OS X. My example just shows the safest way to display a dialog without knowing it’s trigger and still be sure you’re able to display a dialog without problem (no hanging/freezing processes that eats CPU).

For example: I have an AppleScript that’s triggered by root’s launchd but can also be launched simply by the finder. I only want to display a dialog when Mac OS X GUI is up (the user has launched the script) and running but don’t want to when it’s down. When I’m not behind the machine it’s running in console mode. When a user launches the script it will of course need a notification when it’s ready or when it fails to finish because of an error of course.

Hello! :slight_smile:

Interesting, well, in the context of AppleScript, I think SystemUIServer is the closest we come to window control, even if it delegates the tasks further, being a client itself

I don’t really boot up in console, or at least only when battery is sparse, and vim is working. :slight_smile:

And it is still a point, that Applescript can be used to for so much more, than merely interact with users.

I have Applescript’s, that updates the recently launched lists in my dock for instance; through launchd services, shell scripts with osa scripts with in, which then communicates with small applets, to make sure that everything is handled allright. Sneaking out from the core, and into the UI!

The applets are making sure that there are icons, parlty opaque, covering the clutter, made by the stacks with recently used documents and applications. If I don’t, then I’m seeing the most recently used app there, which I used to often mistake for the real one! How annoying, when you drop a file onto it! :smiley:

A good evening, from rainy Norway!