1

Edit: my original question, below, assumes there is a secret copy (a cache) of the system preferences settings. I have since learned that is not the case. However, my problem still needed a solution, which I have found and also posted, below. No special Objective-C process is needed. /edit.

I want to use the defaults write command to change a checkbox in System Preferences. E.g:

defaults write ./com.apple.speech.synthesis.general.prefs TimeAnnouncementPrefs -dict-add TimeAnnouncementsEnabled -bool YES 

However, the OS caches the preference files, so the defaults command has no effect until the cache is updated. The System Preferences app updates the cache after a change is made in its UI -- so it is possible. Some people have suggested sudo killall cfprefsd but that does not work in Big Sur.

Elsewhere, user3439894 has posted a good AppleScript that manipulates the System Preferences UI. But such UI manipulation does not work when the Screen Saver is running.

I'd like to figure out how the System Preferences app manages to update the preferences cache. Doing strings of the System Preferences app (at /System/Applications/System\ Preferences.app/Contents/macOS/System\ Preferences) shows a method updateCacheFileWithPrefPane: that might do the job, but I don't know how to call that method. The method, whatever it is, is private, but I only need this for myself (not a public app).

Is it possible to use dtrace or dtruss to figure out what the System Preferences app is doing to signal the OS to update its cache for a particular checkbox? I want to write an Objective-C program to do the same thing, but I need a dtrace/dtruss expert to help me.

8
  • 4
    It seems you might have this backwards. There is no cache - it’s a live database that programs subscribe to. Killing the database server doesn’t change the contents of the database, it just interrupts programs. You’re trying to make a program for a function that doesn’t exist. Could you clarify what problem you’re trying to solve by “killing the cache”? Commented May 5, 2021 at 8:22
  • I did not write "killing the cache" (nor do I want to). I want to update what the OS sees as the preference for that checkbox. I used "cache" because it's a copy of the preference visible to defaults (read or write), but defaults write does not affect that cached copy (in Big Sur). However, when the checkbox is changed using the System Preferences app, the value seen by defaults read AND the value seen by the OS are both changed, so the change takes effect. I want to use defaults write and then do whatever it is that the System Preferences app does to update the copy that the OS sees. Commented May 6, 2021 at 4:24
  • 1
    Jeff, RE: "But such UI manipulation does not work when the Screen Saver is running." -- You could try adding tell application "System Events" to stop screen savers as the first line of code in the script. In my testing after you mentioned it in the linked answer, it worked for me. -- Another method I use is to use /usr/libexec/PlistBuddy instead of defaults. Additionally either can be appended with ; killall cfprefsd or ; sudo killall cfprefsd, the latter of which needs to be done from an Admin account. If this doesn't work then rebooting will necessary. Commented May 6, 2021 at 12:41
  • 1
    So, you really don’t care about updating the cache, you want to automate changing a setting and have that setting stick? The title of the question is what keeps me returning to cache. Commented May 6, 2021 at 19:39
  • 1
    @bmike. Thank you. I have removed the term "cache" and updated the title (the old title was, "How to update the System Preferences cache using Objective-C"). I'm sorry about my confusion. I have learned that the "Announce the time:" preference sticks not because of a saved copy but because of a process that is (or is not) running as a result of changing the System Preferences checkbox. I have posted that solution. No special Objective-C process is needed. Commented May 13, 2021 at 22:35

2 Answers 2

1

macOS uses NSUserDefaults to implement defaults. It offers an Objective-C interface and is part of the Foundation framework used by System Preferences and most applications to deal with preferences.

Caching of defaults is handled by the framework and the calling application is not expected to know about or manipulate the cache. See NSUserDefaultsDidChangeNotification and Key Value Observation (KVO) for how applications can track changes.

CFPreferencesSynchronize

NSUserDefaults itself is implemented with the C based CFPreferences set of functions, found in CoreFoundation framework.

In this layer, the documentation for the function CFPreferencesSynchronize reads:

This function is the primitive synchronize mechanism for the higher level preference function CFPreferencesAppSynchronize it writes updated preferences to permanent storage, and reads the latest preferences from permanent storage.

Only the exact domain specified is modified. Note that to modify “Any User” preferences requires root privileges (or Admin privileges prior to OS X v10.6)—see Authorization Services Programming Guide.

Do not use this function directly unless you have a specific need. All arguments must be non-NULL. Do not use arbitrary user and host names, instead pass the pre-defined constants.

macOS 11.2 is partly open source. Be sure to check if defaults is part of the open section. This may be useful in determining which domains and suites the preferences affect.

1
  • This looks promising. I did a defaults write to change the checkbox and then ran a short program I wrote (UpdateSysPrefs.app) that executes CFPreferencesSynchronize(CFSTR(".GlobalPreferences"), kCFPreferencesAnyUser, kCFPreferencesCurrentHost) which returned true (success). But, that did not work -- the OS still used the previous checkbox value. I also tried using sudo open UpdateSysPrefs.app. Note that I want to update the System Preferences, not the user defaults for an application. What should I use for the first argument to CFPreferencesSynchronize (applicationID)? Commented May 6, 2021 at 6:12
1

I have a solution. The secret is to manipulate the macOS SpeechSynthesisServer.app after changing the Time Announce preference setting using defaults write. There is no saved copy of the settings, and no special Objective-C process is needed. (Dale Sedivec helped via email after I read his June 2019 post about System Preferences at assert.cc).

To turn "Announce the time:" ON:

defaults write ./com.apple.speech.synthesis.general.prefs TimeAnnouncementPrefs -dict-add TimeAnnouncementsEnabled -bool YES open /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesisServer.app 

To turn "Announce the time:" OFF:

defaults write ./com.apple.speech.synthesis.general.prefs TimeAnnouncementPrefs -dict-add TimeAnnouncementsEnabled -bool NO killall SpeechSynthesisServer # takes effect after 30 seconds 

In both cases, re-launching System Preferences causes the System Preferences GUI to show the new Time Announce checkbox setting. The launching or killing of SpeechSynthesisServer.app causes the OS to respect the new setting. I'm using Keyboard Maestro to do this.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.