Control status item and login item from within app

In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.

Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings. Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting. Should I remove the option inside the app?

This also makes me think of login items, which can be managed both in System Settings and inside the app via SMAppService. Some users ask why my app doesn't have a launch at login option, and I tell them that System Settings already offers that functionality. Since there is SMAppService I could offer an option inside the app that is kept in sync with System Settings, but I prefer to avoid duplicating functionality, particularly if it's something that is changed once by the user and then rarely (if ever) changed afterwards. But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows to change the option in System Settings)? If the status item can be overridden in System Settings, why do login items behave differently?

Answered by DTS Engineer in 859969022

I tried before posting, but isVisible stays true even when disabling the status item in System Settings.

Hmmm. Please file a bug on that, as you should be able to detect your status.

But will the agent be listed in System Settings > General > Login Items?

No, it'll be shown immediately below in the section labeled "App Background Activity".

In my opinion, it would make more sense to use an option that is kept in sync between the app and System Settings.

Yes, and that's how this works. SMAppService.status returns the app’s current "state" in the preference UI.

Looking at how different privacy features are implemented in macOS, I would have expected that login items (or agents, or however we might call them) can be disabled in System Settings, without the app being able to turn them on again without the user noticing.

Yes, and that's how this works too. If your status is "SMAppServiceStatusRequiresApproval", then the user will need to activate the component themselves through the preference UI. Having said that, one thing to understand here is that app lifetime and process management is FAR less controlled on macOS than it is on iOS and probably always will be.

If not, then that could be another source of confusion, similar to the status item: the user can enable "launch at login" both in the app (which uses an agent) and in System Settings (which uses a login item), then later removes it from System Settings and wonders why it keeps being launched at login (because they forgot that they have to disable it in the app as well).

No, not really.

All of the SMAppService APIs are tied to objects "inside" your app bundle. The user could add your app to login items, but adding the login item INSIDE your app to the login item list is... trickier. It can be done, but the UI in System Settings won't navigate "inside" an app bundle. To add an item like this, you'd need to use "Show Package Contents" in the Finder to navigate to the item, then drop that item inside the interface. Certainly possible, but not something that's going to happen by accident.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

So, let me start with the "why" at the heart of this:

But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows you to change the option in System Settings)?

This is basically a case of "convergent" evolution between two totally unrelated system components addressing two unrelated issues.

(1) "Login Items"

These actually come from classic macOS and were originally "Startup Items", which were nothing more than "the apps the users have configured to launch when the system starts". Originally this was "start up" because the feature existed before macOS allowed multiple logins. Critically, this was a USER feature, not API. As I recall, apps could "make" themselves startup/login items by adding an alias to the right directory, but this was generally considered "rude" and most apps didn't "do that". If the user wanted your app to launch, they'd add it themselves.

This same functionality was included in macOS X and, eventually, API was created to allow apps to add themselves to the same list.

(2) "Agents and Daemons"

macOS X has always been built as a collection of processes running in different contexts ("system wide" vs "specific to a logged-in user") and that eventually led to launchd and the formal division between "daemons" and "agents". See the classic TN2083: Daemons and Agents for more information.

Historically, the main difference here is that "Login Items" were part of the "high level" system, so they were always "apps". By contract, agents/daemons came from the "low level" system, so they could basically be "anything"- standalone apps, bare executables, shell scripts... basically, anything Unix can run can be run through launchd. The other difference is how they were configured. Login Items went through the UI, while launchd used configuration plist in specific directories.

That leads to here:

If the status item can be overridden in System Settings, why do login items behave differently?

What's happened since then is that the mechanisms have "evolved" toward each other. SMAppService consolidated the three different mechanisms into the same API, which is how things stand today.

Theoretically, keeping this option inside the app could lead to confusion.

Yes, the state of things leaves plenty of space for confusion. The behavioral differences you see are fallout from that long and complicated history, which has only recently ended up at the place we are today. If we were doing this "today", we'd either eliminate them entirely in favor of agents or keep them "around" as an EXCLUSIVELY user-level feature which apps had NO control over. In terms of what your app "should" do, I'd generally recommend using an agent (not login item) for any kind of new development but could argue both sides if you're considering switching architectures.

In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.

Yes, this is new.

Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings.

That's ultimately a judgement call you'll need to make, but my own preference would probably be to continue providing an in-app option.

Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting.

I haven't tested this, but I think isVisible will tell you when it's been disabled. However, post back if it doesn't work and I'll try and track down a more definitive answer.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for your input.

I haven't tested this, but I think isVisible will tell you when it's been disabled.

I tried before posting, but isVisible stays true even when disabling the status item in System Settings.

I'd generally recommend using an agent (not login item)

But will the agent be listed in System Settings > General > Login Items? If not, then that could be another source of confusion, similar to the status item: the user can enable "launch at login" both in the app (which uses an agent) and in System Settings (which uses a login item), then later removes it from System Settings and wonders why it keeps being launched at login (because they forgot that they have to disable it in the app as well). In my opinion it would make more sense to use an option that is kept in sync between the app and System Settings, or remove it from the app entirely since it can already be done in System Settings. Looking at how different privacy features are implemented in macOS, I would have expected that login items (or agents, or however we might call them) can be disabled in System Settings, without the app being able to turn them on again without the user noticing.

That's the same reasoning that makes me want to remove the option to show or hide the status item from my app in macOS 26: System Settings has the last word in regard to the status item's visibility, so it would only be confusing to have that option in the app if toggling it doesn't do anything. After all, the status item is something similar to a widget, and I haven't done widgets yet but I would assume that they can only be added and removed by the user outside of the app (in macOS itself).

I tried before posting, but isVisible stays true even when disabling the status item in System Settings.

Hmmm. Please file a bug on that, as you should be able to detect your status.

But will the agent be listed in System Settings > General > Login Items?

No, it'll be shown immediately below in the section labeled "App Background Activity".

In my opinion, it would make more sense to use an option that is kept in sync between the app and System Settings.

Yes, and that's how this works. SMAppService.status returns the app’s current "state" in the preference UI.

Looking at how different privacy features are implemented in macOS, I would have expected that login items (or agents, or however we might call them) can be disabled in System Settings, without the app being able to turn them on again without the user noticing.

Yes, and that's how this works too. If your status is "SMAppServiceStatusRequiresApproval", then the user will need to activate the component themselves through the preference UI. Having said that, one thing to understand here is that app lifetime and process management is FAR less controlled on macOS than it is on iOS and probably always will be.

If not, then that could be another source of confusion, similar to the status item: the user can enable "launch at login" both in the app (which uses an agent) and in System Settings (which uses a login item), then later removes it from System Settings and wonders why it keeps being launched at login (because they forgot that they have to disable it in the app as well).

No, not really.

All of the SMAppService APIs are tied to objects "inside" your app bundle. The user could add your app to login items, but adding the login item INSIDE your app to the login item list is... trickier. It can be done, but the UI in System Settings won't navigate "inside" an app bundle. To add an item like this, you'd need to use "Show Package Contents" in the Finder to navigate to the item, then drop that item inside the interface. Certainly possible, but not something that's going to happen by accident.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for explaining about SMAppService. Admittedly I haven't used it to add login items or agents yet, and I probably made some wrong assumptions about how it would work.

Regarding the status item, I filed FB20384263. Below is the code I attached to reproduce it.

If this bug gets solved, will I be able to change isVisible back to true from within the app and have it reflected in System Settings? That's what I was talking about in my previous reply: if the current behaviour is intended, then one could enable the status item in the app, disable it in System Settings, and later become mad at the app because apparently the in-app preference doesn't make the status item visible again. That's why I was considering to remove the in-app preference and let the user toggle it in System Settings, just like with the system status bar icons. I personally like the idea of an app offering the functionality and then letting the user enable or disable it outside of the app, just like widgets or extensions (e.g. for Finder and Shortcuts).

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var statusItem: NSStatusItem!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        statusItem = NSStatusBar.system.statusItem(withLength: 20)
        statusItem.button?.image = NSImage(systemSymbolName: "globe", accessibilityDescription: nil)
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
            print(Date(), statusItem.isVisible)
        }
    }

}

If this bug gets solved, will I be able to change isVisible back to true from within the app and have it reflected in System Settings?

I don't know what the final resolution might be (or how long it might take), but we really need two APIs. One would be "isVisible", meaning "can the user see my item" (it could be invisible because it's offscreen). The other would then be for whether or not the system has enabled it.

That's what I was talking about in my previous reply: if the current behaviour is intended, then one could enable the status item in the app, disable it in System Settings, and later become mad at the app because apparently the in-app preference doesn't make the status item visible again.

So, the problem here is that there are two unrelated mechanisms at work here:

  1. Getting your code to run at login, through either of the two SMAppService options we talked about above. In Settings, this is managed through "Login Items & Extensions".

  2. Whether or not your app can use the status bar, which is managed through "Menu Bar" in Settings.

The problem is that those two settings are TOTALLY independent of each other. Running at login doesn't mean you can use the status bar and enabling the status bar doesn't have any effect on what your app does. The worst case here is that the user needs to change two separate settings (1 to enable login, 2 to allow the status bar).

That leads to here:

That's why I was considering removing the in-app preference and letting the user toggle it in System Settings, just like with the system status bar icons.

At this point, I think your best option is to keep the activation inside your app (#1) and use that part of your interface to mention issue #2. I think that's your best option for mitigating this:

...later become mad at the app because apparently the in-app preference doesn't make the status item visible again.

...because what actually causes this user frustration is that they don't even know that setting #2 exists. You can't detect whether or not you have access to the menu bar, but keeping in app activation at least gives you the interface "space" to tell them that #2 might be a problem.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I'm sorry for the confusion, I know the two are completely unrelated. I just wanted to point out that they are currently different in the following way: one can be controlled within the app (login items), and the other one cannot (status item). For me, it would have made sense if both can be managed in System Settings alone. I just thought that perhaps it was Apple's intention to "slowly" migrate the status item to something that can strictly be toggled in System Settings (similarly to widgets and extensions which cannot be added or removed by the app itself).

I'm sorry for the confusion. I know the two are completely unrelated. I just wanted to point out that they are currently different in the following way:

Again, I want to make sure it's very clear about what's actually going on here:

one can be controlled within the app (login items),

(1) There are TWO things here, not one. Those are "Login Items" and "Launch Agents", which are the convergence of two unrelated APIs. Programmatically, both are controlled through the "SMAppService" API and managed in Settings through "Login Items & Extensions". "Launch Agents" are the more powerful* of the two technologies and, IMHO, should generally be considered the preferred solution.

and the other one cannot (status item).

(2) As far as the system is concerned, there is no such thing as a "status item". NSStatusBar is an API that any app (Login Item, Launch Agent, or standard application) can use. There is a setting inside "Menu Bar" that allows the user to prevent any given app from showing up in the menu bar. There isn't currently any programmatic API to detect that setting.

I just thought that perhaps it was Apple's intention to "slowly" migrate the status item to something that can strictly be toggled in System Settings.

While this is theoretically possible, I don't think this is particularly likely to occur as the recent evolution of our API has actually made it easier for an app to programmatically manage this data, not harder. SMAppService is a relatively new API (it was introduced in macOS 13) and what it replaced was management through launchctl and/or the installation of privileged helper tools through SMJobBless.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Control status item and login item from within app
 
 
Q