The screensaver module in OS X has a command-line option that allows it to be run in the
background of your screen, as a replacement for the desktop picture. I found this in the
application bgscreensaver, which
contains an AppleScript to launch the screensaver module in the background, using a shell
command found by Michael Coyle at ResExcellence.
While this is pretty cool in and of itself, I wanted something that would let me run one
screensaver as the background, and have another one for my actual screensaver.
So after poking around a bit, I found where OS X stores the screensaver preferences. Using this,
I extended the AppleScript to change to store the current screensaver, set the preferences
to use the slideshow screensaver, and point to a directory of pictures of my choice. Then
it launches the screensaverm module as the background, and restores the preferences for
the actual screensaver. Running the script again turns the background screensaver off.
The heart of the code, which launches the screensaver module in the background or turns
it off if it’s already running, is as follows:
set isrunning to do shell script "ps -ax | grep ScreenSaver.framework | grep -v grep | awk '{print $1}'" if isrunning is "" then do shell script "/System/Library/Frameworks/ScreenSaver.framework/Resources/ ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background > /dev/null 2>&1 &" else do shell script "kill -9 " & isrunning & " > /dev/null 2>&1" end if
The first line uses the do shell script command in AppleScript to execute the
following text in a command shell. This string actually consists of several commands,
piped together with the | character. What this does is pass the output of one
command to the input of the next. In this way, each of these is chained to following
command, to continue processing.
The first command, ps -ax, returns a list of all currently running processes. The
next command, grep ScreenSaver.framework, takes this list and searches for one
that is using the screensaver module. However, this will actually end up return two
processes: the screensaver, and the process that’s searching for the screensaver. The
solution in this case is to search this next list (of two items), and remove the one
that is actually just a search itself. This is done with grep -v grep, which,
because of the -v option, searches for lines that don’t match the argument,
in thise case, grep. Thus, what remains is the screensaver process if it is running,
or nothing if it is not. The final command in this chain is awk ‘{print $1}’. The
program awk is used to scan text for a pattern, and then do something with it. In
this case, no pattern is specified, but the action is to print the first field it scans,
which in output from ps is the process ID.
The reason the process ID is relevant is because of the behavior we want. If the
screensaver is not currently running, we want it to start running. If it is running,
we want it to stop running. From the command in the first line, the variable in the
AppleScript isrunning will be set to the process ID if the screensaver is
running, and to the empty string “” if it is not running. The if structure on
the next few lines then takes the appropriate action.
If the screensaver process is not running, it is launched by running another shell script.
This shell command executes the ScreenSaverEngine executable code contained in the
long path shown, inside the ScreenSaver.framework framework. The executable is
passed the argument -background in the command shell, which causes it to run as
the desktop, instead of in front like a regular screensaver. The next bit in the line
(and everything between the two ” characters should be on one line), > /dev/null,
directs all output from the ScreenSaverEngine executable to be ignored. The next
bit, 2>&1, directs any error output to go the same place as the standard
output, which in this case is to be ignored. Finally, the & at the end allows
the command shell to continue running without waiting for the process to end. Otherwise,
the AppleScript would not finish. On the other hand, if the screensaver module is
currently running, it is stopped with the kill -9 command, which again has its
output and errors ignored.
So far, so good. If you have copied this text into the Script Editor application,
you will be able to turn your desktop background into your screensaver by running this
code, and revert to your regular desktop by running it again. But I found that I would
rather have a different moving background than my screensaver. So the next few lines of
code will allow us to save the currently selected screensaver from the preferences and
change it to one of our liking before running the screensaver module in the background.
In this case, we will set it to the slide show screensaver, using a particular folder
of picture. You should paste these lines in before those above.
set screensaver to "~/Library/Preferences/ByHost/" & (do shell script "cd ~/Library/ Preferences/ByHost;ls | grep 'com\\.apple\\.screensaver\\..\\{12\\}\\.plist' | sed 's/\\.plist//'") set lastPictureDirectoryChosen to (do shell script "defaults read " & screensaver & " lastPictureDirectoryChosen") set modulePath to (do shell script "defaults read " & screensaver & " modulePath") do shell script "defaults write " & screensaver & " lastPictureDirectoryChosen /Users/paul/Pictures/Airplanes" do shell script "defaults write " & screensaver & " modulePath \"/System/Library/ Frameworks/ScreenSaver.framework/Resources/Pictures Folder.saver\""
Each of these should actually be a single line. The Script Editor will perform
automatic line breaking and indenting similar to what is shown here, depending on the
size of your window.
The first line sets the variable screensaver to the file path of your screensaver
preferences file. You may notice that it involves another shell script. This is because
there is not a fixed file name for the screensaver preferences. For whatever reason,
Apple placed the screensaver preferences in the ByHost directory, where the files
all include your ethernet address in the name. Consequently, we have to find the file
without knowing its exact name. This is fairly simple using grep again, however,
since we know the format for the name. The name will be com.apple.screensaver.
followed by the address followed by .plist. The address is twelve characters long.
So the grep pattern here searches for the first part of the file name, followed by any
twelve characters, followed by the extension. A period has a special meaning for a
regular expression matcher, such as grep. It means that it can match any character.
Consequently, to match a period, you have to escape it by placing a \ in
before it. But a \ also has special meaning in AppleScript, so you first have to
escape the back slash in AppleScript by using \\., which will pass along to the
command shell to be passed to grep: \. . Now, we wouldn’t really need to escape
the periods, since they would match anyway, but I did for demonstration purposes.
The next line makes use of the OS X tool defaults, which reads and writes the
XML-based preference files used throughout OS X. In this case, it reads the property
named lastPictureDirectoryChosen from the screensaver preference file we located
in the previous line. The lastPictureDirectoryChosen property contains the file
path to the last folder the user set to use with the picture slideshow screensaver. We
need to save this because it is one of the things we’ll be changing to supply our own
folder of pictures.
The next line also makes use of the defaults functionality, this time reading in
what actual screensaver module the user currently has set. We need to save this because
we will be setting this to the slideshow screensaver, regardless of what it is currently
on.
Next we use defaults to write to the preference files. First we update the
lastPictureDirectoryChosen property, setting it to the file path of the folder in
which you want all the pictures to be displayed as your background. In my case, it is set
to a folder of airplane pictures. You can hard code this value, as I did here, or you
could make it a user input when the application is run, allowing you to specify each time
a potentially different folder with pictures.
The last line in this block sets the preference for the screensaver to use the Pictures
Folder screensaver. You can see there are more back-slashes in this line. That’s
because the path has spaces in it, and so must be within quotes. But if you just typed
a quote, that would end the string in the AppleScript, so you have to escape it with
\”.
If you ran the AppleScript with these new lines pasted above the earlier lines (with the
picture folder path set properly for your computer), your background would become an
animated slideshow of the pictures in that folder. However, this would also set your
actual screensaver to the same slideshow! What you need to do after starting the screensaver
module is restore the information you saved previously. That is what these final lines
accomplish:
do shell script "sleep 2; defaults write " & screensaver & " lastPictureDirectoryChosen " & "\"" & lastPictureDirectoryChosen & "\"" do shell script "defaults write " & screensaver & " modulePath " & "\"" & modulePath & "\""
The first line executes another shell command. Actually, it’s two commands combined, but
not piped together this time. The semicolor after sleep 2 indicates to the shell
process that these are two different commands. The reason we have the sleep command
(which in this cases causes the shell to wait for 2 seconds before continuing to execute
the next command on the line) is that we started the screensaver executable with the
& option, which means the shell won’t wait for it to finish executing. One consequence
of this is that we could potentially be restoring the user preferences before the background
screensaver actually reads the new values we wanted to supply. So we wait two seconds
before restoring the saved preferences. There are quotes again around the
lastPictureDirectoryChosen because we don’t know what the value is, and there may
be spaces or other strange characters in there, so the shell might interpret it other than
the way we want. Placing it in quotes (escaped for AppleScript, of course), ensures that
it will be treated as the path we want.
The last line, then, restores users actual screensaver preference.
Now when you put this together, it will read and save the user’s system screensaver
preferences, write the proper values for the screensaver you want to run in the background,
and restore the user’s preferences after waiting two seconds to ensure the background
values will be read. Running it again will kill the background screensaver and revert
to whatever your normal desktop is. The finished source code is available
here.