diff --git a/LCSharedUtils.h b/LCSharedUtils.h index 437ae1e..74a4ef3 100644 --- a/LCSharedUtils.h +++ b/LCSharedUtils.h @@ -5,5 +5,5 @@ + (NSString *)certificatePassword; + (BOOL)launchToGuestApp; + (BOOL)launchToGuestAppWithURL:(NSURL *)url; - ++ (void)setWebPageUrlForNextLaunch:(NSString*)urlString; @end diff --git a/LCSharedUtils.m b/LCSharedUtils.m index 0697a0f..9a4af1a 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -49,4 +49,8 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url { return NO; } ++ (void)setWebPageUrlForNextLaunch:(NSString*) urlString { + [lcUserDefaults setObject:urlString forKey:@"webPageToOpen"]; +} + @end diff --git a/LiveContainerUI/LCAppDelegate.m b/LiveContainerUI/LCAppDelegate.m index 5cd6fc6..4594e09 100644 --- a/LiveContainerUI/LCAppDelegate.m +++ b/LiveContainerUI/LCAppDelegate.m @@ -2,6 +2,7 @@ #import "LCJITLessSetupViewController.h" #import "LCTabBarController.h" #import "LCUtils.h" +#import @implementation LCAppDelegate @@ -20,6 +21,17 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + // handle page open request from URL scheme + if([url.host isEqualToString:@"open-web-page"]) { + NSURLComponents* urlComponent = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + if(urlComponent.queryItems.count == 0){ + return YES; + } + + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:urlComponent.queryItems[0].value options:0]; + NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + [((LCTabBarController*)_rootViewController) openWebPage:decodedUrl]; + } return [LCUtils launchToGuestAppWithURL:url]; } diff --git a/LiveContainerUI/LCAppInfo.h b/LiveContainerUI/LCAppInfo.h index c143acf..145c775 100644 --- a/LiveContainerUI/LCAppInfo.h +++ b/LiveContainerUI/LCAppInfo.h @@ -5,7 +5,7 @@ NSMutableDictionary* _info; NSString* _bundlePath; } - +@property NSString* relativeBundlePath; - (NSMutableDictionary*)info; - (UIImage*)icon; - (NSString*)displayName; @@ -14,9 +14,10 @@ - (NSString*)version; - (NSString*)dataUUID; - (NSString*)tweakFolder; +- (NSMutableArray*) urlSchemes; - (void)setDataUUID:(NSString *)uuid; - (void)setTweakFolder:(NSString *)tweakFolder; - (instancetype)initWithBundlePath:(NSString*)bundlePath; - (NSDictionary *)generateWebClipConfig; - (void)save; -@end \ No newline at end of file +@end diff --git a/LiveContainerUI/LCAppInfo.m b/LiveContainerUI/LCAppInfo.m index 68e0523..cd6f85b 100644 --- a/LiveContainerUI/LCAppInfo.m +++ b/LiveContainerUI/LCAppInfo.m @@ -5,13 +5,38 @@ @implementation LCAppInfo - (instancetype)initWithBundlePath:(NSString*)bundlePath { self = [super init]; + if(self) { _bundlePath = bundlePath; _info = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]]; + } return self; } +- (NSMutableArray*)urlSchemes { + // find all url schemes + NSMutableArray* urlSchemes = [[NSMutableArray alloc] init]; + int nowSchemeCount = 0; + if (_info[@"CFBundleURLTypes"]) { + NSMutableArray* urlTypes = _info[@"CFBundleURLTypes"]; + + for(int i = 0; i < [urlTypes count]; ++i) { + NSMutableDictionary* nowUrlType = [urlTypes objectAtIndex:i]; + if (!nowUrlType[@"CFBundleURLSchemes"]){ + continue; + } + NSMutableArray *schemes = nowUrlType[@"CFBundleURLSchemes"]; + for(int j = 0; j < [schemes count]; ++j) { + [urlSchemes insertObject:[schemes objectAtIndex:j] atIndex:nowSchemeCount]; + ++nowSchemeCount; + } + } + } + + return urlSchemes; +} + - (NSString*)displayName { if (_info[@"CFBundleDisplayName"]) { return _info[@"CFBundleDisplayName"]; @@ -127,4 +152,4 @@ - (NSDictionary *)generateWebClipConfig { - (void)save { [_info writeToFile:[NSString stringWithFormat:@"%@/Info.plist", _bundlePath] atomically:YES]; } -@end \ No newline at end of file +@end diff --git a/LiveContainerUI/LCAppListViewController.h b/LiveContainerUI/LCAppListViewController.h index 94bcf1b..56822ed 100644 --- a/LiveContainerUI/LCAppListViewController.h +++ b/LiveContainerUI/LCAppListViewController.h @@ -2,4 +2,5 @@ @interface LCAppListViewController : UITableViewController @property(nonatomic) NSString* acError; +- (void) openWebViewByURLString:(NSString*) urlString; @end diff --git a/LiveContainerUI/LCAppListViewController.m b/LiveContainerUI/LCAppListViewController.m index 08caf03..50131ff 100644 --- a/LiveContainerUI/LCAppListViewController.m +++ b/LiveContainerUI/LCAppListViewController.m @@ -11,6 +11,7 @@ #import "UIKitPrivate.h" #import "UIViewController+LCAlert.h" #import "unarchive.h" +#import "LCWebView.h" @implementation NSURL(hack) - (BOOL)safari_isHTTPFamilyURL { @@ -22,8 +23,9 @@ - (BOOL)safari_isHTTPFamilyURL { @interface LCAppListViewController () @property(atomic) NSMutableArray *objects; @property(nonatomic) NSString *bundlePath, *docPath, *tweakPath; - +@property(atomic) NSString* pageUrlToOpen; @property(nonatomic) MBRoundProgressView *progressView; + @end @implementation LCAppListViewController @@ -56,13 +58,33 @@ - (void)loadView { [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(launchButtonTapped)], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped)] ]; + + UIButton* openLinkButton = [UIButton buttonWithType:UIButtonTypeCustom]; + openLinkButton.enabled = !!LCUtils.certificatePassword; + openLinkButton.frame = CGRectMake(0, 0, 40, 40); + [openLinkButton setImage:[UIImage systemImageNamed:@"link"] forState:UIControlStateNormal]; + [openLinkButton addTarget:self action:@selector(openUrlButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + + self.navigationItem.leftBarButtonItems = @[ + [[UIBarButtonItem alloc] initWithCustomView:openLinkButton] + ]; self.progressView = [[MBRoundProgressView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; + if(self.pageUrlToOpen) { + [self openWebViewByURLString:self.pageUrlToOpen]; + self.pageUrlToOpen = nil; + } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.tableView reloadData]; + + NSString* webpageToOpen = [NSUserDefaults.standardUserDefaults objectForKey:@"webPageToOpen"]; + if(webpageToOpen) { + [NSUserDefaults.standardUserDefaults removeObjectForKey:@"webPageToOpen"]; + [self openWebViewByURLString:webpageToOpen]; + } } - (void)addButtonTapped { @@ -302,6 +324,20 @@ - (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^ } else { [self.objects removeObjectAtIndex:indexPath.row]; [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; + [self showConfirmationDialogTitle:@"Delete Data Folder" + message:[NSString stringWithFormat:@"Do you also want to delete data folder of %@? You can keep it for future use.", appInfo.displayName] + destructive:YES + confirmButtonTitle:@"Delete" + handler:^(UIAlertAction * action) { + if (action.style != UIAlertActionStyleCancel) { + NSError *error = nil; + NSString* dataFolderPath = [NSString stringWithFormat:@"%@/Data/Application/%@", self.docPath, [appInfo dataUUID]]; + [NSFileManager.defaultManager removeItemAtPath:dataFolderPath error:&error]; + if (error) { + [self showDialogTitle:@"Error" message:error.localizedDescription]; + } + } + }]; } } handler(YES); @@ -615,4 +651,84 @@ - (UIMenu *)destructiveActionWithTitle:(NSString *)title image:(UIImage *)image return menu; } + +- (void) openUrlButtonTapped { + [self showInputDialogTitle:@"Input URL" message:@"Input URL scheme or URL to a web page" placeholder:@"scheme://" callback:^(NSString *ans) { + dispatch_async(dispatch_get_main_queue(), ^(void){ + [self openWebViewByURLString:ans]; + }); + return (NSString*)nil; + }]; + +} + +- (void) openWebViewByURLString:(NSString*) urlString { + // wait for the loadView to call again. + if(!self.isViewLoaded) { + self.pageUrlToOpen = urlString; + return; + } + + NSURLComponents* url = [[NSURLComponents alloc] initWithString:urlString]; + if(!url) { + [self showDialogTitle:@"Invalid URL" message:@"The given URL is invalid. Check it and try again."]; + } else { + NSMutableArray* apps = [[NSMutableArray alloc] init]; + for(int i = 0; i < [self.objects count]; ++i) { + LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath: [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[i]]]; + appInfo.relativeBundlePath = self.objects[i]; + [apps insertObject:appInfo atIndex:i]; + } + + // use https for http and empty scheme + if([url.scheme length ] == 0) { + url.scheme = @"https"; + } else if (![url.scheme isEqualToString: @"https"] && ![url.scheme isEqualToString: @"http"]){ + [self launchAppByScheme:url apps:apps]; + } + LCWebView *webViewController = [[LCWebView alloc] initWithURL:url.URL apps:apps]; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:webViewController]; + navController.navigationBar.translucent = NO; + + navController.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:navController animated:YES completion:nil]; + } +} + +- (void) launchAppByScheme:(NSURLComponents*)schemeURL apps:(NSMutableArray*)apps { + // find app + NSString* appId = nil; + for(int i = 0; i < [apps count] && !appId; ++i) { + LCAppInfo* nowApp = apps[i]; + NSMutableArray* schemes = [nowApp urlSchemes]; + if(!schemes) continue; + for(int j = 0; j < [schemes count]; ++j) { + NSString* nowScheme = schemes[j]; + if([nowScheme isEqualToString:schemeURL.scheme]) { + appId = [nowApp relativeBundlePath]; + break; + } + } + } + if (!appId) { + [self showDialogTitle:@"Invalid Scheme" message:@"LiveContainer cannot find an app that supports this scheme."]; + return; + } + + + NSString* message = [NSString stringWithFormat:@"You are about to launch %@, continue?", appId]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [NSUserDefaults.standardUserDefaults setObject:appId forKey:@"selected"]; + [NSUserDefaults.standardUserDefaults setObject:schemeURL.string forKey:@"launchAppUrlScheme"]; + if ([LCUtils launchToGuestApp]) return; + }]; + [alert addAction:okAction]; + UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + + }]; + [alert addAction:cancelAction]; + [self presentViewController:alert animated:YES completion:nil]; +} + @end diff --git a/LiveContainerUI/LCTabBarController.h b/LiveContainerUI/LCTabBarController.h index f3c2052..37d7652 100644 --- a/LiveContainerUI/LCTabBarController.h +++ b/LiveContainerUI/LCTabBarController.h @@ -1,4 +1,7 @@ #import +#import "LCAppListViewController.h" @interface LCTabBarController : UITabBarController +@property() LCAppListViewController* appTableVC; +- (void) openWebPage:(NSString*) urlString; @end diff --git a/LiveContainerUI/LCTabBarController.m b/LiveContainerUI/LCTabBarController.m index dee0375..b3e50a7 100644 --- a/LiveContainerUI/LCTabBarController.m +++ b/LiveContainerUI/LCTabBarController.m @@ -1,4 +1,3 @@ -#import "LCAppListViewController.h" #import "LCSettingsListController.h" #import "LCTweakListViewController.h" #import "LCTabBarController.h" @@ -10,6 +9,7 @@ - (void)loadView { LCAppListViewController* appTableVC = [LCAppListViewController new]; appTableVC.title = @"Apps"; + self.appTableVC = appTableVC; LCTweakListViewController* tweakTableVC = [LCTweakListViewController new]; tweakTableVC.title = @"Tweaks"; @@ -28,4 +28,8 @@ - (void)loadView { self.viewControllers = @[appNavigationController, tweakNavigationController, settingsNavigationController]; } +- (void) openWebPage:(NSString*) urlString { + [self.appTableVC openWebViewByURLString:urlString]; +} + @end diff --git a/LiveContainerUI/LCWebView.h b/LiveContainerUI/LCWebView.h new file mode 100644 index 0000000..a977783 --- /dev/null +++ b/LiveContainerUI/LCWebView.h @@ -0,0 +1,11 @@ +#import +#import +#import "LCAppInfo.h" + +@interface LCWebView : UIViewController +- (instancetype)initWithURL:(NSURL *)url apps:(NSMutableArray*)apps; +- (void)askIfLaunchApp:(NSString*)appId url:(NSURL*)launchUrl; +@property (nonatomic) NSURL *url; +@property (nonatomic) NSMutableArray* apps; +@property (strong, nonatomic) WKWebView *webView; +@end diff --git a/LiveContainerUI/LCWebview.m b/LiveContainerUI/LCWebview.m new file mode 100644 index 0000000..c26aa0f --- /dev/null +++ b/LiveContainerUI/LCWebview.m @@ -0,0 +1,150 @@ +// +// LCWebview.m +// jump +// +// Created by s s on 2024/8/18. +// + + +#import "LCWebView.h" +#import "LCUtils.h" + +@interface MySchemeHandler : NSObject +- (instancetype)initWithApp:(NSString*)appId viewController:(LCWebView*)lcController; +@property NSString* appId; +@property LCWebView* lcController; +@end + +@implementation MySchemeHandler + +- (instancetype)initWithApp:(NSString*)appId viewController:(LCWebView*)lcController{ + self = [super init]; + self.appId = appId; + self.lcController = lcController; + return self; +} + +- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { + [self.lcController askIfLaunchApp:self.appId url: urlSchemeTask.request.URL]; +} + +- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { + NSLog(@"stopURLScheme"); +} + +@end + + + +@implementation LCWebView + +- (instancetype)initWithURL:(NSURL *)url apps:(NSMutableArray*)apps { + self = [super init]; // Call the superclass's init method + if (self) { + self.apps = apps; + self.url = url; // Store the URL string + + } + return self; +} + + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (!self.webView.superview) { + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + + + CGRect webViewSize = self.view.bounds; + webViewSize.size.height -= self.navigationController.navigationBar.frame.size.height; + webViewSize.size.height -= [self.view.window.windowScene.statusBarManager statusBarFrame].size.height; + webViewSize.size.height -= 30; + self.webView = [[WKWebView alloc] initWithFrame:webViewSize configuration:config]; + self.webView.navigationDelegate = self; + self.webView.customUserAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1"; + [self.view addSubview:self.webView]; + } + UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.backward"] style:UIBarButtonItemStylePlain target:self action:@selector(goBack)]; + UIBarButtonItem *forwardButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.forward"] style:UIBarButtonItemStylePlain target:self action:@selector(goForward)]; + self.navigationItem.leftBarButtonItems = @[backButton, forwardButton]; + + // Add a refresh button on the right + UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadWebView)]; + UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)]; + self.navigationItem.rightBarButtonItems = @[closeButton, refreshButton]; + + // Load the webpage passed via the initializer + if (self.url) { + NSURLRequest *request = [NSURLRequest requestWithURL:self.url]; + [self.webView loadRequest:request]; + } +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + self.title = webView.title; +} + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + // use private API, get rid of Universal link + decisionHandler((WKNavigationActionPolicy)(WKNavigationActionPolicyAllow + 2)); + NSString* scheme = navigationAction.request.URL.scheme; + if([scheme length] == 0 || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"about"] || [scheme isEqualToString:@"itms-appss"]) { + return; + } + // add a unique urlHandler for each app + LCAppInfo* appToOpen = nil; + for(int i = 0; i < [self.apps count] && !appToOpen; ++i) { + LCAppInfo* nowAppInfo = self.apps[i]; + NSMutableArray* schemes = [nowAppInfo urlSchemes]; + if(!schemes) continue; + for(int j = 0; j < [schemes count]; ++j) { + if([scheme isEqualToString:schemes[j]]) { + appToOpen = nowAppInfo; + break; + } + } + } + if(!appToOpen){ + return; + } + [self askIfLaunchApp:appToOpen.relativeBundlePath url:navigationAction.request.URL]; +} + +- (void)goBack { + if ([self.webView canGoBack]) { + [self.webView goBack]; + } +} + +- (void)goForward { + if ([self.webView canGoForward]) { + [self.webView goForward]; + } +} + +- (void)reloadWebView { + [self.webView reload]; +} + +- (void)close { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)askIfLaunchApp:(NSString*)appId url:(NSURL*)launchUrl { + NSString* message = [NSString stringWithFormat:@"This web page is trying to launch %@, continue?", appId]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [NSUserDefaults.standardUserDefaults setObject:appId forKey:@"selected"]; + [NSUserDefaults.standardUserDefaults setObject:launchUrl.absoluteString forKey:@"launchAppUrlScheme"]; + if ([LCUtils launchToGuestApp]) return; + }]; + [alert addAction:okAction]; + UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + + }]; + [alert addAction:cancelAction]; + [self presentViewController:alert animated:YES completion:nil]; +} + +@end diff --git a/LiveContainerUI/Makefile b/LiveContainerUI/Makefile index 527f565..10556a2 100644 --- a/LiveContainerUI/Makefile +++ b/LiveContainerUI/Makefile @@ -2,7 +2,7 @@ include $(THEOS)/makefiles/common.mk FRAMEWORK_NAME = LiveContainerUI -LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCMachOUtils.m LCAppListViewController.m LCSettingsListController.m LCTabBarController.m LCTweakListViewController.m LCUtils.m MBRoundProgressView.m UIViewController+LCAlert.m unarchive.m LCAppInfo.m +LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCMachOUtils.m LCAppListViewController.m LCSettingsListController.m LCTabBarController.m LCTweakListViewController.m LCUtils.m MBRoundProgressView.m UIViewController+LCAlert.m unarchive.m LCAppInfo.m LCWebView.m LiveContainerUI_CFLAGS = \ -fobjc-arc \ -DCONFIG_TYPE=\"$(CONFIG_TYPE)\" \ diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index 30ac378..56954e5 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -35,6 +35,29 @@ void LCShowSwitchAppConfirmation(NSURL *url) { objc_setAssociatedObject(alert, @"window", window, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +void LCOpenWebPage(NSString* webPageUrlString) { + NSString *message = [NSString stringWithFormat:@"Are you sure you want to open the web page and launch an app? Doing so will terminate this app."]; + UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [NSClassFromString(@"LCSharedUtils") setWebPageUrlForNextLaunch:webPageUrlString]; + [NSClassFromString(@"LCSharedUtils") launchToGuestApp]; + }]; + [alert addAction:okAction]; + UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + window.windowScene = nil; + }]; + [alert addAction:cancelAction]; + window.rootViewController = [UIViewController new]; + window.windowLevel = UIApplication.sharedApplication.windows.lastObject.windowLevel + 1; + window.windowScene = (id)UIApplication.sharedApplication.connectedScenes.anyObject; + [window makeKeyAndVisible]; + [window.rootViewController presentViewController:alert animated:YES completion:nil]; + objc_setAssociatedObject(alert, @"window", window, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + +} + // Handler for AppDelegate @implementation UIApplication(LiveContainerHook) - (void)hook__applicationOpenURLAction:(id)action payload:(NSDictionary *)payload origin:(id)origin { @@ -42,13 +65,38 @@ - (void)hook__applicationOpenURLAction:(id)action payload:(NSDictionary *)payloa if ([url hasPrefix:@"livecontainer://livecontainer-relaunch"]) { // Ignore return; - } else if (![url hasPrefix:@"livecontainer://livecontainer-launch?"]) { - // Not what we're looking for, pass it - [self hook__applicationOpenURLAction:action payload:payload origin:origin]; + } else if ([url hasPrefix:@"livecontainer://open-web-page?"]) { + // launch to UI and open web page + NSURLComponents* lcUrl = [NSURLComponents componentsWithString:url]; + NSString* realUrlEncoded = lcUrl.queryItems[0].value; + if(!realUrlEncoded) return; + // Convert the base64 encoded url into String + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:realUrlEncoded options:0]; + NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + LCOpenWebPage(decodedUrl); return; - } else if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) { - LCShowSwitchAppConfirmation([NSURL URLWithString:url]); + } else if ([url hasPrefix:@"livecontainer://open-url"]) { + // pass url to guest app + NSURLComponents* lcUrl = [NSURLComponents componentsWithString:url]; + NSString* realUrlEncoded = lcUrl.queryItems[0].value; + if(!realUrlEncoded) return; + // Convert the base64 encoded url into String + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:realUrlEncoded options:0]; + NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + NSMutableDictionary* newPayload = [payload mutableCopy]; + newPayload[UIApplicationLaunchOptionsURLKey] = decodedUrl; + [self hook__applicationOpenURLAction:action payload:newPayload origin:origin]; + return; + } else if ([url hasPrefix:@"livecontainer://livecontainer-launch?"]) { + if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) { + LCShowSwitchAppConfirmation([NSURL URLWithString:url]); + } + return; + // Not what we're looking for, pass it + } + [self hook__applicationOpenURLAction:action payload:payload origin:origin]; + return; } @end @@ -72,12 +120,39 @@ - (void)hook_scene:(id)scene didReceiveActions:(NSSet *)actions fromTransitionCo NSString *url = urlAction.url.absoluteString; if ([url hasPrefix:@"livecontainer://livecontainer-relaunch"]) { // Ignore - } else if (![url hasPrefix:@"livecontainer://livecontainer-launch?"]) { - // Not what we're looking for, pass it - [self hook_scene:scene didReceiveActions:actions fromTransitionContext:context]; - return; - } else if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) { - LCShowSwitchAppConfirmation(urlAction.url); + + } else if ([url hasPrefix:@"livecontainer://open-web-page?"]) { + NSURLComponents* lcUrl = [NSURLComponents componentsWithString:url]; + NSString* realUrlEncoded = lcUrl.queryItems[0].value; + if(realUrlEncoded) { + // launch to UI and open web page + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:realUrlEncoded options:0]; + NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + LCOpenWebPage(decodedUrl); + } + + } else if ([url hasPrefix:@"livecontainer://open-url?"]) { + // Open guest app's URL scheme + NSURLComponents* lcUrl = [NSURLComponents componentsWithString:url]; + NSString* realUrlEncoded = lcUrl.queryItems[0].value; + if(realUrlEncoded) { + // Convert the base64 encoded url into String + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:realUrlEncoded options:0]; + NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + + NSMutableSet *newActions = actions.mutableCopy; + [newActions removeObject:urlAction]; + UIOpenURLAction *newUrlAction = [[UIOpenURLAction alloc] initWithURL:[NSURL URLWithString:decodedUrl]]; + [newActions addObject:newUrlAction]; + [self hook_scene:scene didReceiveActions:newActions fromTransitionContext:context]; + return; + } + } else if ([url hasPrefix:@"livecontainer://livecontainer-launch?"]){ + // If it's not current app, then switch + if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) { + LCShowSwitchAppConfirmation(urlAction.url); + } + } NSMutableSet *newActions = actions.mutableCopy; diff --git a/UIKitPrivate.h b/UIKitPrivate.h index dc64b5e..2f99c5e 100644 --- a/UIKitPrivate.h +++ b/UIKitPrivate.h @@ -31,6 +31,7 @@ @interface UIOpenURLAction : NSObject - (NSURL *)url; +- (instancetype)initWithURL:(NSURL *)arg1; @end @interface UITableViewHeaderFooterView(private) diff --git a/main.m b/main.m index 2a4a372..3705399 100644 --- a/main.m +++ b/main.m @@ -310,7 +310,23 @@ int LiveContainerMain(int argc, char *argv[]) { lcUserDefaults = NSUserDefaults.standardUserDefaults; NSString *selectedApp = [lcUserDefaults stringForKey:@"selected"]; if (selectedApp) { + NSString *launchUrl = [lcUserDefaults stringForKey:@"launchAppUrlScheme"]; [lcUserDefaults removeObjectForKey:@"selected"]; + // wait for app to launch so that it can receive the url + if(launchUrl) { + [lcUserDefaults removeObjectForKey:@"launchAppUrlScheme"]; + dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); + dispatch_after(delay, dispatch_get_main_queue(), ^{ + // Base64 encode the data + NSData *data = [launchUrl dataUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedUrl = [data base64EncodedStringWithOptions:0]; + + NSString* finalUrl = [NSString stringWithFormat:@"livecontainer://open-url?url=%@", encodedUrl]; + NSURL* url = [NSURL URLWithString: finalUrl]; + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + }); + } NSSetUncaughtExceptionHandler(&exceptionHandler); setenv("LC_HOME_PATH", getenv("HOME"), 1); NSString *appError = invokeAppMain(selectedApp, argc, argv);