macOS menubar application: main menu not being displayed

3.3k views Asked by At

I have a statusbar application, which runs in the menu bar. Therefore I set Application is agent (UIElement) to true in the info.plst. That results in no dock icon and no menu bar for my application.

However, I also have a preference window that the user can open from the statusbar menu. Here is how I open it:

if (!NSApp.setActivationPolicy(.regular)) {
    print("unable to set regular activation policy")
}
NSApp.activate(ignoringOtherApps: true)
if let window = preferencesWindowController.window {
    window.makeKeyAndOrderFront(nil)
}

The window shows up as expected, but the application's main menu bar with File, Edit and so on, does not show up. Only if I click on another app and come back to my app, the menubar is being displayed.

I noticed, that if I change the value in the info.plst to false and use NSApp.setActivationPolicy(.accessory) in applicationDidFinishLaunching(), it has the same result. However, if I call NSApp.setActivationPolicy(.accessory) with a timer a few milliseconds after applicationDidFinishLaunching() is being called, it works and the main menu is being displayed as expected. This however has the side effect that the app icon pops up in the dock for a few seconds (until the timer is being fired), which is not a nice user experience.

Does anyone have an idea what else I could try? What is happening when I switch the active app, that I am not doing in code?

I am using Version 8.2.1 (8C1002) on macOS 10.12.2 (16C67)

Thanks!

5

There are 5 answers

3
Daniel On BEST ANSWER

This is my workaround solution for now:

As I wrote in the question, if I click on another app and come back to my app, the menubar is being displayed. I am simulating this when I try to show the preference window:

    NSApp.setActivationPolicy(.regular)
    NSApp.activate(ignoringOtherApps: true)
    window.makeKeyAndOrderFront(nil)

   if (NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.activate(options: []))! {
       let deadlineTime = DispatchTime.now() + .milliseconds(200)
       DispatchQueue.main.asyncAfter(deadline: deadlineTime) {                 
       NSApp.setActivationPolicy(.regular)
            NSApp.activate(ignoringOtherApps: true)
       }
   }

This is not a perfect solution. If I don't find a better solution, I will file a bug.

1
Mark Sinkovics On

I might found another solution for this (which is still an issue on macOS 10.15). I found a similar issue + solution here. The idea is about to show and hide the menuBar for the current application. I needed to run it 2 different run loops but it worked. Here is the solution:

OperationQueue.main.addOperation {
    NSMenu.setMenuBarVisible(false)
    OperationQueue.main.addOperation {
        NSMenu.setMenuBarVisible(true)
    }
}
1
bauerMusic On

Based on the fix (or workaround) from OP. The key is really to toggle out and back to focus (thanks @Daniel!).

Some small improvements:

No force unwrapping, no need to set ActivationPolicy and call activate twice.

NSApp.setActivationPolicy(.regular)
window.makeKeyAndOrderFront(nil)    

NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.activate(options: [])

OperationQueue.current?.underlyingQueue?.asyncAfter(deadline: .now() + .milliseconds(200), execute: {
    NSApp.activate(ignoringOtherApps: true)
})
0
gdub On

I found another solution that doesn't involve focusing the dock and back, which was causing a flicker as the app exited/entered focus. This sets the activation policy to prohibited then regular in the next tick:

if NSApp.activationPolicy() != .regular {
  NSApp.setActivationPolicy(.prohibited)
  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(200)) {
      NSApp.setActivationPolicy(.regular)
      NSApp.activate(ignoringOtherApps: true)
      window.makeKeyAndOrderFront(nil)
  }
} else {
    // activation policy was already regular, just do normal window opening
    NSApp.activate(ignoringOtherApps: true)
    window.makeKeyAndOrderFront(nil)
}
2
Cheungbo Mong On

I tried with apple scripts, which appears working as expected.

Except, after closing by key strokes and then reopening a new window (short time interval), menu will stay selected.

if NSApp.activationPolicy() == .accessory {
    NSApp.setActivationPolicy(.regular)
} else {
    var errorDict: NSDictionary?
    NSAppleScript(source: """
        tell application "Dock" to activate current application
        """)?.executeAndReturnError(&errorDict)
    if errorDict != nil{
        print(errorDict!)
        // error executing apple script
        NSApp.activate(ignoringOtherApps: true)
    }
}

window?.makeKeyAndOrderFront(self)

GIF demo:

Video link