Update SPMediaKeyTap to a more recent version.

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

View File

@ -11,68 +11,66 @@
@implementation SPInvocationGrabber @implementation SPInvocationGrabber
- (id)initWithObject:(id)obj; - (id)initWithObject:(id)obj;
{ {
return [self initWithObject:obj stacktraceSaving:YES]; return [self initWithObject:obj stacktraceSaving:YES];
} }
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack; -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
{ {
self.object = obj; self.object = obj;
if(saveStack) if(saveStack)
[self saveBacktrace]; [self saveBacktrace];
return self; return self;
} }
-(void)dealloc; -(void)dealloc;
{ {
free(frameStrings); free(frameStrings);
self.object = nil; self.object = nil;
self.invocation = nil; self.invocation = nil;
} }
@synthesize invocation = _invocation, object = _object; @synthesize invocation = _invocation, object = _object;
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone; @synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
- (void)runInBackground; - (void)runInBackground;
{ {
@autoreleasepool { [self invoke];
[self invoke];
}
} }
- (void)forwardInvocation:(NSInvocation *)anInvocation { - (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation retainArguments]; [anInvocation retainArguments];
anInvocation.target = _object; anInvocation.target = _object;
self.invocation = anInvocation; self.invocation = anInvocation;
if(backgroundAfterForward) if(backgroundAfterForward)
[NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil];
else if(onMainAfterForward) else if(onMainAfterForward)
[self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone]; [self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone];
} }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector { - (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:inSelector]; NSMethodSignature *signature = [super methodSignatureForSelector:inSelector];
if (signature == NULL) if (signature == NULL)
signature = [_object methodSignatureForSelector:inSelector]; signature = [_object methodSignatureForSelector:inSelector];
return signature; return signature;
} }
- (void)invoke; - (void)invoke;
{ {
@try { @try {
[_invocation invoke]; [_invocation invoke];
} }
@catch (NSException * e) { @catch (NSException * e) {
NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e); NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e);
[self printBacktrace]; [self printBacktrace];
printf("\n"); printf("\n");
[e raise]; [e raise];
} }
self.invocation = nil; self.invocation = nil;
self.object = nil; self.object = nil;
} }
-(void)saveBacktrace; -(void)saveBacktrace;
@ -83,40 +81,40 @@
} }
-(void)printBacktrace; -(void)printBacktrace;
{ {
for(int x = 3; x < frameCount; x++) { for(int x = 3; x < frameCount; x++) {
if(frameStrings[x] == NULL) { break; } if(frameStrings[x] == NULL) { break; }
printf("%s\n", frameStrings[x]); printf("%s\n", frameStrings[x]);
} }
} }
@end @end
@implementation NSObject (SPInvocationGrabbing) @implementation NSObject (SPInvocationGrabbing)
-(id)grab; -(id)grab;
{ {
return [[SPInvocationGrabber alloc] initWithObject:self]; return [[SPInvocationGrabber alloc] initWithObject:self];
} }
-(id)invokeAfter:(NSTimeInterval)delta; -(id)invokeAfter:(NSTimeInterval)delta;
{ {
id grabber = [self grab]; id grabber = [self grab];
[NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO]; [NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO];
return grabber; return grabber;
} }
- (id)nextRunloop; - (id)nextRunloop;
{ {
return [self invokeAfter:0]; return [self invokeAfter:0];
} }
-(id)inBackground; -(id)inBackground;
{ {
SPInvocationGrabber *grabber = [self grab]; SPInvocationGrabber *grabber = [self grab];
grabber.backgroundAfterForward = YES; grabber.backgroundAfterForward = YES;
return grabber; return grabber;
} }
-(id)onMainAsync:(BOOL)async; -(id)onMainAsync:(BOOL)async;
{ {
SPInvocationGrabber *grabber = [self grab]; SPInvocationGrabber *grabber = [self grab];
grabber.onMainAfterForward = YES; grabber.onMainAfterForward = YES;
grabber.waitUntilDone = !async; grabber.waitUntilDone = !async;
return grabber; return grabber;
} }
@end @end

View File

@ -4,18 +4,19 @@
// 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;
EventHandlerRef _app_terminating_ref; EventHandlerRef _app_terminating_ref;
CFMachPortRef _eventPort; CFMachPortRef _eventPort;
CFRunLoopSourceRef _eventPortSource; CFRunLoopSourceRef _eventPortSource;
CFRunLoopRef _tapThreadRL; CFRunLoopRef _tapThreadRL;
BOOL _shouldInterceptMediaKeyEvents; BOOL _shouldInterceptMediaKeyEvents;
id _delegate; id _delegate;
// The app that is frontmost in this list owns media keys // The app that is frontmost in this list owns media keys
NSMutableArray *_mediaKeyAppList; NSMutableArray *_mediaKeyAppList;
} }
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers; + (NSArray*)defaultMediaKeyUserBundleIdentifiers;
@ -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,8 +40,9 @@ extern "C" {
#endif #endif
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;
extern NSString *kMediaKeyUsingBlackListBundleIdentifiersDefaultsKey;
extern NSString *kIgnoreMediaKeysDefaultsKey; extern NSString *kIgnoreMediaKeysDefaultsKey;
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

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;
@ -24,79 +24,85 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
#pragma mark Setup and teardown #pragma mark Setup and teardown
-(id)initWithDelegate:(id)delegate; -(id)initWithDelegate:(id)delegate;
{ {
_delegate = delegate; _delegate = delegate;
[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;
return self; return self;
} }
-(void)dealloc; -(void)dealloc;
{ {
[self stopWatchingMediaKeys]; [self stopWatchingMediaKeys];
[self stopWatchingAppSwitching]; [self stopWatchingAppSwitching];
} }
-(void)startWatchingAppSwitching; -(void)startWatchingAppSwitching;
{ {
// 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;
{ {
if(!_app_switching_ref) return; if(!_app_switching_ref) return;
RemoveEventHandler(_app_switching_ref); RemoveEventHandler(_app_switching_ref);
_app_switching_ref = NULL; _app_switching_ref = NULL;
} }
-(void)startWatchingMediaKeys;{ -(void)startWatchingMediaKeys;{
// Prevent having multiple mediaKeys threads // Prevent having multiple mediaKeys threads
[self stopWatchingMediaKeys]; [self stopWatchingMediaKeys];
[self setShouldInterceptMediaKeyEvents:YES]; [self setShouldInterceptMediaKeyEvents:YES];
// Add an event tap to intercept the system defined media key events @synchronized(self){
_eventPort = CGEventTapCreate(kCGSessionEventTap, // Add an event tap to intercept the system defined media key events
kCGHeadInsertEventTap, _eventPort = CGEventTapCreate(kCGSessionEventTap,
kCGEventTapOptionDefault, kCGHeadInsertEventTap,
CGEventMaskBit(NX_SYSDEFINED), kCGEventTapOptionDefault,
tapEventCallback, CGEventMaskBit(NX_SYSDEFINED),
(__bridge void *)(self)); tapEventCallback,
assert(_eventPort != NULL); (__bridge void*)self);
assert(_eventPort != NULL);
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0); _eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
assert(_eventPortSource != NULL); assert(_eventPortSource != NULL);
// 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) {
CFRunLoopStop(_tapThreadRL); if (_tapThreadRL) {
_tapThreadRL=nil; CFRunLoopStop(_tapThreadRL);
} _tapThreadRL = nil;
}
if(_eventPort){
CFMachPortInvalidate(_eventPort); if (_eventPort) {
CFRelease(_eventPort); CFMachPortInvalidate(_eventPort);
_eventPort=nil; CFRelease(_eventPort);
} _eventPort = nil;
}
if(_eventPortSource){
CFRelease(_eventPortSource); if (_eventPortSource) {
_eventPortSource=nil; CFRelease(_eventPortSource);
_eventPortSource = nil;
}
} }
} }
@ -106,75 +112,91 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv
+(BOOL)usesGlobalMediaKeyTap +(BOOL)usesGlobalMediaKeyTap
{ {
#ifdef _DEBUG #ifdef _DEBUG
// breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot // breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot
return NO; return NO;
#else #else
// XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy. // XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
return return
![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey] ![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey]
&& floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/; && floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
#endif #endif
} }
+ (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",
@"com.apple.quicktimeplayer", @"com.apple.quicktimeplayer",
@"com.apple.iWork.Keynote", @"com.apple.iWork.Keynote",
@"com.apple.iPhoto", @"com.apple.iPhoto",
@"org.videolan.vlc", @"org.videolan.vlc",
@"com.apple.Aperture", @"com.apple.Aperture",
@"com.plexsquared.Plex", @"com.plexsquared.Plex",
@"com.soundcloud.desktop", @"com.soundcloud.desktop",
@"org.niltsh.MPlayerX", @"org.niltsh.MPlayerX",
@"com.ilabs.PandorasHelper", @"com.ilabs.PandorasHelper",
@"com.mahasoftware.pandabar", @"com.mahasoftware.pandabar",
@"com.bitcartel.pandorajam", @"com.bitcartel.pandorajam",
@"org.clementine-player.clementine", @"org.clementine-player.clementine",
@"fm.last.Last.fm", @"fm.last.Last.fm",
@"fm.last.Scrobbler", @"fm.last.Scrobbler",
@"com.beatport.BeatportPro", @"com.beatport.BeatportPro",
@"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",
nil @"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
];
} }
-(BOOL)shouldInterceptMediaKeyEvents; -(BOOL)shouldInterceptMediaKeyEvents;
{ {
BOOL shouldIntercept = NO; BOOL shouldIntercept = NO;
@synchronized(self) { @synchronized(self) {
shouldIntercept = _shouldInterceptMediaKeyEvents; shouldIntercept = _shouldInterceptMediaKeyEvents;
} }
return shouldIntercept; return shouldIntercept;
} }
-(void)pauseTapOnTapThread:(BOOL)yeahno; -(void)pauseTapOnTapThread:(BOOL)yeahno;
{ {
CGEventTapEnable(self->_eventPort, yeahno); @synchronized(self){
if (self->_eventPort) {
CGEventTapEnable(self->_eventPort, yeahno);
}
}
} }
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting; -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
{ {
BOOL oldSetting; BOOL oldSetting;
@synchronized(self) { @synchronized(self) {
oldSetting = _shouldInterceptMediaKeyEvents; oldSetting = _shouldInterceptMediaKeyEvents;
_shouldInterceptMediaKeyEvents = newSetting; _shouldInterceptMediaKeyEvents = newSetting;
} }
if(_tapThreadRL && oldSetting != newSetting) { if(_tapThreadRL && oldSetting != newSetting) {
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);
} }
} }
#pragma mark #pragma mark
#pragma mark - #pragma mark -
#pragma mark Event tap callbacks #pragma mark Event tap callbacks
@ -182,152 +204,179 @@ 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");
CGEventTapEnable(self->_eventPort, TRUE); @synchronized(self){
return event; if (self->_eventPort) {
} else if(type == kCGEventTapDisabledByUserInput) { CGEventTapEnable(self->_eventPort, TRUE);
// Was disabled manually by -[pauseTapOnTapThread] }
return event; }
} return event;
NSEvent *nsEvent = nil; } else if(type == kCGEventTapDisabledByUserInput) {
@try { // Was disabled manually by -[pauseTapOnTapThread]
nsEvent = [NSEvent eventWithCGEvent:event]; return event;
} }
@catch (NSException * e) { NSEvent *nsEvent = nil;
NSLog(@"Strange CGEventType: %d: %@", type, e); @try {
assert(0); nsEvent = [NSEvent eventWithCGEvent:event];
return event; }
} @catch (NSException * e) {
NSLog(@"Strange CGEventType: %d: %@", type, e);
assert(0);
return event;
}
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys) // Passthrough marker found
return event; if (nsEvent.data2 == SPPassthroughEventData2Value) {
return event;
}
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
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)
return event; 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;
if (![self shouldInterceptMediaKeyEvents]) if (![self shouldInterceptMediaKeyEvents])
return event; return event;
[self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO]; [self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
return NULL; return NULL;
} }
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{ {
return tapEventCallback2(proxy, type, event, refcon); return tapEventCallback2(proxy, type, event, refcon);
} }
// event will have been retained in the other thread // event will have been retained in the other thread
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event { -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event]; [_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
} }
- (void)eventTapThread;
-(void)eventTapThread;
{ {
_tapThreadRL = CFRunLoopGetCurrent(); @synchronized(self) {
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes); if (_eventPortSource) {
CFRunLoopRun(); _tapThreadRL = CFRunLoopGetCurrent();
if (_tapThreadRL) {
CFRunLoopAddSource(_tapThreadRL, _eventPortSource,
kCFRunLoopCommonModes);
}
}
}
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";
-(void)mediaKeyAppListChanged; -(void)mediaKeyAppListChanged;
{ {
if([_mediaKeyAppList count] == 0) return; 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; /*NSLog(@"--");
OSErr err = SameProcess(&mySerial, &topSerial, &same); int i = 0;
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; 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; - (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;
NSDictionary *processInfo = (__bridge id)cfDict;
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
CFRelease(cfDict);
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey]; if ([self.blackListBundleIdentifiers containsObject:bundleIdentifier]) {
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return; NSLog(@"Media key event tap was activated by blacklist");
[self setShouldInterceptMediaKeyEvents:YES];
return;
}
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults]
arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
if (![whitelistIdentifiers containsObject:bundleIdentifier])
return;
[_mediaKeyAppList removeObject:psnv]; [_mediaKeyAppList removeObject:psnv];
[_mediaKeyAppList insertObject:psnv atIndex:0]; [_mediaKeyAppList insertObject:psnv atIndex:0];
[self mediaKeyAppListChanged]; [self mediaKeyAppListChanged];
} }
-(void)appTerminated:(ProcessSerialNumber)psn; -(void)appTerminated:(ProcessSerialNumber)psn;
{ {
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
[_mediaKeyAppList removeObject:psnv]; [_mediaKeyAppList removeObject:psnv];
[self mediaKeyAppListChanged]; [self mediaKeyAppListChanged];
} }
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData) static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{ {
SPMediaKeyTap *self = (__bridge id)userData; SPMediaKeyTap *self = (__bridge id)userData;
ProcessSerialNumber newSerial; ProcessSerialNumber newSerial;
GetFrontProcess(&newSerial); GetFrontProcess(&newSerial);
[self appIsNowFrontmost:newSerial]; [self appIsNowFrontmost:newSerial];
return CallNextEventHandler(nextHandler, evt); return CallNextEventHandler(nextHandler, evt);
} }
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData) static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{ {
SPMediaKeyTap *self = (__bridge id)userData; SPMediaKeyTap *self = (__bridge id)userData;
ProcessSerialNumber deadPSN;
GetEventParameter( ProcessSerialNumber deadPSN;
evt,
kEventParamProcessID,
typeProcessSerialNumber,
NULL,
sizeof(deadPSN),
NULL,
&deadPSN
);
GetEventParameter(
[self appTerminated:deadPSN]; evt,
kEventParamProcessID,
typeProcessSerialNumber,
NULL,
sizeof(deadPSN),
NULL,
&deadPSN
);
[self appTerminated:deadPSN];
return CallNextEventHandler(nextHandler, evt); return CallNextEventHandler(nextHandler, evt);
} }