├── .clang-format ├── .gitignore ├── Blossom.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── Blossom.xcscheme ├── LICENSE ├── README.md ├── Resources └── Info.plist ├── build.sh ├── headers ├── BackboardServices.h ├── CAFilter.h ├── LSApplicationProxy.h ├── LSApplicationWorkspace.h ├── NSUserDefaults+Private.h ├── SBSAccessibilityWindowHostingController.h ├── SpringBoardServices.h ├── UIApplication+Private.h ├── UIEvent+Private.h ├── UIEventDispatcher.h ├── UIEventFetcher.h ├── UIWindow+Private.h ├── kern_memorystatus.h ├── libproc.h ├── pac_helper.h └── rootless.h ├── layout ├── DEBIAN │ ├── control │ └── prerm └── Library │ └── LaunchDaemons │ └── com.inyourwalls.blossomservice.plist ├── libraries ├── AssertionServices.tbd ├── BackBoardServices.tbd ├── GraphicsServices.tbd └── SpringBoardServices.tbd ├── sources ├── Daemon │ ├── Daemon.h │ ├── Daemon.m │ ├── HUDApp.mm │ ├── HUDBackdropView.h │ ├── HUDBackdropView.mm │ ├── HUDHelper.h │ ├── HUDHelper.mm │ ├── HUDMainApplication.h │ ├── HUDMainApplication.mm │ ├── HUDMainApplicationDelegate.h │ ├── HUDMainApplicationDelegate.mm │ ├── HUDMainWindow.h │ ├── HUDMainWindow.mm │ ├── HUDRootViewController.h │ ├── HUDRootViewController.mm │ ├── RootViewController.h │ └── RootViewController.mm ├── MainApplication.h ├── MainApplication.mm ├── MainApplicationDelegate.h ├── MainApplicationDelegate.mm ├── UI │ ├── BlossomView.swift │ ├── BlossomViewController.swift │ ├── ConditionalNavigationView.swift │ ├── CropGuideView.swift │ ├── Editor │ │ ├── AVPlayer+isPlaying.swift │ │ ├── Main.storyboard │ │ ├── UIImage+scalePreservingAspectRatio.swift │ │ ├── VideoCropperViewController.swift │ │ └── VideoTrimmerViewController.swift │ ├── LiveWallpaperEditorView.swift │ ├── LiveWallpaperView.swift │ ├── SheetManager.swift │ └── WallpaperSelectorModal.swift └── Wallpaper │ ├── Wallpaper.h │ ├── Wallpaper.m │ ├── WallpaperContents.swift │ └── WallpaperSelection.swift └── supports ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ └── New Project.png ├── Contents.json ├── Icon.imageset │ ├── Contents.json │ └── New Project.png └── setup.dataset │ ├── Contents.json │ └── setup.MOV ├── entitlements.plist ├── hudapp-bridging-header.h ├── hudapp-prefix.pch └── icon.png /.clang-format: -------------------------------------------------------------------------------- 1 | # clang-format 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | AccessModifierOffset: -4 5 | ContinuationIndentWidth: 4 6 | ColumnLimit: 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .theos/ 3 | packages/ 4 | _site/ 5 | *-cache/ 6 | .jekyll-metadata 7 | compile_commands.json 8 | *.xcarchive 9 | *.tipa 10 | 11 | # Created by https://www.toptal.com/developers/gitignore/api/swift,macos,xcode 12 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,macos,xcode 13 | 14 | ### macOS ### 15 | # General 16 | .DS_Store 17 | .AppleDouble 18 | .LSOverride 19 | 20 | # Icon must end with two \r 21 | Icon 22 | 23 | 24 | # Thumbnails 25 | ._* 26 | 27 | # Files that might appear in the root of a volume 28 | .DocumentRevisions-V100 29 | .fseventsd 30 | .Spotlight-V100 31 | .TemporaryItems 32 | .Trashes 33 | .VolumeIcon.icns 34 | .com.apple.timemachine.donotpresent 35 | 36 | # Directories potentially created on remote AFP share 37 | .AppleDB 38 | .AppleDesktop 39 | Network Trash Folder 40 | Temporary Items 41 | .apdisk 42 | 43 | ### macOS Patch ### 44 | # iCloud generated files 45 | *.icloud 46 | 47 | ### Swift ### 48 | # Xcode 49 | # 50 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 51 | 52 | ## User settings 53 | xcuserdata/ 54 | 55 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 56 | *.xcscmblueprint 57 | *.xccheckout 58 | 59 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 60 | build/ 61 | DerivedData/ 62 | *.moved-aside 63 | *.pbxuser 64 | !default.pbxuser 65 | *.mode1v3 66 | !default.mode1v3 67 | *.mode2v3 68 | !default.mode2v3 69 | *.perspectivev3 70 | !default.perspectivev3 71 | 72 | ## Obj-C/Swift specific 73 | *.hmap 74 | 75 | ## App packaging 76 | *.ipa 77 | *.dSYM.zip 78 | *.dSYM 79 | 80 | ## Playgrounds 81 | timeline.xctimeline 82 | playground.xcworkspace 83 | 84 | # Swift Package Manager 85 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 86 | # Packages/ 87 | # Package.pins 88 | # Package.resolved 89 | # *.xcodeproj 90 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 91 | # hence it is not needed unless you have added a package configuration file to your project 92 | # .swiftpm 93 | 94 | .build/ 95 | 96 | # CocoaPods 97 | # We recommend against adding the Pods directory to your .gitignore. However 98 | # you should judge for yourself, the pros and cons are mentioned at: 99 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 100 | # Pods/ 101 | # Add this line if you want to avoid checking in source code from the Xcode workspace 102 | # *.xcworkspace 103 | 104 | # Carthage 105 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 106 | # Carthage/Checkouts 107 | 108 | Carthage/Build/ 109 | 110 | # Accio dependency management 111 | Dependencies/ 112 | .accio/ 113 | 114 | # fastlane 115 | # It is recommended to not store the screenshots in the git repo. 116 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 117 | # For more information about the recommended setup visit: 118 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 119 | 120 | fastlane/report.xml 121 | fastlane/Preview.html 122 | fastlane/screenshots/**/*.png 123 | fastlane/test_output 124 | 125 | # Code Injection 126 | # After new code Injection tools there's a generated folder /iOSInjectionProject 127 | # https://github.com/johnno1962/injectionforxcode 128 | 129 | iOSInjectionProject/ 130 | 131 | ### Xcode ### 132 | 133 | ## Xcode 8 and earlier 134 | 135 | ### Xcode Patch ### 136 | *.xcodeproj/* 137 | !*.xcodeproj/project.pbxproj 138 | !*.xcodeproj/xcshareddata/ 139 | !*.xcodeproj/project.xcworkspace/ 140 | !*.xcworkspace/contents.xcworkspacedata 141 | /*.gcno 142 | **/xcshareddata/WorkspaceSettings.xcsettings 143 | 144 | # End of https://www.toptal.com/developers/gitignore/api/swift,macos,xcode 145 | n -------------------------------------------------------------------------------- /Blossom.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Blossom.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Blossom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "1d1b7498192bdff04f357f0367684dd6cc06ed8be1b547ed621a0abb98cb5f45", 3 | "pins" : [ 4 | { 5 | "identity" : "prynttrimmerview", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/HHK1/PryntTrimmerView", 8 | "state" : { 9 | "revision" : "ac1b60a22c7e6a6514de7a66d2f3d5b537c956d5", 10 | "version" : "4.0.2" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /Blossom.xcodeproj/xcshareddata/xcschemes/Blossom.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Blossom 3 |

4 | 5 | # Notice - Read first 6 | 7 | This app is discontinued in favor of more recent developments, as they didn't exist when I was making this app. 8 | 9 | **As of now, use Nugget v5 by lemin!** 10 | 11 | ---- 12 | 13 | [Read this guide](https://gist.github.com/MWRevamped/9161837f2bda90d13c7d24e285226691) if you want to understand more. 14 | 15 | You can also get Mica app on Mac (Apple internal tool) to make your own custom wallpapers. 16 | 17 | *Dev note: While TrollStore edition would be very convinient, I don't use a device on iOS 17 anymore, so Blossom would probably not get any updates, but I might work on something else related to live wallpapers instead.* 18 | 19 | # Blossom 20 | 21 | Live wallpapers for non-jailbroken iOS 17.0 with TrollStore. 22 | 23 | ![preview](https://github.com/user-attachments/assets/e60ce8d4-9da1-47a9-8b53-542db70efa56) 24 | 25 | ## How does it work 26 | 27 | iOS 17.0 reintroduced live photos as wallpapers, but they aren't very good. 28 | 29 | Blossom replaces the live photo with a video of your choice that can last for 5 seconds. 30 | 31 | Live wallpapers can be looped by repeatedly crashing the `PhotosPosterProvider` process. 32 | 33 | ## How to use 34 | 35 | Download the **.tipa** file from [Releases](https://github.com/inyourwalls/Blossom/releases) page and install it in TrollStore. 36 | 37 | 1. Create a Live Photo wallpaper in iOS. Make sure to configure widgets, clock style, etc., now, as you **cannot** edit the wallpaper afterwards. 38 | 39 | **Note:** If you encounter issues with setting a Live Photo as a wallpaper through camera roll or Photos app, you can use [apps like this one](https://apps.apple.com/de/app/video-to-live-photos-maker/id1596786737) to do so, because it's a bit bugged in iOS 17.0. 40 | 41 | 2. Open Blossom and click "Set Wallpaper", select your recently created wallpaper and any custom video. 42 | 43 | ## FAQ 44 | 45 | ### Why is this only for iOS 17.0? 46 | 47 | The live photo as a wallpaper feature this app uses was introduced in iOS 17.0. 48 | 49 | CoreTrust bug TrollStore uses was fixed on 17.0.1. This makes iOS 17.0 the only version this app is available on. 50 | 51 | ### I cannot create a new wallpaper 52 | 53 | If it looks like a crash whenever you try to select a photo for the wallpaper, disable "Loop" in the app and then you'll be able to create wallpapers again. 54 | 55 | ### There are frame jumps or fade out to black 56 | 57 | If you notice frame jumps on the last second of the video or if you notice the video fading to black for a second - you have to switch to another wallpaper, disable looping, switch back to your live wallpaper and wait for 5 seconds - and enable looping afterwards. It should fix itself. 58 | 59 | ### Does it consume battery? 60 | 61 | It uses as much battery as a normal live photo. 62 | 63 | However, if you decide to loop the video, it will consume some additional battery. 64 | 65 | ### My wallpaper is blank after respring 66 | 67 | The recommended filesize for a wallpaper is 7 megabytes. If the file size is larger, iOS might not load the video for some reason. 68 | 69 | ## Compiling 70 | 71 | Use `./build.sh`. 72 | 73 | The .tipa file will be located in **/packages**. 74 | 75 | ## Credits 76 | 77 | [TrollStore](https://github.com/opa334/TrollStore) for the respring code. 78 | 79 | [TrollSpeed](https://github.com/Lessica/TrollSpeed) and [UIDaemon](https://github.com/limneos/UIDaemon) for AssistiveTouch code, allowing this to work in the background indefinitely. 80 | -------------------------------------------------------------------------------- /Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | Blossom 9 | CFBundleExecutable 10 | Blossom 11 | CFBundleIconFile 12 | icon.png 13 | CFBundleIconName 14 | AppIcon 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | Blossom 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.1 25 | CFBundleSignature 26 | ???? 27 | CFBundleURLTypes 28 | 29 | 30 | CFBundleURLName 31 | com.inyourwalls.blossom 32 | CFBundleURLSchemes 33 | 34 | blossom 35 | 36 | 37 | 38 | CFBundleVersion 39 | 1.1 40 | LSApplicationCategoryType 41 | public.app-category.developer-tools 42 | NSHumanReadableCopyright 43 | Copyright © 2024 inyourwalls 44 | NSPrincipalClass 45 | HUDMainApplication 46 | UIApplicationShowsViewsWhileLocked 47 | 48 | UIApplicationSystemWindowsSecureKey 49 | 50 | UIBackgroundStyle 51 | UIBackgroundStyleDarkBlur 52 | UILaunchStoryboardName 53 | LaunchScreen 54 | UIRequiresFullScreen 55 | 56 | UIStatusBarStyle 57 | UIStatusBarStyleDarkContent 58 | UISupportedInterfaceOrientations 59 | 60 | UIInterfaceOrientationPortrait 61 | 62 | UIViewControllerBasedStatusBarAppearance 63 | 64 | NSPhotoLibraryUsageDescription 65 | Allow to view your saved videos. 66 | 67 | 68 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Build using Xcode 4 | xcodebuild clean build archive \ 5 | -scheme Blossom \ 6 | -project Blossom.xcodeproj \ 7 | -sdk iphoneos \ 8 | -destination 'generic/platform=iOS' \ 9 | -archivePath Blossom \ 10 | CODE_SIGNING_ALLOWED=NO 11 | 12 | chmod 0644 Resources/Info.plist 13 | cp supports/entitlements.plist Blossom.xcarchive/Products 14 | cd Blossom.xcarchive/Products/Applications 15 | codesign --remove-signature Blossom.app 16 | cd - 17 | cd Blossom.xcarchive/Products 18 | mv Applications Payload 19 | ldid -Sentitlements.plist Payload/Blossom.app 20 | chmod 0644 Payload/Blossom.app/Info.plist 21 | zip -qr Blossom.tipa Payload 22 | cd - 23 | mkdir -p packages 24 | mv Blossom.xcarchive/Products/Blossom.tipa packages/Blossom.tipa 25 | -------------------------------------------------------------------------------- /headers/BackboardServices.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #if __cplusplus 4 | extern "C" { 5 | #endif 6 | void UIApplicationInstantiateSingleton(id aclass); 7 | void UIApplicationInitialize(); 8 | void BKSDisplayServicesStart(); 9 | void GSInitialize(); 10 | void GSEventInitialize(Boolean registerPurple); 11 | void GSEventPopRunLoopMode(CFStringRef mode); 12 | void GSEventPushRunLoopMode(CFStringRef mode); 13 | #if __cplusplus 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /headers/CAFilter.h: -------------------------------------------------------------------------------- 1 | /* CoreAnimation - CAFilter.h 2 | 3 | Copyright (c) 2006-2007 Apple Inc. 4 | All rights reserved. */ 5 | 6 | #ifndef CAFILTER_H 7 | #define CAFILTER_H 8 | 9 | #include 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | CA_EXTERN_C_BEGIN 13 | 14 | @interface CAFilter : NSObject 15 | 16 | @property (class, readonly) NSArray *_Nonnull filterTypes; 17 | 18 | @property (assign) BOOL cachesInputImage; 19 | @property (assign, getter=isEnabled) BOOL enabled; 20 | @property (copy) NSString *name; 21 | @property (readonly, assign) NSString *type; 22 | 23 | @property (readonly, strong) NSArray *inputKeys; 24 | @property (readonly, strong) NSArray *outputKeys; 25 | 26 | + (nullable CAFilter *)filterWithType:(nonnull NSString *)type NS_SWIFT_UNAVAILABLE("Use init(type:) instead."); 27 | + (nullable CAFilter *)filterWithName:(nonnull NSString *)name NS_SWIFT_UNAVAILABLE("Use init(type:) instead."); 28 | - (nullable instancetype)initWithType:(nonnull NSString *)type; 29 | - (nullable instancetype)initWithName:(nonnull NSString *)name NS_SWIFT_UNAVAILABLE("Use init(type:) instead."); 30 | 31 | - (void)setDefaults; 32 | 33 | @end 34 | 35 | /** Filter types. **/ 36 | 37 | CA_EXTERN NSString * const kCAFilterMultiplyColor; 38 | CA_EXTERN NSString * const kCAFilterColorAdd; 39 | CA_EXTERN NSString * const kCAFilterColorSubtract; 40 | CA_EXTERN NSString * const kCAFilterColorMonochrome; 41 | CA_EXTERN NSString * const kCAFilterColorMatrix; 42 | CA_EXTERN NSString * const kCAFilterColorHueRotate; 43 | CA_EXTERN NSString * const kCAFilterColorSaturate; 44 | CA_EXTERN NSString * const kCAFilterColorBrightness; 45 | CA_EXTERN NSString * const kCAFilterColorContrast; 46 | CA_EXTERN NSString * const kCAFilterColorInvert; 47 | CA_EXTERN NSString * const kCAFilterLuminanceToAlpha; 48 | CA_EXTERN NSString * const kCAFilterBias; 49 | CA_EXTERN NSString * const kCAFilterDistanceField; 50 | CA_EXTERN NSString * const kCAFilterGaussianBlur; 51 | CA_EXTERN NSString * const kCAFilterLanczosResize; 52 | CA_EXTERN NSString * const kCAFilterClear; 53 | CA_EXTERN NSString * const kCAFilterCopy; 54 | CA_EXTERN NSString * const kCAFilterSourceOver; 55 | CA_EXTERN NSString * const kCAFilterSourceIn; 56 | CA_EXTERN NSString * const kCAFilterSourceOut; 57 | CA_EXTERN NSString * const kCAFilterSourceAtop; 58 | CA_EXTERN NSString * const kCAFilterDest; 59 | CA_EXTERN NSString * const kCAFilterDestOver; 60 | CA_EXTERN NSString * const kCAFilterDestIn; 61 | CA_EXTERN NSString * const kCAFilterDestOut; 62 | CA_EXTERN NSString * const kCAFilterDestAtop; 63 | CA_EXTERN NSString * const kCAFilterXor; 64 | CA_EXTERN NSString * const kCAFilterPlusL; 65 | CA_EXTERN NSString * const kCAFilterSubtractS; 66 | CA_EXTERN NSString * const kCAFilterSubtractD; 67 | CA_EXTERN NSString * const kCAFilterMultiply; 68 | CA_EXTERN NSString * const kCAFilterMinimum; 69 | CA_EXTERN NSString * const kCAFilterMaximum; 70 | CA_EXTERN NSString * const kCAFilterPlusD; 71 | CA_EXTERN NSString * const kCAFilterNormalBlendMode; 72 | CA_EXTERN NSString * const kCAFilterMultiplyBlendMode; 73 | CA_EXTERN NSString * const kCAFilterScreenBlendMode; 74 | CA_EXTERN NSString * const kCAFilterOverlayBlendMode; 75 | CA_EXTERN NSString * const kCAFilterDarkenBlendMode; 76 | CA_EXTERN NSString * const kCAFilterLightenBlendMode; 77 | CA_EXTERN NSString * const kCAFilterColorDodgeBlendMode; 78 | CA_EXTERN NSString * const kCAFilterColorBurnBlendMode; 79 | CA_EXTERN NSString * const kCAFilterSoftLightBlendMode; 80 | CA_EXTERN NSString * const kCAFilterHardLightBlendMode; 81 | CA_EXTERN NSString * const kCAFilterDifferenceBlendMode; 82 | CA_EXTERN NSString * const kCAFilterExclusionBlendMode; 83 | CA_EXTERN NSString * const kCAFilterSubtractBlendMode; 84 | CA_EXTERN NSString * const kCAFilterDivideBlendMode; 85 | CA_EXTERN NSString * const kCAFilterLinearBurnBlendMode; 86 | CA_EXTERN NSString * const kCAFilterLinearDodgeBlendMode; 87 | CA_EXTERN NSString * const kCAFilterLinearLightBlendMode; 88 | CA_EXTERN NSString * const kCAFilterPinLightBlendMode; 89 | CA_EXTERN NSString * const kCAFilterPageCurl; 90 | CA_EXTERN NSString * const kCAFilterVibrantDark; 91 | CA_EXTERN NSString * const kCAFilterVibrantLight; 92 | CA_EXTERN NSString * const kCAFilterDarkenSourceOver; 93 | CA_EXTERN NSString * const kCAFilterLightenSourceOver; 94 | 95 | CA_EXTERN NSString * const kCAFilterHomeAffordanceBase; 96 | 97 | CA_EXTERN_C_END 98 | 99 | struct CAColorMatrix { 100 | float m11, m12, m13, m14, m15; 101 | float m21, m22, m23, m24, m25; 102 | float m31, m32, m33, m34, m35; 103 | float m41, m42, m43, m44, m45; 104 | }; 105 | typedef struct CAColorMatrix CAColorMatrix; 106 | 107 | @interface NSValue (CADetails) 108 | + (NSValue *)valueWithCAColorMatrix:(CAColorMatrix)t; 109 | @end 110 | 111 | NS_ASSUME_NONNULL_END 112 | 113 | #endif // CAFILTER_H 114 | -------------------------------------------------------------------------------- /headers/LSApplicationProxy.h: -------------------------------------------------------------------------------- 1 | #ifndef LSApplicationProxy_h 2 | #define LSApplicationProxy_h 3 | 4 | #import 5 | 6 | @class LSPlugInKitProxy; 7 | 8 | @interface LSApplicationProxy : NSObject 9 | + (LSApplicationProxy *)applicationProxyForIdentifier:(NSString *)bundleIdentifier; 10 | - (BOOL)installed; 11 | - (BOOL)restricted; 12 | - (NSString *)applicationIdentifier; 13 | - (NSString *)localizedName; 14 | - (NSString *)shortVersionString; 15 | - (NSString *)applicationType; 16 | - (NSURL *)bundleURL; 17 | - (NSURL *)dataContainerURL; 18 | - (NSURL *)bundleContainerURL; 19 | - (NSDictionary *)groupContainerURLs; 20 | - (NSDictionary *)entitlements; 21 | - (NSArray *)plugInKitPlugins; 22 | @end 23 | 24 | #endif /* LSApplicationProxy_h */ 25 | -------------------------------------------------------------------------------- /headers/LSApplicationWorkspace.h: -------------------------------------------------------------------------------- 1 | #ifndef LSApplicationWorkspace_h 2 | #define LSApplicationWorkspace_h 3 | 4 | #import 5 | 6 | @class LSApplicationProxy; 7 | 8 | @interface LSApplicationWorkspace : NSObject 9 | + (LSApplicationWorkspace *)defaultWorkspace; 10 | - (NSArray *)allApplications; 11 | - (void)enumerateApplicationsOfType:(NSInteger)type block:(void (^)(id))block; 12 | - (BOOL)openApplicationWithBundleID:(NSString *)bundleIdentifier; 13 | - (BOOL)installApplication:(NSURL *)ipaPath withOptions:(id)arg2 error:(NSError *__autoreleasing*)error; 14 | - (BOOL)uninstallApplication:(NSString *)bundleIdentifier withOptions:(id)arg2; 15 | - (BOOL)uninstallApplication:(NSString *)arg1 withOptions:(id)arg2 error:(NSError *__autoreleasing*)arg3 usingBlock:(/*^block*/id)arg4 ; 16 | - (BOOL)invalidateIconCache:(id)arg1; 17 | - (BOOL)openSensitiveURL:(NSURL *)url withOptions:(id)arg2 error:(NSError *__autoreleasing*)error; 18 | - (void)removeObserver:(id)arg1; 19 | - (void)addObserver:(id)arg1; 20 | @end 21 | 22 | #endif /* LSApplicationWorkspace_h */ 23 | -------------------------------------------------------------------------------- /headers/NSUserDefaults+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSUserDefaults (Private) 4 | 5 | - (instancetype)_initWithSuiteName:(NSString *)suiteName container:(NSURL *)container; 6 | 7 | - (id)objectForKey:(NSString *)key inDomain:(NSString *)domain; 8 | 9 | - (void)setObject:(id)value forKey:(NSString *)key inDomain:(NSString *)domain; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /headers/SBSAccessibilityWindowHostingController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SBSAccessibilityWindowHostingController : NSObject 4 | - (void)registerWindowWithContextID:(unsigned)arg1 atLevel:(double)arg2; 5 | @end 6 | -------------------------------------------------------------------------------- /headers/SpringBoardServices.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | FOUNDATION_EXPORT mach_port_t SBSSpringBoardServerPort(); 4 | 5 | FOUNDATION_EXPORT void SBFrontmostApplicationDisplayIdentifier(mach_port_t port, char *result); 6 | FOUNDATION_EXPORT NSString *SBSCopyFrontmostApplicationDisplayIdentifier(); 7 | FOUNDATION_EXPORT void SBGetScreenLockStatus(mach_port_t port, BOOL *lockStatus, BOOL *passcodeEnabled); 8 | FOUNDATION_EXPORT void SBSUndimScreen(); 9 | 10 | FOUNDATION_EXPORT int SBSLaunchApplicationWithIdentifierAndURLAndLaunchOptions(NSString *bundleIdentifier, NSURL *url, NSDictionary *appOptions, NSDictionary *launchOptions, BOOL suspended); 11 | FOUNDATION_EXPORT int SBSLaunchApplicationWithIdentifierAndLaunchOptions(NSString *bundleIdentifier, NSDictionary *appOptions, NSDictionary *launchOptions, BOOL suspended); 12 | FOUNDATION_EXPORT bool SBSOpenSensitiveURLAndUnlock(CFURLRef url, char flags); 13 | 14 | FOUNDATION_EXPORT NSString *const SBSApplicationLaunchOptionUnlockDeviceKey; 15 | -------------------------------------------------------------------------------- /headers/UIApplication+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UIApplication (Private) 4 | - (UIEvent *)_touchesEvent; 5 | - (void)_run; 6 | - (void)suspend; 7 | - (void)_accessibilityInit; 8 | - (void)terminateWithSuccess; 9 | - (void)__completeAndRunAsPlugin; 10 | - (id)_systemAnimationFenceExemptQueue; 11 | @end 12 | -------------------------------------------------------------------------------- /headers/UIEvent+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UIEvent (Private) 4 | - (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)delayed; 5 | - (void)_clearTouches; 6 | @end 7 | -------------------------------------------------------------------------------- /headers/UIEventDispatcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UIEventDispatcher : NSObject 4 | - (void)_installEventRunLoopSources:(CFRunLoopRef)arg1; 5 | @end 6 | -------------------------------------------------------------------------------- /headers/UIEventFetcher.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class UIEventDispatcher; 4 | 5 | @interface UIEventFetcher : NSObject 6 | - (void)setEventFetcherSink:(UIEventDispatcher *)arg1; 7 | @end 8 | -------------------------------------------------------------------------------- /headers/UIWindow+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UIWindow (Private) 4 | - (unsigned int)_contextId; 5 | @end 6 | -------------------------------------------------------------------------------- /headers/kern_memorystatus.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2018 Apple Computer, Inc. All rights reserved. 3 | * 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ 5 | * 6 | * This file contains Original Code and/or Modifications of Original Code 7 | * as defined in and that are subject to the Apple Public Source License 8 | * Version 2.0 (the 'License'). You may not use this file except in 9 | * compliance with the License. The rights granted to you under the License 10 | * may not be used to create, or enable the creation or redistribution of, 11 | * unlawful or unlicensed copies of an Apple operating system, or to 12 | * circumvent, violate, or enable the circumvention or violation of, any 13 | * terms of an Apple operating system software license agreement. 14 | * 15 | * Please obtain a copy of the License at 16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. 17 | * 18 | * The Original Code and all software distributed under the License are 19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 23 | * Please see the License for the specific language governing rights and 24 | * limitations under the License. 25 | * 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ 27 | */ 28 | 29 | #ifndef SYS_MEMORYSTATUS_H 30 | #define SYS_MEMORYSTATUS_H 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #define MEMORYSTATUS_ENTITLEMENT "com.apple.private.memorystatus" 37 | 38 | #define JETSAM_PRIORITY_REVISION 2 39 | 40 | #define JETSAM_PRIORITY_IDLE_HEAD -2 41 | /* The value -1 is an alias to JETSAM_PRIORITY_DEFAULT */ 42 | #define JETSAM_PRIORITY_IDLE 0 43 | #define JETSAM_PRIORITY_IDLE_DEFERRED 1 /* Keeping this around till all xnu_quick_tests can be moved away from it.*/ 44 | #define JETSAM_PRIORITY_AGING_BAND1 JETSAM_PRIORITY_IDLE_DEFERRED 45 | #define JETSAM_PRIORITY_BACKGROUND_OPPORTUNISTIC 2 46 | #define JETSAM_PRIORITY_AGING_BAND2 JETSAM_PRIORITY_BACKGROUND_OPPORTUNISTIC 47 | #define JETSAM_PRIORITY_BACKGROUND 3 48 | #define JETSAM_PRIORITY_ELEVATED_INACTIVE JETSAM_PRIORITY_BACKGROUND 49 | #define JETSAM_PRIORITY_MAIL 4 50 | #define JETSAM_PRIORITY_PHONE 5 51 | #define JETSAM_PRIORITY_UI_SUPPORT 8 52 | #define JETSAM_PRIORITY_FOREGROUND_SUPPORT 9 53 | #define JETSAM_PRIORITY_FOREGROUND 10 54 | #define JETSAM_PRIORITY_AUDIO_AND_ACCESSORY 12 55 | #define JETSAM_PRIORITY_CONDUCTOR 13 56 | #define JETSAM_PRIORITY_DRIVER_APPLE 15 57 | #define JETSAM_PRIORITY_HOME 16 58 | #define JETSAM_PRIORITY_EXECUTIVE 17 59 | #define JETSAM_PRIORITY_IMPORTANT 18 60 | #define JETSAM_PRIORITY_CRITICAL 19 61 | 62 | #define JETSAM_PRIORITY_MAX 21 63 | 64 | /* TODO - tune. This should probably be lower priority */ 65 | #define JETSAM_PRIORITY_DEFAULT 18 66 | #define JETSAM_PRIORITY_TELEPHONY 19 67 | 68 | /* Compatibility */ 69 | #define DEFAULT_JETSAM_PRIORITY 18 70 | 71 | /* 72 | * The deferral time used by default for apps and daemons in all aging 73 | * policies except kJetsamAgingPolicySysProcsReclaimedFirst is 74 | * DEFERRED_IDLE_EXIT_TIME_SECS. 75 | * 76 | * For kJetsamAgingPolicySysProcsReclaimedFirst, 77 | * 78 | * Daemons: The actual idle deferred time for the daemon is based on 79 | * the relaunch behavior of the daemon. The relaunch behavior determines 80 | * the scaling factor applied to DEFERRED_IDLE_EXIT_TIME_SECS. See 81 | * kJetsamSysProcsIdleDelayTime* ratios defined in kern_memorystatus.c 82 | * 83 | * Apps: The apps are aged for DEFERRED_IDLE_EXIT_TIME_SECS factored 84 | * by kJetsamAppsIdleDelayTimeRatio. 85 | */ 86 | #define DEFERRED_IDLE_EXIT_TIME_SECS 10 87 | 88 | #define KEV_MEMORYSTATUS_SUBCLASS 3 89 | 90 | enum { 91 | kMemorystatusLevelNote = 1, 92 | kMemorystatusSnapshotNote = 2, 93 | kMemorystatusFreezeNote = 3, 94 | kMemorystatusPressureNote = 4 95 | }; 96 | 97 | enum { 98 | kMemorystatusLevelAny = -1, 99 | kMemorystatusLevelNormal = 0, 100 | kMemorystatusLevelWarning = 1, 101 | kMemorystatusLevelUrgent = 2, 102 | kMemorystatusLevelCritical = 3 103 | }; 104 | 105 | typedef struct memorystatus_priority_entry { 106 | pid_t pid; 107 | int32_t priority; 108 | uint64_t user_data; 109 | int32_t limit; /* MB */ 110 | uint32_t state; 111 | } memorystatus_priority_entry_t; 112 | 113 | /* 114 | * This should be the structure to specify different properties 115 | * for processes (group or single) from user-space. Unfortunately, 116 | * we can't move to it completely because the priority_entry structure 117 | * above has been in use for a while now. We'll have to deprecate it. 118 | * 119 | * To support new fields/properties, we will add a new structure with a 120 | * new version and a new size. 121 | */ 122 | #define MEMORYSTATUS_MPE_VERSION_1 1 123 | 124 | #define MEMORYSTATUS_MPE_VERSION_1_SIZE sizeof(struct memorystatus_properties_entry_v1) 125 | 126 | typedef struct memorystatus_properties_entry_v1 { 127 | int version; 128 | pid_t pid; 129 | int32_t priority; 130 | int use_probability; 131 | uint64_t user_data; 132 | int32_t limit; /* MB */ 133 | uint32_t state; 134 | char proc_name[MAXCOMLEN + 1]; 135 | char __pad1[3]; 136 | } memorystatus_properties_entry_v1_t; 137 | 138 | typedef struct memorystatus_kernel_stats { 139 | uint32_t free_pages; 140 | uint32_t active_pages; 141 | uint32_t inactive_pages; 142 | uint32_t throttled_pages; 143 | uint32_t purgeable_pages; 144 | uint32_t wired_pages; 145 | uint32_t speculative_pages; 146 | uint32_t filebacked_pages; 147 | uint32_t anonymous_pages; 148 | uint32_t compressor_pages; 149 | uint64_t compressions; 150 | uint64_t decompressions; 151 | uint64_t total_uncompressed_pages_in_compressor; 152 | uint64_t zone_map_size; 153 | uint64_t zone_map_capacity; 154 | uint64_t largest_zone_size; 155 | char largest_zone_name[MACH_ZONE_NAME_MAX_LEN]; 156 | } memorystatus_kernel_stats_t; 157 | 158 | /* 159 | ** This is a variable-length struct. 160 | ** Allocate a buffer of the size returned by the sysctl, cast to a memorystatus_snapshot_t * 161 | */ 162 | 163 | typedef struct jetsam_snapshot_entry { 164 | pid_t pid; 165 | char name[(2 * MAXCOMLEN) + 1]; 166 | int32_t priority; 167 | uint32_t state; 168 | uint32_t fds; 169 | uint8_t uuid[16]; 170 | uint64_t user_data; 171 | uint64_t killed; 172 | uint64_t pages; 173 | uint64_t max_pages_lifetime; 174 | uint64_t purgeable_pages; 175 | uint64_t jse_internal_pages; 176 | uint64_t jse_internal_compressed_pages; 177 | uint64_t jse_purgeable_nonvolatile_pages; 178 | uint64_t jse_purgeable_nonvolatile_compressed_pages; 179 | uint64_t jse_alternate_accounting_pages; 180 | uint64_t jse_alternate_accounting_compressed_pages; 181 | uint64_t jse_iokit_mapped_pages; 182 | uint64_t jse_page_table_pages; 183 | uint64_t jse_memory_region_count; 184 | uint64_t jse_gencount; /* memorystatus_thread generation counter */ 185 | uint64_t jse_starttime; /* absolute time when process starts */ 186 | uint64_t jse_killtime; /* absolute time when jetsam chooses to kill a process */ 187 | uint64_t jse_idle_delta; /* time spent in idle band */ 188 | uint64_t jse_coalition_jetsam_id; /* we only expose coalition id for COALITION_TYPE_JETSAM */ 189 | struct timeval64 cpu_time; 190 | uint64_t jse_thaw_count; 191 | } memorystatus_jetsam_snapshot_entry_t; 192 | 193 | typedef struct jetsam_snapshot { 194 | uint64_t snapshot_time; /* absolute time snapshot was initialized */ 195 | uint64_t notification_time; /* absolute time snapshot was consumed */ 196 | uint64_t js_gencount; /* memorystatus_thread generation counter */ 197 | memorystatus_kernel_stats_t stats; /* system stat when snapshot is initialized */ 198 | size_t entry_count; 199 | memorystatus_jetsam_snapshot_entry_t entries[]; 200 | } memorystatus_jetsam_snapshot_t; 201 | 202 | /* TODO - deprecate; see */ 203 | #define kMaxSnapshotEntries 192 204 | 205 | /* 206 | * default jetsam snapshot support 207 | */ 208 | extern memorystatus_jetsam_snapshot_t *memorystatus_jetsam_snapshot; 209 | extern memorystatus_jetsam_snapshot_t *memorystatus_jetsam_snapshot_copy; 210 | extern unsigned int memorystatus_jetsam_snapshot_count; 211 | extern unsigned int memorystatus_jetsam_snapshot_copy_count; 212 | extern unsigned int memorystatus_jetsam_snapshot_max; 213 | extern unsigned int memorystatus_jetsam_snapshot_size; 214 | extern uint64_t memorystatus_jetsam_snapshot_last_timestamp; 215 | extern uint64_t memorystatus_jetsam_snapshot_timeout; 216 | #define memorystatus_jetsam_snapshot_list memorystatus_jetsam_snapshot->entries 217 | #define JETSAM_SNAPSHOT_TIMEOUT_SECS 30 218 | 219 | /* General memorystatus stuff */ 220 | 221 | extern uint64_t memorystatus_sysprocs_idle_delay_time; 222 | extern uint64_t memorystatus_apps_idle_delay_time; 223 | 224 | /* State */ 225 | #define kMemorystatusSuspended 0x01 226 | #define kMemorystatusFrozen 0x02 227 | #define kMemorystatusWasThawed 0x04 228 | #define kMemorystatusTracked 0x08 229 | #define kMemorystatusSupportsIdleExit 0x10 230 | #define kMemorystatusDirty 0x20 231 | #define kMemorystatusAssertion 0x40 232 | 233 | /* 234 | * Jetsam exit reason definitions - related to memorystatus 235 | * 236 | * When adding new exit reasons also update: 237 | * JETSAM_REASON_MEMORYSTATUS_MAX 238 | * kMemorystatusKilled... Cause enum 239 | * memorystatus_kill_cause_name[] 240 | */ 241 | #define JETSAM_REASON_INVALID 0 242 | #define JETSAM_REASON_GENERIC 1 243 | #define JETSAM_REASON_MEMORY_HIGHWATER 2 244 | #define JETSAM_REASON_VNODE 3 245 | #define JETSAM_REASON_MEMORY_VMPAGESHORTAGE 4 246 | #define JETSAM_REASON_MEMORY_PROCTHRASHING 5 247 | #define JETSAM_REASON_MEMORY_FCTHRASHING 6 248 | #define JETSAM_REASON_MEMORY_PERPROCESSLIMIT 7 249 | #define JETSAM_REASON_MEMORY_DISK_SPACE_SHORTAGE 8 250 | #define JETSAM_REASON_MEMORY_IDLE_EXIT 9 251 | #define JETSAM_REASON_ZONE_MAP_EXHAUSTION 10 252 | #define JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING 11 253 | #define JETSAM_REASON_MEMORY_VMCOMPRESSOR_SPACE_SHORTAGE 12 254 | #define JETSAM_REASON_LOWSWAP 13 255 | #define JETSAM_REASON_MEMORYSTATUS_MAX JETSAM_REASON_LOWSWAP 256 | 257 | /* 258 | * Jetsam exit reason definitions - not related to memorystatus 259 | */ 260 | #define JETSAM_REASON_CPULIMIT 100 261 | 262 | /* Cause */ 263 | enum { 264 | kMemorystatusInvalid = JETSAM_REASON_INVALID, 265 | kMemorystatusKilled = JETSAM_REASON_GENERIC, 266 | kMemorystatusKilledHiwat = JETSAM_REASON_MEMORY_HIGHWATER, 267 | kMemorystatusKilledVnodes = JETSAM_REASON_VNODE, 268 | kMemorystatusKilledVMPageShortage = JETSAM_REASON_MEMORY_VMPAGESHORTAGE, 269 | kMemorystatusKilledProcThrashing = JETSAM_REASON_MEMORY_PROCTHRASHING, 270 | kMemorystatusKilledFCThrashing = JETSAM_REASON_MEMORY_FCTHRASHING, 271 | kMemorystatusKilledPerProcessLimit = JETSAM_REASON_MEMORY_PERPROCESSLIMIT, 272 | kMemorystatusKilledDiskSpaceShortage = JETSAM_REASON_MEMORY_DISK_SPACE_SHORTAGE, 273 | kMemorystatusKilledIdleExit = JETSAM_REASON_MEMORY_IDLE_EXIT, 274 | kMemorystatusKilledZoneMapExhaustion = JETSAM_REASON_ZONE_MAP_EXHAUSTION, 275 | kMemorystatusKilledVMCompressorThrashing = JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING, 276 | kMemorystatusKilledVMCompressorSpaceShortage = JETSAM_REASON_MEMORY_VMCOMPRESSOR_SPACE_SHORTAGE, 277 | kMemorystatusKilledLowSwap = JETSAM_REASON_LOWSWAP, 278 | }; 279 | 280 | /* 281 | * For backwards compatibility 282 | * Keeping these around for external users (e.g. ReportCrash, Ariadne). 283 | * TODO: Remove once they stop using these. 284 | */ 285 | #define kMemorystatusKilledDiagnostic kMemorystatusKilledDiskSpaceShortage 286 | #define kMemorystatusKilledVMThrashing kMemorystatusKilledVMCompressorThrashing 287 | #define JETSAM_REASON_MEMORY_VMTHRASHING JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING 288 | 289 | /* Memorystatus control */ 290 | #define MEMORYSTATUS_BUFFERSIZE_MAX 65536 291 | 292 | #ifndef KERNEL 293 | int memorystatus_get_level(user_addr_t level); 294 | int memorystatus_control(uint32_t command, int32_t pid, uint32_t flags, void *buffer, size_t buffersize); 295 | #endif 296 | 297 | /* Commands */ 298 | #define MEMORYSTATUS_CMD_GET_PRIORITY_LIST 1 299 | #define MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES 2 300 | #define MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT 3 301 | #define MEMORYSTATUS_CMD_GET_PRESSURE_STATUS 4 302 | #define MEMORYSTATUS_CMD_SET_JETSAM_HIGH_WATER_MARK 5 /* Set active memory limit = inactive memory limit, both non-fatal */ 303 | #define MEMORYSTATUS_CMD_SET_JETSAM_TASK_LIMIT 6 /* Set active memory limit = inactive memory limit, both fatal */ 304 | #define MEMORYSTATUS_CMD_SET_MEMLIMIT_PROPERTIES 7 /* Set memory limits plus attributes independently */ 305 | #define MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES 8 /* Get memory limits plus attributes */ 306 | #define MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_ENABLE 9 /* Set the task's status as a privileged listener w.r.t memory notifications */ 307 | #define MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_DISABLE 10 /* Reset the task's status as a privileged listener w.r.t memory notifications */ 308 | #define MEMORYSTATUS_CMD_AGGRESSIVE_JETSAM_LENIENT_MODE_ENABLE 11 /* Enable the 'lenient' mode for aggressive jetsam. See comments in kern_memorystatus.c near the top. */ 309 | #define MEMORYSTATUS_CMD_AGGRESSIVE_JETSAM_LENIENT_MODE_DISABLE 12 /* Disable the 'lenient' mode for aggressive jetsam. */ 310 | #define MEMORYSTATUS_CMD_GET_MEMLIMIT_EXCESS 13 /* Compute how much a process's phys_footprint exceeds inactive memory limit */ 311 | #define MEMORYSTATUS_CMD_ELEVATED_INACTIVEJETSAMPRIORITY_ENABLE 14 /* Set the inactive jetsam band for a process to JETSAM_PRIORITY_ELEVATED_INACTIVE */ 312 | #define MEMORYSTATUS_CMD_ELEVATED_INACTIVEJETSAMPRIORITY_DISABLE 15 /* Reset the inactive jetsam band for a process to the default band (0)*/ 313 | #define MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED 16 /* (Re-)Set state on a process that marks it as (un-)managed by a system entity e.g. assertiond */ 314 | #define MEMORYSTATUS_CMD_GET_PROCESS_IS_MANAGED 17 /* Return the 'managed' status of a process */ 315 | #define MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE 18 /* Is the process eligible for freezing? Apps and extensions can pass in FALSE to opt out of freezing, i.e., 316 | * if they would prefer being jetsam'ed in the idle band to being frozen in an elevated band. */ 317 | #define MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE 19 /* Return the freezable state of a process. */ 318 | 319 | #if CONFIG_FREEZE 320 | #if DEVELOPMENT || DEBUG 321 | #define MEMORYSTATUS_CMD_FREEZER_CONTROL 20 322 | #endif /* DEVELOPMENT || DEBUG */ 323 | #endif /* CONFIG_FREEZE */ 324 | 325 | #define MEMORYSTATUS_CMD_GET_AGGRESSIVE_JETSAM_LENIENT_MODE 21 /* Query if the lenient mode for aggressive jetsam is enabled. */ 326 | 327 | #define MEMORYSTATUS_CMD_INCREASE_JETSAM_TASK_LIMIT 22 /* Used by DYLD to increase the jetsam active and inactive limits, when using roots */ 328 | 329 | /* Commands that act on a group of processes */ 330 | #define MEMORYSTATUS_CMD_GRP_SET_PROPERTIES 100 331 | 332 | #if PRIVATE 333 | /* Test commands */ 334 | 335 | /* Trigger forced jetsam */ 336 | #define MEMORYSTATUS_CMD_TEST_JETSAM 1000 337 | #define MEMORYSTATUS_CMD_TEST_JETSAM_SORT 1001 338 | 339 | /* Panic on jetsam options */ 340 | typedef struct memorystatus_jetsam_panic_options { 341 | uint32_t data; 342 | uint32_t mask; 343 | } memorystatus_jetsam_panic_options_t; 344 | 345 | #define MEMORYSTATUS_CMD_SET_JETSAM_PANIC_BITS 1002 346 | 347 | /* Select priority band sort order */ 348 | #define JETSAM_SORT_NOSORT 0 349 | #define JETSAM_SORT_DEFAULT 1 350 | 351 | #endif /* PRIVATE */ 352 | 353 | /* memorystatus_control() flags */ 354 | 355 | #define MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND 0x1 /* A populated snapshot buffer is returned on demand */ 356 | #define MEMORYSTATUS_FLAGS_SNAPSHOT_AT_BOOT 0x2 /* Returns a snapshot with memstats collected at boot */ 357 | #define MEMORYSTATUS_FLAGS_SNAPSHOT_COPY 0x4 /* Returns the previously populated snapshot created by the system */ 358 | #define MEMORYSTATUS_FLAGS_GRP_SET_PRIORITY 0x8 /* Set jetsam priorities for a group of pids */ 359 | #define MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY 0x10 /* Set probability of use for a group of processes */ 360 | 361 | /* 362 | * For use with memorystatus_control: 363 | * MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT 364 | * 365 | * A jetsam snapshot is initialized when a non-idle 366 | * jetsam event occurs. The data is held in the 367 | * buffer until it is reaped. This is the default 368 | * behavior. 369 | * 370 | * Flags change the default behavior: 371 | * Demand mode - this is an on_demand snapshot, 372 | * meaning data is populated upon request. 373 | * 374 | * Boot mode - this is a snapshot of 375 | * memstats collected before loading the 376 | * init program. Once collected, these 377 | * stats do not change. In this mode, 378 | * the snapshot entry_count is always 0. 379 | * 380 | * Copy mode - this returns the previous snapshot 381 | * collected by the system. The current snaphshot 382 | * might be only half populated. 383 | * 384 | * Snapshots are inherently racey between request 385 | * for buffer size and actual data compilation. 386 | */ 387 | 388 | /* These definitions are required for backwards compatibility */ 389 | #define MEMORYSTATUS_SNAPSHOT_ON_DEMAND MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND 390 | #define MEMORYSTATUS_SNAPSHOT_AT_BOOT MEMORYSTATUS_FLAGS_SNAPSHOT_AT_BOOT 391 | #define MEMORYSTATUS_SNAPSHOT_COPY MEMORYSTATUS_FLAGS_SNAPSHOT_COPY 392 | 393 | /* 394 | * For use with memorystatus_control: 395 | * MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES 396 | */ 397 | typedef struct memorystatus_priority_properties { 398 | int32_t priority; 399 | uint64_t user_data; 400 | } memorystatus_priority_properties_t; 401 | 402 | /* 403 | * Inform the kernel that setting the priority property is driven by assertions. 404 | */ 405 | #define MEMORYSTATUS_SET_PRIORITY_ASSERTION 0x1 406 | 407 | /* 408 | * For use with memorystatus_control: 409 | * MEMORYSTATUS_CMD_SET_MEMLIMIT_PROPERTIES 410 | * MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES 411 | */ 412 | typedef struct memorystatus_memlimit_properties { 413 | int32_t memlimit_active; /* jetsam memory limit (in MB) when process is active */ 414 | uint32_t memlimit_active_attr; 415 | int32_t memlimit_inactive; /* jetsam memory limit (in MB) when process is inactive */ 416 | uint32_t memlimit_inactive_attr; 417 | } memorystatus_memlimit_properties_t; 418 | 419 | typedef struct memorystatus_memlimit_properties2 { 420 | memorystatus_memlimit_properties_t v1; 421 | uint32_t memlimit_increase; /* jetsam memory limit increase (in MB) for active and inactive states */ 422 | uint32_t memlimit_increase_bytes; /* bytes used to determine the jetsam memory limit increase, for active and inactive states */ 423 | } memorystatus_memlimit_properties2_t; 424 | 425 | #define MEMORYSTATUS_MEMLIMIT_ATTR_FATAL 0x1 /* if set, exceeding the memlimit is fatal */ 426 | 427 | #ifdef XNU_KERNEL_PRIVATE 428 | 429 | /* 430 | * A process will be killed immediately if it crosses a memory limit marked as fatal. 431 | * Fatal limit types are the 432 | * - default system-wide task limit 433 | * - per-task custom memory limit 434 | * 435 | * A process with a non-fatal memory limit can exceed that limit, but becomes an early 436 | * candidate for jetsam when the device is under memory pressure. 437 | * Non-fatal limit types are the 438 | * - high-water-mark limit 439 | * 440 | * Processes that opt into dirty tracking are evaluated 441 | * based on clean vs dirty state. 442 | * dirty ==> active 443 | * clean ==> inactive 444 | * 445 | * Processes that do not opt into dirty tracking are 446 | * evalulated based on priority level. 447 | * Foreground or above ==> active 448 | * Below Foreground ==> inactive 449 | */ 450 | 451 | /* 452 | * p_memstat_state flag holds 453 | * - in kernel process state and memlimit state 454 | */ 455 | 456 | #define P_MEMSTAT_SUSPENDED 0x00000001 /* Process is suspended and likely in the IDLE band */ 457 | #define P_MEMSTAT_FROZEN 0x00000002 /* Process has some state on disk. It should be suspended */ 458 | #define P_MEMSTAT_FREEZE_DISABLED 0x00000004 /* Process isn't freeze-eligible and will not be frozen */ 459 | #define P_MEMSTAT_ERROR 0x00000008 /* Process couldn't be jetsammed for some reason. Transient state so jetsam can skip it next time it sees it */ 460 | #define P_MEMSTAT_LOCKED 0x00000010 /* Process is being actively worked on behind the proc_list_lock */ 461 | #define P_MEMSTAT_TERMINATED 0x00000020 /* Process is exiting */ 462 | #define P_MEMSTAT_FREEZE_IGNORE 0x00000040 /* Process was evaluated by freezer and will be ignored till the next time it goes active and does something */ 463 | #define P_MEMSTAT_PRIORITYUPDATED 0x00000080 /* Process had its jetsam priority updated */ 464 | #define P_MEMSTAT_FOREGROUND 0x00000100 /* Process is in the FG jetsam band...unused??? */ 465 | #define P_MEMSTAT_REFREEZE_ELIGIBLE 0x00000400 /* Process was once thawed i.e. its state was brought back from disk. It is now refreeze eligible.*/ 466 | #define P_MEMSTAT_MANAGED 0x00000800 /* Process is managed by assertiond i.e. is either application or extension */ 467 | #define P_MEMSTAT_INTERNAL 0x00001000 /* Process is a system-critical-not-be-jetsammed process i.e. launchd */ 468 | #define P_MEMSTAT_FATAL_MEMLIMIT 0x00002000 /* current fatal state of the process's memlimit */ 469 | #define P_MEMSTAT_MEMLIMIT_ACTIVE_FATAL 0x00004000 /* if set, exceeding limit is fatal when the process is active */ 470 | #define P_MEMSTAT_MEMLIMIT_INACTIVE_FATAL 0x00008000 /* if set, exceeding limit is fatal when the process is inactive */ 471 | #define P_MEMSTAT_USE_ELEVATED_INACTIVE_BAND 0x00010000 /* if set, the process will go into this band & stay there when in the background instead 472 | * of the aging bands and/or the IDLE band. */ 473 | #define P_MEMSTAT_PRIORITY_ASSERTION 0x00020000 /* jetsam priority is being driven by an assertion */ 474 | 475 | 476 | /* 477 | * p_memstat_relaunch_flags holds 478 | * - relaunch behavior when jetsammed 479 | */ 480 | #define P_MEMSTAT_RELAUNCH_UNKNOWN 0x0 481 | #define P_MEMSTAT_RELAUNCH_LOW 0x1 482 | #define P_MEMSTAT_RELAUNCH_MED 0x2 483 | #define P_MEMSTAT_RELAUNCH_HIGH 0x4 484 | 485 | /* 486 | * Checking the p_memstat_state almost always requires the proc_list_lock 487 | * because the jetsam thread could be on the other core changing the state. 488 | * 489 | * App -- almost always managed by a system process. Always have dirty tracking OFF. Can include extensions too. 490 | * System Processes -- not managed by anybody. Always have dirty tracking ON. Can include extensions (here) too. 491 | */ 492 | #define isApp(p) ((p->p_memstat_state & P_MEMSTAT_MANAGED) || ! (p->p_memstat_dirty & P_DIRTY_TRACK)) 493 | #define isSysProc(p) ( ! (p->p_memstat_state & P_MEMSTAT_MANAGED) || (p->p_memstat_dirty & P_DIRTY_TRACK)) 494 | 495 | #define MEMSTAT_BUCKET_COUNT (JETSAM_PRIORITY_MAX + 1) 496 | 497 | typedef struct memstat_bucket { 498 | TAILQ_HEAD(, proc) list; 499 | int count; 500 | int relaunch_high_count; 501 | } memstat_bucket_t; 502 | 503 | extern memstat_bucket_t memstat_bucket[MEMSTAT_BUCKET_COUNT]; 504 | 505 | /* 506 | * Table that expresses the probability of a process 507 | * being used in the next hour. 508 | */ 509 | typedef struct memorystatus_internal_probabilities { 510 | char proc_name[MAXCOMLEN + 1]; 511 | int use_probability; 512 | } memorystatus_internal_probabilities_t; 513 | 514 | extern memorystatus_internal_probabilities_t *memorystatus_global_probabilities_table; 515 | extern size_t memorystatus_global_probabilities_size; 516 | 517 | 518 | extern void memorystatus_init(void) __attribute__((section("__TEXT, initcode"))); 519 | 520 | extern void memorystatus_init_at_boot_snapshot(void); 521 | 522 | extern int memorystatus_add(proc_t p, boolean_t locked); 523 | extern int memorystatus_update(proc_t p, int priority, uint64_t user_data, boolean_t is_assertion, boolean_t effective, 524 | boolean_t update_memlimit, int32_t memlimit_active, boolean_t memlimit_active_is_fatal, 525 | int32_t memlimit_inactive, boolean_t memlimit_inactive_is_fatal); 526 | 527 | /* Remove this process from jetsam bands for killing or freezing. 528 | * The proc_list_lock is held by the caller. 529 | * @param p: The process to remove. 530 | * @return: 0 if successful. EAGAIN if the process can't be removed right now (because it's being frozen) or ESRCH. 531 | */ 532 | extern int memorystatus_remove(proc_t p); 533 | 534 | int memorystatus_update_inactive_jetsam_priority_band(pid_t pid, uint32_t opflags, int priority, boolean_t effective_now); 535 | int memorystatus_relaunch_flags_update(proc_t p, int relaunch_flags); 536 | 537 | extern int memorystatus_dirty_track(proc_t p, uint32_t pcontrol); 538 | extern int memorystatus_dirty_set(proc_t p, boolean_t self, uint32_t pcontrol); 539 | extern int memorystatus_dirty_get(proc_t p, boolean_t locked); 540 | extern int memorystatus_dirty_clear(proc_t p, uint32_t pcontrol); 541 | 542 | extern int memorystatus_on_terminate(proc_t p); 543 | 544 | extern void memorystatus_on_suspend(proc_t p); 545 | extern void memorystatus_on_resume(proc_t p); 546 | extern void memorystatus_on_inactivity(proc_t p); 547 | 548 | extern void memorystatus_on_pageout_scan_end(void); 549 | 550 | /* Memorystatus kevent */ 551 | 552 | void memorystatus_kevent_init(lck_grp_t *grp, lck_attr_t *attr); 553 | 554 | int memorystatus_knote_register(struct knote *kn); 555 | void memorystatus_knote_unregister(struct knote *kn); 556 | 557 | #if CONFIG_MEMORYSTATUS 558 | void memorystatus_log_exception(const int max_footprint_mb, boolean_t memlimit_is_active, boolean_t memlimit_is_fatal); 559 | void memorystatus_on_ledger_footprint_exceeded(int warning, boolean_t memlimit_is_active, boolean_t memlimit_is_fatal); 560 | void proc_memstat_terminated(proc_t p, boolean_t set); 561 | void memorystatus_proc_flags_unsafe(void * v, boolean_t *is_dirty, boolean_t *is_dirty_tracked, boolean_t *allow_idle_exit); 562 | 563 | #if __arm64__ 564 | void memorystatus_act_on_legacy_footprint_entitlement(proc_t p, boolean_t footprint_increase); 565 | #endif /* __arm64__ */ 566 | 567 | #endif /* CONFIG_MEMORYSTATUS */ 568 | 569 | int memorystatus_get_pressure_status_kdp(void); 570 | 571 | #if CONFIG_JETSAM 572 | 573 | typedef enum memorystatus_policy { 574 | kPolicyDefault = 0x0, 575 | kPolicyMoreFree = 0x1, 576 | } memorystatus_policy_t; 577 | 578 | boolean_t memorystatus_kill_on_VM_page_shortage(boolean_t async); 579 | boolean_t memorystatus_kill_on_FC_thrashing(boolean_t async); 580 | boolean_t memorystatus_kill_on_VM_compressor_thrashing(boolean_t async); 581 | boolean_t memorystatus_kill_on_vnode_limit(void); 582 | 583 | void jetsam_on_ledger_cpulimit_exceeded(void); 584 | void memorystatus_fast_jetsam_override(boolean_t enable_override); 585 | 586 | #endif /* CONFIG_JETSAM */ 587 | 588 | /* These are very verbose printfs(), enable with 589 | * MEMORYSTATUS_DEBUG_LOG 590 | */ 591 | #if MEMORYSTATUS_DEBUG_LOG 592 | #define MEMORYSTATUS_DEBUG(cond, format, ...) \ 593 | do { \ 594 | if (cond) { printf(format, ##__VA_ARGS__); } \ 595 | } while(0) 596 | #else 597 | #define MEMORYSTATUS_DEBUG(cond, format, ...) 598 | #endif 599 | 600 | boolean_t memorystatus_kill_on_zone_map_exhaustion(pid_t pid); 601 | boolean_t memorystatus_kill_on_VM_compressor_space_shortage(boolean_t async); 602 | void memorystatus_pages_update(unsigned int pages_avail); 603 | boolean_t memorystatus_idle_exit_from_VM(void); 604 | proc_t memorystatus_get_first_proc_locked(unsigned int *bucket_index, boolean_t search); 605 | proc_t memorystatus_get_next_proc_locked(unsigned int *bucket_index, proc_t p, boolean_t search); 606 | void memorystatus_get_task_page_counts(task_t task, uint32_t *footprint, uint32_t *max_footprint_lifetime, uint32_t *purgeable_pages); 607 | void memorystatus_invalidate_idle_demotion_locked(proc_t p, boolean_t clean_state); 608 | void memorystatus_update_priority_locked(proc_t p, int priority, boolean_t head_insert, boolean_t skip_demotion_check); 609 | 610 | #if VM_PRESSURE_EVENTS 611 | 612 | extern kern_return_t memorystatus_update_vm_pressure(boolean_t); 613 | 614 | #if CONFIG_MEMORYSTATUS 615 | /* Flags */ 616 | extern int memorystatus_low_mem_privileged_listener(uint32_t op_flags); 617 | extern int memorystatus_send_pressure_note(int pid); 618 | extern boolean_t memorystatus_is_foreground_locked(proc_t p); 619 | extern boolean_t memorystatus_bg_pressure_eligible(proc_t p); 620 | #endif /* CONFIG_MEMORYSTATUS */ 621 | 622 | #endif /* VM_PRESSURE_EVENTS */ 623 | 624 | #endif /* XNU_KERNEL_PRIVATE */ 625 | 626 | #endif /* SYS_MEMORYSTATUS_H */ 627 | -------------------------------------------------------------------------------- /headers/libproc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006, 2007, 2010 Apple Inc. All rights reserved. 3 | * 4 | * @APPLE_LICENSE_HEADER_START@ 5 | * 6 | * This file contains Original Code and/or Modifications of Original Code 7 | * as defined in and that are subject to the Apple Public Source License 8 | * Version 2.0 (the 'License'). You may not use this file except in 9 | * compliance with the License. Please obtain a copy of the License at 10 | * http://www.opensource.apple.com/apsl/ and read it before using this 11 | * file. 12 | * 13 | * The Original Code and all software distributed under the License are 14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 | * Please see the License for the specific language governing rights and 19 | * limitations under the License. 20 | * 21 | * @APPLE_LICENSE_HEADER_END@ 22 | */ 23 | #ifndef _LIBPROC_H_ 24 | #define _LIBPROC_H_ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include /* for audit_token_t */ 35 | 36 | #include 37 | 38 | #include 39 | #include 40 | 41 | /* 42 | * This header file contains private interfaces to obtain process information. 43 | * These interfaces are subject to change in future releases. 44 | */ 45 | 46 | /*! 47 | * @define PROC_LISTPIDSPATH_PATH_IS_VOLUME 48 | * @discussion This flag indicates that all processes that hold open 49 | * file references on the volume associated with the specified 50 | * path should be returned. 51 | */ 52 | #define PROC_LISTPIDSPATH_PATH_IS_VOLUME 1 53 | 54 | 55 | /*! 56 | * @define PROC_LISTPIDSPATH_EXCLUDE_EVTONLY 57 | * @discussion This flag indicates that file references that were opened 58 | * with the O_EVTONLY flag should be excluded from the matching 59 | * criteria. 60 | */ 61 | #define PROC_LISTPIDSPATH_EXCLUDE_EVTONLY 2 62 | 63 | __BEGIN_DECLS 64 | 65 | 66 | /*! 67 | * @function proc_listpidspath 68 | * @discussion A function which will search through the current 69 | * processes looking for open file references which match 70 | * a specified path or volume. 71 | * @param type types of processes to be searched (see proc_listpids) 72 | * @param typeinfo adjunct information for type 73 | * @param path file or volume path 74 | * @param pathflags flags to control which files should be considered 75 | * during the process search. 76 | * @param buffer a C array of int-sized values to be filled with 77 | * process identifiers that hold an open file reference 78 | * matching the specified path or volume. Pass NULL to 79 | * obtain the minimum buffer size needed to hold the 80 | * currently active processes. 81 | * @param buffersize the size (in bytes) of the provided buffer. 82 | * @result the number of bytes of data returned in the provided buffer; 83 | * -1 if an error was encountered; 84 | */ 85 | int proc_listpidspath(uint32_t type, 86 | uint32_t typeinfo, 87 | const char *path, 88 | uint32_t pathflags, 89 | void *buffer, 90 | int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 91 | 92 | int proc_listpids(uint32_t type, uint32_t typeinfo, void *buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 93 | int proc_listallpids(void * buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_1); 94 | int proc_listpgrppids(pid_t pgrpid, void * buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_1); 95 | int proc_listchildpids(pid_t ppid, void * buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_1); 96 | int proc_pidinfo(int pid, int flavor, uint64_t arg, void *buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 97 | int proc_pidfdinfo(int pid, int fd, int flavor, void * buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 98 | int proc_pidfileportinfo(int pid, uint32_t fileport, int flavor, void *buffer, int buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); 99 | int proc_name(int pid, void * buffer, uint32_t buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 100 | int proc_regionfilename(int pid, uint64_t address, void * buffer, uint32_t buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 101 | int proc_kmsgbuf(void * buffer, uint32_t buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 102 | int proc_pidpath(int pid, void * buffer, uint32_t buffersize) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 103 | int proc_pidpath_audittoken(audit_token_t *audittoken, void * buffer, uint32_t buffersize) API_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0)); 104 | int proc_libversion(int *major, int * minor) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); 105 | 106 | /* 107 | * Return resource usage information for the given pid, which can be a live process or a zombie. 108 | * 109 | * Returns 0 on success; or -1 on failure, with errno set to indicate the specific error. 110 | */ 111 | int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer) __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); 112 | 113 | /* 114 | * A process can use the following api to set its own process control 115 | * state on resoure starvation. The argument can have one of the PROC_SETPC_XX values 116 | */ 117 | #define PROC_SETPC_NONE 0 118 | #define PROC_SETPC_THROTTLEMEM 1 119 | #define PROC_SETPC_SUSPEND 2 120 | #define PROC_SETPC_TERMINATE 3 121 | 122 | int proc_setpcontrol(const int control) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); 123 | int proc_setpcontrol(const int control); 124 | 125 | int proc_track_dirty(pid_t pid, uint32_t flags); 126 | int proc_set_dirty(pid_t pid, bool dirty); 127 | int proc_get_dirty(pid_t pid, uint32_t *flags); 128 | int proc_clear_dirty(pid_t pid, uint32_t flags); 129 | 130 | int proc_terminate(pid_t pid, int *sig); 131 | 132 | /* 133 | * NO_SMT means that on an SMT CPU, this thread must be scheduled alone, 134 | * with the paired CPU idle. 135 | * 136 | * Set NO_SMT on the current proc (all existing and future threads) 137 | * This attribute is inherited on fork and exec 138 | */ 139 | int proc_set_no_smt(void) __API_AVAILABLE(macos(10.16)); 140 | 141 | /* Set NO_SMT on the current thread */ 142 | int proc_setthread_no_smt(void) __API_AVAILABLE(macos(10.16)); 143 | 144 | /* 145 | * CPU Security Mitigation APIs 146 | * 147 | * Set CPU security mitigation on the current proc (all existing and future threads) 148 | * This attribute is inherited on fork and exec 149 | */ 150 | int proc_set_csm(uint32_t flags) __API_AVAILABLE(macos(10.16)); 151 | 152 | /* Set CPU security mitigation on the current thread */ 153 | int proc_setthread_csm(uint32_t flags) __API_AVAILABLE(macos(10.16)); 154 | 155 | /* 156 | * flags for CPU Security Mitigation APIs 157 | * PROC_CSM_ALL should be used in most cases, 158 | * the individual flags are provided only for performance evaluation etc 159 | */ 160 | #define PROC_CSM_ALL 0x0001 /* Set all available mitigations */ 161 | #define PROC_CSM_NOSMT 0x0002 /* Set NO_SMT - see above */ 162 | #define PROC_CSM_TECS 0x0004 /* Execute VERW on every return to user mode */ 163 | 164 | #ifdef PRIVATE 165 | #include 166 | /* 167 | * Enumerate potential userspace pointers embedded in kernel data structures. 168 | * Currently inspects kqueues only. 169 | * 170 | * NOTE: returned "pointers" are opaque user-supplied values and thus not 171 | * guaranteed to address valid objects or be pointers at all. 172 | * 173 | * Returns the number of pointers found (which may exceed buffersize), or -1 on 174 | * failure and errno set appropriately. 175 | */ 176 | int proc_list_uptrs(pid_t pid, uint64_t *buffer, uint32_t buffersize); 177 | 178 | int proc_list_dynkqueueids(int pid, kqueue_id_t *buf, uint32_t bufsz); 179 | int proc_piddynkqueueinfo(int pid, int flavor, kqueue_id_t kq_id, void *buffer, 180 | int buffersize); 181 | #endif /* PRIVATE */ 182 | 183 | int proc_udata_info(int pid, int flavor, void *buffer, int buffersize); 184 | 185 | __END_DECLS 186 | 187 | #endif /*_LIBPROC_H_ */ 188 | -------------------------------------------------------------------------------- /headers/pac_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef PTRAUTH_HELPERS_H 2 | #define PTRAUTH_HELPERS_H 3 | 4 | // Helpers for PAC archs. 5 | 6 | // If the compiler understands __arm64e__, assume it's paired with an SDK that has 7 | // ptrauth.h. Otherwise, it'll probably error if we try to include it so don't. 8 | #if __arm64e__ 9 | #include 10 | #endif 11 | 12 | #pragma clang diagnostic push 13 | #pragma clang diagnostic ignored "-Wunused-function" 14 | 15 | // Given a pointer to instructions, sign it so you can call it like a normal fptr. 16 | static void *make_sym_callable(void *ptr) { 17 | #if __arm64e__ 18 | if (!ptr) return ptr; 19 | ptr = ptrauth_sign_unauthenticated(ptrauth_strip(ptr, ptrauth_key_function_pointer), ptrauth_key_function_pointer, 0); 20 | #endif 21 | return ptr; 22 | } 23 | 24 | // Given a function pointer, strip the PAC so you can read the instructions. 25 | static void *make_sym_readable(void *ptr) { 26 | #if __arm64e__ 27 | if (!ptr) return ptr; 28 | ptr = ptrauth_strip(ptr, ptrauth_key_function_pointer); 29 | #endif 30 | return ptr; 31 | } 32 | 33 | #pragma clang diagnostic pop 34 | #endif // PTRAUTH_HELPERS_H 35 | -------------------------------------------------------------------------------- /headers/rootless.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifdef XINA_SUPPORT // Only define this for rootful compilations that need support for xina 5 | #define ROOT_PATH(cPath) !access("/var/LIY", F_OK) ? "/var/jb" cPath : cPath 6 | #define ROOT_PATH_NS(path) !access("/var/LIY", F_OK) ? @"/var/jb" path : path 7 | #define ROOT_PATH_NS_VAR !access("/var/LIY", F_OK) ? [@"/var/jb" stringByAppendingPathComponent:path] : path 8 | #define ROOT_PATH_VAR(path) !access("/var/LIY", F_OK) ? ({ \ 9 | char outPath[PATH_MAX]; \ 10 | strlcpy(outPath, "/var/jb", PATH_MAX); \ 11 | strlcat(outPath, path, PATH_MAX); \ 12 | outPath; \ 13 | }) : path 14 | #else 15 | #define ROOT_PATH(cPath) THEOS_PACKAGE_INSTALL_PREFIX cPath 16 | #define ROOT_PATH_NS(path) @THEOS_PACKAGE_INSTALL_PREFIX path 17 | #define ROOT_PATH_NS_VAR(path) [@THEOS_PACKAGE_INSTALL_PREFIX stringByAppendingPathComponent:path] 18 | #define ROOT_PATH_VAR(path) sizeof(THEOS_PACKAGE_INSTALL_PREFIX) > 1 ? ({ \ 19 | char outPath[PATH_MAX]; \ 20 | strlcpy(outPath, THEOS_PACKAGE_INSTALL_PREFIX, PATH_MAX); \ 21 | strlcat(outPath, path, PATH_MAX); \ 22 | outPath; \ 23 | }) : path 24 | #endif 25 | -------------------------------------------------------------------------------- /layout/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: ch.xxtou.hudapp.jb 2 | Name: TrollSpeed JB 3 | Version: 1.12.1 4 | Section: Tweaks 5 | Depends: firmware (>= 14.0) 6 | Architecture: iphoneos-arm 7 | Author: Lessica <82flex@gmail.com> 8 | Maintainer: Lessica <82flex@gmail.com> 9 | Description: Troll your speed, but jailbroken. 10 | -------------------------------------------------------------------------------- /layout/DEBIAN/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f /Library/LaunchDaemons/ch.xxtou.hudservices.plist ]; then 4 | launchctl unload /Library/LaunchDaemons/ch.xxtou.hudservices.plist || true 5 | fi 6 | 7 | if [ -f /var/jb/Library/LaunchDaemons/ch.xxtou.hudservices.plist ]; then 8 | launchctl unload /var/jb/Library/LaunchDaemons/ch.xxtou.hudservices.plist || true 9 | fi 10 | 11 | exit 0 -------------------------------------------------------------------------------- /layout/Library/LaunchDaemons/com.inyourwalls.blossomservice.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EnablePressuredExit 6 | 7 | EnableTransactions 8 | 9 | EnvironmentVariables 10 | 11 | DISABLE_TWEAKS 12 | 1 13 | 14 | GroupName 15 | wheel 16 | HighPriorityIO 17 | 18 | KeepAlive 19 | 20 | Label 21 | com.inyourwalls.blossomservice 22 | POSIXSpawnType 23 | App 24 | ProcessType 25 | Interactive 26 | ProgramArguments 27 | 28 | /var/jb/Applications/Blossom.app/Blossom 29 | -hud 30 | 31 | RunAtLoad 32 | 33 | ThrottleInterval 34 | 5 35 | UserName 36 | root 37 | _AdditionalProperties 38 | 39 | RunningBoard 40 | 41 | Managed 42 | 43 | Reported 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /libraries/AssertionServices.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd-v3 2 | archs: [ armv7, armv7s, arm64, arm64e ] 3 | platform: ios 4 | flags: [ flat_namespace ] 5 | install-name: /System/Library/PrivateFrameworks/AssertionServices.framework/AssertionServices 6 | current-version: 505.100.7 7 | compatibility-version: 1 8 | objc-constraint: retain_release 9 | exports: 10 | - archs: [ armv7, armv7s, arm64, arm64e ] 11 | symbols: [ _AssertionServicesVersionNumber, 12 | _AssertionServicesVersionString, 13 | _BKLaunchdJobApplicationLabelPrefix, 14 | _BKLaunchdJobSystemShellLabelPrefix, 15 | _BKSApplicationNotificationStateChanged, 16 | _BKSApplicationStateAll, 17 | _BKSApplicationStateAppIsFrontmostKey, 18 | _BKSApplicationStateDisplayIDKey, 19 | _BKSApplicationStateExitCodeKey, 20 | _BKSApplicationStateExitReasonKey, 21 | _BKSApplicationStateExtensionKey, 22 | _BKSApplicationStateHostPIDKey, 23 | _BKSApplicationStateKey, 24 | _BKSApplicationStateMostElevatedStateForProcessIDKey, 25 | _BKSApplicationStateProcessIDKey, 26 | _BKSApplicationStateRunningReasonAssertionIdentifierKey, 27 | _BKSApplicationStateRunningReasonAssertionNotificationKey, 28 | _BKSApplicationStateRunningReasonAssertionReasonKey, 29 | _BKSApplicationStateRunningReasonsKey, 30 | _BKSAssertionErrorDescription, 31 | _BKSAssertionErrorDomain, 32 | _BKSProcessAssertionBackgroundTimeRemaining, 33 | _BKSProcessAssertionSetExpirationHandler, 34 | _BKSProcessDiagnosticReportTypeDescription, 35 | _BKSProcessErrorDomain, 36 | _BKSProcessExtensionPropertyBundleID, 37 | _BKSProcessVisibilityIsForeground, 38 | _BKSProcessVisibilityMax, 39 | _BKSTerminationAssertionActiveEfficacyForBundleIdentifier, 40 | _BKSTerminationAssertionEfficacyDescription, 41 | _BKSTerminationAssertionHeldForBundleIdentifier, 42 | _BKSTerminationAssertionRegisterObserver, 43 | _BKSTerminationAssertionUnregisterObserver, 44 | _BKSWatchdogAssertionCreateForPID, 45 | _BKSWatchdogAssertionGetTypeID, 46 | _NSStringFromBKSProcessErrorCode, 47 | _NSStringFromBKSProcessExitReason, 48 | _NSStringFromBKSProcessTaskState, 49 | _NSStringFromBKSProcessVisibility, 50 | _RBSProcessLegacyStateDescriptor ] 51 | objc-classes: [ BKSApplicationStateMonitor, BKSAssertion, 52 | BKSLaunchdJobSpecification, BKSProcess, 53 | BKSProcessAssertion, BKSProcessExitContext, 54 | BKSTerminationAssertion, 55 | BKSTerminationAssertionObserverManager, 56 | BKSTerminationContext, BKSWorkspace ] 57 | objc-ivars: [ BKSApplicationStateMonitor._elevatedPriority, 58 | BKSApplicationStateMonitor._handler, 59 | BKSApplicationStateMonitor._interestedAssertionReasons, 60 | BKSApplicationStateMonitor._interestedBundleIDs, 61 | BKSApplicationStateMonitor._interestedStates, 62 | BKSApplicationStateMonitor._lock, 63 | BKSApplicationStateMonitor._monitor, 64 | BKSAssertion._acquisitionHandler, 65 | BKSAssertion._attributes, 66 | BKSAssertion._internalAssertion, 67 | BKSAssertion._invalidationHandler, 68 | BKSAssertion._lock, BKSAssertion._name, 69 | BKSAssertion._target, 70 | BKSLaunchdJobSpecification._arguments, 71 | BKSLaunchdJobSpecification._bundleIdentifier, 72 | BKSLaunchdJobSpecification._bundlePath, 73 | BKSLaunchdJobSpecification._environment, 74 | BKSLaunchdJobSpecification._executablePath, 75 | BKSLaunchdJobSpecification._executionOptions, 76 | BKSLaunchdJobSpecification._labelPrefix, 77 | BKSLaunchdJobSpecification._machServices, 78 | BKSLaunchdJobSpecification._managedPersona, 79 | BKSLaunchdJobSpecification._standardError, 80 | BKSLaunchdJobSpecification._standardOutput, 81 | BKSProcess._accessoryAssertion, 82 | BKSProcess._assertion, BKSProcess._audioAssertion, 83 | BKSProcess._bootstrapped, 84 | BKSProcess._connectedToExternalAccessories, 85 | BKSProcess._delegate, BKSProcess._handle, 86 | BKSProcess._identity, BKSProcess._jobSpec, 87 | BKSProcess._lastExitContext, BKSProcess._lock, 88 | BKSProcess._mediaAssertion, BKSProcess._monitor, 89 | BKSProcess._nowPlayingWithAudio, 90 | BKSProcess._processHandle, 91 | BKSProcess._recordingAudio, BKSProcess._taskState, 92 | BKSProcess._terminationReason, 93 | BKSProcess._visibility, 94 | BKSProcess._visibilityAssertion, 95 | BKSProcess._workspaceLocked, 96 | BKSProcessAssertion._flags, 97 | BKSProcessAssertion._mediaPlaybackHackAssertion, 98 | BKSProcessAssertion._reason, 99 | BKSProcessExitContext._exitReason, 100 | BKSTerminationAssertion._bundleIdentifier, 101 | BKSTerminationAssertion._context, 102 | BKSTerminationAssertion._efficacy, 103 | BKSTerminationAssertionObserverManager._calloutQueue, 104 | BKSTerminationAssertionObserverManager._launchPreventedBundleIDs, 105 | BKSTerminationAssertionObserverManager._lock, 106 | BKSTerminationAssertionObserverManager._monitor, 107 | BKSTerminationAssertionObserverManager._monitorIsReady, 108 | BKSTerminationAssertionObserverManager._observers, 109 | BKSTerminationContext._exceptionCode, 110 | BKSTerminationContext._explanation, 111 | BKSTerminationContext._reportType ] 112 | ... 113 | -------------------------------------------------------------------------------- /libraries/GraphicsServices.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd-v3 2 | archs: [ armv7, armv7s, arm64, arm64e ] 3 | platform: ios 4 | flags: [ flat_namespace ] 5 | install-name: /System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices 6 | current-version: 14 7 | compatibility-version: 1 8 | objc-constraint: retain_release 9 | exports: 10 | - archs: [ armv7, armv7s, arm64, arm64e ] 11 | symbols: [ _GSColorCreateColorWithDeviceRGBA, 12 | _GSColorCreateColorWithDeviceRGBAInfo, 13 | _GSColorCreateWithDeviceWhite, 14 | _GSColorGetRGBAComponents, _GSColorGetRGBAInfo, 15 | _GSCopyPurpleNamedPerPIDPort, _GSCopyPurpleNamedPort, 16 | _GSCurrentEventTimestamp, _GSEventAccelerometerAxisX, 17 | _GSEventAccelerometerAxisY, 18 | _GSEventAccelerometerAxisZ, 19 | _GSEventAccessoryAvailabilityChanged, _GSEventCopy, 20 | _GSEventCopyCharacters, 21 | _GSEventCopyCharactersIgnoringModifiers, 22 | _GSEventCopyMarkedCharacters, 23 | _GSEventCreateAccessoryKeyStateEvent, 24 | _GSEventCreateApplicationSuspendEvent, 25 | _GSEventCreateKeyEvent, 26 | _GSEventCreatePlistRepresentation, 27 | _GSEventCreateWithEventRecord, 28 | _GSEventCreateWithPlist, _GSEventDeviceOrientation, 29 | _GSEventDisableHandEventCoalescing, 30 | _GSEventFinishedActivating, 31 | _GSEventGetAccessoryKeyStateInfo, 32 | _GSEventGetCharacterSet, _GSEventGetClickCount, 33 | _GSEventGetDeltaX, _GSEventGetDeltaY, 34 | _GSEventGetHIDTimestamp, _GSEventGetHandInfo, 35 | _GSEventGetHardwareKeyboardCountry, 36 | _GSEventGetHardwareKeyboardType, 37 | _GSEventGetInnerMostPathPosition, _GSEventGetKeyCode, 38 | _GSEventGetKeyFlags, _GSEventGetLocationInWindow, 39 | _GSEventGetModifierFlags, 40 | _GSEventGetOuterMostPathPosition, 41 | _GSEventGetPathInfoAtIndex, _GSEventGetSenderPID, 42 | _GSEventGetSubType, _GSEventGetTimestamp, 43 | _GSEventGetType, _GSEventGetTypeID, 44 | _GSEventGetUsagePage, _GSEventGetWindow, 45 | _GSEventInitialize, _GSEventInitializeAsExtension, 46 | _GSEventInitializeWorkspaceWithQueue, 47 | _GSEventIsChordingHandEvent, 48 | _GSEventIsForceQuitEvent, _GSEventIsHandEvent, 49 | _GSEventIsHardwareKeyboardAttached, 50 | _GSEventIsHardwareKeyboardEvent, 51 | _GSEventIsKeyRepeating, _GSEventIsTabKeyEvent, 52 | _GSEventLockDevice, _GSEventPopRunLoopMode, 53 | _GSEventPushRunLoopMode, 54 | _GSEventQueueContainsMouseEvent, 55 | _GSEventQuitTopApplication, 56 | _GSEventReceiveRunLoopMode, 57 | _GSEventRegisterEventCallBack, 58 | _GSEventRemoveShouldRouteToFrontMost, 59 | _GSEventResetIdleTimer, _GSEventRun, _GSEventRunModal, 60 | _GSEventSendApplicationOpenURL, _GSEventSendKeyEvent, 61 | _GSEventSetBacklightLevel, _GSEventSetCharacters, 62 | _GSEventSetHardwareKeyboardAttached, 63 | _GSEventSetHardwareKeyboardAttachedWithCountryCodeAndType, 64 | _GSEventSetLocationInWindow, 65 | _GSEventSetPathInfoAtIndex, _GSEventSetType, 66 | _GSEventShouldRouteToFrontMost, 67 | _GSEventSourceIsHardware, _GSEventStopModal, 68 | _GSEventStopVibrator, _GSEventVibrateForDuration, 69 | _GSFontCopyFamilyNames, _GSFontCopyFontFilePath, 70 | _GSFontCopyFontNamesForFamilyName, 71 | _GSFontCopyNormalizedAdditionalFontName, 72 | _GSFontCopyPersistentPostscriptURL, 73 | _GSFontCreateWithName, _GSFontGetCacheDictionary, 74 | _GSFontInitialize, _GSFontPurgeFontCache, 75 | _GSFontRegisterCGFont, _GSFontRegisterPersistentURLs, 76 | _GSFontRegisterURL, _GSFontUnregisterCGFont, 77 | _GSFontUnregisterPersistentURLs, 78 | _GSFontUnregisterURL, _GSGetPurpleApplicationPort, 79 | _GSGetPurpleSystemAppPort, 80 | _GSGetPurpleSystemEventPort, 81 | _GSGetPurpleWorkspacePort, _GSGetTimeEventHandling, 82 | _GSInitialize, _GSKeyboardCreate, 83 | _GSKeyboardCreateWithCountryCode, 84 | _GSKeyboardGetHWKeyboardType, 85 | _GSKeyboardGetKeyCodeForChar, _GSKeyboardGetLayout, 86 | _GSKeyboardGetLiveModifierState, 87 | _GSKeyboardGetModifierState, 88 | _GSKeyboardGetStickyLockModifierState, 89 | _GSKeyboardGetTranslationOptions, 90 | _GSKeyboardGetTypeID, 91 | _GSKeyboardHWKeyboardLayoutsPlist, 92 | _GSKeyboardHWKeyboardNormalizeInput, 93 | _GSKeyboardRelease, _GSKeyboardReset, 94 | _GSKeyboardSetHardwareKeyboardAttached, 95 | _GSKeyboardSetLayout, 96 | _GSKeyboardSetTranslationOptions, 97 | _GSKeyboardTranslateKey, 98 | _GSKeyboardTranslateKeyExtended, 99 | _GSKeyboardTranslateKeyExtendedCommand, 100 | _GSKeyboardTranslateKeyWithModifiers, 101 | _GSMainScreenOrientation, _GSMainScreenPixelSize, 102 | _GSMainScreenPointSize, 103 | _GSMainScreenPositionTransform, 104 | _GSMainScreenScaleFactor, _GSMainScreenSize, 105 | _GSMainScreenWindowTransform, 106 | _GSRegisterPurpleNamedPerPIDPort, 107 | _GSRegisterPurpleNamedPort, 108 | _GSSaveEventHandlingTimes, 109 | _GSSendAppPreferencesChanged, 110 | _GSSendApplicationFinishedBackgroundContentFetchingEvent, 111 | _GSSendApplicationFinishedBackgroundContentFetchingEventWithSequenceNumber, 112 | _GSSendApplicationFinishedBackgroundNotificationActionEvent, 113 | _GSSendApplicationSuspendEvent, 114 | _GSSendApplicationSuspendedSettingsUpdatedEvent, 115 | _GSSendEvent, _GSSendSimpleEvent, 116 | _GSSendSimpleEventWithSubtype, _GSSendSystemAppEvent, 117 | _GSSendSystemEvent, _GSSendWorkspaceEvent, 118 | _GSSetMainScreenInfo, _GSSetTimeEventHandling, 119 | _GSSystemHasCapability, _GSSystemRootDirectory, 120 | _GSSystemSetRequiresCapabilities, 121 | __GSEventGetGSEventRecord, _kGS3GVeniceCapability, 122 | _kGS720pPlaybackCapability, 123 | _kGSARMV6ExecutionCapability, 124 | _kGSARMV7ExecutionCapability, 125 | _kGSAccelerometerCapability, 126 | _kGSAccessibilityCapability, 127 | _kGSAdditionalTextTonesCapability, 128 | _kGSAmbientLightSensorCapability, 129 | _kGSAppleInternalInstallCapability, 130 | _kGSAssistantCapability, 131 | _kGSAutoFocusCameraCapability, 132 | _kGSBluetoothCapability, _kGSCameraCapability, 133 | _kGSCameraFlashCapability, _kGSCameraRestriction, 134 | _kGSCapabilityChangedNotification, 135 | _kGSCapabilityUpdateNotification, 136 | _kGSCellularDataCapability, 137 | _kGSCellularTelephonyCapability, 138 | _kGSContainsCellularRadioCapability, 139 | _kGSDataPlanCapability, 140 | _kGSDelaySleepForHeadsetClickCapability, 141 | _kGSDeviceNameString, _kGSDictationCapability, 142 | _kGSDisplayFCCLogosViaSoftwareCapability, 143 | _kGSDisplayIdentifiersCapability, 144 | _kGSDisplayMirroringCapability, 145 | _kGSDisplayPortCapability, _kGSEncodeAACCapability, 146 | _kGSEncryptedDataPartitionCapability, 147 | _kGSEnforceCameraShutterClick, _kGSEnforceGoogleMail, 148 | _kGSEventHardwareKeyboardAvailabilityChangedNotification, 149 | _kGSExplicitContentRestriction, 150 | _kGSFrontFacingCameraCapability, 151 | _kGSFull6FeaturesCapability, _kGSGPSCapability, 152 | _kGSGameKitCapability, _kGSGasGaugeBatteryCapability, 153 | _kGSGreenTeaDeviceCapability, 154 | _kGSGyroscopeCapability, _kGSH264EncoderCapability, 155 | _kGSHDRImageCaptureCapability, 156 | _kGSHDVideoCaptureCapability, 157 | _kGSHallEffectSensorCapability, 158 | _kGSHardwareEncodeSnapshotsCapability, 159 | _kGSHardwareKeyboardCapability, 160 | _kGSHardwareSnapshotsRequirePurpleGfxCapability, 161 | _kGSHasAllFeaturesCapability, 162 | _kGSHearingAidAudioEqualizationCapability, 163 | _kGSHearingAidLowEnergyAudioCapability, 164 | _kGSHearingAidPowerReductionCapability, 165 | _kGSHiDPICapability, _kGSHiccoughInterval, 166 | _kGSHideNonDefaultApplicationsCapability, 167 | _kGSIOSurfaceBackedImagesCapability, 168 | _kGSInternationalSettingsCapability, 169 | _kGSLTEDeviceCapability, _kGSLaunchModeCapability, 170 | _kGSLaunchModePostAnimate, _kGSLaunchModePreAnimate, 171 | _kGSLaunchModeSerial, 172 | _kGSLoadThumbnailsWhileScrollingCapability, 173 | _kGSLocalizedDeviceNameString, 174 | _kGSLocationRemindersCapability, 175 | _kGSLocationServicesCapability, _kGSMMSCapability, 176 | _kGSMagnetometerCapability, _kGSMainScreenHeight, 177 | _kGSMainScreenOrientation, _kGSMainScreenScale, 178 | _kGSMainScreenWidth, _kGSMarketingNameString, 179 | _kGSMicrophoneCapability, _kGSMultitaskingCapability, 180 | _kGSMultitaskingGesturesCapability, 181 | _kGSNikeIpodCapability, 182 | _kGSNotGreenTeaDeviceCapability, 183 | _kGSOpenGLES1Capability, _kGSOpenGLES2Capability, 184 | _kGSPTPLargeFilesCapability, _kGSPeer2PeerCapability, 185 | _kGSPersonalHotspotCapability, 186 | _kGSPhotoAdjustmentsCapability, 187 | _kGSPhotoStreamCapability, 188 | _kGSPiezoClickerCapability, 189 | _kGSPlatformStandAloneContactsCapability, 190 | _kGSProximitySensorCapability, 191 | _kGSRearFacingCameraCapability, 192 | _kGSRingerSwitchCapability, _kGSSMSCapability, 193 | _kGSScreenDimensionsCapability, 194 | _kGSSensitiveUICapability, _kGSShoeboxCapability, 195 | _kGSSiriGestureCapability, _kGSSoftwareDimmingAlpha, 196 | _kGSSystemTelephonyOfAnyKindCapability, 197 | _kGSTVOutCrossfadeCapability, 198 | _kGSTVOutSettingsCapability, 199 | _kGSTelephonyMaximumGeneration, 200 | _kGSUnifiedIPodCapability, _kGSVOIPCapability, 201 | _kGSVeniceCapability, _kGSVideoCameraCapability, 202 | _kGSVideoStillsCapability, 203 | _kGSVoiceControlCapability, 204 | _kGSVolumeButtonCapability, _kGSWAPICapability, 205 | _kGSWiFiCapability, _kGSYouTubeCapability, 206 | _kGSYouTubePluginCapability, _kGSiPadCapability ] 207 | ... 208 | -------------------------------------------------------------------------------- /sources/Daemon/Daemon.h: -------------------------------------------------------------------------------- 1 | typedef void (^ToggleCallback)(BOOL); 2 | 3 | @interface Daemon: NSObject 4 | @property (nonatomic, copy) ToggleCallback callback; 5 | 6 | -(void) toggle; 7 | -(BOOL) isEnabled; 8 | @end 9 | -------------------------------------------------------------------------------- /sources/Daemon/Daemon.m: -------------------------------------------------------------------------------- 1 | #import "Daemon.h" 2 | #import 3 | #import "HUDHelper.h" 4 | 5 | @implementation Daemon 6 | - (BOOL)isEnabled { 7 | return IsHUDEnabled(); 8 | } 9 | 10 | - (void)toggle { 11 | BOOL isNowEnabled = IsHUDEnabled(); 12 | SetHUDEnabled(!isNowEnabled); 13 | isNowEnabled = !isNowEnabled; 14 | 15 | if (isNowEnabled) { 16 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 17 | 18 | int anyToken; 19 | __weak typeof(self) weakSelf = self; 20 | notify_register_dispatch(NOTIFY_LAUNCHED_HUD, &anyToken, dispatch_get_main_queue(), ^(int token) { 21 | __strong typeof(weakSelf) strongSelf = weakSelf; 22 | notify_cancel(token); 23 | dispatch_semaphore_signal(semaphore); 24 | }); 25 | 26 | if(self.callback) self.callback(false); 27 | 28 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ 29 | intptr_t timedOut = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC))); 30 | dispatch_async(dispatch_get_main_queue(), ^{ 31 | if (timedOut) { 32 | log_error(OS_LOG_DEFAULT, "Timed out waiting for HUD to launch"); 33 | } 34 | 35 | if(self.callback) self.callback(true); 36 | }); 37 | }); 38 | } else { 39 | self.callback(false); 40 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 41 | self.callback(true); 42 | }); 43 | } 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /sources/Daemon/HUDApp.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | 6 | #import "HUDHelper.h" 7 | #import "BackboardServices.h" 8 | #import "UIApplication+Private.h" 9 | 10 | #define PID_PATH "/var/mobile/Library/Caches/ch.xxtou.hudapp.pid" 11 | 12 | static __used 13 | NSString *mDeviceModel(void) { 14 | struct utsname systemInfo; 15 | uname(&systemInfo); 16 | return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; 17 | } 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | @autoreleasepool 22 | { 23 | log_debug(OS_LOG_DEFAULT, "launched argc %{public}d, argv[1] %{public}s", argc, argc > 1 ? argv[1] : "NULL"); 24 | 25 | if (argc <= 1) { 26 | return UIApplicationMain(argc, argv, @"MainApplication", @"MainApplicationDelegate"); 27 | } 28 | 29 | #if !TARGET_IPHONE_SIMULATOR 30 | if (strcmp(argv[1], "-hud") == 0) 31 | { 32 | pid_t pid = getpid(); 33 | pid_t pgid = getgid(); 34 | (void)pgid; 35 | log_debug(OS_LOG_DEFAULT, "HUD pid %d, pgid %d", pid, pgid); 36 | 37 | NSString *pidString = [NSString stringWithFormat:@"%d", pid]; 38 | [pidString writeToFile:ROOT_PATH_NS(PID_PATH) 39 | atomically:YES 40 | encoding:NSUTF8StringEncoding 41 | error:nil]; 42 | 43 | [UIScreen initialize]; 44 | CFRunLoopGetCurrent(); 45 | 46 | GSInitialize(); 47 | BKSDisplayServicesStart(); 48 | UIApplicationInitialize(); 49 | 50 | UIApplicationInstantiateSingleton(objc_getClass("HUDMainApplication")); 51 | static id appDelegate = [[objc_getClass("HUDMainApplicationDelegate") alloc] init]; 52 | [UIApplication.sharedApplication setDelegate:appDelegate]; 53 | [UIApplication.sharedApplication _accessibilityInit]; 54 | 55 | [NSRunLoop currentRunLoop]; 56 | 57 | if (@available(iOS 15.0, *)) { 58 | GSEventInitialize(0); 59 | GSEventPushRunLoopMode(kCFRunLoopDefaultMode); 60 | } 61 | 62 | [UIApplication.sharedApplication __completeAndRunAsPlugin]; 63 | 64 | static int _springboardBootToken; 65 | notify_register_dispatch("SBSpringBoardDidLaunchNotification", &_springboardBootToken, dispatch_get_main_queue(), ^(int token) { 66 | notify_cancel(token); 67 | 68 | notify_post(NOTIFY_DISMISSAL_HUD); 69 | 70 | // Re-enable HUD after SpringBoard is launched. 71 | SetHUDEnabled(YES); 72 | 73 | // Exit the current instance of HUD. 74 | kill(pid, SIGKILL); 75 | }); 76 | 77 | CFRunLoopRun(); 78 | return EXIT_SUCCESS; 79 | } 80 | else if (strcmp(argv[1], "-exit") == 0) 81 | { 82 | NSString *pidString = [NSString stringWithContentsOfFile:ROOT_PATH_NS(PID_PATH) 83 | encoding:NSUTF8StringEncoding 84 | error:nil]; 85 | 86 | if (pidString) 87 | { 88 | pid_t pid = (pid_t)[pidString intValue]; 89 | kill(pid, SIGKILL); 90 | unlink([ROOT_PATH_NS(PID_PATH) UTF8String]); 91 | } 92 | 93 | return EXIT_SUCCESS; 94 | } 95 | else if (strcmp(argv[1], "-check") == 0) 96 | { 97 | NSString *pidString = [NSString stringWithContentsOfFile:ROOT_PATH_NS(PID_PATH) 98 | encoding:NSUTF8StringEncoding 99 | error:nil]; 100 | 101 | if (pidString) 102 | { 103 | pid_t pid = (pid_t)[pidString intValue]; 104 | int killed = kill(pid, 0); 105 | return (killed == 0 ? EXIT_FAILURE : EXIT_SUCCESS); 106 | } 107 | else return EXIT_SUCCESS; // No PID file, so HUD is not running 108 | } 109 | #endif 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /sources/Daemon/HUDBackdropView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface HUDBackdropView : UIView 6 | 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END 10 | -------------------------------------------------------------------------------- /sources/Daemon/HUDBackdropView.mm: -------------------------------------------------------------------------------- 1 | #import "HUDBackdropView.h" 2 | 3 | @implementation HUDBackdropView 4 | 5 | + (Class)layerClass { 6 | return [NSClassFromString(@"CABackdropLayer") class]; 7 | } 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /sources/Daemon/HUDHelper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | OBJC_EXTERN BOOL IsHUDEnabled(void); 6 | OBJC_EXTERN void SetHUDEnabled(BOOL isEnabled); 7 | 8 | OBJC_EXTERN double daemonWallpaperDelayInSeconds; 9 | 10 | #if DEBUG 11 | OBJC_EXTERN void SimulateMemoryPressure(void); 12 | #endif 13 | 14 | OBJC_EXTERN NSUserDefaults *GetStandardUserDefaults(void); 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /sources/Daemon/HUDHelper.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "HUDHelper.h" 6 | #import "NSUserDefaults+Private.h" 7 | 8 | extern "C" char **environ; 9 | 10 | #define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1 11 | extern "C" int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t); 12 | extern "C" int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t); 13 | extern "C" int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t); 14 | 15 | BOOL IsHUDEnabled(void) 16 | { 17 | static char *executablePath = NULL; 18 | uint32_t executablePathSize = 0; 19 | _NSGetExecutablePath(NULL, &executablePathSize); 20 | executablePath = (char *)calloc(1, executablePathSize); 21 | _NSGetExecutablePath(executablePath, &executablePathSize); 22 | 23 | posix_spawnattr_t attr; 24 | posix_spawnattr_init(&attr); 25 | 26 | posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); 27 | posix_spawnattr_set_persona_uid_np(&attr, 0); 28 | posix_spawnattr_set_persona_gid_np(&attr, 0); 29 | 30 | pid_t task_pid; 31 | const char *args[] = { executablePath, "-check", NULL }; 32 | posix_spawn(&task_pid, executablePath, NULL, &attr, (char **)args, environ); 33 | posix_spawnattr_destroy(&attr); 34 | log_debug(OS_LOG_DEFAULT, "spawned %{public}s -check pid = %{public}d", executablePath, task_pid); 35 | 36 | int status; 37 | do { 38 | if (waitpid(task_pid, &status, 0) != -1) 39 | { 40 | log_debug(OS_LOG_DEFAULT, "child status %d", WEXITSTATUS(status)); 41 | } 42 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 43 | 44 | return WEXITSTATUS(status) != 0; 45 | } 46 | 47 | #define LAUNCH_DAEMON_PATH ROOT_PATH("/Library/LaunchDaemons/com.inyourwalls.blossomservice.plist") 48 | 49 | void SetHUDEnabled(BOOL isEnabled) 50 | { 51 | notify_post(NOTIFY_DISMISSAL_HUD); 52 | 53 | posix_spawnattr_t attr; 54 | posix_spawnattr_init(&attr); 55 | 56 | posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); 57 | posix_spawnattr_set_persona_uid_np(&attr, 0); 58 | posix_spawnattr_set_persona_gid_np(&attr, 0); 59 | 60 | if (access(LAUNCH_DAEMON_PATH, F_OK) == 0) 61 | { 62 | if (!isEnabled) { 63 | [NSThread sleepForTimeInterval:FADE_OUT_DURATION]; 64 | } 65 | 66 | pid_t task_pid; 67 | static const char *executablePath = ROOT_PATH("/usr/bin/launchctl"); 68 | const char *args[] = { executablePath, isEnabled ? "load" : "unload", LAUNCH_DAEMON_PATH, NULL }; 69 | posix_spawn(&task_pid, executablePath, NULL, &attr, (char **)args, environ); 70 | posix_spawnattr_destroy(&attr); 71 | log_debug(OS_LOG_DEFAULT, "spawned %{public}s -exit pid = %{public}d", executablePath, task_pid); 72 | 73 | int status; 74 | do { 75 | if (waitpid(task_pid, &status, 0) != -1) 76 | { 77 | log_debug(OS_LOG_DEFAULT, "child status %d", WEXITSTATUS(status)); 78 | } 79 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 80 | 81 | return; 82 | } 83 | 84 | static char *executablePath = NULL; 85 | uint32_t executablePathSize = 0; 86 | _NSGetExecutablePath(NULL, &executablePathSize); 87 | executablePath = (char *)calloc(1, executablePathSize); 88 | _NSGetExecutablePath(executablePath, &executablePathSize); 89 | 90 | if (isEnabled) 91 | { 92 | posix_spawnattr_setpgroup(&attr, 0); 93 | posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); 94 | 95 | pid_t task_pid; 96 | const char *args[] = { executablePath, "-hud", NULL }; 97 | posix_spawn(&task_pid, executablePath, NULL, &attr, (char **)args, environ); 98 | posix_spawnattr_destroy(&attr); 99 | log_debug(OS_LOG_DEFAULT, "spawned %{public}s -hud pid = %{public}d", executablePath, task_pid); 100 | } 101 | else 102 | { 103 | [NSThread sleepForTimeInterval:FADE_OUT_DURATION]; 104 | 105 | pid_t task_pid; 106 | const char *args[] = { executablePath, "-exit", NULL }; 107 | posix_spawn(&task_pid, executablePath, NULL, &attr, (char **)args, environ); 108 | posix_spawnattr_destroy(&attr); 109 | log_debug(OS_LOG_DEFAULT, "spawned %{public}s -exit pid = %{public}d", executablePath, task_pid); 110 | 111 | int status; 112 | do { 113 | if (waitpid(task_pid, &status, 0) != -1) 114 | { 115 | log_debug(OS_LOG_DEFAULT, "child status %d", WEXITSTATUS(status)); 116 | } 117 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 118 | } 119 | } 120 | 121 | #if DEBUG 122 | void SimulateMemoryPressure(void) 123 | { 124 | static NSString *nsExecutablePath = nil; 125 | static const char *executablePath = NULL; 126 | static dispatch_once_t onceToken; 127 | dispatch_once(&onceToken, ^{ 128 | NSBundle *mainBundle = [NSBundle mainBundle]; 129 | nsExecutablePath = [mainBundle pathForResource:@"memory_pressure" ofType:nil]; 130 | if (nsExecutablePath) { 131 | executablePath = [nsExecutablePath UTF8String]; 132 | } 133 | }); 134 | 135 | if (!executablePath) { 136 | return; 137 | } 138 | 139 | posix_spawnattr_t attr; 140 | posix_spawnattr_init(&attr); 141 | 142 | posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); 143 | posix_spawnattr_set_persona_uid_np(&attr, 0); 144 | posix_spawnattr_set_persona_gid_np(&attr, 0); 145 | 146 | pid_t task_pid; 147 | const char *args[] = { executablePath, "-l", "critical", NULL }; 148 | posix_spawn(&task_pid, executablePath, NULL, &attr, (char **)args, environ); 149 | posix_spawnattr_destroy(&attr); 150 | 151 | log_debug(OS_LOG_DEFAULT, "spawned %{public}s -l critical pid = %{public}d", executablePath, task_pid); 152 | } 153 | #endif 154 | 155 | NSUserDefaults *GetStandardUserDefaults(void) 156 | { 157 | static NSUserDefaults *_userDefaults = nil; 158 | static dispatch_once_t onceToken; 159 | dispatch_once(&onceToken, ^{ 160 | NSString *containerPath = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByDeletingLastPathComponent]; 161 | NSURL *containerURL = [NSURL fileURLWithPath:containerPath]; 162 | _userDefaults = [[NSUserDefaults alloc] _initWithSuiteName:nil container:containerURL]; 163 | [_userDefaults registerDefaults:@{ 164 | @"LatestWallpaperContentsFilePath": @"" 165 | }]; 166 | }); 167 | return _userDefaults; 168 | } 169 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainApplication.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface HUDMainApplication : UIApplication 6 | @end 7 | 8 | NS_ASSUME_NONNULL_END 9 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainApplication.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | 7 | #import "pac_helper.h" 8 | #import "UIEventFetcher.h" 9 | #import "UIEventDispatcher.h" 10 | #import "HUDMainApplication.h" 11 | #import "UIApplication+Private.h" 12 | 13 | @implementation HUDMainApplication 14 | 15 | - (instancetype)init 16 | { 17 | if (self = [super init]) 18 | { 19 | log_debug(OS_LOG_DEFAULT, "- [HUDMainApplication init]"); 20 | 21 | { 22 | int outToken; 23 | notify_register_dispatch(NOTIFY_DISMISSAL_HUD, &outToken, dispatch_get_main_queue(), ^(int token) { 24 | notify_cancel(token); 25 | 26 | // Fade out the HUD window 27 | [UIView animateWithDuration:FADE_OUT_DURATION animations:^{ 28 | [[self.windows firstObject] setAlpha:0.0]; 29 | } completion:^(BOOL finished) { 30 | // Terminate the HUD app 31 | [self terminateWithSuccess]; 32 | }]; 33 | }); 34 | } 35 | 36 | do { 37 | UIEventDispatcher *dispatcher = (UIEventDispatcher *)[self valueForKey:@"eventDispatcher"]; 38 | if (!dispatcher) 39 | { 40 | log_error(OS_LOG_DEFAULT, "failed to get ivar _eventDispatcher"); 41 | break; 42 | } 43 | log_debug(OS_LOG_DEFAULT, "got ivar _eventDispatcher: %p", dispatcher); 44 | 45 | if ([dispatcher respondsToSelector:@selector(_installEventRunLoopSources:)]) 46 | { 47 | CFRunLoopRef mainRunLoop = CFRunLoopGetMain(); 48 | [dispatcher _installEventRunLoopSources:mainRunLoop]; 49 | } 50 | else 51 | { 52 | IMP runMethodIMP = class_getMethodImplementation([self class], @selector(_run)); 53 | if (!runMethodIMP) 54 | { 55 | log_error(OS_LOG_DEFAULT, "failed to get - [UIApplication _run] method"); 56 | break; 57 | } 58 | 59 | uint32_t *runMethodPtr = (uint32_t *)make_sym_readable((void *)runMethodIMP); 60 | log_debug(OS_LOG_DEFAULT, "- [UIApplication _run]: %p", runMethodPtr); 61 | 62 | void (*orig_UIEventDispatcher__installEventRunLoopSources_)(id _Nonnull, SEL _Nonnull, CFRunLoopRef) = NULL; 63 | for (int i = 0; i < 0x140; i++) 64 | { 65 | // mov x2, x0 66 | // mov x0, x? 67 | if (runMethodPtr[i] != 0xaa0003e2 || (runMethodPtr[i + 1] & 0xff000000) != 0xaa000000) 68 | continue; 69 | 70 | // bl -[UIEventDispatcher _installEventRunLoopSources:] 71 | uint32_t blInst = runMethodPtr[i + 2]; 72 | uint32_t *blInstPtr = &runMethodPtr[i + 2]; 73 | if ((blInst & 0xfc000000) != 0x94000000) 74 | { 75 | log_error(OS_LOG_DEFAULT, "not a BL instruction: 0x%x, address %p", blInst, blInstPtr); 76 | continue; 77 | } 78 | log_debug(OS_LOG_DEFAULT, "found BL instruction: 0x%x, address %p", blInst, blInstPtr); 79 | 80 | int32_t blOffset = blInst & 0x03ffffff; 81 | if (blOffset & 0x02000000) 82 | blOffset |= 0xfc000000; 83 | blOffset <<= 2; 84 | log_debug(OS_LOG_DEFAULT, "BL offset: 0x%x", blOffset); 85 | 86 | uint64_t blAddr = (uint64_t)blInstPtr + blOffset; 87 | log_debug(OS_LOG_DEFAULT, "BL target address: %p", (void *)blAddr); 88 | 89 | // cbz x0, loc_????????? 90 | uint32_t cbzInst = *((uint32_t *)make_sym_readable((void *)blAddr)); 91 | if ((cbzInst & 0xff000000) != 0xb4000000) 92 | { 93 | log_error(OS_LOG_DEFAULT, "not a CBZ instruction: 0x%x", cbzInst); 94 | continue; 95 | } 96 | 97 | log_debug(OS_LOG_DEFAULT, "found CBZ instruction: 0x%x, address %p", cbzInst, (void *)blAddr); 98 | 99 | orig_UIEventDispatcher__installEventRunLoopSources_ = (void (*)(id _Nonnull __strong, SEL _Nonnull, CFRunLoopRef))make_sym_callable((void *)blAddr); 100 | } 101 | 102 | if (!orig_UIEventDispatcher__installEventRunLoopSources_) 103 | { 104 | log_error(OS_LOG_DEFAULT, "failed to find -[UIEventDispatcher _installEventRunLoopSources:]"); 105 | break; 106 | } 107 | 108 | log_debug(OS_LOG_DEFAULT, "- [UIEventDispatcher _installEventRunLoopSources:]: %p", orig_UIEventDispatcher__installEventRunLoopSources_); 109 | 110 | CFRunLoopRef mainRunLoop = CFRunLoopGetMain(); 111 | orig_UIEventDispatcher__installEventRunLoopSources_(dispatcher, @selector(_installEventRunLoopSources:), mainRunLoop); 112 | } 113 | 114 | UIEventFetcher *fetcher = [[objc_getClass("UIEventFetcher") alloc] init]; 115 | [dispatcher setValue:fetcher forKey:@"eventFetcher"]; 116 | 117 | if ([fetcher respondsToSelector:@selector(setEventFetcherSink:)]) { 118 | [fetcher setEventFetcherSink:dispatcher]; 119 | } 120 | else 121 | { 122 | /* Tested on iOS 15.1.1 and below */ 123 | [fetcher setValue:dispatcher forKey:@"eventFetcherSink"]; 124 | } 125 | 126 | [self setValue:fetcher forKey:@"eventFetcher"]; 127 | } while (NO); 128 | } 129 | return self; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainApplicationDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface HUDMainApplicationDelegate : UIResponder 6 | @property (nonatomic, strong) UIWindow *window; 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END 10 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainApplicationDelegate.mm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "HUDMainApplicationDelegate.h" 4 | #import "HUDMainWindow.h" 5 | #import "HUDRootViewController.h" 6 | 7 | #import "SBSAccessibilityWindowHostingController.h" 8 | #import "UIWindow+Private.h" 9 | 10 | @implementation HUDMainApplicationDelegate { 11 | HUDRootViewController *_rootViewController; 12 | SBSAccessibilityWindowHostingController *_windowHostingController; 13 | } 14 | 15 | - (instancetype)init 16 | { 17 | if (self = [super init]) 18 | { 19 | log_debug(OS_LOG_DEFAULT, "- [HUDMainApplicationDelegate init]"); 20 | } 21 | return self; 22 | } 23 | 24 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 25 | { 26 | log_debug(OS_LOG_DEFAULT, "- [HUDMainApplicationDelegate application:%{public}@ didFinishLaunchingWithOptions:%{public}@]", application, launchOptions); 27 | 28 | _rootViewController = [[HUDRootViewController alloc] init]; 29 | 30 | self.window = [[HUDMainWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 31 | [self.window setRootViewController:_rootViewController]; 32 | 33 | [self.window setWindowLevel:10000010.0]; 34 | [self.window setHidden:NO]; 35 | [self.window makeKeyAndVisible]; 36 | 37 | _windowHostingController = [[objc_getClass("SBSAccessibilityWindowHostingController") alloc] init]; 38 | unsigned int _contextId = [self.window _contextId]; 39 | double windowLevel = [self.window windowLevel]; 40 | 41 | #pragma clang diagnostic push 42 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 43 | // [_windowHostingController registerWindowWithContextID:_contextId atLevel:windowLevel]; 44 | NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:Id"]; 45 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 46 | [invocation setTarget:_windowHostingController]; 47 | [invocation setSelector:NSSelectorFromString(@"registerWindowWithContextID:atLevel:")]; 48 | [invocation setArgument:&_contextId atIndex:2]; 49 | [invocation setArgument:&windowLevel atIndex:3]; 50 | [invocation invoke]; 51 | #pragma clang diagnostic pop 52 | 53 | return YES; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainWindow.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface HUDMainWindow : UIWindow 6 | @end 7 | 8 | NS_ASSUME_NONNULL_END 9 | -------------------------------------------------------------------------------- /sources/Daemon/HUDMainWindow.mm: -------------------------------------------------------------------------------- 1 | #import "HUDMainWindow.h" 2 | #import "HUDRootViewController.h" 3 | 4 | @implementation HUDMainWindow 5 | 6 | + (BOOL)_isSystemWindow { return YES; } 7 | - (BOOL)_isWindowServerHostingManaged { return NO; } 8 | - (BOOL)_ignoresHitTest { return YES; } 9 | - (BOOL)_isSecure { return YES; } 10 | - (BOOL)_shouldCreateContextAsSecure { return YES; } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /sources/Daemon/HUDRootViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "../Wallpaper/Wallpaper.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface HUDRootViewController: UIViewController 7 | 8 | @property (nonatomic, strong) Wallpaper *wallpaper; 9 | @property (nonatomic, strong) NSTimer *wallpaperTimer; 10 | @property (nonatomic, assign) BOOL isPaused; 11 | @property (nonatomic, assign) double interval; 12 | 13 | @end 14 | 15 | NS_ASSUME_NONNULL_END 16 | -------------------------------------------------------------------------------- /sources/Daemon/HUDRootViewController.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | 6 | #import "../Wallpaper/Wallpaper.h" 7 | #import "HUDRootViewController.h" 8 | 9 | #pragma mark - 10 | 11 | #import "UIApplication+Private.h" 12 | #import "LSApplicationProxy.h" 13 | #import "LSApplicationWorkspace.h" 14 | #import "SpringBoardServices.h" 15 | 16 | #define NOTIFY_UI_LOCKSTATE "com.apple.springboard.lockstate" 17 | #define NOTIFY_LS_APP_CHANGED "com.apple.LaunchServices.ApplicationsChanged" 18 | 19 | static void LaunchServicesApplicationStateChanged 20 | (CFNotificationCenterRef center, 21 | void *observer, 22 | CFStringRef name, 23 | const void *object, 24 | CFDictionaryRef userInfo) 25 | { 26 | /* Application installed or uninstalled */ 27 | 28 | BOOL isAppInstalled = NO; 29 | 30 | for (LSApplicationProxy *app in [[objc_getClass("LSApplicationWorkspace") defaultWorkspace] allApplications]) 31 | { 32 | if ([app.applicationIdentifier isEqualToString:@"com.inyourwalls.blossom"]) 33 | { 34 | isAppInstalled = YES; 35 | break; 36 | } 37 | } 38 | 39 | if (!isAppInstalled) 40 | { 41 | UIApplication *app = [UIApplication sharedApplication]; 42 | [app terminateWithSuccess]; 43 | } 44 | } 45 | 46 | #pragma mark - HUDRootViewController 47 | 48 | @interface HUDRootViewController (Troll) 49 | @end 50 | 51 | @implementation HUDRootViewController { 52 | } 53 | 54 | - (void)registerNotifications 55 | { 56 | 57 | #if !TARGET_IPHONE_SIMULATOR 58 | int token; 59 | notify_register_dispatch(NOTIFY_RELOAD_HUD, &token, dispatch_get_main_queue(), ^(int token) { 60 | 61 | }); 62 | 63 | [[NSNotificationCenter defaultCenter] addObserver:self 64 | selector:@selector(screenBrightnessDidChange:) 65 | name:UIScreenBrightnessDidChangeNotification 66 | object:nil]; 67 | 68 | if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 69 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 70 | [[NSNotificationCenter defaultCenter] addObserver:self 71 | selector:@selector(deviceOrientationDidChange:) 72 | name:UIDeviceOrientationDidChangeNotification 73 | object:nil]; 74 | } 75 | #endif 76 | } 77 | 78 | - (void)deviceOrientationDidChange:(NSNotification *)notification { 79 | UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; 80 | 81 | NSString* contentsFilePath = [GetStandardUserDefaults() stringForKey:@"LatestWallpaperContentsFilePath"]; 82 | 83 | if ([[NSFileManager defaultManager] fileExistsAtPath:contentsFilePath]) { 84 | NSError *error = nil; 85 | NSString *fileContents = [NSString stringWithContentsOfFile:contentsFilePath 86 | encoding:NSUTF8StringEncoding 87 | error:&error]; 88 | 89 | if (error) { 90 | NSLog(@"Failed to read file: %@", error.localizedDescription); 91 | return; 92 | } 93 | 94 | if (orientation == UIDeviceOrientationPortrait || orientation == UIDeviceOrientationPortraitUpsideDown) { 95 | NSLog(@"Device is in Portrait orientation."); 96 | 97 | NSString *modifiedContents = [fileContents stringByReplacingOccurrencesOfString:@"landscape-layer_background.HEIC" 98 | withString:@"portrait-layer_background.HEIC"]; 99 | 100 | [modifiedContents writeToFile:contentsFilePath 101 | atomically:YES 102 | encoding:NSUTF8StringEncoding 103 | error:&error]; 104 | } else if (orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight) { 105 | NSLog(@"Device is in Landscape orientation."); 106 | 107 | NSString *modifiedContents = [fileContents stringByReplacingOccurrencesOfString:@"portrait-layer_background.HEIC" 108 | withString:@"landscape-layer_background.HEIC"]; 109 | 110 | [modifiedContents writeToFile:contentsFilePath 111 | atomically:YES 112 | encoding:NSUTF8StringEncoding 113 | error:&error]; 114 | } else { 115 | NSLog(@"Device orientation is unknown or flat."); 116 | } 117 | } else { 118 | NSLog(@"Failed to find Contents.json file. Was wallpaper deleted?"); 119 | } 120 | } 121 | 122 | - (void)screenBrightnessDidChange:(NSNotification *)notification { 123 | CGFloat brightness = [UIScreen mainScreen].brightness; 124 | 125 | if(brightness < 0.1) { 126 | if(!self.isPaused) { 127 | [[self wallpaperTimer] invalidate]; 128 | self.isPaused = YES; 129 | } 130 | } else { 131 | if(self.isPaused) { 132 | self.wallpaperTimer = [NSTimer scheduledTimerWithTimeInterval:[self interval] repeats:true block:^(NSTimer * _Nonnull timer) { 133 | [[self wallpaper] restartPoster]; 134 | }]; 135 | self.isPaused = NO; 136 | } 137 | } 138 | } 139 | 140 | - (BOOL)usesCustomFontSize { return NO; } 141 | - (CGFloat)realCustomFontSize { return 0; } 142 | - (BOOL)usesCustomOffset { return NO; } 143 | - (CGFloat)realCustomOffsetX { return 0; } 144 | - (CGFloat)realCustomOffsetY { return 0; } 145 | 146 | - (instancetype)init 147 | { 148 | self = [super init]; 149 | 150 | self.wallpaper = [[Wallpaper alloc] init]; 151 | self.interval = 5.3; 152 | 153 | if (self) { 154 | [self registerNotifications]; 155 | } 156 | return self; 157 | } 158 | 159 | - (void)viewDidLoad { 160 | [super viewDidLoad]; 161 | 162 | [[self wallpaper] restartPoster]; 163 | 164 | self.wallpaperTimer = [NSTimer scheduledTimerWithTimeInterval:[self interval] repeats: true block: ^(NSTimer * _Nonnull timer) { 165 | [[self wallpaper] restartPoster]; 166 | }]; 167 | } 168 | 169 | - (void)viewDidAppear:(BOOL)animated 170 | { 171 | [super viewDidAppear:animated]; 172 | notify_post(NOTIFY_LAUNCHED_HUD); 173 | } 174 | 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /sources/Daemon/RootViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface RootViewController : UIViewController 6 | @property (nonatomic, strong) UIView *backgroundView; 7 | + (void)setShouldToggleHUDAfterLaunch:(BOOL)flag; 8 | @end 9 | 10 | NS_ASSUME_NONNULL_END 11 | -------------------------------------------------------------------------------- /sources/Daemon/RootViewController.mm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "HUDHelper.h" 4 | #import "MainApplication.h" 5 | #import "RootViewController.h" 6 | #import "UIApplication+Private.h" 7 | #import "HUDRootViewController.h" 8 | 9 | #import "Blossom-Swift.h" 10 | 11 | #define HUD_TRANSITION_DURATION 0.25 12 | 13 | static BOOL _gShouldToggleHUDAfterLaunch = NO; 14 | 15 | @implementation RootViewController { 16 | BOOL _supportsCenterMost; 17 | BOOL _isRemoteHUDActive; 18 | } 19 | 20 | + (void)setShouldToggleHUDAfterLaunch:(BOOL)flag 21 | { 22 | _gShouldToggleHUDAfterLaunch = flag; 23 | } 24 | 25 | + (BOOL)shouldToggleHUDAfterLaunch 26 | { 27 | return _gShouldToggleHUDAfterLaunch; 28 | } 29 | 30 | - (BOOL)isHUDEnabled 31 | { 32 | return IsHUDEnabled(); 33 | } 34 | 35 | - (void)setHUDEnabled:(BOOL)enabled 36 | { 37 | SetHUDEnabled(enabled); 38 | } 39 | 40 | - (void)registerNotifications 41 | { 42 | int token; 43 | notify_register_dispatch(NOTIFY_RELOAD_APP, &token, dispatch_get_main_queue(), ^(int token) { 44 | 45 | }); 46 | 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(toggleHUDNotificationReceived:) name:kToggleHUDAfterLaunchNotificationName object:nil]; 48 | } 49 | 50 | - (void)loadView 51 | { 52 | CGRect bounds = UIScreen.mainScreen.bounds; 53 | 54 | self.view = [[UIView alloc] initWithFrame:bounds]; 55 | } 56 | 57 | - (void)viewDidLayoutSubviews { 58 | [super viewDidLayoutSubviews]; 59 | _supportsCenterMost = CGRectGetMinY(self.view.window.safeAreaLayoutGuide.layoutFrame) >= 51; 60 | } 61 | 62 | - (void)viewDidLoad { 63 | [super viewDidLoad]; 64 | 65 | BlossomViewController *controller = [[BlossomViewController alloc] init]; 66 | [self addChildViewController:controller]; 67 | [self.view addSubview:controller.view]; 68 | 69 | controller.view.frame = self.view.bounds; 70 | [controller didMoveToParentViewController:self]; 71 | 72 | [self registerNotifications]; 73 | } 74 | 75 | - (void)viewDidAppear:(BOOL)animated { 76 | [super viewDidAppear:animated]; 77 | [self toggleHUDAfterLaunch]; 78 | } 79 | 80 | - (void)toggleHUDNotificationReceived:(NSNotification *)notification { 81 | NSString *toggleAction = notification.userInfo[kToggleHUDAfterLaunchNotificationActionKey]; 82 | if (!toggleAction) { 83 | [self toggleHUDAfterLaunch]; 84 | } else if ([toggleAction isEqualToString:kToggleHUDAfterLaunchNotificationActionToggleOn]) { 85 | [self toggleOnHUDAfterLaunch]; 86 | } else if ([toggleAction isEqualToString:kToggleHUDAfterLaunchNotificationActionToggleOff]) { 87 | [self toggleOffHUDAfterLaunch]; 88 | } 89 | } 90 | 91 | - (void)toggleHUDAfterLaunch { 92 | if ([RootViewController shouldToggleHUDAfterLaunch]) { 93 | [RootViewController setShouldToggleHUDAfterLaunch:NO]; 94 | 95 | [[UIApplication sharedApplication] suspend]; 96 | } 97 | } 98 | 99 | - (void)toggleOnHUDAfterLaunch { 100 | if ([RootViewController shouldToggleHUDAfterLaunch]) { 101 | [RootViewController setShouldToggleHUDAfterLaunch:NO]; 102 | 103 | [[UIApplication sharedApplication] suspend]; 104 | } 105 | } 106 | 107 | - (void)toggleOffHUDAfterLaunch { 108 | if ([RootViewController shouldToggleHUDAfterLaunch]) { 109 | [RootViewController setShouldToggleHUDAfterLaunch:NO]; 110 | 111 | [[UIApplication sharedApplication] suspend]; 112 | } 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /sources/MainApplication.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | static NSString * const kToggleHUDAfterLaunchNotificationName = @"com.inyourwalls.blossom.notification.toggle-hud"; 4 | static NSString * const kToggleHUDAfterLaunchNotificationActionKey = @"action"; 5 | static NSString * const kToggleHUDAfterLaunchNotificationActionToggleOn = @"toggle-on"; 6 | static NSString * const kToggleHUDAfterLaunchNotificationActionToggleOff = @"toggle-off"; 7 | 8 | NS_ASSUME_NONNULL_BEGIN 9 | 10 | @interface MainApplication : UIApplication 11 | @end 12 | 13 | NS_ASSUME_NONNULL_END 14 | -------------------------------------------------------------------------------- /sources/MainApplication.mm: -------------------------------------------------------------------------------- 1 | #import "MainApplication.h" 2 | 3 | @implementation MainApplication 4 | @end 5 | -------------------------------------------------------------------------------- /sources/MainApplicationDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface MainApplicationDelegate : UIResponder 6 | @property (nonatomic, strong) UIWindow *window; 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END 10 | -------------------------------------------------------------------------------- /sources/MainApplicationDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "MainApplicationDelegate.h" 2 | #import "MainApplication.h" 3 | #import "RootViewController.h" 4 | 5 | #import "HUDHelper.h" 6 | 7 | @implementation MainApplicationDelegate { 8 | RootViewController *_rootViewController; 9 | } 10 | 11 | - (instancetype)init { 12 | if (self = [super init]) { 13 | log_debug(OS_LOG_DEFAULT, "- [MainApplicationDelegate init]"); 14 | } 15 | return self; 16 | } 17 | 18 | - (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary *)options 19 | { 20 | if ([url.scheme isEqualToString:@"blossom"]) { 21 | if ([url.host isEqualToString:@"toggle"]) { 22 | [self setupAndNotifyToggleHUDAfterLaunchWithAction:nil]; 23 | return YES; 24 | } else if ([url.host isEqualToString:@"on"]) { 25 | [self setupAndNotifyToggleHUDAfterLaunchWithAction:kToggleHUDAfterLaunchNotificationActionToggleOn]; 26 | return YES; 27 | } else if ([url.host isEqualToString:@"off"]) { 28 | [self setupAndNotifyToggleHUDAfterLaunchWithAction:kToggleHUDAfterLaunchNotificationActionToggleOff]; 29 | return YES; 30 | } 31 | } 32 | return NO; 33 | } 34 | 35 | - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler 36 | { 37 | if ([shortcutItem.type isEqualToString:@"com.inyourwalls.shortcut.toggle-hud"]) 38 | { 39 | [self setupAndNotifyToggleHUDAfterLaunchWithAction:nil]; 40 | } 41 | } 42 | 43 | - (void)setupAndNotifyToggleHUDAfterLaunchWithAction:(NSString *)action 44 | { 45 | [RootViewController setShouldToggleHUDAfterLaunch:YES]; 46 | if (action) { 47 | [[NSNotificationCenter defaultCenter] postNotificationName:kToggleHUDAfterLaunchNotificationName object:nil userInfo:@{ 48 | kToggleHUDAfterLaunchNotificationActionKey: action, 49 | }]; 50 | } else { 51 | [[NSNotificationCenter defaultCenter] postNotificationName:kToggleHUDAfterLaunchNotificationName object:nil]; 52 | } 53 | } 54 | 55 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 56 | log_debug(OS_LOG_DEFAULT, "- [MainApplicationDelegate application:%{public}@ didFinishLaunchingWithOptions:%{public}@]", application, launchOptions); 57 | 58 | _rootViewController = [[RootViewController alloc] init]; 59 | 60 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 61 | [self.window setRootViewController:_rootViewController]; 62 | [self.window makeKeyAndVisible]; 63 | 64 | return YES; 65 | } 66 | 67 | - (void)applicationDidBecomeActive:(UIApplication *)application { 68 | log_debug(OS_LOG_DEFAULT, "- [MainApplicationDelegate applicationDidBecomeActive:%{public}@]", application); 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /sources/UI/BlossomView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct BlossomView: View { 4 | 5 | @Environment(\.horizontalSizeClass) var horizontalSizeClass 6 | 7 | @State private var isLoopEnabled: Bool = false 8 | @State private var isInitialized: Bool = false 9 | @State private var interactionAllowed: Bool = false 10 | 11 | @State private var aboutAlert = false 12 | 13 | @ObservedObject var sheetManager: SheetManager = SheetManager() 14 | 15 | private var daemon: Daemon = Daemon() 16 | 17 | var body: some View { 18 | ConditionalNavigationView { 19 | VStack(spacing: 10) { 20 | Spacer() 21 | 22 | Image(uiImage: UIImage(named: "Icon")!) 23 | .resizable() 24 | .frame(width: 300, height: 300) 25 | .cornerRadius(10) 26 | .padding(.top, 10) 27 | .padding(.bottom, 10) 28 | 29 | Spacer() 30 | 31 | List { 32 | Section(header: 33 | HStack { 34 | Image(systemName: "sparkles") 35 | .foregroundColor(.purple) 36 | Text("Blossom") 37 | .foregroundColor(.purple) 38 | } 39 | .padding(.bottom, 5) 40 | ) { 41 | Button(action: { 42 | sheetManager.wallpaperSelector = true 43 | }) { 44 | HStack { 45 | Image(systemName: "photo") 46 | .foregroundColor(.purple) 47 | .frame(width: 30, height: 30) 48 | Text("Change Wallpaper") 49 | .foregroundStyle(.black) 50 | } 51 | } 52 | } 53 | 54 | Section { 55 | HStack { 56 | Image(systemName: "timer") 57 | .foregroundColor(.purple) 58 | .frame(width: 30, height: 30) 59 | 60 | Text("Loop") 61 | 62 | Spacer() 63 | Toggle("", isOn: $isLoopEnabled) 64 | .frame(width: 50, height: 0) 65 | .toggleStyle(SwitchToggleStyle(tint: .purple)) 66 | .disabled(!interactionAllowed) 67 | } 68 | } 69 | 70 | if horizontalSizeClass != .compact { 71 | Section { 72 | Button(action: { 73 | self.aboutAlert = true 74 | }) { 75 | HStack { 76 | Image(systemName: "info.circle") 77 | .foregroundColor(.purple) 78 | .frame(width: 30, height: 30) 79 | 80 | Text("About") 81 | .foregroundStyle(.black) 82 | } 83 | } 84 | } 85 | } 86 | } 87 | .listStyle(InsetGroupedListStyle()) 88 | .scrollDisabled(true) 89 | .frame(height: horizontalSizeClass == .compact ? 190 : 260) 90 | } 91 | .navigationTitle("Blossom") 92 | .navigationBarTitleDisplayMode(.inline) 93 | .toolbar { 94 | ToolbarItem(placement: .navigationBarTrailing) { 95 | Button(action: { 96 | aboutAlert = true 97 | }) { 98 | Image(systemName: "info.circle") .font(.title3) 99 | .foregroundStyle(.purple) 100 | } 101 | } 102 | } 103 | .onChange(of: isLoopEnabled) { newValue in 104 | if isInitialized { 105 | daemon.toggle() 106 | } 107 | } 108 | .onAppear { 109 | daemon.callback = { allowInteraction in 110 | interactionAllowed = allowInteraction 111 | } 112 | 113 | self.isLoopEnabled = daemon.isEnabled() 114 | 115 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 116 | self.isInitialized = true 117 | self.interactionAllowed = true 118 | } 119 | } 120 | } 121 | .alert(isPresented: $aboutAlert) { 122 | Alert( 123 | title: Text("Blossom"), 124 | message: Text("iOS 17 offers the ability to set a live photo as your wallpaper, but it comes with limitations and doesn’t even work as expected.\n\nThis app allows you to swap live photo from camera roll with a custom 5 second video and make it loop if desired.\n\nIf you encounter any issues, refer to the FAQ section in the GitHub repository."), 125 | primaryButton: .default(Text("Close")), 126 | secondaryButton: .default(Text("Visit GitHub repository")) { 127 | if let url = URL(string: "https://github.com/inyourwalls/Blossom") { 128 | UIApplication.shared.open(url) 129 | } 130 | } 131 | ) 132 | } 133 | .sheet(isPresented: $sheetManager.wallpaperSelector, content: { 134 | WallpaperSelectorModal(sheetManager: sheetManager) 135 | }) 136 | .onChange(of: sheetManager.cropGuide) { newValue in 137 | if !sheetManager.cropGuide { 138 | sheetManager.closeAll() 139 | } 140 | } 141 | .preferredColorScheme(.light) 142 | } 143 | } 144 | 145 | #Preview { 146 | BlossomView() 147 | } 148 | -------------------------------------------------------------------------------- /sources/UI/BlossomViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | 4 | class BlossomViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | 9 | let blossomView = BlossomView() 10 | let hostingController = UIHostingController(rootView: blossomView) 11 | 12 | addChild(hostingController) 13 | view.addSubview(hostingController.view) 14 | 15 | hostingController.view.translatesAutoresizingMaskIntoConstraints = false 16 | NSLayoutConstraint.activate([ 17 | hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), 18 | hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), 19 | hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), 20 | hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) 21 | ]) 22 | 23 | overrideUserInterfaceStyle = .light 24 | UIApplication.shared.keyWindow?.tintColor = .purple 25 | 26 | hostingController.didMove(toParent: self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sources/UI/ConditionalNavigationView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ConditionalNavigationView: View { 4 | 5 | @Environment(\.horizontalSizeClass) var horizontalSizeClass 6 | let content: Content 7 | 8 | init(@ViewBuilder content: () -> Content) { 9 | self.content = content() 10 | } 11 | 12 | var body: some View { 13 | Group { 14 | if horizontalSizeClass == .compact { 15 | NavigationView { 16 | content 17 | } 18 | } else { 19 | content 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sources/UI/CropGuideView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AVFoundation 3 | 4 | struct CropGuideView: View { 5 | 6 | @State var sheetManager: SheetManager 7 | @State var showText = true 8 | 9 | var body: some View { 10 | ZStack { 11 | if let url = createLocalUrl(for: "setup", ofType: "mov") { 12 | LiveWallpaperView(wallpaperPath: "setup", player: AVPlayer(url: url)) 13 | } else { 14 | Text("Unable to load video.") 15 | .padding() 16 | } 17 | 18 | if showText { 19 | let width = Int( UIScreen.main.bounds.size.width * UIScreen.main.scale) 20 | let height = Int(UIScreen.main.bounds.size.height * UIScreen.main.scale) 21 | 22 | VStack { 23 | Spacer() 24 | Text("Your video must be exactly\n5 seconds long\nand cropped to your screen resolution of \(width) x \(height).") 25 | .font(.headline) 26 | .frame(width: 250) 27 | .foregroundColor(.white) 28 | .multilineTextAlignment(.center) 29 | .padding() 30 | .background(Color.black.opacity(0.7)) 31 | .cornerRadius(10) 32 | .padding() 33 | .padding(.bottom, 200) 34 | } 35 | } 36 | } 37 | .preferredColorScheme(.dark) 38 | .background(.black) 39 | .onTapGesture { 40 | showText = false 41 | } 42 | } 43 | 44 | func createLocalUrl(for filename: String, ofType: String) -> URL? { 45 | let fileManager = FileManager.default 46 | let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0] 47 | let url = cacheDirectory.appendingPathComponent("\(filename).\(ofType)") 48 | 49 | guard fileManager.fileExists(atPath: url.path) else { 50 | guard let video = NSDataAsset(name: filename) else { return nil } 51 | fileManager.createFile(atPath: url.path, contents: video.data, attributes: nil) 52 | return url 53 | } 54 | 55 | return url 56 | } 57 | } 58 | 59 | #Preview { 60 | CropGuideView(sheetManager: SheetManager()) 61 | } 62 | -------------------------------------------------------------------------------- /sources/UI/Editor/AVPlayer+isPlaying.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | extension AVPlayer { 4 | 5 | var isPlaying: Bool { 6 | return self.rate != 0 && self.error == nil 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /sources/UI/Editor/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /sources/UI/Editor/UIImage+scalePreservingAspectRatio.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIImage { 4 | func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage { 5 | let widthRatio = targetSize.width / size.width 6 | let heightRatio = targetSize.height / size.height 7 | 8 | let scaleFactor = min(widthRatio, heightRatio) 9 | 10 | let scaledImageSize = CGSize( 11 | width: size.width * scaleFactor, 12 | height: size.height * scaleFactor 13 | ) 14 | 15 | let renderer = UIGraphicsImageRenderer( 16 | size: scaledImageSize 17 | ) 18 | 19 | let scaledImage = renderer.image { _ in 20 | self.draw(in: CGRect( 21 | origin: .zero, 22 | size: scaledImageSize 23 | )) 24 | } 25 | 26 | return scaledImage 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sources/UI/Editor/VideoCropperViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import Photos 4 | import PryntTrimmerView 5 | import AVFoundation 6 | 7 | class VideoCropperViewController: UIViewController { 8 | 9 | @IBOutlet weak var videoCropView: VideoCropView! 10 | @IBOutlet weak var selectThumbView: ThumbSelectorView! 11 | 12 | var asset: AVAsset? 13 | var trimStartTime: CMTime = CMTime() 14 | var trimEndTime: CMTime = CMTime() 15 | var onComplete: ((AVAsset, UIImage) -> Void)? 16 | var onLoading: ((Bool) -> Void)? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | if UIDevice.current.userInterfaceIdiom == .pad { 22 | videoCropView.setAspectRatio(CGSize(width: 3, height: 4), animated: false) 23 | } else { 24 | videoCropView.setAspectRatio(CGSize(width: 9, height: 19.5), animated: false) 25 | } 26 | 27 | if let asset = asset { 28 | self.loadAsset(asset) 29 | } 30 | } 31 | 32 | func showError(message: String) { 33 | let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) 34 | let ok = UIAlertAction(title: "OK", style: .default) 35 | 36 | alert.addAction(ok) 37 | present(alert, animated: true, completion: nil) 38 | } 39 | 40 | func loadAsset(_ asset: AVAsset) { 41 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 42 | self.selectThumbView.asset = asset 43 | self.selectThumbView.delegate = self 44 | self.videoCropView.asset = asset 45 | } 46 | } 47 | 48 | @IBAction func done(_ sender: Any) { 49 | onLoading?(true) 50 | try? prepareAssetComposition() 51 | } 52 | 53 | func prepareAssetComposition() throws { 54 | guard let asset = videoCropView.asset, let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first else { 55 | onLoading?(false) 56 | return 57 | } 58 | 59 | let assetComposition = AVMutableComposition() 60 | let trackTimeRange = CMTimeRangeMake(start: trimStartTime, duration: trimEndTime) 61 | 62 | guard let videoCompositionTrack = assetComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { 63 | onLoading?(false) 64 | return 65 | } 66 | 67 | try videoCompositionTrack.insertTimeRange(trackTimeRange, of: videoTrack, at: CMTime.zero) 68 | 69 | let mainInstructions = AVMutableVideoCompositionInstruction() 70 | mainInstructions.timeRange = CMTimeRangeMake(start: .zero, duration: asset.duration) 71 | 72 | let layerInstructions = AVMutableVideoCompositionLayerInstruction(assetTrack: videoCompositionTrack) 73 | 74 | var renderSize = CGSize(width: 16 * videoCropView.aspectRatio.width * 18, 75 | height: 16 * videoCropView.aspectRatio.height * 18) 76 | 77 | let transform = getTransform(for: videoTrack) 78 | 79 | layerInstructions.setTransform(transform, at: CMTime.zero) 80 | layerInstructions.setOpacity(1.0, at: CMTime.zero) 81 | mainInstructions.layerInstructions = [layerInstructions] 82 | 83 | let videoComposition = AVMutableVideoComposition() 84 | videoComposition.renderSize = renderSize 85 | videoComposition.instructions = [mainInstructions] 86 | videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) 87 | 88 | let url = URL(fileURLWithPath: "\(NSTemporaryDirectory())TrimmedMovie.mov") 89 | try? FileManager.default.removeItem(at: url) 90 | 91 | let exportSession = AVAssetExportSession(asset: assetComposition, presetName: AVAssetExportPresetHighestQuality) 92 | exportSession?.outputFileType = AVFileType.mov 93 | exportSession?.shouldOptimizeForNetworkUse = true 94 | exportSession?.videoComposition = videoComposition 95 | exportSession?.outputURL = url 96 | exportSession?.exportAsynchronously(completionHandler: { 97 | DispatchQueue.main.async { 98 | if let url = exportSession?.outputURL, exportSession?.status == .completed { 99 | let generator = AVAssetImageGenerator(asset: asset) 100 | generator.requestedTimeToleranceBefore = CMTime.zero 101 | generator.requestedTimeToleranceAfter = CMTime.zero 102 | generator.appliesPreferredTrackTransform = true 103 | let image = try? generator.copyCGImage(at: self.trimEndTime, actualTime: nil) 104 | if let image = image { 105 | let selectedImage = UIImage(cgImage: image, scale: UIScreen.main.scale, orientation: .up) 106 | let croppedImage = selectedImage.crop(in: self.videoCropView.getImageCropFrame())! 107 | 108 | let scaledImage = croppedImage.scalePreservingAspectRatio(targetSize: CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) 109 | 110 | self.onLoading?(false) 111 | self.onComplete?(AVAsset(url: url), scaledImage) 112 | } else { 113 | self.onLoading?(false) 114 | self.showError(message: "Failed to extract last frame of the video") 115 | } 116 | } else { 117 | self.onLoading?(false) 118 | let error = exportSession?.error 119 | self.showError(message: "Error exporting video: \(String(describing: error))") 120 | } 121 | } 122 | }) 123 | } 124 | 125 | private func getTransform(for videoTrack: AVAssetTrack) -> CGAffineTransform { 126 | let renderSize = CGSize(width: 16 * videoCropView.aspectRatio.width * 18, 127 | height: 16 * videoCropView.aspectRatio.height * 18) 128 | let cropFrame = videoCropView.getImageCropFrame() 129 | let renderScale = renderSize.width / cropFrame.width 130 | let offset = CGPoint(x: -cropFrame.origin.x, y: -cropFrame.origin.y) 131 | let rotation = atan2(videoTrack.preferredTransform.b, videoTrack.preferredTransform.a) 132 | 133 | var rotationOffset = CGPoint(x: 0, y: 0) 134 | 135 | if videoTrack.preferredTransform.b == -1.0 { 136 | rotationOffset.y = videoTrack.naturalSize.width 137 | } else if videoTrack.preferredTransform.c == -1.0 { 138 | rotationOffset.x = videoTrack.naturalSize.height 139 | } else if videoTrack.preferredTransform.a == -1.0 { 140 | rotationOffset.x = videoTrack.naturalSize.width 141 | rotationOffset.y = videoTrack.naturalSize.height 142 | } 143 | 144 | var transform = CGAffineTransform.identity 145 | transform = transform.scaledBy(x: renderScale, y: renderScale) 146 | transform = transform.translatedBy(x: offset.x + rotationOffset.x, y: offset.y + rotationOffset.y) 147 | transform = transform.rotated(by: rotation) 148 | 149 | print("track size \(videoTrack.naturalSize)") 150 | print("preferred Transform = \(videoTrack.preferredTransform)") 151 | print("rotation angle \(rotation)") 152 | print("rotation offset \(rotationOffset)") 153 | print("actual Transform = \(transform)") 154 | return transform 155 | } 156 | } 157 | 158 | extension VideoCropperViewController: ThumbSelectorViewDelegate { 159 | func didChangeThumbPosition(_ imageTime: CMTime) { 160 | videoCropView.player?.seek(to: imageTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) 161 | } 162 | } 163 | 164 | extension UIImage { 165 | func crop(in frame: CGRect) -> UIImage? { 166 | if let croppedImage = self.cgImage?.cropping(to: frame) { 167 | return UIImage(cgImage: croppedImage, scale: scale, orientation: imageOrientation) 168 | } 169 | return nil 170 | } 171 | } 172 | 173 | struct VideoCropperViewControllerRepresentable: UIViewControllerRepresentable { 174 | var asset: AVAsset? 175 | var trimStartTime: CMTime 176 | var trimEndTime: CMTime 177 | var onComplete: ((AVAsset, UIImage) -> Void)? 178 | var onLoading: ((Bool) -> Void)? 179 | 180 | func makeUIViewController(context: Context) -> VideoCropperViewController { 181 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 182 | guard let viewController = storyboard.instantiateViewController(withIdentifier: "videoCropViewController") as? VideoCropperViewController else { 183 | fatalError("Could not find controller") 184 | } 185 | 186 | viewController.onComplete = onComplete 187 | viewController.onLoading = onLoading 188 | viewController.trimStartTime = trimStartTime 189 | viewController.trimEndTime = trimEndTime 190 | viewController.asset = asset 191 | 192 | return viewController 193 | } 194 | 195 | func updateUIViewController(_ uiViewController: VideoCropperViewController, context: Context) { 196 | 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /sources/UI/Editor/VideoTrimmerViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import AVFoundation 4 | import MobileCoreServices 5 | import PryntTrimmerView 6 | 7 | class VideoTrimmerViewController: UIViewController { 8 | 9 | @IBOutlet weak var playerView: UIView! 10 | @IBOutlet weak var trimmerView: TrimmerView! 11 | 12 | var asset: AVAsset? 13 | var onComplete: ((CMTime, CMTime) -> Void)? 14 | 15 | var player: AVPlayer? 16 | var playbackTimeCheckerTimer: Timer? 17 | var trimmerPositionChangedTimer: Timer? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | trimmerView.handleColor = UIColor.white 22 | trimmerView.mainColor = UIColor.darkGray 23 | 24 | trimmerView.minDuration = 5.0 25 | 26 | if let asset = asset { 27 | loadAsset(asset) 28 | } 29 | } 30 | 31 | @IBAction func done(_ sender: Any) { 32 | let tolerance: Double = 0.05 33 | 34 | let diff = CMTimeGetSeconds(trimmerView.endTime!) - CMTimeGetSeconds(trimmerView.startTime!) 35 | if(abs(diff - 5) > tolerance) { 36 | let formattedDiff = String(format: "%.2f", diff) 37 | let alert = UIAlertController(title: "Error", message: "Trim the video to be exactly 5 seconds long.\nCurrent duration: \(formattedDiff)s", preferredStyle: .alert) 38 | let ok = UIAlertAction(title: "OK", style: .default) 39 | 40 | alert.addAction(ok) 41 | present(alert, animated: true, completion: nil) 42 | return 43 | } 44 | 45 | onComplete?(trimmerView.startTime!, trimmerView.endTime!) 46 | } 47 | 48 | func loadAsset(_ asset: AVAsset) { 49 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 50 | self.trimmerView.delegate = self 51 | self.trimmerView.asset = asset 52 | self.addVideoPlayer(with: asset, playerView: self.playerView) 53 | } 54 | } 55 | 56 | private func addVideoPlayer(with asset: AVAsset, playerView: UIView) { 57 | let playerItem = AVPlayerItem(asset: asset) 58 | player = AVPlayer(playerItem: playerItem) 59 | 60 | NotificationCenter.default.addObserver(self, selector: #selector(VideoTrimmerViewController.itemDidFinishPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem) 61 | 62 | let layer: AVPlayerLayer = AVPlayerLayer(player: player) 63 | layer.backgroundColor = UIColor.white.cgColor 64 | layer.frame = CGRect(x: 0, y: 0, width: playerView.frame.width, height: playerView.frame.height) 65 | layer.videoGravity = AVLayerVideoGravity.resizeAspectFill 66 | playerView.layer.sublayers?.forEach({$0.removeFromSuperlayer()}) 67 | playerView.layer.addSublayer(layer) 68 | 69 | player!.play() 70 | startPlaybackTimeChecker() 71 | } 72 | 73 | @objc func itemDidFinishPlaying(_ notification: Notification) { 74 | if let startTime = trimmerView.startTime { 75 | player?.seek(to: startTime) 76 | if (player?.isPlaying != true) { 77 | player?.play() 78 | } 79 | } 80 | } 81 | 82 | func startPlaybackTimeChecker() { 83 | stopPlaybackTimeChecker() 84 | playbackTimeCheckerTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: 85 | #selector(VideoTrimmerViewController.onPlaybackTimeChecker), userInfo: nil, repeats: true) 86 | } 87 | 88 | func stopPlaybackTimeChecker() { 89 | 90 | playbackTimeCheckerTimer?.invalidate() 91 | playbackTimeCheckerTimer = nil 92 | } 93 | 94 | @objc func onPlaybackTimeChecker() { 95 | guard let startTime = trimmerView.startTime, let endTime = trimmerView.endTime, let player = player else { 96 | return 97 | } 98 | 99 | let playBackTime = player.currentTime() 100 | trimmerView.seek(to: playBackTime) 101 | 102 | if playBackTime >= endTime { 103 | player.seek(to: startTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) 104 | trimmerView.seek(to: startTime) 105 | } 106 | } 107 | } 108 | 109 | extension VideoTrimmerViewController: TrimmerViewDelegate { 110 | func positionBarStoppedMoving(_ playerTime: CMTime) { 111 | player?.seek(to: playerTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) 112 | player?.play() 113 | startPlaybackTimeChecker() 114 | } 115 | 116 | func didChangePositionBar(_ playerTime: CMTime) { 117 | stopPlaybackTimeChecker() 118 | player?.pause() 119 | player?.seek(to: playerTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) 120 | let duration = (trimmerView.endTime! - trimmerView.startTime!).seconds 121 | print(duration) 122 | } 123 | } 124 | 125 | struct VideoTrimmerViewControllerRepresentable: UIViewControllerRepresentable { 126 | var asset: AVAsset? 127 | var onComplete: ((CMTime, CMTime) -> Void)? 128 | 129 | func makeUIViewController(context: Context) -> VideoTrimmerViewController { 130 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 131 | guard let viewController = storyboard.instantiateViewController(withIdentifier: "trimmerViewController") as? VideoTrimmerViewController else { 132 | fatalError("Could not find controller") 133 | } 134 | 135 | viewController.asset = asset 136 | viewController.onComplete = onComplete 137 | 138 | return viewController 139 | 140 | } 141 | 142 | func updateUIViewController(_ uiViewController: VideoTrimmerViewController, context: Context) { 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /sources/UI/LiveWallpaperEditorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PhotosUI 3 | import AVKit 4 | import AVFoundation 5 | import UIKit 6 | import System 7 | 8 | struct LiveWallpaperEditorView: View { 9 | 10 | @Environment(\.horizontalSizeClass) var horizontalSizeClass 11 | 12 | @ObservedObject var sheetManager: SheetManager 13 | 14 | @State var liveWallpaper: WallpaperSelection? = nil 15 | @State private var player = AVPlayer() 16 | @State private var videoURL: URL? = nil 17 | 18 | @State private var activeAlert: SheetAlert? 19 | 20 | @State private var videoLoaded: Bool = false 21 | @State private var videoCropped: Bool = false 22 | @State private var videoTrimmed: Bool = false 23 | @State private var isCropping: Bool = false 24 | 25 | @State private var trimStartTime: CMTime = CMTime() 26 | @State private var trimEndTime: CMTime = CMTime() 27 | 28 | @State private var ignoreFileSizeCheck: Bool = false 29 | 30 | struct SheetAlert: Identifiable { 31 | let id = UUID() 32 | let title: String 33 | let message: String 34 | let primaryAction: (() -> Void)? 35 | let secondaryAction: (() -> Void)? 36 | let primaryText: String? 37 | let secondaryText: String? 38 | } 39 | 40 | var body: some View { 41 | VStack { 42 | if !videoLoaded || isCropping || (videoLoaded && videoCropped && videoTrimmed) { 43 | Spacer() 44 | ProgressView() 45 | .progressViewStyle(CircularProgressViewStyle(tint: .purple)) 46 | .scaleEffect(2.0, anchor: .center) 47 | .padding(25) 48 | Spacer() 49 | } 50 | else if !videoTrimmed { 51 | VideoTrimmerViewControllerRepresentable( 52 | asset: player.currentItem?.asset, 53 | onComplete: { (startTime, endTime) in 54 | trimStartTime = startTime 55 | trimEndTime = endTime 56 | videoTrimmed = true 57 | } 58 | ) 59 | } 60 | else if !videoCropped { 61 | VideoCropperViewControllerRepresentable( 62 | asset: player.currentItem?.asset, 63 | trimStartTime: trimStartTime, 64 | trimEndTime: trimEndTime, 65 | onComplete: { (asset, image) in 66 | videoCropped = true 67 | self.setWallpaper(videoAsset: asset, image: image) 68 | }, 69 | onLoading: { (state) in 70 | self.isCropping = state 71 | } 72 | ) 73 | } 74 | } 75 | .onAppear { 76 | if let liveWallpaper = liveWallpaper { 77 | loadVideo(liveWallpaper.userVideo) 78 | } 79 | } 80 | .alert(item: $activeAlert) { alert in 81 | if let primaryAction = alert.primaryAction, let secondaryAction = alert.secondaryAction { 82 | return Alert( 83 | title: Text(alert.title), 84 | message: Text(alert.message), 85 | primaryButton: .default(Text(alert.primaryText ?? "")) { 86 | primaryAction() 87 | }, 88 | secondaryButton: .default(Text(alert.secondaryText ?? "")) { 89 | secondaryAction() 90 | } 91 | ) 92 | } else { 93 | return Alert( 94 | title: Text(alert.title), 95 | message: Text(alert.message), 96 | dismissButton: .default(Text("OK")) { 97 | if let primaryAction = alert.primaryAction { 98 | primaryAction() 99 | } 100 | } 101 | ) 102 | } 103 | } 104 | .sheet(isPresented: $sheetManager.cropGuide, content: { 105 | CropGuideView(sheetManager: sheetManager) 106 | }) 107 | .preferredColorScheme(.light) 108 | } 109 | 110 | private func loadVideo(_ item: PhotosPickerItem) { 111 | Task { 112 | do { 113 | if let url = try await item.loadTransferable(type: VideoTransferable.self) { 114 | let playerItem = AVPlayerItem(url: url.url) 115 | player.replaceCurrentItem(with: playerItem) 116 | 117 | let videoAsset = player.currentItem!.asset 118 | let durationSeconds = CMTimeGetSeconds(videoAsset.duration) 119 | 120 | if durationSeconds < 5.0 { 121 | self.activeAlert = SheetAlert( 122 | title: "Error", 123 | message: "Video file must be at least 5 seconds long.", 124 | primaryAction: { sheetManager.closeAll() }, 125 | secondaryAction: nil, 126 | primaryText: nil, 127 | secondaryText: nil 128 | ) 129 | return 130 | } 131 | 132 | self.activeAlert = SheetAlert( 133 | title: "Blossom", 134 | message: "You can use in-app video editor or Apple's default Photos app to crop and trim the video.", 135 | primaryAction: { 136 | self.videoLoaded = true 137 | }, 138 | secondaryAction: { 139 | if durationSeconds > 5.1 { 140 | self.sheetManager.cropGuide = true 141 | return 142 | } 143 | 144 | let track = videoAsset.tracks(withMediaType: AVMediaType.video).first! 145 | let size = track.naturalSize.applying(track.preferredTransform) 146 | 147 | let videoSize = CGSize(width: fabs(size.width), height: fabs(size.height)) 148 | let screenWidth = UIScreen.main.bounds.size.width * UIScreen.main.scale 149 | let screenHeight = UIScreen.main.bounds.size.height * UIScreen.main.scale 150 | 151 | let videoAspectRatio = videoSize.width / videoSize.height 152 | let screenAspectRatio = screenWidth / screenHeight 153 | 154 | let generator = AVAssetImageGenerator(asset: player.currentItem!.asset) 155 | generator.requestedTimeToleranceBefore = CMTime.zero 156 | generator.requestedTimeToleranceAfter = CMTime.zero 157 | generator.appliesPreferredTrackTransform = true 158 | var image = try? generator.copyCGImage(at: player.currentItem!.asset.duration, actualTime: nil) 159 | 160 | if image == nil { 161 | activeAlert = SheetAlert( 162 | title: "Error", 163 | message: "Failed to extract last frame from your video.", 164 | primaryAction: { self.sheetManager.closeAll() }, 165 | secondaryAction: nil, 166 | primaryText: "OK", 167 | secondaryText: nil 168 | ) 169 | return 170 | } 171 | 172 | let uiImage = UIImage(cgImage: image!, scale: UIScreen.main.scale, orientation: .up) 173 | let scaledImage = uiImage.scalePreservingAspectRatio(targetSize: CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) 174 | 175 | if abs(videoAspectRatio - screenAspectRatio) > 0.01 { 176 | activeAlert = SheetAlert( 177 | title: "Warning", 178 | message: "The selected video file must have \(screenWidth)x\(screenHeight) resolution.\nYou can continue anyway, but the end result might look wrong.\nYou can also check the video guide about fixing this issue using default Photos app.", 179 | primaryAction: { self.setWallpaper(videoAsset: player.currentItem!.asset, image: scaledImage)}, 180 | secondaryAction: { sheetManager.cropGuide = true }, 181 | primaryText: "Continue anyway", 182 | secondaryText: "View guide" 183 | ) 184 | return 185 | } 186 | 187 | self.setWallpaper(videoAsset: player.currentItem!.asset, image: scaledImage) 188 | }, 189 | primaryText: "Use in-app editor", 190 | secondaryText: "Use default Photos app" 191 | ) 192 | } 193 | } catch { 194 | print("Video loading error: \(error)") 195 | } 196 | } 197 | } 198 | 199 | private func setWallpaper(videoAsset: AVAsset, image: UIImage) { 200 | let targetDuration: Double = 5.0 201 | let tolerance: Double = 0.09 202 | 203 | let track = videoAsset.tracks(withMediaType: AVMediaType.video).first! 204 | let size = track.naturalSize.applying(track.preferredTransform) 205 | 206 | let videoSize = CGSize(width: fabs(size.width), height: fabs(size.height)) 207 | let screenWidth = UIScreen.main.bounds.size.width * UIScreen.main.scale 208 | let screenHeight = UIScreen.main.bounds.size.height * UIScreen.main.scale 209 | 210 | let videoAspectRatio = videoSize.width / videoSize.height 211 | let screenAspectRatio = screenWidth / screenHeight 212 | 213 | let fileSize = self.assetFileSize(asset: videoAsset) 214 | if !ignoreFileSizeCheck && fileSize >= 7 { 215 | let formattedFileSize = String(format: "%.2f", fileSize) 216 | activeAlert = SheetAlert( 217 | title: "Warning", 218 | message: "The selected video file size is \(formattedFileSize)MB. The recommended file size is ~7-15MB. You may continue, but if the wallpaper appears blank in result, you should compress the video file.\nContinue?", 219 | primaryAction: { self.ignoreFileSizeCheck = true; self.setWallpaper(videoAsset: videoAsset, image: image) }, 220 | secondaryAction: { sheetManager.closeAll() }, 221 | primaryText: "Continue", 222 | secondaryText: "Cancel" 223 | ) 224 | return 225 | } 226 | 227 | self.patch(videoAsset: videoAsset, image: image) 228 | } 229 | 230 | private func assetFileSize(asset: AVAsset) -> Double { 231 | guard let urlAsset = asset as? AVURLAsset else { 232 | print("Unable to get URL from asset.") 233 | return 0 234 | } 235 | 236 | let fileURL = urlAsset.url 237 | do { 238 | let fileAttributes = try FileManager.default.attributesOfItem(atPath: fileURL.path) 239 | 240 | if let fileSize = fileAttributes[.size] as? UInt64 { 241 | let fileSizeInMB = Double(fileSize) / (1024 * 1024) 242 | return fileSizeInMB 243 | } 244 | } catch { 245 | print("Error retrieving file attributes: \(error)") 246 | } 247 | 248 | return 0 249 | } 250 | 251 | private func patch(videoAsset: AVAsset, image: UIImage) { 252 | let screenWidth = UIScreen.main.bounds.size.width * UIScreen.main.scale 253 | let screenHeight = UIScreen.main.bounds.size.height * UIScreen.main.scale 254 | 255 | let exportSession = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPresetHEVCHighestQuality)! 256 | exportSession.outputURL = URL(filePath: liveWallpaper!.wallpaper.path) 257 | exportSession.outputFileType = .mov 258 | exportSession.shouldOptimizeForNetworkUse = true 259 | 260 | let timeRange = CMTimeRange(start: .zero, duration: CMTime(seconds: 5, preferredTimescale: 600)) 261 | exportSession.timeRange = timeRange 262 | 263 | do { 264 | try FileManager.default.moveItem(atPath: liveWallpaper!.wallpaper.path, toPath: liveWallpaper!.wallpaper.path + ".backup." + UUID().uuidString) 265 | } catch { 266 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 267 | activeAlert = SheetAlert( 268 | title: "Error", 269 | message: "Failed to rename .MOV file: \(error.localizedDescription)", 270 | primaryAction: nil, 271 | secondaryAction: nil, 272 | primaryText: nil, 273 | secondaryText: nil 274 | ) 275 | } 276 | } 277 | 278 | exportSession.exportAsynchronously { 279 | switch exportSession.status { 280 | case .completed: 281 | if let landscapeHeicImage = makeLandscapeImage(image: image)?.heicData(compressionQuality: 1.0) { 282 | if let heicData = image.heicData(compressionQuality: 1.0) { 283 | do { 284 | try FileManager.default.moveItem(atPath: liveWallpaper!.wallpaper.stillImagePath, toPath: liveWallpaper!.wallpaper.stillImagePath + ".backup." + UUID().uuidString) 285 | 286 | try heicData.write(to: URL(filePath: liveWallpaper!.wallpaper.stillImagePath)) 287 | 288 | if FileManager.default.fileExists(atPath: liveWallpaper!.wallpaper.stillImagePathLandscape) { 289 | try FileManager.default.moveItem(atPath: liveWallpaper!.wallpaper.stillImagePathLandscape, toPath: liveWallpaper!.wallpaper.stillImagePathLandscape + ".backup." + UUID().uuidString) 290 | } 291 | 292 | try landscapeHeicImage.write(to: URL(filePath: liveWallpaper!.wallpaper.stillImagePathLandscape)) 293 | 294 | let adjusted = liveWallpaper!.wallpaper.wallpaperRootDirectory + "/input.segmentation/asset.resource/Adjusted.HEIC" 295 | let proxy = liveWallpaper!.wallpaper.wallpaperRootDirectory + "/input.segmentation/asset.resource/proxy.heic"; 296 | 297 | if(FileManager.default.fileExists(atPath: adjusted)) { 298 | try FileManager.default.removeItem(atPath: adjusted); 299 | } 300 | 301 | if(FileManager.default.fileExists(atPath: proxy)) { 302 | try FileManager.default.removeItem(atPath: proxy); 303 | } 304 | 305 | if(!FileManager.default.fileExists(atPath: liveWallpaper!.wallpaper.contentsPath)) { 306 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 307 | activeAlert = SheetAlert( 308 | title: "Error", 309 | message: "Contents.json file does not exist.", 310 | primaryAction: nil, 311 | secondaryAction: nil, 312 | primaryText: nil, 313 | secondaryText: nil 314 | ) 315 | } 316 | return 317 | } 318 | 319 | do { 320 | let url = URL(filePath: liveWallpaper!.wallpaper.contentsPath) 321 | let data = try Data(contentsOf: url) 322 | let decoder = JSONDecoder() 323 | var contents = try decoder.decode(Contents.self, from: data) 324 | 325 | let deviceResolution = contents.properties.portraitLayout.deviceResolution 326 | 327 | contents.layers = [ 328 | Layer( 329 | frame: Frame( 330 | Width: deviceResolution.Width, 331 | Height: deviceResolution.Height, 332 | X: 0, 333 | Y: 0 334 | ), 335 | filename: "portrait-layer_background.HEIC", 336 | zPosition: 5, 337 | identifier: "background" 338 | ), 339 | Layer( 340 | frame: Frame( 341 | Width: deviceResolution.Width, 342 | Height: deviceResolution.Height, 343 | X: 0, 344 | Y: 0 345 | ), 346 | filename: "portrait-layer_settling-video.MOV", 347 | zPosition: 6, 348 | identifier: "settling-video" 349 | ) 350 | ] 351 | 352 | // iPad 353 | if horizontalSizeClass != .compact { 354 | contents.properties.landscapeLayout?.visibleFrame.Width = deviceResolution.Height 355 | contents.properties.landscapeLayout?.visibleFrame.Height = deviceResolution.Width 356 | contents.properties.landscapeLayout?.visibleFrame.X = 0 357 | contents.properties.landscapeLayout?.visibleFrame.Y = 0 358 | 359 | contents.properties.landscapeLayout?.imageSize.Width = deviceResolution.Height 360 | contents.properties.landscapeLayout?.imageSize.Height = deviceResolution.Width 361 | 362 | contents.properties.landscapeLayout?.inactiveFrame.Width = deviceResolution.Height 363 | contents.properties.landscapeLayout?.inactiveFrame.Height = deviceResolution.Width 364 | contents.properties.landscapeLayout?.inactiveFrame.X = 0 365 | contents.properties.landscapeLayout?.inactiveFrame.Y = 0 366 | 367 | contents.properties.landscapeLayout?.parallaxPadding.Width = 0 368 | contents.properties.landscapeLayout?.parallaxPadding.Height = 0 369 | } 370 | 371 | contents.properties.portraitLayout.visibleFrame.Width = deviceResolution.Width 372 | contents.properties.portraitLayout.visibleFrame.Height = deviceResolution.Height 373 | contents.properties.portraitLayout.visibleFrame.X = 0 374 | contents.properties.portraitLayout.visibleFrame.Y = 0 375 | 376 | contents.properties.portraitLayout.imageSize.Width = deviceResolution.Width 377 | contents.properties.portraitLayout.imageSize.Height = deviceResolution.Height 378 | 379 | contents.properties.portraitLayout.inactiveFrame.Width = deviceResolution.Width 380 | contents.properties.portraitLayout.inactiveFrame.Height = deviceResolution.Height 381 | contents.properties.portraitLayout.inactiveFrame.X = 0 382 | contents.properties.portraitLayout.inactiveFrame.Y = 0 383 | 384 | contents.properties.portraitLayout.parallaxPadding.Width = 0 385 | contents.properties.portraitLayout.parallaxPadding.Height = 0 386 | 387 | contents.properties.settlingEffectEnabled = true 388 | contents.properties.depthEnabled = false 389 | contents.properties.parallaxDisabled = false 390 | 391 | let encoder = JSONEncoder() 392 | encoder.outputFormatting = .prettyPrinted 393 | 394 | let encodedData = try encoder.encode(contents) 395 | try encodedData.write(to: url) 396 | 397 | let wallpaper = Wallpaper() 398 | wallpaper.deleteSnapshots(liveWallpaper!.wallpaper.wallpaperVersionDirectory) 399 | 400 | UserDefaults.standard.set(liveWallpaper!.wallpaper.contentsPath, forKey: "LatestWallpaperContentsFilePath") 401 | 402 | sheetManager.closeAll() 403 | wallpaper.restartPosterBoard() 404 | wallpaper.respring() 405 | } catch { 406 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 407 | activeAlert = SheetAlert( 408 | title: "Error", 409 | message: "Failed to patch Contents.json: \(error)", 410 | primaryAction: nil, 411 | secondaryAction: nil, 412 | primaryText: nil, 413 | secondaryText: nil 414 | ) 415 | } 416 | } 417 | } catch { 418 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 419 | activeAlert = SheetAlert( 420 | title: "Error", 421 | message: "Failed to export portrait HEIC image: \(error.localizedDescription)", 422 | primaryAction: nil, 423 | secondaryAction: nil, 424 | primaryText: nil, 425 | secondaryText: nil 426 | ) 427 | } 428 | } catch { 429 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 430 | activeAlert = SheetAlert( 431 | title: "Error", 432 | message: "Failed to export landscape HEIC image: \(error.localizedDescription)", 433 | primaryAction: nil, 434 | secondaryAction: nil, 435 | primaryText: nil, 436 | secondaryText: nil 437 | ) 438 | } 439 | } 440 | } 441 | break 442 | } 443 | default: 444 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 445 | activeAlert = SheetAlert( 446 | title: "Error", 447 | message: "Failed to export video: \(exportSession.error.debugDescription)", 448 | primaryAction: nil, 449 | secondaryAction: nil, 450 | primaryText: nil, 451 | secondaryText: nil 452 | ) 453 | } 454 | } 455 | } 456 | } 457 | 458 | private func makeLandscapeImage(image: UIImage) -> UIImage? { 459 | let originalSize = image.size 460 | 461 | let landscapeSize = CGSize(width: originalSize.height, height: originalSize.width) 462 | 463 | UIGraphicsBeginImageContextWithOptions(landscapeSize, false, image.scale) 464 | defer { 465 | UIGraphicsEndImageContext() 466 | } 467 | 468 | guard let context = UIGraphicsGetCurrentContext() else { 469 | return nil 470 | } 471 | 472 | context.translateBy(x: landscapeSize.width / 2, y: landscapeSize.height / 2) 473 | context.rotate(by: .pi / 2) 474 | 475 | image.draw(in: CGRect(x: -originalSize.width / 2, y: -originalSize.height / 2, 476 | width: originalSize.width, height: originalSize.height)) 477 | 478 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 479 | 480 | return newImage 481 | } 482 | 483 | private func loadVideoURL(_ item: PhotosPickerItem) async throws -> URL? { 484 | guard let video = try await item.loadTransferable(type: VideoTransferable.self) else { 485 | return nil 486 | } 487 | return video.url 488 | } 489 | } 490 | 491 | struct VideoTransferable: Transferable { 492 | let url: URL 493 | 494 | static var transferRepresentation: some TransferRepresentation { 495 | FileRepresentation(contentType: .movie) { exporting in 496 | return SentTransferredFile(exporting.url) 497 | } importing: { received in 498 | let origin = received.file 499 | let filename = origin.lastPathComponent 500 | let copied = URL.documentsDirectory.appendingPathComponent(filename) 501 | let filePath = copied.path() 502 | 503 | if FileManager.default.fileExists(atPath: filePath) { 504 | try FileManager.default.removeItem(atPath: filePath) 505 | } 506 | 507 | try FileManager.default.copyItem(at: origin, to: copied) 508 | return VideoTransferable(url: copied) 509 | } 510 | } 511 | } 512 | 513 | extension UIImage { 514 | func heicData(compressionQuality: CGFloat) -> Data? { 515 | return self.jpegData(compressionQuality: compressionQuality) 516 | } 517 | } 518 | 519 | #Preview { 520 | LiveWallpaperEditorView(sheetManager: SheetManager()) 521 | } 522 | -------------------------------------------------------------------------------- /sources/UI/LiveWallpaperView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AVKit 3 | 4 | struct LiveWallpaperView: View { 5 | let wallpaperPath: String 6 | @State var player: AVPlayer? 7 | 8 | var body: some View { 9 | if player != nil { 10 | AVPlayerControllerRepresented(player: player!) 11 | .onAppear { 12 | player!.isMuted = true 13 | player!.play() 14 | 15 | NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { _ in 16 | if(player?.currentTime() == player?.currentItem?.duration) { 17 | player!.seek(to: .zero) 18 | player!.play() 19 | } 20 | } 21 | } 22 | .onDisappear { 23 | player!.pause() 24 | } 25 | .scaledToFill() 26 | } else { 27 | ProgressView() 28 | .progressViewStyle(CircularProgressViewStyle(tint: .purple)) 29 | .scaleEffect(1.0, anchor: .center) 30 | .onAppear { 31 | if player == nil { 32 | let fileURL = URL(fileURLWithPath: wallpaperPath) 33 | self.player = AVPlayer(url: fileURL) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | struct AVPlayerControllerRepresented : UIViewControllerRepresentable { 42 | var player : AVPlayer 43 | 44 | func makeUIViewController(context: Context) -> AVPlayerViewController { 45 | let controller = AVPlayerViewController() 46 | controller.player = player 47 | controller.showsPlaybackControls = false 48 | return controller 49 | } 50 | 51 | func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sources/UI/SheetManager.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | class SheetManager: ObservableObject { 4 | @Published var wallpaperSelector = false 5 | @Published var selectedWallpaper: WallpaperSelection? = nil 6 | @Published var cropGuide = false 7 | 8 | func closeAll() { 9 | wallpaperSelector = false 10 | selectedWallpaper = nil 11 | cropGuide = false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sources/UI/WallpaperSelectorModal.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PhotosUI 3 | import AVFoundation 4 | 5 | struct WallpaperSelectorModal: View { 6 | 7 | @ObservedObject var sheetManager: SheetManager 8 | 9 | @State private var wallpapers: [LiveWallpaper] = [] 10 | @State private var isLoading = true 11 | 12 | @State private var selectedLiveWallpaper: LiveWallpaper? = nil 13 | @State private var selectedUserVideo: PhotosPickerItem? 14 | 15 | var body: some View { 16 | VStack { 17 | if isLoading { 18 | ProgressView() 19 | .progressViewStyle(CircularProgressViewStyle(tint: .purple)) 20 | .scaleEffect(2.0, anchor: .center) 21 | } else { 22 | if wallpapers.isEmpty { 23 | Image(systemName: "livephoto.slash") 24 | .foregroundColor(.purple) 25 | .scaleEffect(4.0, anchor: .center) 26 | .padding(.bottom, 40) 27 | 28 | Text("No Wallpapers Found") 29 | .fontWeight(.semibold) 30 | .font(.title2) 31 | .padding(.bottom, 5) 32 | 33 | Text("Open your lockscreen and create a new Live Photo wallpaper.") 34 | .padding(.horizontal) 35 | .multilineTextAlignment(.center) 36 | .padding(.bottom, 10) 37 | 38 | Button(action: { 39 | loadWallpapers() 40 | }) { 41 | Text("Refresh") 42 | .foregroundStyle(.purple) 43 | } 44 | } else { 45 | GeometryReader { geometry in 46 | ScrollView { 47 | let columns = [ 48 | GridItem(.flexible(minimum: 0, maximum: geometry.size.width / 2), spacing: 20), 49 | GridItem(.flexible(minimum: 0, maximum: geometry.size.width / 2), spacing: 20) 50 | ] 51 | 52 | LazyVGrid(columns: columns, spacing: 20) { 53 | ForEach(wallpapers, id: \.path) { wallpaper in 54 | if let path = wallpaper.path { 55 | PhotosPicker(selection: $selectedUserVideo, matching: .videos) { 56 | LiveWallpaperView(wallpaperPath: path) 57 | .frame(width: geometry.size.width / 2 - 20, height: geometry.size.width - 20) 58 | .cornerRadius(10) 59 | .clipped() 60 | .containerShape(Rectangle()) 61 | } 62 | .simultaneousGesture(TapGesture() 63 | .onEnded({ 64 | self.selectedLiveWallpaper = wallpaper 65 | }) 66 | ) 67 | } 68 | } 69 | } 70 | .padding() 71 | } 72 | } 73 | } 74 | } 75 | } 76 | .preferredColorScheme(.light) 77 | .sheet(item: $sheetManager.selectedWallpaper) { item in 78 | LiveWallpaperEditorView(sheetManager: sheetManager, liveWallpaper: item) 79 | .onDisappear { 80 | self.selectedUserVideo = nil 81 | } 82 | } 83 | .onChange(of: selectedUserVideo) { newValue in 84 | if let selectedUserVideo = selectedUserVideo { 85 | sheetManager.selectedWallpaper = WallpaperSelection(wallpaper: selectedLiveWallpaper!, userVideo: selectedUserVideo) 86 | } 87 | } 88 | .onAppear { 89 | do { 90 | let audioSession = AVAudioSession.sharedInstance() 91 | try audioSession.setCategory(.ambient, mode: .default, options: [.mixWithOthers]) 92 | try audioSession.setActive(true) 93 | } catch { 94 | print("Failed to set audio session category") 95 | } 96 | 97 | loadWallpapers() 98 | } 99 | } 100 | 101 | private func loadWallpapers() { 102 | self.isLoading = true 103 | 104 | DispatchQueue.global(qos: .userInitiated).async { 105 | let wallpaperManager = Wallpaper() 106 | let loadedWallpapers = wallpaperManager.getLiveWallpapers()! 107 | 108 | DispatchQueue.main.async { 109 | self.wallpapers = loadedWallpapers 110 | self.isLoading = false 111 | } 112 | } 113 | } 114 | } 115 | 116 | #Preview { 117 | WallpaperSelectorModal(sheetManager: SheetManager()) 118 | } 119 | -------------------------------------------------------------------------------- /sources/Wallpaper/Wallpaper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface PrivateApi_LSApplicationWorkspace 4 | - (NSArray*)allInstalledApplications; 5 | @end 6 | 7 | 8 | @interface PrivateApi_LSApplicationProxy 9 | 10 | + (instancetype)applicationProxyForIdentifier:(NSString*)identifier; 11 | @property (nonatomic, readonly) NSString* localizedShortName; 12 | @property (nonatomic, readonly) NSString* localizedName; 13 | @property (nonatomic, readonly) NSString* bundleIdentifier; 14 | @property (nonatomic, readonly) NSArray* appTags; 15 | 16 | @property (nonatomic, readonly) NSString *applicationDSID; 17 | @property (nonatomic, readonly) NSString *applicationIdentifier; 18 | @property (nonatomic, readonly) NSString *applicationType; 19 | @property (nonatomic, readonly) NSNumber *dynamicDiskUsage; 20 | @property (nonatomic, readonly) NSURL *bundleURL; 21 | @property (nonatomic, readonly) NSURL *containerURL; 22 | 23 | @property (nonatomic, readonly) NSArray *groupIdentifiers; 24 | @property (nonatomic, readonly) NSNumber *itemID; 25 | @property (nonatomic, readonly) NSString *itemName; 26 | @property (nonatomic, readonly) NSString *minimumSystemVersion; 27 | @property (nonatomic, readonly) NSArray *requiredDeviceCapabilities; 28 | @property (nonatomic, readonly) NSString *roleIdentifier; 29 | @property (nonatomic, readonly) NSString *sdkVersion; 30 | @property (nonatomic, readonly) NSString *shortVersionString; 31 | @property (nonatomic, readonly) NSString *sourceAppIdentifier; 32 | @property (nonatomic, readonly) NSNumber *staticDiskUsage; 33 | @property (nonatomic, readonly) NSString *teamID; 34 | @property (nonatomic, readonly) NSString *vendorName; 35 | 36 | @end 37 | 38 | @interface LiveWallpaper: NSObject 39 | 40 | @property (nonatomic, strong) NSString* wallpaperRootDirectory; 41 | @property (nonatomic, strong) NSString* wallpaperVersionDirectory; 42 | @property (nonatomic, strong) NSString* path; 43 | @property (nonatomic, strong) NSString* stillImagePath; 44 | @property (nonatomic, strong) NSString* stillImagePathLandscape; 45 | @property (nonatomic, strong) NSString* contentsPath; 46 | 47 | @end 48 | 49 | 50 | @interface Wallpaper: NSObject 51 | - (void) enumerateProcessesUsingBlock:(void (^)(pid_t pid, NSString *executablePath, BOOL *stop))enumerator; 52 | - (void) killall: (NSString*) processName withSoftly: (BOOL) softly; 53 | - (void) restartPoster; 54 | - (void) restartPosterBoard; 55 | - (void) respring; 56 | - (void) deleteSnapshots: (NSString*) wallpaperRootPath; 57 | 58 | - (NSMutableArray*) getDirectories:(NSString*) path; 59 | - (NSString*) getPosterBoardRoot; 60 | - (NSArray*) getLiveWallpapers; 61 | @end 62 | -------------------------------------------------------------------------------- /sources/Wallpaper/Wallpaper.m: -------------------------------------------------------------------------------- 1 | #import "Wallpaper.h" 2 | #import 3 | #import 4 | 5 | @implementation LiveWallpaper 6 | @end 7 | 8 | @implementation Wallpaper 9 | 10 | - (void)deleteSnapshots: (NSString*) wallpaperRootPath { 11 | NSFileManager *fileManager = [NSFileManager defaultManager]; 12 | NSError *error = nil; 13 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:wallpaperRootPath error:&error]; 14 | 15 | if (error) NSLog(@"Error reading directory: %@", error); 16 | 17 | for (NSString *item in contents) { 18 | NSString *fullPath = [wallpaperRootPath stringByAppendingPathComponent:item]; 19 | BOOL isDirectory; 20 | if ([fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory] && !isDirectory) { 21 | if ([item localizedCaseInsensitiveContainsString:@"Snapshot"]) { 22 | [fileManager removeItemAtPath:fullPath error:nil]; 23 | } 24 | } 25 | } 26 | } 27 | 28 | - (NSMutableArray *)getDirectories:(NSString *)path { 29 | NSFileManager *fileManager = [NSFileManager defaultManager]; 30 | NSError *error = nil; 31 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:path error:&error]; 32 | 33 | if (error) { 34 | NSLog(@"Error reading directory: %@", error); 35 | return nil; 36 | } 37 | 38 | NSMutableArray *folders = [NSMutableArray array]; 39 | for (NSString *item in contents) { 40 | NSString *fullPath = [path stringByAppendingPathComponent:item]; 41 | BOOL isDirectory; 42 | if ([fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory] && isDirectory) { 43 | [folders addObject:item]; 44 | } 45 | } 46 | 47 | return folders; 48 | } 49 | 50 | -(NSString*)getPosterBoardRoot { 51 | PrivateApi_LSApplicationWorkspace* _workspace = [NSClassFromString(@"LSApplicationWorkspace") new]; 52 | NSArray* allInstalledApplications = [_workspace allInstalledApplications]; 53 | 54 | for(id proxy in allInstalledApplications) { 55 | PrivateApi_LSApplicationProxy* _applicationProxy = (PrivateApi_LSApplicationProxy*)proxy; 56 | 57 | if([_applicationProxy.bundleIdentifier isEqualToString:@"com.apple.PosterBoard"]) { 58 | NSString* dataStore = [NSString stringWithFormat:@"%@/Library/Application Support/PRBPosterExtensionDataStore", _applicationProxy.containerURL.path]; 59 | 60 | dataStore = [dataStore stringByReplacingOccurrencesOfString:@"/private" withString:@""]; 61 | 62 | NSMutableArray* dirs = [self getDirectories:dataStore]; 63 | 64 | NSString* path = [NSString stringWithFormat:@"%@/%@/Extensions/com.apple.PhotosUIPrivate.PhotosPosterProvider/configurations", dataStore, dirs[0]]; 65 | 66 | return path; 67 | } 68 | } 69 | 70 | return nil; 71 | } 72 | 73 | - (NSArray *)getLiveWallpapers { 74 | NSMutableArray *liveWallpapers = [NSMutableArray array]; 75 | NSString* root = [self getPosterBoardRoot]; 76 | 77 | NSFileManager *fileManager = [NSFileManager defaultManager]; 78 | 79 | for(NSString* configurationPath in [self getDirectories:root]) { 80 | NSString* versions = [NSString stringWithFormat:@"%@/%@/versions", root, configurationPath]; 81 | NSString* version = [self getDirectories:versions][0]; 82 | 83 | NSString* contents = [NSString stringWithFormat:@"%@/%@/contents",versions,version]; 84 | NSString* contentDirectory = [self getDirectories:contents][0]; 85 | 86 | NSString* wallpaperDirectory = [NSString stringWithFormat:@"%@/%@/output.layerStack", contents, contentDirectory]; 87 | 88 | NSString* liveWallpaperPath = [NSString stringWithFormat:@"%@/portrait-layer_settling-video.MOV", wallpaperDirectory]; 89 | 90 | if([fileManager fileExistsAtPath:liveWallpaperPath]) { 91 | LiveWallpaper* wallpaper = [[LiveWallpaper alloc] init]; 92 | 93 | wallpaper.wallpaperRootDirectory = [NSString stringWithFormat:@"%@/%@", contents, contentDirectory]; 94 | wallpaper.path = liveWallpaperPath; 95 | wallpaper.stillImagePath = [NSString stringWithFormat:@"%@/portrait-layer_background.HEIC", wallpaperDirectory]; 96 | wallpaper.stillImagePathLandscape = [NSString stringWithFormat:@"%@/landscape-layer_background.HEIC", wallpaperDirectory]; 97 | wallpaper.contentsPath = [NSString stringWithFormat:@"%@/Contents.json", wallpaperDirectory]; 98 | wallpaper.wallpaperVersionDirectory = [NSString stringWithFormat:@"%@/%@", versions, version]; 99 | 100 | [liveWallpapers addObject:wallpaper]; 101 | } 102 | } 103 | 104 | return [liveWallpapers copy]; 105 | } 106 | 107 | - (void)enumerateProcessesUsingBlock:(void (^)(pid_t, NSString *, BOOL *))enumerator { 108 | static int maxArgumentSize = 0; 109 | if (maxArgumentSize == 0) { 110 | size_t size = sizeof(maxArgumentSize); 111 | if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { 112 | perror("sysctl argument size"); 113 | maxArgumentSize = 4096; // Default 114 | } 115 | } 116 | int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; 117 | struct kinfo_proc *info; 118 | size_t length; 119 | int count; 120 | 121 | if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) 122 | return; 123 | if (!(info = malloc(length))) 124 | return; 125 | if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { 126 | free(info); 127 | return; 128 | } 129 | count = length / sizeof(struct kinfo_proc); 130 | for (int i = 0; i < count; i++) { 131 | @autoreleasepool { 132 | pid_t pid = info[i].kp_proc.p_pid; 133 | if (pid == 0) { 134 | continue; 135 | } 136 | size_t size = maxArgumentSize; 137 | char* buffer = (char *)malloc(length); 138 | if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { 139 | NSString* executablePath = [NSString stringWithCString:(buffer+sizeof(int)) encoding:NSUTF8StringEncoding]; 140 | 141 | BOOL stop = NO; 142 | enumerator(pid, executablePath, &stop); 143 | if(stop) 144 | { 145 | free(buffer); 146 | break; 147 | } 148 | } 149 | free(buffer); 150 | } 151 | } 152 | free(info); 153 | } 154 | 155 | - (void)killall:(NSString *)processName withSoftly:(BOOL)softly { 156 | [self enumerateProcessesUsingBlock:(^(pid_t pid, NSString* executablePath, BOOL* stop) { 157 | if([executablePath.lastPathComponent isEqualToString:processName]) { 158 | if(softly) kill(pid, SIGTERM); 159 | else kill(pid, SIGKILL); 160 | } 161 | })]; 162 | } 163 | 164 | - (void)restartPoster { 165 | [self killall:@"PhotosPosterProvider" withSoftly:FALSE]; 166 | } 167 | 168 | - (void)restartPosterBoard { 169 | [self killall:@"PosterBoard" withSoftly:FALSE]; 170 | } 171 | 172 | - (void)respring { 173 | [self killall:@"SpringBoard" withSoftly:TRUE]; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /sources/Wallpaper/WallpaperContents.swift: -------------------------------------------------------------------------------- 1 | struct Contents: Codable { 2 | var version: Double 3 | var layers: [Layer] 4 | var properties: Properties 5 | } 6 | 7 | struct Layer: Codable { 8 | var frame: Frame 9 | var filename: String 10 | var zPosition: Double 11 | var identifier: String 12 | } 13 | 14 | struct Frame: Codable { 15 | var Width: Double 16 | var Height: Double 17 | var X: Double 18 | var Y: Double 19 | } 20 | 21 | struct Resolution: Codable { 22 | var Width: Double 23 | var Height: Double 24 | } 25 | 26 | struct Properties: Codable { 27 | var portraitLayout: Layout 28 | var landscapeLayout: Layout? 29 | 30 | var settlingEffectEnabled: Bool 31 | var depthEnabled: Bool 32 | var clockAreaLuminance: Double 33 | var parallaxDisabled: Bool 34 | } 35 | 36 | struct Layout: Codable { 37 | var clockIntersection: Double 38 | var deviceResolution: Resolution 39 | var visibleFrame: Frame 40 | var timeFrame: Frame 41 | var clockLayerOrder: String 42 | var inactiveFrame: Frame 43 | var imageSize: Resolution 44 | var parallaxPadding: Resolution 45 | } 46 | -------------------------------------------------------------------------------- /sources/Wallpaper/WallpaperSelection.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PhotosUI 3 | 4 | struct WallpaperSelection: Identifiable { 5 | var wallpaper: LiveWallpaper 6 | var userVideo: PhotosPickerItem 7 | 8 | var id: String { 9 | return wallpaper.path 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "New Project.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/AppIcon.appiconset/New Project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inyourwalls/Blossom/c88ebe00874f9ea60b100f41d1ef879fc78cc18a/supports/Assets.xcassets/AppIcon.appiconset/New Project.png -------------------------------------------------------------------------------- /supports/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "New Project.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/Icon.imageset/New Project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inyourwalls/Blossom/c88ebe00874f9ea60b100f41d1ef879fc78cc18a/supports/Assets.xcassets/Icon.imageset/New Project.png -------------------------------------------------------------------------------- /supports/Assets.xcassets/setup.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : [ 3 | { 4 | "filename" : "setup.MOV", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /supports/Assets.xcassets/setup.dataset/setup.MOV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inyourwalls/Blossom/c88ebe00874f9ea60b100f41d1ef879fc78cc18a/supports/Assets.xcassets/setup.dataset/setup.MOV -------------------------------------------------------------------------------- /supports/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | application-identifier 6 | com.inyourwalls.blossom 7 | com.apple.QuartzCore.displayable-context 8 | 9 | com.apple.QuartzCore.secure-mode 10 | 11 | com.apple.backboard.client 12 | 13 | com.apple.private.security.storage.AppBundles 14 | 15 | com.apple.private.security.storage.AppDataContainers 16 | 17 | com.apple.security.iokit-user-client-class 18 | 19 | AGXDeviceUserClient 20 | IOHDIXControllerUserClient 21 | IOSurfaceRootUserClient 22 | 23 | com.apple.private.allow-background-haptics 24 | 25 | com.apple.private.hid.client.event-dispatch 26 | 27 | com.apple.private.hid.client.event-filter 28 | 29 | com.apple.private.hid.client.event-monitor 30 | 31 | com.apple.private.hid.client.service-protected 32 | 33 | com.apple.private.hid.manager.client 34 | 35 | com.apple.private.kernel.jetsam 36 | 37 | com.apple.private.memorystatus 38 | 39 | com.apple.private.persona-mgmt 40 | 41 | com.apple.private.security.no-sandbox 42 | 43 | com.apple.springboard.CFUserNotification 44 | 45 | com.apple.springboard.accessibility-window-hosting 46 | 47 | com.apple.springboard.appbackgroundstyle 48 | 49 | file-read-data 50 | 51 | platform-application 52 | 53 | user-preference-read 54 | 55 | user-preference-write 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /supports/hudapp-bridging-header.h: -------------------------------------------------------------------------------- 1 | #ifndef hudapp_bridging_header_h 2 | #define hudapp_bridging_header_h 3 | 4 | #import 5 | 6 | #import "HUDHelper.h" 7 | #import "Daemon.h" 8 | #import "Wallpaper.h" 9 | 10 | #endif /* hudapp_bridging_header_h */ 11 | -------------------------------------------------------------------------------- /supports/hudapp-prefix.pch: -------------------------------------------------------------------------------- 1 | #if __has_include() 2 | #import 3 | #endif 4 | 5 | #ifdef __OBJC__ 6 | #if __has_include() 7 | #import 8 | #endif 9 | 10 | #import 11 | #import 12 | #import 13 | #import "rootless.h" 14 | #endif 15 | 16 | #if DEBUG 17 | #define log_debug os_log_debug 18 | #define log_info os_log_info 19 | #define log_error os_log_error 20 | #else 21 | #define log_debug(...) 22 | #define log_info(...) 23 | #define log_error(...) 24 | #endif 25 | 26 | #define FADE_OUT_DURATION 0.25 27 | 28 | #define USER_DEFAULTS_PATH @"/var/mobile/Library/Preferences/com.inyourwalls.blossom.plist" 29 | 30 | // HUD -> APP: Notify APP that the HUD's view is appeared. 31 | #define NOTIFY_LAUNCHED_HUD "com.inyourwalls.blossom.notification.hud.launched" 32 | 33 | // APP -> HUD: Notify HUD to dismiss itself. 34 | #define NOTIFY_DISMISSAL_HUD "com.inyourwalls.blossom.notification.hud.dismissal" 35 | 36 | // APP -> HUD: Notify HUD that the user defaults has been changed by APP. 37 | #define NOTIFY_RELOAD_HUD "com.inyourwalls.blossom.notification.hud.reload" 38 | 39 | // HUD -> APP: Notify APP that the user defaults has been changed by HUD. 40 | #define NOTIFY_RELOAD_APP "com.inyourwalls.blossom.notification.app.reload" 41 | 42 | #ifdef __OBJC__ 43 | #import "hudapp-bridging-header.h" 44 | #endif 45 | -------------------------------------------------------------------------------- /supports/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inyourwalls/Blossom/c88ebe00874f9ea60b100f41d1ef879fc78cc18a/supports/icon.png --------------------------------------------------------------------------------