mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
Feat: Add deep link
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
#include <QEvent>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QUrl>
|
||||
#include <QCoreApplication>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QWindow>
|
||||
|
||||
@@ -81,6 +84,44 @@ AmneziaApplication::~AmneziaApplication()
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
namespace {
|
||||
QString vpnUrlFromArguments(const QStringList &args)
|
||||
{
|
||||
for (const QString &arg : args) {
|
||||
const QString t = arg.trimmed();
|
||||
if (t.startsWith(QLatin1String("vpn://"), Qt::CaseInsensitive)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
namespace {
|
||||
void registerWindowsVpnUrlSchemeIfNeeded()
|
||||
{
|
||||
QSettings flag(ORGANIZATION_NAME, APPLICATION_NAME);
|
||||
if (flag.value(QStringLiteral("protocolHandler/vpnRegistered")).toBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString exe = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
|
||||
|
||||
QSettings vpnKey(QStringLiteral("HKEY_CURRENT_USER\\Software\\Classes\\vpn"), QSettings::NativeFormat);
|
||||
vpnKey.setValue(QStringLiteral("."), QStringLiteral("URL:AmneziaVPN"));
|
||||
vpnKey.setValue(QStringLiteral("URL Protocol"), QString());
|
||||
|
||||
QSettings cmdKey(QStringLiteral("HKEY_CURRENT_USER\\Software\\Classes\\vpn\\shell\\open\\command"), QSettings::NativeFormat);
|
||||
cmdKey.setValue(QStringLiteral("."), QStringLiteral("\"%1\" \"%2\"").arg(exe, QStringLiteral("%1")));
|
||||
|
||||
flag.setValue(QStringLiteral("protocolHandler/vpnRegistered"), true);
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
namespace {
|
||||
static void clearQtCaches()
|
||||
@@ -190,6 +231,18 @@ void AmneziaApplication::init()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
# ifdef Q_OS_WIN
|
||||
registerWindowsVpnUrlSchemeIfNeeded();
|
||||
# endif
|
||||
if (!m_parser.isSet(m_optImport)) {
|
||||
const QString vpnArg = vpnUrlFromArguments(QCoreApplication::arguments());
|
||||
if (!vpnArg.isEmpty()) {
|
||||
QTimer::singleShot(0, this, [this, vpnArg]() { deliverVpnDeepLink(vpnArg); });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AmneziaApplication::registerTypes()
|
||||
@@ -250,23 +303,73 @@ bool AmneziaApplication::parseCommands()
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
void AmneziaApplication::startLocalServer()
|
||||
{
|
||||
const QString serverName(QStringLiteral("AmneziaVPNInstance"));
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer *server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
if (!server->listen(serverName)) {
|
||||
qWarning() << "QLocalServer::listen failed:" << server->errorString();
|
||||
}
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [this, server]() {
|
||||
QLocalSocket *sock = server->nextPendingConnection();
|
||||
if (!sock) {
|
||||
return;
|
||||
}
|
||||
emit m_coreController->pageController()->raiseMainWindow(); //TODO
|
||||
|
||||
QString vpnPayload;
|
||||
if (sock->waitForReadyRead(3000)) {
|
||||
const QByteArray buf = sock->readAll();
|
||||
static const QByteArray prefix = QByteArrayLiteral("VPN\n");
|
||||
if (buf.startsWith(prefix)) {
|
||||
vpnPayload = QString::fromUtf8(buf.mid(prefix.size())).trimmed();
|
||||
}
|
||||
}
|
||||
sock->deleteLater();
|
||||
|
||||
if (!vpnPayload.isEmpty()) {
|
||||
QTimer::singleShot(0, this, [this, vpnPayload]() { deliverVpnDeepLink(vpnPayload); });
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::deliverVpnDeepLink(const QString &payload)
|
||||
{
|
||||
if (!m_coreController) {
|
||||
return;
|
||||
}
|
||||
const QString trimmed = payload.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
m_coreController->openVpnKeyImportPreview(trimmed);
|
||||
}
|
||||
|
||||
bool AmneziaApplication::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::FileOpen) {
|
||||
auto *foe = static_cast<QFileOpenEvent *>(event);
|
||||
const QUrl url = foe->url();
|
||||
if (url.scheme().compare(QLatin1String("vpn"), Qt::CaseInsensitive) == 0) {
|
||||
const QString payload = url.toString(QUrl::PrettyDecoded);
|
||||
QTimer::singleShot(0, this, [this, payload]() { deliverVpnDeepLink(payload); });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return AMNEZIA_BASE_CLASS::event(event);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Close) {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <QClipboard>
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include <QEvent>
|
||||
#endif
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "secureQSettings.h"
|
||||
@@ -67,12 +70,19 @@ private:
|
||||
QCommandLineOption m_optConnect;
|
||||
QCommandLineOption m_optImport;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void deliverVpnDeepLink(const QString &payload);
|
||||
#endif
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
bool event(QEvent *event) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
||||
@@ -243,7 +243,10 @@ class AmneziaActivity : QtActivity() {
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
intent?.let(::processIntent)
|
||||
intent?.let {
|
||||
setIntent(it)
|
||||
processIntent(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIntent(intent: Intent) {
|
||||
|
||||
@@ -36,6 +36,7 @@ class ImportConfigActivity : ComponentActivity() {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
setIntent(intent)
|
||||
intent.let(::readConfig)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaOpenUrlImport.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
|
||||
@@ -47,6 +48,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaOpenUrlImport.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -340,3 +340,19 @@ void CoreController::importConfigFromData(const QString &data)
|
||||
m_importController->importConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::openVpnKeyImportPreview(const QString &data)
|
||||
{
|
||||
if (!m_importController || data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit m_pageController->goToPageHome();
|
||||
if (!m_importController->extractConfigFromData(data)) {
|
||||
return;
|
||||
}
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
emit m_pageController->raiseMainWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ public:
|
||||
|
||||
void openConnectionByIndex(int serverIndex);
|
||||
void importConfigFromData(const QString &data);
|
||||
/** Navigate home, parse key, open preview (same path as mobile deep link / share). */
|
||||
void openVpnKeyImportPreview(const QString &data);
|
||||
void updateTranslator(const QLocale &locale);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -361,10 +361,8 @@ void CoreSignalHandlers::initAndroidConnectionHandler()
|
||||
m_coreController->m_connectionController->restoreConnection();
|
||||
});
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
m_coreController->m_importController->extractConfigFromData(data);
|
||||
m_coreController->openVpnKeyImportPreview(data);
|
||||
data.clear();
|
||||
emit m_coreController->m_pageController->goToPageViewConfig();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
@@ -373,9 +371,7 @@ void CoreSignalHandlers::initIosImportHandler()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
m_coreController->m_importController->extractConfigFromData(data);
|
||||
emit m_coreController->m_pageController->goToPageViewConfig();
|
||||
m_coreController->openVpnKeyImportPreview(data);
|
||||
});
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_coreController->m_pageController->goToPageHome();
|
||||
|
||||
@@ -387,6 +387,7 @@ void ImportController::importConfig(const QJsonObject &config)
|
||||
} else if (config.contains(configKey::configVersion)) {
|
||||
quint16 crc = qChecksum(QJsonDocument(config).toJson());
|
||||
if (m_serversRepository->hasServerWithCrc(crc)) {
|
||||
// Same API key / subscription blob already present (incl. deep link re-import).
|
||||
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
||||
} else {
|
||||
QJsonObject configWithCrc = config;
|
||||
|
||||
@@ -86,6 +86,19 @@
|
||||
<dict/>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>org.amnezia.AmneziaVPN.vpn-deeplink</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>vpn</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
@@ -46,6 +46,19 @@
|
||||
</dict>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>org.amnezia.AmneziaVPN.vpn-deeplink</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>vpn</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
+34
-9
@@ -1,3 +1,5 @@
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -6,7 +8,7 @@
|
||||
#include "core/utils/migrations.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
@@ -17,18 +19,41 @@
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
bool isAnotherInstanceRunning()
|
||||
namespace {
|
||||
QString findVpnDeepLinkInArguments(const QStringList &args)
|
||||
{
|
||||
for (const QString &arg : args) {
|
||||
const QString t = arg.trimmed();
|
||||
if (t.startsWith(QLatin1String("vpn://"), Qt::CaseInsensitive)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool notifyRunningInstanceOrExit(AmneziaApplication &app, const QString &vpnPayload)
|
||||
{
|
||||
QLocalSocket socket;
|
||||
socket.connectToServer("AmneziaVPNInstance");
|
||||
if (socket.waitForConnected(500)) {
|
||||
qWarning() << "AmneziaVPN is already running";
|
||||
return true;
|
||||
socket.connectToServer(QStringLiteral("AmneziaVPNInstance"));
|
||||
if (!socket.waitForConnected(500)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
qWarning() << "AmneziaVPN is already running";
|
||||
if (!vpnPayload.isEmpty()) {
|
||||
const QByteArray msg = QByteArrayLiteral("VPN\n") + vpnPayload.toUtf8() + '\n';
|
||||
socket.write(msg);
|
||||
socket.waitForBytesWritten(3000);
|
||||
}
|
||||
socket.flush();
|
||||
QTimer::singleShot(1000, &app, [&app]() { app.quit(); });
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
// Desktop (non-NE): single-instance IPC forwards vpn:// to the running process. MACOS_NE has no IPC here;
|
||||
// deep links use argv / QFileOpenEvent after registration in the app bundle Info.plist.
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Migrations migrationsManager;
|
||||
@@ -48,8 +73,8 @@ int main(int argc, char *argv[])
|
||||
OsSignalHandler::setup();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||
const QString vpnFromArgv = findVpnDeepLinkInArguments(QCoreApplication::arguments());
|
||||
if (notifyRunningInstanceOrExit(app, vpnFromArgv)) {
|
||||
return app.exec();
|
||||
}
|
||||
app.startLocalServer();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Handles custom scheme vpn:// (full absoluteString) and file URLs for config / backup import. */
|
||||
void AmneziaHandleOpenUrl(NSURL *url);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#import "AmneziaOpenUrlImport.h"
|
||||
|
||||
#include "ios_controller.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
void AmneziaHandleOpenUrl(NSURL *url)
|
||||
{
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *scheme = url.scheme ? [url.scheme lowercaseString] : @"";
|
||||
if ([scheme isEqualToString:@"vpn"]) {
|
||||
NSString *absolute = url.absoluteString;
|
||||
if (absolute.length == 0) {
|
||||
return;
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
IosController::Instance()->importConfigFromOutside(QString::fromUtf8([absolute UTF8String]));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.isFileURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath = QString::fromUtf8([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(QLatin1String("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));
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
#include "ios_controller.h"
|
||||
#import "AmneziaOpenUrlImport.h"
|
||||
|
||||
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
|
||||
|
||||
@@ -14,29 +9,7 @@ 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));
|
||||
});
|
||||
AmneziaHandleOpenUrl(url);
|
||||
}
|
||||
|
||||
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#import "QtAppDelegate.h"
|
||||
#import "ios_controller.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#import "AmneziaOpenUrlImport.h"
|
||||
|
||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
#if !MACOS_NE
|
||||
@@ -11,6 +8,10 @@
|
||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||
// Override point for customization after application launch.
|
||||
NSLog(@"Application didFinishLaunchingWithOptions");
|
||||
NSURL *launchUrl = launchOptions[UIApplicationLaunchOptionsURLKey];
|
||||
if (launchUrl) {
|
||||
AmneziaHandleOpenUrl(launchUrl);
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -35,24 +36,11 @@
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
if (url.fileURL) {
|
||||
QString filePath(url.path.UTF8String);
|
||||
if (filePath.isEmpty()) return NO;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
NSLog(@"Application openURL: %@", url);
|
||||
|
||||
if (filePath.contains("backup")) {
|
||||
IosController::Instance()->importBackupFromOutside(filePath);
|
||||
} else {
|
||||
QFile file(filePath);
|
||||
bool isOpenFile = file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
IosController::Instance()->importConfigFromOutside(QString(data));
|
||||
}
|
||||
});
|
||||
NSLog(@"Application openURL: %@", url);
|
||||
AmneziaHandleOpenUrl(url);
|
||||
|
||||
NSString *scheme = url.scheme ? [url.scheme lowercaseString] : @"";
|
||||
if ([scheme isEqualToString:@"vpn"] || url.fileURL) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
|
||||
@@ -220,7 +220,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
|
||||
m_rawConfig = configuration;
|
||||
m_serverAddress = configuration.value(configKey::hostName).toString().toNSString();
|
||||
|
||||
const QString serverDescription = configuration.value(config_key::description).toString().trimmed();
|
||||
const QString serverDescription = configuration.value(configKey::description).toString().trimmed();
|
||||
QString tunnelName;
|
||||
if (serverDescription.isEmpty()) {
|
||||
tunnelName = ProtocolUtils::protoToString(proto);
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <QThread>
|
||||
#endif
|
||||
#include "version.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include <QClipboard>
|
||||
|
||||
@@ -72,7 +72,7 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("New connection")
|
||||
headerText: qsTr("Add this connection?")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -204,7 +204,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
text: qsTr("Connect")
|
||||
text: qsTr("Add")
|
||||
clickedFunc: function() {
|
||||
const headerItem = listView.headerItem;
|
||||
if (!headerItem) {
|
||||
|
||||
Reference in New Issue
Block a user