Autogenerating defaults(1) Commands

Posted on

In previous posts, I described some advanced uses of the macOS defaults(1) command, including adding arbitrarily complex data structures to defaults dictionaries. I also described less than obvious locations where system and applicaton preferences get recorded. Those were all background for this article.

It can be difficult know what plist file on disk stores a given setting, or exactly what the key (and corresponding value in that dictionary) are for a setting. In this post I’ll describe a tool I’ve written for detecting what file on disk is changed and exactly what the change was.

As a brief refresher, let’s look at a few examples of things you might configure on a new Mac and how you would apply those settings using defaults.

To start, it’s helpful to use the following shell alias to view all plist files as XML (whether they’re binary or XML on disk):

 (0) $ alias plcat='plutil -convert xml1 -o -'    
 (0) $ file foo.bar.plist
foo.bar.plist: Apple binary property list
-==< zach@endor:~/Library/Preferences >==-
 (0) $ plcat foo.bar.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>myarray</key>
    <array>
        <string>1.2</string>
        <string>3.14</string>
        <string>42</string>
        <integer>3</integer>
        <real>3.1400000000000001</real>
    </array>
</dict>
</plist>

If we wanted to set the dock’s position to the left of the screen, that preference is stored in ~/Library/Preferences/com.apple.dock.plist. There’s a key in that dictionary, orientation, whose value we can set to the string left.

 (0) $ plcat com.apple.dock.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>orientation</key>
    <string>left</string>
    ...
</dict>
</plist>

You can set that preference using:

defaults write 'com.apple.dock' 'orientation' -string left

You’d probably be able to work that out by looking at ~/Library/Preferences/com.apple.dock.plist. Some settings aren’t as straightforward, though. Look at keyboard shortcuts under System Preferences > Mission Control (the ones for “Mission Control”, “Application Windows,” etc.).

Changing “Show Desktop” from the default of F11, to Left Command, for example, involves modifying the subdictionary under the AppleSymbolicHotKeys key in that dictionary. More specifically it involves setting sub-subdictionaries for four different subkeys in that subdictionary. Unintuitively, the subkeys are 36, 37, 48, and 49. Here are plist contents chopped down to its relevant parts:

<?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>AppleSymbolicHotKeys</key>
    <dict>


        ...

        <key>36</key>
        <dict>
            <key>enabled</key>
            <false/>
        </dict>
        <key>37</key>
        <dict>
            <key>enabled</key>
            <false/>
        </dict>

        ...

        <key>48</key>
        <dict>
            <key>enabled</key>
            <true/>
            <key>value</key>
            <dict>
                <key>parameters</key>
                <array>
                    <integer>8</integer>
                    <integer>8</integer>
                </array>
                <key>type</key>
                <string>modifier</string>
            </dict>
        </dict>
        <key>49</key>
        <dict>
            <key>enabled</key>
            <true/>
            <key>value</key>
            <dict>
                <key>parameters</key>
                <array>
                    <integer>131080</integer>
                    <integer>131080</integer>
                </array>
                <key>type</key>
                <string>modifier</string>
            </dict>
        </dict>

        ...

    </dict>
</dict>
</plist>

Introducing prefsniff

The point of this example is to illustrate how complex and opaque some of the defaults structures can get. It would be hard to work out what subkeys to modify or what their values should be in order to set that preference. There are nested containers several layers deep and the values are not intuitive. I might not even know what defaults domain/plist was responsible for the Mission Control hotkeys. So I wrote a tool to work these things out for me. It’s called prefsniff, and you can get it here:

https://github.com/zcutlip/prefsniff

Here’s a screenshot of it in action.

Sniffing what default gets set. Sniffing what default gets set.

While you manually apply system or application preferences, prefsniff watches what changes and generates the magical defaults incantations to apply those settings. In the above case, we just tell prefsniff to monitor ~/Library/Preferences/com.apple.symbolichotkeys.plist for changes.

But what if you didn’t even know what plist file to watch? In that case, prefsniff will watch a directory and note what plist files get deleted and replaced as you apply settings:

$ prefsniff ~/Library/Preferences/
Watching prefs file:
Detected change: [deleted] /Users/zach/Library/Preferences/com.apple.symbolichotkeys.plist
Detected change: [created] /Users/zach/Library/Preferences/com.apple.symbolichotkeys.plist
Detected change: [modified] /Users/zach/Library/Preferences/
Detected change: [deleted] /Users/zach/Library/Preferences/com.apple.symbolichotkeys.plist
Detected change: [created] /Users/zach/Library/Preferences/com.apple.symbolichotkeys.plist
Detected change: [modified] /Users/zach/Library/Preferences/

Above, as we change the mission control hotkeys and change them back, we see com.apple.symbolichotkeys.plist getting deleted and created. That’s the file to watch for changes.

Future Work

Here’s a list of things I hope to add or change about prefsniff as personal time allows:

  • Test for python 3 compat
  • Implement data and date plist types
  • Make an actual installer
  • Clean up output so that it can be redirected to a shell script or similar
  • Add additional output options (such as the name of a shell script to create)
  • Split utility & API
    • Make prefsniff into a python module that exports API
    • Make a separate prefsniff command-line utility that uses the API

You can grab prefsniff from my GitHub.