Learning something new can be daunting. Don’t try to learn all the things at once.

Doing stuff to your Mac with AppleScript

A few days ago I realized that all five of my Mac desktop spaces were sporting the exact same default background image, and it has been that way for quite some time. Sad. But updating my wallpaper with fresh visuals is down at the bottom of my massive to-do list so I wondered: “Is there was a way to do this automatically?”

A cursory Google search revealed that this problem has been solved in countless ways already. But figuring things out on your own is a great way to sharpen your problem solving and critical thinking skills, and to learn something new. So one Saturday morning I decided to see if I could do just that.

I gave myself the following constraints:

  • The solution will update all of my desktop backgrounds automatically with random images. It will do this every morning.
  • The solution will leverage free tools that ship with the OS, nothing else.
  • I will have a working solution by noon.

My initial thought was that I could use something like Automator or AppleScript in conjunction with a Unix command like cron to achieve my goal and it turns out I wasn’t too far off the mark. Once I began looking things up on the tubes I quickly came across Philip Hutchison’s OS X Wallpaper Changer and used it as my starting point for learning about AppleScript. Alvin Alexander’s blog was a great resource for understanding syntax. I also quickly discovered that cron has been deprecated in favor of something called launchd for reasons. Nathan Grigg posted a helpful article that served as a solid introduction to the usage of launchd. I followed up with this in-depth site and Apple’s documentation as well.

The Script

(*AuthorLee Brenner, @leebert, http://leebrenner.mePurposeRandomly sets the background image for all your desktops.Use this in conjunction with launchd to update desktop backgrounds on an automatic schedule.Usage1.) Put this script in a folder.2.) Create a folder in the same location as the script and fill it with images.3.) Update the "imagePath" variable to the location of your image folder.4.) Update the "keyCodes" variable to the number of desktops you have...The keycode for the 1 key is 18, the keycode for the 2 key is 19, etc.So, if you have 3 desktops update the variable to {18,19,20}*)-- Variablesset imagePath to "Documents:DesktopImages:Images"set keyCodes to {18, 19, 20, 21, 22, 23}-- Workertell application "Finder"delay 2repeat with currentCode in keyCodestell application "System Events" to key code currentCode using {control down}delay 0.5set desktop picture to some file of folder (imagePath) of home as textdelay 0.5end repeattell application "System Events" to key code item 1 of keyCodes using {control down}end tell

After a few hours of hacking things together and several code refactors I came up with a concise little script that seems to get the job done. It iterates over every desktop space and assigns a random background image from a provided folder of images, and then returns to the primary desktop.

Here are some things I learned while writing this script:

  • I had to add a delay before doing anything. It seems like the script fails if I try to run it immediately upon logging in. I’m guessing it has something to do with the fact that I’m manipulating the UI.
  • As far as I know, AppleScript doesn’t expose a way to programmatically access all of your desktop spaces. I did some debugging with Philip Hutchison’s script (mentioned earlier) and found that the dekstop property only ever returns your primary desktop space. So my workaround was to invoke keyboard shortcuts to switch between spaces.
  • The downside to using keyboard shortcuts is that you have to introduce delays to account for UI animation otherwise the code to assign a new background image doesn’t execute.
  • Another downside to using keyboard shortcuts is that it is obtrusive since the code doesn’t execute instantaneously in the background.
  • Christopher Kielty has a handy list of keycodes.
  • Use log to help with your debugging efforts.
  • For some reason that I have yet to understand, the script sets the background image for my fourth desktop twice. It’s a complete mystery as to why since it is the exact same code executing on every iteration of the loop.

The launchd plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.naptime.pchanger</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/osascript</string>
<string>/Users/leebert15/Documents/DesktopImages/wallpaper.scpt</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</array>
</dict>
</plist>

It took a few tries to get the launchd plist file correct but I eventually got it working. It specifies to run my script at 5 am every day. If I’m not logged in - or if my computer is asleep - then it will automatically run at the next available opportunity. I want this to run whenever anyone is logged in so I placed the file in /Library/LaunchAgents.

Here are some things I learned while writing this XML:

  • I got tripped up on how to call my script from the plist file. You need to use the osascript command to run .scpt files. And the XML structure needs to be just right.
  • LaunchControl is pretty helpful in debugging plist problems if you don’t want to take the time to dig in and fully understand launchd’s many configuration possibilities.
  • NOTE: I’m actually using a plist file that just specifies RunAtLoad and no StartCalendarInterval but I left the old script here because it’s more interesting to look at. For me, it works better to just have the script run whenever I log onto my computer.

Wrapping Up

There are still two things I’d like to improve about this solution:

  • Browsing sites and downloading images is a time-consuming task, even if you only do it now-and-then. I’d like to update my script so that it automatically visits a list of my favorite sites and snags images for me on-the-fly.
  • As noted above, the current implementation for updating the desktop backgrounds is super kludgey. Ideally there is something in AppleScript that allows me to iterate over all desktops programmatically rather than being forced to use hard-coded key commands and delays.

There are probably many other problems with the solution that I don’t even realize. Nevertheless, it was a fun little exercise and it is working well enough for now.

Product designer, iOS developer, illustrator.