summaryrefslogtreecommitdiffstats
path: root/osdep/macosx_application.m
diff options
context:
space:
mode:
Diffstat (limited to 'osdep/macosx_application.m')
-rw-r--r--osdep/macosx_application.m375
1 files changed, 375 insertions, 0 deletions
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
new file mode 100644
index 0000000..73503ad
--- /dev/null
+++ b/osdep/macosx_application.m
@@ -0,0 +1,375 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include "config.h"
+#include "mpv_talloc.h"
+
+#include "common/msg.h"
+#include "input/input.h"
+#include "player/client.h"
+#include "options/m_config.h"
+#include "options/options.h"
+
+#import "osdep/macosx_application_objc.h"
+#import "osdep/macosx_events_objc.h"
+#include "osdep/threads.h"
+#include "osdep/main-fn.h"
+
+#if HAVE_MACOS_TOUCHBAR
+#import "osdep/macosx_touchbar.h"
+#endif
+#if HAVE_MACOS_COCOA_CB
+#include "osdep/macOS_swift.h"
+#endif
+
+#define MPV_PROTOCOL @"mpv://"
+
+#define OPT_BASE_STRUCT struct macos_opts
+const struct m_sub_options macos_conf = {
+ .opts = (const struct m_option[]) {
+ {"macos-title-bar-appearance", OPT_CHOICE(macos_title_bar_appearance,
+ {"auto", 0}, {"aqua", 1}, {"darkAqua", 2},
+ {"vibrantLight", 3}, {"vibrantDark", 4},
+ {"aquaHighContrast", 5}, {"darkAquaHighContrast", 6},
+ {"vibrantLightHighContrast", 7},
+ {"vibrantDarkHighContrast", 8})},
+ {"macos-title-bar-material", OPT_CHOICE(macos_title_bar_material,
+ {"titlebar", 0}, {"selection", 1}, {"menu", 2},
+ {"popover", 3}, {"sidebar", 4}, {"headerView", 5},
+ {"sheet", 6}, {"windowBackground", 7}, {"hudWindow", 8},
+ {"fullScreen", 9}, {"toolTip", 10}, {"contentBackground", 11},
+ {"underWindowBackground", 12}, {"underPageBackground", 13},
+ {"dark", 14}, {"light", 15}, {"mediumLight", 16},
+ {"ultraDark", 17})},
+ {"macos-title-bar-color", OPT_COLOR(macos_title_bar_color)},
+ {"macos-fs-animation-duration",
+ OPT_CHOICE(macos_fs_animation_duration, {"default", -1}),
+ M_RANGE(0, 1000)},
+ {"macos-force-dedicated-gpu", OPT_BOOL(macos_force_dedicated_gpu)},
+ {"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy,
+ {"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
+ {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
+ {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
+ {"macos-render-timer", OPT_CHOICE(macos_render_timer,
+ {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
+ {"system", RENDER_TIMER_SYSTEM})},
+ {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
+ {"auto", -1}, {"no", 0}, {"yes", 1})},
+ {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
+ {0}
+ },
+ .size = sizeof(struct macos_opts),
+ .defaults = &(const struct macos_opts){
+ .macos_title_bar_color = {0, 0, 0, 0},
+ .macos_fs_animation_duration = -1,
+ .cocoa_cb_sw_renderer = -1,
+ .cocoa_cb_10bit_context = true
+ },
+};
+
+// Whether the NSApplication singleton was created. If this is false, we are
+// running in libmpv mode, and cocoa_main() was never called.
+static bool application_instantiated;
+
+static mp_thread playback_thread_id;
+
+@interface Application ()
+{
+ EventsResponder *_eventsResponder;
+}
+
+@end
+
+static Application *mpv_shared_app(void)
+{
+ return (Application *)[Application sharedApplication];
+}
+
+static void terminate_cocoa_application(void)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp hide:NSApp];
+ [NSApp terminate:NSApp];
+ });
+}
+
+@implementation Application
+@synthesize menuBar = _menu_bar;
+@synthesize openCount = _open_count;
+@synthesize cocoaCB = _cocoa_cb;
+
+- (void)sendEvent:(NSEvent *)event
+{
+ if ([self modalWindow] || ![_eventsResponder processKeyEvent:event])
+ [super sendEvent:event];
+ [_eventsResponder wakeup];
+}
+
+- (id)init
+{
+ if (self = [super init]) {
+ _eventsResponder = [EventsResponder sharedInstance];
+
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em setEventHandler:self
+ andSelector:@selector(getUrl:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em removeEventHandlerForEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+ [em removeEventHandlerForEventClass:kCoreEventClass
+ andEventID:kAEQuitApplication];
+ [super dealloc];
+}
+
+static const char macosx_icon[] =
+#include "TOOLS/osxbundle/icon.icns.inc"
+;
+
+- (NSImage *)getMPVIcon
+{
+ // The C string contains a trailing null, so we strip it away
+ NSData *icon_data = [NSData dataWithBytesNoCopy:(void *)macosx_icon
+ length:sizeof(macosx_icon) - 1
+ freeWhenDone:NO];
+ return [[NSImage alloc] initWithData:icon_data];
+}
+
+#if HAVE_MACOS_TOUCHBAR
+- (NSTouchBar *)makeTouchBar
+{
+ TouchBar *tBar = [[TouchBar alloc] init];
+ [tBar setApp:self];
+ tBar.delegate = tBar;
+ tBar.customizationIdentifier = customID;
+ tBar.defaultItemIdentifiers = @[play, previousItem, nextItem, seekBar];
+ tBar.customizationAllowedItemIdentifiers = @[play, seekBar, previousItem,
+ nextItem, previousChapter, nextChapter, cycleAudio, cycleSubtitle,
+ currentPosition, timeLeft];
+ return tBar;
+}
+#endif
+
+- (void)processEvent:(struct mpv_event *)event
+{
+#if HAVE_MACOS_TOUCHBAR
+ [(TouchBar *)self.touchBar processEvent:event];
+#endif
+ if (_cocoa_cb) {
+ [_cocoa_cb processEvent:event];
+ }
+}
+
+- (void)setMpvHandle:(struct mpv_handle *)ctx
+{
+#if HAVE_MACOS_COCOA_CB
+ [NSApp setCocoaCB:[[CocoaCB alloc] init:ctx]];
+#endif
+}
+
+- (const struct m_sub_options *)getMacOSConf
+{
+ return &macos_conf;
+}
+
+- (const struct m_sub_options *)getVoSubConf
+{
+ return &vo_sub_opts;
+}
+
+- (void)queueCommand:(char *)cmd
+{
+ [_eventsResponder queueCommand:cmd];
+}
+
+- (void)stopMPV:(char *)cmd
+{
+ if (![_eventsResponder queueCommand:cmd])
+ terminate_cocoa_application();
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification
+{
+ NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager];
+ [em setEventHandler:self
+ andSelector:@selector(handleQuitEvent:withReplyEvent:)
+ forEventClass:kCoreEventClass
+ andEventID:kAEQuitApplication];
+}
+
+- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
+ withReplyEvent:(NSAppleEventDescriptor *)replyEvent
+{
+ [self stopMPV:"quit"];
+}
+
+- (void)getUrl:(NSAppleEventDescriptor *)event
+ withReplyEvent:(NSAppleEventDescriptor *)replyEvent
+{
+ NSString *url =
+ [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
+
+ url = [url stringByReplacingOccurrencesOfString:MPV_PROTOCOL
+ withString:@""
+ options:NSAnchoredSearch
+ range:NSMakeRange(0, [MPV_PROTOCOL length])];
+
+ url = [url stringByRemovingPercentEncoding];
+ [_eventsResponder handleFilesArray:@[url]];
+}
+
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+{
+ if (mpv_shared_app().openCount > 0) {
+ mpv_shared_app().openCount--;
+ return;
+ }
+ [self openFiles:filenames];
+}
+
+- (void)openFiles:(NSArray *)filenames
+{
+ SEL cmpsel = @selector(localizedStandardCompare:);
+ NSArray *files = [filenames sortedArrayUsingSelector:cmpsel];
+ [_eventsResponder handleFilesArray:files];
+}
+@end
+
+struct playback_thread_ctx {
+ int *argc;
+ char ***argv;
+};
+
+static void cocoa_run_runloop(void)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [NSApp run];
+ [pool drain];
+}
+
+static MP_THREAD_VOID playback_thread(void *ctx_obj)
+{
+ mp_thread_set_name("core/playback");
+ @autoreleasepool {
+ struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj;
+ int r = mpv_main(*ctx->argc, *ctx->argv);
+ terminate_cocoa_application();
+ // normally never reached - unless the cocoa mainloop hasn't started yet
+ exit(r);
+ }
+}
+
+void cocoa_register_menu_item_action(MPMenuKey key, void* action)
+{
+ if (application_instantiated)
+ [[NSApp menuBar] registerSelector:(SEL)action forKey:key];
+}
+
+static void init_cocoa_application(bool regular)
+{
+ NSApp = mpv_shared_app();
+ [NSApp setDelegate:NSApp];
+ [NSApp setMenuBar:[[MenuBar alloc] init]];
+
+ // 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 ?
+ NSApplicationActivationPolicyRegular :
+ NSApplicationActivationPolicyAccessory];
+
+ 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.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
+ });
+ });
+}
+
+static bool bundle_started_from_finder()
+{
+ NSString* bundle = [[[NSProcessInfo processInfo] environment] objectForKey:@"MPVBUNDLE"];
+ return [bundle isEqual:@"true"];
+}
+
+static bool is_psn_argument(char *arg_to_check)
+{
+ NSString *arg = [NSString stringWithUTF8String:arg_to_check];
+ return [arg hasPrefix:@"-psn_"];
+}
+
+static void setup_bundle(int *argc, char *argv[])
+{
+ if (*argc > 1 && is_psn_argument(argv[1])) {
+ *argc = 1;
+ argv[1] = NULL;
+ }
+
+ NSDictionary *env = [[NSProcessInfo processInfo] environment];
+ NSString *path_bundle = [env objectForKey:@"PATH"];
+ NSString *path_new = [NSString stringWithFormat:@"%@:%@:%@:%@:%@",
+ path_bundle,
+ @"/usr/local/bin",
+ @"/usr/local/sbin",
+ @"/opt/local/bin",
+ @"/opt/local/sbin"];
+ setenv("PATH", [path_new UTF8String], 1);
+}
+
+int cocoa_main(int argc, char *argv[])
+{
+ @autoreleasepool {
+ application_instantiated = true;
+ [[EventsResponder sharedInstance] setIsApplication:YES];
+
+ struct playback_thread_ctx ctx = {0};
+ ctx.argc = &argc;
+ ctx.argv = &argv;
+
+ if (bundle_started_from_finder()) {
+ setup_bundle(&argc, argv);
+ init_cocoa_application(true);
+ } else {
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] != '-')
+ mpv_shared_app().openCount++;
+ init_cocoa_application(false);
+ }
+
+ mp_thread_create(&playback_thread_id, playback_thread, &ctx);
+ [[EventsResponder sharedInstance] waitForInputContext];
+ cocoa_run_runloop();
+
+ // This should never be reached: cocoa_run_runloop blocks until the
+ // process is quit
+ fprintf(stderr, "There was either a problem "
+ "initializing Cocoa or the Runloop was stopped unexpectedly. "
+ "Please report this issues to a developer.\n");
+ mp_thread_join(playback_thread_id);
+ return 1;
+ }
+}