├── .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 |
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 | 
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
--------------------------------------------------------------------------------