Skip to content

Commit

Permalink
Merge pull request #260 from hugeBlack/main
Browse files Browse the repository at this point in the history
3.1.0
  • Loading branch information
hugeBlack authored Dec 16, 2024
2 parents aeb7dc0 + 175a9d0 commit c586e79
Show file tree
Hide file tree
Showing 68 changed files with 13,312 additions and 1,006 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
build:
name: Build
runs-on: macos-latest
runs-on: macos-15

steps:
- name: Checkout
Expand Down
7 changes: 5 additions & 2 deletions LCSharedUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
+ (BOOL)launchToGuestAppWithURL:(NSURL *)url;
+ (void)setWebPageUrlForNextLaunch:(NSString*)urlString;
+ (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId;
+ (NSString*)getContainerUsingLCSchemeWithFolderName:(NSString*)folderName;
+ (void)setAppRunningByThisLC:(NSString*)bundleId;
+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo;
+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom;
+ (void)setContainerUsingByThisLC:(NSString*)folderName;
+ (void)moveSharedAppFolderBack;
+ (void)removeAppRunningByLC:(NSString*)LCScheme;
+ (void)removeContainerUsingByLC:(NSString*)LCScheme;
+ (NSBundle*)findBundleWithBundleId:(NSString*)bundleId;
+ (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)dataUUID;
+ (NSString*)findDefaultContainerWithBundleId:(NSString*)bundleId;
@end
144 changes: 89 additions & 55 deletions LCSharedUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url {

NSString* launchBundleId = nil;
NSString* openUrl = nil;
NSString* containerFolderName = nil;
for (NSURLQueryItem* queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"bundle-name"]) {
launchBundleId = queryItem.value;
} else if ([queryItem.name isEqualToString:@"open-url"]){
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:queryItem.value options:0];
openUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
} else if ([queryItem.name isEqualToString:@"container-folder-name"]) {
containerFolderName = queryItem.value;
}
}
if(launchBundleId) {
Expand All @@ -112,6 +115,7 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url {

// Attempt to restart LiveContainer with the selected guest app
[lcUserDefaults setObject:launchBundleId forKey:@"selected"];
[lcUserDefaults setObject:containerFolderName forKey:@"selectedContainer"];
return [self launchToGuestApp];
}

Expand All @@ -133,6 +137,17 @@ + (NSURL*)appLockPath {
return infoPath;
}

+ (NSURL*)containerLockPath {
static dispatch_once_t once;
static NSURL *infoPath;

dispatch_once(&once, ^{
NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]];
infoPath = [appGroupPath URLByAppendingPathComponent:@"LiveContainer/containerLock.plist"];
});
return infoPath;
}

+ (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId {
NSURL* infoPath = [self appLockPath];
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
Expand All @@ -152,6 +167,25 @@ + (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId {
return nil;
}

+ (NSString*)getContainerUsingLCSchemeWithFolderName:(NSString*)folderName {
NSURL* infoPath = [self containerLockPath];
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return nil;
}

for (NSString* key in info) {
if([folderName isEqualToString:info[key]]) {
if([key isEqualToString:lcAppUrlScheme]) {
return nil;
}
return key;
}
}

return nil;
}

// if you pass null then remove this lc from appLock
+ (void)setAppRunningByThisLC:(NSString*)bundleId {
NSURL* infoPath = [self appLockPath];
Expand All @@ -169,76 +203,43 @@ + (void)setAppRunningByThisLC:(NSString*)bundleId {

}

+ (void)removeAppRunningByLC:(NSString*)LCScheme {
NSURL* infoPath = [self appLockPath];
+ (void)setContainerUsingByThisLC:(NSString*)folderName {
NSURL* infoPath = [self containerLockPath];

NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return;
info = [NSMutableDictionary new];
}
if(folderName == nil) {
[info removeObjectForKey:lcAppUrlScheme];
} else {
info[lcAppUrlScheme] = folderName;
}
[info removeObjectForKey:LCScheme];
[info writeToFile:infoPath.path atomically:YES];

}

// move all plists file from fromPath to toPath
+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo {
NSFileManager* fm = [[NSFileManager alloc] init];
NSError* error1;
NSArray<NSString *> * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1];

// remove all plists in toPath first
NSArray *directoryContents = [fm contentsOfDirectoryAtPath:plistLocationTo error:&error1];
for (NSString *item in directoryContents) {
// Check if the item is a plist and does not contain "LiveContainer"
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString *itemPath = [plistLocationTo stringByAppendingPathComponent:item];
// Attempt to delete the file
[fm removeItemAtPath:itemPath error:&error1];
}
+ (void)removeAppRunningByLC:(NSString*)LCScheme {
NSURL* infoPath = [self appLockPath];

[fm createDirectoryAtPath:plistLocationTo withIntermediateDirectories:YES attributes:@{} error:&error1];
// move all plists in fromPath to toPath
for (NSString* item in plists) {
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString* toPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationTo, item];
NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item];

[fm moveItemAtPath:fromPlistPath toPath:toPlistPath error:&error1];
if(error1) {
NSLog(@"[LC] error1 = %@", error1.description);
}

NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return;
}
[info removeObjectForKey:LCScheme];
[info writeToFile:infoPath.path atomically:YES];

}

// to make apple happy and prevent, we have to load all preferences into NSUserDefault so that guest app can read them
+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom {
NSFileManager* fm = [[NSFileManager alloc] init];
NSError* error1;
NSArray<NSString *> * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1];
+ (void)removeContainerUsingByLC:(NSString*)LCScheme {
NSURL* infoPath = [self containerLockPath];

// move all plists in fromPath to toPath
for (NSString* item in plists) {
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item];
// load, the file and sync
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithContentsOfFile:fromPlistPath];
NSUserDefaults* nud = [[NSUserDefaults alloc] initWithSuiteName: [item substringToIndex:[item length]-6]];
for(NSString* key in dict) {
[nud setObject:dict[key] forKey:key];
}

[nud synchronize];

NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return;
}
[info removeObjectForKey:LCScheme];
[info writeToFile:infoPath.path atomically:YES];

}

Expand Down Expand Up @@ -297,4 +298,37 @@ + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId {
return appBundle;
}

+ (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)dataUUID {
NSFileManager* fm = [[NSFileManager alloc] init];
NSError* error1;

NSDictionary* preferences = [lcUserDefaults objectForKey:dataUUID];
if(!preferences) {
return;
}

[fm createDirectoryAtPath:plistLocationTo withIntermediateDirectories:YES attributes:@{} error:&error1];
for(NSString* identifier in preferences) {
NSDictionary* preference = preferences[identifier];
NSString *itemPath = [plistLocationTo stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", identifier]];
if([preference count] == 0) {
// Attempt to delete the file
[fm removeItemAtPath:itemPath error:&error1];
continue;
}
[preference writeToFile:itemPath atomically:YES];
}
[lcUserDefaults removeObjectForKey:dataUUID];
}

+ (NSString*)findDefaultContainerWithBundleId:(NSString*)bundleId {
// find app's default container
NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]];
NSURL* appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"];

NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", appGroupFolder.path, bundleId];
NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath];
return infoDict[@"LCDataUUID"];
}

@end
102 changes: 102 additions & 0 deletions LiveContainerSwiftUI/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import UIKit
import SwiftUI

@objc class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private static var urlStrToOpen: String? = nil
private static var openUrlStrFunc: ((String) async -> Void)?
private static var bundleToLaunch: String? = nil
private static var containerToLaunch: String? = nil
private static var launchAppFunc: ((String, String?) async -> Void)?

public static func setOpenUrlStrFunc(handler: @escaping ((String) async -> Void)){
self.openUrlStrFunc = handler
if let urlStrToOpen = self.urlStrToOpen {
Task { await handler(urlStrToOpen) }
self.urlStrToOpen = nil
} else if let urlStr = UserDefaults.standard.string(forKey: "webPageToOpen") {
UserDefaults.standard.removeObject(forKey: "webPageToOpen")
Task { await handler(urlStr) }
}
}

public static func setLaunchAppFunc(handler: @escaping ((String, String?) async -> Void)){
self.launchAppFunc = handler
if let bundleToLaunch = self.bundleToLaunch {
Task { await handler(bundleToLaunch, containerToLaunch) }
self.bundleToLaunch = nil
}
}

private static func openWebPage(urlStr: String) {
if openUrlStrFunc == nil {
urlStrToOpen = urlStr
} else {
Task { await openUrlStrFunc!(urlStr) }
}
}

private static func launchApp(bundleId: String, container: String?) {
if launchAppFunc == nil {
bundleToLaunch = bundleId
containerToLaunch = container
} else {
Task { await launchAppFunc!(bundleId, container) }
}
}


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.window = window
let contentView = LCTabView()
window.rootViewController = UIHostingController(rootView: contentView)
window.makeKeyAndVisible()
return true
}

func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
if url.host == "open-web-page" {
if let urlComponent = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItem = urlComponent.queryItems?.first {
if queryItem.value?.isEmpty ?? true {
return true
}

if let decodedData = Data(base64Encoded: queryItem.value ?? ""),
let decodedUrl = String(data: decodedData, encoding: .utf8) {
AppDelegate.openWebPage(urlStr: decodedUrl)
}
}
} else if url.host == "livecontainer-launch" {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
var bundleId : String? = nil
var containerName : String? = nil
for queryItem in components.queryItems ?? [] {
if queryItem.name == "bundle-name", let bundleId1 = queryItem.value {
bundleId = bundleId1
} else if queryItem.name == "container-folder-name", let containerName1 = queryItem.value {
containerName = containerName1
}
}
if let bundleId {
AppDelegate.launchApp(bundleId: bundleId, container: containerName)
}
}
}

return false
}

func applicationWillTerminate(_ application: UIApplication) {
// Fix launching app if user opens JIT waiting dialog and kills the app. Won't trigger normally.
UserDefaults.standard.removeObject(forKey: "selected")
UserDefaults.standard.removeObject(forKey: "selectedContainer")

if (UserDefaults.standard.object(forKey: "LCLastLanguages") != nil) {
// recover livecontainer's own language
UserDefaults.standard.set(UserDefaults.standard.object(forKey: "LCLastLanguages"), forKey: "AppleLanguages")
UserDefaults.standard.removeObject(forKey: "LCLastLanguages")
}
}

}
Loading

0 comments on commit c586e79

Please sign in to comment.