There are a number of tutorials/questions/answers on StackOverflow about being able to launch a GUI application with a simple program, without creating an app bundle and Info.plist. The majority of these use the Objective-C method [NSApp activateIgnoringOtherApps:true]
to bring the application to the foreground, along with [NSWindow makeKeyAndOrderFront:]
to bring the windows to the foreground.
The problem seems to be that on version 10.15 of macOS, the menu bar is not enabled until the first time you switch away from the app and back again. I've managed to reduce the problem to the following few lines of Swift:
// Run this, click on the Apple logo in the top left
// You cannot get the Apple system menu until you tab
// to another application and back again
import AppKit
var app = NSApplication.shared
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps: true)
app.run()
When this is run as a command line application, the Apple menu at the top left cannot be selected. If you switch from that app and then back again, it works.
Adding windows and doing the makeKeyAndOrderFront
has no effect; you can add them in, and the window does get the focus - but the menu bar isn't enabled until you switch away/back.
What's also confusing is that if you make a directory Example.app/Contents/MacOS
and then copy the above binary in, and use open
to launch the app, then the menu does work right from first launch. The difference appears to be the way in which the app is launched from the Terminal vs LaunchServices.
Is there any way to fix this minimal example so that the menu works right away?
For ease of reproduction, I have pushed the code to https://github.com/alblue/Bugger and there's a makefile that will do the build and run it from the command line (make run
) and via an app bundle (make app
).
I managed to discover a simple fix for this; move the activate into the applicationDidFinishLaunching
delegate:
import AppKit
@objc
class Delegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps: true)
}
}
var app = NSApplication.shared
let delegate = Delegate()
app.delegate = delegate
app.run()
After this change, the menu is clickable.
For 10.15, the call to setActivationPolicy
also needs to be in the applicationDidFinishLaunching
callback.