Edit: Snow Leopard Problems 11/09/2009 I am seeing some issues with Snow Leopard and this script. It seems that Snow Leopard has a different definition of what constitutes user interaction in as much as the screen saver is interrupted by Keynote now. The practical upshot (downshot?) is that when the current presentation ends the screen saver is not running and the desktop is visible. If you have multiple monitors, you can simply tell keynote to output to the alternate display and set that monitor’s desktop background to solid black (or whatever). However if you have a single monitor, I don’t yet have any reasonable work around. For the time being, do not upgrade any machine running Kiosk to Snow Leopard. I am working on finding a solution, but unfortunately I am not confident that I will be able to do so.
Edit: Updated 02/05/2009 Confirmed that the script works with Keynote 09, though if you have both 09 and 08 there can be issues (so don’t do that). Also, make sure that all your presentations, including your “screen saver.key” are updated to Keynote 09 format (just open and save them). Some minor tweaks to the code, including a lot of cleanup and additional comments. If you end up using this script in a production environment, I’d appreciate an email tell me how you’re using it!
Recently my group was tasked with creating a Digital Sign system. We evaluated several commercial products but ultimately settled on using a Mac mini and Keynote. We chose the Sharp LC-52D62U as a display because it is a full native 1920x1080p LCD display with HDMI. The inspiration to use Keynote for this project is this site.
Anyway, I wrote the following Apple Script applet to drive the display. You’re free to use the below code as long as you include an attribution to myself. We’ve run this on a G5 and on an Intel Mac Mini but YMMV.
Some important information before the code:
-
The script requires the following folders in the same location as itself. You can rename them if you like.
-Active Presentations ← The keynote files you wish to display go here. They are processed in (Apple) Alphabetical order.
-Kill ← Place any file in this folder to make the script terminate when the current presentation ends. Check this folder for files if you are trouble-shooting.
-updates ← Any files placed here will be moved (with replacing) to the Active Presentations folder when the current presentation ends. -
Make sure that your keynote files follow these guidelines:
-All slides and embedded objects MUST automatically transition or it won’t go anywhere. (HOWEVER, do NOT set the presentation to auto-start on open as the script will start it for you. Should you be so inclined you can edit the script to change this.)
-Save your presentations with the First Slide selected. (For some reason Keynote starts the presentation from the selected slide when using Applescript.)
-You will need a place holder presentation called “Screen Saver.key” which will be displayed while updated files are copied to the Active Presentations folder.
-If you use any web content, make several smaller presentations instead of one big one. Then set the Web objects in each presentation to update automatically. (When the presentation switches to the next one, these objects will get updated. We use this to have semi-dynamic content on our sign.) Edit: [b]Apparently web views have been dropped from Keynote? Not sure when this happened…[b]
-It is strongly recommended to always embed all video and other files rather than linking to them. -
Set the copy of Keynote on the Digital Display computer with these settings:
-Set Keynote to use a Template for new documents instead of the Template Chooser.
-If you have multiple monitors make sure that keynote is set to display on the correct monitor.
-You may have to enable the “Allow Expose, Dashboard, and others to use the screen” option. (The jury is still out on whether this is really needed or not.) Edit: Seems that Keynote 09 has some issues with this setting. We’ve turned it off on our sign with no ill effect. -
Settings for the Digial Display computer:
-Enable System Preferences → Energy Saver → Options → “Restart automatically after a power failure”
-Enable a Screen saver. This will obscure the Desktop when changing between presentations. (I suggest using “Basic Black.saver”.)
-Set an automatic login and set the Applet to auto-launch.
-Enable File Sharing or “Remote Login” to allow you to remotely upload files to the updates folder via AFP or SFTP.
If you have any comments or questions, let me know!
--Kiosk v1.3
--
--Written by Steven Hunter (hunters@NOSPAMpurdue.edu)
--
--Version History:
--1.0 Initial Release
--1.1 Added automatic file updating mechanism, set single ß
--1.2 Added improved error reporting, fixed paths to be relative for better portability, added checks to make sure that update files aren't still being copied.
--1.2a Corrected spelling error and tweaked error reporting for Display_This()
--1.3 Fixed some niggling bugs and cleaned up the code a bit.
global base_path --The path to this program
global presentation_path --The path to the location of the currently active .key files
global update_file --The path to folder containing new or updated .key files
global kill_file --The path to the "killfile" folder. If any items are found in this folder when the current presentation ends, everything shuts down.
tell application "Finder" --Setting the value of these globals now.
set base_path to container of (path to me) as string
set presentation_path to base_path & "Active Presentations"
set update_file to base_path & "updates"
set kill_file to base_path & "Kill"
end tell
global last_error_time -- Used to prevent the system from sending excessive error messages.
global error_rate -- The number of seconds between error messages. Default is 5 seconds.
global system_name -- Inserted into the subject line of error email messages, this should be a Unique identifier for your system; the default is "Digital Sign".
global error_email
(*The email address to which errors should be sent. The email will come from "username@hostname" where "username"
is the short name of the user running Kiosk and "hostname" is the fully qualified Unix hostname of the Mac running Kiosk.*)
set last_error_time to current date
set error_rate to 5
set system_name to "Digital Sign"
set error_email to "Your Email Goes Here!!"
set File_List to my Build_File_List(presentation_path)
set presentation_count to count of items in File_List
if presentation_count is less than 1 then
my report_error("[" & system_name & "] Error: No presentation files found!", current date)
display dialog "No presentation files found, or error enumerating presentation list! Please contact your IT support group." buttons {"OK"}
tell me to quit
end if
set x to 1
tell me to activate
display dialog "Now beginning show, you have 5 seconds to Cancel." buttons {"Continue", "Cancel"} default button "Continue" giving up after 5
do shell script ("open /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app")
--I recommend the "Basic Black" screen saver. See http://www.monkeybreadsoftware.de/Freeware/BasicSaver.shtml
repeat --This is the main loop where all the magic happens.
if x is greater than presentation_count then set x to 1 --If we've gone through all the presentations then it's time to start over.
set updatekill to my update_kill()
if updatekill is "kill" then
tell me to activate
my terminate_keynote(false)
display dialog "Killfile detected: Exiting Keynote" buttons {"OK"} default button "OK" giving up after 5
exit repeat
else if updatekill is "update" then
my Update_Slides()
set File_List to my Build_File_List(presentation_path)
set presentation_count to count of items in File_List
set x to 1
else
my Display_this(item x in File_List)
set x to x + 1
end if
end repeat
on Build_File_List(aPath)
set theFile_list to null
tell application "Finder"
set temp to aPath as alias
select (every file in temp)
set theFile_list to the name of every file in temp
close every window
end tell
return theFile_list
end Build_File_List
on Display_this(aFile)
try
tell application "Keynote"
my close_current_document()
open (presentation_path & ":" & aFile)
start slideshow
set foo to true
repeat until foo is false
repeat with curDoc in slideshows
if (playing of curDoc) is true then
delay 1
else
set foo to false
delay 1
my close_current_document()
end if
end repeat
end repeat
end tell
on error number n
my report_error("[" & system_name & "] Error (" & n & ") : Display_this, file: " & aFile as string, current date)
end try
end Display_this
on Update_Slides()
try
repeat
tell application "Keynote"
my close_current_document()
open base_path & "Screen Saver.key"
start slideshow
set foo to true
repeat until foo is false
repeat with curDoc in slideshows
if (playing of curDoc) is true then
my do_update()
delay 1
else
set foo to false
end if
end repeat
end repeat
end tell
set updatekill to my update_kill()
if updatekill is "kill" then exit repeat
if updatekill is "empty" then
my close_current_document()
my terminate_keynote(true)
exit repeat
end if
end repeat
on error
my report_error("[" & system_name & "] Error: Update_Slides", current date)
end try
end Update_Slides
on close_current_document()
tell application "Keynote"
repeat with curDoc in slideshows
try
close curDoc
end try
end repeat
end tell
end close_current_document
on do_update()
try
tell application "Finder"
set source to update_file as alias
set destination to presentation_path as alias
set updated_files to every item of (source as alias)
repeat with i from 1 to number of items in updated_files
set this_item to item i of updated_files
set last_size to -1
set copy_done to false
repeat while copy_done is false --This loop makes sure that the file has finished being copied to the updates folder.
tell application "System Events"
set curr_size to size of this_item
end tell
if curr_size > last_size then
set last_size to curr_size
delay 1
else
tell application "System Events"
set curr_size2 to size of this_item
end tell
if (curr_size2 is not equal to curr_size) and (last_size is not equal to curr_size) then
delay 1
else
set copy_done to true
end if
end if
end repeat
end repeat
move (every file in source) to destination with replacing --Since the script doesn't clean up the "active" folder, if you want to remove depreciated presentations, you'll have to do it manually.
end tell
on error
my report_error("[" & system_name & "] Error: do_update", current date)
end try
end do_update
on update_kill() --Looks for updated presentation(s) in the Updates folder and for a kill file in the Kill folder. The Kill file always overrides updates.
try
tell application "Finder"
set update_yn to count of files in folder (update_file as alias) --Keep the update folder clear of non-keynote files as this script doesn't differentiate between them!
set kill_yn to count of files in folder (kill_file as alias)
end tell
if kill_yn is greater than 0 then return "kill"
if update_yn is greater than 0 then return "update"
return "empty"
on error
my report_error("[" & system_name & "] Error: update_kill", current date)
end try
end update_kill
on report_error(error_message, error_time)
set difference to (error_time - last_error_time)
if difference is greater than error_rate then --Has there been at least (error_rate) seconds since the last error email was sent? This thing can mail a LOT of errors when something screws up.
do shell script ("mail -s " & quoted form of error_message & " " & quoted form of error_email & " < /dev/null > /dev/null")
set last_error_time to error_time
end if
end report_error
on terminate_keynote(restart_yn) --This was added so I could kick, or just kill, Keynote should the need arise.
tell application "System Events"
if process named "Keynote" exists then
tell application "Keynote" to quit saving no --Try to quite Keynote
delay 5 --Give it a few seconds to exit
if process named "Keynote" exists then --But if it is still running...
set proc_id to (unix id of processes whose name is "Keynote") --get the PID so
do shell script ("kill -9 " & proc_id) --we can forceably kill it.
delay 5 --Give it a few seconds to be killed
end if
end if
if restart_yn is true then
tell application "Keynote" to activate
my close_current_document()
end if
end tell
end terminate_keynote