├── .gitattributes ├── NowPlayingIconXVI.plist ├── packages └── com.nahtedetihw.nowplayingiconxvi_1.0_iphoneos-arm.deb ├── control ├── Makefile ├── ReadMe.md ├── NowPlayingIconXVI.h └── NowPlayingIconXVI.xm /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /NowPlayingIconXVI.plist: -------------------------------------------------------------------------------- 1 | { 2 | Filter = { 3 | Bundles = ( 4 | "com.apple.springboard", 5 | ); 6 | }; 7 | } -------------------------------------------------------------------------------- /packages/com.nahtedetihw.nowplayingiconxvi_1.0_iphoneos-arm.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nahtedetihw/NowPlayingIconXVI/HEAD/packages/com.nahtedetihw.nowplayingiconxvi_1.0_iphoneos-arm.deb -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.nahtedetihw.nowplayingiconxvi 2 | Name: NowPlayingIconXVI 3 | Version: 1.0 4 | Architecture: iphoneos-arm 5 | Description: Change your now playing app icon to the album artwork of currently playing music! 6 | Maintainer: Ethan Whited 7 | Author: Ethan Whited 8 | Section: Tweaks 9 | Depends: mobilesubstrate 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:latest:13.0 2 | INSTALL_TARGET_PROCESSES = SpringBoard 3 | ARCHS = arm64 arm64e 4 | SYSROOT = $(THEOS)/sdks/iPhoneOS14.2.sdk 5 | PREFIX=$(THEOS)/toolchain/Xcode.xctoolchain/usr/bin/ 6 | DEBUG = 0 7 | FINALPACKAGE = 1 8 | 9 | include $(THEOS)/makefiles/common.mk 10 | 11 | TWEAK_NAME = NowPlayingIconXVI 12 | 13 | NowPlayingIconXVI_FILES = NowPlayingIconXVI.xm 14 | NowPlayingIconXVI_CFLAGS = -fobjc-arc 15 | NowPlayingIconXVI_PRIVATE_FRAMEWORKS = MediaRemote 16 | 17 | include $(THEOS_MAKE_PATH)/tweak.mk 18 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

NowPlayingIconXVI

4 | 5 | 6 |

7 | 8 | ### Change your now playing app icon to the album artwork of currently playing music! 9 | * Tested on Apple Music, Pandora, Spotify 10 | 11 | * No options to configure, should work on iOS 13 - iOS 16 12 | 13 | 14 | Join my Discord today to report any issues: https://discord.gg/64kVRNzKnF 15 | 16 | ### Source Code [https://github.com/nahtedetihw/NowPlayingIconXVI](https://github.com/nahtedetihw/NowPlayingIconXVI) 17 | 18 | 19 | ### Follow 20 | 21 | [Twitter](https://twitter.com/ethanwhited) - follow me for more up to date info, or ask me anything. 22 | 23 | [Reddit](https://www.reddit.com/user/Nahtedetihw) - reach out if you have any questions. 24 | 25 | [Email](mailto:ethanwhited2208@gmail.com) - open to any questions or concerns. 26 | 27 | [Donate](https://paypal.me/nahtdetihw) - buy me a coffee if you like my work. 28 | 29 | [Repo](https://havoc.app) - Repo containing NowPlayingIconXVI 30 | -------------------------------------------------------------------------------- /NowPlayingIconXVI.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface __NSSingleObjectArrayI : NSArray 4 | @end 5 | 6 | @interface SBIcon : NSObject 7 | -(NSString *)applicationBundleID; 8 | -(id)application; 9 | -(id)parentFolderIcon; 10 | @end 11 | 12 | @interface SBIconView : UIView 13 | @end 14 | 15 | @interface SBIconImageView : UIView 16 | -(SBIcon *)icon; 17 | @end 18 | 19 | @interface SBIconImageCrossfadeView : UIView 20 | @end 21 | 22 | @interface SBApplication : NSObject 23 | @property (nonatomic, readonly) NSString *bundleIdentifier; 24 | @end 25 | 26 | @interface SBApplicationIcon : SBIcon 27 | @end 28 | 29 | @interface SBIconModel : NSObject 30 | -(SBApplicationIcon *)applicationIconForBundleIdentifier:(NSString *)arg1; 31 | @end 32 | 33 | @interface SBMediaController : NSObject 34 | +(id)sharedInstance; 35 | -(SBApplication *)nowPlayingApplication; 36 | @end 37 | 38 | @interface SBHIconImageCache : NSObject 39 | @property (nonatomic, readonly) UIImage *overlayImage; 40 | -(void)cacheImage:(UIImage *)arg1 forIcon:(SBApplicationIcon *)arg2; 41 | -(void)notifyObserversOfUpdateForIcon:(SBApplicationIcon *)arg1; 42 | -(void)purgeCachedImagesForIcons:(id)arg1; 43 | @end 44 | 45 | @interface SBFolderIconImageCache : NSObject 46 | -(void)iconImageCache:(id)arg1 didUpdateImageForIcon:(id)arg2; 47 | @end 48 | 49 | @interface SBHIconManager : NSObject 50 | -(SBHIconImageCache *)iconImageCache; 51 | -(SBFolderIconImageCache *)folderIconImageCache; 52 | @end 53 | 54 | @interface SBIconController : NSObject 55 | @property (nonatomic, readonly) SBHIconManager *iconManager; 56 | @property (nonatomic, readonly) SBHIconImageCache *appSwitcherHeaderIconImageCache; 57 | @property (nonatomic, readonly) SBHIconImageCache *appSwitcherUnmaskedIconImageCache; 58 | @property (nonatomic, readonly) SBHIconImageCache *tableUIIconImageCache; 59 | @property (nonatomic, readonly) SBHIconImageCache *notificationIconImageCache; 60 | @property (nonatomic, retain) SBIconModel *model; 61 | +(instancetype)sharedInstance; 62 | 63 | //NowPlayingIcon 64 | - (void)registerForNowPlayingNotifications; 65 | -(void)setNowPlayingArtworkForApp:(SBApplicationIcon *)appIcon withArtwork:(UIImage *)artwork; 66 | @end 67 | -------------------------------------------------------------------------------- /NowPlayingIconXVI.xm: -------------------------------------------------------------------------------- 1 | /* 2 | * Tweak.xm 3 | * NowPlayingIcon 4 | * 5 | * Created by Ethan Whited on 12/19/2022. 6 | * Copyright © 2022 Ethan Whited . All rights reserved. 7 | */ 8 | 9 | // original tweak https://github.com/LacertosusRepo/Open-Source-Tweaks/tree/master/NowPlayingIcon 10 | 11 | #import "MediaRemote.h" 12 | #import "NowPlayingIconXVI.h" 13 | 14 | NSString *lastNowPlayingBundleID; 15 | UIImage *currentArtwork; 16 | UIImage *currentMaskedArtwork; 17 | 18 | %hook SBMediaController 19 | -(void)_mediaRemoteNowPlayingApplicationDidChange:(id)arg1 { 20 | %orig; 21 | if (arg1 != nil) { 22 | [[NSNotificationCenter defaultCenter] postNotificationName:@"NowPlayingAppChanged" object:nil]; 23 | } else { 24 | [[NSNotificationCenter defaultCenter] postNotificationName:@"NowPlayingAppTerminated" object:nil]; 25 | } 26 | } 27 | 28 | -(void)_mediaRemoteNowPlayingInfoDidChange:(id)arg1 { 29 | %orig; 30 | [[NSNotificationCenter defaultCenter] postNotificationName:@"NowPlayingInfoChanged" object:nil]; 31 | } 32 | %end 33 | 34 | %hook SBIconController 35 | // iOS 13-14 36 | -(instancetype)initWithApplicationController:(id)arg1 applicationPlaceholderController:(id)arg2 userInterfaceController:(id)arg3 policyAggregator:(id)arg4 alertItemsController:(id)arg5 assistantController:(id)arg6 { 37 | [self registerForNowPlayingNotifications]; 38 | return %orig; 39 | } 40 | 41 | // iOS 15 42 | -(id)initWithApplicationController:(id)arg1 applicationPlaceholderController:(id)arg2 userInterfaceController:(id)arg3 policyAggregator:(id)arg4 alertItemsController:(id)arg5 assistantController:(id)arg6 powerLogAggregator:(id)arg7 { 43 | [self registerForNowPlayingNotifications]; 44 | return %orig; 45 | } 46 | 47 | // iOS 16 48 | - (id)initWithMainDisplayWindowScene:(id)arg1 { 49 | [self registerForNowPlayingNotifications]; 50 | return %orig; 51 | } 52 | 53 | %new 54 | - (void)registerForNowPlayingNotifications { 55 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingAppDidChange) name:@"NowPlayingAppChanged" object:nil]; 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingAppDidTerminate) name:@"NowPlayingAppTerminated" object:nil]; 57 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingInfoDidChange) name:@"NowPlayingInfoChanged" object:nil]; 58 | } 59 | 60 | %new 61 | -(void)nowPlayingInfoDidChange { 62 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 63 | NSDictionary *nowPlayingInfo = (__bridge NSDictionary *)information; 64 | NSData *artworkData = [nowPlayingInfo objectForKey:(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData]; 65 | if (artworkData) { 66 | NSData *oldArtworkData = UIImageJPEGRepresentation(currentArtwork, 1.0); 67 | NSData *newArtworkData = UIImageJPEGRepresentation([UIImage imageWithData:artworkData], 1.0); 68 | if ([oldArtworkData isEqualToData:newArtworkData]) { 69 | return; 70 | } else { 71 | currentArtwork = [UIImage imageWithData:artworkData]; 72 | } 73 | 74 | SBIconController *iconController = [%c(SBIconController) sharedInstance]; 75 | SBApplication *nowPlayingApp = [[%c(SBMediaController) sharedInstance] nowPlayingApplication]; 76 | NSString *bundleID = nowPlayingApp.bundleIdentifier; 77 | if (bundleID != nil) { 78 | SBApplicationIcon *appIcon = [iconController.model applicationIconForBundleIdentifier:bundleID]; 79 | 80 | if (currentArtwork != nil) [self setNowPlayingArtworkForApp:appIcon withArtwork:currentArtwork]; 81 | lastNowPlayingBundleID = nowPlayingApp.bundleIdentifier; 82 | } 83 | } 84 | }); 85 | } 86 | 87 | %new 88 | -(void)nowPlayingAppDidChange { 89 | SBIconController *iconController = [%c(SBIconController) sharedInstance]; 90 | if (lastNowPlayingBundleID != nil) { 91 | SBApplicationIcon *appIcon = [iconController.model applicationIconForBundleIdentifier:lastNowPlayingBundleID]; 92 | 93 | if ([appIcon application]) { 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | __NSSingleObjectArrayI *iconsToPurge = [%c(__NSSingleObjectArrayI) arrayWithObjects:appIcon, nil]; 96 | [[iconController.iconManager iconImageCache] purgeCachedImagesForIcons:iconsToPurge]; 97 | [[iconController.iconManager iconImageCache] notifyObserversOfUpdateForIcon:appIcon]; 98 | }); 99 | } 100 | } 101 | } 102 | 103 | %new 104 | -(void)nowPlayingAppDidTerminate { 105 | SBIconController *iconController = [%c(SBIconController) sharedInstance]; 106 | if (lastNowPlayingBundleID != nil) { 107 | SBApplicationIcon *appIcon = [iconController.model applicationIconForBundleIdentifier:lastNowPlayingBundleID]; 108 | 109 | if ([appIcon application]) { 110 | dispatch_async(dispatch_get_main_queue(), ^{ 111 | __NSSingleObjectArrayI *iconsToPurge = [%c(__NSSingleObjectArrayI) arrayWithObjects:appIcon, nil]; 112 | [[iconController.iconManager iconImageCache] purgeCachedImagesForIcons:iconsToPurge]; 113 | [[iconController.iconManager iconImageCache] notifyObserversOfUpdateForIcon:appIcon]; 114 | }); 115 | } 116 | } 117 | } 118 | 119 | %new 120 | -(void)setNowPlayingArtworkForApp:(SBApplicationIcon *)appIcon withArtwork:(UIImage *)artwork { 121 | if (appIcon && artwork) { 122 | SBHIconImageCache *imageCacheHS = [self.iconManager iconImageCache]; 123 | SBFolderIconImageCache *imageCacheFLDR = [self.iconManager folderIconImageCache]; 124 | NSArray *imageCaches = @[imageCacheHS, 125 | self.appSwitcherHeaderIconImageCache,]; 126 | 127 | CALayer *maskLayer = [CALayer layer]; 128 | maskLayer.frame = CGRectMake(0, 0, artwork.size.width, artwork.size.height); 129 | maskLayer.contents = (id)imageCacheHS.overlayImage.CGImage; 130 | 131 | CALayer *artLayer = [CALayer layer]; 132 | artLayer.frame = CGRectMake(0, 0, artwork.size.width, artwork.size.height); 133 | artLayer.contents = (id)artwork.CGImage; 134 | artLayer.masksToBounds = YES; 135 | artLayer.mask = maskLayer; 136 | 137 | UIGraphicsBeginImageContext(artwork.size); 138 | [artLayer renderInContext:UIGraphicsGetCurrentContext()]; 139 | currentMaskedArtwork = UIGraphicsGetImageFromCurrentImageContext(); 140 | UIGraphicsEndImageContext(); 141 | 142 | for (SBHIconImageCache *cache in imageCaches) { 143 | [cache cacheImage:currentMaskedArtwork forIcon:appIcon]; 144 | [cache notifyObserversOfUpdateForIcon:appIcon]; 145 | } 146 | [imageCacheFLDR iconImageCache:imageCacheHS didUpdateImageForIcon:appIcon]; 147 | } 148 | } 149 | %end 150 | 151 | %hook SBIconImageCrossfadeView 152 | //iOS 13 153 | -(instancetype)initWithImageView:(SBIconImageView *)arg1 crossfadeView:(UIView *)arg2 { 154 | SBIcon *icon = [arg1 icon]; 155 | SBApplication *nowPlayingApp = [[%c(SBMediaController) sharedInstance] nowPlayingApplication]; 156 | NSString *bundleID = nowPlayingApp.bundleIdentifier; 157 | if (bundleID != nil) { 158 | if ([[icon applicationBundleID] isEqualToString:bundleID] && currentMaskedArtwork) { 159 | CATransition *transition = [CATransition animation]; 160 | transition.duration = 1.0f; 161 | transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 162 | transition.type = kCATransitionFade; 163 | [arg1.layer addAnimation:transition forKey:nil]; 164 | 165 | arg1.layer.contents = (id)currentMaskedArtwork.CGImage; 166 | } 167 | } 168 | return %orig; 169 | } 170 | 171 | //iOS 14+ 172 | -(instancetype)initWithSource:(SBIconImageView *)arg1 crossfadeView:(UIView *)arg2 { 173 | SBIcon *icon = [arg1 icon]; 174 | SBApplication *nowPlayingApp = [[%c(SBMediaController) sharedInstance] nowPlayingApplication]; 175 | NSString *bundleID = nowPlayingApp.bundleIdentifier; 176 | if (bundleID != nil) { 177 | if ([[icon applicationBundleID] isEqualToString:bundleID] && currentMaskedArtwork) { 178 | CATransition *transition = [CATransition animation]; 179 | transition.duration = 1.0f; 180 | transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 181 | transition.type = kCATransitionFade; 182 | [arg1.layer addAnimation:transition forKey:nil]; 183 | 184 | arg1.layer.contents = (id)currentMaskedArtwork.CGImage; 185 | } 186 | } 187 | return %orig; 188 | } 189 | %end 190 | --------------------------------------------------------------------------------