Desktop background on OS X 10.9 Mavericks

I upgraded to OS X 10.9 a week or so ago, along with everyone else in the Mac nerd world. And like everyone else in the Mac nerd world, something of mine broke. In my case, it was a small script that would alter com.apple.desktop.plist to daily change my desktop.

Essentially, you have no way in OS X of syncing desktop backgrounds between spaces. You can set desktops so that all your spaces have the same desktop, but if you want to cycle them randomly, the first time the OS changes them, they’ll be all different once again. The problem is not a huge one, but it fell into a particular category of being big enough to annoy me, and small enough that I could write a script to fix it.

Under OS X Lion (and before) these preferences were stored in ~/Library/Preferences/com.apple.desktop.plist, and my original script would open this up and edit values for each space. However, as soon as I moved to Mavericks, this broke. The script would continue to edit the plist file, but the desktop wouldn’t change in response.

The internet was of no help - I imagine this problem is pretty niche - so I ended up poking around in the guts of ~/Library/ to work out where OS X now stored desktop preferences. After several sessions of searching, I came across a promising file: ~/Application Support/Dock/desktoppicture.db. This is an SQLite database file, and poking around in its innards, it definitely seems to be linked to desktop background preferences:

1
2
3
4
5
6
7
8
9
10
11
12
> sqlite3 desktoppicture.db 
SQLite version 3.7.13 2012-07-17 17:46:21
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
data         displays     pictures     preferences  prefs        spaces     
sqlite> SELECT * FROM data;
~/Archive/Wallpapers/SimpleD
~/Archive/Wallpapers/SimpleD/code.png
~/Archive/Wallpapers/Merek
~/Archive/Wallpapers/Merek/Purple_Rain-Retina.jpg
~/Archive/Wallpapers/SimpleD/old-fashioned.png

At first, some of the tables look a bit cryptic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
sqlite> .header ON
sqlite> SELECT * FROM preferences;
key|data_id|picture_id
1|5|8
10|4|8
10|4|9
1|5|9
1|5|10
10|4|10
10|4|11
1|5|11
1|7|2
10|6|2
10|6|3
1|7|3
1|7|14
10|6|14
10|6|15
1|7|15
1|5|12
10|4|12
10|4|13
1|5|13
10|6|4
1|7|4
1|11|5
10|4|5
10|4|6
1|11|6
10|4|7
1|11|7
10|4|1
1|11|1

However, after a bit of investigation I pieced together some structure to them:

desktoppicture.db table layout. Arrows indicate references to other tables. I never took a course in proper UML.

This is how the tables relate:

While this is educational, the main thing we learn is that we hardly have to touch any of the database. All we need to do is extract the filenames from the data table, use them to pick new file names, and then update the table. Here’s the full ruby code to do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env ruby

require "sqlite3"

DB_LOC = File.join(ENV["HOME"], "Library/Application Support/Dock/desktoppicture.db")
DB = SQLite3::Database.new(DB_LOC)

DB.execute("SELECT ROWID,value FROM data").each do |(rowid,path)|
  path = File.expand_path(path)
  next if File.directory?(path)

  image_dir = File.dirname(path)
  new_image = Dir[File.join(image_dir,"*")].sort_by{ rand }[0]
  new_image.sub!(ENV["HOME"],"~")

  DB.execute("UPDATE data SET value=? WHERE ROWID=?",[new_image,rowid])  
end

`killall Dock`

We need to use File::expand_path because the database quite happily encodes your home directory as ~, which ruby doesn’t like. Similarly, we sub the home directory out at the end to put it in a form the system will like. We could probably get away with leaving the full path in there, but better safe than sorry. Finally, we need to run killall to restart the dock: it looks like otherwise the deskop won’t change background.

So there we go. It took a while to work out what was wrong, but Apple’s replacement desktop background file is surprisingly easy to edit.