mirror of https://github.com/mpv-player/mpv
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:
parent
8a6ee7fe94
commit
a32db637b5
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 ?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
5
wscript
5
wscript
|
@ -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',
|
||||
|
|
|
@ -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" ),
|
||||
|
|
Loading…
Reference in New Issue