KISS

Be Among the 20% of the Best!

OS X: Flexible Random Screensavers

| comments

Welcome back!

XScreenSaver

Today’s post is a trivial script to pick a random screensaver every day on OS X. Yes, if you go the “Desktop & Screen Saver” preference pane, there is the last item called “Random”, but it’s very limited — a screensaver is picked randomly among all available every time when it’s requested. To show why that’s not what I need, let’s install a pack of great screensavers, which has been ported to OSX — XScreenSaver:

1
2
3
# run this command if you don't have `brew cask` installed yet
$ brew tap caskroom/cask
$ brew cask install xscreensaver

Now, I don’t want to run some of the screensavers and none of the standard ones. Plus, I want one to be picked at random every day, not on every use. Check out “XAnalogTV”, “Substrate”, or “BSOD”.

What’s the current screensaver?

This article directed me to the right path by showing how to change the current screensaver from the command line: http://krypted.com/mac-security/mac-setting-screen-saver-from-the-cli/. I’m going to sometimes use PlistBuddy instead of defaults, because the former one is more modern and functional. PlistBuddy is located in /usr/libexec/, which is not included in $PATH by default, so let’s fix this with symlinking it into /usr/local/bin/ (which should be in $PATH if you’re using brew):

1
$ ln -s /usr/libexec/PlistBuddy /usr/local/bin/plistbuddy

Let’s start by printing the current screensaver information:

1
2
3
4
5
6
$ plistbuddy -c "Print :moduleDict" ~/Library/Preferences/ByHost/com.apple.screenSaver.$( ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ && gsub("\"", "") { print $3 }' ).plist
Dict {
    moduleName = iLifeSlideshows
    path = /System/Library/Frameworks/ScreenSaver.framework/Resources/iLifeSlideshows.saver
    type = 0
}

Here you see its name (moduleName) and path. We’ll need to update those values from our script. The ioreg … subcommand gets the host’s UUID to figure out the proper plist filename — that command is taken from this article: http://wranglingmacs.blogspot.com/2009/04/getting-byhost-string.html.

Setting a random screensaver

You can set a random xscreensaver from command line like this:

1
2
3
4
# `coreutils` provides GNU versions of core utils, including `gshuf`, which is absent on OSX
$ brew install coreutils

$ f=${$( ls -1 ~/Library/Screen\ Savers | gshuf -n1 )%.saver}; defaults -currentHost write com.apple.screensaver moduleDict '{"type"=0; "path"="'$HOME/Library/Screen\ Savers/$f.saver'"; "moduleName"='$f';}'

(To my surprise, the command with plistbuddy didn’t work: plistbuddy -c "Set :moduleDict:moduleName $f" -c "Set :moduleDict:path $HOME/Library/Screen Savers/$f.saver" ~/Library/Preferences/ByHost/com.apple.screenSaver.$( ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ && gsub("\"", "") { print $3 }' ).plist).

Now, I’d like to pick a random screensaver from a subset of all screensavers. So I’m listing the existing xscreensavers into a file, from which one will be selected:

1
$ ls -1d ~/Library/Screen\ Savers/*.saver | sed -E 's!.*/(.*)\.saver!\1!' > ~/.screensavers

(That’s a weird way of getting the base name, but basename doesn’t like spaces in names). You can remove ones you don’t want from the file now or later. And the setting script becomes a little shorter:

1
$ f=$( gshuf -n1 ~/.screensavers ); defaults -currentHost write com.apple.screensaver moduleDict '{"type"=0; "path"="'$HOME/Library/Screen\ Savers/$f.saver'"; "moduleName"='$f';}'

Autoupdate

And the last step, autoupdate. We’ll use our old friend, crond daemon (I know, we should use launchd on OS X, which has crond features and more, but the config is more verbose, so I may upload it later if necessary). Add this line to your ~/.crontab file (a random screensaver is set at 09:00 every day):

1
0 9 * * * f=$( /usr/local/bin/gshuf -n1 $HOME/.screensavers ); /usr/bin/defaults -currentHost write com.apple.screensaver moduleDict '{"type"=0; "path"="'$HOME/Library/Screen\ Savers/$f.saver'"; "moduleName"='$f';}'

and set it with crontab ~/.crontab. If this doesn’t work, you can check your local mail with mail and should see mails with the output from stderr.

Testing, testing, one, two, three

To quickly test the result, you can use either a hot corner that starts a screensaver, or define and use this alias:

1
$ alias sc='open -a /System/Library/Frameworks/ScreenSaver.framework/Versions/A/Resources/ScreenSaverEngine.app'

Comments