From 3c5149795480a15bf00c1a14045294f389e37af5 Mon Sep 17 00:00:00 2001 From: der richter Date: Mon, 1 Apr 2024 01:00:19 +0200 Subject: [PATCH] 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 --- osdep/mac/application.swift | 73 +++++++++++++------------------------ osdep/main-fn-mac.c | 5 +-- 2 files changed, 26 insertions(+), 52 deletions(-) diff --git a/osdep/mac/application.swift b/osdep/mac/application.swift index 086b6eff66..c66be61647 100644 --- a/osdep/mac/application.swift +++ b/osdep/mac/application.swift @@ -19,6 +19,8 @@ import Cocoa class Application: NSApplication, NSApplicationDelegate { 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 argc: Int32? var argv: UnsafeMutablePointer?>? @@ -31,34 +33,6 @@ class Application: NSApplication, NSApplicationDelegate { 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) { if modalWindow != nil || !appHub.input.processKey(event: event) { super.sendEvent(event) @@ -77,29 +51,36 @@ class Application: NSApplication, NSApplicationDelegate { } func applicationWillFinishLaunching(_ notification: Notification) { - let eventManager = NSAppleEventManager.shared() + // register quit and exit events eventManager.setEventHandler( self, andSelector: #selector(handleQuit(event:replyEvent:)), forEventClass: AEEventClass(kCoreEventClass), 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?) { + // send quit to core, terminates mpv_main called in playbackThread, 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() { + if !isBundle { return } + // 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_") { argc? = 1 argv?[1] = nil @@ -113,28 +94,24 @@ class Application: NSApplication, NSApplicationDelegate { let playbackThread: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in let application: Application = TypeHelper.bridge(ptr: ptr) mp_thread_set_name("core/playback") - let r: Int32 = mpv_main(application.argc ?? 1, application.argv) - application.terminateApplication() - // normally never reached - unless the cocoa mainloop hasn't started yet - exit(r) + let exitCode: Int32 = mpv_main(application.argc ?? 1, application.argv) + // exit of any proper shut down + exit(exitCode) } @objc func main(_ argc: Int32, _ argv: UnsafeMutablePointer?>) -> Int { self.argc = argc self.argv = argv - if bundleStartedFromFinder() { - setupBundle() - initApplication(true) - } else { - initApplication(false) - } - + NSApp = self + NSApp.delegate = self + NSApp.setActivationPolicy(isBundle ? .regular : .accessory) + setupBundle() pthread_create(&playbackThreadId, nil, playbackThread, TypeHelper.bridge(obj: self)) appHub.input.wait() NSApp.run() - // This should never be reached: NSApp.run() blocks until the process is quit + // should never be reached print(""" There was either a problem initializing Cocoa or the Runloop was stopped unexpectedly. \ Please report this issues to a developer.\n diff --git a/osdep/main-fn-mac.c b/osdep/main-fn-mac.c index ee2d6e3464..140610093f 100644 --- a/osdep/main-fn-mac.c +++ b/osdep/main-fn-mac.c @@ -1,9 +1,6 @@ #include "osdep/mac/app_bridge.h" -// This is needed because Cocoa absolutely requires creating the NSApplication -// 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. +// Cocoa absolutely requires creating the NSApplication singleton and running it on the main thread. int main(int argc, char *argv[]) { return cocoa_main(argc, argv);