Build app list from worse_query path traversal with name resolution
This commit is contained in:
1 parent
176ff30ee5
commit
9bc7b9bcc6
5 files changed
+201
-22
No files matched your search
Binary file not shown.
@@ -15,7 +15,7 @@
|
|||||||
- 源码 / Source:[source/](source/)(iOS 27 Hook、内置文件浏览器集成与构建脚本 / iOS 27 hook, file-browser integration and build scripts)
|
- 源码 / Source:[source/](source/)(iOS 27 Hook、内置文件浏览器集成与构建脚本 / iOS 27 hook, file-browser integration and build scripts)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
98ff0ad0128b107d1f1a21583d9d6e28617f3491ba4ab923a15312fc8e0c14d4 AppsManager_iOS27.ipa
|
f33f847a203fde8cb30e659406e2d72e65eb12aaab0de2fb6665350d68e60275 AppsManager_iOS27.ipa
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能 / Features
|
## 功能 / Features
|
||||||
@@ -67,6 +67,12 @@
|
|||||||
|
|
||||||
## 更新说明 / Updates
|
## 更新说明 / Updates
|
||||||
|
|
||||||
|
- 应用列表改为每次启动通过 `worse_query` 遍历 `Data/Application` 容器生成,不再依赖 LaunchServices CSStore 解析成功才能显示列表。
|
||||||
|
The app list is now built by traversing `Data/Application` containers through `worse_query` on each launch; a readable LaunchServices CSStore is no longer required.
|
||||||
|
- App 名称依次从原生 LaunchServices API、App bundle 的 `Info.plist`、LaunchServices 显示名获取,最后回退到 bundle ID。
|
||||||
|
App names come from the native LaunchServices API, the app bundle's `Info.plist`, then LaunchServices display names, with the bundle ID as the final fallback.
|
||||||
|
- 首次全量路径扫描约 30 秒,会显示进度条;后续启动使用增量扫描缓存。
|
||||||
|
The first full path scan takes about 30 seconds and shows progress; later launches use the incremental scan cache.
|
||||||
- 修复首次启动空白、一次性「开发者提示」遮挡列表的问题。
|
- 修复首次启动空白、一次性「开发者提示」遮挡列表的问题。
|
||||||
Fixed the blank first launch and the one-time developer alert covering the list.
|
Fixed the blank first launch and the one-time developer alert covering the list.
|
||||||
- iOS 27 Hook 已移入 `Frameworks/AppsManagerHook.framework`,标准签名工具重签时会把它作为嵌套代码一并签名。
|
- iOS 27 Hook 已移入 `Frameworks/AppsManagerHook.framework`,标准签名工具重签时会把它作为嵌套代码一并签名。
|
||||||
@@ -94,5 +100,5 @@
|
|||||||
## 校验 / Checksum
|
## 校验 / Checksum
|
||||||
|
|
||||||
```text
|
```text
|
||||||
98ff0ad0128b107d1f1a21583d9d6e28617f3491ba4ab923a15312fc8e0c14d4 AppsManager_iOS27.ipa
|
f33f847a203fde8cb30e659406e2d72e65eb12aaab0de2fb6665350d68e60275 AppsManager_iOS27.ipa
|
||||||
```
|
```
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
98ff0ad0128b107d1f1a21583d9d6e28617f3491ba4ab923a15312fc8e0c14d4 AppsManager_iOS27.ipa
|
f33f847a203fde8cb30e659406e2d72e65eb12aaab0de2fb6665350d68e60275 AppsManager_iOS27.ipa
|
||||||
@@ -33,6 +33,20 @@ source/
|
|||||||
- Detail page version/path/date/app-group support.
|
- Detail page version/path/date/app-group support.
|
||||||
- Replace the `filza://` prompt with the built-in WQ Files browser.
|
- Replace the `filza://` prompt with the built-in WQ Files browser.
|
||||||
- Present media through the native `AVPlayerViewController`.
|
- Present media through the native `AVPlayerViewController`.
|
||||||
|
- Build the app list by traversing `/private/var/mobile/Containers/Data/Application` through `worse_query` on every launch. LaunchServices is optional and only used for names and bundle paths.
|
||||||
|
- Resolve app names from the native LaunchServices API, the bundle `Info.plist`, LaunchServices display names, and finally a prettified bundle ID.
|
||||||
|
- Rebuild the list after preparation if the first load ran before the container registry was ready.
|
||||||
|
|
||||||
|
## 应用列表 / App list
|
||||||
|
|
||||||
|
The list no longer requires a readable LaunchServices CSStore:
|
||||||
|
|
||||||
|
1. Enumerate app data containers through `wq_enumerate` (MCM / LaunchServices API / full inode scan fallback).
|
||||||
|
2. Resolve each container's bundle ID from the native API, LaunchServices map, container xattr, metadata plist, or bundle ID resolver.
|
||||||
|
3. Resolve the display name from the native API, bundle `Info.plist`, LaunchServices display names, or the bundle ID.
|
||||||
|
4. If the screen loaded before preparation finished, reload it once the registry is ready.
|
||||||
|
|
||||||
|
The first full scan on a device where the LaunchServices API and container manager return nothing can take around 30 seconds; the launch progress sheet is shown. Later launches reuse the incremental scan cache.
|
||||||
|
|
||||||
## 构建要求 / Build requirements
|
## 构建要求 / Build requirements
|
||||||
|
|
||||||
|
|||||||
+178
-19
@@ -355,6 +355,14 @@ static NSString *WBLocalized(NSString *key)
|
|||||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary<NSString *, NSURL *> *> *groupCache;
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary<NSString *, NSURL *> *> *groupCache;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@protocol WBLProxyInfo <NSObject>
|
||||||
|
- (NSString *)bundleIdentifier;
|
||||||
|
- (NSString *)localizedName;
|
||||||
|
- (NSURL *)bundleURL;
|
||||||
|
- (NSURL *)dataContainerURL;
|
||||||
|
- (NSString *)shortVersionString;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation WBRegistry
|
@implementation WBRegistry
|
||||||
|
|
||||||
+ (instancetype)shared
|
+ (instancetype)shared
|
||||||
@@ -610,6 +618,58 @@ static NSString *WBFindStoreWithProgress(void (^progress)(double, NSString *))
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NSArray *(*gOrigAllInstalled)(id, SEL);
|
||||||
|
|
||||||
|
static NSDictionary *WBReadInfoPlist(NSString *bundlePath)
|
||||||
|
{
|
||||||
|
if (!bundlePath.length) return nil;
|
||||||
|
NSString *path = [bundlePath stringByAppendingPathComponent:@"Info.plist"];
|
||||||
|
NSData *data = wq_read(path, 2 * 1024 * 1024, NULL);
|
||||||
|
if (!data) {
|
||||||
|
wq_grant(bundlePath);
|
||||||
|
data = wq_read(path, 2 * 1024 * 1024, NULL);
|
||||||
|
}
|
||||||
|
if (!data) return nil;
|
||||||
|
id plist = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:nil];
|
||||||
|
return [plist isKindOfClass:NSDictionary.class] ? plist : nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *WBInfoString(NSDictionary *info, NSArray<NSString *> *keys)
|
||||||
|
{
|
||||||
|
for (NSString *key in keys) {
|
||||||
|
id value = info[key];
|
||||||
|
if ([value isKindOfClass:NSString.class] && [value length]) return value;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *WBBundleIDFromContainerMetadata(NSString *container)
|
||||||
|
{
|
||||||
|
NSString *path = [container stringByAppendingPathComponent:@".com.apple.mobile_container_manager.metadata.plist"];
|
||||||
|
wq_grant(container);
|
||||||
|
wq_grant(path);
|
||||||
|
NSData *data = wq_read(path, 256 * 1024, NULL);
|
||||||
|
if (!data) return nil;
|
||||||
|
id plist = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:nil];
|
||||||
|
id identifier = [plist isKindOfClass:NSDictionary.class] ? plist[@"MCMMetadataIdentifier"] : nil;
|
||||||
|
return [identifier isKindOfClass:NSString.class] ? identifier : nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSArray *WBOriginalInstalledApplications(void)
|
||||||
|
{
|
||||||
|
Class workspaceClass = NSClassFromString(@"LSApplicationWorkspace");
|
||||||
|
if (!workspaceClass || !gOrigAllInstalled) return nil;
|
||||||
|
id workspace = [workspaceClass respondsToSelector:@selector(defaultWorkspace)]
|
||||||
|
? [workspaceClass performSelector:@selector(defaultWorkspace)] : nil;
|
||||||
|
if (!workspace) return nil;
|
||||||
|
@try {
|
||||||
|
return gOrigAllInstalled(workspace, NSSelectorFromString(@"allInstalledApplications"));
|
||||||
|
} @catch (__unused NSException *exception) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)prepareWithProgress:(void (^)(double, NSString *))progress
|
- (void)prepareWithProgress:(void (^)(double, NSString *))progress
|
||||||
completion:(void (^)(BOOL, NSString *))completion
|
completion:(void (^)(BOOL, NSString *))completion
|
||||||
{
|
{
|
||||||
@@ -620,19 +680,77 @@ static NSString *WBFindStoreWithProgress(void (^progress)(double, NSString *))
|
|||||||
if (completion) completion(NO, WBLocalized(@"no_group"));
|
if (completion) completion(NO, WBLocalized(@"no_group"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LaunchServices is optional enrichment: names, bundle paths, and a
|
||||||
|
// container->bundle-id map. The list itself comes from path traversal
|
||||||
|
// below, so a missing or unreadable store no longer produces blank UI.
|
||||||
if (progress) progress(0.08, WBLocalized(@"locating_ls"));
|
if (progress) progress(0.08, WBLocalized(@"locating_ls"));
|
||||||
NSString *store = WBFindStoreWithProgress(progress);
|
NSString *store = WBFindStoreWithProgress(progress);
|
||||||
if (!store.length) {
|
NSMutableDictionary<NSString *, NSString *> *lsContainerToID = [NSMutableDictionary new];
|
||||||
if (completion) completion(NO, WBLocalized(@"no_ls"));
|
if (store.length) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (progress) progress(0.40, WBLocalized(@"parsing_apps"));
|
|
||||||
[NSUserDefaults.standardUserDefaults setObject:store forKey:@"WQLaunchServicesStore"];
|
[NSUserDefaults.standardUserDefaults setObject:store forKey:@"WQLaunchServicesStore"];
|
||||||
[NSUserDefaults.standardUserDefaults synchronize];
|
[NSUserDefaults.standardUserDefaults synchronize];
|
||||||
NSArray<NSString *> *containers = wq_ls_container_paths(store);
|
|
||||||
wq_display_names_reset_miss();
|
wq_display_names_reset_miss();
|
||||||
wq_load_display_names();
|
wq_load_display_names();
|
||||||
if (!containers.count) {
|
for (NSString *path in wq_ls_container_paths(store)) {
|
||||||
|
NSString *bundleID = wq_bundle_id_for_container(path);
|
||||||
|
if (bundleID.length) lsContainerToID[path] = bundleID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The native LaunchServices API is independent of the CSStore parser
|
||||||
|
// and still provides display names and bundle paths when the store is
|
||||||
|
// missing or unreadable.
|
||||||
|
NSMutableDictionary<NSString *, NSString *> *apiName = [NSMutableDictionary new];
|
||||||
|
NSMutableDictionary<NSString *, NSString *> *apiBundle = [NSMutableDictionary new];
|
||||||
|
NSMutableDictionary<NSString *, NSString *> *apiVersion = [NSMutableDictionary new];
|
||||||
|
NSMutableDictionary<NSString *, NSString *> *apiContainerToID = [NSMutableDictionary new];
|
||||||
|
for (id<WBLProxyInfo> proxy in WBOriginalInstalledApplications()) {
|
||||||
|
NSString *bundleID = [proxy bundleIdentifier];
|
||||||
|
if (!bundleID.length) continue;
|
||||||
|
NSString *name = [proxy localizedName];
|
||||||
|
if (name.length) apiName[bundleID] = name;
|
||||||
|
NSURL *bundleURL = [proxy bundleURL];
|
||||||
|
if (bundleURL.path.length) apiBundle[bundleID] = bundleURL.path;
|
||||||
|
NSString *version = [proxy shortVersionString];
|
||||||
|
if (version.length && ![version isEqualToString:@"null"]) apiVersion[bundleID] = version;
|
||||||
|
NSURL *dataURL = [proxy dataContainerURL];
|
||||||
|
if (dataURL.path.length) apiContainerToID[dataURL.path] = bundleID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerate app data containers directly through worse_query on every
|
||||||
|
// launch. This does not depend on the LaunchServices database being
|
||||||
|
// readable, which is the failure mode on affected devices.
|
||||||
|
if (progress) progress(0.25, WBLocalized(@"scanning_containers"));
|
||||||
|
NSString *root = @"/private/var/mobile/Containers/Data/Application";
|
||||||
|
wq_set_full_scan_ui(WBScanConfirmC, WBScanProgressC);
|
||||||
|
gWBScanProgressBlock = ^(int64_t done, int64_t total, BOOL __unused finished) {
|
||||||
|
if (progress && total > 0) {
|
||||||
|
progress(0.25 + 0.35 * ((double)done / (double)total), WBLocalized(@"scanning_containers_short"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NSArray<NSString *> *enumerated = wq_enumerate(root, 0, nil, ^(int64_t done, int64_t total, NSUInteger __unused count) {
|
||||||
|
if (progress && total > 0) {
|
||||||
|
progress(0.25 + 0.35 * ((double)done / (double)total), WBLocalized(@"scanning_containers_short"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gWBScanProgressBlock = nil;
|
||||||
|
NSLog(@"[WB] app containers: %lu enumerated, %lu from LaunchServices API",
|
||||||
|
(unsigned long)enumerated.count, (unsigned long)apiContainerToID.count);
|
||||||
|
|
||||||
|
NSMutableOrderedSet<NSString *> *containerSet = [NSMutableOrderedSet orderedSet];
|
||||||
|
for (NSString *path in enumerated) {
|
||||||
|
if ([path.stringByDeletingLastPathComponent isEqualToString:root]) [containerSet addObject:path];
|
||||||
|
}
|
||||||
|
for (NSString *path in apiContainerToID.allKeys) {
|
||||||
|
if ([path.stringByDeletingLastPathComponent isEqualToString:root]) [containerSet addObject:path];
|
||||||
|
}
|
||||||
|
for (NSString *path in lsContainerToID.allKeys) {
|
||||||
|
if ([path.stringByDeletingLastPathComponent isEqualToString:root]) [containerSet addObject:path];
|
||||||
|
}
|
||||||
|
NSLog(@"[WB] containers: %lu enumerated, %lu API, %lu LaunchServices maps",
|
||||||
|
(unsigned long)enumerated.count, (unsigned long)apiContainerToID.count, (unsigned long)lsContainerToID.count);
|
||||||
|
if (!containerSet.count) {
|
||||||
if (completion) completion(NO, WBLocalized(@"no_containers"));
|
if (completion) completion(NO, WBLocalized(@"no_containers"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -641,30 +759,57 @@ static NSString *WBFindStoreWithProgress(void (^progress)(double, NSString *))
|
|||||||
NSMutableDictionary<NSString *, WBAppInfo *> *byID = [NSMutableDictionary new];
|
NSMutableDictionary<NSString *, WBAppInfo *> *byID = [NSMutableDictionary new];
|
||||||
NSMutableArray<WBAppInfo *> *apps = [NSMutableArray new];
|
NSMutableArray<WBAppInfo *> *apps = [NSMutableArray new];
|
||||||
NSMutableArray<WBAppProxy *> *proxies = [NSMutableArray new];
|
NSMutableArray<WBAppProxy *> *proxies = [NSMutableArray new];
|
||||||
for (NSString *container in containers) {
|
NSMutableDictionary<NSString *, NSDictionary *> *infoCache = [NSMutableDictionary new];
|
||||||
if (![container containsString:@"/Data/Application/"]) continue;
|
NSUInteger missingLabels = 0;
|
||||||
NSString *bundleID = wq_bundle_id_for_container(container);
|
for (NSString *container in containerSet) {
|
||||||
if (!bundleID.length || byID[bundleID]) continue;
|
NSString *bundleID = apiContainerToID[container];
|
||||||
NSString *bundlePath = wq_bundle_path_for_id(bundleID);
|
if (!bundleID.length) bundleID = lsContainerToID[container];
|
||||||
if (!bundlePath.length || ![bundlePath.pathExtension isEqualToString:@"app"]) continue;
|
if (!bundleID.length) bundleID = wq_container_label(container);
|
||||||
NSString *parent = bundlePath.stringByDeletingLastPathComponent;
|
if (!bundleID.length) bundleID = WBBundleIDFromContainerMetadata(container);
|
||||||
if (![parent.stringByDeletingLastPathComponent isEqualToString:@"/private/var/containers/Bundle/Application"]) continue;
|
if (!bundleID.length) bundleID = wq_bundle_id_for_container(container);
|
||||||
|
if (!bundleID.length || byID[bundleID]) {
|
||||||
|
if (!bundleID.length) missingLabels++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *bundlePath = apiBundle[bundleID];
|
||||||
|
if (!bundlePath.length) bundlePath = wq_bundle_path_for_id(bundleID);
|
||||||
|
NSDictionary *info = nil;
|
||||||
|
if (bundlePath.length) {
|
||||||
|
info = infoCache[bundlePath];
|
||||||
|
if (!info) {
|
||||||
|
info = WBReadInfoPlist(bundlePath);
|
||||||
|
if (info) infoCache[bundlePath] = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSString *name = apiName[bundleID];
|
||||||
|
if (!name.length) name = WBInfoString(info, @[@"CFBundleDisplayName", @"CFBundleName", @"CFBundleExecutable"]);
|
||||||
|
if (!name.length) name = wq_display_name(bundleID);
|
||||||
|
if (!name.length) name = WBPrettifyBundleID(bundleID);
|
||||||
|
|
||||||
WBAppInfo *app = [WBAppInfo new];
|
WBAppInfo *app = [WBAppInfo new];
|
||||||
app.bundleID = bundleID;
|
app.bundleID = bundleID;
|
||||||
app.containerPath = container;
|
app.containerPath = container;
|
||||||
app.bundlePath = bundlePath;
|
app.bundlePath = bundlePath;
|
||||||
|
app.version = apiVersion[bundleID] ?: WBInfoString(info, @[@"CFBundleShortVersionString", @"CFBundleVersion"]);
|
||||||
app.isSystem = [bundleID hasPrefix:@"com.apple."];
|
app.isSystem = [bundleID hasPrefix:@"com.apple."];
|
||||||
NSString *name = wq_display_name(bundleID);
|
app.name = name;
|
||||||
app.name = name.length ? name : WBPrettifyBundleID(bundleID);
|
|
||||||
byID[bundleID] = app;
|
byID[bundleID] = app;
|
||||||
[apps addObject:app];
|
[apps addObject:app];
|
||||||
[proxies addObject:[[WBAppProxy alloc] initWithInfo:app]];
|
[proxies addObject:[[WBAppProxy alloc] initWithInfo:app]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!apps.count) {
|
||||||
|
NSLog(@"[WB] built 0 apps from %lu containers; %lu without bundle id",
|
||||||
|
(unsigned long)containerSet.count, (unsigned long)missingLabels);
|
||||||
|
if (completion) completion(NO, WBLocalized(@"no_containers"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSLog(@"[WB] built %lu apps from %lu containers; %lu without bundle id",
|
||||||
|
(unsigned long)apps.count, (unsigned long)containerSet.count, (unsigned long)missingLabels);
|
||||||
[apps sortUsingComparator:^NSComparisonResult(WBAppInfo *a, WBAppInfo *b) {
|
[apps sortUsingComparator:^NSComparisonResult(WBAppInfo *a, WBAppInfo *b) {
|
||||||
return [a.name localizedStandardCompare:b.name];
|
return [a.name localizedStandardCompare:b.name];
|
||||||
}];
|
}];
|
||||||
// Rebuild proxies in sorted order.
|
|
||||||
[proxies removeAllObjects];
|
[proxies removeAllObjects];
|
||||||
for (WBAppInfo *app in apps) [proxies addObject:[[WBAppProxy alloc] initWithInfo:app]];
|
for (WBAppInfo *app in apps) [proxies addObject:[[WBAppProxy alloc] initWithInfo:app]];
|
||||||
|
|
||||||
@@ -773,7 +918,6 @@ static NSString *WBFindStoreWithProgress(void (^progress)(double, NSString *))
|
|||||||
|
|
||||||
#pragma mark - Hooks
|
#pragma mark - Hooks
|
||||||
|
|
||||||
static NSArray *(*gOrigAllInstalled)(id, SEL);
|
|
||||||
static NSArray *(*gOrigAllApplications)(id, SEL);
|
static NSArray *(*gOrigAllApplications)(id, SEL);
|
||||||
static id (*gOrigProxyForIdentifier)(id, SEL, NSString *);
|
static id (*gOrigProxyForIdentifier)(id, SEL, NSString *);
|
||||||
static NSString *(*gOrigDataPath)(id, SEL);
|
static NSString *(*gOrigDataPath)(id, SEL);
|
||||||
@@ -822,6 +966,7 @@ static BOOL WBWaitForRegistry(void)
|
|||||||
|
|
||||||
static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
||||||
{
|
{
|
||||||
|
if (gPreparing) return gOrigAllInstalled ? gOrigAllInstalled(self, _cmd) : @[];
|
||||||
if (!WBRegistry.shared.ready && !NSThread.isMainThread) WBWaitForRegistry();
|
if (!WBRegistry.shared.ready && !NSThread.isMainThread) WBWaitForRegistry();
|
||||||
NSArray *proxies = WBRegistry.shared.proxies;
|
NSArray *proxies = WBRegistry.shared.proxies;
|
||||||
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
||||||
@@ -830,6 +975,7 @@ static NSArray *WBHookAllInstalled(id self, SEL _cmd)
|
|||||||
|
|
||||||
static NSArray *WBHookAllApplications(id self, SEL _cmd)
|
static NSArray *WBHookAllApplications(id self, SEL _cmd)
|
||||||
{
|
{
|
||||||
|
if (gPreparing) return gOrigAllApplications ? gOrigAllApplications(self, _cmd) : @[];
|
||||||
if (!WBRegistry.shared.ready && !NSThread.isMainThread) WBWaitForRegistry();
|
if (!WBRegistry.shared.ready && !NSThread.isMainThread) WBWaitForRegistry();
|
||||||
NSArray *proxies = WBRegistry.shared.proxies;
|
NSArray *proxies = WBRegistry.shared.proxies;
|
||||||
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
if (WBRegistry.shared.ready && proxies.count) return proxies;
|
||||||
@@ -1711,6 +1857,7 @@ static void WBStartPreparation(void)
|
|||||||
// Last-resort fallback: start even without a presenter.
|
// Last-resort fallback: start even without a presenter.
|
||||||
gPreparing = YES;
|
gPreparing = YES;
|
||||||
[WBRegistry.shared prepareWithProgress:nil completion:^(BOOL ok, NSString *message) {
|
[WBRegistry.shared prepareWithProgress:nil completion:^(BOOL ok, NSString *message) {
|
||||||
|
gPreparing = NO;
|
||||||
gPrepared = YES;
|
gPrepared = YES;
|
||||||
[gReadyCondition lock];
|
[gReadyCondition lock];
|
||||||
[gReadyCondition broadcast];
|
[gReadyCondition broadcast];
|
||||||
@@ -1732,6 +1879,7 @@ static void WBStartPreparation(void)
|
|||||||
[WBRegistry.shared prepareWithProgress:^(double fraction, NSString *status) {
|
[WBRegistry.shared prepareWithProgress:^(double fraction, NSString *status) {
|
||||||
[gProgressController updateFraction:fraction status:status];
|
[gProgressController updateFraction:fraction status:status];
|
||||||
} completion:^(BOOL ok, NSString *message) {
|
} completion:^(BOOL ok, NSString *message) {
|
||||||
|
gPreparing = NO;
|
||||||
gPrepared = YES;
|
gPrepared = YES;
|
||||||
[gReadyCondition lock];
|
[gReadyCondition lock];
|
||||||
[gReadyCondition broadcast];
|
[gReadyCondition broadcast];
|
||||||
@@ -1741,6 +1889,17 @@ static void WBStartPreparation(void)
|
|||||||
[gProgressController dismissViewControllerAnimated:YES completion:nil];
|
[gProgressController dismissViewControllerAnimated:YES completion:nil];
|
||||||
gProgressController = nil;
|
gProgressController = nil;
|
||||||
}
|
}
|
||||||
|
// The first load may have started before preparation finished and
|
||||||
|
// received an empty native list. Rebuild it now that the registry
|
||||||
|
// is ready so the path-enumerated apps actually appear.
|
||||||
|
id appsVC = gAppsTableViewController;
|
||||||
|
if (appsVC) {
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 200 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
|
||||||
|
if ([appsVC respondsToSelector:@selector(reload)]) {
|
||||||
|
[(id<WBAppsTableRefresh>)appsVC reload];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!ok && message.length) {
|
if (!ok && message.length) {
|
||||||
NSString *detail = message;
|
NSString *detail = message;
|
||||||
if ([message containsString:@"App Group"]) {
|
if ([message containsString:@"App Group"]) {
|
||||||
|
|||||||
Reference in new issue
Block a user