mirror of
https://github.com/jellyfin/jellyfin-media-player.git
synced 2024-11-27 00:00:38 +00:00
[macOS] remove no longer used SPMediaKeyTap
MPRemoteCommandCenter and MPNowPlayingInfoCenter are available since macOS 10.12.2
This commit is contained in:
parent
316c0c307d
commit
25affb5b9c
@ -6,8 +6,8 @@ find_Library(CARBON Carbon)
|
|||||||
find_library(SECURITY Security)
|
find_library(SECURITY Security)
|
||||||
find_library(MEDIAPLAYER MediaPlayer)
|
find_library(MEDIAPLAYER MediaPlayer)
|
||||||
|
|
||||||
set(OS_LIBS ${FOUNDATION} ${APPKIT} ${IOKIT} ${COCOA} ${SECURITY} ${CARBON} spmediakeytap hidremote plistparser letsmove)
|
set(OS_LIBS ${FOUNDATION} ${APPKIT} ${IOKIT} ${COCOA} ${SECURITY} ${CARBON} hidremote plistparser letsmove)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -weak_framework MediaPlayer")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework MediaPlayer")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.9 -fno-omit-frame-pointer")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.9 -fno-omit-frame-pointer")
|
||||||
set(WARNINGS "-Wall")
|
set(WARNINGS "-Wall")
|
||||||
|
1
external/CMakeLists.txt
vendored
1
external/CMakeLists.txt
vendored
@ -3,7 +3,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILER_FLAGS_THIRD_PARTY}")
|
|||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_subdirectory(plistparser)
|
add_subdirectory(plistparser)
|
||||||
add_subdirectory(SPMediaKeyTap)
|
|
||||||
add_subdirectory(HIDRemote)
|
add_subdirectory(HIDRemote)
|
||||||
add_subdirectory(letsmove)
|
add_subdirectory(letsmove)
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
9
external/SPMediaKeyTap/CMakeLists.txt
vendored
9
external/SPMediaKeyTap/CMakeLists.txt
vendored
@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
# SPMediaKeyTap uses some deprecated methods, no need to fix that right now, so let's supress the warnings.
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations")
|
|
||||||
|
|
||||||
add_library(spmediakeytap STATIC
|
|
||||||
SPMediaKeyTap.h SPMediaKeyTap.m
|
|
||||||
SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m
|
|
||||||
SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h
|
|
||||||
)
|
|
8
external/SPMediaKeyTap/LICENSE
vendored
8
external/SPMediaKeyTap/LICENSE
vendored
@ -1,8 +0,0 @@
|
|||||||
Copyright (c) 2011, Joachim Bengtsson
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
12
external/SPMediaKeyTap/README.md
vendored
12
external/SPMediaKeyTap/README.md
vendored
@ -1,12 +0,0 @@
|
|||||||
SPMediaKeyTap
|
|
||||||
=============
|
|
||||||
|
|
||||||
`SPMediaKeyTap` abstracts a `CGEventHook` and other nastiness in order to give you a relatively simple API to receive media key events (prev/next/playpause, on F7 to F9 on modern MacBook Pros) exclusively, without them reaching other applications like iTunes. `SPMediaKeyTap` is clever enough to resign its exclusive lock on media keys by looking for which application was active most recently: if that application is in `SPMediaKeyTap`'s whitelist, it will resign the keys. This is similar to the behavior of Apple's applications collaborating on media key handling exclusivity, but unfortunately, Apple is not exposing any APIs allowing third-parties to join in on this collaboration.
|
|
||||||
|
|
||||||
For now, the whitelist is just a hardcoded array in `+[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers]`. If your app starts using `SPMediaKeyTap`, please [mail me](mailto:nevyn@spotify.com) your bundle ID, and I'll include it in the canonical repository. This is a bad solution; a better solution would be to use distributed notifications to collaborate in creating this whitelist at runtime. Hopefully someone'll have the time and energy to write this soon.
|
|
||||||
|
|
||||||
In `Example/SPMediaKeyTapExampleAppDelegate.m` is an example of both how you use `SPMediaKeyTap`, and how you handle the semi-private `NSEvent` subtypes involved in media keys, including on how to fall back to non-event tap handling of these events.
|
|
||||||
|
|
||||||
`SPMediaKeyTap` and other `CGEventHook`s on the event type `NSSystemDefined` is known to interfere with each other and applications doing weird stuff with mouse input, because mouse clicks are also part of the `NSSystemDefined` category. The single issue we have had reported here at Spotify is Adobe Fireworks, in which item selection stops working with `SPMediaKeyTap` is active.
|
|
||||||
|
|
||||||
`SPMediaKeyTap` requires 10.5 to work, and disables itself on 10.4.
|
|
@ -1,30 +0,0 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@interface SPInvocationGrabber : NSObject {
|
|
||||||
id _object;
|
|
||||||
NSInvocation *_invocation;
|
|
||||||
int frameCount;
|
|
||||||
char **frameStrings;
|
|
||||||
BOOL backgroundAfterForward;
|
|
||||||
BOOL onMainAfterForward;
|
|
||||||
BOOL waitUntilDone;
|
|
||||||
}
|
|
||||||
-(id)initWithObject:(id)obj;
|
|
||||||
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
|
|
||||||
@property (readonly, retain, nonatomic) id object;
|
|
||||||
@property (readonly, retain, nonatomic) NSInvocation *invocation;
|
|
||||||
@property BOOL backgroundAfterForward;
|
|
||||||
@property BOOL onMainAfterForward;
|
|
||||||
@property BOOL waitUntilDone;
|
|
||||||
-(void)invoke; // will release object and invocation
|
|
||||||
-(void)printBacktrace;
|
|
||||||
-(void)saveBacktrace;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface NSObject (SPInvocationGrabbing)
|
|
||||||
-(id)grab;
|
|
||||||
-(id)invokeAfter:(NSTimeInterval)delta;
|
|
||||||
-(id)nextRunloop;
|
|
||||||
-(id)inBackground;
|
|
||||||
-(id)onMainAsync:(BOOL)async;
|
|
||||||
@end
|
|
@ -1,127 +0,0 @@
|
|||||||
#import "NSObject+SPInvocationGrabbing.h"
|
|
||||||
#import <execinfo.h>
|
|
||||||
|
|
||||||
#pragma mark Invocation grabbing
|
|
||||||
@interface SPInvocationGrabber ()
|
|
||||||
@property (readwrite, retain, nonatomic) id object;
|
|
||||||
@property (readwrite, retain, nonatomic) NSInvocation *invocation;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SPInvocationGrabber
|
|
||||||
- (id)initWithObject:(id)obj;
|
|
||||||
{
|
|
||||||
return [self initWithObject:obj stacktraceSaving:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
|
|
||||||
{
|
|
||||||
self.object = obj;
|
|
||||||
|
|
||||||
if(saveStack)
|
|
||||||
[self saveBacktrace];
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
-(void)dealloc;
|
|
||||||
{
|
|
||||||
free(frameStrings);
|
|
||||||
self.object = nil;
|
|
||||||
self.invocation = nil;
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
@synthesize invocation = _invocation, object = _object;
|
|
||||||
|
|
||||||
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
|
|
||||||
- (void)runInBackground;
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
@try {
|
|
||||||
[self invoke];
|
|
||||||
}
|
|
||||||
@finally {
|
|
||||||
[pool drain];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
|
||||||
[anInvocation retainArguments];
|
|
||||||
anInvocation.target = _object;
|
|
||||||
self.invocation = anInvocation;
|
|
||||||
|
|
||||||
if(backgroundAfterForward)
|
|
||||||
[NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil];
|
|
||||||
else if(onMainAfterForward)
|
|
||||||
[self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone];
|
|
||||||
}
|
|
||||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
|
|
||||||
NSMethodSignature *signature = [super methodSignatureForSelector:inSelector];
|
|
||||||
if (signature == NULL)
|
|
||||||
signature = [_object methodSignatureForSelector:inSelector];
|
|
||||||
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)invoke;
|
|
||||||
{
|
|
||||||
|
|
||||||
@try {
|
|
||||||
[_invocation invoke];
|
|
||||||
}
|
|
||||||
@catch (NSException * e) {
|
|
||||||
NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e);
|
|
||||||
[self printBacktrace];
|
|
||||||
printf("\n");
|
|
||||||
[e raise];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.invocation = nil;
|
|
||||||
self.object = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)saveBacktrace;
|
|
||||||
{
|
|
||||||
void *backtraceFrames[128];
|
|
||||||
frameCount = backtrace(&backtraceFrames[0], 128);
|
|
||||||
frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount);
|
|
||||||
}
|
|
||||||
-(void)printBacktrace;
|
|
||||||
{
|
|
||||||
for(int x = 3; x < frameCount; x++) {
|
|
||||||
if(frameStrings[x] == NULL) { break; }
|
|
||||||
printf("%s\n", frameStrings[x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation NSObject (SPInvocationGrabbing)
|
|
||||||
-(id)grab;
|
|
||||||
{
|
|
||||||
return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease];
|
|
||||||
}
|
|
||||||
-(id)invokeAfter:(NSTimeInterval)delta;
|
|
||||||
{
|
|
||||||
id grabber = [self grab];
|
|
||||||
[NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO];
|
|
||||||
return grabber;
|
|
||||||
}
|
|
||||||
- (id)nextRunloop;
|
|
||||||
{
|
|
||||||
return [self invokeAfter:0];
|
|
||||||
}
|
|
||||||
-(id)inBackground;
|
|
||||||
{
|
|
||||||
SPInvocationGrabber *grabber = [self grab];
|
|
||||||
grabber.backgroundAfterForward = YES;
|
|
||||||
return grabber;
|
|
||||||
}
|
|
||||||
-(id)onMainAsync:(BOOL)async;
|
|
||||||
{
|
|
||||||
SPInvocationGrabber *grabber = [self grab];
|
|
||||||
grabber.onMainAfterForward = YES;
|
|
||||||
grabber.waitUntilDone = !async;
|
|
||||||
return grabber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
43
external/SPMediaKeyTap/SPMediaKeyTap.h
vendored
43
external/SPMediaKeyTap/SPMediaKeyTap.h
vendored
@ -1,43 +0,0 @@
|
|||||||
#include <Cocoa/Cocoa.h>
|
|
||||||
#import <IOKit/hidsystem/ev_keymap.h>
|
|
||||||
#import <Carbon/Carbon.h>
|
|
||||||
|
|
||||||
// http://overooped.com/post/2593597587/mediakeys
|
|
||||||
|
|
||||||
#define SPSystemDefinedEventMediaKeys 8
|
|
||||||
|
|
||||||
@interface SPMediaKeyTap : NSObject {
|
|
||||||
EventHandlerRef _app_switching_ref;
|
|
||||||
EventHandlerRef _app_terminating_ref;
|
|
||||||
CFMachPortRef _eventPort;
|
|
||||||
CFRunLoopSourceRef _eventPortSource;
|
|
||||||
CFRunLoopRef _tapThreadRL;
|
|
||||||
BOOL _shouldInterceptMediaKeyEvents;
|
|
||||||
id _delegate;
|
|
||||||
// The app that is frontmost in this list owns media keys
|
|
||||||
NSMutableArray *_mediaKeyAppList;
|
|
||||||
}
|
|
||||||
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
|
|
||||||
|
|
||||||
-(id)initWithDelegate:(id)delegate;
|
|
||||||
|
|
||||||
+(BOOL)usesGlobalMediaKeyTap;
|
|
||||||
-(void)startWatchingMediaKeys;
|
|
||||||
-(void)stopWatchingMediaKeys;
|
|
||||||
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface NSObject (SPMediaKeyTapDelegate)
|
|
||||||
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
|
|
||||||
@end
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;
|
|
||||||
extern NSString *kIgnoreMediaKeysDefaultsKey;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
346
external/SPMediaKeyTap/SPMediaKeyTap.m
vendored
346
external/SPMediaKeyTap/SPMediaKeyTap.m
vendored
@ -1,346 +0,0 @@
|
|||||||
// Copyright (c) 2010 Spotify AB
|
|
||||||
#import "SPMediaKeyTap.h"
|
|
||||||
#import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule
|
|
||||||
|
|
||||||
@interface SPMediaKeyTap ()
|
|
||||||
-(BOOL)shouldInterceptMediaKeyEvents;
|
|
||||||
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
|
|
||||||
-(void)startWatchingAppSwitching;
|
|
||||||
-(void)stopWatchingAppSwitching;
|
|
||||||
-(void)eventTapThread;
|
|
||||||
@end
|
|
||||||
static SPMediaKeyTap *singleton = nil;
|
|
||||||
|
|
||||||
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
|
|
||||||
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
|
|
||||||
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
|
|
||||||
|
|
||||||
|
|
||||||
// Inspired by http://gist.github.com/546311
|
|
||||||
|
|
||||||
@implementation SPMediaKeyTap
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
#pragma mark Setup and teardown
|
|
||||||
-(id)initWithDelegate:(id)delegate;
|
|
||||||
{
|
|
||||||
_delegate = delegate;
|
|
||||||
[self startWatchingAppSwitching];
|
|
||||||
singleton = self;
|
|
||||||
_mediaKeyAppList = [NSMutableArray new];
|
|
||||||
_tapThreadRL=nil;
|
|
||||||
_eventPort=nil;
|
|
||||||
_eventPortSource=nil;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
-(void)dealloc;
|
|
||||||
{
|
|
||||||
[self stopWatchingMediaKeys];
|
|
||||||
[self stopWatchingAppSwitching];
|
|
||||||
[_mediaKeyAppList release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)startWatchingAppSwitching;
|
|
||||||
{
|
|
||||||
// Listen to "app switched" event, so that we don't intercept media keys if we
|
|
||||||
// weren't the last "media key listening" app to be active
|
|
||||||
EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
|
|
||||||
OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
|
|
||||||
assert(err == noErr);
|
|
||||||
|
|
||||||
eventType.eventKind = kEventAppTerminated;
|
|
||||||
err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
|
|
||||||
assert(err == noErr);
|
|
||||||
}
|
|
||||||
-(void)stopWatchingAppSwitching;
|
|
||||||
{
|
|
||||||
if(!_app_switching_ref) return;
|
|
||||||
RemoveEventHandler(_app_switching_ref);
|
|
||||||
_app_switching_ref = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)startWatchingMediaKeys;{
|
|
||||||
// Prevent having multiple mediaKeys threads
|
|
||||||
[self stopWatchingMediaKeys];
|
|
||||||
|
|
||||||
[self setShouldInterceptMediaKeyEvents:YES];
|
|
||||||
|
|
||||||
// Add an event tap to intercept the system defined media key events
|
|
||||||
_eventPort = CGEventTapCreate(kCGSessionEventTap,
|
|
||||||
kCGHeadInsertEventTap,
|
|
||||||
kCGEventTapOptionDefault,
|
|
||||||
CGEventMaskBit(NX_SYSDEFINED),
|
|
||||||
tapEventCallback,
|
|
||||||
self);
|
|
||||||
assert(_eventPort != NULL);
|
|
||||||
|
|
||||||
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
|
|
||||||
assert(_eventPortSource != NULL);
|
|
||||||
|
|
||||||
// Let's do this in a separate thread so that a slow app doesn't lag the event tap
|
|
||||||
[NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
|
|
||||||
}
|
|
||||||
-(void)stopWatchingMediaKeys;
|
|
||||||
{
|
|
||||||
// TODO<nevyn>: Shut down thread, remove event tap port and source
|
|
||||||
|
|
||||||
if(_tapThreadRL){
|
|
||||||
CFRunLoopStop(_tapThreadRL);
|
|
||||||
_tapThreadRL=nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_eventPort){
|
|
||||||
CFMachPortInvalidate(_eventPort);
|
|
||||||
CFRelease(_eventPort);
|
|
||||||
_eventPort=nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_eventPortSource){
|
|
||||||
CFRelease(_eventPortSource);
|
|
||||||
_eventPortSource=nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
#pragma mark Accessors
|
|
||||||
|
|
||||||
+(BOOL)usesGlobalMediaKeyTap
|
|
||||||
{
|
|
||||||
#ifdef _DEBUG
|
|
||||||
// breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot
|
|
||||||
return NO;
|
|
||||||
#else
|
|
||||||
// XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
|
|
||||||
return
|
|
||||||
![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey]
|
|
||||||
&& floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
|
|
||||||
{
|
|
||||||
return [NSArray arrayWithObjects:
|
|
||||||
[[NSBundle mainBundle] bundleIdentifier], // your app
|
|
||||||
@"com.spotify.client",
|
|
||||||
@"com.apple.iTunes",
|
|
||||||
@"com.apple.QuickTimePlayerX",
|
|
||||||
@"com.apple.quicktimeplayer",
|
|
||||||
@"com.apple.iWork.Keynote",
|
|
||||||
@"com.apple.iPhoto",
|
|
||||||
@"org.videolan.vlc",
|
|
||||||
@"com.apple.Aperture",
|
|
||||||
@"com.plexsquared.Plex",
|
|
||||||
@"com.soundcloud.desktop",
|
|
||||||
@"org.niltsh.MPlayerX",
|
|
||||||
@"com.ilabs.PandorasHelper",
|
|
||||||
@"com.mahasoftware.pandabar",
|
|
||||||
@"com.bitcartel.pandorajam",
|
|
||||||
@"org.clementine-player.clementine",
|
|
||||||
@"fm.last.Last.fm",
|
|
||||||
@"fm.last.Scrobbler",
|
|
||||||
@"com.beatport.BeatportPro",
|
|
||||||
@"com.Timenut.SongKey",
|
|
||||||
@"com.macromedia.fireworks", // the tap messes up their mouse input
|
|
||||||
@"at.justp.Theremin",
|
|
||||||
@"ru.ya.themblsha.YandexMusic",
|
|
||||||
@"com.jriver.MediaCenter18",
|
|
||||||
@"com.jriver.MediaCenter19",
|
|
||||||
@"com.jriver.MediaCenter20",
|
|
||||||
@"co.rackit.mate",
|
|
||||||
@"com.ttitt.b-music",
|
|
||||||
@"com.beardedspice.BeardedSpice",
|
|
||||||
@"com.plug.Plug",
|
|
||||||
@"com.plug.Plug2",
|
|
||||||
@"com.netease.163music",
|
|
||||||
nil
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-(BOOL)shouldInterceptMediaKeyEvents;
|
|
||||||
{
|
|
||||||
BOOL shouldIntercept = NO;
|
|
||||||
@synchronized(self) {
|
|
||||||
shouldIntercept = _shouldInterceptMediaKeyEvents;
|
|
||||||
}
|
|
||||||
return shouldIntercept;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)pauseTapOnTapThread:(BOOL)yeahno;
|
|
||||||
{
|
|
||||||
CGEventTapEnable(self->_eventPort, yeahno);
|
|
||||||
}
|
|
||||||
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
|
|
||||||
{
|
|
||||||
BOOL oldSetting;
|
|
||||||
@synchronized(self) {
|
|
||||||
oldSetting = _shouldInterceptMediaKeyEvents;
|
|
||||||
_shouldInterceptMediaKeyEvents = newSetting;
|
|
||||||
}
|
|
||||||
if(_tapThreadRL && oldSetting != newSetting) {
|
|
||||||
id grab = [self grab];
|
|
||||||
[grab pauseTapOnTapThread:newSetting];
|
|
||||||
NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
|
|
||||||
CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark
|
|
||||||
#pragma mark -
|
|
||||||
#pragma mark Event tap callbacks
|
|
||||||
|
|
||||||
// Note: method called on background thread
|
|
||||||
|
|
||||||
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
|
||||||
{
|
|
||||||
SPMediaKeyTap *self = refcon;
|
|
||||||
|
|
||||||
if(type == kCGEventTapDisabledByTimeout) {
|
|
||||||
NSLog(@"Media key event tap was disabled by timeout");
|
|
||||||
CGEventTapEnable(self->_eventPort, TRUE);
|
|
||||||
return event;
|
|
||||||
} else if(type == kCGEventTapDisabledByUserInput) {
|
|
||||||
// Was disabled manually by -[pauseTapOnTapThread]
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
NSEvent *nsEvent = nil;
|
|
||||||
@try {
|
|
||||||
nsEvent = [NSEvent eventWithCGEvent:event];
|
|
||||||
}
|
|
||||||
@catch (NSException * e) {
|
|
||||||
NSLog(@"Strange CGEventType: %d: %@", type, e);
|
|
||||||
assert(0);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
|
|
||||||
return event;
|
|
||||||
|
|
||||||
int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
|
|
||||||
if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND && keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_NEXT)
|
|
||||||
return event;
|
|
||||||
|
|
||||||
if (![self shouldInterceptMediaKeyEvents])
|
|
||||||
return event;
|
|
||||||
|
|
||||||
[nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent:
|
|
||||||
[self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
||||||
CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
|
|
||||||
[pool drain];
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// event will have been retained in the other thread
|
|
||||||
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
|
|
||||||
[event autorelease];
|
|
||||||
|
|
||||||
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-(void)eventTapThread;
|
|
||||||
{
|
|
||||||
_tapThreadRL = CFRunLoopGetCurrent();
|
|
||||||
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
|
|
||||||
CFRunLoopRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark Task switching callbacks
|
|
||||||
|
|
||||||
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
|
|
||||||
NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-(void)mediaKeyAppListChanged;
|
|
||||||
{
|
|
||||||
if([_mediaKeyAppList count] == 0) return;
|
|
||||||
|
|
||||||
/*NSLog(@"--");
|
|
||||||
int i = 0;
|
|
||||||
for (NSValue *psnv in _mediaKeyAppList) {
|
|
||||||
ProcessSerialNumber psn; [psnv getValue:&psn];
|
|
||||||
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
|
|
||||||
&psn,
|
|
||||||
kProcessDictionaryIncludeAllInformationMask
|
|
||||||
) autorelease];
|
|
||||||
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
|
|
||||||
NSLog(@"%d: %@", i++, bundleIdentifier);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
ProcessSerialNumber mySerial, topSerial;
|
|
||||||
GetCurrentProcess(&mySerial);
|
|
||||||
[[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
|
|
||||||
|
|
||||||
Boolean same;
|
|
||||||
OSErr err = SameProcess(&mySerial, &topSerial, &same);
|
|
||||||
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
|
|
||||||
|
|
||||||
}
|
|
||||||
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn;
|
|
||||||
{
|
|
||||||
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
|
|
||||||
|
|
||||||
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
|
|
||||||
&psn,
|
|
||||||
kProcessDictionaryIncludeAllInformationMask
|
|
||||||
) autorelease];
|
|
||||||
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
|
|
||||||
|
|
||||||
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
|
|
||||||
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return;
|
|
||||||
|
|
||||||
[_mediaKeyAppList removeObject:psnv];
|
|
||||||
[_mediaKeyAppList insertObject:psnv atIndex:0];
|
|
||||||
[self mediaKeyAppListChanged];
|
|
||||||
}
|
|
||||||
-(void)appTerminated:(ProcessSerialNumber)psn;
|
|
||||||
{
|
|
||||||
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
|
|
||||||
[_mediaKeyAppList removeObject:psnv];
|
|
||||||
[self mediaKeyAppListChanged];
|
|
||||||
}
|
|
||||||
|
|
||||||
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
|
|
||||||
{
|
|
||||||
SPMediaKeyTap *self = (id)userData;
|
|
||||||
|
|
||||||
ProcessSerialNumber newSerial;
|
|
||||||
GetFrontProcess(&newSerial);
|
|
||||||
|
|
||||||
[self appIsNowFrontmost:newSerial];
|
|
||||||
|
|
||||||
return CallNextEventHandler(nextHandler, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
|
|
||||||
{
|
|
||||||
SPMediaKeyTap *self = (id)userData;
|
|
||||||
|
|
||||||
ProcessSerialNumber deadPSN;
|
|
||||||
|
|
||||||
GetEventParameter(
|
|
||||||
evt,
|
|
||||||
kEventParamProcessID,
|
|
||||||
typeProcessSerialNumber,
|
|
||||||
NULL,
|
|
||||||
sizeof(deadPSN),
|
|
||||||
NULL,
|
|
||||||
&deadPSN
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
[self appTerminated:deadPSN];
|
|
||||||
return CallNextEventHandler(nextHandler, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -1,5 +1,4 @@
|
|||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_SOURCE_DIR}/external/SPMediaKeyTap
|
|
||||||
${CMAKE_SOURCE_DIR}/external/HIDRemote
|
${CMAKE_SOURCE_DIR}/external/HIDRemote
|
||||||
${CMAKE_SOURCE_DIR}/external/plistparser
|
${CMAKE_SOURCE_DIR}/external/plistparser
|
||||||
${CMAKE_SOURCE_DIR}/external/letsmove
|
${CMAKE_SOURCE_DIR}/external/letsmove
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
// Created by Tobias Hieta on 21/08/15.
|
// Created by Tobias Hieta on 21/08/15.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include "InputAppleMediaKeys.h"
|
#include "InputAppleMediaKeys.h"
|
||||||
#include "SPMediaKeyTap.h"
|
#include <QDebug>
|
||||||
|
|
||||||
#import <dlfcn.h>
|
#import <dlfcn.h>
|
||||||
|
|
||||||
@ -12,7 +11,6 @@
|
|||||||
|
|
||||||
@interface MediaKeysDelegate : NSObject
|
@interface MediaKeysDelegate : NSObject
|
||||||
{
|
{
|
||||||
SPMediaKeyTap* keyTap;
|
|
||||||
InputAppleMediaKeys* input;
|
InputAppleMediaKeys* input;
|
||||||
}
|
}
|
||||||
-(instancetype)initWithInput:(InputAppleMediaKeys*)input;
|
-(instancetype)initWithInput:(InputAppleMediaKeys*)input;
|
||||||
@ -25,37 +23,24 @@
|
|||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
input = input_;
|
input = input_;
|
||||||
if (NSClassFromString(@"MPRemoteCommandCenter")) {
|
MPRemoteCommandCenter* center = [MPRemoteCommandCenter sharedCommandCenter];
|
||||||
MPRemoteCommandCenter* center = [MPRemoteCommandCenter sharedCommandCenter];
|
|
||||||
#define CONFIG_CMD(name) \
|
#define CONFIG_CMD(name) \
|
||||||
[center.name ## Command addTarget:self action:@selector(gotCommand:)]
|
[center.name ## Command addTarget:self action:@selector(gotCommand:)]
|
||||||
CONFIG_CMD(play);
|
CONFIG_CMD(play);
|
||||||
CONFIG_CMD(pause);
|
CONFIG_CMD(pause);
|
||||||
CONFIG_CMD(togglePlayPause);
|
CONFIG_CMD(togglePlayPause);
|
||||||
CONFIG_CMD(stop);
|
CONFIG_CMD(stop);
|
||||||
CONFIG_CMD(nextTrack);
|
CONFIG_CMD(nextTrack);
|
||||||
CONFIG_CMD(previousTrack);
|
CONFIG_CMD(previousTrack);
|
||||||
CONFIG_CMD(seekForward);
|
CONFIG_CMD(seekForward);
|
||||||
CONFIG_CMD(seekBackward);
|
CONFIG_CMD(seekBackward);
|
||||||
CONFIG_CMD(skipForward);
|
CONFIG_CMD(skipForward);
|
||||||
CONFIG_CMD(skipBackward);
|
CONFIG_CMD(skipBackward);
|
||||||
[center.changePlaybackPositionCommand addTarget:self action:@selector(gotPlaybackPosition:)];
|
[center.changePlaybackPositionCommand addTarget:self action:@selector(gotPlaybackPosition:)];
|
||||||
} else {
|
|
||||||
keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
|
|
||||||
if ([SPMediaKeyTap usesGlobalMediaKeyTap])
|
|
||||||
[keyTap startWatchingMediaKeys];
|
|
||||||
else
|
|
||||||
qWarning() << "Could not grab global media keys";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(MPRemoteCommandHandlerStatus)gotCommand:(MPRemoteCommandEvent *)event
|
-(MPRemoteCommandHandlerStatus)gotCommand:(MPRemoteCommandEvent *)event
|
||||||
{
|
{
|
||||||
QString keyPressed;
|
QString keyPressed;
|
||||||
@ -88,38 +73,6 @@
|
|||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)mediaKeyTap:(SPMediaKeyTap *)keyTap receivedMediaKeyEvent:(NSEvent *)event
|
|
||||||
{
|
|
||||||
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
|
|
||||||
int keyFlags = ([event data1] & 0x0000FFFF);
|
|
||||||
BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA;
|
|
||||||
|
|
||||||
QString keyPressed;
|
|
||||||
|
|
||||||
switch (keyCode) {
|
|
||||||
case NX_KEYTYPE_PLAY:
|
|
||||||
keyPressed = INPUT_KEY_PLAY_PAUSE;
|
|
||||||
break;
|
|
||||||
case NX_KEYTYPE_FAST:
|
|
||||||
keyPressed = "KEY_FAST";
|
|
||||||
break;
|
|
||||||
case NX_KEYTYPE_REWIND:
|
|
||||||
keyPressed = "KEY_REWIND";
|
|
||||||
break;
|
|
||||||
case NX_KEYTYPE_NEXT:
|
|
||||||
keyPressed = INPUT_KEY_NEXT;
|
|
||||||
break;
|
|
||||||
case NX_KEYTYPE_PREVIOUS:
|
|
||||||
keyPressed = INPUT_KEY_PREV;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
// More cases defined in hidsystem/ev_keymap.h
|
|
||||||
}
|
|
||||||
|
|
||||||
emit input->receivedInput("AppleMediaKeys", keyPressed, keyIsPressed ? InputBase::KeyDown : InputBase::KeyUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// macOS private enum
|
// macOS private enum
|
||||||
@ -136,12 +89,16 @@ bool InputAppleMediaKeys::initInput()
|
|||||||
m_currentTime = 0;
|
m_currentTime = 0;
|
||||||
m_pendingUpdate = false;
|
m_pendingUpdate = false;
|
||||||
m_delegate = [[MediaKeysDelegate alloc] initWithInput:this];
|
m_delegate = [[MediaKeysDelegate alloc] initWithInput:this];
|
||||||
if (NSClassFromString(@"MPNowPlayingInfoCenter")) {
|
connect(&PlayerComponent::Get(), &PlayerComponent::stateChanged, this,
|
||||||
connect(&PlayerComponent::Get(), &PlayerComponent::stateChanged, this, &InputAppleMediaKeys::handleStateChanged);
|
&InputAppleMediaKeys::handleStateChanged);
|
||||||
connect(&PlayerComponent::Get(), &PlayerComponent::positionUpdate, this, &InputAppleMediaKeys::handlePositionUpdate);
|
connect(&PlayerComponent::Get(), &PlayerComponent::positionUpdate, this,
|
||||||
connect(&PlayerComponent::Get(), &PlayerComponent::updateDuration, this, &InputAppleMediaKeys::handleUpdateDuration);
|
&InputAppleMediaKeys::handlePositionUpdate);
|
||||||
void* lib = dlopen("/System/Library/PrivateFrameworks/MediaRemote.framework/MediaRemote", RTLD_NOW);
|
connect(&PlayerComponent::Get(), &PlayerComponent::updateDuration, this,
|
||||||
if (lib) {
|
&InputAppleMediaKeys::handleUpdateDuration);
|
||||||
|
|
||||||
|
if (auto lib =
|
||||||
|
dlopen("/System/Library/PrivateFrameworks/MediaRemote.framework/MediaRemote", RTLD_NOW))
|
||||||
|
{
|
||||||
#define LOAD_FUNC(name) \
|
#define LOAD_FUNC(name) \
|
||||||
name = (name ## Func)dlsym(lib, "MRMediaRemote" #name)
|
name = (name ## Func)dlsym(lib, "MRMediaRemote" #name)
|
||||||
LOAD_FUNC(SetNowPlayingVisibility);
|
LOAD_FUNC(SetNowPlayingVisibility);
|
||||||
@ -149,7 +106,6 @@ bool InputAppleMediaKeys::initInput()
|
|||||||
LOAD_FUNC(SetCanBeNowPlayingApplication);
|
LOAD_FUNC(SetCanBeNowPlayingApplication);
|
||||||
if (SetCanBeNowPlayingApplication)
|
if (SetCanBeNowPlayingApplication)
|
||||||
SetCanBeNowPlayingApplication(1);
|
SetCanBeNowPlayingApplication(1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -202,7 +158,6 @@ void InputAppleMediaKeys::handlePositionUpdate(quint64 position)
|
|||||||
NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
|
NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
|
||||||
[playingInfo setObject:[NSNumber numberWithDouble:(double)position / 1000] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
|
[playingInfo setObject:[NSNumber numberWithDouble:(double)position / 1000] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
|
||||||
center.nowPlayingInfo = playingInfo;
|
center.nowPlayingInfo = playingInfo;
|
||||||
[MPNowPlayingInfoCenter defaultCenter].playbackState = [MPNowPlayingInfoCenter defaultCenter].playbackState;
|
|
||||||
m_pendingUpdate = false;
|
m_pendingUpdate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user