Update SPMediaKeyTap to a more recent version.

CQTexperiment
Christopher Snowhill 2018-06-09 19:22:04 -07:00
parent a429025dc2
commit 18176782e7
3 changed files with 306 additions and 254 deletions

View File

@ -34,9 +34,7 @@
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone; @synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
- (void)runInBackground; - (void)runInBackground;
{ {
@autoreleasepool {
[self invoke]; [self invoke];
}
} }

View File

@ -5,6 +5,7 @@
// http://overooped.com/post/2593597587/mediakeys // http://overooped.com/post/2593597587/mediakeys
#define SPSystemDefinedEventMediaKeys 8 #define SPSystemDefinedEventMediaKeys 8
#define SPPassthroughEventData2Value -10
@interface SPMediaKeyTap : NSObject { @interface SPMediaKeyTap : NSObject {
EventHandlerRef _app_switching_ref; EventHandlerRef _app_switching_ref;
@ -25,6 +26,9 @@
-(void)startWatchingMediaKeys; -(void)startWatchingMediaKeys;
-(void)stopWatchingMediaKeys; -(void)stopWatchingMediaKeys;
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event; -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event;
@property NSArray *blackListBundleIdentifiers;
@end @end
@interface NSObject (SPMediaKeyTapDelegate) @interface NSObject (SPMediaKeyTapDelegate)
@ -36,6 +40,7 @@ extern "C" {
#endif #endif
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;
extern NSString *kMediaKeyUsingBlackListBundleIdentifiersDefaultsKey;
extern NSString *kIgnoreMediaKeysDefaultsKey; extern NSString *kIgnoreMediaKeysDefaultsKey;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -1,6 +1,6 @@
// Copyright (c) 2010 Spotify AB // Copyright (c) 2010 Spotify AB
#import "SPMediaKeyTap.h" #import "SPMediaKeyTap.h"
#import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule #import "NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule
@interface SPMediaKeyTap () @interface SPMediaKeyTap ()
-(BOOL)shouldInterceptMediaKeyEvents; -(BOOL)shouldInterceptMediaKeyEvents;
@ -28,6 +28,8 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
[self startWatchingAppSwitching]; [self startWatchingAppSwitching];
singleton = self; singleton = self;
_mediaKeyAppList = [NSMutableArray new]; _mediaKeyAppList = [NSMutableArray new];
_blackListBundleIdentifiers = [[NSUserDefaults standardUserDefaults]
arrayForKey:kMediaKeyUsingBlackListBundleIdentifiersDefaultsKey];
_tapThreadRL=nil; _tapThreadRL=nil;
_eventPort=nil; _eventPort=nil;
_eventPortSource=nil; _eventPortSource=nil;
@ -44,11 +46,11 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
// Listen to "app switched" event, so that we don't intercept media keys if we // 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 // weren't the last "media key listening" app to be active
EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched }; EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, (__bridge void *)(self), &_app_switching_ref); OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, (__bridge void*)self, &_app_switching_ref);
assert(err == noErr); assert(err == noErr);
eventType.eventKind = kEventAppTerminated; eventType.eventKind = kEventAppTerminated;
err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, (__bridge void *)(self), &_app_terminating_ref); err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, (__bridge void*)self, &_app_terminating_ref);
assert(err == noErr); assert(err == noErr);
} }
-(void)stopWatchingAppSwitching; -(void)stopWatchingAppSwitching;
@ -64,13 +66,14 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
[self setShouldInterceptMediaKeyEvents:YES]; [self setShouldInterceptMediaKeyEvents:YES];
@synchronized(self){
// Add an event tap to intercept the system defined media key events // Add an event tap to intercept the system defined media key events
_eventPort = CGEventTapCreate(kCGSessionEventTap, _eventPort = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionDefault, kCGEventTapOptionDefault,
CGEventMaskBit(NX_SYSDEFINED), CGEventMaskBit(NX_SYSDEFINED),
tapEventCallback, tapEventCallback,
(__bridge void *)(self)); (__bridge void*)self);
assert(_eventPort != NULL); assert(_eventPort != NULL);
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0); _eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
@ -78,25 +81,28 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
// Let's do this in a separate thread so that a slow app doesn't lag the event tap // 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]; [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
}
} }
-(void)stopWatchingMediaKeys; -(void)stopWatchingMediaKeys;
{ {
// TODO<nevyn>: Shut down thread, remove event tap port and source // TODO<nevyn>: Shut down thread, remove event tap port and source
if(_tapThreadRL){ @synchronized(self) {
if (_tapThreadRL) {
CFRunLoopStop(_tapThreadRL); CFRunLoopStop(_tapThreadRL);
_tapThreadRL=nil; _tapThreadRL = nil;
} }
if(_eventPort){ if (_eventPort) {
CFMachPortInvalidate(_eventPort); CFMachPortInvalidate(_eventPort);
CFRelease(_eventPort); CFRelease(_eventPort);
_eventPort=nil; _eventPort = nil;
} }
if(_eventPortSource){ if (_eventPortSource) {
CFRelease(_eventPortSource); CFRelease(_eventPortSource);
_eventPortSource=nil; _eventPortSource = nil;
}
} }
} }
@ -119,7 +125,7 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers; + (NSArray*)defaultMediaKeyUserBundleIdentifiers;
{ {
return [NSArray arrayWithObjects: return [NSArray arrayWithObjects:
[[NSBundle mainBundle] bundleIdentifier], // your app [[NSBundle mainBundle] bundleIdentifier],
@"com.spotify.client", @"com.spotify.client",
@"com.apple.iTunes", @"com.apple.iTunes",
@"com.apple.QuickTimePlayerX", @"com.apple.QuickTimePlayerX",
@ -141,11 +147,23 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
@"com.Timenut.SongKey", @"com.Timenut.SongKey",
@"com.macromedia.fireworks", // the tap messes up their mouse input @"com.macromedia.fireworks", // the tap messes up their mouse input
@"at.justp.Theremin", @"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", //BeardedSpice
@"com.plug.Plug",
@"com.plug.Plug2",
@"com.netease.163music",
@"com.coppertino.Vox",
@"com.tidal.desktop",
@"com.amazon.music",
nil nil
]; ];
} }
-(BOOL)shouldInterceptMediaKeyEvents; -(BOOL)shouldInterceptMediaKeyEvents;
{ {
BOOL shouldIntercept = NO; BOOL shouldIntercept = NO;
@ -157,7 +175,11 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
-(void)pauseTapOnTapThread:(BOOL)yeahno; -(void)pauseTapOnTapThread:(BOOL)yeahno;
{ {
@synchronized(self){
if (self->_eventPort) {
CGEventTapEnable(self->_eventPort, yeahno); CGEventTapEnable(self->_eventPort, yeahno);
}
}
} }
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting; -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
{ {
@ -170,7 +192,7 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
id grab = [self grab]; id grab = [self grab];
[grab pauseTapOnTapThread:newSetting]; [grab pauseTapOnTapThread:newSetting];
NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO]; NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes); CFRunLoopAddTimer(_tapThreadRL, (__bridge CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
} }
} }
@ -182,11 +204,15 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{ {
SPMediaKeyTap *self = (__bridge SPMediaKeyTap *)(refcon); SPMediaKeyTap *self = (__bridge id)refcon;
if(type == kCGEventTapDisabledByTimeout) { if(type == kCGEventTapDisabledByTimeout) {
NSLog(@"Media key event tap was disabled by timeout"); NSLog(@"Media key event tap was disabled by timeout");
@synchronized(self){
if (self->_eventPort) {
CGEventTapEnable(self->_eventPort, TRUE); CGEventTapEnable(self->_eventPort, TRUE);
}
}
return event; return event;
} else if(type == kCGEventTapDisabledByUserInput) { } else if(type == kCGEventTapDisabledByUserInput) {
// Was disabled manually by -[pauseTapOnTapThread] // Was disabled manually by -[pauseTapOnTapThread]
@ -202,11 +228,25 @@ static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGE
return event; return event;
} }
// Passthrough marker found
if (nsEvent.data2 == SPPassthroughEventData2Value) {
return event;
}
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys) if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
return event; return event;
int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); 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)
if (keyCode != NX_KEYTYPE_PLAY
&& keyCode != NX_KEYTYPE_FAST
&& keyCode != NX_KEYTYPE_REWIND
&& keyCode != NX_KEYTYPE_PREVIOUS
&& keyCode != NX_KEYTYPE_NEXT
&& keyCode != NX_KEYTYPE_MUTE
&& keyCode != NX_KEYTYPE_SOUND_UP
&& keyCode != NX_KEYTYPE_SOUND_DOWN
)
return event; return event;
if (![self shouldInterceptMediaKeyEvents]) if (![self shouldInterceptMediaKeyEvents])
@ -228,17 +268,25 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event]; [_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
} }
- (void)eventTapThread;
-(void)eventTapThread;
{ {
@synchronized(self) {
if (_eventPortSource) {
_tapThreadRL = CFRunLoopGetCurrent(); _tapThreadRL = CFRunLoopGetCurrent();
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
if (_tapThreadRL) {
CFRunLoopAddSource(_tapThreadRL, _eventPortSource,
kCFRunLoopCommonModes);
}
}
}
CFRunLoopRun(); CFRunLoopRun();
} }
#pragma mark Task switching callbacks #pragma mark Task switching callbacks
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys"; NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
NSString *kMediaKeyUsingBlackListBundleIdentifiersDefaultsKey = @"SPApplicationsNotNeedingMediaKeys";
NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys"; NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys";
@ -268,24 +316,25 @@ NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys";
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; [self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
} }
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn; - (void)appIsNowFrontmost:(ProcessSerialNumber)psn;
{ {
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; NSValue *psnv =
[NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
CFDictionaryRef cfDict = ProcessInformationCopyDictionary( NSDictionary *processInfo = CFBridgingRelease(ProcessInformationCopyDictionary(
&psn, &psn, kProcessDictionaryIncludeAllInformationMask));
kProcessDictionaryIncludeAllInformationMask NSString *bundleIdentifier =
); [processInfo objectForKey:(id)kCFBundleIdentifierKey];
if (!cfDict) return; if ([self.blackListBundleIdentifiers containsObject:bundleIdentifier]) {
NSLog(@"Media key event tap was activated by blacklist");
NSDictionary *processInfo = (__bridge id)cfDict; [self setShouldInterceptMediaKeyEvents:YES];
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey]; return;
}
CFRelease(cfDict); NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults]
arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey]; if (![whitelistIdentifiers containsObject:bundleIdentifier])
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return; return;
[_mediaKeyAppList removeObject:psnv]; [_mediaKeyAppList removeObject:psnv];
[_mediaKeyAppList insertObject:psnv atIndex:0]; [_mediaKeyAppList insertObject:psnv atIndex:0];