summaryrefslogtreecommitdiffstats
path: root/osdep/mac
diff options
context:
space:
mode:
authorder richter <der.richter@gmx.de>2024-02-27 16:55:29 +0100
committerder richter <der.richter@gmx.de>2024-02-28 15:52:47 +0100
commit86fa9b18a3619a379a597ca0902c23dc053cafc0 (patch)
treef187f85a97744bd0468290975e5ec4d10054b25b /osdep/mac
parent661f45377a17635125ae36f0b8a3487ae13cf606 (diff)
downloadmpv-86fa9b18a3619a379a597ca0902c23dc053cafc0.tar.bz2
mpv-86fa9b18a3619a379a597ca0902c23dc053cafc0.tar.xz
osdep/mac: make mac naming of files, folders and function consistent
rename all macOS namings (osx, macosx, macOS, macos, apple) to mac, to make naming consistent.
Diffstat (limited to 'osdep/mac')
-rw-r--r--osdep/mac/application.h54
-rw-r--r--osdep/mac/application.m377
-rw-r--r--osdep/mac/application_objc.h40
-rw-r--r--osdep/mac/events.h37
-rw-r--r--osdep/mac/events.m427
-rw-r--r--osdep/mac/events_objc.h45
-rw-r--r--osdep/mac/libmpv_helper.swift254
-rw-r--r--osdep/mac/log_helper.swift47
-rw-r--r--osdep/mac/menubar.h30
-rw-r--r--osdep/mac/menubar.m853
-rw-r--r--osdep/mac/menubar_objc.h25
-rw-r--r--osdep/mac/meson.build51
-rw-r--r--osdep/mac/mpv_helper.swift182
-rw-r--r--osdep/mac/precise_timer.swift153
-rw-r--r--osdep/mac/remote_command_center.swift214
-rw-r--r--osdep/mac/swift_bridge.h58
-rw-r--r--osdep/mac/swift_compat.swift36
-rw-r--r--osdep/mac/swift_extensions.swift54
-rw-r--r--osdep/mac/touchbar.h46
-rw-r--r--osdep/mac/touchbar.m336
20 files changed, 3319 insertions, 0 deletions
diff --git a/osdep/mac/application.h b/osdep/mac/application.h
new file mode 100644
index 0000000000..9d86f9e312
--- /dev/null
+++ b/osdep/mac/application.h
@@ -0,0 +1,54 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MAC_APPLICATION
+#define MAC_APPLICATION
+
+#include "osdep/mac/menubar.h"
+#include "options/m_option.h"
+
+enum {
+ FRAME_VISIBLE = 0,
+ FRAME_WHOLE,
+};
+
+enum {
+ RENDER_TIMER_CALLBACK = 0,
+ RENDER_TIMER_PRECISE,
+ RENDER_TIMER_SYSTEM,
+};
+
+struct macos_opts {
+ int macos_title_bar_appearance;
+ int macos_title_bar_material;
+ struct m_color macos_title_bar_color;
+ int macos_fs_animation_duration;
+ bool macos_force_dedicated_gpu;
+ int macos_app_activation_policy;
+ int macos_geometry_calculation;
+ int macos_render_timer;
+ int cocoa_cb_sw_renderer;
+ bool cocoa_cb_10bit_context;
+};
+
+// multithreaded wrapper for mpv_main
+int cocoa_main(int argc, char *argv[]);
+void cocoa_register_menu_item_action(MPMenuKey key, void* action);
+
+extern const struct m_sub_options macos_conf;
+
+#endif /* MAC_APPLICATION */
diff --git a/osdep/mac/application.m b/osdep/mac/application.m
new file mode 100644
index 0000000000..5938180a85
--- /dev/null
+++ b/osdep/mac/application.m
@@ -0,0 +1,377 @@
+/*
+ * 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/mac/application_objc.h"
+#import "osdep/mac/events_objc.h"
+#include "osdep/threads.h"
+#include "osdep/main-fn.h"
+
+#if HAVE_MACOS_TOUCHBAR
+#import "osdep/mac/touchbar.h"
+#endif
+#if HAVE_SWIFT
+#include "osdep/mac/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 mac_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 *)mac_icon
+ length:sizeof(mac_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)initCocoaCb:(struct mpv_handle *)ctx
+{
+#if HAVE_MACOS_COCOA_CB
+ if (!_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;
+ }
+}
diff --git a/osdep/mac/application_objc.h b/osdep/mac/application_objc.h
new file mode 100644
index 0000000000..ad4f13ec5d
--- /dev/null
+++ b/osdep/mac/application_objc.h
@@ -0,0 +1,40 @@
+/*
+ * 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/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "osdep/mac/application.h"
+#import "osdep/mac/menubar_objc.h"
+
+@class CocoaCB;
+struct mpv_event;
+struct mpv_handle;
+
+@interface Application : NSApplication
+
+- (NSImage *)getMPVIcon;
+- (void)processEvent:(struct mpv_event *)event;
+- (void)queueCommand:(char *)cmd;
+- (void)stopMPV:(char *)cmd;
+- (void)openFiles:(NSArray *)filenames;
+- (void)initCocoaCb:(struct mpv_handle *)ctx;
++ (const struct m_sub_options *)getMacOSConf;
++ (const struct m_sub_options *)getVoSubConf;
+
+@property(nonatomic, retain) MenuBar *menuBar;
+@property(nonatomic, assign) size_t openCount;
+@property(nonatomic, retain) CocoaCB *cocoaCB;
+@end
diff --git a/osdep/mac/events.h b/osdep/mac/events.h
new file mode 100644
index 0000000000..1767b28778
--- /dev/null
+++ b/osdep/mac/events.h
@@ -0,0 +1,37 @@
+/*
+ * Cocoa Application Event Handling
+ *
+ * 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/>.
+ */
+
+#ifndef MAC_EVENTS
+#define MAC_EVENTS
+#include "input/keycodes.h"
+
+struct input_ctx;
+struct mpv_handle;
+
+void cocoa_put_key(int keycode);
+void cocoa_put_key_with_modifiers(int keycode, int modifiers);
+
+void cocoa_init_media_keys(void);
+void cocoa_uninit_media_keys(void);
+
+void cocoa_set_input_context(struct input_ctx *input_context);
+void cocoa_set_mpv_handle(struct mpv_handle *ctx);
+void cocoa_init_cocoa_cb(void);
+
+#endif
diff --git a/osdep/mac/events.m b/osdep/mac/events.m
new file mode 100644
index 0000000000..50d9242ca6
--- /dev/null
+++ b/osdep/mac/events.m
@@ -0,0 +1,427 @@
+/*
+ * Cocoa Application Event Handling
+ *
+ * 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/>.
+ */
+
+// Carbon header is included but Carbon is NOT linked to mpv's binary. This
+// file only needs this include to use the keycode definitions in keymap.
+#import <Carbon/Carbon.h>
+
+// Media keys definitions
+#import <IOKit/hidsystem/ev_keymap.h>
+#import <Cocoa/Cocoa.h>
+
+#include "mpv_talloc.h"
+#include "input/event.h"
+#include "input/input.h"
+#include "player/client.h"
+#include "input/keycodes.h"
+// doesn't make much sense, but needed to access keymap functionality
+#include "video/out/vo.h"
+
+#import "osdep/mac/events_objc.h"
+#import "osdep/mac/application_objc.h"
+
+#include "config.h"
+
+#if HAVE_SWIFT
+#include "osdep/mac/swift.h"
+#endif
+
+@interface EventsResponder ()
+{
+ struct input_ctx *_inputContext;
+ struct mpv_handle *_ctx;
+ BOOL _is_application;
+ NSCondition *_input_lock;
+}
+
+- (NSEvent *)handleKey:(NSEvent *)event;
+- (BOOL)setMpvHandle:(struct mpv_handle *)ctx;
+- (void)initCocoaCb;
+- (void)readEvents;
+- (void)startMediaKeys;
+- (void)stopMediaKeys;
+- (int)mapKeyModifiers:(int)cocoaModifiers;
+- (int)keyModifierMask:(NSEvent *)event;
+@end
+
+
+#define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption)
+#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption)
+
+static bool LeftAltPressed(int mask)
+{
+ return (mask & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask;
+}
+
+static bool RightAltPressed(int mask)
+{
+ return (mask & NSRightAlternateKeyMask) == NSRightAlternateKeyMask;
+}
+
+static const struct mp_keymap keymap[] = {
+ // special keys
+ {kVK_Return, MP_KEY_ENTER}, {kVK_Escape, MP_KEY_ESC},
+ {kVK_Delete, MP_KEY_BACKSPACE}, {kVK_Option, MP_KEY_BACKSPACE},
+ {kVK_Control, MP_KEY_BACKSPACE}, {kVK_Shift, MP_KEY_BACKSPACE},
+ {kVK_Tab, MP_KEY_TAB},
+
+ // cursor keys
+ {kVK_UpArrow, MP_KEY_UP}, {kVK_DownArrow, MP_KEY_DOWN},
+ {kVK_LeftArrow, MP_KEY_LEFT}, {kVK_RightArrow, MP_KEY_RIGHT},
+
+ // navigation block
+ {kVK_Help, MP_KEY_INSERT}, {kVK_ForwardDelete, MP_KEY_DELETE},
+ {kVK_Home, MP_KEY_HOME}, {kVK_End, MP_KEY_END},
+ {kVK_PageUp, MP_KEY_PAGE_UP}, {kVK_PageDown, MP_KEY_PAGE_DOWN},
+
+ // F-keys
+ {kVK_F1, MP_KEY_F + 1}, {kVK_F2, MP_KEY_F + 2}, {kVK_F3, MP_KEY_F + 3},
+ {kVK_F4, MP_KEY_F + 4}, {kVK_F5, MP_KEY_F + 5}, {kVK_F6, MP_KEY_F + 6},
+ {kVK_F7, MP_KEY_F + 7}, {kVK_F8, MP_KEY_F + 8}, {kVK_F9, MP_KEY_F + 9},
+ {kVK_F10, MP_KEY_F + 10}, {kVK_F11, MP_KEY_F + 11}, {kVK_F12, MP_KEY_F + 12},
+ {kVK_F13, MP_KEY_F + 13}, {kVK_F14, MP_KEY_F + 14}, {kVK_F15, MP_KEY_F + 15},
+ {kVK_F16, MP_KEY_F + 16}, {kVK_F17, MP_KEY_F + 17}, {kVK_F18, MP_KEY_F + 18},
+ {kVK_F19, MP_KEY_F + 19}, {kVK_F20, MP_KEY_F + 20},
+
+ // numpad
+ {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'},
+ {kVK_ANSI_KeypadMultiply, '*'}, {kVK_ANSI_KeypadDivide, '/'},
+ {kVK_ANSI_KeypadEnter, MP_KEY_KPENTER},
+ {kVK_ANSI_KeypadDecimal, MP_KEY_KPDEC},
+ {kVK_ANSI_Keypad0, MP_KEY_KP0}, {kVK_ANSI_Keypad1, MP_KEY_KP1},
+ {kVK_ANSI_Keypad2, MP_KEY_KP2}, {kVK_ANSI_Keypad3, MP_KEY_KP3},
+ {kVK_ANSI_Keypad4, MP_KEY_KP4}, {kVK_ANSI_Keypad5, MP_KEY_KP5},
+ {kVK_ANSI_Keypad6, MP_KEY_KP6}, {kVK_ANSI_Keypad7, MP_KEY_KP7},
+ {kVK_ANSI_Keypad8, MP_KEY_KP8}, {kVK_ANSI_Keypad9, MP_KEY_KP9},
+
+ {0, 0}
+};
+
+static int convert_key(unsigned key, unsigned charcode)
+{
+ int mpkey = lookup_keymap_table(keymap, key);
+ if (mpkey)
+ return mpkey;
+ return charcode;
+}
+
+void cocoa_init_media_keys(void)
+{
+ [[EventsResponder sharedInstance] startMediaKeys];
+}
+
+void cocoa_uninit_media_keys(void)
+{
+ [[EventsResponder sharedInstance] stopMediaKeys];
+}
+
+void cocoa_put_key(int keycode)
+{
+ [[EventsResponder sharedInstance] putKey:keycode];
+}
+
+void cocoa_put_key_with_modifiers(int keycode, int modifiers)
+{
+ keycode |= [[EventsResponder sharedInstance] mapKeyModifiers:modifiers];
+ cocoa_put_key(keycode);
+}
+
+void cocoa_set_input_context(struct input_ctx *input_context)
+{
+ [[EventsResponder sharedInstance] setInputContext:input_context];
+}
+
+static void wakeup(void *context)
+{
+ [[EventsResponder sharedInstance] readEvents];
+}
+
+void cocoa_set_mpv_handle(struct mpv_handle *ctx)
+{
+ if ([[EventsResponder sharedInstance] setMpvHandle:ctx]) {
+ mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(ctx, 0, "speed", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG);
+ mpv_observe_property(ctx, 0, "media-title", MPV_FORMAT_STRING);
+ mpv_observe_property(ctx, 0, "chapter-metadata/title", MPV_FORMAT_STRING);
+ mpv_observe_property(ctx, 0, "metadata/by-key/album", MPV_FORMAT_STRING);
+ mpv_observe_property(ctx, 0, "metadata/by-key/artist", MPV_FORMAT_STRING);
+ mpv_set_wakeup_callback(ctx, wakeup, NULL);
+ }
+}
+
+void cocoa_init_cocoa_cb(void)
+{
+ [[EventsResponder sharedInstance] initCocoaCb];
+}
+
+@implementation EventsResponder
+
+@synthesize remoteCommandCenter = _remoteCommandCenter;
+
++ (EventsResponder *)sharedInstance
+{
+ static EventsResponder *responder = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ responder = [EventsResponder new];
+ });
+ return responder;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ _input_lock = [NSCondition new];
+ }
+ return self;
+}
+
+- (void)waitForInputContext
+{
+ [_input_lock lock];
+ while (!_inputContext)
+ [_input_lock wait];
+ [_input_lock unlock];
+}
+
+- (void)setInputContext:(struct input_ctx *)ctx
+{
+ [_input_lock lock];
+ _inputContext = ctx;
+ [_input_lock signal];
+ [_input_lock unlock];
+}
+
+- (void)wakeup
+{
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_wakeup(_inputContext);
+ [_input_lock unlock];
+}
+
+- (bool)queueCommand:(char *)cmd
+{
+ bool r = false;
+ [_input_lock lock];
+ if (_inputContext) {
+ mp_cmd_t *cmdt = mp_input_parse_cmd(_inputContext, bstr0(cmd), "");
+ mp_input_queue_cmd(_inputContext, cmdt);
+ r = true;
+ }
+ [_input_lock unlock];
+ return r;
+}
+
+- (void)putKey:(int)keycode
+{
+ [_input_lock lock];
+ if (_inputContext)
+ mp_input_put_key(_inputContext, keycode);
+ [_input_lock unlock];
+}
+
+- (BOOL)useAltGr
+{
+ BOOL r = YES;
+ [_input_lock lock];
+ if (_inputContext)
+ r = mp_input_use_alt_gr(_inputContext);
+ [_input_lock unlock];
+ return r;
+}
+
+- (void)setIsApplication:(BOOL)isApplication
+{
+ _is_application = isApplication;
+}
+
+- (BOOL)setMpvHandle:(struct mpv_handle *)ctx
+{
+ if (_is_application) {
+ _ctx = ctx;
+ return YES;
+ }
+
+ mpv_destroy(ctx);
+ return NO;
+}
+
+- (void)initCocoaCb
+{
+ if (_is_application) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApp initCocoaCb:_ctx];
+ });
+ }
+}
+
+- (void)readEvents
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ while (_ctx) {
+ mpv_event *event = mpv_wait_event(_ctx, 0);
+ if (event->event_id == MPV_EVENT_NONE)
+ break;
+ [self processEvent:event];
+ }
+ });
+}
+
+-(void)processEvent:(struct mpv_event *)event
+{
+ if(_is_application) {
+ [NSApp processEvent:event];
+ }
+
+ if (_remoteCommandCenter) {
+ [_remoteCommandCenter processEvent:event];
+ }
+
+ switch (event->event_id) {
+ case MPV_EVENT_SHUTDOWN: {
+#if HAVE_MACOS_COCOA_CB
+ if ([(Application *)NSApp cocoaCB].isShuttingDown) {
+ _ctx = nil;
+ return;
+ }
+#endif
+ mpv_destroy(_ctx);
+ _ctx = nil;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+- (void)startMediaKeys
+{
+#if HAVE_MACOS_MEDIA_PLAYER
+ if (_remoteCommandCenter == nil) {
+ _remoteCommandCenter = [[RemoteCommandCenter alloc] init];
+ }
+#endif
+
+ [_remoteCommandCenter start];
+}
+
+- (void)stopMediaKeys
+{
+ [_remoteCommandCenter stop];
+}
+
+- (int)mapKeyModifiers:(int)cocoaModifiers
+{
+ int mask = 0;
+ if (cocoaModifiers & NSEventModifierFlagShift)
+ mask |= MP_KEY_MODIFIER_SHIFT;
+ if (cocoaModifiers & NSEventModifierFlagControl)
+ mask |= MP_KEY_MODIFIER_CTRL;
+ if (LeftAltPressed(cocoaModifiers) ||
+ (RightAltPressed(cocoaModifiers) && ![self useAltGr]))
+ mask |= MP_KEY_MODIFIER_ALT;
+ if (cocoaModifiers & NSEventModifierFlagCommand)
+ mask |= MP_KEY_MODIFIER_META;
+ return mask;
+}
+
+- (int)mapTypeModifiers:(NSEventType)type
+{
+ NSDictionary *map = @{
+ @(NSEventTypeKeyDown) : @(MP_KEY_STATE_DOWN),
+ @(NSEventTypeKeyUp) : @(MP_KEY_STATE_UP),
+ };
+ return [map[@(type)] intValue];
+}
+
+- (int)keyModifierMask:(NSEvent *)event
+{
+ return [self mapKeyModifiers:[event modifierFlags]] |
+ [self mapTypeModifiers:[event type]];
+}
+
+-(BOOL)handleMPKey:(int)key withMask:(int)mask
+{
+ if (key > 0) {
+ cocoa_put_key(key | mask);
+ if (mask & MP_KEY_STATE_UP)
+ cocoa_put_key(MP_INPUT_RELEASE_ALL);
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+- (NSEvent*)handleKey:(NSEvent *)event
+{
+ if ([event isARepeat]) return nil;
+
+ NSString *chars;
+
+ if ([self useAltGr] && RightAltPressed([event modifierFlags])) {
+ chars = [event characters];
+ } else {
+ chars = [event charactersIgnoringModifiers];
+ }
+
+ struct bstr t = bstr0([chars UTF8String]);
+ int key = convert_key([event keyCode], bstr_decode_utf8(t, &t));
+
+ if (key > -1)
+ [self handleMPKey:key withMask:[self keyModifierMask:event]];
+
+ return nil;
+}
+
+- (bool)processKeyEvent:(NSEvent *)event
+{
+ if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp){
+ if (![[NSApp mainMenu] performKeyEquivalent:event])
+ [self handleKey:event];
+ return true;
+ }
+ return false;
+}
+
+- (void)handleFilesArray:(NSArray *)files
+{
+ enum mp_dnd_action action = [NSEvent modifierFlags] &
+ NSEventModifi