Request internet permission before connect for iOS (#794)
* Attempt to fix API error 1100 * NSURLSession fake call to exec iOS network settings dialog * use http://captive.apple.com/generate_204 for requesting internet permission * moved MobileUtils to IosController * replaced callbacks with signal-slots in apiController
This commit is contained in:
parent
abb3c918e3
commit
33d1518fd2
20 changed files with 274 additions and 264 deletions
|
@ -151,7 +151,6 @@ include_directories(mozilla/models)
|
||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
@ -193,7 +192,6 @@ endif()
|
||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -55,6 +55,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||||
|
m_nam = new QNetworkAccessManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
|
@ -150,7 +151,7 @@ void AmneziaApplication::init()
|
||||||
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::openConnection);
|
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define AMNEZIA_APPLICATION_H
|
#define AMNEZIA_APPLICATION_H
|
||||||
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
@ -75,6 +76,7 @@ public:
|
||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
|
QNetworkAccessManager *manager() { return m_nam; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void translationsUpdated();
|
void translationsUpdated();
|
||||||
|
@ -128,6 +130,8 @@ private:
|
||||||
QScopedPointer<SitesController> m_sitesController;
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
QScopedPointer<SystemController> m_systemController;
|
QScopedPointer<SystemController> m_systemController;
|
||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_nam;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|
|
@ -46,7 +46,6 @@ set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -27,8 +29,7 @@ ApiController::ApiController(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
||||||
QString &config)
|
|
||||||
{
|
{
|
||||||
if (protocol == configKey::cloak) {
|
if (protocol == configKey::cloak) {
|
||||||
config.replace("<key>", "<key>\n");
|
config.replace("<key>", "<key>\n");
|
||||||
|
@ -64,50 +65,45 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig)
|
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
||||||
{
|
{
|
||||||
QFutureWatcher<ErrorCode> watcher;
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &serverConfig, &installationUuid]() {
|
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
QNetworkAccessManager manager;
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(7000);
|
request.setTransferTimeout(7000);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Authorization",
|
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||||
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
|
||||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||||
request.setUrl(endpoint);
|
request.setUrl(endpoint);
|
||||||
|
|
||||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
|
||||||
auto apiPayloadData = generateApiPayloadData(protocol);
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
auto apiPayload = fillApiPayload(protocol, apiPayloadData);
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
QScopedPointer<QNetworkReply> reply;
|
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
||||||
reply.reset(manager.post(request, requestBody));
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(reply.get(), &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
QString contents = QString::fromUtf8(reply->readAll());
|
QString contents = QString::fromUtf8(reply->readAll());
|
||||||
auto data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
||||||
|
|
||||||
data.replace("vpn://", "");
|
data.replace("vpn://", "");
|
||||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(),
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
||||||
|
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
return ErrorCode::ApiConfigDownloadError;
|
emit errorOccurred(errorString(ErrorCode::ApiConfigEmptyError));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ba_uncompressed = qUncompress(ba);
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
|
@ -119,30 +115,37 @@ ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUu
|
||||||
processApiConfig(protocol, apiPayloadData, configStr);
|
processApiConfig(protocol, apiPayloadData, configStr);
|
||||||
|
|
||||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
|
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
||||||
serverConfig.insert(config_key::dns1, apiConfig.value(config_key::dns1));
|
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
||||||
serverConfig.insert(config_key::dns2, apiConfig.value(config_key::dns2));
|
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
||||||
serverConfig.insert(config_key::containers, apiConfig.value(config_key::containers));
|
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
||||||
serverConfig.insert(config_key::hostName, apiConfig.value(config_key::hostName));
|
|
||||||
|
|
||||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
|
emit configUpdated(true, serverConfig, serverIndex);
|
||||||
|
} else {
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
emit errorOccurred(errorString(ErrorCode::ApiConfigTimeoutError));
|
||||||
} else {
|
} else {
|
||||||
QString err = reply->errorString();
|
QString err = reply->errorString();
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
qDebug() << reply->error();
|
qDebug() << reply->error();
|
||||||
qDebug() << err;
|
qDebug() << err;
|
||||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
return ErrorCode::ApiConfigDownloadError;
|
emit errorOccurred(errorString(ErrorCode::ApiConfigDownloadError));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrorCode::NoError;
|
|
||||||
|
reply->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
QEventLoop wait;
|
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||||
watcher.setFuture(future);
|
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
||||||
wait.exec();
|
qDebug().noquote() << errors;
|
||||||
|
emit errorOccurred(errorString(ErrorCode::ApiConfigSslError));
|
||||||
return watcher.result();
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class ApiController : public QObject
|
class ApiController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -13,10 +17,15 @@ public:
|
||||||
explicit ApiController(QObject *parent = nullptr);
|
explicit ApiController(QObject *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
ErrorCode updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig);
|
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(const QString &errorMessage);
|
||||||
|
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData {
|
struct ApiPayloadData
|
||||||
|
{
|
||||||
OpenVpnConfigurator::ConnectionData certRequest;
|
OpenVpnConfigurator::ConnectionData certRequest;
|
||||||
|
|
||||||
QString wireGuardClientPrivKey;
|
QString wireGuardClientPrivKey;
|
||||||
|
|
|
@ -99,6 +99,9 @@ namespace amnezia
|
||||||
// Api errors
|
// Api errors
|
||||||
ApiConfigDownloadError = 1100,
|
ApiConfigDownloadError = 1100,
|
||||||
ApiConfigAlreadyAdded = 1101,
|
ApiConfigAlreadyAdded = 1101,
|
||||||
|
ApiConfigEmptyError = 1102,
|
||||||
|
ApiConfigTimeoutError = 1103,
|
||||||
|
ApiConfigSslError = 1104,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|
|
@ -51,6 +51,13 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
<key>CFBundleIcons</key>
|
<key>CFBundleIcons</key>
|
||||||
<dict/>
|
<dict/>
|
||||||
<key>CFBundleIcons~ipad</key>
|
<key>CFBundleIcons~ipad</key>
|
||||||
|
|
|
@ -64,6 +64,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||||
|
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
#include "MobileUtils.h"
|
|
||||||
|
|
||||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MobileUtils::shareText(const QStringList &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MobileUtils::openFile()
|
|
||||||
{
|
|
||||||
return QString();
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
#ifndef MOBILEUTILS_H
|
|
||||||
#define MOBILEUTILS_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class MobileUtils : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MobileUtils(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool shareText(const QStringList &filesToSend);
|
|
||||||
QString openFile();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MOBILEUTILS_H
|
|
|
@ -1,109 +0,0 @@
|
||||||
#include "MobileUtils.h"
|
|
||||||
|
|
||||||
#include <UIKit/UIKit.h>
|
|
||||||
#include <Security/Security.h>
|
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
|
|
||||||
static UIViewController* getViewController() {
|
|
||||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
|
||||||
for (UIWindow *window in windows) {
|
|
||||||
if (window.isKeyWindow) {
|
|
||||||
return window.rootViewController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MobileUtils::shareText(const QStringList& filesToSend) {
|
|
||||||
NSMutableArray *sharingItems = [NSMutableArray new];
|
|
||||||
|
|
||||||
for (int i = 0; i < filesToSend.size(); i++) {
|
|
||||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
|
||||||
[sharingItems addObject:logFileUrl];
|
|
||||||
}
|
|
||||||
|
|
||||||
UIViewController *qtController = getViewController();
|
|
||||||
if (!qtController) return;
|
|
||||||
|
|
||||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
|
||||||
|
|
||||||
__block bool isAccepted = false;
|
|
||||||
|
|
||||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
|
||||||
isAccepted = completed;
|
|
||||||
emit finished();
|
|
||||||
}];
|
|
||||||
|
|
||||||
[qtController presentViewController:activityController animated:YES completion:nil];
|
|
||||||
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
|
||||||
if (popController) {
|
|
||||||
popController.sourceView = qtController.view;
|
|
||||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
return isAccepted;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
|
||||||
|
|
||||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
|
||||||
|
|
||||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation DocumentPickerDelegate
|
|
||||||
|
|
||||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
|
||||||
for (NSURL *url in urls) {
|
|
||||||
if (self.documentPickerClosedCallback) {
|
|
||||||
self.documentPickerClosedCallback([url path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
|
||||||
if (self.documentPickerClosedCallback) {
|
|
||||||
self.documentPickerClosedCallback(nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
QString MobileUtils::openFile() {
|
|
||||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
|
||||||
|
|
||||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
|
||||||
documentPicker.delegate = documentPickerDelegate;
|
|
||||||
|
|
||||||
UIViewController *qtController = getViewController();
|
|
||||||
if (!qtController) return;
|
|
||||||
|
|
||||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
|
||||||
|
|
||||||
__block QString filePath;
|
|
||||||
|
|
||||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
|
||||||
if (path) {
|
|
||||||
filePath = QString::fromUtf8(path.UTF8String);
|
|
||||||
} else {
|
|
||||||
filePath = QString();
|
|
||||||
}
|
|
||||||
emit finished();
|
|
||||||
};
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
|
@ -50,12 +50,19 @@ public:
|
||||||
|
|
||||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
||||||
void checkStatus();
|
void checkStatus();
|
||||||
|
|
||||||
|
bool shareText(const QStringList &filesToSend);
|
||||||
|
QString openFile();
|
||||||
|
|
||||||
|
void requestInetAccess();
|
||||||
signals:
|
signals:
|
||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
void importConfigFromOutside(const QString);
|
void importConfigFromOutside(const QString);
|
||||||
void importBackupFromOutside(const QString);
|
void importBackupFromOutside(const QString);
|
||||||
|
|
||||||
|
void finished();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
#include "../protocols/vpnprotocol.h"
|
#include "../protocols/vpnprotocol.h"
|
||||||
#import "ios_controller_wrapper.h"
|
#import "ios_controller_wrapper.h"
|
||||||
|
@ -26,6 +27,15 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
||||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||||
|
|
||||||
|
static UIViewController* getViewController() {
|
||||||
|
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||||
|
for (UIWindow *window in windows) {
|
||||||
|
if (window.isKeyWindow) {
|
||||||
|
return window.rootViewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
@ -703,3 +713,86 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IosController::shareText(const QStringList& filesToSend) {
|
||||||
|
NSMutableArray *sharingItems = [NSMutableArray new];
|
||||||
|
|
||||||
|
for (int i = 0; i < filesToSend.size(); i++) {
|
||||||
|
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||||
|
[sharingItems addObject:logFileUrl];
|
||||||
|
}
|
||||||
|
|
||||||
|
UIViewController *qtController = getViewController();
|
||||||
|
if (!qtController) return;
|
||||||
|
|
||||||
|
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||||
|
|
||||||
|
__block bool isAccepted = false;
|
||||||
|
|
||||||
|
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||||
|
isAccepted = completed;
|
||||||
|
emit finished();
|
||||||
|
}];
|
||||||
|
|
||||||
|
[qtController presentViewController:activityController animated:YES completion:nil];
|
||||||
|
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||||
|
if (popController) {
|
||||||
|
popController.sourceView = qtController.view;
|
||||||
|
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
return isAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IosController::openFile() {
|
||||||
|
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||||
|
|
||||||
|
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||||
|
documentPicker.delegate = documentPickerDelegate;
|
||||||
|
|
||||||
|
UIViewController *qtController = getViewController();
|
||||||
|
if (!qtController) return;
|
||||||
|
|
||||||
|
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||||
|
|
||||||
|
__block QString filePath;
|
||||||
|
|
||||||
|
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||||
|
if (path) {
|
||||||
|
filePath = QString::fromUtf8(path.UTF8String);
|
||||||
|
} else {
|
||||||
|
filePath = QString();
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
|
};
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IosController::requestInetAccess() {
|
||||||
|
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
|
||||||
|
if (url) {
|
||||||
|
qDebug() << "IosController::requestInetAccess URL error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLSession *session = [NSURLSession sharedSession];
|
||||||
|
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "IosController::requestInetAccess error:" << error.localizedDescription;
|
||||||
|
} else {
|
||||||
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||||
|
QString responseBody = QString::fromUtf8((const char*)data.bytes, data.length);
|
||||||
|
qDebug() << "IosController::requestInetAccess server response:" << httpResponse.statusCode << "\n\n" <<responseBody;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#import <NetworkExtension/NetworkExtension.h>
|
#import <NetworkExtension/NetworkExtension.h>
|
||||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <UIKit/UIKit.h>
|
||||||
|
#include <Security/Security.h>
|
||||||
|
|
||||||
class IosController;
|
class IosController;
|
||||||
|
|
||||||
|
@ -13,3 +15,11 @@ class IosController;
|
||||||
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||||
|
|
||||||
|
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
|
@ -24,5 +24,22 @@
|
||||||
// cppController->vpnStatusDidChange(notification);
|
// cppController->vpnStatusDidChange(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation DocumentPickerDelegate
|
||||||
|
|
||||||
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||||
|
for (NSURL *url in urls) {
|
||||||
|
if (self.documentPickerClosedCallback) {
|
||||||
|
self.documentPickerClosedCallback([url path]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||||
|
if (self.documentPickerClosedCallback) {
|
||||||
|
self.documentPickerClosedCallback(nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
|
@ -1,5 +1,4 @@
|
||||||
#include "secure_qsettings.h"
|
#include "secure_qsettings.h"
|
||||||
#include "platforms/ios/MobileUtils.h"
|
|
||||||
|
|
||||||
#include "QAead.h"
|
#include "QAead.h"
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
#endif
|
#endif
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "utilities.h"
|
|
||||||
#include "core/controllers/apiController.h"
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/errorstrings.h"
|
#include "core/errorstrings.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
@ -19,6 +17,7 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||||
const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
QObject *parent)
|
QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
|
m_apiController(this),
|
||||||
m_serversModel(serversModel),
|
m_serversModel(serversModel),
|
||||||
m_containersModel(containersModel),
|
m_containersModel(containersModel),
|
||||||
m_clientManagementModel(clientManagementModel),
|
m_clientManagementModel(clientManagementModel),
|
||||||
|
@ -29,6 +28,10 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||||
connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||||
connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
connect(&m_apiController, &ApiController::configUpdated, this,
|
||||||
|
static_cast<void (ConnectionController::*)(const bool, const QJsonObject &, const int)>(&ConnectionController::openConnection));
|
||||||
|
connect(&m_apiController, &ApiController::errorOccurred, this, &ConnectionController::connectionErrorOccurred);
|
||||||
|
|
||||||
m_state = Vpn::ConnectionState::Disconnected;
|
m_state = Vpn::ConnectionState::Disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,64 +46,16 @@ void ConnectionController::openConnection()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
|
||||||
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()
|
if (serverConfig.value(config_key::configVersion).toInt()
|
||||||
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
ApiController apiController;
|
m_apiController.updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverIndex, serverConfig);
|
||||||
errorCode = apiController.updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverConfig);
|
} else {
|
||||||
if (errorCode != ErrorCode::NoError) {
|
openConnection(false, serverConfig, serverIndex);
|
||||||
emit connectionErrorOccurred(errorString(errorCode));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_serversModel->editServer(serverConfig, serverIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
|
||||||
emit noInstalledContainers();
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
|
||||||
|
|
||||||
if (!m_containersModel->isSupportedByCurrentPlatform(container)) {
|
|
||||||
emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container == DockerContainer::None) {
|
|
||||||
emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qApp->processEvents();
|
|
||||||
|
|
||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
|
||||||
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
|
||||||
|
|
||||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
|
||||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
|
||||||
errorCode = updateProtocolConfig(container, credentials, containerConfig, serverController);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit connectionErrorOccurred(errorString(errorCode));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dns = m_serversModel->getDnsPair(serverIndex);
|
|
||||||
serverConfig = m_serversModel->getServerConfig(serverIndex);
|
|
||||||
|
|
||||||
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container, errorCode);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit connectionErrorOccurred(tr("unable to create configuration"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionController::closeConnection()
|
void ConnectionController::closeConnection()
|
||||||
|
@ -231,6 +186,53 @@ bool ConnectionController::isProtocolConfigExists(const QJsonObject &containerCo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionController::openConnection(const bool updateConfig, const QJsonObject &config, const int serverIndex)
|
||||||
|
{
|
||||||
|
// Update config for this server as it was received from API
|
||||||
|
if (updateConfig) {
|
||||||
|
m_serversModel->editServer(config, serverIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
emit noInstalledContainers();
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
|
|
||||||
|
if (!m_containersModel->isSupportedByCurrentPlatform(container)) {
|
||||||
|
emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container == DockerContainer::None) {
|
||||||
|
emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
|
|
||||||
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
|
ErrorCode errorCode = updateProtocolConfig(container, credentials, containerConfig, serverController);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit connectionErrorOccurred(errorString(errorCode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dns = m_serversModel->getDnsPair(serverIndex);
|
||||||
|
|
||||||
|
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, config, containerConfig, container, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit connectionErrorOccurred(tr("unable to create configuration"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials,
|
||||||
QJsonObject &containerConfig, QSharedPointer<ServerController> serverController)
|
QJsonObject &containerConfig, QSharedPointer<ServerController> serverController)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef CONNECTIONCONTROLLER_H
|
#ifndef CONNECTIONCONTROLLER_H
|
||||||
#define CONNECTIONCONTROLLER_H
|
#define CONNECTIONCONTROLLER_H
|
||||||
|
|
||||||
|
#include "core/controllers/apiController.h"
|
||||||
#include "protocols/vpnprotocol.h"
|
#include "protocols/vpnprotocol.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
|
@ -60,6 +61,10 @@ private:
|
||||||
Vpn::ConnectionState getCurrentConnectionState();
|
Vpn::ConnectionState getCurrentConnectionState();
|
||||||
bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container);
|
bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container);
|
||||||
|
|
||||||
|
void openConnection(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
|
ApiController m_apiController;
|
||||||
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
#include "platforms/ios/MobileUtils.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -46,9 +46,8 @@ void SystemController::saveFile(QString fileName, const QString &data)
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
QStringList filesToSend;
|
QStringList filesToSend;
|
||||||
filesToSend.append(fileUrl.toString());
|
filesToSend.append(fileUrl.toString());
|
||||||
MobileUtils mobileUtils;
|
|
||||||
// todo check if save successful
|
// todo check if save successful
|
||||||
mobileUtils.shareText(filesToSend);
|
IosController::Instance()->shareText(filesToSend);
|
||||||
return;
|
return;
|
||||||
#else
|
#else
|
||||||
QFileInfo fi(fileName);
|
QFileInfo fi(fileName);
|
||||||
|
@ -67,8 +66,7 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
|
|
||||||
MobileUtils mobileUtils;
|
fileName = IosController::Instance()->openFile();
|
||||||
fileName = mobileUtils.openFile();
|
|
||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty()) {
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue