diff options
author | der richter <der.richter@gmx.de> | 2024-02-27 16:55:29 +0100 |
---|---|---|
committer | der richter <der.richter@gmx.de> | 2024-02-28 15:52:47 +0100 |
commit | 86fa9b18a3619a379a597ca0902c23dc053cafc0 (patch) | |
tree | f187f85a97744bd0468290975e5ec4d10054b25b /osdep/mac | |
parent | 661f45377a17635125ae36f0b8a3487ae13cf606 (diff) | |
download | mpv-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.h | 54 | ||||
-rw-r--r-- | osdep/mac/application.m | 377 | ||||
-rw-r--r-- | osdep/mac/application_objc.h | 40 | ||||
-rw-r--r-- | osdep/mac/events.h | 37 | ||||
-rw-r--r-- | osdep/mac/events.m | 427 | ||||
-rw-r--r-- | osdep/mac/events_objc.h | 45 | ||||
-rw-r--r-- | osdep/mac/libmpv_helper.swift | 254 | ||||
-rw-r--r-- | osdep/mac/log_helper.swift | 47 | ||||
-rw-r--r-- | osdep/mac/menubar.h | 30 | ||||
-rw-r--r-- | osdep/mac/menubar.m | 853 | ||||
-rw-r--r-- | osdep/mac/menubar_objc.h | 25 | ||||
-rw-r--r-- | osdep/mac/meson.build | 51 | ||||
-rw-r--r-- | osdep/mac/mpv_helper.swift | 182 | ||||
-rw-r--r-- | osdep/mac/precise_timer.swift | 153 | ||||
-rw-r--r-- | osdep/mac/remote_command_center.swift | 214 | ||||
-rw-r--r-- | osdep/mac/swift_bridge.h | 58 | ||||
-rw-r--r-- | osdep/mac/swift_compat.swift | 36 | ||||
-rw-r--r-- | osdep/mac/swift_extensions.swift | 54 | ||||
-rw-r--r-- | osdep/mac/touchbar.h | 46 | ||||
-rw-r--r-- | osdep/mac/touchbar.m | 336 |
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 |