Question

I am writing a preferences editor tool (see http://www.tempel.org/PrefsEditor). It is effectively a GUI version of the defaults command.

I have trouble reading (let alone writing) preferences of random sandboxed applications, though.

For instance, when I try to get the keys of the Maps app, I get NULL returned:

CFArrayRef prefs = CFPreferencesCopyKeyList (CFSTR("com.apple.Maps"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);

However, the defaults command is able to read those prefs:

defaults read com.apple.Maps

I like to know how the defaults command accomplishes this, trying to do the same in my tool.

Was it helpful?

Solution

try that:

CFPropertyListRef prop = CFPreferencesCopyValue(CFSTR("ElementsVersion"),
CFSTR("/Users/karsten/Library/Containers/com.apple.Maps/Data/Library/Preferences/com.apple.Maps"),
CFSTR("kCFPreferencesCurrentUser"),
CFSTR("kCFPreferencesAnyHost"));

seems you need the path to the file, not just the bundle-id

OTHER TIPS

Karsten’s answer is correct but for the sake of completeness, the defaults command uses the undocumented _CFPreferencesCopyApplicationMap() function to retrieve the full URL of the preferences.

#import <CoreFoundation/CoreFoundation.h>

extern CFDictionaryRef _CFPreferencesCopyApplicationMap(CFStringRef userName, CFStringRef hostName);

int main(int argc, char *argv[])
{
    @autoreleasepool
    {
        CFDictionaryRef applicationMap = _CFPreferencesCopyApplicationMap(kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
        CFArrayRef urls = CFDictionaryGetValue(applicationMap, CFSTR("com.apple.mail"));
        CFShow(urls);
        CFRelease(applicationMap);
    }
}

I've added to 0xced's excellent answer so that the code can be packaged into a command-line tool that accepts the bundle ID as an argument. Forgive me if this is obvious to experienced Mac programmers, but as someone who has never used CoreFoundation I found this to be non-trivial.

#import <CoreFoundation/CoreFoundation.h>

extern CFDictionaryRef _CFPreferencesCopyApplicationMap(CFStringRef userName, CFStringRef hostName);

int main(int argc, char *argv[]) {
    @autoreleasepool {
        if (argc < 2) {
            // Print usage string & exit.
            fprintf(stderr, "usage: GetPrefDomains bundle_id\n");
            exit(1);
        }
        
        // Get the bundle ID from the first command-line argument.
        CFStringRef bundleID = CFStringCreateWithCString(NULL, argv[1], kCFStringEncodingUTF8);
        
        // Get the list of preference domain urls.
        CFDictionaryRef applicationMap = _CFPreferencesCopyApplicationMap(kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
        CFArrayRef urls = CFDictionaryGetValue(applicationMap, bundleID);
        
        // If no urls exist (invalid bundle ID), exit.
        if (!urls) {
            fprintf(stderr, "No preference domains found.\n");
            exit(0);
        }
        
        // Print the paths to the preference domains.
        CFIndex urlsCount = CFArrayGetCount(urls);
        for (int i = 0; i < urlsCount; i++) {
            CFURLRef url = CFArrayGetValueAtIndex(urls, i);
            CFStringRef path = CFURLCopyPath(url);
            printf("%s\n", CFStringGetCStringPtr(path, kCFStringEncodingUTF8));
        }
        
        // Clean up.
        CFRelease(bundleID);
        CFRelease(applicationMap);
    }
}

Save the code as GetPrefDomains.m, compile, and invoke as: GetPrefDomains com.apple.mail

This was useful to me because surprisingly the defaults command is case-sensitive and misbehaves silently with certain Apple applications that are under the filesystem protections in SIP on Mojave 10.14 or later (Safari & Mail, most notably). Add in the fact that Apple's capitalization rules are not consistent (com.apple.mail vs. com.apple.Notes), sandboxed preference paths, and the fact that that the filesystem is not case-sensitive and you quickly run into some very frustrating edge cases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top