mac: replace old event tap for media key support with MediaPlayer

the old event tap has several problems, like no proper priority support
or having to set accessibility permissions for mpv or the terminal.

it is now replaced by the new MediaPlayer which has proper priority
support and isn't as greedy as previously. this only includes Media Key
support and not any of the other features included in the MediaPlayer
framework, like proper Now Playing data (only set dummy data for now).
this is only available on macOS 10.12.2 and higher.

also removes some unnecessary redefines.

Fixes #6389
This commit is contained in:
der richter 2019-11-30 16:08:32 +01:00
parent 8a6ee7fe94
commit a32db637b5
10 changed files with 197 additions and 133 deletions

View File

@ -44,11 +44,8 @@ static int SWIFT_MBTN_BACK = MP_MBTN_BACK;
static int SWIFT_MBTN_FORWARD = MP_MBTN_FORWARD;
static int SWIFT_MBTN9 = MP_MBTN9;
static int SWIFT_KEY_CLOSE_WIN = MP_KEY_CLOSE_WIN;
static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE;
static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER;
static int SWIFT_KEY_STATE_DOWN = MP_KEY_STATE_DOWN;
static int SWIFT_KEY_STATE_UP = MP_KEY_STATE_UP;
// only used from Swift files and therefore seen as unused by the c compiler
static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) __attribute__ ((unused));

View File

@ -0,0 +1,159 @@
/*
* 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 MediaPlayer
@available(macOS 10.12.2, *)
class RemoteCommandCenter: NSObject {
enum KeyType {
case normal
case repeatable
}
var config: [MPRemoteCommand:[String:Any]] = [
MPRemoteCommandCenter.shared().pauseCommand: [
"mpKey": MP_KEY_PAUSE,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().playCommand: [
"mpKey": MP_KEY_PLAY,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().stopCommand: [
"mpKey": MP_KEY_STOP,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().nextTrackCommand: [
"mpKey": MP_KEY_NEXT,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().previousTrackCommand: [
"mpKey": MP_KEY_PREV,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().togglePlayPauseCommand: [
"mpKey": MP_KEY_PLAYPAUSE,
"keyType": KeyType.normal
],
MPRemoteCommandCenter.shared().seekForwardCommand: [
"mpKey": MP_KEY_FORWARD,
"keyType": KeyType.repeatable,
"state": MP_KEY_STATE_UP
],
MPRemoteCommandCenter.shared().seekBackwardCommand: [
"mpKey": MP_KEY_REWIND,
"keyType": KeyType.repeatable,
"state": MP_KEY_STATE_UP
],
]
var nowPlayingInfo: [String: Any] = [
MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
MPNowPlayingInfoPropertyDefaultPlaybackRate: NSNumber(value: 1),
MPNowPlayingInfoPropertyPlaybackProgress: NSNumber(value: 0.0),
MPMediaItemPropertyPlaybackDuration: NSNumber(value: 0),
MPMediaItemPropertyTitle: "mpv",
MPMediaItemPropertyAlbumTitle: "mpv",
MPMediaItemPropertyArtist: "mpv",
]
let disabledCommands: [MPRemoteCommand] = [
MPRemoteCommandCenter.shared().changePlaybackRateCommand,
MPRemoteCommandCenter.shared().changeRepeatModeCommand,
MPRemoteCommandCenter.shared().changeShuffleModeCommand,
MPRemoteCommandCenter.shared().skipForwardCommand,
MPRemoteCommandCenter.shared().skipBackwardCommand,
MPRemoteCommandCenter.shared().changePlaybackPositionCommand,
MPRemoteCommandCenter.shared().enableLanguageOptionCommand,
MPRemoteCommandCenter.shared().disableLanguageOptionCommand,
MPRemoteCommandCenter.shared().ratingCommand,
MPRemoteCommandCenter.shared().likeCommand,
MPRemoteCommandCenter.shared().dislikeCommand,
MPRemoteCommandCenter.shared().bookmarkCommand,
]
let application: Application;
@objc init(app: Application) {
application = app
super.init()
for cmd in disabledCommands {
cmd.isEnabled = false
}
}
@objc func makeCurrent() {
MPNowPlayingInfoCenter.default().playbackState = .paused
MPNowPlayingInfoCenter.default().playbackState = .playing
}
@objc func start() {
for (cmd, _) in config {
cmd.isEnabled = true
cmd.addTarget { [unowned self] event in
return self.cmdHandler(event)
}
}
if let icon = application.getMPVIcon(), #available(macOS 10.13.2, *) {
let albumArt = MPMediaItemArtwork(boundsSize:icon.size) { _ in
return icon
}
nowPlayingInfo[MPMediaItemPropertyArtwork] = albumArt
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
MPNowPlayingInfoCenter.default().playbackState = .playing
}
@objc func stop() {
for (cmd, _) in config {
cmd.isEnabled = false
cmd.removeTarget(nil)
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
MPNowPlayingInfoCenter.default().playbackState = .unknown
}
func cmdHandler(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
guard let cmdConfig = config[event.command],
let mpKey = cmdConfig["mpKey"] as? Int32,
let keyType = cmdConfig["keyType"] as? KeyType else
{
return .commandFailed
}
var state = cmdConfig["state"] as? UInt32 ?? 0
if let currentState = cmdConfig["state"] as? UInt32, keyType == .repeatable {
state = MP_KEY_STATE_DOWN
config[event.command]?["state"] = MP_KEY_STATE_DOWN
if currentState == MP_KEY_STATE_DOWN {
state = MP_KEY_STATE_UP
config[event.command]?["state"] = MP_KEY_STATE_UP
}
}
application.handleMPKey(mpKey, withMask: Int32(state));
return .success
}
}

View File

@ -105,6 +105,7 @@ static void terminate_cocoa_application(void)
@synthesize menuBar = _menu_bar;
@synthesize openCount = _open_count;
@synthesize cocoaCB = _cocoa_cb;
@synthesize remoteCommandCenter = _remoteCommandCenter;
- (void)sendEvent:(NSEvent *)event
{
@ -199,6 +200,11 @@ static const char macosx_icon[] =
[_eventsResponder queueCommand:cmd];
}
- (void)handleMPKey:(int)key withMask:(int)mask
{
[_eventsResponder handleMPKey:key withMask:mask];
}
- (void)stopMPV:(char *)cmd
{
if (![_eventsResponder queueCommand:cmd])
@ -216,7 +222,7 @@ static const char macosx_icon[] =
- (void)applicationWillBecomeActive:(NSNotification *)notification
{
[_eventsResponder setHighestPriotityMediaKeysTap];
[_remoteCommandCenter makeCurrent];
}
- (void)handleQuitEvent:(NSAppleEventDescriptor *)event
@ -293,6 +299,13 @@ static void init_cocoa_application(bool regular)
[NSApp setDelegate:NSApp];
[NSApp setMenuBar:[[MenuBar alloc] init]];
#if HAVE_MACOS_10_12_2_FEATURES
// 10.12.2 runtime availability check
if ([NSApp respondsToSelector:@selector(touchBar)]) {
[NSApp setRemoteCommandCenter:[[RemoteCommandCenter alloc] initWithApp:NSApp]];
}
#endif
// 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 ?

View File

@ -20,6 +20,7 @@
#import "osdep/macosx_menubar_objc.h"
@class CocoaCB;
@class RemoteCommandCenter;
struct mpv_event;
struct mpv_handle;
@ -28,6 +29,7 @@ struct mpv_handle;
- (NSImage *)getMPVIcon;
- (void)processEvent:(struct mpv_event *)event;
- (void)queueCommand:(char *)cmd;
- (void)handleMPKey:(int)key withMask:(int)mask;
- (void)stopMPV:(char *)cmd;
- (void)openFiles:(NSArray *)filenames;
- (void)setMpvHandle:(struct mpv_handle *)ctx;
@ -37,4 +39,5 @@ struct mpv_handle;
@property(nonatomic, retain) MenuBar *menuBar;
@property(nonatomic, assign) size_t openCount;
@property(nonatomic, retain) CocoaCB *cocoaCB;
@property(nonatomic, retain) RemoteCommandCenter *remoteCommandCenter;
@end

View File

@ -49,15 +49,12 @@
struct mpv_handle *_ctx;
BOOL _is_application;
NSCondition *_input_lock;
CFMachPortRef _mk_tap_port;
}
- (BOOL)handleMediaKey:(NSEvent *)event;
- (NSEvent *)handleKey:(NSEvent *)event;
- (BOOL)setMpvHandle:(struct mpv_handle *)ctx;
- (void)readEvents;
- (void)startMediaKeys;
- (void)restartMediaKeys;
- (void)stopMediaKeys;
- (int)mapKeyModifiers:(int)cocoaModifiers;
- (int)keyModifierMask:(NSEvent *)event;
@ -121,53 +118,6 @@ static int convert_key(unsigned key, unsigned charcode)
return charcode;
}
static int mk_code(NSEvent *event)
{
return (([event data1] & 0xFFFF0000) >> 16);
}
static int mk_flags(NSEvent *event)
{
return ([event data1] & 0x0000FFFF);
}
static int mk_down(NSEvent *event)
{
return (((mk_flags(event) & 0xFF00) >> 8)) == 0xA;
}
static CGEventRef tap_event_callback(CGEventTapProxy proxy, CGEventType type,
CGEventRef event, void *ctx)
{
EventsResponder *responder = ctx;
if (type == kCGEventTapDisabledByTimeout) {
// The Mach Port receiving the taps became unresponsive for some
// reason, restart listening on it.
[responder restartMediaKeys];
return event;
}
if (type == kCGEventTapDisabledByUserInput)
return event;
NSEvent *nse = [NSEvent eventWithCGEvent:event];
if ([nse type] != NSEventTypeSystemDefined || [nse subtype] != 8)
// This is not a media key
return event;
if (mk_down(nse) && [responder handleMediaKey:nse]) {
// Handled this event, return nil so that it is removed from the
// global queue.
return nil;
} else {
// Was a media key but we were not interested in it. Leave it in the
// global queue by returning the original event.
return event;
}
}
void cocoa_init_media_keys(void)
{
[[EventsResponder sharedInstance] startMediaKeys];
@ -337,77 +287,18 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
}
}
- (void)restartMediaKeys
{
if (self->_mk_tap_port)
CGEventTapEnable(self->_mk_tap_port, true);
}
- (void)setHighestPriotityMediaKeysTap
{
if (self->_mk_tap_port == nil)
return;
CGEventTapInformation *taps = ta_alloc_size(nil, sizeof(CGEventTapInformation));
uint32_t numTaps = 0;
CGError err = CGGetEventTapList(1, taps, &numTaps);
if (err == kCGErrorSuccess && numTaps > 0) {
pid_t processID = [NSProcessInfo processInfo].processIdentifier;
if (taps[0].tappingProcess != processID) {
[self stopMediaKeys];
[self startMediaKeys];
}
}
talloc_free(taps);
}
- (void)startMediaKeys
{
dispatch_async(dispatch_get_main_queue(), ^{
// Install a Quartz Event Tap. This will notify mpv through the
// returned Mach Port and cause mpv to execute the `tap_event_callback`
// function.
self->_mk_tap_port = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(NX_SYSDEFINED),
tap_event_callback,
self);
if (self->_mk_tap_port) {
NSMachPort *port = (NSMachPort *)self->_mk_tap_port;
[[NSRunLoop mainRunLoop] addPort:port forMode:NSRunLoopCommonModes];
}
});
if ([(Application *)NSApp remoteCommandCenter]) {
[[(Application *)NSApp remoteCommandCenter] start];
}
}
- (void)stopMediaKeys
{
dispatch_async(dispatch_get_main_queue(), ^{
NSMachPort *port = (NSMachPort *)self->_mk_tap_port;
if (port) {
CGEventTapEnable(self->_mk_tap_port, false);
[[NSRunLoop mainRunLoop] removePort:port forMode:NSRunLoopCommonModes];
CFRelease(self->_mk_tap_port);
self->_mk_tap_port = nil;
}
});
}
- (BOOL)handleMediaKey:(NSEvent *)event
{
NSDictionary *keymapd = @{
@(NX_KEYTYPE_PLAY): @(MP_KEY_PLAY),
@(NX_KEYTYPE_REWIND): @(MP_KEY_PREV),
@(NX_KEYTYPE_FAST): @(MP_KEY_NEXT),
@(NX_KEYTYPE_PREVIOUS): @(MP_KEY_REWIND),
@(NX_KEYTYPE_NEXT): @(MP_KEY_FORWARD),
};
return [self handleKey:mk_code(event)
withMask:[self keyModifierMask:event]
andMapping:keymapd];
if ([(Application *)NSApp remoteCommandCenter]) {
[[(Application *)NSApp remoteCommandCenter] stop];
}
}
- (int)mapKeyModifiers:(int)cocoaModifiers
@ -452,12 +343,6 @@ void cocoa_set_mpv_handle(struct mpv_handle *ctx)
}
}
-(BOOL)handleKey:(int)key withMask:(int)mask andMapping:(NSDictionary *)mapping
{
int mpkey = [mapping[@(key)] intValue];
return [self handleMPKey:mpkey withMask:mask];
}
- (NSEvent*)handleKey:(NSEvent *)event
{
if ([event isARepeat]) return nil;

View File

@ -32,10 +32,11 @@ struct input_ctx;
- (void)waitForInputContext;
- (void)wakeup;
- (void)putKey:(int)keycode;
- (void)setHighestPriotityMediaKeysTap;
- (void)handleFilesArray:(NSArray *)files;
- (bool)queueCommand:(char *)cmd;
- (bool)processKeyEvent:(NSEvent *)event;
- (BOOL)handleMPKey:(int)key withMask:(int)mask;
@end

View File

@ -180,20 +180,20 @@ class EventsView: NSView {
}
func signalMouseDown(_ event: NSEvent) {
signalMouseEvent(event, SWIFT_KEY_STATE_DOWN)
signalMouseEvent(event, MP_KEY_STATE_DOWN)
if event.clickCount > 1 {
signalMouseEvent(event, SWIFT_KEY_STATE_UP)
signalMouseEvent(event, MP_KEY_STATE_UP)
}
}
func signalMouseUp(_ event: NSEvent) {
signalMouseEvent(event, SWIFT_KEY_STATE_UP)
signalMouseEvent(event, MP_KEY_STATE_UP)
}
func signalMouseEvent(_ event: NSEvent, _ state: Int32) {
hasMouseDown = state == SWIFT_KEY_STATE_DOWN
func signalMouseEvent(_ event: NSEvent, _ state: UInt32) {
hasMouseDown = state == MP_KEY_STATE_DOWN
let mpkey = getMpvButton(event)
cocoa_put_key_with_modifiers((mpkey | state), Int32(event.modifierFlags.rawValue));
cocoa_put_key_with_modifiers((mpkey | Int32(state)), Int32(event.modifierFlags.rawValue));
}
func signalMouseMovement(_ event: NSEvent) {

View File

@ -486,7 +486,7 @@ class Window: NSWindow, NSWindowDelegate {
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
cocoa_put_key(SWIFT_KEY_CLOSE_WIN)
cocoa_put_key(MP_KEY_CLOSE_WIN)
return false
}

View File

@ -918,6 +918,11 @@ standalone_features = [
'desc': 'macOS 10.11 SDK Features',
'deps': 'cocoa',
'func': check_macos_sdk('10.11')
}, {
'name': '--macos-10-12-2-features',
'desc': 'macOS 10.12.2 SDK Features',
'deps': 'cocoa',
'func': check_macos_sdk('10.12.2')
}, {
'name': '--macos-10-14-features',
'desc': 'macOS 10.14 SDK Features',

View File

@ -174,6 +174,7 @@ def build(ctx):
( "osdep/macos/mpv_helper.swift" ),
( "osdep/macos/swift_extensions.swift" ),
( "osdep/macos/swift_compat.swift" ),
( "osdep/macos/remote_command_center.swift", "macos-10-12-2-features" ),
( "video/out/cocoa-cb/events_view.swift" ),
( "video/out/cocoa-cb/video_layer.swift" ),
( "video/out/cocoa-cb/window.swift" ),