From 0d54c9433dc2ea87a79ea7675f77ce06f4bb6bd3 Mon Sep 17 00:00:00 2001 From: Ryan Pendleton Date: Mon, 27 Jul 2020 00:43:31 -0600 Subject: [PATCH] add preliminary support for Catalina (issue #3) --- README.md | 8 + SpotifyRCD.xcodeproj/project.pbxproj | 69 ++--- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + SpotifyRCD/JRSwizzle.h | 41 ++- SpotifyRCD/JRSwizzle.m | 236 ++++++++++-------- .../MRDRemoteControlServer+SpotifyRCD.m | 63 +++++ SpotifyRCD/NSAppleScript+SpotifyRCD.h | 13 - SpotifyRCD/NSAppleScript+SpotifyRCD.m | 32 --- 8 files changed, 287 insertions(+), 183 deletions(-) create mode 100644 SpotifyRCD.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 SpotifyRCD/MRDRemoteControlServer+SpotifyRCD.m delete mode 100644 SpotifyRCD/NSAppleScript+SpotifyRCD.h delete mode 100644 SpotifyRCD/NSAppleScript+SpotifyRCD.m diff --git a/README.md b/README.md index 26e5a53..70f2fd7 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ On a standard installation of macOS, pressing the playback control keys on an Apple keyboard opens iTunes if no other media applications are open. The purpose of this project is to patch this behavior such that Spotify is opened instead. +## :warning: Catalina support is experimental + +This project hasn't worked since High Sierra. This branch is a work-in-progress +that adds support for Catalina, but the instructions and installer are outdated, +nor have I 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`. + ## :warning: Compatibility This is the README for the legacy version of the project. The legacy version is 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