├── ASSWatchdog ├── ASSWatchdog.plist ├── Makefile └── Tweak.c ├── .gitignore ├── Music ├── MitsuhaForeverMusic.plist ├── Makefile ├── Tweak.h └── MusicTweak.xm ├── Spotify ├── MitsuhaForeverSpotify.plist ├── Makefile ├── Tweak.h └── SpotifyTweak.xm ├── Soundcloud ├── MitsuhaForeverSoundCloud.plist ├── Makefile ├── Tweak.h └── SCTweak.xm ├── Springboard ├── MitsuhaForeverSpringboard.plist ├── Makefile ├── HomescreenTweak.xm ├── Tweak.h ├── ControlCenterTweak.xm └── SBTweak.xm ├── Prefs ├── Resources │ ├── icon.png │ ├── safari.png │ ├── icon@2x.png │ ├── icon@3x.png │ ├── twitter.png │ ├── safari@2x.png │ ├── safari@3x.png │ ├── twitter@2x.png │ ├── twitter@3x.png │ ├── icon_83.5@2x.png │ ├── Apps │ │ ├── Music.plist │ │ ├── HomeScreen.plist │ │ ├── ControlCenter.plist │ │ ├── LockScreen.plist │ │ ├── Springboard.plist │ │ └── Spotify.plist │ ├── Prefs.plist │ └── App.plist ├── entry.plist ├── MSHFAppPrefsListController.h ├── Makefile ├── MSHFPrefsListController.h ├── MSHFAppPrefsListController.m └── MSHFPrefsListController.m ├── .github └── FUNDING.yml ├── Makefile ├── control ├── README.md ├── LICENSE └── DragonMake /ASSWatchdog/ASSWatchdog.plist: -------------------------------------------------------------------------------- 1 | Filter = { Executables = ("mediaserverd"); }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.theos 2 | obj 3 | .DS_Store 4 | /*.xcodeproj 5 | packages 6 | .dragon -------------------------------------------------------------------------------- /Music/MitsuhaForeverMusic.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.Music" ); }; } 2 | -------------------------------------------------------------------------------- /Spotify/MitsuhaForeverSpotify.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.spotify.client" ); }; } -------------------------------------------------------------------------------- /Soundcloud/MitsuhaForeverSoundCloud.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.soundcloud.TouchApp" ); }; } -------------------------------------------------------------------------------- /Springboard/MitsuhaForeverSpringboard.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.springboard" ); }; } 2 | -------------------------------------------------------------------------------- /Prefs/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/icon.png -------------------------------------------------------------------------------- /Prefs/Resources/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/safari.png -------------------------------------------------------------------------------- /Prefs/Resources/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/icon@2x.png -------------------------------------------------------------------------------- /Prefs/Resources/icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/icon@3x.png -------------------------------------------------------------------------------- /Prefs/Resources/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/twitter.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: https://www.ko-fi.com/ConorTheDev 4 | -------------------------------------------------------------------------------- /Prefs/Resources/safari@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/safari@2x.png -------------------------------------------------------------------------------- /Prefs/Resources/safari@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/safari@3x.png -------------------------------------------------------------------------------- /Prefs/Resources/twitter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/twitter@2x.png -------------------------------------------------------------------------------- /Prefs/Resources/twitter@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/twitter@3x.png -------------------------------------------------------------------------------- /Prefs/Resources/icon_83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryannair05/MitsuhaForever/HEAD/Prefs/Resources/icon_83.5@2x.png -------------------------------------------------------------------------------- /Prefs/entry.plist: -------------------------------------------------------------------------------- 1 | { 2 | entry = { 3 | bundle = MitsuhaForeverPrefs; 4 | cell = PSLinkCell; 5 | detail = MSHFPrefsListController; 6 | icon = icon.png; 7 | isController = 1; 8 | label = "Mitsuha Forever"; 9 | }; 10 | } -------------------------------------------------------------------------------- /Music/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | TWEAK_NAME = MitsuhaForeverMusic 6 | $(TWEAK_NAME)_FILES = MusicTweak.xm 7 | $(TWEAK_NAME)_LIBRARIES += mitsuhaforever 8 | 9 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /Spotify/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | TWEAK_NAME = MitsuhaForeverSpotify 6 | $(TWEAK_NAME)_FILES = SpotifyTweak.xm 7 | $(TWEAK_NAME)_LIBRARIES += mitsuhaforever 8 | 9 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /ASSWatchdog/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | TWEAK_NAME = ASSWatchdog 6 | $(TWEAK_NAME)_FILES = Tweak.c 7 | $(TWEAK_NAME)_WEAK_LIBRARIES = $(THEOS_LIBRARY_PATH)/libhooker.dylib 8 | 9 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /Soundcloud/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | TWEAK_NAME = MitsuhaForeverSoundCloud 6 | $(TWEAK_NAME)_FILES = SCTweak.xm 7 | $(TWEAK_NAME)_PRIVATE_FRAMEWORKS += MediaRemote 8 | $(TWEAK_NAME)_LIBRARIES += mitsuhaforever 9 | 10 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /Prefs/Resources/Apps/Music.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Music 7 | title 8 | Apple Music 9 | 10 | -------------------------------------------------------------------------------- /Springboard/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | TWEAK_NAME = MitsuhaForeverSpringboard 6 | $(TWEAK_NAME)_FILES = SBTweak.xm HomescreenTweak.xm ControlCenterTweak.xm 7 | $(TWEAK_NAME)_PRIVATE_FRAMEWORKS += MediaRemote 8 | $(TWEAK_NAME)_LIBRARIES += mitsuhaforever 9 | 10 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /Prefs/Resources/Apps/HomeScreen.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | HomeScreen 7 | title 8 | Homescreen 9 | 10 | -------------------------------------------------------------------------------- /Prefs/Resources/Apps/ControlCenter.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | ControlCenter 7 | title 8 | Control Center 9 | 10 | -------------------------------------------------------------------------------- /Prefs/Resources/Apps/LockScreen.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | LockScreen 7 | title 8 | Lockscreen Background 9 | 10 | -------------------------------------------------------------------------------- /Prefs/Resources/Apps/Springboard.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Springboard 7 | title 8 | Lockscreen Notification 9 | 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FINALPACKAGE = 1 2 | 3 | PREFIX = $(THEOS)/toolchain/Xcode.xctoolchain/usr/bin/ 4 | 5 | export ADDITIONAL_CFLAGS = -DTHEOS_LEAN_AND_MEAN -fobjc-arc -O3 6 | export TARGET = iphone:13.5:12.0 7 | 8 | include $(THEOS)/makefiles/common.mk 9 | 10 | SUBPROJECTS += ASSWatchdog Music Prefs Spotify Springboard 11 | 12 | after-install:: 13 | install.exec "killall -9 SpringBoard" 14 | 15 | include $(THEOS_MAKE_PATH)/aggregate.mk -------------------------------------------------------------------------------- /Prefs/MSHFAppPrefsListController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface PSListController (Method) 6 | -(BOOL)containsSpecifier:(id)arg1; 7 | @end 8 | 9 | @interface MSHFAppPrefsListController : PSListController 10 | @property (nonatomic, retain) NSMutableDictionary *savedSpecifiers; 11 | @property (nonatomic, retain) NSString *appName; 12 | @end 13 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.ryannair05.mitsuhaforever 2 | Name: Mitsuha Forever 3 | Depends: mobilesubstrate, preferenceloader, com.ryannair05.libmitsuhaforever (>=1.4.0), ws.hbang.alderis, firmware (>=11.0) 4 | Replaces: io.c0ldra1n.mitsuha, io.ominousness.mitsuhaxi, me.nepeta.mitsuhainfinity, me.nepeta.mitsuhainfinity-lockscreen 5 | Version: 1.4.3 6 | Architecture: iphoneos-arm 7 | Description: Universal audio visualizer for iOS 8 | Maintainer: Ryan Nair 9 | Author: Ryan Nair, ConorTheDev, and Nepeta 10 | Section: Tweaks -------------------------------------------------------------------------------- /Prefs/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | 3 | include $(THEOS_MAKE_PATH)/common.mk 4 | 5 | BUNDLE_NAME = MitsuhaForeverPrefs 6 | $(BUNDLE_NAME)_FILES = MSHFPrefsListController.m MSHFAppPrefsListController.m 7 | $(BUNDLE_NAME)_INSTALL_PATH = /Library/PreferenceBundles 8 | $(BUNDLE_NAME)_LIBRARIES = colorpicker mitsuhaforever 9 | $(BUNDLE_NAME)_PRIVATE_FRAMEWORKS = Preferences 10 | 11 | include $(THEOS_MAKE_PATH)/bundle.mk 12 | 13 | internal-stage:: 14 | $(ECHO_NOTHING)mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences$(ECHO_END) 15 | $(ECHO_NOTHING)cp entry.plist $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(BUNDLE_NAME).plist$(ECHO_END) -------------------------------------------------------------------------------- /Music/Tweak.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweak.h 3 | // Mitsuha2 4 | // 5 | // Created by c0ldra1n on 12/10/17. 6 | // Copyright © 2017 c0ldra1n. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MusicArtworkComponentImageView : UIImageView 12 | @end 13 | 14 | @interface MusicNowPlayingControlsViewController : UIViewController 15 | @property(retain, nonatomic) MSHFView *mshfView; 16 | @end 17 | 18 | @interface CFWPrefsManager : NSObject 19 | + (id)sharedInstance; 20 | @end 21 | 22 | @interface CFWColorInfo : NSObject 23 | @property (nonatomic, strong, readwrite) UIColor *primaryColor; 24 | @end 25 | 26 | @interface CFWMusicStateManager : NSObject 27 | @property (atomic, strong, readonly) CFWColorInfo *colorInfo; 28 | + (id)sharedInstance; 29 | @end 30 | -------------------------------------------------------------------------------- /Soundcloud/Tweak.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweak.h 3 | // Mitsuha2 4 | // 5 | // Created by c0ldra1n on 12/10/17. 6 | // Copyright © 2017 c0ldra1n. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PlayerArtworkView : UIView 12 | 13 | @property(retain, nonatomic) UIImage *artworkImage; 14 | @property(retain, nonatomic) UIImageView *artworkImageView; 15 | - (void)observeValueForKeyPath:(NSString *)keyPath 16 | ofObject:(id)object 17 | change:(NSDictionary *)change 18 | context:(void *)context; 19 | - (void)readjustWaveColor; 20 | 21 | @end 22 | 23 | @interface WaveFormController : UIView 24 | 25 | @end 26 | 27 | @interface TrackPlayerViewController : UIViewController 28 | 29 | @property(retain, nonatomic) MSHFView *mshfView; 30 | 31 | @end -------------------------------------------------------------------------------- /Prefs/Resources/Apps/Spotify.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Spotify 7 | items 8 | 9 | 10 | cell 11 | PSSwitchCell 12 | default 13 | 14 | defaults 15 | com.ryannair05.mitsuhaforever 16 | label 17 | Ignore ColorFlow 18 | key 19 | MSHFSpotifyIgnoreColorFlow 20 | alternateColors 21 | 22 | 23 | 24 | cell 25 | PSSwitchCell 26 | default 27 | 28 | defaults 29 | com.ryannair05.mitsuhaforever 30 | label 31 | Cover art bug fix 32 | key 33 | MSHFSpotifyEnableCoverArtBugFix 34 | alternateColors 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Prefs/MSHFPrefsListController.h: -------------------------------------------------------------------------------- 1 | #import "MSHFAppPrefsListController.h" 2 | #import 3 | #import 4 | #import 5 | #import 6 | 7 | @interface MSHFPrefsListController : PSListController 8 | - (void)resetPrefs:(id)sender; 9 | - (void)respring:(id)sender; 10 | - (void)restartmsd:(id)sender; 11 | @end 12 | 13 | @interface MSHFTintedTableCell : PSTableCell 14 | @end 15 | 16 | @interface MSHFLinkTableCell : MSHFTintedTableCell 17 | @property (nonatomic, readonly) BOOL isBig; 18 | @property (nonatomic, retain, readonly) UIView *avatarView; 19 | @property (nonatomic, retain, readonly) UIImageView *avatarImageView; 20 | @property (nonatomic, retain) UIImage *avatarImage; 21 | @property (nonatomic, retain) NSURL *avatarURL; 22 | @property (nonatomic, readonly) BOOL isAvatarCircular; 23 | - (void)loadAvatarIfNeeded; 24 | - (BOOL)shouldShowAvatar; 25 | @end 26 | 27 | @interface MSHFTwitterCell : MSHFLinkTableCell 28 | @end 29 | 30 | @interface MSHFTwitterCell () { 31 | NSString *_user; 32 | } 33 | @end 34 | 35 | @interface MSHFPackageNameHeaderCell : PSTableCell 36 | @end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mitsuha Forever 2 | 3 | This is a fork of a Nepeta's excellent tweak - [MitsuhaInfinity](https://github.com/Nepeta/MitsuhaInfinity) with support for iOS 13, bug fixes and new features 4 | 5 | For support / to report a bug [join my discord server](https://discord.gg/J2Tmaqy) 6 | 7 | ## Installation 8 | 9 | 1. Add this repository to your package manager: https://repo.chariz.com 10 | 2. Install **Mitsuha Forever**. 11 | 12 | ## Compatibility 13 | 14 | ### Supported apps 15 | 16 | * Apple Music 17 | * Spotify 18 | * iOS audio player notification (global) 19 | 20 | ### Tweaks 21 | 22 | Should be compatible with Eclipse and similar tweaks. 23 | Works with Artsy. 24 | 25 | ## Bugs 26 | 27 | For support / to report a bug you may open an issue here on Github 28 | 29 | ## Donations 30 | 31 | Support the original developer, c0ldra1n, not me, here: https://www.paypal.me/c0ldra1n 32 | 33 | ## Credits 34 | 35 | * [c0ldra1n](https://github.com/c0ldra1n/) - Developed the original tweak for iOS 10 and earlier. 36 | * Nepeta - Developed the original Mitsuha Infinity. 37 | * [cbyrne](https://github.com/conorthedev) - Originally developed this tweak. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andy Shin 4 | Copyright (c) 2019 Nepeta 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /DragonMake: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mitsuha Forever 3 | icmd: killall -9 SpringBoard 4 | 5 | all: 6 | targetvers: 11.0 7 | archs: 8 | - arm64 9 | - arm64e 10 | optim: 3 11 | 12 | ASSWatchdog: 13 | dir: ASSWatchdog 14 | type: tweak 15 | c_files: 16 | - "Tweak.c" 17 | MitsuhaForeverHomeScreen: 18 | dir: Homescreen 19 | type: tweak 20 | logos_files: 21 | - "HomescreenTweak.xm" 22 | libs: 23 | - MitsuhaForever 24 | frameworks: 25 | - MediaRemote 26 | MitsuhaForeverMusic: 27 | dir: Music 28 | type: tweak 29 | logos_files: 30 | - "MusicTweak.xm" 31 | libs: 32 | - MitsuhaForever 33 | MitsuhaForeverSpotify: 34 | dir: Spotify 35 | type: tweak 36 | logos_files: 37 | - "SpotifyTweak.xm" 38 | libs: 39 | - MitsuhaForever 40 | MitsuhaForeverSpringboardLS: 41 | dir: SpringboardLS 42 | type: tweak 43 | logos_files: 44 | - "SBTweak.xm" 45 | libs: 46 | - MitsuhaForever 47 | frameworks: 48 | - MediaRemote 49 | MitsuhaForeverSpringboardLSBackground: 50 | dir: SpringboardLSBackground 51 | type: tweak 52 | logos_files: 53 | - "SBLSTweak.xm" 54 | libs: 55 | - MitsuhaForever 56 | frameworks: 57 | - MediaRemote 58 | 59 | MitsuhaForeverPrefs: 60 | dir: Prefs 61 | type: prefs 62 | objc_files: 63 | - MSHFAppPrefsListController.m 64 | - MSHFPrefsListController.m 65 | libs: 66 | - colorpicker 67 | - mitsuhaforever -------------------------------------------------------------------------------- /ASSWatchdog/Tweak.c: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #include 4 | #include 5 | #include "libhooker.h" 6 | 7 | #define ASSPort 44333 8 | 9 | bool hasConnected; 10 | const int one = 1; 11 | int connfd; 12 | 13 | OSStatus (*_origAudioQueueStart)(AudioQueueRef inAQ, const AudioTimeStamp *inStartTime); 14 | OSStatus _functionAudioQueueStart(AudioQueueRef inAQ, const AudioTimeStamp *inStartTime) { 15 | 16 | os_log(OS_LOG_DEFAULT, "[ASSWatchdog] checking for ASS"); 17 | if (!hasConnected) { 18 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 19 | struct sockaddr_in remote; 20 | memset(&remote, 0, sizeof(struct sockaddr_in)); 21 | remote.sin_family = PF_INET; 22 | remote.sin_port = htons(ASSPort); 23 | inet_aton("127.0.0.1", &remote.sin_addr); 24 | 25 | os_log(OS_LOG_DEFAULT, "[ASSWatchdog] Connecting to ASS"); 26 | connfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 27 | 28 | setsockopt(connfd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); 29 | 30 | if (connfd <= 0) { 31 | os_log(OS_LOG_DEFAULT, "[ASSWatchdog] ASS not running."); 32 | } 33 | else { 34 | os_log(OS_LOG_DEFAULT, "[ASSWatchdog] Connected."); 35 | hasConnected = true; 36 | close(connfd); 37 | } 38 | }); 39 | } else { 40 | os_log(OS_LOG_DEFAULT, "[ASSWatchdog] already has connected..."); 41 | } 42 | 43 | return _origAudioQueueStart(inAQ, inStartTime); 44 | } 45 | 46 | static __attribute__((constructor)) void Init() { 47 | 48 | hasConnected = false; 49 | 50 | if (access("/usr/lib/libhooker.dylib", F_OK) == 0) { 51 | const struct LHFunctionHook hook[1] = {{(void *)AudioQueueStart, (void **)&_functionAudioQueueStart, (void **)&_origAudioQueueStart}}; 52 | LHHookFunctions(hook, 1); 53 | } 54 | else { 55 | MSHookFunction((void *)AudioQueueStart, (void *)&_functionAudioQueueStart, (void **)&_origAudioQueueStart); 56 | } 57 | } -------------------------------------------------------------------------------- /Soundcloud/SCTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | 3 | // bool MSHFNeedsFirstPlayerInit = YES; 4 | 5 | %group MitsuhaVisuals 6 | 7 | MSHFConfig *config = NULL; 8 | 9 | WaveFormController *waveController; 10 | 11 | %hook PlayerArtworkView 12 | 13 | -(void)layoutSubviews{ 14 | %orig; 15 | if ([config view] == NULL) return; 16 | if (!self.superview) return; 17 | if (!self.superview.nextResponder) return; 18 | if (![NSStringFromClass([self.superview.nextResponder class]) isEqualToString:@"TrackPlayerViewController"]) return; 19 | 20 | if (config.colorMode != 2) { 21 | [self readjustWaveColor]; 22 | } 23 | 24 | [self addObserver:self forKeyPath:@"artworkImage" options:NSKeyValueObservingOptionNew context:NULL]; 25 | } 26 | 27 | %new; 28 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 29 | if ([keyPath isEqualToString:@"artworkImage"] && config.colorMode != 2) { 30 | [self readjustWaveColor]; 31 | } 32 | } 33 | 34 | %new; 35 | -(void)readjustWaveColor{ 36 | [config colorizeView:((PlayerArtworkView*)self).artworkImage]; 37 | } 38 | 39 | %end 40 | 41 | %hook TrackPlayerViewController 42 | 43 | %property (retain,nonatomic) MSHFView *mshfView; 44 | 45 | -(void)loadView { 46 | %orig; 47 | 48 | NSLog(@"[Mitsuha]: viewLoaded"); 49 | 50 | CGFloat width = CGRectGetWidth(self.view.bounds); 51 | CGFloat height = CGRectGetHeight(self.view.bounds); 52 | 53 | [config initializeViewWithFrame:CGRectMake(0, height + config.waveOffset, width, height)]; 54 | 55 | self.mshfView = [config view]; 56 | [self.view insertSubview:self.mshfView atIndex:20]; 57 | 58 | } 59 | 60 | -(void)viewWillAppear:(BOOL)animated{ 61 | NSLog(@"[Mitsuha]: viewWillAppear"); 62 | 63 | %orig; 64 | 65 | self.view.clipsToBounds = 1; 66 | 67 | [self.mshfView start]; 68 | } 69 | 70 | %end 71 | 72 | %end 73 | 74 | %ctor{ 75 | config = [MSHFConfig loadConfigForApplication:@"SoundCloud"]; 76 | config.waveOffsetOffset = -130; 77 | 78 | if(config.enabled){ 79 | %init(MitsuhaVisuals); 80 | } 81 | } -------------------------------------------------------------------------------- /Spotify/Tweak.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweak.h 3 | // Mitsuha 4 | // 5 | // Created by c0ldra1n on 2/17/17. 6 | // Copyright © 2017 c0ldra1n. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SPTNowPlayingCoverArtImageView : UIImageView 13 | -(void)setImage:(UIImage *)image; 14 | -(void)readjustWaveColor; 15 | 16 | @end 17 | 18 | @interface SPTNowPlayingContentCell : UIView 19 | 20 | @property(retain, nonatomic) UIImage *cellContentRepresentation; 21 | 22 | @end 23 | 24 | @interface SPTNowPlayingCoverArtView : UIView 25 | 26 | @end 27 | 28 | @interface SPTNowPlayingCarouselContentUnitView : UIView 29 | 30 | @property(retain, nonatomic) SPTNowPlayingCoverArtView *coverArtView; // @synthesize coverArtView=_coverArtView; 31 | 32 | @end 33 | 34 | @interface SPTNowPlayingCarouselAreaViewController : UIViewController 35 | 36 | @property(retain, nonatomic) SPTNowPlayingCarouselContentUnitView *view; // @dynamic view; 37 | 38 | @end 39 | 40 | @interface SPTNowPlayingModel : NSObject 41 | - (void)player:(id)arg1 stateDidChange:(id)arg2 fromState:(id)arg3; 42 | - (void)updateWithPlayerState:(id)arg1; 43 | 44 | -(void)applyColorChange; 45 | 46 | @end 47 | 48 | @interface CFWColorInfo : NSObject 49 | 50 | + (id)colorInfoWithAnalyzedInfo:(struct AnalyzedInfo)arg1; 51 | @property(nonatomic, getter=isBackgroundDark) _Bool backgroundDark; // @synthesize backgroundDark=_backgroundDark; 52 | @property(retain, nonatomic) UIColor *secondaryColor; // @synthesize secondaryColor=_secondaryColor; 53 | @property(retain, nonatomic) UIColor *primaryColor; // @synthesize primaryColor=_primaryColor; 54 | @property(retain, nonatomic) UIColor *backgroundColor; // @synthesize backgroundColor=_backgroundColor; 55 | - (id)initWithAnalyzedInfo:(struct AnalyzedInfo)arg1; 56 | 57 | @end 58 | 59 | @interface CFWSpotifyStateManager : NSObject 60 | 61 | + (id)sharedManager; 62 | 63 | @property(readonly, retain, nonatomic) CFWColorInfo *mainColorInfo; // @synthesize mainColorInfo=_mainColorInfo; 64 | 65 | @end 66 | 67 | @interface CFWPrefsManager : NSObject 68 | 69 | +(id)sharedInstance; 70 | 71 | @end 72 | 73 | @interface SPTNowPlayingViewController : UIViewController 74 | @property (retain,nonatomic) MSHFView *mshfview; 75 | @end 76 | 77 | @interface SPTVideoDisplayView : UIView 78 | @property (nonatomic, strong, readwrite) AVPlayer *player; 79 | @property(nonatomic) _Bool playbackReady; 80 | @end -------------------------------------------------------------------------------- /Springboard/HomescreenTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | #import 3 | #import 4 | 5 | static MSHFConfig *mshConfig; 6 | 7 | %group SBMediaHook 8 | %hook SBMediaController 9 | 10 | -(void)setNowPlayingInfo:(id)arg1 { 11 | %orig; 12 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 13 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 14 | [mshConfig colorizeView:[UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]]; 15 | } 16 | }); 17 | } 18 | %end 19 | %end 20 | 21 | %hook SBIconController 22 | 23 | %property (strong,nonatomic) MSHFView *mshfView; 24 | 25 | -(void)loadView{ 26 | %orig; 27 | mshConfig.waveOffsetOffset = self.view.bounds.size.height - 200; 28 | if (![mshConfig view]) [mshConfig initializeViewWithFrame:self.view.bounds]; 29 | self.mshfView = [mshConfig view]; 30 | 31 | [[self view] insertSubview:self.mshfView atIndex:1]; 32 | 33 | self.mshfView.translatesAutoresizingMaskIntoConstraints = NO; 34 | [self.mshfView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 35 | [self.mshfView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 36 | [self.mshfView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; 37 | [self.mshfView.heightAnchor constraintEqualToConstant:self.mshfView.frame.size.height].active = YES; 38 | } 39 | 40 | -(void)viewWillAppear:(BOOL)animated{ 41 | %orig; 42 | [self.mshfView start]; 43 | } 44 | 45 | -(void)viewWillDisappear:(BOOL)animated{ 46 | %orig; 47 | [self.mshfView stop]; 48 | } 49 | 50 | %end 51 | 52 | static void screenDisplayStatus(CFNotificationCenterRef center, void* o, CFStringRef name, const void* object, CFDictionaryRef userInfo) { 53 | if(@available(iOS 13.0, *)) { 54 | [[mshConfig view] stop]; 55 | } 56 | else if ([[%c(SBMediaController) sharedInstance] isPlaying]) { 57 | uint64_t state; 58 | int token; 59 | notify_register_check("com.apple.iokit.hid.displayStatus", &token); 60 | notify_get_state(token, &state); 61 | notify_cancel(token); 62 | if ([mshConfig view]) { 63 | if (state) { 64 | [[mshConfig view] start]; 65 | } else { 66 | [[mshConfig view] stop]; 67 | } 68 | } 69 | } 70 | else { 71 | [[mshConfig view] stop]; 72 | } 73 | } 74 | 75 | %ctor{ 76 | mshConfig = [MSHFConfig loadConfigForApplication:@"HomeScreen"]; 77 | if(mshConfig.enabled){ 78 | CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)screenDisplayStatus, (CFStringRef)@"com.apple.iokit.hid.displayStatus", NULL, (CFNotificationSuspensionBehavior)kNilOptions); 79 | 80 | %init; 81 | %init(SBMediaHook); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Springboard/Tweak.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweak.h 3 | // Mitsuha2 4 | // 5 | // Created by c0ldra1n on 12/10/17. 6 | // Copyright © 2017 c0ldra1n. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | #define ArtsyTweakDylibFile \ 13 | @"/Library/MobileSubstrate/DynamicLibraries/Artsy.dylib" 14 | #define PrysmTweakDylibFile \ 15 | @"/Library/MobileSubstrate/DynamicLibraries/Prysm.dylib" 16 | #define QuartTweakDylibFile \ 17 | @"/Library/MobileSubstrate/DynamicLibraries/Quart.dylib" 18 | #define FlowTweakDylibFile \ 19 | @"/Library/MobileSubstrate/DynamicLibraries/Flow.dylib" 20 | #define OrionTweakDylibFile \ 21 | "/Library/MobileSubstrate/DynamicLibraries/OrionSpringboard.dylib" 22 | #define ArtsyPreferencesFile \ 23 | @"/var/mobile/Library/Preferences/ch.mdaus.artsy.plist" 24 | #define QuartPreferencesFile \ 25 | @"/var/mobile/Library/Preferences/com.laughingquoll.quartprefs.plist" 26 | #define PrysmPreferencesFile \ 27 | @"/var/mobile/Library/Preferences/com.laughingquoll.prysmprefs.plist" 28 | 29 | @interface MRUCoverSheetViewController : UIViewController 30 | @property(retain, nonatomic) MSHFView *mshfView; 31 | @end 32 | 33 | @interface CSMediaControlsViewController : UIViewController 34 | @property(retain, nonatomic) MSHFView *mshfView; 35 | @end 36 | 37 | @interface MRPlatterViewController : UIViewController 38 | @property(retain, nonatomic) MSHFView *mshfView; 39 | @end 40 | 41 | @interface MRUNowPlayingViewController : UIViewController 42 | @property(retain, nonatomic) MSHFView *mshfView; 43 | @end 44 | 45 | @interface MediaControlsPanelViewController : UIViewController 46 | @property(retain, nonatomic) MSHFView *mshfView; 47 | @end 48 | 49 | @interface MediaControlsInteractionRecognizer : UIGestureRecognizer 50 | @end 51 | 52 | @interface SBDashBoardMediaControlsViewController : UIViewController 53 | @property(retain, nonatomic) MSHFView *mshfView; 54 | @end 55 | 56 | @interface QRTMediaModuleViewController : UIViewController 57 | @property(strong, nonatomic) MSHFView *mshfView; 58 | @property(strong, nonatomic) UIImageView *artworkView; 59 | @end 60 | 61 | @interface SBDashBoardFixedFooterViewController : UIViewController 62 | 63 | @property(strong, nonatomic) MSHFView *mshfview; 64 | 65 | @end 66 | 67 | @interface CSFixedFooterViewController : UIViewController 68 | 69 | @property(strong, nonatomic) MSHFView *mshfview; 70 | 71 | @end 72 | 73 | @interface CFWPrefsManager : NSObject 74 | + (id)sharedInstance; 75 | - (BOOL)lockScreenFullScreenEnabled; 76 | @end 77 | 78 | @interface OrionColorizer : NSObject 79 | @property (nonatomic, strong, readonly) UIColor *secondaryColor; 80 | + (id)sharedInstance; 81 | @end 82 | 83 | @interface CFWColorInfo : NSObject 84 | @property (nonatomic, strong, readwrite) UIColor *primaryColor; 85 | @end 86 | 87 | @interface CFWMusicStateManager : NSObject 88 | @property (atomic, strong, readonly) CFWColorInfo *colorInfo; 89 | + (id)sharedInstance; 90 | @end 91 | 92 | @interface SBIconController : UIViewController 93 | @property (nonatomic,retain) MSHFView * mshfView; 94 | @end 95 | 96 | @interface MRUControlCenterViewController : UIViewController 97 | @property (nonatomic,retain) MSHFView * mshfView; 98 | @end 99 | 100 | @interface PrysmMediaModuleViewController : UIViewController 101 | @property (nonatomic,retain) MSHFView * mshfView; 102 | @end -------------------------------------------------------------------------------- /Prefs/Resources/Prefs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | headerCellClass 11 | MSHFPackageNameHeaderCell 12 | packageIdentifier 13 | com.ryannair05.mitsuhaforever 14 | 16 | titleColor 17 | #ffffff 18 | subtitleColor 19 | #e0e0e0 20 | backgroundGradientColors 21 | 22 | #ee645c 23 | #ee645c 24 | 25 | 26 | 27 | 28 | 29 | cell 30 | PSGroupCell 31 | label 32 | Configuration 33 | isStaticText 34 | 35 | id 36 | apps 37 | 38 | 39 | cell 40 | PSSwitchCell 41 | default 42 | 43 | defaults 44 | com.ryannair05.mitsuhaforever 45 | label 46 | Enable sensitivity boost (AirPods 2 + Pro) 47 | key 48 | MSHFAirpodsSensBoost 49 | alternateColors 50 | 51 | 52 | 53 | 54 | 55 | cell 56 | PSGroupCell 57 | label 58 | Other 59 | 60 | 61 | action 62 | respring: 63 | cell 64 | PSButtonCell 65 | cellClass 66 | MSHFTintedTableCell 67 | label 68 | Respring 69 | 70 | 71 | action 72 | restartmsd: 73 | cell 74 | PSButtonCell 75 | cellClass 76 | MSHFTintedTableCell 77 | label 78 | Restart mediaserverd 79 | 80 | 81 | action 82 | resetPrefs: 83 | cell 84 | PSButtonCell 85 | cellClass 86 | MSHFTintedTableCell 87 | isDestructive 88 | 89 | confirmation 90 | 91 | prompt 92 | Are you sure you want to reset all your Mitsuha Forever preferences? This can not be undone. 93 | title 94 | OK 95 | okTitle 96 | OK 97 | cancelTitle 98 | Cancel 99 | 100 | label 101 | Restore default settings and respring 102 | 103 | 104 | cellClass 105 | MSHFTwitterCell 106 | label 107 | Ryan Nair 108 | subtitle 109 | Current Developer of Mitsuha Forever 110 | user 111 | ryannair05 112 | 113 | 114 | cellClass 115 | MSHFLinkTableCell 116 | label 117 | Github 118 | subtitle 119 | Source code 120 | url 121 | https://github.com/ryannair05/MitsuhaForever 122 | 123 | 124 | cellClass 125 | MSHFLinkTableCell 126 | label 127 | Donate to c0ldra1n 128 | subtitle 129 | Original developer of Mitsuha for iOS 9 and 10 130 | url 131 | https://www.paypal.me/c0ldra1n 132 | 133 | 134 | title 135 | Mitsuha Forever 136 | 137 | -------------------------------------------------------------------------------- /Springboard/ControlCenterTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | #import 3 | #import 4 | #import 5 | 6 | static MSHFConfig *mshConfig; 7 | 8 | %group SBMediaHook 9 | %hook SBMediaController 10 | 11 | -(void)setNowPlayingInfo:(id)arg1 { 12 | %orig; 13 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 14 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 15 | [mshConfig colorizeView:[UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]]; 16 | } 17 | }); 18 | } 19 | %end 20 | %end 21 | 22 | %group Prysm 23 | 24 | %hook PrysmMainPageViewController 25 | - (void)setIsPresented:(BOOL)isPresented { 26 | %orig; 27 | if (isPresented) { 28 | [mshConfig.view start]; 29 | } 30 | else { 31 | [mshConfig.view stop]; 32 | } 33 | } 34 | %end 35 | 36 | %hook PrysmMediaModuleViewController 37 | 38 | %property (strong,nonatomic) MSHFView *mshfView; 39 | 40 | -(void)viewDidLoad { 41 | 42 | %orig; 43 | 44 | if (![mshConfig view]) [mshConfig initializeViewWithFrame:self.view.bounds]; 45 | self.mshfView = [mshConfig view]; 46 | 47 | [self.view addSubview:self.mshfView]; 48 | [self.view bringSubviewToFront:self.mshfView]; 49 | 50 | self.mshfView.translatesAutoresizingMaskIntoConstraints = NO; 51 | [self.mshfView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 52 | [self.mshfView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 53 | [self.mshfView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; 54 | [NSLayoutConstraint constraintWithItem:self.mshfView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0.5 constant:-mshConfig.waveOffset].active = YES; 55 | } 56 | %end 57 | %end 58 | 59 | %group ControlCenter 60 | 61 | %hook MRUControlCenterViewController 62 | 63 | %property (strong,nonatomic) MSHFView *mshfView; 64 | 65 | -(void)loadView{ 66 | %orig; 67 | if (![mshConfig view]) [mshConfig initializeViewWithFrame:self.view.subviews[2].bounds]; 68 | self.mshfView = [mshConfig view]; 69 | 70 | self.mshfView.layer.cornerRadius = 19; 71 | 72 | #pragma clang diagnostic push 73 | #pragma clang diagnostic ignored "-Wunguarded-availability-new" 74 | self.mshfView.layer.cornerCurve = kCACornerCurveCircular; 75 | #pragma clang diagnostic pop 76 | 77 | self.mshfView.layer.maskedCorners = kCALayerMaxXMaxYCorner | kCALayerMinXMaxYCorner; 78 | self.mshfView.layer.masksToBounds = TRUE; 79 | 80 | [self.view.subviews[2] addSubview:self.mshfView]; 81 | [self.view.subviews[2] bringSubviewToFront:self.mshfView]; 82 | 83 | self.mshfView.translatesAutoresizingMaskIntoConstraints = NO; 84 | [self.mshfView.leadingAnchor constraintEqualToAnchor:self.view.subviews[2].leadingAnchor].active = YES; 85 | [self.mshfView.trailingAnchor constraintEqualToAnchor:self.view.subviews[2].trailingAnchor].active = YES; 86 | [self.mshfView.bottomAnchor constraintEqualToAnchor:self.view.subviews[2].bottomAnchor].active = YES; 87 | [NSLayoutConstraint constraintWithItem:self.mshfView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view.subviews[2] attribute:NSLayoutAttributeHeight multiplier:0.5 constant:-mshConfig.waveOffset].active = YES; 88 | } 89 | - (void)setState:(NSInteger)arg1 { 90 | %orig; 91 | if (arg1 == 1) { 92 | self.mshfView.layer.cornerRadius = 38; 93 | } 94 | else { 95 | self.mshfView.layer.cornerRadius = 19; 96 | } 97 | 98 | } 99 | -(void)viewWillAppear:(BOOL)animated { 100 | %orig; 101 | [[self mshfView] start]; 102 | } 103 | %end 104 | 105 | %hook SBControlCenterController 106 | -(void)_didDismiss { 107 | %orig; 108 | [mshConfig.view stop]; 109 | } 110 | %end 111 | %end 112 | 113 | %ctor{ 114 | 115 | bool prysmEnabled; 116 | 117 | if ([[NSFileManager defaultManager] fileExistsAtPath:PrysmTweakDylibFile]) { 118 | mshConfig = [MSHFConfig loadConfigForApplication:@"ControlCenter"]; 119 | prysmEnabled = TRUE; 120 | 121 | NSDictionary *prysmPrefs = [[NSDictionary alloc] initWithContentsOfFile:PrysmPreferencesFile]; 122 | if (prysmPrefs) { 123 | if (![([prysmPrefs objectForKey:@"enable"] ?: @(YES)) boolValue]) { 124 | prysmEnabled = false; 125 | } 126 | } 127 | } 128 | else if (@available(iOS 14.2, *)) { 129 | mshConfig = [MSHFConfig loadConfigForApplication:@"ControlCenter"]; 130 | } 131 | 132 | if (mshConfig && mshConfig.enabled) { 133 | %init(SBMediaHook); 134 | 135 | if (prysmEnabled) { 136 | dlopen("/Library/Prysm/Bundles/com.laughingquoll.prysm.PrysmMedia.bundle/PrysmMedia", RTLD_NOW); 137 | dlopen("/Library/MobileSubstrate/DynamicLibraries/Prysm.dylib", RTLD_NOW); 138 | %init(Prysm); 139 | } 140 | else %init(ControlCenter); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Music/MusicTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | 3 | static MSHFConfig *config = NULL; 4 | static bool const colorflow = [%c(CFWPrefsManager) class] && MSHookIvar([%c(CFWPrefsManager) sharedInstance], "_musicEnabled"); 5 | 6 | %group MitsuhaVisuals 7 | 8 | %hook MusicArtworkComponentImageView 9 | 10 | -(void)setImage:(id)arg1 { 11 | %orig; 12 | if ([config view] == NULL) return; 13 | 14 | NSString *musicString; 15 | 16 | if (@available(iOS 13.0, *)) 17 | musicString = @"MusicApplication.NowPlayingContentView"; 18 | else 19 | musicString = @"Music.NowPlayingContentView"; 20 | 21 | UIView *me = (UIView *)self; 22 | 23 | if ([NSStringFromClass([me.superview class]) isEqualToString:musicString]) { 24 | if (colorflow && config.colorMode == 0) { 25 | UIColor *color = [[[%c(CFWMusicStateManager) sharedInstance] colorInfo] primaryColor]; 26 | [[config view] updateWaveColor:color subwaveColor:color]; 27 | } 28 | else if (config.colorMode != 2) { 29 | [config colorizeView:((MusicArtworkComponentImageView*)self).image]; 30 | } 31 | 32 | } 33 | 34 | } 35 | 36 | %end 37 | 38 | %hook MusicNowPlayingControlsViewController 39 | %property (retain,nonatomic) MSHFView *mshfView; 40 | 41 | - (instancetype)init{ 42 | 43 | self = %orig; 44 | 45 | if (self) { 46 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMitsuhaApplicationState:) name:UIApplicationDidBecomeActiveNotification object:nil]; 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMitsuhaApplicationState:) name:UIApplicationDidEnterBackgroundNotification object:nil]; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | %new 54 | - (void)handleMitsuhaApplicationState:(NSNotification *)notification{ 55 | if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification]) { 56 | [[config view] start]; 57 | 58 | } else { 59 | [[config view] stop]; 60 | } 61 | } 62 | 63 | - (void)dealloc{ 64 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; 65 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; 66 | %orig; 67 | } 68 | 69 | -(void)viewDidLoad{ 70 | %orig; 71 | 72 | if(colorflow) { 73 | self.view.subviews[3].clipsToBounds = 1; 74 | [config initializeViewWithFrame:CGRectMake(0, -150, self.view.frame.size.width, (self.view.frame.size.height / 2) - 100)]; 75 | 76 | self.mshfView = [config view]; 77 | [self.view.subviews[3] addSubview:[config view]]; 78 | [self.view.subviews[3] sendSubviewToBack:[config view]]; 79 | 80 | if(self.mshfView.superview == NULL) { 81 | self.mshfView = [config view]; 82 | [self.view addSubview:[config view]]; 83 | [self.view sendSubviewToBack:[config view]]; 84 | } 85 | } else { 86 | CGSize const screenSize = [[UIScreen mainScreen] bounds].size; 87 | 88 | self.view.clipsToBounds = 1; 89 | 90 | [config initializeViewWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)]; 91 | 92 | self.mshfView = [config view]; 93 | [self.view addSubview:[config view]]; 94 | [self.view sendSubviewToBack:[config view]]; 95 | 96 | if (@available(iOS 14.0, *)) { 97 | return; 98 | } 99 | 100 | self.view.subviews[3].backgroundColor = [UIColor clearColor]; 101 | self.view.subviews[4].backgroundColor = [UIColor clearColor]; 102 | } 103 | } 104 | 105 | -(void)viewWillAppear:(BOOL)animated{ 106 | %orig; 107 | [[config view] start]; 108 | [config view].center = CGPointMake([config view].center.x, [config view].frame.size.height*2); 109 | } 110 | 111 | -(void)viewDidAppear:(BOOL)animated{ 112 | %orig; 113 | [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 114 | if(colorflow) { 115 | [config view].center = CGPointMake([config view].center.x, 150); 116 | } else { 117 | [config view].center = CGPointMake([config view].center.x, [config view].frame.size.height); 118 | } 119 | } completion:nil]; 120 | } 121 | 122 | -(void)viewWillDisappear:(BOOL)animated{ 123 | %orig; 124 | [[config view] stop]; 125 | } 126 | 127 | -(void)viewDidLayoutSubviews { 128 | %orig; 129 | if (@available(iOS 14.0, *)) { 130 | return; 131 | } 132 | if(!colorflow) { 133 | self.view.subviews[3].backgroundColor = [UIColor clearColor]; 134 | self.view.subviews[4].backgroundColor = [UIColor clearColor]; 135 | } 136 | } 137 | 138 | %end 139 | 140 | %end 141 | 142 | %ctor{ 143 | config = [MSHFConfig loadConfigForApplication:@"Music"]; 144 | config.waveOffsetOffset = 70; 145 | if(config.enabled){ 146 | NSString *classString = nil; 147 | if(@available(iOS 13.0, *)) { 148 | classString = @"MusicApplication.ArtworkComponentImageView"; 149 | } else { 150 | classString = @"Music.ArtworkComponentImageView"; 151 | } 152 | 153 | %init(MitsuhaVisuals, MusicArtworkComponentImageView = NSClassFromString(classString)); 154 | } 155 | } -------------------------------------------------------------------------------- /Spotify/SpotifyTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | #define CFWBackgroundViewTagNumber 896541 3 | 4 | bool MSHFColorFlowSpotifyEnabled = NO; 5 | 6 | %group MitsuhaVisuals 7 | 8 | MSHFConfig *config = NULL; 9 | 10 | %hook SPTNowPlayingCoverArtImageView 11 | 12 | -(void)setImage:(UIImage*)image { 13 | %orig; 14 | [config colorizeView:image]; 15 | } 16 | 17 | %end 18 | 19 | %hook SPTVideoDisplayView 20 | - (void)refreshVideoRect { 21 | %orig; 22 | 23 | AVPlayer *displayView = [self player]; 24 | AVAsset *asset = displayView.currentItem.asset; 25 | 26 | AVAssetImageGenerator* generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; 27 | generator.appliesPreferredTrackTransform = YES; 28 | UIImage* image = [UIImage imageWithCGImage:[generator copyCGImageAtTime:CMTimeMake(0, 1) actualTime:nil error:nil]]; 29 | if (image) [config colorizeView:image]; 30 | } 31 | 32 | %end 33 | 34 | %hook SPTNowPlayingViewController 35 | 36 | %property (retain,nonatomic) MSHFView *mshfview; 37 | 38 | -(void)viewDidLoad{ 39 | %orig; 40 | 41 | NSLog(@"[Mitsuha]: viewDidLoad"); 42 | 43 | if (![config view]) [config initializeViewWithFrame:CGRectMake(0, config.waveOffset, self.view.bounds.size.width, self.view.bounds.size.height)]; 44 | self.mshfview = [config view]; 45 | [self.mshfview setUserInteractionEnabled:NO]; 46 | 47 | [self.view insertSubview:self.mshfview atIndex:1]; 48 | 49 | self.mshfview.translatesAutoresizingMaskIntoConstraints = NO; 50 | [self.mshfview.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 51 | [self.mshfview.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 52 | [self.mshfview.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; 53 | [self.mshfview.heightAnchor constraintEqualToConstant:self.mshfview.frame.size.height].active = YES; 54 | 55 | } 56 | 57 | -(void)viewWillAppear:(BOOL)animated{ 58 | [[config view] start]; 59 | %orig; 60 | } 61 | 62 | -(void)viewDidAppear:(BOOL)animated{ 63 | %orig; 64 | [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 65 | 66 | [config view].center = CGPointMake([config view].center.x, [config view].frame.size.height/2 + config.waveOffset); 67 | 68 | } completion:nil]; 69 | 70 | [[config view] resetWaveLayers]; 71 | 72 | if (config.colorMode == 1) { 73 | [config colorizeView:nil]; 74 | } 75 | // Copied from NowPlayingImpl 76 | else if(MSHFColorFlowSpotifyEnabled){ 77 | CFWSpotifyStateManager *stateManager = [%c(CFWSpotifyStateManager) sharedManager]; 78 | UIColor *backgroundColor = [stateManager.mainColorInfo.backgroundColor colorWithAlphaComponent:0.5]; 79 | [[config view] updateWaveColor:backgroundColor subwaveColor:backgroundColor]; 80 | } 81 | } 82 | 83 | -(void)viewWillDisappear:(BOOL)animated{ 84 | %orig; 85 | [[config view] stop]; 86 | [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 87 | [config view].center = CGPointMake([config view].center.x, [config view].frame.size.height + config.waveOffset); 88 | } completion:^(BOOL finished){ 89 | }]; 90 | } 91 | 92 | %end 93 | %end 94 | 95 | %group MitsuhaSpotifyCoverArtFix 96 | 97 | %hook SPTNowPlayingCarouselAreaViewController 98 | 99 | static CGFloat originalCenterY = 0; 100 | 101 | -(void)viewWillAppear:(BOOL)animated{ 102 | %orig; 103 | 104 | NSLog(@"[Mitsuha]: originalCenterY: %lf", originalCenterY); 105 | 106 | CGPoint center = self.view.coverArtView.center; 107 | 108 | self.view.coverArtView.alpha = 0; 109 | self.view.coverArtView.center = CGPointMake(center.x, originalCenterY); 110 | } 111 | 112 | -(void)viewDidAppear:(BOOL)animated{ 113 | %orig; 114 | 115 | NSLog(@"[Mitsuha]: viewDidAppear"); 116 | 117 | CGPoint center = self.view.coverArtView.center; 118 | 119 | if(originalCenterY == 0){ 120 | originalCenterY = center.y; 121 | } 122 | 123 | [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 124 | self.view.coverArtView.alpha = 1.0; 125 | self.view.coverArtView.center = CGPointMake(center.x, originalCenterY * 0.8); 126 | } completion:^(BOOL finished){ 127 | if(self.view.coverArtView.center.y != originalCenterY * 0.8){ // For some reason I can't explain 128 | [UIView animateWithDuration:0.25 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 129 | self.view.coverArtView.center = CGPointMake(center.x, originalCenterY * 0.8); 130 | } completion:nil]; 131 | } 132 | }]; 133 | } 134 | 135 | -(void)viewWillDisappear:(BOOL)animated{ 136 | %orig; 137 | 138 | CGPoint center = self.view.coverArtView.center; 139 | 140 | [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:3.5 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 141 | self.view.coverArtView.alpha = 0; 142 | self.view.coverArtView.center = CGPointMake(center.x, originalCenterY); 143 | } completion:nil]; 144 | } 145 | 146 | %end 147 | 148 | %end 149 | 150 | %ctor{ 151 | config = [MSHFConfig loadConfigForApplication:@"Spotify"]; 152 | 153 | if(config.enabled){ 154 | config.waveOffsetOffset = 520; 155 | 156 | if ([%c(CFWPrefsManager) class] && MSHookIvar([%c(CFWPrefsManager) sharedInstance], "_spotifyEnabled") && !config.ignoreColorFlow) { 157 | MSHFColorFlowSpotifyEnabled = YES; 158 | } 159 | %init(MitsuhaVisuals); 160 | if (config.enableCoverArtBugFix) %init(MitsuhaSpotifyCoverArtFix) 161 | 162 | } 163 | } -------------------------------------------------------------------------------- /Prefs/MSHFAppPrefsListController.m: -------------------------------------------------------------------------------- 1 | #import "MSHFAppPrefsListController.h" 2 | 3 | @implementation MSHFAppPrefsListController 4 | 5 | - (NSArray *)specifiers { 6 | return _specifiers; 7 | } 8 | 9 | - (void)setSpecifier:(PSSpecifier *)specifier { 10 | [super setSpecifier:specifier]; 11 | 12 | self.appName = [specifier propertyForKey:@"MSHFApp"]; 13 | if (!self.appName) return; 14 | NSString *prefix = [@"MSHF" stringByAppendingString:self.appName]; 15 | NSString *title = [specifier name]; 16 | self.savedSpecifiers = [[NSMutableDictionary alloc] init]; 17 | 18 | _specifiers = [self loadSpecifiersFromPlistName:@"App" target:self]; 19 | 20 | for (PSSpecifier *specifier in _specifiers) { 21 | NSString *key = [specifier propertyForKey:@"key"]; 22 | if (key) { 23 | [specifier setProperty:[prefix stringByAppendingString:key] forKey:@"key"]; 24 | } 25 | 26 | if ([specifier.name isEqualToString:@"%APP_NAME%"]) { 27 | specifier.name = title; 28 | } 29 | 30 | else if ([specifier propertyForKey:@"id"]) { 31 | [self.savedSpecifiers setObject:specifier forKey:[specifier propertyForKey:@"id"]]; 32 | } 33 | } 34 | 35 | NSArray *extra = [self loadSpecifiersFromPlistName:[NSString stringWithFormat:@"Apps/%@", self.appName] target:self]; 36 | if (extra) { 37 | for (PSSpecifier *specifier in extra) { 38 | [self insertSpecifier:specifier afterSpecifierID:@"otherSettings"]; 39 | } 40 | } 41 | 42 | [self setTitle:title]; 43 | } 44 | 45 | -(void)removeBarText:(bool)animated { 46 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"BarText"]] animated:animated]; 47 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"BarSpacingText"]] animated:animated]; 48 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"BarSpacing"]] animated:animated]; 49 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"BarRadiusText"]] animated:animated]; 50 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"BarRadius"]] animated:animated]; 51 | } 52 | -(void)removeLineText:(bool)animated { 53 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"LineText"]] animated:animated]; 54 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"LineThicknessText"]] animated:animated]; 55 | [self removeContiguousSpecifiers:@[self.savedSpecifiers[@"LineThickness"]] animated:animated]; 56 | } 57 | 58 | -(void)viewDidLoad { 59 | [super viewDidLoad]; 60 | 61 | MSHFConfig *config = [MSHFConfig loadConfigForApplication:self.appName]; 62 | 63 | if (config.style != 1) { 64 | [self removeBarText:NO]; 65 | if (config.style != 2) { 66 | [self removeLineText:NO]; 67 | } 68 | } else { 69 | [self removeLineText:NO]; 70 | } 71 | } 72 | 73 | - (void)viewDidAppear:(BOOL)animated { 74 | [super viewDidAppear:animated]; 75 | self.table.separatorColor = [UIColor colorWithWhite:0 alpha:0]; 76 | 77 | UIWindow *keyWindow = [[[UIApplication sharedApplication] windows] firstObject]; 78 | 79 | if ([keyWindow respondsToSelector:@selector(setTintColor:)]) { 80 | keyWindow.tintColor = [UIColor colorWithRed:238.0f / 255.0f 81 | green:100.0f / 255.0f 82 | blue:92.0f / 255.0f 83 | alpha:1]; 84 | } 85 | 86 | [UISwitch appearanceWhenContainedInInstancesOfClasses:@[self.class]].onTintColor = [UIColor colorWithRed:238.0f / 255.0f 87 | green:100.0f / 255.0f 88 | blue:92.0f / 255.0f 89 | alpha:1]; 90 | 91 | 92 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 93 | self.edgesForExtendedLayout = UIRectEdgeNone; 94 | } 95 | } 96 | 97 | - (id)readPreferenceValue:(PSSpecifier*)specifier { 98 | NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", specifier.properties[@"defaults"]]; 99 | NSMutableDictionary *settings = [NSMutableDictionary dictionary]; 100 | [settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]]; 101 | 102 | return ([settings objectForKey:specifier.properties[@"key"]]) ?: specifier.properties[@"default"]; 103 | } 104 | 105 | - (void)setPreferenceValue:(id)value specifier:(PSSpecifier*)specifier { 106 | 107 | NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", specifier.properties[@"defaults"]]; 108 | NSMutableDictionary *settings = [NSMutableDictionary dictionary]; 109 | [settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]]; 110 | 111 | [settings setObject:value forKey:specifier.properties[@"key"]]; 112 | [settings writeToFile:path atomically:YES]; 113 | 114 | CFStringRef notificationName = (__bridge CFStringRef) specifier.properties[@"PostNotification"]; 115 | if (notificationName) { 116 | // [[NSNotificationCenter defaultCenter] postNotificationName:@"com.ryannair05.mitsuhaforever/ReloadPrefs" object:nil]; 117 | CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, NULL, NULL, YES); 118 | } 119 | 120 | NSString const *key = [specifier propertyForKey:@"key"]; 121 | 122 | if ([key containsString:@"Style"]){ 123 | if ([value integerValue] == 1) { 124 | if (![self containsSpecifier:self.savedSpecifiers[@"BarText"]]) { 125 | 126 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"BarText"]] afterSpecifierID:@"NumberOfPoints" animated:YES]; 127 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"BarSpacingText"]] afterSpecifierID:@"BarText" animated:YES]; 128 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"BarSpacing"]] afterSpecifierID:@"BarSpacingText" animated:YES]; 129 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"BarRadiusText"]] afterSpecifierID:@"BarSpacing" animated:YES]; 130 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"BarRadius"]] afterSpecifierID:@"BarRadiusText" animated:YES]; 131 | 132 | if ([self containsSpecifier:self.savedSpecifiers[@"LineText"]]) { 133 | [self removeLineText:YES]; 134 | } 135 | } 136 | } 137 | else if ([value integerValue] == 2) { 138 | 139 | if (![self containsSpecifier:self.savedSpecifiers[@"LineText"]]) { 140 | 141 | if ([self containsSpecifier:self.savedSpecifiers[@"BarText"]]) { 142 | [self removeBarText:YES]; 143 | } 144 | 145 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"LineText"]] afterSpecifierID:@"NumberOfPoints" animated:YES]; 146 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"LineThicknessText"]] afterSpecifierID:@"LineText" animated:YES]; 147 | [self insertContiguousSpecifiers:@[self.savedSpecifiers[@"LineThickness"]] afterSpecifierID:@"LineThicknessText" animated:YES]; 148 | 149 | } 150 | } 151 | else if ([self containsSpecifier:self.savedSpecifiers[@"BarText"]]) { 152 | [self removeBarText:YES]; 153 | } 154 | else if ([self containsSpecifier:self.savedSpecifiers[@"LineText"]]) { 155 | [self removeLineText:YES]; 156 | } 157 | } 158 | 159 | } 160 | 161 | - (bool)shouldReloadSpecifiersOnResume { 162 | return NO; 163 | } 164 | @end 165 | -------------------------------------------------------------------------------- /Prefs/Resources/App.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | label 11 | %APP_NAME% 12 | isStaticText 13 | 14 | 15 | 16 | cell 17 | PSSwitchCell 18 | default 19 | 20 | defaults 21 | com.ryannair05.mitsuhaforever 22 | label 23 | Enabled 24 | key 25 | Enabled 26 | alternateColors 27 | 28 | 29 | 30 | cell 31 | PSSwitchCell 32 | default 33 | 34 | defaults 35 | com.ryannair05.mitsuhaforever 36 | label 37 | FFT (frequency instead of amplitude) 38 | key 39 | EnableFFT 40 | alternateColors 41 | 42 | PostNotification 43 | com.ryannair05.mitsuhaforever/ReloadPrefs 44 | 45 | 46 | cell 47 | PSSwitchCell 48 | default 49 | 50 | defaults 51 | com.ryannair05.mitsuhaforever 52 | label 53 | Automatically hide the visualizer 54 | key 55 | EnableAutoHide 56 | alternateColors 57 | 58 | PostNotification 59 | com.ryannair05.mitsuhaforever/ReloadPrefs 60 | 61 | 62 | cell 63 | PSGroupCell 64 | label 65 | Style 66 | isStaticText 67 | 68 | 69 | 70 | cell 71 | PSSegmentCell 72 | default 73 | 0 74 | defaults 75 | com.ryannair05.mitsuhaforever 76 | key 77 | Style 78 | validValues 79 | 80 | 0 81 | 1 82 | 2 83 | 3 84 | 4 85 | 86 | validTitles 87 | 88 | Wave 89 | Bar 90 | Line 91 | Dot 92 | Siri 93 | 94 | alignment 95 | 5 96 | PostNotification 97 | com.ryannair05.mitsuhaforever/ReloadPrefs 98 | 99 | 100 | cell 101 | PSStaticTextCell 102 | label 103 | Number of points 104 | 105 | 106 | cell 107 | PSSliderCell 108 | label 109 | Number of points 110 | default 111 | 8 112 | defaults 113 | com.ryannair05.mitsuhaforever 114 | key 115 | NumberOfPoints 116 | min 117 | 4 118 | max 119 | 32 120 | showValue 121 | 122 | isSegmented 123 | 124 | id 125 | NumberOfPoints 126 | PostNotification 127 | com.ryannair05.mitsuhaforever/ReloadPrefs 128 | 129 | 130 | cell 131 | PSGroupCell 132 | label 133 | Bar 134 | isStaticText 135 | 136 | id 137 | BarText 138 | 139 | 140 | cell 141 | PSStaticTextCell 142 | label 143 | Bar spacing 144 | id 145 | BarSpacingText 146 | 147 | 148 | cell 149 | PSSliderCell 150 | label 151 | Bar spacing 152 | default 153 | 5 154 | defaults 155 | com.ryannair05.mitsuhaforever 156 | key 157 | BarSpacing 158 | min 159 | 1 160 | max 161 | 10 162 | showValue 163 | 164 | isSegmented 165 | 166 | id 167 | BarSpacing 168 | PostNotification 169 | com.ryannair05.mitsuhaforever/ReloadPrefs 170 | 171 | 172 | cell 173 | PSStaticTextCell 174 | label 175 | Bar corner radius 176 | id 177 | BarRadiusText 178 | 179 | 180 | cell 181 | PSSliderCell 182 | label 183 | Bar corner radius 184 | default 185 | 0 186 | defaults 187 | com.ryannair05.mitsuhaforever 188 | key 189 | BarCornerRadius 190 | min 191 | 0 192 | max 193 | 30 194 | showValue 195 | 196 | isSegmented 197 | 198 | id 199 | BarRadius 200 | PostNotification 201 | com.ryannair05.mitsuhaforever/ReloadPrefs 202 | 203 | 204 | cell 205 | PSGroupCell 206 | label 207 | Line 208 | isStaticText 209 | 210 | id 211 | LineText 212 | 213 | 214 | cell 215 | PSStaticTextCell 216 | label 217 | Line thickness 218 | id 219 | LineThicknessText 220 | 221 | 222 | cell 223 | PSSliderCell 224 | label 225 | Line thickness 226 | default 227 | 5 228 | defaults 229 | com.ryannair05.mitsuhaforever 230 | key 231 | LineThickness 232 | min 233 | 1 234 | max 235 | 10 236 | showValue 237 | 238 | isSegmented 239 | 240 | id 241 | LineThickness 242 | PostNotification 243 | com.ryannair05.mitsuhaforever/ReloadPrefs 244 | 245 | 246 | cell 247 | PSGroupCell 248 | label 249 | Color 250 | isStaticText 251 | 252 | footerText 253 | Siri Style and Color, credit to biD3V for development 254 | 255 | 256 | cell 257 | PSSegmentCell 258 | default 259 | 0 260 | defaults 261 | com.ryannair05.mitsuhaforever 262 | key 263 | ColorMode 264 | validValues 265 | 266 | 0 267 | 1 268 | 2 269 | 270 | validTitles 271 | 272 | Dynamic 273 | Siri 274 | Custom 275 | 276 | alignment 277 | 2 278 | PostNotification 279 | com.ryannair05.mitsuhaforever/ReloadPrefs 280 | 281 | 282 | cell 283 | PSStaticTextCell 284 | label 285 | Dynamic color alpha 286 | 287 | 288 | cell 289 | PSSliderCell 290 | label 291 | Dynamic color alpha 292 | default 293 | 0.6 294 | defaults 295 | com.ryannair05.mitsuhaforever 296 | key 297 | DynamicColorAlpha 298 | min 299 | 0.1 300 | max 301 | 1 302 | showValue 303 | 304 | isSegmented 305 | 306 | 307 | 308 | cell 309 | PSLinkCell 310 | cellClass 311 | HBColorPickerTableCell 312 | defaults 313 | com.ryannair05.mitsuhaforever 314 | default 315 | #000000:0.5 316 | key 317 | WaveColor 318 | label 319 | Wave Color 320 | showAlphaSlider 321 | 322 | PostNotification 323 | com.ryannair05.mitsuhaforever/ReloadPrefs 324 | 325 | 326 | cell 327 | PSGroupCell 328 | label 329 | Other 330 | isStaticText 331 | 332 | id 333 | otherSettings 334 | 335 | 336 | cell 337 | PSStaticTextCell 338 | label 339 | Wave offset (top) 340 | 341 | 342 | cell 343 | PSSliderCell 344 | label 345 | Wave offset (top) 346 | defaults 347 | com.ryannair05.mitsuhaforever 348 | key 349 | WaveOffset 350 | min 351 | -150 352 | max 353 | 150 354 | showValue 355 | 356 | isSegmented 357 | 358 | PostNotification 359 | com.ryannair05.mitsuhaforever/ReloadPrefs 360 | 361 | 362 | cell 363 | PSStaticTextCell 364 | label 365 | Visualizer speed/frames per second 366 | 367 | 368 | cell 369 | PSSliderCell 370 | label 371 | Visualizer speed/frames per second 372 | default 373 | 24 374 | defaults 375 | com.ryannair05.mitsuhaforever 376 | key 377 | Fps 378 | min 379 | 5 380 | max 381 | 120 382 | showValue 383 | 384 | isSegmented 385 | 386 | PostNotification 387 | com.ryannair05.mitsuhaforever/ReloadPrefs 388 | 389 | 390 | cell 391 | PSStaticTextCell 392 | label 393 | Sensitivity 394 | 395 | 396 | cell 397 | PSSliderCell 398 | label 399 | Sensitivity 400 | default 401 | 1 402 | defaults 403 | com.ryannair05.mitsuhaforever 404 | key 405 | Sensitivity 406 | min 407 | 0.25 408 | max 409 | 4 410 | showValue 411 | 412 | isSegmented 413 | 414 | PostNotification 415 | com.ryannair05.mitsuhaforever/ReloadPrefs 416 | 417 | 418 | cell 419 | PSSwitchCell 420 | default 421 | 422 | defaults 423 | com.ryannair05.mitsuhaforever 424 | label 425 | Disable battery saver 426 | key 427 | DisableBatterySaver 428 | alternateColors 429 | 430 | PostNotification 431 | com.ryannair05.mitsuhaforever/ReloadPrefs 432 | 433 | 434 | 435 | cell 436 | PSGroupCell 437 | footerText 438 | Mitsuha, Mitsuha2 by c0ldra1n 439 | MitsuhaXI, Mitsuha Infinity by Nepeta 440 | Mitsuha Forever by ConorTheDev 441 | Mitsuha Forever updated by Ryan Nair 442 | isStaticText 443 | 444 | 445 | 446 | 447 | 448 | -------------------------------------------------------------------------------- /Springboard/SBTweak.xm: -------------------------------------------------------------------------------- 1 | #import "Tweak.h" 2 | #import 3 | #import 4 | #import 5 | 6 | bool moveIntoPanel = false; 7 | static MSHFConfig *SBconfig = NULL; 8 | static MSHFConfig *SBLSconfig = NULL; 9 | 10 | %group ColorFlowMitsuhaVisualsNotification 11 | 12 | %hook CFWSBMediaController 13 | 14 | -(void)setColorInfo:(CFWColorInfo *)colorInfo { 15 | %orig; 16 | 17 | UIColor *backgroundColor = [colorInfo.primaryColor colorWithAlphaComponent:0.5]; 18 | 19 | if (SBconfig.colorMode == 0) { 20 | [[SBconfig view] updateWaveColor:backgroundColor subwaveColor:backgroundColor]; 21 | } 22 | 23 | if (SBLSconfig.colorMode == 0) { 24 | [[SBLSconfig view] updateWaveColor:backgroundColor subwaveColor:backgroundColor]; 25 | } 26 | 27 | } 28 | 29 | %end 30 | 31 | %end 32 | 33 | %group MitsuhaVisualsNotification 34 | 35 | %hook SBMediaController 36 | 37 | -(void)setNowPlayingInfo:(NSDictionary *)arg1 { 38 | %orig; 39 | 40 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 41 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 42 | UIImage *imageToColor = [UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]; 43 | 44 | [SBconfig colorizeView:imageToColor]; 45 | [SBLSconfig colorizeView:imageToColor]; 46 | } 47 | }); 48 | } 49 | 50 | %end 51 | 52 | %end 53 | 54 | %group Quart 55 | 56 | %hook QRTMediaModuleViewController 57 | %property (retain,nonatomic) MSHFView *mshfView; 58 | 59 | -(void)loadView { 60 | %orig; 61 | 62 | if (![SBconfig view]) [SBconfig initializeViewWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; 63 | self.mshfView = [SBconfig view]; 64 | 65 | [self.view addSubview:self.mshfView]; 66 | [self.view sendSubviewToBack:self.mshfView]; 67 | } 68 | 69 | -(void)setArtworkContainer:(id)arg1 { 70 | 71 | %orig; 72 | [[SBconfig view] start]; 73 | [SBconfig view].center = CGPointMake([SBconfig view].center.x, SBconfig.waveOffset); 74 | 75 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 76 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 77 | [SBconfig colorizeView:[UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]]; 78 | } 79 | }); 80 | } 81 | 82 | -(void)viewDidDisappear:(BOOL)animated{ 83 | %orig; 84 | [[SBconfig view] stop]; 85 | } 86 | %end 87 | 88 | %end 89 | 90 | #import 91 | 92 | %group ios13SB 93 | 94 | %hook CSMediaControlsViewController 95 | 96 | %property (retain,nonatomic) MSHFView *mshfView; 97 | 98 | -(void)loadView{ 99 | %orig; 100 | self.view.clipsToBounds = 1; 101 | 102 | MRPlatterViewController *pvc = nil; 103 | 104 | if (@available(iOS 15.0, *)) { 105 | pvc = object_getIvar(self, class_getInstanceVariable([self class], "_mediaRemoteViewController")); 106 | } 107 | else { 108 | pvc = object_getIvar(self, class_getInstanceVariable([self class], "_platterViewController")); 109 | } 110 | 111 | if (![SBconfig view]) [SBconfig initializeViewWithFrame:CGRectMake(-4, 0, self.view.frame.size.width - 8, self.view.frame.size.height)]; 112 | self.mshfView = [SBconfig view]; 113 | 114 | [pvc.view insertSubview:self.mshfView atIndex:0]; 115 | 116 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 117 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 118 | [SBconfig colorizeView:[UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]]; 119 | } 120 | }); 121 | } 122 | 123 | -(void)viewWillAppear:(BOOL)animated{ 124 | %orig; 125 | self.view.superview.layer.cornerRadius = 13; 126 | #pragma clang diagnostic push 127 | #pragma clang diagnostic ignored "-Wunguarded-availability-new" 128 | self.view.superview.layer.cornerCurve = kCACornerCurveContinuous; 129 | #pragma clang diagnostic pop 130 | self.view.superview.layer.masksToBounds = TRUE; 131 | } 132 | 133 | -(void)viewDidAppear:(BOOL)animated { 134 | %orig; 135 | [[SBconfig view] start]; 136 | [SBconfig view].center = CGPointMake([SBconfig view].center.x, SBconfig.waveOffset); 137 | } 138 | 139 | -(void)viewDidDisappear:(BOOL)animated{ 140 | %orig; 141 | [[SBconfig view] stop]; 142 | } 143 | 144 | - (void)platterViewController:(id)arg1 didReceiveInteractionEvent:(MediaControlsInteractionRecognizer *)arg2 { 145 | %orig; 146 | 147 | if (arg2.state == UIGestureRecognizerStateEnded) { 148 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 149 | if ([[%c(SBMediaController) sharedInstance] isPlaying]) { 150 | [[SBconfig view] start]; 151 | } 152 | else { 153 | [[SBconfig view] stop]; 154 | } 155 | }); 156 | } 157 | } 158 | %end 159 | 160 | %end 161 | 162 | %group oldSB 163 | %hook SBDashBoardMediaControlsViewController 164 | 165 | %property (retain,nonatomic) MSHFView *mshfView; 166 | 167 | %new 168 | -(id)valueForUndefinedKey:(NSString *)key { 169 | return nil; 170 | } 171 | 172 | -(void)loadView{ 173 | %orig; 174 | self.view.clipsToBounds = 1; 175 | 176 | MediaControlsPanelViewController *mcpvc = (MediaControlsPanelViewController*)[self valueForKey:@"_mediaControlsPanelViewController"]; 177 | 178 | if (!mcpvc && [self valueForKey:@"_platterViewController"]) { 179 | mcpvc = (MediaControlsPanelViewController*)[self valueForKey:@"_platterViewController"]; 180 | } 181 | 182 | if (!mcpvc) return; 183 | 184 | if (![SBconfig view]) [SBconfig initializeViewWithFrame:CGRectMake(-4, 0, self.view.frame.size.width - 8, self.view.frame.size.height)]; 185 | self.mshfView = [SBconfig view]; 186 | 187 | if (!moveIntoPanel) { 188 | [self.view addSubview:self.mshfView]; 189 | [self.view sendSubviewToBack:self.mshfView]; 190 | } else { 191 | [mcpvc.view insertSubview:self.mshfView atIndex:1]; 192 | } 193 | 194 | MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef information) { 195 | if (information && CFDictionaryContainsKey(information, kMRMediaRemoteNowPlayingInfoArtworkData)) { 196 | [SBconfig colorizeView:[UIImage imageWithData:(__bridge NSData*)CFDictionaryGetValue(information, kMRMediaRemoteNowPlayingInfoArtworkData)]]; 197 | } 198 | }); 199 | } 200 | 201 | -(void)viewWillAppear:(BOOL)animated{ 202 | %orig; 203 | self.view.superview.layer.cornerRadius = 13; 204 | self.view.superview.layer.masksToBounds = TRUE; 205 | 206 | [[SBconfig view] start]; 207 | 208 | [SBconfig view].center = CGPointMake([SBconfig view].center.x, SBconfig.waveOffset); 209 | } 210 | 211 | // -(void)viewDidAppear:(BOOL)animated { 212 | // [[SBconfig view] start]; 213 | // } 214 | 215 | -(void)viewDidDisappear:(BOOL)animated{ 216 | %orig; 217 | [[SBconfig view] stop]; 218 | } 219 | 220 | %end 221 | 222 | %end 223 | 224 | %group ios13SBLS 225 | 226 | %hook CSFixedFooterViewController 227 | 228 | %property (strong,nonatomic) MSHFView *mshfview; 229 | 230 | -(void)loadView{ 231 | %orig; 232 | SBLSconfig.waveOffsetOffset = self.view.bounds.size.height - 200; 233 | 234 | if (![SBLSconfig view]) [SBLSconfig initializeViewWithFrame:self.view.bounds]; 235 | self.mshfview = [SBLSconfig view]; 236 | 237 | [self.view addSubview:self.mshfview]; 238 | [self.view bringSubviewToFront:self.mshfview]; 239 | 240 | self.mshfview.translatesAutoresizingMaskIntoConstraints = NO; 241 | [self.mshfview.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 242 | [self.mshfview.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 243 | [self.mshfview.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; 244 | [self.mshfview.heightAnchor constraintEqualToConstant:self.mshfview.frame.size.height].active = YES; 245 | } 246 | 247 | -(void)viewWillAppear:(BOOL)animated{ 248 | %orig; 249 | if([SBLSconfig view] && [[%c(SBMediaController) sharedInstance] isPlaying]) { 250 | [self.mshfview start]; 251 | } 252 | } 253 | 254 | -(void)viewWillDisappear:(BOOL)animated{ 255 | %orig; 256 | if([SBLSconfig view]) { 257 | [self.mshfview stop]; 258 | } 259 | } 260 | 261 | %end 262 | 263 | %end 264 | 265 | 266 | %group oldSBLS 267 | 268 | %hook SBDashBoardFixedFooterViewController 269 | 270 | %property (strong,nonatomic) MSHFView *mshfview; 271 | 272 | -(void)loadView{ 273 | %orig; 274 | SBLSconfig.waveOffsetOffset = self.view.bounds.size.height - 200; 275 | 276 | if (![SBLSconfig view]) [SBLSconfig initializeViewWithFrame:self.view.bounds]; 277 | self.mshfview = [SBLSconfig view]; 278 | 279 | [self.view addSubview:self.mshfview]; 280 | [self.view bringSubviewToFront:self.mshfview]; 281 | 282 | self.mshfview.translatesAutoresizingMaskIntoConstraints = NO; 283 | [self.mshfview.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; 284 | [self.mshfview.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES; 285 | [self.mshfview.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES; 286 | [self.mshfview.heightAnchor constraintEqualToConstant:self.mshfview.frame.size.height].active = YES; 287 | } 288 | 289 | -(void)viewWillAppear:(BOOL)animated{ 290 | %orig; 291 | if([SBLSconfig view] && [[%c(SBMediaController) sharedInstance] isPlaying]) { 292 | [self.mshfview start]; 293 | } 294 | } 295 | 296 | -(void)viewWillDisappear:(BOOL)animated{ 297 | %orig; 298 | if([SBLSconfig view]) { 299 | [self.mshfview stop]; 300 | } 301 | } 302 | 303 | %end 304 | 305 | %end 306 | 307 | static void screenDisplayStatus(CFNotificationCenterRef center, void* o, CFStringRef name, const void* object, CFDictionaryRef userInfo) { 308 | uint64_t state; 309 | int token; 310 | notify_register_check("com.apple.iokit.hid.displayStatus", &token); 311 | notify_get_state(token, &state); 312 | notify_cancel(token); 313 | if (![[%c(SBMediaController) sharedInstance] isPlaying]) { 314 | state = false; 315 | } 316 | if (SBLSconfig.enabled) { 317 | if (state) { 318 | [[SBLSconfig view] start]; 319 | } else { 320 | [[SBLSconfig view] stop]; 321 | } 322 | } 323 | if (SBconfig.enabled) { 324 | if (state) { 325 | [[SBconfig view] start]; 326 | } else { 327 | [[SBconfig view] stop]; 328 | } 329 | } 330 | } 331 | 332 | static void loadPrefs() { 333 | [SBLSconfig reload]; 334 | [SBconfig reload]; 335 | } 336 | 337 | 338 | %ctor{ 339 | CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)screenDisplayStatus, (CFStringRef)@"com.apple.iokit.hid.displayStatus", NULL, (CFNotificationSuspensionBehavior)kNilOptions); 340 | CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)loadPrefs, CFSTR("com.ryannair05.mitsuhaforever/ReloadPrefs"), NULL, CFNotificationSuspensionBehaviorCoalesce); 341 | 342 | SBLSconfig = [MSHFConfig loadConfigForApplication:@"LockScreen"]; 343 | SBconfig = [MSHFConfig loadConfigForApplication:@"Springboard"]; 344 | 345 | if (SBLSconfig.enabled || SBconfig.enabled) { 346 | if ([%c(CFWPrefsManager) class] && MSHookIvar([%c(CFWPrefsManager) sharedInstance], "_lockScreenEnabled")) %init(ColorFlowMitsuhaVisualsNotification); 347 | else if (access(OrionTweakDylibFile, F_OK) == 0) { 348 | [[NSNotificationCenter defaultCenter] addObserverForName:@"OrionMusicArtworkChanged" object:nil queue:nil usingBlock:^(NSNotification *n){ 349 | 350 | UIColor *backgroundColor = [[[%c(OrionColorizer) sharedInstance] primaryColor] colorWithAlphaComponent:0.5]; 351 | 352 | if (SBconfig.colorMode == 0) { 353 | [[SBconfig view] updateWaveColor:backgroundColor subwaveColor:backgroundColor]; 354 | } 355 | 356 | if (SBLSconfig.colorMode == 0) { 357 | [[SBLSconfig view] updateWaveColor:backgroundColor subwaveColor:backgroundColor]; 358 | } 359 | 360 | }]; 361 | } 362 | else %init(MitsuhaVisualsNotification); 363 | } 364 | else return; 365 | 366 | if(SBLSconfig.enabled){ 367 | if(@available(iOS 13.0, *)) { 368 | %init(ios13SBLS) 369 | } else { 370 | %init(oldSBLS) 371 | } 372 | } 373 | 374 | if(SBconfig.enabled){ 375 | NSFileManager *fileManager = [NSFileManager defaultManager]; 376 | 377 | bool const flowPresent = [fileManager fileExistsAtPath: FlowTweakDylibFile]; 378 | if(flowPresent) { 379 | return; 380 | } 381 | 382 | bool quartPresent = [fileManager fileExistsAtPath: QuartTweakDylibFile]; 383 | 384 | if (quartPresent) { 385 | 386 | NSDictionary *quartPrefs = [[NSDictionary alloc] initWithContentsOfFile: QuartPreferencesFile]; 387 | if (quartPrefs) { 388 | quartPresent = [([quartPrefs objectForKey:@"enable"] ?: @(YES)) boolValue]; 389 | if (quartPresent) 390 | quartPresent = [([quartPrefs objectForKey:@"enableMedia"] ?: @(YES)) boolValue]; 391 | } 392 | 393 | if (quartPresent) { 394 | void *Quart = dlopen("/Library/MobileSubstrate/DynamicLibraries/Quart.dylib", RTLD_LAZY); 395 | if ([([quartPrefs objectForKey:@"largerMedia"] ?: @(NO)) boolValue]) { 396 | SBconfig.waveOffsetOffset = 385; 397 | } 398 | else { 399 | SBconfig.waveOffsetOffset = 375; 400 | } 401 | %init(Quart); 402 | dlclose(Quart); 403 | return; 404 | } 405 | } 406 | 407 | if (@available(iOS 13.0, *)) { 408 | SBconfig.waveOffsetOffset = 500; 409 | %init(ios13SB) 410 | } 411 | else { 412 | bool const artsyPresent = [fileManager fileExistsAtPath: ArtsyTweakDylibFile]; // Check if Artsy is installed 413 | 414 | if (artsyPresent) { 415 | NSLog(@"[MitsuhaForever] Artsy found"); 416 | 417 | NSDictionary *artsyPrefs = [[NSDictionary alloc] initWithContentsOfFile:ArtsyPreferencesFile]; 418 | if (artsyPrefs) { //Check if Artsy is enabled 419 | bool const artsyEnabled = [([artsyPrefs objectForKey:@"enabled"] ?: @(YES)) boolValue]; 420 | if (artsyEnabled) 421 | moveIntoPanel = [([artsyPrefs objectForKey:@"lsEnabled"] ?: @(YES)) boolValue]; 422 | } 423 | else { //It's enabled by default when Artsy is installed 424 | NSLog(@"[MitsuhaForever: ARTSY] lsEnabled = true"); 425 | moveIntoPanel = true; 426 | } 427 | } 428 | 429 | SBconfig.waveOffsetOffset = 500; 430 | %init(oldSB) 431 | } 432 | } 433 | } -------------------------------------------------------------------------------- /Prefs/MSHFPrefsListController.m: -------------------------------------------------------------------------------- 1 | #import "MSHFPrefsListController.h" 2 | 3 | @implementation MSHFPrefsListController 4 | - (instancetype)init { 5 | self = [super init]; 6 | 7 | if (self) { 8 | UIBarButtonItem *respringItem = 9 | [[UIBarButtonItem alloc] initWithTitle:@"Apply" 10 | style:UIBarButtonItemStylePlain 11 | target:self 12 | action:@selector(respring:)]; 13 | self.navigationItem.rightBarButtonItem = respringItem; 14 | } 15 | 16 | return self; 17 | } 18 | 19 | - (NSArray *)specifiers { 20 | if(!_specifiers) { 21 | _specifiers = [self loadSpecifiersFromPlistName:@"Prefs" target:self]; 22 | } 23 | return _specifiers; 24 | } 25 | 26 | - (void)loadFromSpecifier:(PSSpecifier *)specifier { 27 | _specifiers = [self loadSpecifiersFromPlistName:@"Prefs" target:self]; 28 | 29 | NSFileManager *manager = [NSFileManager defaultManager]; 30 | NSString *directory = MSHFAppSpecifiersDirectory; 31 | NSArray *appPlists = [manager contentsOfDirectoryAtPath:directory error:nil]; 32 | NSMutableArray *appSpecifiers = [NSMutableArray new]; 33 | 34 | for (NSString *filename in appPlists) { 35 | NSString *path = [directory stringByAppendingPathComponent:filename]; 36 | NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:path]; 37 | 38 | if (plist) { 39 | NSString *name = plist[@"name"] ?: [filename stringByReplacingOccurrencesOfString:@".plist" withString:@""]; 40 | NSString *title = plist[@"title"] ?: name; 41 | PSSpecifier *spec = [PSSpecifier preferenceSpecifierNamed:title target:nil set:nil get:nil detail:[MSHFAppPrefsListController class] cell:2 edit:nil]; 42 | [spec setProperty:name forKey:@"MSHFApp"]; 43 | 44 | if (plist[@"important"]) { 45 | [appSpecifiers insertObject:spec atIndex:0]; 46 | } else { 47 | [appSpecifiers addObject:spec]; 48 | } 49 | } 50 | } 51 | 52 | for (PSSpecifier *spec in [appSpecifiers reverseObjectEnumerator]) { 53 | [self insertSpecifier:spec afterSpecifierID:@"apps"]; 54 | } 55 | 56 | [self setTitle:@"Mitsuha Forever"]; 57 | [self.navigationItem setTitle:@"Mitsuha Forever"]; 58 | } 59 | 60 | - (id)readPreferenceValue:(PSSpecifier*)specifier { 61 | NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", specifier.properties[@"defaults"]]; 62 | NSMutableDictionary *settings = [NSMutableDictionary dictionary]; 63 | [settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]]; 64 | 65 | return ([settings objectForKey:specifier.properties[@"key"]]) ?: specifier.properties[@"default"]; 66 | } 67 | 68 | - (void)setPreferenceValue:(id)value specifier:(PSSpecifier*)specifier { 69 | NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", specifier.properties[@"defaults"]]; 70 | NSMutableDictionary *settings = [NSMutableDictionary dictionary]; 71 | [settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]]; 72 | 73 | [settings setObject:value forKey:specifier.properties[@"key"]]; 74 | [settings writeToFile:path atomically:YES]; 75 | } 76 | 77 | - (void)setSpecifier:(PSSpecifier *)specifier { 78 | [self loadFromSpecifier:specifier]; 79 | [super setSpecifier:specifier]; 80 | } 81 | 82 | - (void)viewWillAppear:(BOOL)animated { 83 | [super viewWillAppear:animated]; 84 | 85 | self.table.separatorColor = [UIColor colorWithWhite:0 alpha:0]; 86 | 87 | if ([self.view respondsToSelector:@selector(setTintColor:)]) { 88 | 89 | UIWindow *keyWindow = [[[UIApplication sharedApplication] windows] firstObject]; 90 | 91 | keyWindow.tintColor = [UIColor colorWithRed:238.0f / 255.0f 92 | green:100.0f / 255.0f 93 | blue:92.0f / 255.0f 94 | alpha:1]; 95 | } 96 | 97 | [UISwitch appearanceWhenContainedInInstancesOfClasses:@[self.class]].onTintColor = [UIColor colorWithRed:238.0f / 255.0f 98 | green:100.0f / 255.0f 99 | blue:92.0f / 255.0f 100 | alpha:1]; 101 | 102 | 103 | if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) { 104 | self.edgesForExtendedLayout = UIRectEdgeNone; 105 | } 106 | } 107 | 108 | - (void)viewDidDisappear:(BOOL)animated { 109 | [super viewDidDisappear:animated]; 110 | 111 | if ([self.view respondsToSelector:@selector(setTintColor:)]) { 112 | UIWindow *keyWindow = [[[UIApplication sharedApplication] windows] firstObject]; 113 | keyWindow.tintColor = nil; 114 | } 115 | } 116 | 117 | - (void)resetPrefs:(id)sender { 118 | 119 | NSString *plistPath = @"/User/Library/Preferences/com.ryannair05.mitsuhaforever.plist"; 120 | 121 | if([[NSFileManager defaultManager] fileExistsAtPath:plistPath]){ 122 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath]; 123 | [prefs removeAllObjects]; 124 | [prefs writeToFile:plistPath atomically:YES]; 125 | } 126 | 127 | [self respring:sender]; 128 | } 129 | 130 | - (bool)shouldReloadSpecifiersOnResume { 131 | return NO; 132 | } 133 | 134 | - (void)respring:(id)sender { 135 | pid_t pid; 136 | const char* args[] = {"killall", "backboardd", NULL}; 137 | posix_spawn(&pid, "/usr/bin/killall", NULL, NULL, (char* const*)args, NULL); 138 | } 139 | 140 | - (void)restartmsd:(id)sender { 141 | pid_t pid; 142 | const char* args[] = {"killall", "mediaserverd", NULL}; 143 | posix_spawn(&pid, "/usr/bin/killall", NULL, NULL, (char* const*)args, NULL); 144 | } 145 | @end 146 | 147 | 148 | @implementation MSHFTintedTableCell 149 | 150 | - (void)tintColorDidChange { 151 | [super tintColorDidChange]; 152 | 153 | self.textLabel.textColor = [UIColor colorWithRed:238.0f / 255.0f 154 | green:100.0f / 255.0f 155 | blue:92.0f / 255.0f 156 | alpha:1]; 157 | self.textLabel.highlightedTextColor = [UIColor colorWithRed:238.0f / 255.0f 158 | green:100.0f / 255.0f 159 | blue:92.0f / 255.0f 160 | alpha:1]; 161 | } 162 | 163 | - (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier { 164 | [super refreshCellContentsWithSpecifier:specifier]; 165 | 166 | if ([self respondsToSelector:@selector(tintColor)]) { 167 | self.textLabel.textColor = [UIColor colorWithRed:238.0f / 255.0f 168 | green:100.0f / 255.0f 169 | blue:92.0f / 255.0f 170 | alpha:1]; 171 | self.textLabel.highlightedTextColor = [UIColor colorWithRed:238.0f / 255.0f 172 | green:100.0f / 255.0f 173 | blue:92.0f / 255.0f 174 | alpha:1]; 175 | } 176 | } 177 | 178 | @end 179 | 180 | @implementation MSHFLinkTableCell 181 | 182 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier { 183 | self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier specifier:specifier]; 184 | 185 | if (self) { 186 | _isBig = specifier.properties[@"big"] && ((NSNumber *)specifier.properties[@"big"]).boolValue; 187 | _isAvatarCircular = specifier.properties[@"avatarCircular"] && ((NSNumber *)specifier.properties[@"avatarCircular"]).boolValue; 188 | _avatarURL = [NSURL URLWithString:specifier.properties[@"avatarURL"]]; 189 | 190 | self.selectionStyle = UITableViewCellSelectionStyleBlue; 191 | 192 | //UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"safari" inBundle:globalBundle]]; 193 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"/Library/PreferenceBundles/MitsuhaForeverPrefs.bundle/safari.png"]]]; 194 | imageView.image = [imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 195 | if (@available(iOS 13.0, *)) { 196 | imageView.tintColor = [UIColor systemGray3Color]; 197 | } 198 | self.accessoryView = imageView; 199 | 200 | self.detailTextLabel.numberOfLines = _isBig ? 0 : 1; 201 | self.detailTextLabel.text = specifier.properties[@"subtitle"] ?: @""; 202 | if (@available(iOS 13.0, *)) { 203 | self.detailTextLabel.textColor = [UIColor secondaryLabelColor]; 204 | } else { 205 | self.detailTextLabel.textColor = [UIColor systemGrayColor]; 206 | } 207 | 208 | self.specifier = specifier; 209 | if (self.shouldShowAvatar) {NSLog(@"avatar? %i %@", self.shouldShowAvatar, self.specifier.properties); 210 | CGFloat size = _isBig ? 38.f : 29.f; 211 | 212 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), NO, [UIScreen mainScreen].scale); 213 | specifier.properties[@"iconImage"] = UIGraphicsGetImageFromCurrentImageContext(); 214 | UIGraphicsEndImageContext(); 215 | 216 | _avatarView = [[UIView alloc] initWithFrame:self.imageView.bounds]; 217 | _avatarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 218 | _avatarView.backgroundColor = [UIColor colorWithWhite:0.9f alpha:1]; 219 | _avatarView.userInteractionEnabled = NO; 220 | _avatarView.clipsToBounds = YES; 221 | [self.imageView addSubview:_avatarView]; 222 | 223 | if (specifier.properties[@"initials"]) { 224 | _avatarView.backgroundColor = [UIColor colorWithWhite:0.8f alpha:1]; 225 | 226 | UILabel *label = [[UILabel alloc] initWithFrame:_avatarView.bounds]; 227 | label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 228 | label.font = [UIFont systemFontOfSize:13.f]; 229 | label.textAlignment = NSTextAlignmentCenter; 230 | label.textColor = [UIColor whiteColor]; 231 | label.text = specifier.properties[@"initials"]; 232 | [_avatarView addSubview:label]; 233 | } else { 234 | _avatarImageView = [[UIImageView alloc] initWithFrame:_avatarView.bounds]; 235 | _avatarImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 236 | _avatarImageView.alpha = 0; 237 | _avatarImageView.userInteractionEnabled = NO; 238 | _avatarImageView.layer.minificationFilter = kCAFilterTrilinear; 239 | [_avatarView addSubview:_avatarImageView]; 240 | 241 | if (_avatarURL != nil) { 242 | if (specifier.properties[@"avatarCircular"] == nil) { 243 | _isAvatarCircular = YES; 244 | } 245 | } 246 | 247 | [self loadAvatarIfNeeded]; 248 | } 249 | 250 | _avatarView.layer.cornerRadius = size / 2; 251 | } 252 | } 253 | 254 | return self; 255 | } 256 | 257 | #pragma mark - Avatar 258 | 259 | - (UIImage *)avatarImage { 260 | return _avatarImageView.image; 261 | } 262 | 263 | - (void)setAvatarImage:(UIImage *)avatarImage { 264 | _avatarImageView.image = avatarImage; 265 | 266 | // Fade in if we haven’t yet 267 | if (_avatarImageView.alpha == 0) { 268 | [UIView animateWithDuration:0.15 animations:^{ 269 | _avatarImageView.alpha = 1; 270 | }]; 271 | } 272 | } 273 | 274 | - (BOOL)shouldShowAvatar { 275 | // If we were explicitly told to show an avatar, or if we have an avatar URL or initials 276 | return (self.specifier.properties[@"showAvatar"] && ((NSNumber *)self.specifier.properties[@"showAvatar"]).boolValue) 277 | || self.specifier.properties[@"avatarURL"] != nil || self.specifier.properties[@"initials"] != nil; 278 | } 279 | 280 | - (void)loadAvatarIfNeeded { 281 | if (_avatarURL == nil || self.avatarImage != nil) { 282 | return; 283 | } 284 | } 285 | - (void)setSelected:(BOOL)arg1 animated:(BOOL)arg2 286 | { 287 | if (arg1) [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.specifier.properties[@"url"]] options:@{} completionHandler:nil]; 288 | } 289 | @end 290 | 291 | 292 | @implementation MSHFTwitterCell 293 | 294 | + (NSString *)_urlForUsername:(NSString *)user { 295 | 296 | /* if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"aphelion://"]]) { 297 | return [@"aphelion://profile/" stringByAppendingString:user]; 298 | } else */ if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tweetbot://"]]) { 299 | return [@"tweetbot:///user_profile/" stringByAppendingString:user]; 300 | } else if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"twitterrific://"]]) { 301 | return [@"twitterrific:///profile?screen_name=" stringByAppendingString:user]; 302 | } else if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tweetings://"]]) { 303 | return [@"tweetings:///user?screen_name=" stringByAppendingString:user]; 304 | } else { 305 | return [@"https://mobile.twitter.com/" stringByAppendingString:user]; 306 | } 307 | } 308 | 309 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier { 310 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier specifier:specifier]; 311 | 312 | if (self) { 313 | UIImageView *imageView = (UIImageView *)self.accessoryView; 314 | imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"/Library/PreferenceBundles/MitsuhaForeverPrefs.bundle/twitter.png"]]; 315 | imageView.image = [imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 316 | [imageView sizeToFit]; 317 | 318 | _user = [specifier.properties[@"user"] copy]; 319 | NSAssert(_user, @"User name not provided"); 320 | 321 | specifier.properties[@"url"] = [self.class _urlForUsername:_user]; 322 | 323 | self.detailTextLabel.text = [@"@" stringByAppendingString:_user]; 324 | 325 | [self loadAvatarIfNeeded]; 326 | } 327 | 328 | return self; 329 | } 330 | 331 | #pragma mark - Avatar 332 | 333 | - (BOOL)shouldShowAvatar { 334 | // HBLinkTableCell doesn’t want avatars by default, but we do. override its check method so that 335 | // if showAvatar is unset, we return YES 336 | return self.specifier.properties[@"showAvatar"] ? [super shouldShowAvatar] : YES; 337 | } 338 | 339 | - (void)loadAvatarIfNeeded { 340 | if (!_user || self.avatarImage) { 341 | return; 342 | } 343 | 344 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 345 | 346 | // NSString *size = [UIScreen mainScreen].scale > 2 ? @"original" : @"bigger"; 347 | NSError __block *err = NULL; 348 | NSData __block *data; 349 | BOOL __block reqProcessed = false; 350 | 351 | // NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://mobile.twitter.com/%@/profile_image?size=%@", _user, size]]]; 352 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://pbs.twimg.com/profile_images/1161080936836018176/4GUKuGlb_200x200.jpg"]]]; 353 | 354 | [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *_data, NSURLResponse *_response, NSError *_error) { 355 | err = _error; 356 | data = _data; 357 | reqProcessed = true; 358 | }] resume]; 359 | 360 | while (!reqProcessed) { 361 | [NSThread sleepForTimeInterval:0]; 362 | } 363 | 364 | if (err) 365 | return; 366 | 367 | UIImage *image = [UIImage imageWithData:data]; 368 | 369 | dispatch_async(dispatch_get_main_queue(), ^{ 370 | self.avatarImage = image; 371 | }); 372 | }); 373 | } 374 | 375 | @end 376 | --------------------------------------------------------------------------------