/* * 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 . */ // 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 // Media keys definitions #import #import #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/macosx_events_objc.h" #import "osdep/macosx_application_objc.h" #include "config.h" #if HAVE_SWIFT #include "osdep/macOS_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] & NSEventModifierFlagShift ? DND_APPEND : DND_REPLACE; size_t num_files = [files count]; char **files_utf8 = talloc_array(NULL, char*, num_files); [files enumerateObjectsUsingBlock:^(NSString *p, NSUInteger i, BOOL *_){ if ([p hasPrefix:@"file:///.file/id="]) p = [[NSURL URLWithString:p] path]; char *filename = (char *)[p UTF8String]; size_t bytes = [p lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; files_utf8[i] = talloc_memdup(files_utf8, filename, bytes + 1); }]; [_input_lock lock]; if (_inputContext) mp_event_drop_files(_inputContext, num_files, files_utf8, action); [_input_lock unlock]; talloc_free(files_utf8); } @end