├── .gitignore ├── Dy.h ├── Dy.x ├── Makefile ├── README.md ├── Slalom.h ├── SlalomEnabler ├── Makefile ├── SlalomEnabler.plist └── Tweak.xm ├── SlalomEnableriOS10 ├── Makefile └── Tweak.xm ├── SlalomEnableriOS7 ├── Makefile └── Tweak.xm ├── SlalomEnableriOS8 ├── Makefile └── Tweak.xm ├── SlalomEnableriOS9 ├── Makefile └── Tweak.xm ├── SlalomHelper.h ├── SlalomHelper.m ├── SlalomMBProgressHUD.h ├── SlalomMBProgressHUD.m ├── SlalomPref ├── Makefile ├── Resources │ ├── Heart.png │ ├── Heart@2x.png │ ├── Info.plist │ ├── Sla.plist │ ├── Sla.png │ ├── Sla@2x.png │ └── Sla@3x.png ├── SlalomModPreferenceController.m └── entry.plist ├── SlalomUtilities.h ├── SlalomUtilities.m ├── control └── layout ├── DEBIAN ├── postinst └── prerm └── Library └── SlalomUI ├── PhotoLibrary ├── CAMShutterButtonSlalom.png ├── CAMShutterButtonSlalom@2x.png ├── PLSlalomGrabberHandle.png └── PLSlalomGrabberHandle@2x.png └── PhotosUI ├── PUSlalomBadgeIcon.png └── PUSlalomBadgeIcon@2x.png /.gitignore: -------------------------------------------------------------------------------- 1 | .theos 2 | packages/* 3 | -------------------------------------------------------------------------------- /Dy.h: -------------------------------------------------------------------------------- 1 | #import "../PS.h" 2 | #import "SlalomMBProgressHUD.h" 3 | 4 | BOOL EnableSlalom; 5 | BOOL indicatorTap; 6 | BOOL ForceSlalom; 7 | BOOL FakeFPS; 8 | BOOL hideIndicator; 9 | NSInteger mailMax; 10 | BOOL padHook = NO; 11 | NSUInteger MogulFrameRate; 12 | double rate; 13 | double volumeRamp; 14 | NSInteger aFPS; 15 | -------------------------------------------------------------------------------- /Dy.x: -------------------------------------------------------------------------------- 1 | #import "Slalom.h" 2 | #import "SlalomUtilities.h" 3 | #import "Dy.h" 4 | #import 5 | #import "../PSPrefs.x" 6 | 7 | static NSObject *cameraInstance() { 8 | return objc_getClass("CAMCaptureController") ? [objc_getClass("CAMCaptureController") sharedInstance] : [objc_getClass("PLCameraController") sharedInstance]; 9 | } 10 | 11 | static UIView *cameraView(UIView *view) { 12 | return isiOS9Up ? view.superview.superview : [cameraInstance() performSelector:@selector(delegate)]; 13 | } 14 | 15 | static void _setFPS(UIView *self) { 16 | if (FakeFPS) { 17 | [SoftSlalomMBProgressHUD showHUD:cameraView(self) text:@"❗ Fake Framerate option is enabled, disable it first if you want to change this." delay:3]; 18 | return; 19 | } 20 | NSString *message = @"Enter new FPS"; 21 | NSString *cancel = @"Cancel"; 22 | NSString *set = @"Set"; 23 | NSString *autoSet = @"Auto Set"; 24 | UIAlertView *fpsInput7 = [[UIAlertView alloc] initWithTitle:nil message:message delegate:self cancelButtonTitle:cancel otherButtonTitles:set, autoSet, nil]; 25 | fpsInput7.alertViewStyle = UIAlertViewStylePlainTextInput; 26 | UITextField *alertTextField = [fpsInput7 textFieldAtIndex:0]; 27 | alertTextField.keyboardType = UIKeyboardTypeNumberPad; 28 | alertTextField.textAlignment = NSTextAlignmentCenter; 29 | fpsInput7.tag = 5566; 30 | [fpsInput7 show]; 31 | [fpsInput7 release]; 32 | } 33 | 34 | static void _alertView_clickedButtonAtIndex(UIAlertView *alertView, NSInteger buttonIndex, UIView *self) { 35 | if (buttonIndex == 0 || alertView.tag != 5566) 36 | return; 37 | NSUInteger fps = 60; 38 | switch (buttonIndex) { 39 | case 1: 40 | { 41 | UITextField *textField = [alertView textFieldAtIndex:0]; 42 | fps = textField.text.intValue; 43 | if (fps <= 1) { 44 | [SoftSlalomMBProgressHUD showHUDWithBlock:cameraView(self) text:@"❗ Invalid FPS." delay:1.5 block:^{ 45 | [alertView show]; 46 | }]; 47 | return; 48 | } 49 | break; 50 | } 51 | case 2: 52 | fps = (NSUInteger)[SoftSlalomUtilities maximumFPS]; 53 | break; 54 | } 55 | if (![SoftSlalomUtilities isSupportedFPS:fps]) { 56 | [SoftSlalomMBProgressHUD showHUDWithBlock:cameraView(self) text:@"❗ This FPS is not supported by the device." delay:1.8 block:^{ 57 | [alertView show]; 58 | }]; 59 | return; 60 | } 61 | [(id) self sm_setFPS:fps]; 62 | } 63 | 64 | HaveCallback() { 65 | GetPrefs() 66 | GetBool2(EnableSlalom, YES) 67 | GetInt(MogulFrameRate, MogulFramerateKey, 60) 68 | GetBool2(ForceSlalom, NO) 69 | GetBool2(FakeFPS, NO) 70 | GetBool2(hideIndicator, NO) 71 | GetBool2(indicatorTap, YES) 72 | GetInt2(mailMax, 0) 73 | GetFloat2(rate, 0.25) 74 | GetFloat2(volumeRamp, 0.15) 75 | GetInt2(aFPS, 240) 76 | } 77 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_VERSION = 1.5.6a 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | AGGREGATE_NAME = SlalomEnabler 6 | SUBPROJECTS = SlalomEnableriOS7 SlalomEnableriOS8 SlalomEnableriOS9 SlalomEnableriOS10 SlalomEnabler SlalomPref 7 | 8 | include $(THEOS_MAKE_PATH)/aggregate.mk 9 | 10 | LIBRARY_NAME = SlalomShared 11 | SlalomShared_FILES = SlalomMBProgressHUD.m SlalomUtilities.m SlalomHelper.m 12 | SlalomShared_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries/SlalomEnabler 13 | 14 | include $(THEOS_MAKE_PATH)/library.mk 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Slo-mo Mod 2 | ========== 3 | 4 | Enable Slo-mo capture on iOS 7+ devices and customize it. 5 | 6 | MIT License. 7 | -------------------------------------------------------------------------------- /Slalom.h: -------------------------------------------------------------------------------- 1 | #define UNRESTRICTED_AVAILABILITY 2 | #import "../PS.h" 3 | 4 | #define TWEAK_NAME @"Slo-mo Mod" 5 | #define SAVE_TEXT @"Save Slo-mo" 6 | 7 | #define tweakIdentifier @"com.PS.SlalomMod" 8 | 9 | #define EnableSlalomKey @"EnableSlalom" 10 | #define MogulFramerateKey @"MogulFramerate" 11 | #define ForceSlalomKey @"ForceSlalom" 12 | #define FakeFPSKey @"FakeFPS" 13 | #define hideIndicatorKey @"hideIndicator" 14 | #define indicatorTapKey @"indicatorTap" 15 | #define mailMaxKey @"mailMax" 16 | #define rateKey @"rate" 17 | #define volumeRampKey @"volumeRamp" 18 | #define aFPSKey @"aFPS" 19 | 20 | @interface CAMSlalomIndicatorView (Addition) 21 | - (void)autoSetFPS:(UIGestureRecognizer *)sender; 22 | - (void)sm_setFPS:(NSUInteger)fps; 23 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSUInteger)index; 24 | @end 25 | 26 | @interface CAMFramerateIndicatorView (Addition) 27 | - (void)autoSetFPS:(UIGestureRecognizer *)sender; 28 | - (void)sm_setFPS:(NSUInteger)fps; 29 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSUInteger)index; 30 | - (void)setFramesPerSecond:(NSInteger)fps; 31 | @end 32 | 33 | @interface PLPhotoBrowserController (Addition) 34 | - (void)se_saveSlomo; 35 | @end 36 | 37 | @interface PUPhotoBrowserController (Addition) 38 | - (void)se_saveSlomo; 39 | @end 40 | 41 | @interface PUVideoEditViewController (Addition) 42 | - (void)se_saveSlomo; 43 | @end 44 | -------------------------------------------------------------------------------- /SlalomEnabler/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:latest:7.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | TWEAK_NAME = SlalomEnabler 6 | SlalomEnabler_FILES = Tweak.xm 7 | 8 | include $(THEOS_MAKE_PATH)/tweak.mk 9 | -------------------------------------------------------------------------------- /SlalomEnabler/SlalomEnabler.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomEnabler/SlalomEnabler.plist -------------------------------------------------------------------------------- /SlalomEnabler/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "../../PS.h" 2 | #import 3 | 4 | %ctor { 5 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomShared.dylib", RTLD_LAZY); 6 | if (isiOS10Up) 7 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomEnableriOS10.dylib", RTLD_LAZY); 8 | else if (isiOS9) 9 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomEnableriOS9.dylib", RTLD_LAZY); 10 | else if (isiOS8) 11 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomEnableriOS8.dylib", RTLD_LAZY); 12 | else 13 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomEnableriOS7.dylib", RTLD_LAZY); 14 | } 15 | -------------------------------------------------------------------------------- /SlalomEnableriOS10/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:latest:8.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | LIBRARY_NAME = SlalomEnableriOS10 6 | SlalomEnableriOS10_FILES = Tweak.xm 7 | SlalomEnableriOS10_FRAMEWORKS = AVFoundation CoreMedia UIKit 8 | SlalomEnableriOS10_EXTRA_FRAMEWORKS = CydiaSubstrate 9 | SlalomEnableriOS10_LIBRARIES = MobileGestalt 10 | SlalomEnableriOS10_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries/SlalomEnabler 11 | 12 | include $(THEOS_MAKE_PATH)/library.mk 13 | -------------------------------------------------------------------------------- /SlalomEnableriOS10/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "../Slalom.h" 2 | #import "../SlalomUtilities.h" 3 | #import "../SlalomHelper.h" 4 | #import "../Dy.h" 5 | #import "../Dy.x" 6 | #import 7 | #import 8 | 9 | AVCaptureFigVideoDevice *dev; 10 | 11 | %hook CAMCaptureEngine 12 | 13 | - (AVCaptureFigVideoDevice *)backCameraDevice { 14 | return dev = %orig; 15 | } 16 | 17 | %end 18 | 19 | %group Legacy 20 | 21 | %hook AVCaptureDevice 22 | 23 | - (AVCaptureDeviceFormat *)cameraVideoFormatForVideoConfiguration:(NSInteger)config { 24 | return (config == 1 || config == 2) ? [SoftSlalomUtilities bestDeviceFormat2:self] : %orig; 25 | } 26 | 27 | %end 28 | 29 | %end 30 | 31 | NSInteger _fps = -1; 32 | 33 | %hook CAMFramerateIndicatorView 34 | 35 | - (NSInteger)_framesPerSecond { 36 | return FakeFPS ? aFPS : _fps != -1 ? _fps : %orig; 37 | } 38 | 39 | %new 40 | - (void)setFramesPerSecond:(NSInteger)fps { 41 | MSHookIvar(self, "__label").text = [NSString stringWithFormat:@"%ld", (long)fps]; 42 | _fps = fps; 43 | [self _updateLabels]; 44 | } 45 | 46 | %new 47 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 48 | _alertView_clickedButtonAtIndex(alertView, buttonIndex, self); 49 | } 50 | 51 | %new 52 | - (void)sm_setFPS:(NSUInteger)fps { 53 | [self setFramesPerSecond:fps]; 54 | if (dev) { 55 | [dev lockForConfiguration:nil]; 56 | [dev setActiveVideoMinFrameDuration:CMTimeMake(1, fps)]; 57 | [dev setActiveVideoMaxFrameDuration:CMTimeMake(1, fps)]; 58 | [dev unlockForConfiguration]; 59 | } 60 | } 61 | 62 | %new 63 | - (void)autoSetFPS:(UIGestureRecognizer *)sender { 64 | [self sm_setFPS:(NSUInteger)[SoftSlalomUtilities maximumFPS]]; 65 | } 66 | 67 | %new 68 | - (void)setFPS:(UIGestureRecognizer *)sender { 69 | _setFPS(self); 70 | } 71 | 72 | %end 73 | 74 | %hook CAMViewfinderViewController 75 | 76 | - (BOOL)canBecomeFirstResponder { 77 | return YES; 78 | } 79 | 80 | - (void)_commonCAMFramerateIndicatorViewInitializationWithLayoutStyle:(NSInteger)layoutStyle { 81 | %orig; 82 | if (indicatorTap) { 83 | UITapGestureRecognizer *doubleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(autoSetFPS:)]; 84 | doubleGesture.numberOfTapsRequired = 2; 85 | UITapGestureRecognizer *singleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(setFPS:)]; 86 | singleGesture.numberOfTapsRequired = 1; 87 | [singleGesture requireGestureRecognizerToFail:doubleGesture]; 88 | [self._framerateIndicatorView addGestureRecognizer:doubleGesture]; 89 | [self._framerateIndicatorView addGestureRecognizer:singleGesture]; 90 | [singleGesture release]; 91 | [doubleGesture release]; 92 | } 93 | } 94 | 95 | %end 96 | 97 | %group Photos 98 | 99 | %hook PFSlowMotionUtilities 100 | 101 | + (NSString *)exportPresetForAsset:(id)asset preferredPreset:(NSString *)preset { 102 | if ([preset hasPrefix:@"AVAssetExportPresetMail"]) { 103 | if (mailMax == 1) 104 | return %orig(asset, @"AVAssetExportPresetHighestQuality"); 105 | } 106 | return %orig(asset, AVAssetExportPresetPassthrough); 107 | } 108 | 109 | + (void)configureExportSession:(AVAssetExportSession *)session forcePreciseConversion:(BOOL)precise { 110 | %orig; 111 | [session setMinVideoFrameDuration:CMTimeMake(1, MogulFrameRate)]; 112 | } 113 | 114 | %end 115 | 116 | %hook PFSlowMotionConfiguration 117 | 118 | - (float)volumeDuringSlowMotion { 119 | return volumeRamp; 120 | } 121 | 122 | - (float)volumeDuringRampToSlowMotion { 123 | return volumeRamp; 124 | } 125 | 126 | %end 127 | 128 | %hook PFVideoAdjustments 129 | 130 | + (float)defaultSlowMotionRateForNominalFrameRate:(float)framerate { 131 | return rate; 132 | } 133 | 134 | - (float)slowMotionRate { 135 | return rate; 136 | } 137 | 138 | - (void)setSlowMotionRate:(float)origRate { 139 | %orig(rate); 140 | } 141 | 142 | %end 143 | 144 | %hook PUVideoBannerView 145 | 146 | - (UIImage *)_badgeImageForVideoSubtype:(NSUInteger)subtype { 147 | return ForceSlalom ? [UIImage pu_PhotosUIImageNamed:@"PUBadgeSlomo"] : %orig; 148 | } 149 | 150 | %end 151 | 152 | %hook PUBadgeManager 153 | 154 | - (long long)_badgeTypeForPLAsset:(PLManagedAsset *)asset size:(long long)a2 { 155 | return ForceSlalom && [asset isVideo] ? 7 : %orig; 156 | } 157 | 158 | %end 159 | 160 | %hook PLVideoView 161 | 162 | - (BOOL)_shouldShowSlalomEditor { 163 | return (ForceSlalom || [MSHookIvar(self, "_videoCameraImage") isMogul]) ? YES : %orig; 164 | } 165 | 166 | %end 167 | 168 | %hook PUVideoEditViewController 169 | 170 | %new 171 | - (void)se_saveSlomo { 172 | [SoftSlalomHelper saveSlomo:self.view videoView:nil asset:self._videoAsset.pl_managedAsset PU:YES]; 173 | } 174 | 175 | %new 176 | - (void)se_override { 177 | UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles: 178 | [NSString stringWithFormat:@"%@ & Done", SAVE_TEXT], 179 | @"Done", nil]; 180 | sheet.tag = 95969596; 181 | [sheet showInView:self.view]; 182 | [sheet release]; 183 | } 184 | 185 | - (void)_updateButtons { 186 | %orig; 187 | if (MSHookIvar(self, "_mainButtonAction") == 1) { 188 | UIButton *btn = MSHookIvar(self, "_mainActionButton"); 189 | [btn removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents]; 190 | [btn addTarget:self action:@selector(se_override) forControlEvents:0x40]; 191 | } 192 | } 193 | 194 | %new 195 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex { 196 | if (popup.tag == 95969596) 197 | [SoftSlalomHelper slalomActionSheet2:self popup:popup buttonIndex:buttonIndex]; 198 | } 199 | 200 | %end 201 | 202 | %hook PLPublishingAgent 203 | 204 | %new 205 | - (void)se_video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 206 | [SoftSlalomHelper clearHUD]; 207 | } 208 | 209 | - (void)videoRemakerDidEndRemaking:(id)arg1 temporaryPath:(NSString *)mediaPath { 210 | if ([SoftSlalomHelper buttonAction]) { 211 | [SoftSlalomHelper setButtonAction:NO]; 212 | if (mediaPath) 213 | UISaveVideoAtPathToSavedPhotosAlbum(mediaPath, self, @selector(se_video:didFinishSavingWithError:contextInfo:), nil); 214 | } 215 | %orig; 216 | } 217 | 218 | %end 219 | 220 | %end 221 | 222 | %group assetsd 223 | 224 | %hook PLManagedAsset 225 | 226 | - (BOOL)setVideoInfoFromFileAtURL:(NSURL *)fileURL fullSizeRenderURL:(NSURL *)fullSizeURL overwriteOriginalProperties:(BOOL)overwrite { 227 | BOOL orig = %orig; 228 | NSURL *url = fullSizeURL ? fullSizeURL : fileURL; 229 | AVAsset *asset = [AVAsset assetWithURL:url]; 230 | AVAssetTrack *track = [asset tracksWithMediaType:AVMediaTypeVideo][0]; 231 | float fps = track.nominalFrameRate; 232 | short int slalomType = 3; 233 | if (ForceSlalom || self.savedAssetType == slalomType || fps > 30) 234 | self.kindSubtype = 0x65; 235 | return orig; 236 | } 237 | 238 | %end 239 | 240 | %end 241 | 242 | %hook PLManagedAsset 243 | 244 | - (BOOL)isMogul { 245 | return ForceSlalom && [self isVideo] ? YES : %orig; 246 | } 247 | 248 | %end 249 | 250 | %group MG 251 | 252 | extern "C" Boolean MGGetBoolAnswer(CFStringRef); 253 | %hookf(BOOL, MGGetBoolAnswer, CFStringRef key) { 254 | if (CFStringEqual(key, CFSTR("RearFacingCameraHFRCapability"))) 255 | return YES; 256 | return %orig; 257 | } 258 | 259 | %end 260 | 261 | %ctor { 262 | callback(); 263 | if (EnableSlalom) { 264 | HaveObserver(); 265 | if ([NSBundle.mainBundle.bundleIdentifier isEqualToString:@"com.apple.mobileslideshow"]) { 266 | dlopen("/System/Library/Frameworks/PhotosUI.framework/PhotosUI", RTLD_LAZY); 267 | %init(Photos); 268 | } 269 | %init(MG); 270 | if ([@"assetsd" isEqualToString:[NSProcessInfo processInfo].processName]) { 271 | %init(assetsd); 272 | } else { 273 | if ([SoftSlalomUtilities isLegacyDevice]) { 274 | %init(Legacy); 275 | } 276 | %init; 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /SlalomEnableriOS7/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:latest:7.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | LIBRARY_NAME = SlalomEnableriOS7 6 | SlalomEnableriOS7_FILES = Tweak.xm 7 | SlalomEnableriOS7_FRAMEWORKS = AVFoundation CoreMedia UIKit 8 | SlalomEnableriOS7_EXTRA_FRAMEWORKS = CydiaSubstrate 9 | SlalomEnableriOS7_LIBRARIES = MobileGestalt 10 | SlalomEnableriOS7_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries/SlalomEnabler 11 | 12 | include $(THEOS_MAKE_PATH)/library.mk 13 | -------------------------------------------------------------------------------- /SlalomEnableriOS7/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "../Slalom.h" 2 | #import "../SlalomUtilities.h" 3 | #import "../SlalomHelper.h" 4 | #import "../Dy.h" 5 | #import "../Dy.x" 6 | 7 | %hook PLCameraView 8 | 9 | - (BOOL)canBecomeFirstResponder { 10 | return YES; 11 | } 12 | 13 | - (void)_createSlalomIndicatorIfNecessary { 14 | padHook = YES; 15 | %orig; 16 | padHook = NO; 17 | } 18 | 19 | - (void)_showControlsForCapturingVideoAnimated:(BOOL)animated { 20 | %orig; 21 | if (self.cameraMode == 2) 22 | self._bottomBar.slalomIndicatorView.hidden = hideIndicator; 23 | } 24 | 25 | - (void)_hideControlsForCapturingVideoAnimated:(BOOL)animated { 26 | %orig; 27 | if (self.cameraMode == 2) 28 | self._bottomBar.slalomIndicatorView.hidden = NO; 29 | } 30 | 31 | %end 32 | 33 | %hook PLCameraController 34 | 35 | - (double)mogulFrameRate { 36 | return (double)MogulFrameRate; 37 | } 38 | 39 | - (AVCaptureDeviceFormat *)_mogulFormatFromDevice:(AVCaptureFigVideoDevice *)device { 40 | AVCaptureDeviceFormat *format = [SoftSlalomUtilities bestDeviceFormat2:device further:NO]; 41 | return format ? format : %orig; 42 | } 43 | 44 | %end 45 | 46 | %hook PLSlalomConfiguration 47 | 48 | - (void)setRate:(float)r { 49 | %orig(rate); 50 | } 51 | 52 | - (void)setVolumeDuringSlalom:(float)vol { 53 | %orig(volumeRamp); 54 | } 55 | 56 | - (void)setVolumeDuringRampToSlalom:(float)vol { 57 | %orig(volumeRamp); 58 | } 59 | 60 | %end 61 | 62 | %hook PLSlalomUtilities 63 | 64 | + (NSString *)exportPresetForAsset:(id)asset preferredPreset:(NSString *)preset { 65 | if ([preset hasPrefix:@"AVAssetExportPresetMail"] && mailMax == 1) 66 | return %orig(asset, @"AVAssetExportPresetHighestQuality"); 67 | return %orig(asset, AVAssetExportPresetPassthrough); 68 | } 69 | 70 | + (void)configureExportSession:(AVAssetExportSession *)session forcePreciseConversion:(BOOL)precise { 71 | %orig; 72 | session.minVideoFrameDuration = CMTimeMake(1, MogulFrameRate); 73 | } 74 | 75 | %end 76 | 77 | %hook NSUserDefaults 78 | 79 | - (BOOL)boolForKey:(NSString *)name { 80 | return [name isEqualToString:@"PLDebugCowbell"] ? YES : %orig; 81 | } 82 | 83 | %end 84 | 85 | %hook CAMSlalomIndicatorView 86 | 87 | - (void)setFramesPerSecond:(NSInteger)fps { 88 | %orig(FakeFPS ? aFPS : fps); 89 | } 90 | 91 | %new 92 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 93 | _alertView_clickedButtonAtIndex(alertView, buttonIndex, self); 94 | } 95 | 96 | %new 97 | - (void)sm_setFPS:(NSUInteger)fps { 98 | [self setFramesPerSecond:fps]; 99 | AVCaptureDevice *device = [(PLCameraController *) cameraInstance ()currentDevice]; 100 | [device lockForConfiguration:nil]; 101 | [device setActiveVideoMinFrameDuration:CMTimeMake(1, fps)]; 102 | [device setActiveVideoMaxFrameDuration:CMTimeMake(1, fps)]; 103 | [device unlockForConfiguration]; 104 | } 105 | 106 | %new 107 | - (void)autoSetFPS:(UIGestureRecognizer *)sender { 108 | [self sm_setFPS:(NSUInteger)[SoftSlalomUtilities maximumFPS]]; 109 | } 110 | 111 | %new 112 | - (void)setFPS:(UIGestureRecognizer *)sender { 113 | _setFPS(self); 114 | } 115 | 116 | - (id)initWithFrame:(CGRect)frame { 117 | self = %orig; 118 | if (indicatorTap) { 119 | UITapGestureRecognizer *doubleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(autoSetFPS:)]; 120 | doubleGesture.numberOfTapsRequired = 2; 121 | UITapGestureRecognizer *singleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(setFPS:)]; 122 | singleGesture.numberOfTapsRequired = 1; 123 | [singleGesture requireGestureRecognizerToFail:doubleGesture]; 124 | [self addGestureRecognizer:doubleGesture]; 125 | [self addGestureRecognizer:singleGesture]; 126 | [singleGesture release]; 127 | [doubleGesture release]; 128 | } 129 | return self; 130 | } 131 | 132 | %end 133 | 134 | %hook PLVideoView 135 | 136 | - (BOOL)_shouldShowSlalomEditor { 137 | return (ForceSlalom || [MSHookIvar(self, "_videoCameraImage") isMogul]) ? YES : %orig; 138 | } 139 | 140 | %end 141 | 142 | %hook PLPhotoBrowserController 143 | 144 | - (void)updateOverlaysAnimated:(BOOL)animated { 145 | %orig; 146 | [SoftSlalomHelper updateOverlays:self.currentVideoView isVideo:[[self currentAsset] isVideo] isEditingVideo:[self isEditingVideo] isCameraApp:[self isCameraApp] navigationItems:self.navigationBar.items animated:animated target:self]; 147 | } 148 | 149 | %new 150 | - (void)se_saveSlomo { 151 | [SoftSlalomHelper saveSlomo:self.view videoView:self.currentVideoView asset:self.currentVideoView.videoCameraImage PU:NO]; 152 | } 153 | 154 | %new 155 | - (void)se_multipleOptions { 156 | [SoftSlalomHelper slalomMultipleOptions:self.view target:self]; 157 | } 158 | 159 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex { 160 | if (popup.tag == 95969596) 161 | [SoftSlalomHelper slalomActionSheet:self popup:popup buttonIndex:buttonIndex]; 162 | else 163 | %orig; 164 | } 165 | 166 | %end 167 | 168 | %hook PLPublishingAgent 169 | 170 | %new 171 | - (void)se_video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 172 | [SoftSlalomHelper clearHUD]; 173 | } 174 | 175 | - (void)videoRemakerDidEndRemaking:(id)arg1 temporaryPath:(NSString *)mediaPath { 176 | if ([SoftSlalomHelper buttonAction]) { 177 | [SoftSlalomHelper setButtonAction:NO]; 178 | if (mediaPath) 179 | UISaveVideoAtPathToSavedPhotosAlbum(mediaPath, self, @selector(se_video:didFinishSavingWithError:contextInfo:), nil); 180 | } 181 | %orig; 182 | } 183 | 184 | %end 185 | 186 | %hook CAMPadApplicationSpec 187 | 188 | %new 189 | - (BOOL)shouldCreateSlalomIndicator { 190 | return YES; 191 | } 192 | 193 | %end 194 | 195 | %hook CAMBottomBar 196 | 197 | - (void)_layoutForVerticalOrientation { 198 | %orig; 199 | CGRect frame = self.frame; 200 | CGFloat midX = frame.size.width / 2 - 20.0; 201 | self.slalomIndicatorView.frame = CGRectMake(midX, 20.0, 40.0, 40.0); 202 | } 203 | 204 | %end 205 | 206 | %hook CAMCameraSpec 207 | 208 | - (BOOL)shouldCreateSlalomIndicator { 209 | return YES; 210 | } 211 | 212 | - (BOOL)isPhone { 213 | return padHook ? YES : %orig; 214 | } 215 | 216 | %end 217 | 218 | extern "C" Boolean MGGetBoolAnswer(CFStringRef); 219 | %hookf(Boolean, MGGetBoolAnswer, CFStringRef key) { 220 | if (CFStringEqual(key, CFSTR("RearFacingCameraHFRCapability"))) 221 | return YES; 222 | return %orig(key); 223 | } 224 | 225 | %ctor { 226 | callback(); 227 | if (EnableSlalom) { 228 | HaveObserver(); 229 | %init; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /SlalomEnableriOS8/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:latest:8.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | LIBRARY_NAME = SlalomEnableriOS8 6 | SlalomEnableriOS8_FILES = Tweak.xm 7 | SlalomEnableriOS8_FRAMEWORKS = AVFoundation CoreMedia UIKit 8 | SlalomEnableriOS8_EXTRA_FRAMEWORKS = CydiaSubstrate 9 | SlalomEnableriOS8_LIBRARIES = MobileGestalt 10 | SlalomEnableriOS8_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries/SlalomEnabler 11 | 12 | include $(THEOS_MAKE_PATH)/library.mk 13 | -------------------------------------------------------------------------------- /SlalomEnableriOS8/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "../Slalom.h" 2 | #import "../SlalomUtilities.h" 3 | #import "../SlalomHelper.h" 4 | #import "../Dy.h" 5 | #import "../Dy.x" 6 | #import 7 | 8 | %hook CAMCameraView 9 | 10 | - (BOOL)canBecomeFirstResponder { 11 | return YES; 12 | } 13 | 14 | - (void)_slalomIndicatorTapped:(id)arg1 { 15 | if ([(CAMCaptureController *)[%c(CAMCaptureController) sharedInstance] mogulFrameRate] < 120) 16 | return; 17 | %orig; 18 | } 19 | 20 | - (void)_createSlalomIndicatorIfNecessary { 21 | padHook = YES; 22 | %orig; 23 | padHook = NO; 24 | if (indicatorTap) { 25 | for (UIGestureRecognizer *recognizer in self._bottomBar.slalomIndicatorView.gestureRecognizers) { 26 | Ivar targetsIvar = class_getInstanceVariable([UIGestureRecognizer class], "_targets"); 27 | id targetActionPairs = object_getIvar(recognizer, targetsIvar); 28 | Class targetActionPairClass = NSClassFromString(@"UIGestureRecognizerTarget"); 29 | Ivar actionIvar = class_getInstanceVariable(targetActionPairClass, "_action"); 30 | for (id targetActionPair in targetActionPairs) { 31 | SEL action = (SEL)object_getIvar(targetActionPair, actionIvar); 32 | if ([NSStringFromSelector(action) isEqualToString:@"_slalomIndicatorTapped:"]) 33 | [self._bottomBar.slalomIndicatorView removeGestureRecognizer:recognizer]; 34 | } 35 | } 36 | } 37 | } 38 | 39 | - (void)_showControlsForCapturingVideoAnimated:(BOOL)animated { 40 | %orig; 41 | if (self.cameraMode == 2) 42 | self._bottomBar.slalomIndicatorView.hidden = hideIndicator; 43 | } 44 | 45 | - (void)_hideControlsForCapturingVideoAnimated:(BOOL)animated { 46 | %orig; 47 | if (self.cameraMode == 2) 48 | self._bottomBar.slalomIndicatorView.hidden = NO; 49 | } 50 | 51 | %end 52 | 53 | %hook PFSlowMotionUtilities 54 | 55 | + (NSString *)exportPresetForAsset:(id)asset preferredPreset:(NSString *)preset { 56 | if ([preset hasPrefix:@"AVAssetExportPresetMail"] && mailMax == 1) 57 | return %orig(asset, @"AVAssetExportPresetHighestQuality"); 58 | return %orig(asset, AVAssetExportPresetPassthrough); 59 | } 60 | 61 | + (void)configureExportSession:(AVAssetExportSession *)session forcePreciseConversion:(BOOL)precise { 62 | %orig; 63 | [session setMinVideoFrameDuration:CMTimeMake(1, MogulFrameRate)]; 64 | } 65 | 66 | %end 67 | 68 | BOOL noHigh = NO; 69 | 70 | %hook CAMCaptureController 71 | 72 | - (double)mogulFrameRate { 73 | return (double)MogulFrameRate; 74 | } 75 | 76 | - (void)_configureSessionWithCameraMode:(NSInteger)mode cameraDevice:(NSInteger)device options:(id)options { 77 | noHigh = YES; 78 | %orig; 79 | noHigh = NO; 80 | } 81 | 82 | - (AVCaptureDeviceFormat *)_mogulFormatFromDevice:(AVCaptureFigVideoDevice *)device frameRate:(BOOL)fps { 83 | if ([self mogulFrameRate] > 60) 84 | return %orig; 85 | return [SoftSlalomUtilities bestDeviceFormat2:device]; 86 | } 87 | 88 | %end 89 | 90 | %hook PFSlowMotionConfiguration 91 | 92 | - (float)volumeDuringSlowMotion { 93 | return volumeRamp; 94 | } 95 | 96 | - (float)volumeDuringRampToSlowMotion { 97 | return volumeRamp; 98 | } 99 | 100 | %end 101 | 102 | %hook PFVideoAVObjectBuilder 103 | 104 | - (id)initWithVideoAsset:(AVAsset *)videoAsset videoAdjustments:(PFVideoAdjustments *)videoAdjustments { 105 | self = %orig; 106 | if (self && ForceSlalom && videoAsset) { 107 | CMTime duration = videoAsset.duration; 108 | CMTimeRange range = [%c(PFVideoAdjustments) defaultSlowMotionTimeRangeForDuration: duration]; 109 | if (videoAdjustments == nil) { 110 | float nominalFrameRate = [(AVAssetTrack *)[[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject] nominalFrameRate]; 111 | float rate = [%c(PFVideoAdjustments) defaultSlowMotionRateForNominalFrameRate: nominalFrameRate]; 112 | PFVideoAdjustments *newVideoAdjustments = [[%c(PFVideoAdjustments) alloc] initWithSlowMotionTimeRange:range rate:rate]; 113 | MSHookIvar(self, "_videoAdjustments") = [newVideoAdjustments copy]; 114 | [newVideoAdjustments release]; 115 | } 116 | MSHookIvar(self, "_videoAdjustments").slowMotionTimeRange = range; 117 | } 118 | return self; 119 | } 120 | 121 | %end 122 | 123 | %hook PFVideoAdjustments 124 | 125 | + (float)defaultSlowMotionRateForNominalFrameRate:(float)framerate { 126 | return rate; 127 | } 128 | 129 | - (float)slowMotionRate { 130 | return rate; 131 | } 132 | 133 | - (void)setSlowMotionRate:(float)origRate { 134 | %orig(rate); 135 | } 136 | 137 | %end 138 | 139 | %hook PUVideoBannerView 140 | 141 | - (UIImage *)_badgeImageForVideoSubtype:(NSUInteger)subtype { 142 | return ForceSlalom ? [UIImage pu_PhotosUIImageNamed:@"PUSlalomBadgeIcon"] : %orig; 143 | } 144 | 145 | %end 146 | 147 | %group assetsd 148 | 149 | %hook PLManagedAsset 150 | 151 | - (BOOL)setVideoInfoFromFileAtURL:(NSURL *)fileURL fullSizeRenderURL:(NSURL *)fullSizeURL overwriteOriginalProperties:(BOOL)overwrite { 152 | BOOL orig = %orig; 153 | NSURL *url = fullSizeURL ? fullSizeURL : fileURL; 154 | AVAsset *asset = [AVAsset assetWithURL:url]; 155 | AVAssetTrack *track = [asset tracksWithMediaType:AVMediaTypeVideo][0]; 156 | float fps = track.nominalFrameRate; 157 | short int slalomType = 3; 158 | if (fps >= MogulFrameRate || ForceSlalom) 159 | self.kindSubtype = 0x65; 160 | if (self.hasAdjustments && self.savedAssetType == slalomType) 161 | self.kindSubtype = 0x65; 162 | return orig; 163 | } 164 | 165 | %end 166 | 167 | %end 168 | 169 | %hookf(CMTime, CMTimeMake, int64_t value, int32_t timescale) { 170 | return %orig(value, noHigh && (timescale > 60) ? MogulFrameRate : timescale); 171 | } 172 | 173 | %hook CAMCameraSpec 174 | 175 | - (BOOL)shouldCreateSlalomIndicator { 176 | return YES; 177 | } 178 | 179 | - (BOOL)isPhone { 180 | return padHook ? YES : %orig; 181 | } 182 | 183 | %end 184 | 185 | %hook CAMPadApplicationSpec 186 | 187 | %new 188 | - (BOOL)shouldCreateSlalomIndicator { 189 | return YES; 190 | } 191 | 192 | %end 193 | 194 | %hook CAMBottomBar 195 | 196 | - (void)_layoutForVerticalOrientation { 197 | %orig; 198 | CGRect frame = self.frame; 199 | CGFloat midX = frame.size.width/2 - 20.0; 200 | [self.slalomIndicatorView setFrame:CGRectMake(midX, 20.0, 40.0, 40.0)]; 201 | } 202 | 203 | %end 204 | 205 | %hook PLManagedAsset 206 | 207 | - (BOOL)isMogul { 208 | return ForceSlalom && [self isVideo] ? YES : %orig; 209 | } 210 | 211 | %end 212 | 213 | %hook CAMSlalomIndicatorView 214 | 215 | - (void)setFramesPerSecond:(NSInteger)fps { 216 | %orig(FakeFPS ? aFPS : fps); 217 | } 218 | 219 | %new 220 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 221 | _alertView_clickedButtonAtIndex(alertView, buttonIndex, self); 222 | } 223 | 224 | %new 225 | - (void)sm_setFPS:(NSUInteger)fps { 226 | [self setFramesPerSecond:fps]; 227 | AVCaptureDevice *device = [(CAMCaptureController *) cameraInstance ()currentDevice]; 228 | [device lockForConfiguration:nil]; 229 | [device setActiveVideoMinFrameDuration:CMTimeMake(1, fps)]; 230 | [device setActiveVideoMaxFrameDuration:CMTimeMake(1, fps)]; 231 | [device unlockForConfiguration]; 232 | } 233 | 234 | %new 235 | - (void)autoSetFPS:(UIGestureRecognizer *)sender { 236 | [self sm_setFPS:(NSUInteger)[SoftSlalomUtilities maximumFPS]]; 237 | } 238 | 239 | %new 240 | - (void)setFPS:(UIGestureRecognizer *)sender { 241 | _setFPS(self); 242 | } 243 | 244 | - (id)initWithFrame:(CGRect)frame { 245 | self = %orig; 246 | if (indicatorTap) { 247 | UITapGestureRecognizer *doubleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(autoSetFPS:)]; 248 | doubleGesture.numberOfTapsRequired = 2; 249 | UITapGestureRecognizer *singleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(setFPS:)]; 250 | singleGesture.numberOfTapsRequired = 1; 251 | [singleGesture requireGestureRecognizerToFail:doubleGesture]; 252 | [self addGestureRecognizer:doubleGesture]; 253 | [self addGestureRecognizer:singleGesture]; 254 | [singleGesture release]; 255 | [doubleGesture release]; 256 | } 257 | return self; 258 | } 259 | 260 | %end 261 | 262 | %hook PLVideoView 263 | 264 | - (BOOL)_shouldShowSlalomEditor { 265 | return (ForceSlalom || [MSHookIvar(self, "_videoCameraImage") isMogul]) ? YES : %orig; 266 | } 267 | 268 | %end 269 | 270 | %hook PLPhotoBrowserController 271 | 272 | - (void)updateOverlaysAnimated:(BOOL)animated { 273 | %orig; 274 | [SoftSlalomHelper updateOverlays:self.currentVideoView isVideo:[[self currentAsset] isVideo] isEditingVideo:[self isEditingVideo] isCameraApp:[self isCameraApp] navigationItems:self.navigationBar.items animated:animated target:self]; 275 | } 276 | 277 | %new 278 | - (void)se_saveSlomo { 279 | [SoftSlalomHelper saveSlomo:self.view videoView:self.currentVideoView asset:self.currentVideoView.videoCameraImage PU:NO]; 280 | } 281 | 282 | %new 283 | - (void)se_multipleOptions { 284 | [SoftSlalomHelper slalomMultipleOptions:self.view target:self]; 285 | } 286 | 287 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex { 288 | if (popup.tag == 95969596) 289 | [SoftSlalomHelper slalomActionSheet:self popup:popup buttonIndex:buttonIndex]; 290 | else 291 | %orig; 292 | } 293 | 294 | %end 295 | 296 | %hook PLPublishingAgent 297 | 298 | %new 299 | - (void)se_video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 300 | [SoftSlalomHelper clearHUD]; 301 | } 302 | 303 | - (void)videoRemakerDidEndRemaking:(id)arg1 temporaryPath:(NSString *)mediaPath { 304 | if ([SoftSlalomHelper buttonAction]) { 305 | [SoftSlalomHelper setButtonAction:NO]; 306 | if (mediaPath) 307 | UISaveVideoAtPathToSavedPhotosAlbum(mediaPath, self, @selector(se_video:didFinishSavingWithError:contextInfo:), nil); 308 | } 309 | %orig; 310 | } 311 | 312 | %end 313 | 314 | %group MG 315 | 316 | extern "C" Boolean MGGetBoolAnswer(CFStringRef); 317 | %hookf(Boolean, MGGetBoolAnswer, CFStringRef key) { 318 | if (CFStringEqual(key, CFSTR("RearFacingCameraHFRCapability"))) 319 | return YES; 320 | return %orig(key); 321 | } 322 | 323 | %end 324 | 325 | %ctor { 326 | callback(); 327 | if (EnableSlalom) { 328 | HaveObserver(); 329 | %init(MG); 330 | if ([@"assetsd" isEqualToString:[NSProcessInfo processInfo].processName]) { 331 | %init(assetsd); 332 | } else { 333 | %init; 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /SlalomEnableriOS9/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:9.2:8.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | LIBRARY_NAME = SlalomEnableriOS9 6 | SlalomEnableriOS9_FILES = Tweak.xm 7 | SlalomEnableriOS9_FRAMEWORKS = AVFoundation CoreMedia UIKit 8 | SlalomEnableriOS9_EXTRA_FRAMEWORKS = CydiaSubstrate 9 | SlalomEnableriOS9_LIBRARIES = MobileGestalt 10 | SlalomEnableriOS9_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries/SlalomEnabler 11 | 12 | include $(THEOS_MAKE_PATH)/library.mk 13 | -------------------------------------------------------------------------------- /SlalomEnableriOS9/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "../Slalom.h" 2 | #import "../SlalomUtilities.h" 3 | #import "../SlalomHelper.h" 4 | #import "../Dy.h" 5 | #import "../Dy.x" 6 | #import 7 | #import 8 | 9 | AVCaptureFigVideoDevice *dev; 10 | 11 | %hook CAMCaptureEngine 12 | 13 | - (AVCaptureFigVideoDevice *)backCameraDevice { 14 | return dev = %orig; 15 | } 16 | 17 | %end 18 | 19 | %group Legacy 20 | 21 | %hook CAMCaptureCapabilities 22 | 23 | - (BOOL)isSupportedSlomoModeConfiguration:(NSInteger)config forDevice:(NSInteger)device { 24 | BOOL orig = %orig; 25 | if (!orig && (config == 1 || config == 2) && device == 0) 26 | return YES; 27 | return orig; 28 | } 29 | 30 | %end 31 | 32 | %hook CAMViewfinderViewController 33 | 34 | - (NSInteger)_videoConfigurationForMode:(NSInteger)mode device:(NSInteger)device { 35 | NSInteger config = %orig; 36 | if (config == 0 && mode == 2 && device == 0) 37 | return 1; 38 | return config; 39 | } 40 | 41 | %end 42 | 43 | %hook AVCaptureDevice 44 | 45 | - (AVCaptureDeviceFormat *)cameraVideoFormatForVideoConfiguration:(NSInteger)config { 46 | return (config == 1 || config == 2) ? [SoftSlalomUtilities bestDeviceFormat2:self] : %orig; 47 | } 48 | 49 | %end 50 | 51 | %end 52 | 53 | NSInteger _fps = -1; 54 | 55 | %hook CAMFramerateIndicatorView 56 | 57 | - (NSInteger)_framesPerSecond { 58 | return FakeFPS ? aFPS : _fps != -1 ? _fps : %orig; 59 | } 60 | 61 | %new 62 | - (void)setFramesPerSecond:(NSInteger)fps { 63 | MSHookIvar(self, "__topLabel").text = [NSString stringWithFormat:@"%ld", (long)fps]; 64 | _fps = fps; 65 | [self _updateLabels]; 66 | } 67 | 68 | %new 69 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 70 | _alertView_clickedButtonAtIndex(alertView, buttonIndex, self); 71 | } 72 | 73 | %new 74 | - (void)sm_setFPS:(NSUInteger)fps { 75 | [self setFramesPerSecond:fps]; 76 | if (dev) { 77 | [dev lockForConfiguration:nil]; 78 | [dev setActiveVideoMinFrameDuration:CMTimeMake(1, fps)]; 79 | [dev setActiveVideoMaxFrameDuration:CMTimeMake(1, fps)]; 80 | [dev unlockForConfiguration]; 81 | } 82 | } 83 | 84 | %new 85 | - (void)autoSetFPS:(UIGestureRecognizer *)sender { 86 | [self sm_setFPS:(NSUInteger)[SoftSlalomUtilities maximumFPS]]; 87 | } 88 | 89 | %new 90 | - (void)setFPS:(UIGestureRecognizer *)sender { 91 | _setFPS(self); 92 | } 93 | 94 | - (id)initWithFrame:(CGRect)frame { 95 | self = %orig; 96 | if (indicatorTap) { 97 | UITapGestureRecognizer *doubleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(autoSetFPS:)]; 98 | doubleGesture.numberOfTapsRequired = 2; 99 | UITapGestureRecognizer *singleGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(setFPS:)]; 100 | singleGesture.numberOfTapsRequired = 1; 101 | [singleGesture requireGestureRecognizerToFail:doubleGesture]; 102 | [self addGestureRecognizer:doubleGesture]; 103 | [self addGestureRecognizer:singleGesture]; 104 | [singleGesture release]; 105 | [doubleGesture release]; 106 | } 107 | return self; 108 | } 109 | 110 | %end 111 | 112 | %hook CAMViewfinderViewController 113 | 114 | - (BOOL)canBecomeFirstResponder { 115 | return YES; 116 | } 117 | 118 | - (void)_startCapturingVideoWithRequest:(id)arg1 { 119 | %orig; 120 | if (self._currentMode == 2) 121 | self._framerateIndicatorView.hidden = hideIndicator; 122 | } 123 | 124 | - (void)_stopCapturingVideo { 125 | %orig; 126 | if (self._currentMode == 2) 127 | self._framerateIndicatorView.hidden = NO; 128 | } 129 | 130 | %end 131 | 132 | %group Photos 133 | 134 | %hook PFSlowMotionUtilities 135 | 136 | + (NSString *)exportPresetForAsset:(id)asset preferredPreset:(NSString *)preset { 137 | if ([preset hasPrefix:@"AVAssetExportPresetMail"] && mailMax == 1) 138 | return %orig(asset, @"AVAssetExportPresetHighestQuality"); 139 | return %orig(asset, AVAssetExportPresetPassthrough); 140 | } 141 | 142 | + (void)configureExportSession:(AVAssetExportSession *)session forcePreciseConversion:(BOOL)precise { 143 | %orig; 144 | [session setMinVideoFrameDuration:CMTimeMake(1, MogulFrameRate)]; 145 | } 146 | 147 | %end 148 | 149 | %hook PFSlowMotionConfiguration 150 | 151 | - (float)volumeDuringSlowMotion { 152 | return volumeRamp; 153 | } 154 | 155 | - (float)volumeDuringRampToSlowMotion { 156 | return volumeRamp; 157 | } 158 | 159 | %end 160 | 161 | %hook PFVideoAdjustments 162 | 163 | + (float)defaultSlowMotionRateForNominalFrameRate:(float)framerate { 164 | return rate; 165 | } 166 | 167 | - (float)slowMotionRate { 168 | return rate; 169 | } 170 | 171 | - (void)setSlowMotionRate:(float)origRate { 172 | %orig(rate); 173 | } 174 | 175 | %end 176 | 177 | %hook PUVideoBannerView 178 | 179 | - (UIImage *)_badgeImageForVideoSubtype:(NSUInteger)subtype { 180 | return ForceSlalom ? [UIImage pu_PhotosUIImageNamed:@"PUBadgeSlomo"] : %orig; 181 | } 182 | 183 | %end 184 | 185 | %hook PUBadgeManager 186 | 187 | - (long long)_badgeTypeForPLAsset:(PLManagedAsset *)asset size:(long long)a2 { 188 | return ForceSlalom && [asset isVideo] ? 7 : %orig; 189 | } 190 | 191 | %end 192 | 193 | %hook PLVideoView 194 | 195 | - (BOOL)_shouldShowSlalomEditor { 196 | BOOL isMogul = [MSHookIvar(self, "_videoCameraImage") isMogul]; 197 | return ForceSlalom || isMogul ? YES : %orig; 198 | } 199 | 200 | %end 201 | 202 | %hook PUVideoEditViewController 203 | 204 | %new 205 | - (void)se_saveSlomo { 206 | [SoftSlalomHelper saveSlomo:self.view videoView:nil asset:self._videoAsset.pl_managedAsset PU:YES]; 207 | } 208 | 209 | %new 210 | - (void)se_override { 211 | UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles: 212 | [NSString stringWithFormat:@"%@ & Done", SAVE_TEXT], 213 | @"Done", nil]; 214 | sheet.tag = 95969596; 215 | [sheet showInView:self.view]; 216 | [sheet release]; 217 | } 218 | 219 | - (void)_updateButtons { 220 | %orig; 221 | if (MSHookIvar(self, "_mainButtonAction") == 1) { 222 | UIButton *btn = MSHookIvar(self, "_mainActionButton"); 223 | [btn removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents]; 224 | [btn addTarget:self action:@selector(se_override) forControlEvents:0x40]; 225 | } 226 | } 227 | 228 | %new 229 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex { 230 | if (popup.tag == 95969596) 231 | [SoftSlalomHelper slalomActionSheet2:self popup:popup buttonIndex:buttonIndex]; 232 | } 233 | 234 | %end 235 | 236 | %hook PUPhotoBrowserController 237 | 238 | - (void)updateOverlaysAnimated:(BOOL)animated { 239 | %orig; 240 | [SoftSlalomHelper updateOverlays:self.currentVideoView isVideo:[[self currentAsset] isVideo] isEditingVideo:[self isEditingVideo] isCameraApp:[self isCameraApp] navigationItems:self.navigationBar.items animated:animated target:self]; 241 | } 242 | 243 | %new 244 | - (void)se_saveSlomo { 245 | [SoftSlalomHelper saveSlomo:self.view videoView:self.currentVideoView asset:self.currentVideoView.videoCameraImage PU:NO]; 246 | } 247 | 248 | %new 249 | - (void)se_multipleOptions { 250 | [SoftSlalomHelper slalomMultipleOptions:self.view target:self]; 251 | } 252 | 253 | - (void)actionSheet:(UIActionSheet *)popup clickedButtonAtIndex:(NSInteger)buttonIndex { 254 | if (popup.tag == 95969596) 255 | [SoftSlalomHelper slalomActionSheet:self popup:popup buttonIndex:buttonIndex]; 256 | else 257 | %orig; 258 | } 259 | 260 | %end 261 | 262 | %hook PLPublishingAgent 263 | 264 | %new 265 | - (void)se_video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { 266 | [SoftSlalomHelper clearHUD]; 267 | } 268 | 269 | - (void)videoRemakerDidEndRemaking:(id)arg1 temporaryPath:(NSString *)mediaPath { 270 | if ([SoftSlalomHelper buttonAction]) { 271 | [SoftSlalomHelper setButtonAction:NO]; 272 | if (mediaPath) 273 | UISaveVideoAtPathToSavedPhotosAlbum(mediaPath, self, @selector(se_video:didFinishSavingWithError:contextInfo:), nil); 274 | } 275 | %orig; 276 | } 277 | 278 | %end 279 | 280 | %end 281 | 282 | %group assetsd 283 | 284 | %hook PLManagedAsset 285 | 286 | - (BOOL)setVideoInfoFromFileAtURL:(NSURL *)fileURL fullSizeRenderURL:(NSURL *)fullSizeURL overwriteOriginalProperties:(BOOL)overwrite { 287 | BOOL orig = %orig; 288 | NSURL *url = fullSizeURL ? fullSizeURL : fileURL; 289 | AVAsset *asset = [AVAsset assetWithURL:url]; 290 | AVAssetTrack *track = [asset tracksWithMediaType:AVMediaTypeVideo][0]; 291 | float fps = track.nominalFrameRate; 292 | short int slalomType = 3; 293 | if (ForceSlalom || self.savedAssetType == slalomType || fps > 30) 294 | self.kindSubtype = 0x65; 295 | return orig; 296 | } 297 | 298 | %end 299 | 300 | %end 301 | 302 | %hook PLManagedAsset 303 | 304 | - (BOOL)isMogul { 305 | return [self isVideo] && ForceSlalom ? YES : %orig; 306 | } 307 | 308 | %end 309 | 310 | %group MG 311 | 312 | extern "C" Boolean MGGetBoolAnswer(CFStringRef); 313 | %hookf(Boolean, MGGetBoolAnswer, CFStringRef key) { 314 | if (CFStringEqual(key, CFSTR("RearFacingCameraHFRCapability"))) 315 | return YES; 316 | return %orig(key); 317 | } 318 | 319 | %end 320 | 321 | %ctor { 322 | callback(); 323 | if (EnableSlalom) { 324 | HaveObserver(); 325 | if ([NSBundle.mainBundle.bundleIdentifier isEqualToString:@"com.apple.mobileslideshow"]) { 326 | dlopen("/System/Library/Frameworks/PhotosUI.framework/PhotosUI", RTLD_LAZY); 327 | %init(Photos); 328 | } 329 | %init(MG); 330 | if ([@"assetsd" isEqualToString:[NSProcessInfo processInfo].processName]) { 331 | %init(assetsd); 332 | } else { 333 | if ([SoftSlalomUtilities isLegacyDevice]) { 334 | %init(Legacy); 335 | } 336 | %init; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /SlalomHelper.h: -------------------------------------------------------------------------------- 1 | #define UIFUNCTIONS_NOT_C 2 | #import "Slalom.h" 3 | #import 4 | 5 | static BOOL buttonAction; 6 | static PLProgressHUD *seHUD; 7 | 8 | @interface SlalomHelper : NSObject 9 | + (BOOL)buttonAction; 10 | + (void)setButtonAction:(BOOL)action; 11 | + (void)clearHUD; 12 | + (void)saveSlomo:(UIView *)showView videoView:(PLVideoView *)view asset:(PLManagedAsset *)asset PU:(BOOL)PU; 13 | + (void)slalomMultipleOptions:(UIView *)showView target:(id)target; 14 | + (void)slalomActionSheet:(UIViewController *)vc popup:(UIActionSheet *)popup buttonIndex:(NSInteger)buttonIndex; 15 | + (void)slalomActionSheet2:(PUVideoEditViewController *)vc popup:(UIActionSheet *)popup buttonIndex:(NSInteger)buttonIndex; 16 | + (void)updateOverlays:(PLVideoView *)videoView isVideo:(BOOL)isVideo isEditingVideo:(BOOL)isEditingVideo isCameraApp:(BOOL)isCameraApp navigationItems:(NSArray __OF(UINavigationItem *) *)items animated:(BOOL)animated target:(id)target; 17 | @end 18 | 19 | #define SoftSlalomHelper NSClassFromString(@"SlalomHelper") 20 | -------------------------------------------------------------------------------- /SlalomHelper.m: -------------------------------------------------------------------------------- 1 | #import "SlalomHelper.h" 2 | #import "SlalomMBProgressHUD.h" 3 | 4 | @implementation SlalomHelper 5 | 6 | + (void)clearHUD { 7 | if (seHUD) { 8 | [seHUD hide]; 9 | [seHUD release]; 10 | } 11 | } 12 | 13 | + (void)saveSlomo:(UIView *)showView videoView:(PLVideoView *)view asset:(PLManagedAsset *)asset PU:(BOOL)PU { 14 | BOOL cannotSave = NO; 15 | if (view == nil) 16 | cannotSave = !PU; 17 | else { 18 | id asset; 19 | object_getInstanceVariable(view, "_videoCameraImage", (void **)&asset); 20 | if (![view canEdit] || ![view _canAccessVideo] || ![view _mediaIsPlayable] || ![view _mediaIsVideo] || ![asset isMogul]) 21 | cannotSave = YES; 22 | } 23 | if (cannotSave) { 24 | [SlalomMBProgressHUD showHUD:showView text:@"❗ The video is not ready for saving." delay:3]; 25 | return; 26 | } 27 | [self setButtonAction:YES]; 28 | seHUD = [[NSClassFromString(@"PLProgressHUD") alloc] init]; 29 | [seHUD setText:@"Saving Slo-mo..."]; 30 | [seHUD showInView:showView]; 31 | PLPublishingAgent *saver = [NSClassFromString(@"PLPublishingAgent") publishingAgentForBundleNamed:@"PublishToYouTube" toPublishMedia:asset]; 32 | [saver setEnableHDUpload:YES]; 33 | [saver setMediaIsHDVideo:YES]; 34 | [saver setSelectedOption:1]; 35 | [saver setRemakerMode:[saver _remakerModeForSelectedOption]]; 36 | [saver _transcodeVideo:asset]; 37 | } 38 | 39 | + (void)slalomMultipleOptions:(UIView *)showView target:(id)target { 40 | UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:target cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles: 41 | SAVE_TEXT, 42 | @"Done", nil]; 43 | sheet.tag = 95969596; 44 | [sheet showInView:showView]; 45 | [sheet release]; 46 | } 47 | 48 | + (void)slalomActionSheet:(UIViewController *)vc popup:(UIActionSheet *)popup buttonIndex:(NSInteger)buttonIndex { 49 | switch (buttonIndex) { 50 | case 0: 51 | [(PLPhotoBrowserController *)vc se_saveSlomo]; 52 | break; 53 | case 1: 54 | [vc dismissViewControllerAnimated:YES completion:NULL]; 55 | break; 56 | } 57 | } 58 | 59 | + (void)slalomActionSheet2:(PUVideoEditViewController *)vc popup:(UIActionSheet *)popup buttonIndex:(NSInteger)buttonIndex { 60 | switch (buttonIndex) { 61 | case 0: 62 | [vc se_saveSlomo]; 63 | case 1: 64 | [vc _handleSaveButton:nil]; 65 | break; 66 | } 67 | } 68 | 69 | + (void)updateOverlays:(PLVideoView *)videoView isVideo:(BOOL)isVideo isEditingVideo:(BOOL)isEditingVideo isCameraApp:(BOOL)isCameraApp navigationItems:(NSArray __OF(UINavigationItem *) *)items animated:(BOOL)animated target:(id)target { 70 | if (isVideo) { 71 | if (videoView == nil || ![videoView _shouldShowSlalomEditor]) 72 | return; 73 | NSString *saveTitle = !isCameraApp ? SAVE_TEXT : @"..."; 74 | SEL saveAction = !isCameraApp ? @selector(se_saveSlomo) : @selector(se_multipleOptions); 75 | UIBarButtonItem *slomoBtn = [[UIBarButtonItem alloc] initWithTitle:saveTitle style:UIBarButtonItemStylePlain target:target action:saveAction]; 76 | for (UINavigationItem *item in items) { 77 | if (!isEditingVideo) 78 | [item setRightBarButtonItem:slomoBtn animated:animated]; 79 | } 80 | [slomoBtn release]; 81 | } 82 | } 83 | 84 | + (BOOL)buttonAction { 85 | return buttonAction; 86 | } 87 | 88 | + (void)setButtonAction:(BOOL)action { 89 | buttonAction = action; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /SlalomMBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 0.9 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright (c) 2013 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #define UIFUNCTIONS_NOT_C 30 | #import 31 | #import 32 | #import 33 | #import "Slalom.h" 34 | 35 | @protocol MBProgressHUDDelegate; 36 | 37 | 38 | typedef enum { 39 | /** Progress is shown using an UIActivityIndicatorView. This is the default. */ 40 | MBProgressHUDModeIndeterminate, 41 | /** Progress is shown using a round, pie-chart like, progress view. */ 42 | MBProgressHUDModeDeterminate, 43 | /** Progress is shown using a horizontal progress bar */ 44 | MBProgressHUDModeDeterminateHorizontalBar, 45 | /** Progress is shown using a ring-shaped progress view. */ 46 | MBProgressHUDModeAnnularDeterminate, 47 | /** Shows a custom view */ 48 | MBProgressHUDModeCustomView, 49 | /** Shows only labels */ 50 | MBProgressHUDModeText 51 | } MBProgressHUDMode; 52 | 53 | typedef enum { 54 | /** Opacity animation */ 55 | MBProgressHUDAnimationFade, 56 | /** Opacity + scale animation */ 57 | MBProgressHUDAnimationZoom, 58 | MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, 59 | MBProgressHUDAnimationZoomIn 60 | } MBProgressHUDAnimation; 61 | 62 | 63 | #ifndef MB_INSTANCETYPE 64 | #if __has_feature(objc_instancetype) 65 | #define MB_INSTANCETYPE instancetype 66 | #else 67 | #define MB_INSTANCETYPE id 68 | #endif 69 | #endif 70 | 71 | #ifndef MB_STRONG 72 | #if __has_feature(objc_arc) 73 | #define MB_STRONG strong 74 | #else 75 | #define MB_STRONG retain 76 | #endif 77 | #endif 78 | 79 | #ifndef MB_WEAK 80 | #if __has_feature(objc_arc_weak) 81 | #define MB_WEAK weak 82 | #elif __has_feature(objc_arc) 83 | #define MB_WEAK unsafe_unretained 84 | #else 85 | #define MB_WEAK assign 86 | #endif 87 | #endif 88 | 89 | #if NS_BLOCKS_AVAILABLE 90 | typedef void (^MBProgressHUDCompletionBlock)(); 91 | #endif 92 | 93 | 94 | /** 95 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 96 | * 97 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 98 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all 99 | * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is 100 | * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. 101 | * 102 | * This view supports four modes of operation: 103 | * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView 104 | * - MBProgressHUDModeDeterminate - shows a custom round progress indicator 105 | * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator 106 | * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (@see customView) 107 | * 108 | * All three modes can have optional labels assigned: 109 | * - If the labelText property is set and non-empty then a label containing the provided content is placed below the 110 | * indicator view. 111 | * - If also the detailsLabelText property is set then another label is placed below the first label. 112 | */ 113 | @interface SlalomMBProgressHUD : UIView 114 | 115 | /** 116 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 117 | * 118 | * @param view The view that the HUD will be added to 119 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 120 | * animations while appearing. 121 | * @return A reference to the created HUD. 122 | * 123 | * @see hideHUDForView:animated: 124 | * @see animationType 125 | */ 126 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 127 | 128 | /** 129 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 130 | * 131 | * @param view The view that is going to be searched for a HUD subview. 132 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 133 | * animations while disappearing. 134 | * @return YES if a HUD was found and removed, NO otherwise. 135 | * 136 | * @see showHUDAddedTo:animated: 137 | * @see animationType 138 | */ 139 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 140 | 141 | /** 142 | * Finds all the HUD subviews and hides them. 143 | * 144 | * @param view The view that is going to be searched for HUD subviews. 145 | * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use 146 | * animations while disappearing. 147 | * @return the number of HUDs found and removed. 148 | * 149 | * @see hideHUDForView:animated: 150 | * @see animationType 151 | */ 152 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; 153 | 154 | /** 155 | * Finds the top-most HUD subview and returns it. 156 | * 157 | * @param view The view that is going to be searched. 158 | * @return A reference to the last HUD subview discovered. 159 | */ 160 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view; 161 | 162 | /** 163 | * Finds all HUD subviews and returns them. 164 | * 165 | * @param view The view that is going to be searched. 166 | * @return All found HUD views (array of MBProgressHUD objects). 167 | */ 168 | + (NSArray *)allHUDsForView:(UIView *)view; 169 | 170 | /** 171 | * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with 172 | * window.bounds as the parameter. 173 | * 174 | * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as 175 | * the HUD's superview (i.e., the window that the HUD will be added to). 176 | */ 177 | - (id)initWithWindow:(UIWindow *)window; 178 | 179 | /** 180 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 181 | * view.bounds as the parameter 182 | * 183 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 184 | * the HUD's superview (i.e., the view that the HUD will be added to). 185 | */ 186 | - (id)initWithView:(UIView *)view; 187 | 188 | /** 189 | * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so 190 | * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread 191 | * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). 192 | * 193 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 194 | * animations while appearing. 195 | * 196 | * @see animationType 197 | */ 198 | - (void)show:(BOOL)animated; 199 | 200 | /** 201 | * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 202 | * hide the HUD when your task completes. 203 | * 204 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 205 | * animations while disappearing. 206 | * 207 | * @see animationType 208 | */ 209 | - (void)hide:(BOOL)animated; 210 | 211 | /** 212 | * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 213 | * hide the HUD when your task completes. 214 | * 215 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 216 | * animations while disappearing. 217 | * @param delay Delay in seconds until the HUD is hidden. 218 | * 219 | * @see animationType 220 | */ 221 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; 222 | 223 | /** 224 | * Shows the HUD while a background task is executing in a new thread, then hides the HUD. 225 | * 226 | * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a 227 | * pool. 228 | * 229 | * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. 230 | * @param target The object that the target method belongs to. 231 | * @param object An optional object to be passed to the method. 232 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use 233 | * animations while (dis)appearing. 234 | */ 235 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; 236 | 237 | #if NS_BLOCKS_AVAILABLE 238 | 239 | /** 240 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 241 | * 242 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 243 | */ 244 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; 245 | 246 | /** 247 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 248 | * 249 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 250 | */ 251 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; 252 | 253 | /** 254 | * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. 255 | * 256 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 257 | */ 258 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; 259 | 260 | /** 261 | * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. 262 | * 263 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will 264 | * not use animations while (dis)appearing. 265 | * @param block The block to be executed while the HUD is shown. 266 | * @param queue The dispatch queue on which the block should be executed. 267 | * @param completion The block to be executed on completion. 268 | * 269 | * @see completionBlock 270 | */ 271 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 272 | completionBlock:(MBProgressHUDCompletionBlock)completion; 273 | 274 | /** 275 | * A block that gets called after the HUD was completely hidden. 276 | */ 277 | @property (copy) MBProgressHUDCompletionBlock completionBlock; 278 | 279 | #endif 280 | 281 | /** 282 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 283 | * 284 | * @see MBProgressHUDMode 285 | */ 286 | @property (assign) MBProgressHUDMode mode; 287 | 288 | /** 289 | * The animation type that should be used when the HUD is shown and hidden. 290 | * 291 | * @see MBProgressHUDAnimation 292 | */ 293 | @property (assign) MBProgressHUDAnimation animationType; 294 | 295 | /** 296 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 297 | * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). 298 | */ 299 | @property (MB_STRONG) UIView *customView; 300 | 301 | /** 302 | * The HUD delegate object. 303 | * 304 | * @see MBProgressHUDDelegate 305 | */ 306 | @property (MB_WEAK) id delegate; 307 | 308 | /** 309 | * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 310 | * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or 311 | * set to @"", then no message is displayed. 312 | */ 313 | @property (copy) NSString *labelText; 314 | 315 | /** 316 | * An optional details message displayed below the labelText message. This message is displayed only if the labelText 317 | * property is also set and is different from an empty string (@""). The details text can span multiple lines. 318 | */ 319 | @property (copy) NSString *detailsLabelText; 320 | 321 | /** 322 | * The opacity of the HUD window. Defaults to 0.8 (80% opacity). 323 | */ 324 | @property (assign) float opacity; 325 | 326 | /** 327 | * The color of the HUD window. Defaults to black. If this property is set, color is set using 328 | * this UIColor and the opacity property is not used. using retain because performing copy on 329 | * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. 330 | */ 331 | @property (MB_STRONG) UIColor *color; 332 | 333 | /** 334 | * The x-axis offset of the HUD relative to the centre of the superview. 335 | */ 336 | @property (assign) float xOffset; 337 | 338 | /** 339 | * The y-axis offset of the HUD relative to the centre of the superview. 340 | */ 341 | @property (assign) float yOffset; 342 | 343 | /** 344 | * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). 345 | * Defaults to 20.0 346 | */ 347 | @property (assign) float margin; 348 | 349 | /** 350 | * The corner radius for the HUD 351 | * Defaults to 10.0 352 | */ 353 | @property (assign) float cornerRadius; 354 | 355 | /** 356 | * Cover the HUD background view with a radial gradient. 357 | */ 358 | @property (assign) BOOL dimBackground; 359 | 360 | /* 361 | * Grace period is the time (in seconds) that the invoked method may be run without 362 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 363 | * not be shown at all. 364 | * This may be used to prevent HUD display for very short tasks. 365 | * Defaults to 0 (no grace time). 366 | * Grace time functionality is only supported when the task status is known! 367 | * @see taskInProgress 368 | */ 369 | @property (assign) float graceTime; 370 | 371 | /** 372 | * The minimum time (in seconds) that the HUD is shown. 373 | * This avoids the problem of the HUD being shown and than instantly hidden. 374 | * Defaults to 0 (no minimum show time). 375 | */ 376 | @property (assign) float minShowTime; 377 | 378 | /** 379 | * Indicates that the executed operation is in progress. Needed for correct graceTime operation. 380 | * If you don't set a graceTime (different than 0.0) this does nothing. 381 | * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. 382 | * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), 383 | * you need to set this property when your task starts and completes in order to have normal graceTime 384 | * functionality. 385 | */ 386 | @property (assign) BOOL taskInProgress; 387 | 388 | /** 389 | * Removes the HUD from its parent view when hidden. 390 | * Defaults to NO. 391 | */ 392 | @property (assign) BOOL removeFromSuperViewOnHide; 393 | 394 | /** 395 | * Font to be used for the main label. Set this property if the default is not adequate. 396 | */ 397 | @property (MB_STRONG) UIFont *labelFont; 398 | 399 | /** 400 | * Color to be used for the main label. Set this property if the default is not adequate. 401 | */ 402 | @property (MB_STRONG) UIColor *labelColor; 403 | 404 | /** 405 | * Font to be used for the details label. Set this property if the default is not adequate. 406 | */ 407 | @property (MB_STRONG) UIFont *detailsLabelFont; 408 | 409 | /** 410 | * Color to be used for the details label. Set this property if the default is not adequate. 411 | */ 412 | @property (MB_STRONG) UIColor *detailsLabelColor; 413 | 414 | /** 415 | * The color of the activity indicator. Defaults to [UIColor whiteColor] 416 | * Does nothing on pre iOS 5. 417 | */ 418 | @property (MB_STRONG) UIColor *activityIndicatorColor; 419 | 420 | /** 421 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 422 | */ 423 | @property (assign) float progress; 424 | 425 | /** 426 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 427 | */ 428 | @property (assign) CGSize minSize; 429 | 430 | 431 | /** 432 | * The actual size of the HUD bezel. 433 | * You can use this to limit touch handling on the bezel aria only. 434 | * @see https://github.com/jdg/MBProgressHUD/pull/200 435 | */ 436 | @property (atomic, assign, readonly) CGSize size; 437 | 438 | 439 | /** 440 | * Force the HUD dimensions to be equal if possible. 441 | */ 442 | @property (assign, getter = isSquare) BOOL square; 443 | 444 | + (void)showHUDWithBlock:(id)self text:(NSString *)text delay:(double)delay block:(MBProgressHUDCompletionBlock)block; 445 | + (void)showHUD:(id)self text:(NSString *)text delay:(double)delay; 446 | 447 | @end 448 | 449 | 450 | @protocol MBProgressHUDDelegate 451 | 452 | @optional 453 | 454 | /** 455 | * Called after the HUD was fully hidden from the screen. 456 | */ 457 | - (void)hudWasHidden:(SlalomMBProgressHUD *)hud; 458 | 459 | @end 460 | 461 | #define SoftSlalomMBProgressHUD NSClassFromString(@"SlalomMBProgressHUD") 462 | -------------------------------------------------------------------------------- /SlalomMBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.m 3 | // Version 0.9 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | #import "SlalomMBProgressHUD.h" 8 | #import 9 | #import "../PS.h" 10 | 11 | 12 | #if __has_feature(objc_arc) 13 | #define MB_AUTORELEASE(exp) exp 14 | #define MB_RELEASE(exp) exp 15 | #define MB_RETAIN(exp) exp 16 | #else 17 | #define MB_AUTORELEASE(exp) [exp autorelease] 18 | #define MB_RELEASE(exp) [exp release] 19 | #define MB_RETAIN(exp) [exp retain] 20 | #endif 21 | 22 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 23 | #define MBLabelAlignmentCenter NSTextAlignmentCenter 24 | #else 25 | #define MBLabelAlignmentCenter UITextAlignmentCenter 26 | #endif 27 | 28 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 29 | #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text \ 30 | sizeWithAttributes:@{NSFontAttributeName:font}] : CGSizeZero; 31 | #else 32 | #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text sizeWithFont:font] : CGSizeZero; 33 | #endif 34 | 35 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 36 | #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ 37 | boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \ 38 | attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero; 39 | #else 40 | #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ 41 | sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero; 42 | #endif 43 | 44 | #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 45 | #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 46 | #endif 47 | 48 | #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 49 | #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 50 | #endif 51 | 52 | 53 | static const CGFloat kPadding = 4.f; 54 | static const CGFloat kLabelFontSize = 16.f; 55 | static const CGFloat kDetailsLabelFontSize = 12.f; 56 | 57 | 58 | @interface SlalomMBProgressHUD (){ 59 | BOOL useAnimation; 60 | SEL methodForExecution; 61 | id targetForExecution; 62 | id objectForExecution; 63 | UILabel *label; 64 | UILabel *detailsLabel; 65 | BOOL isFinished; 66 | CGAffineTransform rotationTransform; 67 | } 68 | 69 | @property (atomic, MB_STRONG) UIView *indicator; 70 | @property (atomic, MB_STRONG) NSTimer *graceTimer; 71 | @property (atomic, MB_STRONG) NSTimer *minShowTimer; 72 | @property (atomic, MB_STRONG) NSDate *showStarted; 73 | 74 | 75 | @end 76 | 77 | 78 | @implementation SlalomMBProgressHUD 79 | 80 | #pragma mark - Properties 81 | 82 | @synthesize animationType; 83 | @synthesize delegate; 84 | @synthesize opacity; 85 | @synthesize color; 86 | @synthesize labelFont; 87 | @synthesize labelColor; 88 | @synthesize detailsLabelFont; 89 | @synthesize detailsLabelColor; 90 | @synthesize indicator; 91 | @synthesize xOffset; 92 | @synthesize yOffset; 93 | @synthesize minSize; 94 | @synthesize square; 95 | @synthesize margin; 96 | @synthesize dimBackground; 97 | @synthesize graceTime; 98 | @synthesize minShowTime; 99 | @synthesize graceTimer; 100 | @synthesize minShowTimer; 101 | @synthesize taskInProgress; 102 | @synthesize removeFromSuperViewOnHide; 103 | @synthesize customView; 104 | @synthesize showStarted; 105 | @synthesize mode; 106 | @synthesize labelText; 107 | @synthesize detailsLabelText; 108 | @synthesize progress; 109 | @synthesize size; 110 | @synthesize activityIndicatorColor; 111 | #if NS_BLOCKS_AVAILABLE 112 | @synthesize completionBlock; 113 | #endif 114 | 115 | #pragma mark - Class methods 116 | 117 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 118 | SlalomMBProgressHUD *hud = [[self alloc] initWithView:view]; 119 | hud.removeFromSuperViewOnHide = YES; 120 | [view addSubview:hud]; 121 | [hud show:animated]; 122 | return MB_AUTORELEASE(hud); 123 | } 124 | 125 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 126 | SlalomMBProgressHUD *hud = [self HUDForView:view]; 127 | if (hud != nil) { 128 | hud.removeFromSuperViewOnHide = YES; 129 | [hud hide:animated]; 130 | return YES; 131 | } 132 | return NO; 133 | } 134 | 135 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { 136 | NSArray *huds = [SlalomMBProgressHUD allHUDsForView:view]; 137 | for (SlalomMBProgressHUD *hud in huds) { 138 | hud.removeFromSuperViewOnHide = YES; 139 | [hud hide:animated]; 140 | } 141 | return [huds count]; 142 | } 143 | 144 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view { 145 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 146 | for (UIView *subview in subviewsEnum) { 147 | if ([subview isKindOfClass:self]) { 148 | return (SlalomMBProgressHUD *)subview; 149 | } 150 | } 151 | return nil; 152 | } 153 | 154 | + (NSArray *)allHUDsForView:(UIView *)view { 155 | NSMutableArray *huds = [NSMutableArray array]; 156 | NSArray *subviews = view.subviews; 157 | for (UIView *aView in subviews) { 158 | if ([aView isKindOfClass:self]) { 159 | [huds addObject:aView]; 160 | } 161 | } 162 | return [NSArray arrayWithArray:huds]; 163 | } 164 | 165 | #pragma mark - Lifecycle 166 | 167 | - (id)initWithFrame:(CGRect)frame { 168 | self = [super initWithFrame:frame]; 169 | if (self) { 170 | // Set default values for properties 171 | self.animationType = MBProgressHUDAnimationFade; 172 | self.mode = MBProgressHUDModeIndeterminate; 173 | self.labelText = nil; 174 | self.detailsLabelText = nil; 175 | self.opacity = 0.8f; 176 | self.color = nil; 177 | self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; 178 | self.labelColor = [UIColor whiteColor]; 179 | self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; 180 | self.detailsLabelColor = [UIColor whiteColor]; 181 | self.activityIndicatorColor = [UIColor whiteColor]; 182 | self.xOffset = 0.0f; 183 | self.yOffset = 0.0f; 184 | self.dimBackground = NO; 185 | self.margin = 20.0f; 186 | self.cornerRadius = 10.0f; 187 | self.graceTime = 0.0f; 188 | self.minShowTime = 0.0f; 189 | self.removeFromSuperViewOnHide = NO; 190 | self.minSize = CGSizeZero; 191 | self.square = NO; 192 | self.contentMode = UIViewContentModeCenter; 193 | self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin 194 | | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; 195 | 196 | // Transparent background 197 | self.opaque = NO; 198 | self.backgroundColor = [UIColor clearColor]; 199 | // Make it invisible for now 200 | self.alpha = 0.0f; 201 | 202 | taskInProgress = NO; 203 | rotationTransform = CGAffineTransformIdentity; 204 | 205 | [self setupLabels]; 206 | [self updateIndicators]; 207 | [self registerForKVO]; 208 | [self registerForNotifications]; 209 | } 210 | return self; 211 | } 212 | 213 | - (id)initWithView:(UIView *)view { 214 | NSAssert(view, @"View must not be nil."); 215 | return [self initWithFrame:view.bounds]; 216 | } 217 | 218 | - (id)initWithWindow:(UIWindow *)window { 219 | return [self initWithView:window]; 220 | } 221 | 222 | - (void)dealloc { 223 | [self unregisterFromNotifications]; 224 | [self unregisterFromKVO]; 225 | #if !__has_feature(objc_arc) 226 | [color release]; 227 | [indicator release]; 228 | [label release]; 229 | [detailsLabel release]; 230 | [labelText release]; 231 | [detailsLabelText release]; 232 | [graceTimer release]; 233 | [minShowTimer release]; 234 | [showStarted release]; 235 | [customView release]; 236 | [labelFont release]; 237 | [labelColor release]; 238 | [detailsLabelFont release]; 239 | [detailsLabelColor release]; 240 | #if NS_BLOCKS_AVAILABLE 241 | [completionBlock release]; 242 | #endif 243 | [super dealloc]; 244 | #endif 245 | } 246 | 247 | #pragma mark - Show & hide 248 | 249 | - (void)show:(BOOL)animated { 250 | useAnimation = animated; 251 | // If the grace time is set postpone the HUD display 252 | if (self.graceTime > 0.0) { 253 | self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self 254 | selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 255 | } 256 | // ... otherwise show the HUD imediately 257 | else { 258 | [self setNeedsDisplay]; 259 | [self showUsingAnimation:useAnimation]; 260 | } 261 | } 262 | 263 | - (void)hide:(BOOL)animated { 264 | useAnimation = animated; 265 | // If the minShow time is set, calculate how long the hud was shown, 266 | // and pospone the hiding operation if necessary 267 | if (self.minShowTime > 0.0 && showStarted) { 268 | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; 269 | if (interv < self.minShowTime) { 270 | self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self 271 | selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; 272 | return; 273 | } 274 | } 275 | // ... otherwise hide the HUD immediately 276 | [self hideUsingAnimation:useAnimation]; 277 | } 278 | 279 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { 280 | [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay]; 281 | } 282 | 283 | - (void)hideDelayed:(NSNumber *)animated { 284 | [self hide:[animated boolValue]]; 285 | } 286 | 287 | #pragma mark - Timer callbacks 288 | 289 | - (void)handleGraceTimer:(NSTimer *)theTimer { 290 | // Show the HUD only if the task is still running 291 | if (taskInProgress) { 292 | [self setNeedsDisplay]; 293 | [self showUsingAnimation:useAnimation]; 294 | } 295 | } 296 | 297 | - (void)handleMinShowTimer:(NSTimer *)theTimer { 298 | [self hideUsingAnimation:useAnimation]; 299 | } 300 | 301 | #pragma mark - View Hierrarchy 302 | 303 | - (BOOL)shouldPerformOrientationTransform { 304 | BOOL isPreiOS8 = NSFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0; 305 | // prior to iOS8 code needs to take care of rotation if it is being added to the window 306 | return isPreiOS8 && [self.superview isKindOfClass:[UIWindow class]]; 307 | } 308 | 309 | - (void)didMoveToSuperview { 310 | if ([self shouldPerformOrientationTransform]) { 311 | [self setTransformForCurrentOrientation:NO]; 312 | } 313 | } 314 | 315 | #pragma mark - Internal show & hide operations 316 | 317 | - (void)showUsingAnimation:(BOOL)animated { 318 | if (animated && animationType == MBProgressHUDAnimationZoomIn) { 319 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 320 | } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { 321 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 322 | } 323 | self.showStarted = [NSDate date]; 324 | // Fade in 325 | if (animated) { 326 | [UIView beginAnimations:nil context:NULL]; 327 | [UIView setAnimationDuration:0.30]; 328 | self.alpha = 1.0f; 329 | if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { 330 | self.transform = rotationTransform; 331 | } 332 | [UIView commitAnimations]; 333 | } else { 334 | self.alpha = 1.0f; 335 | } 336 | } 337 | 338 | - (void)hideUsingAnimation:(BOOL)animated { 339 | // Fade out 340 | if (animated && showStarted) { 341 | [UIView beginAnimations:nil context:NULL]; 342 | [UIView setAnimationDuration:0.30]; 343 | [UIView setAnimationDelegate:self]; 344 | [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; 345 | // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden 346 | // in the done method 347 | if (animationType == MBProgressHUDAnimationZoomIn) { 348 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 349 | } else if (animationType == MBProgressHUDAnimationZoomOut) { 350 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 351 | } 352 | 353 | self.alpha = 0.02f; 354 | [UIView commitAnimations]; 355 | } else { 356 | self.alpha = 0.0f; 357 | [self done]; 358 | } 359 | self.showStarted = nil; 360 | } 361 | 362 | - (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context { 363 | [self done]; 364 | } 365 | 366 | - (void)done { 367 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 368 | isFinished = YES; 369 | self.alpha = 0.0f; 370 | if (removeFromSuperViewOnHide) { 371 | [self removeFromSuperview]; 372 | } 373 | #if NS_BLOCKS_AVAILABLE 374 | if (self.completionBlock) { 375 | self.completionBlock(); 376 | self.completionBlock = NULL; 377 | } 378 | #endif 379 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 380 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 381 | } 382 | } 383 | 384 | #pragma mark - Threading 385 | 386 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 387 | methodForExecution = method; 388 | targetForExecution = MB_RETAIN(target); 389 | objectForExecution = MB_RETAIN(object); 390 | // Launch execution in new thread 391 | self.taskInProgress = YES; 392 | [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; 393 | // Show HUD view 394 | [self show:animated]; 395 | } 396 | 397 | #if NS_BLOCKS_AVAILABLE 398 | 399 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { 400 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 401 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 402 | } 403 | 404 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { 405 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 406 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; 407 | } 408 | 409 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { 410 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 411 | } 412 | 413 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 414 | completionBlock:(MBProgressHUDCompletionBlock)completion { 415 | self.taskInProgress = YES; 416 | self.completionBlock = completion; 417 | dispatch_async(queue, ^(void) { 418 | block(); 419 | dispatch_async(dispatch_get_main_queue(), ^(void) { 420 | [self cleanUp]; 421 | }); 422 | }); 423 | [self show:animated]; 424 | } 425 | 426 | #endif 427 | 428 | - (void)launchExecution { 429 | @autoreleasepool { 430 | #pragma clang diagnostic push 431 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 432 | // Start executing the requested task 433 | [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; 434 | #pragma clang diagnostic pop 435 | // Task completed, update view in main thread (note: view operations should 436 | // be done only in the main thread) 437 | [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; 438 | } 439 | } 440 | 441 | - (void)cleanUp { 442 | taskInProgress = NO; 443 | #if !__has_feature(objc_arc) 444 | [targetForExecution release]; 445 | [objectForExecution release]; 446 | #else 447 | targetForExecution = nil; 448 | objectForExecution = nil; 449 | #endif 450 | [self hide:useAnimation]; 451 | } 452 | 453 | #pragma mark - UI 454 | 455 | - (void)setupLabels { 456 | label = [[UILabel alloc] initWithFrame:self.bounds]; 457 | label.adjustsFontSizeToFitWidth = NO; 458 | label.textAlignment = MBLabelAlignmentCenter; 459 | label.opaque = NO; 460 | label.backgroundColor = [UIColor clearColor]; 461 | label.textColor = self.labelColor; 462 | label.font = self.labelFont; 463 | label.text = self.labelText; 464 | [self addSubview:label]; 465 | 466 | detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; 467 | detailsLabel.font = self.detailsLabelFont; 468 | detailsLabel.adjustsFontSizeToFitWidth = NO; 469 | detailsLabel.textAlignment = MBLabelAlignmentCenter; 470 | detailsLabel.opaque = NO; 471 | detailsLabel.backgroundColor = [UIColor clearColor]; 472 | detailsLabel.textColor = self.detailsLabelColor; 473 | detailsLabel.numberOfLines = 0; 474 | detailsLabel.font = self.detailsLabelFont; 475 | detailsLabel.text = self.detailsLabelText; 476 | [self addSubview:detailsLabel]; 477 | } 478 | 479 | - (void)updateIndicators { 480 | 481 | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; 482 | 483 | if (mode == MBProgressHUDModeIndeterminate) { 484 | if (!isActivityIndicator) { 485 | // Update to indeterminate indicator 486 | [indicator removeFromSuperview]; 487 | self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] 488 | initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); 489 | [(UIActivityIndicatorView *) indicator startAnimating]; 490 | [self addSubview:indicator]; 491 | } 492 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 493 | [(UIActivityIndicatorView *) indicator setColor:self.activityIndicatorColor]; 494 | #endif 495 | } else if (mode == MBProgressHUDModeCustomView && customView != indicator) { 496 | // Update custom view indicator 497 | [indicator removeFromSuperview]; 498 | self.indicator = customView; 499 | [self addSubview:indicator]; 500 | } else if (mode == MBProgressHUDModeText) { 501 | [indicator removeFromSuperview]; 502 | self.indicator = nil; 503 | } 504 | } 505 | 506 | #pragma mark - Layout 507 | 508 | - (void)layoutSubviews { 509 | [super layoutSubviews]; 510 | 511 | // Entirely cover the parent view 512 | UIView *parent = self.superview; 513 | if (parent) { 514 | self.frame = parent.bounds; 515 | } 516 | CGRect bounds = self.bounds; 517 | 518 | // Determine the total widt and height needed 519 | CGFloat maxWidth = bounds.size.width - 4 * margin; 520 | CGSize totalSize = CGSizeZero; 521 | 522 | CGRect indicatorF = indicator.bounds; 523 | indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); 524 | totalSize.width = MAX(totalSize.width, indicatorF.size.width); 525 | totalSize.height += indicatorF.size.height; 526 | 527 | CGSize labelSize = MB_TEXTSIZE(label.text, label.font); 528 | labelSize.width = MIN(labelSize.width, maxWidth); 529 | totalSize.width = MAX(totalSize.width, labelSize.width); 530 | totalSize.height += labelSize.height; 531 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 532 | totalSize.height += kPadding; 533 | } 534 | 535 | CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; 536 | CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); 537 | CGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode); 538 | totalSize.width = MAX(totalSize.width, detailsLabelSize.width); 539 | totalSize.height += detailsLabelSize.height; 540 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 541 | totalSize.height += kPadding; 542 | } 543 | 544 | totalSize.width += 2 * margin; 545 | totalSize.height += 2 * margin; 546 | 547 | // Position elements 548 | CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; 549 | CGFloat xPos = xOffset; 550 | indicatorF.origin.y = yPos; 551 | indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos; 552 | indicator.frame = indicatorF; 553 | yPos += indicatorF.size.height; 554 | 555 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 556 | yPos += kPadding; 557 | } 558 | CGRect labelF; 559 | labelF.origin.y = yPos; 560 | labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos; 561 | labelF.size = labelSize; 562 | label.frame = labelF; 563 | yPos += labelF.size.height; 564 | 565 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 566 | yPos += kPadding; 567 | } 568 | CGRect detailsLabelF; 569 | detailsLabelF.origin.y = yPos; 570 | detailsLabelF.origin.x = round((bounds.size.width - detailsLabelSize.width) / 2) + xPos; 571 | detailsLabelF.size = detailsLabelSize; 572 | detailsLabel.frame = detailsLabelF; 573 | 574 | // Enforce minsize and quare rules 575 | if (square) { 576 | CGFloat max = MAX(totalSize.width, totalSize.height); 577 | if (max <= bounds.size.width - 2 * margin) { 578 | totalSize.width = max; 579 | } 580 | if (max <= bounds.size.height - 2 * margin) { 581 | totalSize.height = max; 582 | } 583 | } 584 | if (totalSize.width < minSize.width) { 585 | totalSize.width = minSize.width; 586 | } 587 | if (totalSize.height < minSize.height) { 588 | totalSize.height = minSize.height; 589 | } 590 | 591 | size = totalSize; 592 | } 593 | 594 | #pragma mark BG Drawing 595 | 596 | - (void)drawRect:(CGRect)rect { 597 | 598 | CGContextRef context = UIGraphicsGetCurrentContext(); 599 | UIGraphicsPushContext(context); 600 | 601 | if (self.dimBackground) { 602 | //Gradient colours 603 | size_t gradLocationsNum = 2; 604 | CGFloat gradLocations[2] = { 605 | 0.0f, 1.0f 606 | }; 607 | CGFloat gradColors[8] = { 608 | 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.75f 609 | }; 610 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 611 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); 612 | CGColorSpaceRelease(colorSpace); 613 | //Gradient center 614 | CGPoint gradCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 615 | //Gradient radius 616 | float gradRadius = MIN(self.bounds.size.width, self.bounds.size.height); 617 | //Gradient draw 618 | CGContextDrawRadialGradient(context, gradient, gradCenter, 619 | 0, gradCenter, gradRadius, 620 | kCGGradientDrawsAfterEndLocation); 621 | CGGradientRelease(gradient); 622 | } 623 | 624 | // Set background rect color 625 | if (self.color) { 626 | CGContextSetFillColorWithColor(context, self.color.CGColor); 627 | } else { 628 | CGContextSetGrayFillColor(context, 0.0f, self.opacity); 629 | } 630 | 631 | 632 | // Center HUD 633 | CGRect allRect = self.bounds; 634 | // Draw rounded HUD backgroud rect 635 | CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset, 636 | round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); 637 | float radius = self.cornerRadius; 638 | CGContextBeginPath(context); 639 | CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); 640 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0); 641 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0); 642 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0); 643 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0); 644 | CGContextClosePath(context); 645 | CGContextFillPath(context); 646 | 647 | UIGraphicsPopContext(); 648 | } 649 | 650 | #pragma mark - KVO 651 | 652 | - (void)registerForKVO { 653 | for (NSString *keyPath in [self observableKeypaths]) { 654 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 655 | } 656 | } 657 | 658 | - (void)unregisterFromKVO { 659 | for (NSString *keyPath in [self observableKeypaths]) { 660 | [self removeObserver:self forKeyPath:keyPath]; 661 | } 662 | } 663 | 664 | - (NSArray *)observableKeypaths { 665 | return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", 666 | @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil]; 667 | } 668 | 669 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 670 | if (![NSThread isMainThread]) { 671 | [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; 672 | } else { 673 | [self updateUIForKeypath:keyPath]; 674 | } 675 | } 676 | 677 | - (void)updateUIForKeypath:(NSString *)keyPath { 678 | if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || 679 | [keyPath isEqualToString:@"activityIndicatorColor"]) { 680 | [self updateIndicators]; 681 | } else if ([keyPath isEqualToString:@"labelText"]) { 682 | label.text = self.labelText; 683 | } else if ([keyPath isEqualToString:@"labelFont"]) { 684 | label.font = self.labelFont; 685 | } else if ([keyPath isEqualToString:@"labelColor"]) { 686 | label.textColor = self.labelColor; 687 | } else if ([keyPath isEqualToString:@"detailsLabelText"]) { 688 | detailsLabel.text = self.detailsLabelText; 689 | } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { 690 | detailsLabel.font = self.detailsLabelFont; 691 | } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { 692 | detailsLabel.textColor = self.detailsLabelColor; 693 | } else if ([keyPath isEqualToString:@"progress"]) { 694 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 695 | [indicator setValue:@(progress) forKey:@"progress"]; 696 | } 697 | return; 698 | } 699 | [self setNeedsLayout]; 700 | [self setNeedsDisplay]; 701 | } 702 | 703 | #pragma mark - Notifications 704 | 705 | - (void)registerForNotifications { 706 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 707 | 708 | [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) 709 | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 710 | } 711 | 712 | - (void)unregisterFromNotifications { 713 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 714 | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 715 | } 716 | 717 | - (void)statusBarOrientationDidChange:(NSNotification *)notification { 718 | UIView *superview = self.superview; 719 | if (!superview) { 720 | return; 721 | } else if ([self shouldPerformOrientationTransform]) { 722 | [self setTransformForCurrentOrientation:YES]; 723 | } else { 724 | self.frame = self.superview.bounds; 725 | [self setNeedsDisplay]; 726 | } 727 | } 728 | 729 | - (void)setTransformForCurrentOrientation:(BOOL)animated { 730 | // Stay in sync with the superview 731 | if (self.superview) { 732 | self.bounds = self.superview.bounds; 733 | [self setNeedsDisplay]; 734 | } 735 | 736 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 737 | CGFloat radians = 0; 738 | if (UIInterfaceOrientationIsLandscape(orientation)) { 739 | if (orientation == UIInterfaceOrientationLandscapeLeft) { 740 | radians = -(CGFloat)M_PI/2.0f; 741 | } else { 742 | radians = (CGFloat)M_PI/2.0f; 743 | } 744 | // Window coordinates differ! 745 | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); 746 | } else { 747 | if (orientation == UIInterfaceOrientationPortraitUpsideDown) { 748 | radians = (CGFloat)M_PI; 749 | } else { 750 | radians = 0; 751 | } 752 | } 753 | rotationTransform = CGAffineTransformMakeRotation(radians); 754 | 755 | if (animated) { 756 | [UIView animateWithDuration:0.3 757 | delay:0 758 | options:UIViewAnimationOptionAllowUserInteraction 759 | animations:^{ 760 | [self setTransform:rotationTransform]; 761 | } completion:NULL]; 762 | } else 763 | [self setTransform:rotationTransform]; 764 | } 765 | 766 | + (void)showHUDWithBlock:(id)target text:(NSString *)text delay:(double)delay block:(MBProgressHUDCompletionBlock)block { 767 | SlalomMBProgressHUD *hud = [SlalomMBProgressHUD showHUDAddedTo:target animated:YES]; 768 | hud.margin = 15.0; 769 | hud.color = [UIColor whiteColor]; 770 | hud.labelColor = [UIColor blackColor]; 771 | hud.detailsLabelColor = [UIColor blackColor]; 772 | hud.mode = MBProgressHUDModeText; 773 | hud.labelText = TWEAK_NAME; 774 | hud.detailsLabelText = text; 775 | hud.removeFromSuperViewOnHide = YES; 776 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC); 777 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { 778 | [SlalomMBProgressHUD hideAllHUDsForView:target animated:YES]; 779 | if (block) 780 | block(); 781 | }); 782 | } 783 | 784 | + (void)showHUD:(id)target text:(NSString *)text delay:(double)delay { 785 | [self showHUDWithBlock:target text:text delay:delay block:NULL]; 786 | } 787 | 788 | @end 789 | -------------------------------------------------------------------------------- /SlalomPref/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = iphone:clang:11.2:7.0 2 | 3 | include $(THEOS)/makefiles/common.mk 4 | 5 | BUNDLE_NAME = SlalomModSettings 6 | SlalomModSettings_FILES = SlalomModPreferenceController.m ../SlalomMBProgressHUD.m 7 | SlalomModSettings_INSTALL_PATH = /Library/PreferenceBundles 8 | SlalomModSettings_FRAMEWORKS = AVFoundation CoreGraphics Social UIKit 9 | SlalomModSettings_EXTRA_FRAMEWORKS = CepheiPrefs 10 | SlalomModSettings_PRIVATE_FRAMEWORKS = Preferences 11 | 12 | include $(THEOS_MAKE_PATH)/bundle.mk 13 | 14 | internal-stage:: 15 | $(ECHO_NOTHING)mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences$(ECHO_END) 16 | $(ECHO_NOTHING)cp entry.plist $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/Sla.plist$(ECHO_END) 17 | $(ECHO_NOTHING)find $(THEOS_STAGING_DIR) -name .DS_Store | xargs rm -rf$(ECHO_END) -------------------------------------------------------------------------------- /SlalomPref/Resources/Heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomPref/Resources/Heart.png -------------------------------------------------------------------------------- /SlalomPref/Resources/Heart@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomPref/Resources/Heart@2x.png -------------------------------------------------------------------------------- /SlalomPref/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | SlalomModSettings 9 | CFBundleIdentifier 10 | com.PS.SlalomModSettings 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | DTPlatformName 22 | iphoneos 23 | MinimumOSVersion 24 | 3.0 25 | NSPrincipalClass 26 | SlalomModPreferenceController 27 | 28 | 29 | -------------------------------------------------------------------------------- /SlalomPref/Resources/Sla.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | title 6 | Slo-mo Mod 7 | entry 8 | 9 | cell 10 | PSLinkCell 11 | icon 12 | Sla.png 13 | label 14 | Slo-mo Mod 15 | 16 | items 17 | 18 | 19 | cell 20 | PSSwitchCell 21 | default 22 | 23 | defaults 24 | com.PS.SlalomMod 25 | PostNotification 26 | com.PS.SlalomMod/ReloadPrefs 27 | key 28 | EnableSlalom 29 | label 30 | Enabled 31 | set 32 | masterSwitch:specifier: 33 | 34 | 35 | cell 36 | PSGroupCell 37 | label 38 | Maximum framerate 39 | 40 | 41 | cell 42 | PSEditTextCell 43 | detail 44 | PSDetailController 45 | default 46 | 60 47 | defaults 48 | com.PS.SlalomMod 49 | set 50 | setFPSValue:specifier: 51 | id 52 | fps 53 | PostNotification 54 | com.PS.SlalomMod/ReloadPrefs 55 | key 56 | MogulFramerate 57 | placeholder 58 | Enter FPS 59 | isNumeric 60 | 61 | 62 | 63 | cell 64 | PSButtonCell 65 | action 66 | setFPS: 67 | label 68 | Set 69 | 70 | 71 | cell 72 | PSButtonCell 73 | action 74 | autoFPS: 75 | label 76 | Auto set 77 | 78 | 79 | cell 80 | PSGroupCell 81 | label 82 | Fake framerate 83 | 84 | 85 | cell 86 | PSSwitchCell 87 | default 88 | 89 | defaults 90 | com.PS.SlalomMod 91 | PostNotification 92 | com.PS.SlalomMod/ReloadPrefs 93 | key 94 | FakeFPS 95 | label 96 | Enabled 97 | 98 | 99 | cell 100 | PSEditTextCell 101 | detail 102 | PSDetailController 103 | default 104 | 240 105 | set 106 | setFakeFPSValue:specifier: 107 | defaults 108 | com.PS.SlalomMod 109 | PostNotification 110 | com.PS.SlalomMod/ReloadPrefs 111 | key 112 | aFPS 113 | placeholder 114 | Enter Fake FPS 115 | isNumeric 116 | 117 | 118 | 119 | cell 120 | PSGroupCell 121 | label 122 | Force Slo-mo for all videos 123 | 124 | 125 | cell 126 | PSSwitchCell 127 | default 128 | 129 | PostNotification 130 | com.PS.SlalomMod/ReloadPrefs 131 | defaults 132 | com.PS.SlalomMod 133 | key 134 | ForceSlalom 135 | label 136 | Enabled 137 | 138 | 139 | cell 140 | PSGroupCell 141 | label 142 | Slo-mo Indicator Configuration 143 | 144 | 145 | cell 146 | PSSwitchCell 147 | default 148 | 149 | PostNotification 150 | com.PS.SlalomMod/ReloadPrefs 151 | defaults 152 | com.PS.SlalomMod 153 | key 154 | hideIndicator 155 | label 156 | Auto hide 157 | pl_filter 158 | 159 | CoreFoundationVersion 160 | 161 | 793.1 162 | 1333.1 163 | 164 | 165 | 166 | 167 | cell 168 | PSSwitchCell 169 | default 170 | 171 | defaults 172 | com.PS.SlalomMod 173 | PostNotification 174 | com.PS.SlalomMod/ReloadPrefs 175 | key 176 | indicatorTap 177 | label 178 | FPS gestures 179 | 180 | 181 | cell 182 | PSGroupCell 183 | label 184 | Slo-mo rate 185 | 186 | 187 | PostNotification 188 | com.PS.SlalomMod/ReloadPrefs 189 | cell 190 | PSSliderCell 191 | default 192 | 0.25 193 | defaults 194 | com.PS.SlalomMod 195 | key 196 | rate 197 | max 198 | 1.0 199 | min 200 | 0.1 201 | showValue 202 | 203 | 204 | 205 | cell 206 | PSGroupCell 207 | label 208 | Slo-mo ramp volume 209 | 210 | 211 | PostNotification 212 | com.PS.SlalomMod/ReloadPrefs 213 | cell 214 | PSSliderCell 215 | default 216 | 0.15 217 | defaults 218 | com.PS.SlalomMod 219 | key 220 | volumeRamp 221 | max 222 | 5.0 223 | min 224 | 0.0 225 | showValue 226 | 227 | 228 | 229 | cell 230 | PSGroupCell 231 | label 232 | Slo-mo Quality for Mail app 233 | pl_filter 234 | 235 | CoreFoundationVersion 236 | 237 | 793.1 238 | 1333.1 239 | 240 | 241 | 242 | 243 | cell 244 | PSLinkListCell 245 | defaults 246 | com.PS.SlalomMod 247 | PostNotification 248 | com.PS.SlalomMod/ReloadPrefs 249 | detail 250 | PSListItemsController 251 | key 252 | mailMax 253 | label 254 | Select 255 | validTitles 256 | 257 | Mail Default 258 | Highest Quality 259 | 260 | staticTextMessage 261 | Highest Quality gives better quality, but also larger video size. 262 | validValues 263 | 264 | 0 265 | 1 266 | 267 | pl_filter 268 | 269 | CoreFoundationVersion 270 | 271 | 793.1 272 | 1333.1 273 | 274 | 275 | 276 | 277 | cell 278 | PSGroupCell 279 | label 280 | For legacy devices 281 | footerText 282 | (PoomSmart's Cydia repo) This tweak would enable the hidden 720p 60-fps capture format internally which is supported by Slo-mo Mod. 283 | footerAlignment 284 | 1 285 | 286 | 287 | cell 288 | PSButtonCell 289 | cellClass 290 | HBLinkTableCell 291 | label 292 | 60fps 293 | url 294 | cydia://package/com.ps.sixtyfps 295 | icon 296 | Sla.png 297 | 298 | 299 | cell 300 | PSGroupCell 301 | 302 | 303 | cell 304 | PSButtonCell 305 | action 306 | showGuide: 307 | label 308 | Guide 309 | alignment 310 | 2 311 | 312 | 313 | cell 314 | PSButtonCell 315 | id 316 | as 317 | alignment 318 | 2 319 | action 320 | reloadAS: 321 | label 322 | Reload assetsd 323 | 324 | 325 | cell 326 | PSGroupCell 327 | label 328 | Developer 329 | footerAlignment 330 | 1 331 | footerText 332 | © 2013 - 2017 Thatchapon Unprasert 333 | (@PoomSmart) 334 | 335 | 336 | big 337 | 338 | cellClass 339 | HBTwitterCell 340 | height 341 | 60 342 | label 343 | PoomSmart 344 | user 345 | PoomSmart 346 | 347 | 348 | cell 349 | PSButtonCell 350 | cellClass 351 | HBLinkTableCell 352 | label 353 | Donate ❤️ 354 | alignment 355 | 1 356 | url 357 | https://poomsmart.github.io/repo/ 358 | 359 | 360 | 361 | 362 | -------------------------------------------------------------------------------- /SlalomPref/Resources/Sla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomPref/Resources/Sla.png -------------------------------------------------------------------------------- /SlalomPref/Resources/Sla@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomPref/Resources/Sla@2x.png -------------------------------------------------------------------------------- /SlalomPref/Resources/Sla@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/SlalomPref/Resources/Sla@3x.png -------------------------------------------------------------------------------- /SlalomPref/SlalomModPreferenceController.m: -------------------------------------------------------------------------------- 1 | #define UIFUNCTIONS_NOT_C 2 | #define KILL_PROCESS 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | #include 10 | #import "../Slalom.h" 11 | #import "../SlalomUtilities.h" 12 | #import "../../PSPrefs.x" 13 | #import 14 | #include 15 | 16 | DeclarePrefs() 17 | DeclareGetter() 18 | 19 | extern char **environ; 20 | 21 | void _system(char *cmd) { 22 | pid_t pid; 23 | char *argv[] = {"sh", "-c", cmd, NULL}; 24 | int status; 25 | status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ); 26 | if (status == 0) { 27 | if (waitpid(pid, &status, 0) == -1) { 28 | perror("waitpid"); 29 | } 30 | } 31 | } 32 | 33 | @interface SlalomModGuideViewController : UIViewController 34 | @end 35 | 36 | @implementation SlalomModGuideViewController 37 | 38 | - (id)init { 39 | if (self == [super init]) { 40 | UITextView *guide = [[[UITextView alloc] initWithFrame:CGRectZero] autorelease]; 41 | guide.text = @" iOS Slo-mo videos are videos with \"slo-mo file\" that stores the data of video slow part. \ 42 | So simply uploading these videos by unsupported services like Instagram and Facebook (or grabbing only video file) may not work. \ 43 | But with this tweak, you can re-export the slo-mo videos as a new \"slo-mo applied\" video. To do that, go to the album, the video you want to reexport, then\n\ 44 | A. Tap the \"Save Slo-mo\" (or \"...\") button at top-right to save a new video. (iOS 7-8)\n\ 45 | B. Tap \"Done\" button at bottom-right to save a new video if you want. (iOS 9+)\n\n\ 46 | 🔷 Force Slo-mo for all videos\n\ 47 | If this enabled, the system will recognize any videos as Slo-mo.\n\n\ 48 | 🔶 Slo-mo rate\n\ 49 | Set the factor of the slo-mo region of your video.\n\ 50 | For example, the default value is 0.25, if fps is set to 60, the slow part of the video will be played at 60 x 0.25 = 15 fps.\n\ 51 | Larger values give faster playback, but worse slo-mo experience.\n\n\ 52 | 🔷 Auto hide\n\ 53 | Hide the fps indicator in Slo-mo mode when recording.\n\n\ 54 | 🔶 FPS gestures\n\ 55 | Show FPS setting alert (Single tap), or auto set FPS to its maximum. (Double tap)\n\ 56 | ⭕ This option enabled will disable the iPhone 6 Plus's default FPS toggle gesture. (120 or 240)\n\n\ 57 | 🔷 Slo-mo ramp volume\n\ 58 | Set the volume of the slow part of slo-mo videos. The recommended value is about 2.0 if you want to keep video sound level.\n\n\ 59 | 🔶 Fake framerate\n\ 60 | Fake the FPS indicator number in Slo-mo mode.\n\n\ 61 | 🔷 Slo-mo Quality for Mail app\n\ 62 | There is the reason for creating this option, because exporting videos by email won't support the \"passthrough\" mode (no video touching, no change in quality) this time, it also causes the \"Sharing interrupted\" error message. So the best solution is using the highest available exporting mode instead.\n\n\ 63 | Apparently spotted in iOS 8, changes of slow motion rate and volume are only applied when assetsd is reloaded. So you would want to reload assetsd after you set those properties.\ 64 | "; 65 | guide.font = [UIFont systemFontOfSize:14]; 66 | guide.editable = NO; 67 | self.view = guide; 68 | self.navigationItem.title = @"Slo-mo Mod Guide"; 69 | self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:0 target:self action:@selector(dismissGuide)] autorelease]; 70 | } 71 | return self; 72 | } 73 | 74 | - (void)dismissGuide { 75 | [self.parentViewController dismissViewControllerAnimated:YES completion:nil]; 76 | } 77 | 78 | @end 79 | 80 | @interface SlalomModPreferenceController : PSListController 81 | @property (nonatomic, retain) PSSpecifier *asSpec; 82 | @property (nonatomic, retain) PSSpecifier *fpsSpec; 83 | @end 84 | 85 | @implementation SlalomModPreferenceController 86 | 87 | HavePrefs() 88 | 89 | - (void)masterSwitch:(id)value specifier:(PSSpecifier *)spec { 90 | [self setPreferenceValue:value specifier:spec]; 91 | killProcess("Camera"); 92 | if (isiOS7Up) 93 | killProcess("MobileSlideshow"); 94 | } 95 | 96 | - (NSString *)model { 97 | size_t size; 98 | sysctlbyname("hw.machine", NULL, &size, NULL, 0); 99 | char *answer = (char *)malloc(size); 100 | sysctlbyname("hw.machine", answer, &size, NULL, 0); 101 | NSString *results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding]; 102 | free(answer); 103 | return results; 104 | } 105 | 106 | - (void)reloadAS:(id)param { 107 | if (isiOS8Up) 108 | _system("launchctl kickstart -k system/com.apple.assetsd"); 109 | else { 110 | _system("launchctl stop com.apple.assetsd"); 111 | _system("launchctl start com.apple.assetsd"); 112 | } 113 | } 114 | 115 | HaveBanner2(TWEAK_NAME, UIColor.systemRedColor, @"Slow motion for all devices", nil) 116 | 117 | - (id)init { 118 | if (self == [super init]) { 119 | HBAppearanceSettings *appearanceSettings = [[HBAppearanceSettings alloc] init]; 120 | appearanceSettings.tintColor = UIColor.systemRedColor; 121 | appearanceSettings.tableViewCellTextColor = UIColor.systemRedColor; 122 | appearanceSettings.navigationBarBackgroundColor = UIColor.systemRedColor; 123 | appearanceSettings.navigationBarTitleColor = UIColor.whiteColor; 124 | self.hb_appearanceSettings = appearanceSettings; 125 | UIButton *heart = [[[UIButton alloc] initWithFrame:CGRectZero] autorelease]; 126 | UIImage *image = [[UIImage imageNamed:@"Heart" inBundle:[NSBundle bundleWithPath:@"/Library/PreferenceBundles/SlalomModSettings.bundle"]] _flatImageWithColor:UIColor.whiteColor]; 127 | [heart setImage:image forState:UIControlStateNormal]; 128 | [heart sizeToFit]; 129 | [heart addTarget:self action:@selector(love) forControlEvents:UIControlEventTouchUpInside]; 130 | self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:heart] autorelease]; 131 | dlopen("/Library/MobileSubstrate/DynamicLibraries/SlalomEnabler/SlalomShared.dylib", RTLD_LAZY); 132 | } 133 | return self; 134 | } 135 | 136 | - (void)love { 137 | SLComposeViewController *twitter = [[SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter] retain]; 138 | twitter.initialText = @"#SlomoMod by @PoomSmart is really awesome!"; 139 | if (twitter) 140 | [self.navigationController presentViewController:twitter animated:YES completion:nil]; 141 | [twitter release]; 142 | } 143 | 144 | - (void)showGuideView { 145 | SlalomModGuideViewController *guide = [[[SlalomModGuideViewController alloc] init] autorelease]; 146 | UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:guide] autorelease]; 147 | nav.modalPresentationStyle = 2; 148 | [self.navigationController presentViewController:nav animated:YES completion:nil]; 149 | } 150 | 151 | - (void)hideKeyboard { 152 | [[super view] endEditing:YES]; 153 | } 154 | 155 | - (void)showGuide:(id)param { 156 | [self showGuideView]; 157 | } 158 | 159 | - (NSUInteger)maxCompatibleFPS { 160 | NSUInteger fps = [SoftSlalomUtilities maximumFPS]; 161 | return fps == 0 ? 30 : fps; 162 | } 163 | 164 | - (void)showFPSWarningIfNeeded:(NSUInteger)fps { 165 | NSUInteger limitFPS = [self maxCompatibleFPS]; 166 | if (fps > limitFPS || fps <= 1) { 167 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:TWEAK_NAME message:[NSString stringWithFormat:@"WARNING: %lu FPS is not compatible.\nThe recommended is %lu FPS.", (unsigned long)fps, (unsigned long)limitFPS] delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:@"Ok, set it", nil]; 168 | alert.tag = 112299; 169 | [alert show]; 170 | [alert release]; 171 | } 172 | } 173 | 174 | - (void)setFPS:(id)param { 175 | [self hideKeyboard]; 176 | } 177 | 178 | - (void)autoSetFPS { 179 | [self setPreferenceValue:@([self maxCompatibleFPS]) specifier:self.fpsSpec]; 180 | [self reloadSpecifier:self.fpsSpec animated:YES]; 181 | [self hideKeyboard]; 182 | } 183 | 184 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 185 | if (alertView.tag == 112299 && buttonIndex == 1) 186 | [self autoSetFPS]; 187 | } 188 | 189 | - (void)setFPSValue:(id)value specifier:(PSSpecifier *)spec { 190 | [self showFPSWarningIfNeeded:[value intValue]]; 191 | [self setPreferenceValue:value specifier:spec]; 192 | } 193 | 194 | - (void)setFakeFPSValue:(id)value specifier:(PSSpecifier *)spec { 195 | NSUInteger fps = MAX(1, [value intValue]); 196 | [self setPreferenceValue:@(fps) specifier:spec]; 197 | [self reloadSpecifier:spec animated:NO]; 198 | } 199 | 200 | - (void)autoFPS:(id)param { 201 | [self autoSetFPS]; 202 | } 203 | 204 | - (NSArray *)specifiers { 205 | if (_specifiers == nil) { 206 | NSMutableArray *specs = [NSMutableArray arrayWithArray:[self loadSpecifiersFromPlistName:@"Sla" target:self]]; 207 | for (PSSpecifier *spec in specs) { 208 | NSString *Id = [spec identifier]; 209 | if ([Id isEqualToString:@"fps"]) 210 | self.fpsSpec = spec; 211 | else if ([Id isEqualToString:@"as"]) 212 | self.asSpec = spec; 213 | } 214 | _specifiers = specs.copy; 215 | } 216 | return _specifiers; 217 | } 218 | 219 | @end 220 | -------------------------------------------------------------------------------- /SlalomPref/entry.plist: -------------------------------------------------------------------------------- 1 | { 2 | entry = { 3 | bundle = "SlalomModSettings"; 4 | cell = PSLinkCell; 5 | detail = "SlalomModPreferenceController"; 6 | icon = "Sla.png"; 7 | isController = 1; 8 | label = "Slo-mo Mod"; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /SlalomUtilities.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SlalomUtilities : NSObject 4 | + (BOOL)isLegacyDevice; 5 | + (NSUInteger)bestFrameRateRangeIndex:(AVCaptureDevice *)device; 6 | + (AVCaptureDeviceFormat *)bestDeviceFormat:(AVCaptureDevice *)device; 7 | + (AVCaptureDeviceFormat *)bestDeviceFormat2:(AVCaptureDevice *)device further:(BOOL)further; 8 | + (AVCaptureDeviceFormat *)bestDeviceFormat2:(AVCaptureDevice *)device; 9 | + (NSInteger)maximumFPS; 10 | + (BOOL)isSupportedFPS:(double)fps; 11 | @end 12 | 13 | #define SoftSlalomUtilities NSClassFromString(@"SlalomUtilities") 14 | -------------------------------------------------------------------------------- /SlalomUtilities.m: -------------------------------------------------------------------------------- 1 | #import "SlalomUtilities.h" 2 | #import 3 | 4 | @implementation SlalomUtilities 5 | 6 | + (BOOL)isLegacyDevice { 7 | struct utsname systemInfo; 8 | uname(&systemInfo); 9 | NSString *modelName = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; 10 | return [modelName isEqualToString:@"iPod5,1"] || [modelName hasPrefix:@"iPhone4"] || [modelName hasPrefix:@"iPhone5"] || [modelName hasPrefix:@"iPad2"] || [modelName hasPrefix:@"iPad3"] || [modelName isEqualToString:@"iPad4,4"] || [modelName isEqualToString:@"iPad4,5"] || [modelName isEqualToString:@"iPad4,6"]; 11 | } 12 | 13 | + (NSUInteger)bestFrameRateRangeIndex:(AVCaptureDevice *)device { 14 | NSUInteger formatIndex = 0; 15 | AVFrameRateRange *bestFrameRateRange = nil; 16 | NSArray *formats = device.formats; 17 | for (NSUInteger i = 0; i < formats.count; ++i) { 18 | AVCaptureDeviceFormat *format = formats[i]; 19 | if ([format.mediaType isEqualToString:AVMediaTypeVideo]) { 20 | for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { 21 | if (range.maxFrameRate > bestFrameRateRange.maxFrameRate) { 22 | bestFrameRateRange = range; 23 | formatIndex = i; 24 | } 25 | } 26 | } 27 | } 28 | return formatIndex; 29 | } 30 | 31 | + (AVCaptureDeviceFormat *)bestDeviceFormat:(AVCaptureDevice *)device { 32 | return device.formats[[self bestFrameRateRangeIndex:device]]; 33 | } 34 | 35 | + (AVCaptureDeviceFormat *)bestDeviceFormat2:(AVCaptureDevice *)device further:(BOOL)further { 36 | AVCaptureDeviceFormat *mogulFormat = [self bestDeviceFormat:device]; 37 | AVFrameRateRange *range = mogulFormat.videoSupportedFrameRateRanges[0]; 38 | Float64 maxFrameRate = range.maxFrameRate; 39 | if (maxFrameRate > 30) 40 | return mogulFormat; 41 | if (!further) 42 | return nil; 43 | NSUInteger formatIndex = 0; 44 | NSArray *formats = device.formats; 45 | for (NSUInteger i = 0; i < formats.count; i++) { 46 | AVCaptureDeviceFormat *format = formats[i]; 47 | if ([format.mediaType isEqualToString:AVMediaTypeVideo]) { 48 | CMVideoDimensions dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription); 49 | if (dimension.width == 1280 && dimension.height == 720) 50 | formatIndex = [formats indexOfObject:format]; 51 | } 52 | } 53 | return formats[formatIndex]; 54 | } 55 | 56 | + (AVCaptureDeviceFormat *)bestDeviceFormat2:(AVCaptureDevice *)device { 57 | return [self bestDeviceFormat2:device further:YES]; 58 | } 59 | 60 | + (NSInteger)maximumFPS { 61 | Float64 limitFPS = 30; 62 | AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 63 | NSArray *formats = device.formats; 64 | NSArray *ranges = formats[[self bestFrameRateRangeIndex:device]].videoSupportedFrameRateRanges; 65 | for (AVFrameRateRange *range in ranges) 66 | limitFPS = MAX(range.maxFrameRate, limitFPS); 67 | return (NSInteger)limitFPS; 68 | } 69 | 70 | + (BOOL)isSupportedFPS:(double)fps { 71 | return fps > 1 && fps <= [self maximumFPS]; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Architecture: iphoneos-arm 2 | Name: Slo-mo Mod (Debug) 3 | Package: org.thebigboss.slalommod 4 | Version: 1.0.0 5 | Author: PoomSmart 6 | Maintainer: PoomSmart 7 | Section: Tweaks 8 | Depends: mobilesubstrate, preferenceloader, firmware (>= 7.0), gsc.rear-camera-capability, ws.hbang.common (>= 1.2), org.thebigboss.burstmode | cy+model.iphone (>> 6) 9 | Description: Slo-mo feature for all devices. 10 | Icon: file:///Library/PreferenceBundles/SlalomModSettings.bundle/Sla@2x.png 11 | -------------------------------------------------------------------------------- /layout/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | chk=$(uname -m) 4 | 5 | if [ ! -d "/System/Library/Frameworks/PhotosUI.framework" ];then 6 | ## Pre-iOS 8 7 | if [[ "$chk" != iPhone6* ]];then 8 | cp /Library/SlalomUI/PhotoLibrary/*.png /System/Library/PrivateFrameworks/PhotoLibrary.framework 9 | cp /Library/SlalomUI/PhotosUI/*.png /System/Library/PrivateFrameworks/PhotosUI.framework 10 | fi 11 | fi 12 | 13 | ## No need anymore 14 | rm -rf /Library/SlalomUI 15 | -------------------------------------------------------------------------------- /layout/DEBIAN/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | chk=$(uname -m) 4 | 5 | if [ ! -d "/System/Library/Frameworks/PhotosUI.framework" ];then 6 | ## Pre-iOS 8 7 | if [[ "$chk" != iPhone6* ]];then 8 | rm -f /System/Library/PrivateFrameworks/PhotoLibrary.framework/*Slalom* 9 | rm -f /System/Library/PrivateFrameworks/PhotosUI.framework/PUSlalom* 10 | fi 11 | fi 12 | -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotoLibrary/CAMShutterButtonSlalom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotoLibrary/CAMShutterButtonSlalom.png -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotoLibrary/CAMShutterButtonSlalom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotoLibrary/CAMShutterButtonSlalom@2x.png -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotoLibrary/PLSlalomGrabberHandle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotoLibrary/PLSlalomGrabberHandle.png -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotoLibrary/PLSlalomGrabberHandle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotoLibrary/PLSlalomGrabberHandle@2x.png -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotosUI/PUSlalomBadgeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotosUI/PUSlalomBadgeIcon.png -------------------------------------------------------------------------------- /layout/Library/SlalomUI/PhotosUI/PUSlalomBadgeIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoomSmart/Slo-mo-Mod/f701edef0a84031fc9500581882d0e04cf14c1bc/layout/Library/SlalomUI/PhotosUI/PUSlalomBadgeIcon@2x.png --------------------------------------------------------------------------------