macOS menubar application: main menu not being displayed
Asked Answered
A

5

17

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!

Aitch answered 27/12, 2016 at 6:24 Comment(2)
"The window shows up as expected" I assume the window also becomes first responder and doesn’t look like in the background or something. Correct?Isleen
I've run into this issue as well with my app. You're not alone :)Jenna
A
8

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.

Aitch answered 4/5, 2017 at 10:39 Comment(3)
I'm having the same issue, and I haven't found a working solution. Where do you run the above code? In the applicationDidFinishLaunching function of the AppDelegate ?Ambiguous
Hi! Did you ever find a better solution to this?Squarerigger
No, I did not. Sorry.Aitch
T
2

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)
})
Tsarevna answered 14/10, 2020 at 12:53 Comment(1)
I'v used this to fix an issue that locks the interaction with the menu bar, which happens when I use the app's icon (kept in dock) to re-open the app's window that has been previously hidden to enter a statusbar app stateIlluminance
T
1

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)
}
Torrell answered 29/4, 2021 at 22:6 Comment(0)
C
0

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

Chinquapin answered 4/5, 2020 at 16:59 Comment(2)
Thanks for your answer. One note: Apple Script won’t work on the Mac App Store. I think it doesn’t even work at all if your app is sandboxed.Aitch
Noted 😁@AitchChinquapin
P
0

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)
    }
}
Publicspirited answered 19/10, 2020 at 3:20 Comment(1)
Thanks for the suggestion. Unfortunately the menu bar appears, but clicking on the menu bar items does not work.Aitch

© 2022 - 2024 — McMap. All rights reserved.