hidden menu sometimes does not show

We have a pair of co-operating applications. The standard one maintains the main menu; there's also a login item (installed through +[SMAppService loginItemServiceWithIdentifier:]), which has its own GUI. To the user, both are presented as one application: very often, a login item's window is active while the standard app's main menu is presented (since login items cannot own the menu bar).

Normally it works all right. Nevertheless, when the main menu is hidden (System Settings / Control Center / Automatically hide and show the menu bar: Always), sometimes (intermittently) it does not show. We have ascertained that the setup is all right, i.e.,

  • the login item is the current app all right (by our own logs; also, NSWorkspace.sharedWorkspace.frontmostApplication of an independent application shows our login item)
  • the standard application owns the menu (again, by our logs, and NSWorkspace.sharedWorkspace.menuBarOwningApplication of an independent code shows our standard app).

Yet, when the user brings the pointer to the top of the screen, nothing happens. Notably, when menu is not hidden, the proper menu bar (of the standard application) is shown all the time.

Does anybody know what to do to fix the problem?

We have a pair of co-operating applications. The standard one maintains the main menu; there's also a login item (installed through +[SMAppService loginItemServiceWithIdentifier:]), which has its own GUI. To the user, both are presented as one application: very often, a login item's window is active while the standard app's main menu is presented (since login items cannot own the menu bar).

What does this cooperation actually involve? Is it just that they both run at the same time and they're cooperatively interacting with the same underlying data? Or are they directly manipulating each other interfaces? Also, does your main app always operate as a standard application or are their cases where it can also become faceless (or do anything else that's "unusual")?

On this point:

  • the login item is the current app all right (by our own logs; also, NSWorkspace.sharedWorkspace.frontmostApplication of an independent application shows our login item)
  • the standard application owns the menu (again, by our logs, and NSWorkspace.sharedWorkspace.menuBarOwningApplication of an independent code shows our standard app).

Which process logged that state? Do you have logging from both process at the same time so that you can see what they both "thought"?

Yet, when the user brings the pointer to the top of the screen, nothing happens. Notably, when menu is not hidden, the proper menu bar (of the standard application) is shown all the time.

Does anybody know what to do to fix the problem?

I need to get a better sense of what you're actually doing before I can guess to much.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Kevin,

thanks a lot for a quick answer!

What does this cooperation actually involve?

The applications communicate (through DO) and perform tasks as needed. Typically, the standard application (SA) sends requests to the login item (LI), which contains most of the GUI, whenever the user selects a menu item.

Is it just that they both run at the same time

When the problem with non-functional hidden menu happens, they do.

Sometimes only LI runs (when the user quits SA), but of course, there's no menu in this case and thus no problem either.

they're cooperatively interacting with the same underlying data

In fact the data is not shared; LI manages it, and if need be (e.g., to validate menu items), it sends the current state to SA through DO.

To be frank tho, I can't really see how this could be relevant to the problem that the menu of SA does not unhide properly when SA is the menu owner and the pointer goes up.

are they directly manipulating each other interfaces

Not sure what does this mean :( The apps communicate through DO, which affect both the GUIs:

  • when the user selects a menu item in SA, it (usually, not always — e.g., About... is solved completely SA-side) sends an appropriate request to LI, which updates its GUI accordingly
  • LI validates menu items for SA based on the current data and application state

None of this though actually happens in the simplest case when the problem happens. No menu (and essentially no SA/LI functionality) is really used. The typical scenario is

  1. LI runs and does nothing
  2. SA is launched, connects to LI and asks it to show the main window, which LI does (now, SA is menu owner, LI frontmost, all works well; when the pointer goes up, SA menu shows all right)
  3. SA is deactivated. Another application (e.g., System Settings, Console, Xcode, whatever) is frontmost/menu owner. When the pointer goes up, the menu of the other app shows
  4. LI is re-activated e.g., by clicking to its main window title bar. When it gets didBecomeActive, it checks whether SA happens to be the menu owner, and if not, it activates SA (to make it menu owner)
  5. When SA gets didBecomeActive, it activates LI (to make it frontmost)

Now, SA is the menu owner and LI frontmost. Nevertheless, (pretty often) when the pointer goes up, nothing happens — the SA menu is not shown.

does your main app always operate as a standard application

Yes, SA is normal application and never changes its presentationOptions (nor does other tricks like that).

Which process logged that state?

I wrote a very simple independent application which does nothing but

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSWorkspace *wsp=NSWorkspace.sharedWorkspace;
    NSLog(@"menu owner: “%@” front: “%@”", wsp.menuBarOwningApplication.localizedName, wsp.frontmostApplication.localizedName);
    [self performSelector:_cmd withObject:nil afterDelay:5];
}

I keep it running and observe its log.

Do you have logging from both process at the same time so that you can see what they both "thought"?

Yes, aside of the above, both LI and SA happen to log when they get didBecomeActive, and those logs show that the steps 4 and 5 above do happen all right.

The applications communicate (through DO) and perform tasks as needed. Typically, the standard application (SA) sends requests to the login item (LI), which contains most of the GUI, whenever the user selects a menu item.

As a "side bar" to your original questions, why is your login item running like this at all? If your login item is what actually runs/provides all of your interface, why not just have it transition in/out of "full app" mode as needed using "presentationOptions"? If the answer is "because it's a login item so it needs to be inside the main app", keep in mind that it's not particularly difficult to make the main app be what's actually running as your "login item". Our login item APIs are how you tell the system what it should run, but nothing prevents your login item from the launching "something else" after it's launched. There are some details you need to sort out to handle things like differentiating "the user launched my app" vs "login item launch", but those are simpler than dealing with a complicated entanglement between two components.

are they directly manipulating each other interfaces

Not sure what does this mean :( The apps communicate through DO, which affect both the GUIs:

What you're doing is what I meant be "direct manipulation". The simple case (which should "never" fail) is when a login item and it's parent app are entirely focused on whatever data/work/activity they manage, NOT on the "interface" of the other. To put that another way, the kind of issue you're seeing can't happen if the components never "know" about each other (don't check if the other's running, activate each other, etc). In that kind of architecture, your main app treats/interacts with your login item in EXACTLY the same way it does everything else in your system, so there isn't really anything "special" to break.

That's not what you're doing. Interaction in your SA cause UI activity in your LI and, more importantly, your LI is attempting to activate your SA. This scenario in particular:

  1. SA is deactivated. Another application (e.g., System Settings, Console, Xcode, whatever) is frontmost/menu owner. When the pointer goes up, the menu of the other app shows
  2. LI is re-activated e.g., by clicking to its main window title bar. When it gets didBecomeActive, it checks whether SA happens to be the menu owner, and if not, it activates SA (to make it menu owner)
  3. When SA gets didBecomeActive, it activates LI (to make it frontmost)

Is one I'd be concerned about. From the system perspective, your LI (or SA, depending on where the request originates) is attempting to steal focus and that's something AppKit is specifically trying to remove/prevent. The article "Passing control from one app to another with cooperative activation" talks about how this should be handled, but it opens with this warning:

_
When someone uses your app and another app unexpectedly steals focus, the user experience can be compromised. Not only is it inconvenient for people to switch back to their original app, if they’re typing when the switch occurs, they might accidentally enter text into the wrong app, inadvertently disclosing sensitive information.
_

This is just a guess, but I strongly suspect that the issues you're seeing only happen when another app is involved in the activation process, not if you first activate your SA directly (for example, but clicking on the dock).

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for the elaboration.

As a "side bar" to your original questions, why is your login item running like this at all?

Mostly for historical reasons (the applications are pretty old, 10+ years, and underwent many changes meantime). We are considering a serious refactoring, but now we need to solve the problem quickly.

Interaction in your SA cause UI activity in your LI and, more importantly, your LI is attempting to activate your SA.

Quite. Unless/until we refactor, we simply have to, for clicking into the LI window must bring up the SA menu.

Which works well and completely reliably when the menu is not hidden.

When it is hidden though, for some weird reason the very menu bar which would normally be visible does not show when the pointer goes up. That's the problem we need to solve now.

When someone uses your app and another app unexpectedly steals focus...

... is not what happens here. In our case, the other app does not “steal” the focus and it is not unexpected. The focus is given to it willingly, it is as expected, and both the GUIs are written so that it works properly.

We never encountered any problem like accidentally entering text into the wrong app or so, and never we need to switch back to the original app: as mentioned above, both the applications are written to work like one from the user's perspective.

Our problem is quite different: while the SA is the menu owner and the menu is hidden, it (occasionally) does not show when the pointer goes up.

I strongly suspect that the issues you're seeing only happen when another app is involved in the activation process

Not really. The only involvement of another application is that it is active (both menu owner and frontmost) before the problem occurs.

not if you first activate your SA directly (for example, but clicking on the dock).

Quite, that's not our current problem. It happens when SA is activated through LI, as already outlined above:

  1. another application is both menu owner and frontmost (which is the only involvement of any other application)
  2. the LI's window title is clicked (seems that when the window content is clicked, the problem occasionally happens, but when the title bar is clicked, it always happens; weird)
  3. this makes LI frontmost, but not menu owner (since it is a login item which does not mess with its presentation and thus can't have menu)
  4. therefore, LI activates SA, so that it becomes the menu owner (note SA does not steal focus; LI gives it willingly)
  5. then in the final step, SA activates LI to make its window the frontmost one (again, LI does not steal the focus; SA gives it willingly).

Now, LI is frontmost, SA is the menu owner. If menu is not hidden, the SA menu is at this moment shown properly. Alas, if the menu is hidden and at this moment the pointer goes up, nothing happens. What could we do to make sure that the SA menu occurs? Thanks.

When someone uses your app and another app unexpectedly steals focus...

... is not what happens here. In our case, the other app does not “steal” the focus and it is not unexpected. The focus is given to it willingly, it is as expected, and both the GUIs are written so that it works properly.

Making something clear, what I'm described there is the System's perspective about what's happening, which isn't necessarily the same as yours or the users. What you've described is what's "conceptually" happening, which isn't (necessarily) the same as what's the system sees.

There's actually a clear hint as to what's going on in the difference here:

"the LI's window title is clicked (seems that when the window content is clicked, the problem occasionally happens, but when the title bar is clicked, it always happens; weird)"

The difference here is actually pretty easy to explain. In general terms, focus here means "the app that currently receives key press events" which, in the standard/basic case is alose the app that owns the menu bar. However, FBA (Faceless Background Apps) are an odd case here- they've specifically told the system that they don't want to own the menu bar, but they sometimes do receive keyboard and menu (for example, cut/copy/paste) events.

Looking at your two cases:

  1. "when the title bar is clicked, it always happens"

Clicking on your window brought it to the foreground but it did not acquire key focus or become a menu target.

  1. "when the window content is clicked, the problem occasionally happens"

Sometimes you're clicking on something that does mean you acquire focus.

In other words, in #1 you're stealing focus but in #2 you're not. I suspect that's the underlying trigger here- that the focus shifts mean the system is confused/uncertain about exactly who should have focus and that ends up messing up the menu system when things align in just the wrong way.

In terms of workarounds/solution, this is something that's going to require experimentation and testing, but the point I'd focus on is here:

  1. then in the final step, SA activates LI to make its window the frontmost one (again, LI does not steal the focus; SA gives it willingly).

Two approaches I'd look at here:

  1. Unless you're using the basic window format, you SA never needs to formally activate your LI. It can send a message saying "show this" and your LI can simple present that window directly. How well this works depends a lot on the content you're actually dealing with- if you're not using the system windows (so use can't differentiate between inactive/active) and your primarily viewing data (so issues like keyboard event activation aren't relevant), then it's likely you'd never notice any difference. At the other extreme, it could look pretty broken. It all depends on exactly what you're doing.

  2. Have you specifically implemented the new cooperative activation model? You said that the SA gave it up, but did that specifically go through "yieldActivationToApplication" or did you use a different approach?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

hidden menu sometimes does not show
 
 
Q