Fun with OS X defaults and launchd
February 7th, 2008
Here’s a nifty little prank you can pull on a friend (or coworker) with a Mac (works in both Tiger and Leopard). You should be fairly comfortable with working on the command line before you play with this. Also, follow these instructions at your own risk. While this procedure is pretty safe, we are going to be messing with the internal defaults of OS X. I am not responsible if you break your Mac.
Imagine this: You accidentally left your computer unlocked when you left for the evening. When you came back the next day, your wallpaper has been changed to something …humorous. So you sigh, and change your wallpaper back. Except, a few minutes later, the wallpaper is back. No matter how many times you change your wallpaper, the other image just keeps coming back a few minutes later. Restarting does not help. You also notice other little inconsistencies, like, whenever you go to reset your wallpaper, System Preferences still says it’s set to your original wallpaper, even though that’s obviously not what’s being displayed. And every couple of minutes, the Dock flickers. What’s going on?
First you need to know how you can change your wallpaper from the command line. Open up terminal and type defaults read com.apple.Desktop Background. You’ll get back a bunch of stuff that looks kinda like this:
{
480988512 = {
ChangePath = "/Users/krichard/Pictures/Wallpapers";
ChooseFolderPath = "/Users/krichard/Pictures/Wallpapers";
CollectionString = Wallpapers;
ImageFileAlias = <00000000 00e00003 00000000 c2cc314a 0000482b 00000000 00089e0c 001be568 0000c2fe 8ab30000 00000920 fffe0000 00000000 0000ffff ffff0001 00100008 9e0c0007 4cea0007 4cb40013 52b2000e 00260012 00740068 00650065 006d0070 00690072 0065005f 00310036 00380030 002e006a 00700067 000f001a 000c004d 00610063 0069006e 0074006f 00730068 00200048 00440012 00355573 6572732f 6b726963 68617264 2f506963 74757265 732f5761 6c6c7061 70657273 2f746865 656d7069 72655f31 3638302e 6a706700 00130001 2f000015 0002000f ffff0000 >;
ImageFilePath = “/Users/krichard/Pictures/Wallpapers/theempire_1680.jpg”;
Placement = Crop;
TimerPopUpTag = 6;
};
default = {
ChangePath = “/Users/krichard/Pictures/Wallpapers”;
ChooseFolderPath = “/Users/krichard/Pictures/Wallpapers”;
CollectionString = Wallpapers;
ImageFileAlias = <00000000 00e00003 00000000 c2cc314a 0000482b 00000000 00089e0c 001be568 0000c2fe 8ab30000 00000920 fffe0000 00000000 0000ffff ffff0001 00100008 9e0c0007 4cea0007 4cb40013 52b2000e 00260012 00740068 00650065 006d0070 00690072 0065005f 00310036 00380030 002e006a 00700067 000f001a 000c004d 00610063 0069006e 0074006f 00730068 00200048 00440012 00355573 6572732f 6b726963 68617264 2f506963 74757265 732f5761 6c6c7061 70657273 2f746865 656d7069 72655f31 3638302e 6a706700 00130001 2f000015 0002000f ffff0000 >;
ImageFilePath = “/Users/krichard/Pictures/Wallpapers/theempire_1680.jpg”;
Placement = Crop;
TimerPopUpTag = 6;
};
}
Copy and paste this into a text editor, then remove all the internal blocks except the one called default. Go ahead and try changing ImageFilePath to a different image on your machine, strip the newlines, and wrap the whole thing in single quotes and curly braces. The end result should look something like:
'{default = {ChangePath = "/Users/krichard/Pictures/Wallpapers"; ChooseFolderPath = "/Users/krichard/Pictures/Wallpapers"; CollectionString = Wallpapers; ImageFileAlias = <00000000 00e00003 00000000 c2cc314a 0000482b 00000000 00089e0c 001be568 0000c2fe 8ab30000 00000920 fffe0000 00000000 0000ffff ffff0001 00100008 9e0c0007 4cea0007 4cb40013 52b2000e 00260012 00740068 00650065 006d0070 00690072 0065005f 00310036 00380030 002e006a 00700067 000f001a 000c004d 00610063 0069006e 0074006f 00730068 00200048 00440012 00355573 6572732f 6b726963 68617264 2f506963 74757265 732f5761 6c6c7061 70657273 2f746865 656d7069 72655f31 3638302e 6a706700 00130001 2f000015 0002000f ffff0000 >; ImageFilePath = “/PATH/TO/NEW_IMAGE.jpg”; Placement = Crop; TimerPopUpTag = 6; };}’
Go back to Terminal, and type defaults write com.apple.Desktop Background and then paste the whole block we just made. Hit enter, and if you did it right, nothing will happen. Why not? Because (just like you have to restart Finder to see the change when you modify com.apple.finder AppleShowAllFiles to show hidden files in Finder), you need to restart the Dock for your change to be visible. To restart the Dock from Terminal, just type killall Dock. You’ll see the dock disappear and reappear, and now if you look at your desktop, you’ll see your new wallpaper image. However, if you go to Desktop & Screen Saver in System Preferences, it will appear as if your wallpaper is still set to your old image. I’m not sure how to change wallpaper from the command line so that it reflects the change in System Preferences, but it doesn’t much matter, especially if you’re doing this as a prank on someone else.
So now you know how to change wallpaper from the command line. Go ahead and save the two steps together as a shell script:
defaults write com.apple.Desktop Background '{default = {ChangePath = "/Users/krichard/Pictures/Wallpapers"; ChooseFolderPath = "/Users/krichard/Pictures/Wallpapers"; CollectionString = Wallpapers; ImageFileAlias = <00000000 00e00003 00000000 c2cc314a 0000482b 00000000 00089e0c 001be568 0000c2fe 8ab30000 00000920 fffe0000 00000000 0000ffff ffff0001 00100008 9e0c0007 4cea0007 4cb40013 52b2000e 00260012 00740068 00650065 006d0070 00690072 0065005f 00310036 00380030 002e006a 00700067 000f001a 000c004d 00610063 0069006e 0074006f 00730068 00200048 00440012 00355573 6572732f 6b726963 68617264 2f506963 74757265 732f5761 6c6c7061 70657273 2f746865 656d7069 72655f31 3638302e 6a706700 00130001 2f000015 0002000f ffff0000 >; ImageFilePath = “/PATH/TO/NEW_IMAGE.jpg”; Placement = Crop; TimerPopUpTag = 6; };}’
killall Dock
If you run this script from the command line (make sure you set this file as executable!), you’ll see that it replaces your wallpaper and restarts the Dock, all in one step.
Now we just need a way to have this shell script run automatically. Enter launchd - Apple’s replacement for cron (as well as a few other Unix utilities). New launchd tasks are configured through property lists. If you’ve ever poked around a bit on your harddrive, you’ve probably seen lots of files that ended in .plist. These are property lists. They are really just XML files, and you can create them with any text editor, but if you’ve got the Developer Tools installed, you can use Property List Editor.app instead. You should find it in /Developer/Applications/Utilities/. If you don’t have the Developer Tools installed, hang tight for a few minutes and I’ll give you the XML you’ll need.
Open up Property List Editor. Click on the New Root button, then click on the expand arrow next to the Root it creates, and you’ll get a New Child button instead. Click on New Child, and name the new property Label. Make sure it is a string. It doesn’t much matter what you type in here, as long as it’s unique from other launchd plists you make. I just put in com.apple.Desktop since that’s ultimately what we’re modifying. Add a sibling to Label, and call it ProgramArguments. Set it to be an Array, then click the arrow to expand it so you can add children. Add a child as a String, and set it’s value as the path to the shell script you created previously. Close the expanded ProgramArguments array, and add another sibling. This one is called StartInterval, and it’s a Number. Set this to however many seconds you would like launchd to wait between each subsequent running of your shell script. Alternately, you could use the StartCalendarInterval dictionary to tell your script to run on a certain minute, hour, day, etc. Finally, add one more sibling called UserName. This is a String, and you should set it to your own username if you are doing this on your own computer, or the username of the person you are pranking if you are doing this on someone else’s account/computer. This way, when the task runs, it will run as the user specified.
For a full description of all the various launchd options, you can type man launchd.plist in Terminal.
Save your new plist file. If you open it in a text editor, you will see a bunch of XML. It should look something like this (for people without Property List Editor - copy and paste this XML and just change the values that are in all UPPERCASE):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.Desktop</string>
<key>ProgramArguments</key>
<array>
<string>/PATH/TO/SHELLSCRIPT</string>
</array>
<key>StartInterval</key>
<integer>120</integer>
<key>UserName</key>
<string>USERNAME</string>
</dict>
</plist>
Now, go back to Terminal and type launchctl load /PATH/TO/PLIST, substituting in the plist file you just made. Now, no matter how many times you change your wallpaper, it will get automatically changed back every StartInterval seconds. You’ll also see the Dock flicker each time, as it gets restarted. However, if you restart your machine entirely, it will stop. We need a way to make sure that our new plist file gets loaded by launchctl every time the computer starts. Fortunately, this is really easy. All you have to do is drop your plist file into ~/Library/LaunchAgents/. Now whenever you log in, the plist file is automatically loaded by launchctl.
So now you have all the steps you need. However, if you’re using this to trick a friend, there’s a few extra steps you can take to make it even harder to track down. First of all, OS X doesn’t care about file extensions, or whether or not the files involved in this whole process are hidden. So, go ahead and rename your image, shell script, and plist file to innocuous looking file names that start with a ‘.’. You’ll have to rename them from Terminal, since Finder won’t let you rename a file so that it’s hidden (you can rename from the command line with mv oldfilename newfilename). Make sure you change the path in the shell script to point to the new path to the image, and the plist file to point to the new path to the shell script. Just make sure that the plist file (whatever you’ve named it) is in ~/Library/LaunchAgents. If you’d like to be extra sneaky, you can use touch -t YYYYMMDDhhmm filename (replace all those letters with numbers, like 200802061300 for 1 PM on 2/6/2008) to change the timestamps on your files so they look like they were created months ago instead of just today (Thank Jay for that extra bit of trickery).
You can either wait until your friend next restarts or logs in and the plist file is loaded automatically, or you can run launchctl load ~/Library/LaunchAgents/FILENAME to run it right away. Oh yeah, and clean up your tracks in Terminal by hitting CMD-K to clear the scrollback, then typing history -c to clear the command history.
So there you go! To get it to stop, just type launchctl unload ~/Library/LaunchAgents/PLISTFILENAME and move the plist file out of the LaunchAgents folder. Have fun, but try not to piss your friends off too much.
February 8th, 2008 at 11:01 am
[…] wrote up a long blog post explaining in detail how to hijack the background of an unlocked mac, inspired by a prank we pulled on a co-worker who made just such a mistake. In summary we set his […]