MacOS – Automate the repositioning and refreshing of menu bar icons

accessibilityapplescriptmacosmenu bar

Background
When menu bar items are rearranged manually using Cmd+drag, macOS writes a new "preferred position" preference to each app that is showing a menu bar icon. (This preference helps, but does not guarantee, that they retain their position for subsequent launches.)

Problem
I want to automate the saving of the menu bar icon positions, without interacting manually, for educational and accessibility reasons.

Question
How can I automate the repositioning of a menu bar item (which will trigger the system to write the new prefs)?

Or, how can I trigger macOS to write the new preferences directly (without automating a repositioning of a menu bar item)?

I do not want to interact with the menu items manually, or write out the preferences myself.

Best Answer

I've found a way to directly write the Preferred Position values using the shell command defaults. I'll leave the automation part out since I don't know the specifics of your project (I assume you know how to automate shell commands as the current user.) This should allow you to change the preferred values but won't move the menu bar items without a restart.

Background


The Defaults system is used to store certain user preferences on macOS. This happens to include the Preferred Position for Menu Bar items. I'll let Apple explain the basics better:

From man defaults:

Defaults allows users to read, write, and delete Mac OS X user defaults from a command-line shell. Mac OS X applications and other programs use the defaults system to record user preferences and other information that must be maintained when the applications aren't running (such as default font for new documents, or the position of an Info panel). Much of this information is accessible through an application's Preferences panel, but some of it isn't, such as the position of the Info panel.

User defaults belong to domains, which typically correspond to individual applications. Each domain has a dictionary of keys and values representing its defaults; for example, "Default Font" = "Helvetica". Keys are always strings, but values can be complex data structures comprising arrays, dictionaries, strings, and binary data. These data structures are stored as XML Property Lists.

In short, Defaults are categorized under Domains (the app or service the setting belongs to), are identified using Keys (the name of the particular setting), and each *Key has a Value assigned to it.


The Preferred Position defaults we care about are not all under one domain, since individual apps can add new items to the Menu Bar. However, the keys all share this format:

NSStatusItem Preferred Position <menu-item-name>

where <menu-item-name> is the name of each item a particular app or service adds.

The values are all stored as float numbers, usually integers but some have a decimal point.

Here are the macOS built-in Menu Item Defaults on my machine as an example.

'com.apple.systemuiserver': {
    "NSStatusItem Preferred Position Item-0" = 23;
    "NSStatusItem Preferred Position Siri" = 61;
    "NSStatusItem Preferred Position com.apple.menuextra.TimeMachine" = 548;
    "NSStatusItem Preferred Position com.apple.menuextra.airport" = 483;
    "NSStatusItem Preferred Position com.apple.menuextra.battery" = 179;
    "NSStatusItem Preferred Position com.apple.menuextra.bluetooth" = 513;
    "NSStatusItem Preferred Position com.apple.menuextra.textinput" = 127;
    "NSStatusItem Preferred Position com.apple.menuextra.volume" = 260;
}

com.apple.systemuiserver is a domain containing the key NSStatusItem Preferred Position Siri with value 61.

Listing All Current Preferred Position Defaults


In order to list all the domains, keys, and values for our Preferred Position Defaults, we can use the defaults find command to search for matches.

The command we need in this instance is defaults find "NSStatusItem Preferred Position".

$ defaults find "NSStatusItem Preferred Position"
Found 1 keys in domain 'com.coconut-flavour.coconutBattery-Menu': {
    "NSStatusItem Preferred Position Item-0" = "344.5";
}
Found 1 keys in domain 'com.lastpass.LastPass': {
    "NSStatusItem Preferred Position Item-0" = 795;
}
Found 8 keys in domain 'com.apple.systemuiserver': {
    "NSStatusItem Preferred Position Item-0" = 23;
    "NSStatusItem Preferred Position Siri" = 61;
    "NSStatusItem Preferred Position com.apple.menuextra.TimeMachine" = 548;
    "NSStatusItem Preferred Position com.apple.menuextra.airport" = 483;
    "NSStatusItem Preferred Position com.apple.menuextra.battery" = 179;
    "NSStatusItem Preferred Position com.apple.menuextra.bluetooth" = 513;
    "NSStatusItem Preferred Position com.apple.menuextra.textinput" = 127;
    "NSStatusItem Preferred Position com.apple.menuextra.volume" = 260;
}
Found 1 keys in domain 'org.pqrs.Karabiner-Menu': {
    "NSStatusItem Preferred Position Item-0" = 750;
}
Found 1 keys in domain 'com.google.GoogleDrive': {
    "NSStatusItem Preferred Position Item-0" = 607;
}
... and so on ...

Reading and Writing the Value of a Given Domain & Key


In order to read the value of a given domain and key, we need to use the defaults read command. To write, we use defaults write.

Here, I will read the value of the key NSStatusItem Preferred Position Siri at the com.apple.systemuiserver domain.

Note that the domain and key must be enclosed in double quotes.

$ defaults read "com.apple.systemuiserver" "NSStatusItem Preferred Position Siri"
61

Here, I will change the the key NSStatusItem Preferred Position Siri at the com.apple.systemuiserver domain to the value 42.7.

Note that the domain and key must be enclosed in double quotes, and the -float before the value

$ defaults write "com.apple.systemuiserver" "NSStatusItem Preferred Position Siri" -float 42.7
$ defaults read "com.apple.systemuiserver" "NSStatusItem Preferred Position Siri"
42.7

Again, I'll leave exactly how you automate this up to you, but this can all be easily integrated into your code.

If this doesn't work out for you, here are some links to the Apple Developer AppKit docs for adding items to the Menu Bar as well as for accessing the Defaults system using objective-c and swift. Not sure if they'll be any use to you but maybe they'll set you on the right track.

https://developer.apple.com/documentation/appkit/nsstatusitem

https://developer.apple.com/documentation/appkit/nsstatusbar

https://developer.apple.com/documentation/foundation/userdefaults