mac/app: cleanup and optimise App launch and termination

NSApp.terminate() is not a requirement to properly shut down a cocoa
App since it only calls exit() internally. though when not used the
cocoa termination events won't trigger, which we don't need. this
prevented us to exit with a proper exit code.

rework the whole termination logic to end up at one point where we can
return the exit code from the mpv_main function.

Fixes #7456
This commit is contained in:
der richter 2024-04-01 01:00:19 +02:00
parent d6c621b03b
commit 3c51497954
2 changed files with 26 additions and 52 deletions

View File

@ -19,6 +19,8 @@ import Cocoa
class Application: NSApplication, NSApplicationDelegate { class Application: NSApplication, NSApplicationDelegate {
var appHub: AppHub { get { return AppHub.shared } } var appHub: AppHub { get { return AppHub.shared } }
var eventManager: NSAppleEventManager { get { return NSAppleEventManager.shared() } }
var isBundle: Bool { get { return ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true" } }
var playbackThreadId: mp_thread! var playbackThreadId: mp_thread!
var argc: Int32? var argc: Int32?
var argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>? var argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
@ -31,34 +33,6 @@ class Application: NSApplication, NSApplicationDelegate {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
let eventManager = NSAppleEventManager.shared()
eventManager.removeEventHandler(forEventClass: AEEventClass(kCoreEventClass), andEventID: kAEQuitApplication)
}
func initApplication(_ regular: Bool) {
NSApp = self
NSApp.delegate = self
// Will be set to Regular from cocoa_common during UI creation so that we
// don't create an icon when playing audio only files.
NSApp.setActivationPolicy(regular ? .regular : .accessory)
atexit_b({
// Because activation policy has just been set to behave like a real
// application, that policy must be reset on exit to prevent, among
// other things, the menubar created here from remaining on screen.
DispatchQueue.main.async { NSApp.setActivationPolicy(.prohibited) }
})
}
func terminateApplication() {
DispatchQueue.main.async {
NSApp.hide(NSApp)
NSApp.terminate(NSApp)
}
}
override func sendEvent(_ event: NSEvent) { override func sendEvent(_ event: NSEvent) {
if modalWindow != nil || !appHub.input.processKey(event: event) { if modalWindow != nil || !appHub.input.processKey(event: event) {
super.sendEvent(event) super.sendEvent(event)
@ -77,29 +51,36 @@ class Application: NSApplication, NSApplicationDelegate {
} }
func applicationWillFinishLaunching(_ notification: Notification) { func applicationWillFinishLaunching(_ notification: Notification) {
let eventManager = NSAppleEventManager.shared() // register quit and exit events
eventManager.setEventHandler( eventManager.setEventHandler(
self, self,
andSelector: #selector(handleQuit(event:replyEvent:)), andSelector: #selector(handleQuit(event:replyEvent:)),
forEventClass: AEEventClass(kCoreEventClass), forEventClass: AEEventClass(kCoreEventClass),
andEventID: kAEQuitApplication andEventID: kAEQuitApplication
) )
atexit_b({
// clean up after exit() was called
DispatchQueue.main.async {
NSApp.hide(NSApp)
NSApp.setActivationPolicy(.prohibited)
self.eventManager.removeEventHandler(forEventClass: AEEventClass(kCoreEventClass), andEventID: kAEQuitApplication)
}
})
} }
// quit from App icon // quit from App icon, external quit from NSWorkspace
@objc func handleQuit(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) { @objc func handleQuit(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
// send quit to core, terminates mpv_main called in playbackThread,
if !appHub.input.command("quit") { if !appHub.input.command("quit") {
terminateApplication() appHub.log.warning("Could not properly shut down mpv")
exit(1)
} }
} }
func bundleStartedFromFinder() -> Bool {
return ProcessInfo.processInfo.environment["MPVBUNDLE"] == "true"
}
func setupBundle() { func setupBundle() {
if !isBundle { return }
// started from finder the first argument after the binary may start with -psn_ // started from finder the first argument after the binary may start with -psn_
// remove it and all following
if CommandLine.argc > 1 && CommandLine.arguments[1].hasPrefix("-psn_") { if CommandLine.argc > 1 && CommandLine.arguments[1].hasPrefix("-psn_") {
argc? = 1 argc? = 1
argv?[1] = nil argv?[1] = nil
@ -113,28 +94,24 @@ class Application: NSApplication, NSApplicationDelegate {
let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
let application: Application = TypeHelper.bridge(ptr: ptr) let application: Application = TypeHelper.bridge(ptr: ptr)
mp_thread_set_name("core/playback") mp_thread_set_name("core/playback")
let r: Int32 = mpv_main(application.argc ?? 1, application.argv) let exitCode: Int32 = mpv_main(application.argc ?? 1, application.argv)
application.terminateApplication() // exit of any proper shut down
// normally never reached - unless the cocoa mainloop hasn't started yet exit(exitCode)
exit(r)
} }
@objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> Int { @objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> Int {
self.argc = argc self.argc = argc
self.argv = argv self.argv = argv
if bundleStartedFromFinder() { NSApp = self
NSApp.delegate = self
NSApp.setActivationPolicy(isBundle ? .regular : .accessory)
setupBundle() setupBundle()
initApplication(true)
} else {
initApplication(false)
}
pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self)) pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self))
appHub.input.wait() appHub.input.wait()
NSApp.run() NSApp.run()
// This should never be reached: NSApp.run() blocks until the process is quit // should never be reached
print(""" print("""
There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \ There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \
Please report this issues to a developer.\n Please report this issues to a developer.\n

View File

@ -1,9 +1,6 @@
#include "osdep/mac/app_bridge.h" #include "osdep/mac/app_bridge.h"
// This is needed because Cocoa absolutely requires creating the NSApplication // Cocoa absolutely requires creating the NSApplication singleton and running it on the main thread.
// singleton and running it in the "main" thread. It is apparently not
// possible to do this on a separate thread at all. It is not known how
// Apple managed this colossal fuckup.
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
return cocoa_main(argc, argv); return cocoa_main(argc, argv);