diff --git a/README.md b/README.md index ae8c64f..4a4c0df 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ mac-spotify-rcd =============== -Makes the playback control keys on a MacBook Pro open spotify instead of iTunes. +Makes the playback control keys on a MacBook Pro open Spotify instead of iTunes. + +## :warning: Warning + +This project hasn't worked since High Sierra. This branch includes a partial +workaround that works on Catalina, but the instructions for installation are +outdated and I haven't tested the changes thoroughly. If you want to try your +luck, you can use this branch to patch the `com.apple.mediaremoted` launch +daemon instead of `com.apple.rcd`. ## Installation diff --git a/SpotifyRCD.xcodeproj/project.pbxproj b/SpotifyRCD.xcodeproj/project.pbxproj index 2455726..810c015 100644 --- a/SpotifyRCD.xcodeproj/project.pbxproj +++ b/SpotifyRCD.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 76491CB918E0BCB800803BC4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76491CB818E0BCB800803BC4 /* Cocoa.framework */; }; 76491CC318E0BCB800803BC4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 76491CC118E0BCB800803BC4 /* InfoPlist.strings */; }; - 7686988419D392BB00BBECD9 /* next.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7686988019D392BB00BBECD9 /* next.wav */; }; - 7686988519D392BB00BBECD9 /* pause.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7686988119D392BB00BBECD9 /* pause.wav */; }; - 7686988619D392BB00BBECD9 /* play.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7686988219D392BB00BBECD9 /* play.wav */; }; - 7686988719D392BB00BBECD9 /* prev.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7686988319D392BB00BBECD9 /* prev.wav */; }; - 76EF46C618E0C6480025394C /* NSAppleScript+SpotifyRCD.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EF46C518E0C6480025394C /* NSAppleScript+SpotifyRCD.m */; }; + 76EF46C618E0C6480025394C /* MRDRemoteControlServer+SpotifyRCD.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EF46C518E0C6480025394C /* MRDRemoteControlServer+SpotifyRCD.m */; }; 76EF46C918E0C6660025394C /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EF46C818E0C6660025394C /* JRSwizzle.m */; }; /* End PBXBuildFile section */ @@ -26,12 +22,7 @@ 76491CC018E0BCB800803BC4 /* SpotifyRCD-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SpotifyRCD-Info.plist"; sourceTree = ""; }; 76491CC218E0BCB800803BC4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 76491CC418E0BCB800803BC4 /* SpotifyRCD-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SpotifyRCD-Prefix.pch"; sourceTree = ""; }; - 7686988019D392BB00BBECD9 /* next.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = next.wav; sourceTree = ""; }; - 7686988119D392BB00BBECD9 /* pause.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = pause.wav; sourceTree = ""; }; - 7686988219D392BB00BBECD9 /* play.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = play.wav; sourceTree = ""; }; - 7686988319D392BB00BBECD9 /* prev.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = prev.wav; sourceTree = ""; }; - 76EF46C418E0C6480025394C /* NSAppleScript+SpotifyRCD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAppleScript+SpotifyRCD.h"; sourceTree = ""; }; - 76EF46C518E0C6480025394C /* NSAppleScript+SpotifyRCD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAppleScript+SpotifyRCD.m"; sourceTree = ""; }; + 76EF46C518E0C6480025394C /* MRDRemoteControlServer+SpotifyRCD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MRDRemoteControlServer+SpotifyRCD.m"; sourceTree = ""; }; 76EF46C718E0C6660025394C /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = ""; }; 76EF46C818E0C6660025394C /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -87,10 +78,8 @@ 76491CBE18E0BCB800803BC4 /* SpotifyRCD */ = { isa = PBXGroup; children = ( - 7686987F19D392BB00BBECD9 /* Sounds */, 76491CBF18E0BCB800803BC4 /* Supporting Files */, - 76EF46C418E0C6480025394C /* NSAppleScript+SpotifyRCD.h */, - 76EF46C518E0C6480025394C /* NSAppleScript+SpotifyRCD.m */, + 76EF46C518E0C6480025394C /* MRDRemoteControlServer+SpotifyRCD.m */, ); path = SpotifyRCD; sourceTree = ""; @@ -107,18 +96,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 7686987F19D392BB00BBECD9 /* Sounds */ = { - isa = PBXGroup; - children = ( - 7686988019D392BB00BBECD9 /* next.wav */, - 7686988119D392BB00BBECD9 /* pause.wav */, - 7686988219D392BB00BBECD9 /* play.wav */, - 7686988319D392BB00BBECD9 /* prev.wav */, - ); - name = Sounds; - path = sounds; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -145,15 +122,16 @@ 76491CAD18E0BCB800803BC4 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 1140; ORGANIZATIONNAME = "Inline-Studios"; }; buildConfigurationList = 76491CB018E0BCB800803BC4 /* Build configuration list for PBXProject "SpotifyRCD" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 76491CAC18E0BCB800803BC4; productRefGroup = 76491CB618E0BCB800803BC4 /* Products */; @@ -170,11 +148,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7686988719D392BB00BBECD9 /* prev.wav in Resources */, 76491CC318E0BCB800803BC4 /* InfoPlist.strings in Resources */, - 7686988419D392BB00BBECD9 /* next.wav in Resources */, - 7686988619D392BB00BBECD9 /* play.wav in Resources */, - 7686988519D392BB00BBECD9 /* pause.wav in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -186,7 +160,7 @@ buildActionMask = 2147483647; files = ( 76EF46C918E0C6660025394C /* JRSwizzle.m in Sources */, - 76EF46C618E0C6480025394C /* NSAppleScript+SpotifyRCD.m in Sources */, + 76EF46C618E0C6480025394C /* MRDRemoteControlServer+SpotifyRCD.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -212,18 +186,32 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -250,19 +238,32 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -282,6 +283,7 @@ GCC_PREFIX_HEADER = "SpotifyRCD/SpotifyRCD-Prefix.pch"; INFOPLIST_FILE = "SpotifyRCD/SpotifyRCD-Info.plist"; MACH_O_TYPE = mh_dylib; + PRODUCT_BUNDLE_IDENTIFIER = me.ryanp.mac.inject.SpotifyRCD; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = bundle; }; @@ -295,6 +297,7 @@ GCC_PREFIX_HEADER = "SpotifyRCD/SpotifyRCD-Prefix.pch"; INFOPLIST_FILE = "SpotifyRCD/SpotifyRCD-Info.plist"; MACH_O_TYPE = mh_dylib; + PRODUCT_BUNDLE_IDENTIFIER = me.ryanp.mac.inject.SpotifyRCD; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = bundle; }; diff --git a/SpotifyRCD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SpotifyRCD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SpotifyRCD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SpotifyRCD/JRSwizzle.h b/SpotifyRCD/JRSwizzle.h index 7d29bc2..f8a0aaf 100755 --- a/SpotifyRCD/JRSwizzle.h +++ b/SpotifyRCD/JRSwizzle.h @@ -1,6 +1,6 @@ -// JRSwizzle.h semver:1.0 -// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com -// Some rights reserved: http://opensource.org/licenses/MIT +// JRSwizzle.h semver:1.1.0 +// Copyright (c) 2007-2016 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit // https://github.com/rentzsch/jrswizzle #import @@ -10,4 +10,39 @@ + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; + +/** + ``` + __block NSInvocation *invocation = nil; + invocation = [self jr_swizzleMethod:@selector(initWithCoder:) withBlock:^(id obj, NSCoder *coder) { + NSLog(@"before %@, coder %@", obj, coder); + + [invocation setArgument:&coder atIndex:2]; + [invocation invokeWithTarget:obj]; + + id ret = nil; + [invocation getReturnValue:&ret]; + + NSLog(@"after %@, coder %@", obj, coder); + + return ret; + } error:nil]; + ``` + */ ++ (NSInvocation*)jr_swizzleMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error; + +/** + ``` + __block NSInvocation *classInvocation = nil; + classInvocation = [self jr_swizzleClassMethod:@selector(test) withBlock:^() { + NSLog(@"before"); + + [classInvocation invoke]; + + NSLog(@"after"); + } error:nil]; + ``` + */ ++ (NSInvocation*)jr_swizzleClassMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error; + @end diff --git a/SpotifyRCD/JRSwizzle.m b/SpotifyRCD/JRSwizzle.m index 4e582bf..bb02cb3 100755 --- a/SpotifyRCD/JRSwizzle.m +++ b/SpotifyRCD/JRSwizzle.m @@ -1,134 +1,166 @@ -// JRSwizzle.m semver:1.0 -// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com -// Some rights reserved: http://opensource.org/licenses/MIT +// JRSwizzle.m semver:1.1.0 +// Copyright (c) 2007-2016 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit // https://github.com/rentzsch/jrswizzle #import "JRSwizzle.h" #if TARGET_OS_IPHONE - #import - #import + #import + #import #else - #import + #import #endif -#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ - if (ERROR_VAR) { \ - NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ - *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ - code:-1 \ - userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ - } +#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ + if (ERROR_VAR) { \ + NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ + *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ + code:-1 \ + userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ + } #define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__) #if OBJC_API_VERSION >= 2 -#define GetClass(obj) object_getClass(obj) +#define GetClass(obj) object_getClass(obj) #else -#define GetClass(obj) (obj ? obj->isa : Nil) +#define GetClass(obj) (obj ? obj->isa : Nil) #endif @implementation NSObject (JRSwizzle) + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ { #if OBJC_API_VERSION >= 2 - Method origMethod = class_getInstanceMethod(self, origSel_); - if (!origMethod) { + Method origMethod = class_getInstanceMethod(self, origSel_); + if (!origMethod) { #if TARGET_OS_IPHONE - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); #else - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); #endif - return NO; - } - - Method altMethod = class_getInstanceMethod(self, altSel_); - if (!altMethod) { + return NO; + } + + Method altMethod = class_getInstanceMethod(self, altSel_); + if (!altMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); +#else + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); +#endif + return NO; + } + + class_addMethod(self, + origSel_, + class_getMethodImplementation(self, origSel_), + method_getTypeEncoding(origMethod)); + class_addMethod(self, + altSel_, + class_getMethodImplementation(self, altSel_), + method_getTypeEncoding(altMethod)); + + method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); + return YES; +#else + // Scan for non-inherited methods. + Method directOriginalMethod = NULL, directAlternateMethod = NULL; + + void *iterator = NULL; + struct objc_method_list *mlist = class_nextMethodList(self, &iterator); + while (mlist) { + int method_index = 0; + for (; method_index < mlist->method_count; method_index++) { + if (mlist->method_list[method_index].method_name == origSel_) { + assert(!directOriginalMethod); + directOriginalMethod = &mlist->method_list[method_index]; + } + if (mlist->method_list[method_index].method_name == altSel_) { + assert(!directAlternateMethod); + directAlternateMethod = &mlist->method_list[method_index]; + } + } + mlist = class_nextMethodList(self, &iterator); + } + + // If either method is inherited, copy it up to the target class to make it non-inherited. + if (!directOriginalMethod || !directAlternateMethod) { + Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; + if (!directOriginalMethod) { + inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); + if (!inheritedOriginalMethod) { #if TARGET_OS_IPHONE - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); #else - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); #endif - return NO; - } - - class_addMethod(self, - origSel_, - class_getMethodImplementation(self, origSel_), - method_getTypeEncoding(origMethod)); - class_addMethod(self, - altSel_, - class_getMethodImplementation(self, altSel_), - method_getTypeEncoding(altMethod)); - - method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); - return YES; + return NO; + } + } + if (!directAlternateMethod) { + inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); + if (!inheritedAlternateMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); #else - // Scan for non-inherited methods. - Method directOriginalMethod = NULL, directAlternateMethod = NULL; - - void *iterator = NULL; - struct objc_method_list *mlist = class_nextMethodList(self, &iterator); - while (mlist) { - int method_index = 0; - for (; method_index < mlist->method_count; method_index++) { - if (mlist->method_list[method_index].method_name == origSel_) { - assert(!directOriginalMethod); - directOriginalMethod = &mlist->method_list[method_index]; - } - if (mlist->method_list[method_index].method_name == altSel_) { - assert(!directAlternateMethod); - directAlternateMethod = &mlist->method_list[method_index]; - } - } - mlist = class_nextMethodList(self, &iterator); - } - - // If either method is inherited, copy it up to the target class to make it non-inherited. - if (!directOriginalMethod || !directAlternateMethod) { - Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; - if (!directOriginalMethod) { - inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); - if (!inheritedOriginalMethod) { - SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); - return NO; - } - } - if (!directAlternateMethod) { - inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); - if (!inheritedAlternateMethod) { - SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); - return NO; - } - } - - int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; - struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); - hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind - hoisted_method_list->method_count = hoisted_method_count; - Method hoisted_method = hoisted_method_list->method_list; - - if (!directOriginalMethod) { - bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); - directOriginalMethod = hoisted_method++; - } - if (!directAlternateMethod) { - bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); - directAlternateMethod = hoisted_method; - } - class_addMethods(self, hoisted_method_list); - } - - // Swizzle. - IMP temp = directOriginalMethod->method_imp; - directOriginalMethod->method_imp = directAlternateMethod->method_imp; - directAlternateMethod->method_imp = temp; - - return YES; + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); +#endif + return NO; + } + } + + int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; + struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); + hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind + hoisted_method_list->method_count = hoisted_method_count; + Method hoisted_method = hoisted_method_list->method_list; + + if (!directOriginalMethod) { + bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); + directOriginalMethod = hoisted_method++; + } + if (!directAlternateMethod) { + bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); + directAlternateMethod = hoisted_method; + } + class_addMethods(self, hoisted_method_list); + } + + // Swizzle. + IMP temp = directOriginalMethod->method_imp; + directOriginalMethod->method_imp = directAlternateMethod->method_imp; + directAlternateMethod->method_imp = temp; + + return YES; #endif } + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ { - return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; + return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; +} + ++ (NSInvocation*)jr_swizzleMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error { + IMP blockIMP = imp_implementationWithBlock(block); + NSString *blockSelectorString = [NSString stringWithFormat:@"_jr_block_%@_%p", NSStringFromSelector(origSel), block]; + SEL blockSel = sel_registerName([blockSelectorString cStringUsingEncoding:NSUTF8StringEncoding]); + Method origSelMethod = class_getInstanceMethod(self, origSel); + const char* origSelMethodArgs = method_getTypeEncoding(origSelMethod); + class_addMethod(self, blockSel, blockIMP, origSelMethodArgs); + + NSMethodSignature *origSig = [NSMethodSignature signatureWithObjCTypes:origSelMethodArgs]; + NSInvocation *origInvocation = [NSInvocation invocationWithMethodSignature:origSig]; + origInvocation.selector = blockSel; + + [self jr_swizzleMethod:origSel withMethod:blockSel error:nil]; + + return origInvocation; +} + ++ (NSInvocation*)jr_swizzleClassMethod:(SEL)origSel withBlock:(id)block error:(NSError**)error { + NSInvocation *invocation = [GetClass((id)self) jr_swizzleMethod:origSel withBlock:block error:error]; + invocation.target = self; + + return invocation; } @end diff --git a/SpotifyRCD/MRDRemoteControlServer+SpotifyRCD.m b/SpotifyRCD/MRDRemoteControlServer+SpotifyRCD.m new file mode 100644 index 0000000..f6b55b2 --- /dev/null +++ b/SpotifyRCD/MRDRemoteControlServer+SpotifyRCD.m @@ -0,0 +1,63 @@ +// +// MRDRemoteControlServer+SpotifyRCD.m +// SpotifyRCD +// +// Created by Ryan Pendleton on 5/15/2020. +// Copyright (c) 2020 Ryan Pendleton. All rights reserved. +// + +#import +#import + +#pragma mark - MRDRemoteControlServer + +@interface NSObject (SpotifyRCD_MRDRemoteControlServer_Private) + +- (void)_enqueueCommand:(id)command forApplication:(NSString *)application withCompletion:(id)completion; + +@end + +@interface NSObject (SpotifyRCD_MRDRemoteControlServer_Tweak) + +- (void)rkp__enqueueCommand:(id)command forApplication:(NSString *)application withCompletion:(id)completion; + +@end + +@implementation NSObject (SpotifyRCD_MRDRemoteControlServer_Tweak) + +- (void)rkp__enqueueCommand:(id)command forApplication:(NSString *)application withCompletion:(id)completion { + NSLog(@"command (%@) = %@", [command class], command); + NSLog(@"application (%@) = %@", [application class], application); + NSLog(@"completion (%@) = %@", [completion class], completion); + + if ([application isEqualToString:@"com.apple.Music"] && [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.Music"] count] == 0) { + NSLog(@"changing application to Spotify"); + application = @"com.spotify.client"; + } + + [self rkp__enqueueCommand:command forApplication:application withCompletion:completion]; +} + +@end + +#pragma mark - + +@interface NSObject (SpotifyRCD_Loader) +@end + +@implementation NSObject (SpotifyRCD_Loader) + ++ (void)load +{ + unsetenv("DYLD_INSERT_LIBRARIES"); + NSLog(@"SpotifyRCD loaded"); + + Class MRDRemoteControlServer = NSClassFromString(@"MRDRemoteControlServer"); + + NSError *error = nil; + + [MRDRemoteControlServer jr_swizzleMethod:@selector(_enqueueCommand:forApplication:withCompletion:) withMethod:@selector(rkp__enqueueCommand:forApplication:withCompletion:) error:&error]; + if (error) NSLog(@"Error swizzling enqueue command method: %@", error.localizedDescription); +} + +@end diff --git a/SpotifyRCD/NSAppleScript+SpotifyRCD.h b/SpotifyRCD/NSAppleScript+SpotifyRCD.h deleted file mode 100644 index 29b43aa..0000000 --- a/SpotifyRCD/NSAppleScript+SpotifyRCD.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// NSAppleScript+SpotifyRCD.h -// SpotifyRCD -// -// Created by Ryan Pendleton on 3/24/14. -// Copyright (c) 2014 Inline-Studios. All rights reserved. -// - -#import - -@interface NSAppleScript (SpotifyRCD) - -@end diff --git a/SpotifyRCD/NSAppleScript+SpotifyRCD.m b/SpotifyRCD/NSAppleScript+SpotifyRCD.m deleted file mode 100644 index 8391b4b..0000000 --- a/SpotifyRCD/NSAppleScript+SpotifyRCD.m +++ /dev/null @@ -1,32 +0,0 @@ -// -// NSAppleScript+SpotifyRCD.m -// SpotifyRCD -// -// Created by Ryan Pendleton on 3/24/14. -// Copyright (c) 2014 Inline-Studios. All rights reserved. -// - -#import "NSAppleScript+SpotifyRCD.h" -#import - -@implementation NSAppleScript (SpotifyRCD) - -+ (void)load -{ - NSLog(@"SpotifyRCD loaded"); - - unsetenv("DYLD_INSERT_LIBRARIES"); - [self jr_swizzleMethod:@selector(initWithSource:) withMethod:@selector(is_initWithSource:) error:nil]; -} - -- (id)is_initWithSource:(NSString *)source -{ - if([source isEqualToString:@"tell application id \"com.apple.iTunes\" to launch"]) - { - source = @"tell application id \"com.spotify.client\" to launch"; - } - - return [self is_initWithSource:source]; -} - -@end