diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index a498a5b1..ab820c71 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -46,6 +46,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm ) diff --git a/client/ios/app/Info.plist.in b/client/ios/app/Info.plist.in index 45b08cc9..6165daf3 100644 --- a/client/ios/app/Info.plist.in +++ b/client/ios/app/Info.plist.in @@ -32,17 +32,41 @@ UILaunchStoryboardName AmneziaVPNLaunchScreen + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + QIOSWindowSceneDelegate + + + + UIRequiredDeviceCapabilities UIRequiresFullScreen - + UISupportedInterfaceOrientations UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad - + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIUserInterfaceStyle Light com.wireguard.ios.app_group_id diff --git a/client/platforms/ios/AmneziaSceneDelegateHooks.mm b/client/platforms/ios/AmneziaSceneDelegateHooks.mm new file mode 100644 index 00000000..60cbbe0f --- /dev/null +++ b/client/platforms/ios/AmneziaSceneDelegateHooks.mm @@ -0,0 +1,82 @@ +#import +#import +#include + +#include +#include +#include + +#include "ios_controller.h" + +using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet *); + +static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr; + +static void amnezia_handleURL(NSURL *url) +{ + if (!url || !url.isFileURL) { + return; + } + + QString filePath(url.path.UTF8String); + if (filePath.isEmpty()) { + return; + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (filePath.contains("backup")) { + IosController::Instance()->importBackupFromOutside(filePath); + return; + } + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + return; + } + + const QByteArray data = file.readAll(); + IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data)); + }); +} + +static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet *contexts) +{ + if (g_originalSceneOpenURLContexts) { + g_originalSceneOpenURLContexts(self, _cmd, scene, contexts); + } + + if (!contexts || contexts.count == 0) { + return; + } + + if (@available(iOS 13.0, *)) { + for (UIOpenURLContext *context in contexts) { + amnezia_handleURL(context.URL); + } + } +} + +@interface AmneziaSceneDelegateHooks : NSObject +@end + +@implementation AmneziaSceneDelegateHooks + ++ (void)load +{ + Class cls = objc_getClass("QIOSWindowSceneDelegate"); + if (!cls) { + return; + } + + SEL selector = @selector(scene:openURLContexts:); + Method method = class_getInstanceMethod(cls, selector); + if (method) { + g_originalSceneOpenURLContexts = reinterpret_cast(method_getImplementation(method)); + method_setImplementation(method, reinterpret_cast(amnezia_scene_openURLContexts)); + } else { + const char *types = "v@:@@"; + class_addMethod(cls, selector, reinterpret_cast(amnezia_scene_openURLContexts), types); + } +} + +@end diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index b57f8d1d..64da50ea 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -29,12 +29,46 @@ const char* MessageKey::SplitTunnelSites = "SplitTunnelSites"; #if !MACOS_NE static UIViewController* getViewController() { - NSArray *windows = [[UIApplication sharedApplication]windows]; - for (UIWindow *window in windows) { - if (window.isKeyWindow) { + UIApplication *application = [UIApplication sharedApplication]; + + if (@available(iOS 13.0, *)) { + for (UIScene *scene in application.connectedScenes) { + if (scene.activationState != UISceneActivationStateForegroundActive) { + continue; + } + + if (![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + + UIWindowScene *windowScene = (UIWindowScene *)scene; + + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow && window.rootViewController) { + return window.rootViewController; + } + } + + for (UIWindow *window in windowScene.windows) { + if (!window.isHidden && window.rootViewController) { + return window.rootViewController; + } + } + } + } + + for (UIWindow *window in application.windows) { + if (window.isKeyWindow && window.rootViewController) { return window.rootViewController; } } + + for (UIWindow *window in application.windows) { + if (window.rootViewController) { + return window.rootViewController; + } + } + return nil; } #endif