diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 0ef72ed0..936dcbf2 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -293,6 +293,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h ) @@ -301,6 +302,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 53d8fe30..2ee9af16 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -14,9 +15,12 @@ #include "version.h" #include "platforms/ios/QRCodeReaderBase.h" +#if defined(Q_OS_ANDROID) + #include "platforms/android/android_controller.h" +#endif -#include "ui/pages.h" #include "protocols/qml_register_protocols.h" +#include "ui/pages.h" #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" @@ -33,7 +37,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond setQuitOnLastWindowClosed(false); // Fix config file permissions -#ifdef Q_OS_LINUX && !defined(Q_OS_ANDROID) +#if defined Q_OS_LINUX && !defined(Q_OS_ANDROID) { QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); s.setValue("permFixed", true); @@ -87,16 +91,35 @@ void AmneziaApplication::init() initModels(); initControllers(); +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initialized, this, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); + if (!AndroidController::instance()->initialize()) { + qCritical() << QString("Init failed"); + if (m_vpnConnection) + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); + return; + } + + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_importController.get(), + &ImportController::extractConfigFromData); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), + &PageController::goToPageViewConfig); +#endif + m_notificationHandler.reset(NotificationHandler::create(nullptr)); connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), &NotificationHandler::setConnectionState); - void openConnection(); - void closeConnection(); - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), - &PageController::raise); + &PageController::raiseMainWindow); connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 18955532..a56edcbf 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -13,17 +13,16 @@ #include "android_controller.h" #include "private/qandroidextras_p.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "androidvpnactivity.h" #include "androidutils.h" +#include "androidvpnactivity.h" -namespace { -AndroidController* s_instance = nullptr; +namespace +{ + AndroidController *s_instance = nullptr; -constexpr auto PERMISSIONHELPER_CLASS = - "org/amnezia/vpn/qt/VPNPermissionHelper"; -} // namespace + constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper"; +} // namespace AndroidController::AndroidController() : QObject() { @@ -33,109 +32,127 @@ AndroidController::AndroidController() : QObject() auto activity = AndroidVPNActivity::instance(); - connect(activity, &AndroidVPNActivity::serviceConnected, this, []() { - qDebug() << "Transact: service connected"; - AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceConnected, this, + []() { + qDebug() << "Transact: service connected"; + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventInitialized, this, - [this](const QString& parcelBody) { - // We might get multiple Init events as widgets, or fragments - // might query this. - if (m_init) { - return; - } + connect( + activity, &AndroidVPNActivity::eventInitialized, this, + [this](const QString &parcelBody) { + // We might get multiple Init events as widgets, or fragments + // might query this. + if (m_init) { + return; + } - qDebug() << "Transact: init"; + qDebug() << "Transact: init"; - m_init = true; + m_init = true; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - qlonglong time = doc.object()["time"].toVariant().toLongLong(); + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + qlonglong time = doc.object()["time"].toVariant().toLongLong(); - isConnected = doc.object()["connected"].toBool(); + isConnected = doc.object()["connected"].toBool(); - if (isConnected) { - emit scheduleStatusCheckSignal(); - } + if (isConnected) { + emit scheduleStatusCheckSignal(); + } - emit initialized( - true, isConnected, - time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); + emit initialized(true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); - setFallbackConnectedNotification(); - }, Qt::QueuedConnection); + setFallbackConnectedNotification(); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConnected, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody); - qDebug() << "Transact: connected"; + connect( + activity, &AndroidVPNActivity::eventConnected, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody); + qDebug() << "Transact: connected"; - if (!isConnected) { - emit scheduleStatusCheckSignal(); - } + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } - isConnected = true; + isConnected = true; - emit connectionStateChanged(VpnProtocol::Connected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventDisconnected, this, - [this]() { - qDebug() << "Transact: disconnected"; + connect( + activity, &AndroidVPNActivity::eventDisconnected, this, + [this]() { + qDebug() << "Transact: disconnected"; - isConnected = false; + isConnected = false; - emit connectionStateChanged(VpnProtocol::Disconnected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, - [this](const QString& parcelBody) { - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventStatisticUpdate, this, + [this](const QString &parcelBody) { + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString rx = doc.object()["rx_bytes"].toString(); - QString tx = doc.object()["tx_bytes"].toString(); - QString endpoint = doc.object()["endpoint"].toString(); - QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); + QString rx = doc.object()["rx_bytes"].toString(); + QString tx = doc.object()["tx_bytes"].toString(); + QString endpoint = doc.object()["endpoint"].toString(); + QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); - emit statusUpdated(rx, tx, endpoint, deviceIPv4); - }, Qt::QueuedConnection); + emit statusUpdated(rx, tx, endpoint, deviceIPv4); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventBackendLogs, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: backend logs"; + connect( + activity, &AndroidVPNActivity::eventBackendLogs, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: backend logs"; - QString buffer = parcelBody.toUtf8(); - if (m_logCallback) { - m_logCallback(buffer); - } - }, Qt::QueuedConnection); + QString buffer = parcelBody.toUtf8(); + if (m_logCallback) { + m_logCallback(buffer); + } + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventActivationError, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody) - qDebug() << "Transact: error"; - emit connectionStateChanged(VpnProtocol::Error); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::eventActivationError, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody) + qDebug() << "Transact: error"; + emit connectionStateChanged(Vpn::ConnectionState::Error); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConfigImport, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: config import"; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventConfigImport, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: config import"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - importConfig(buffer); - }, Qt::QueuedConnection); + QString buffer = doc.object()["config"].toString(); + qDebug() << "Transact: config string" << buffer; + importConfigFromOutside(buffer); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::serviceDisconnected, this, - [this]() { - qDebug() << "Transact: service disconnected"; - m_serviceConnected = false; - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceDisconnected, this, + [this]() { + qDebug() << "Transact: service disconnected"; + m_serviceConnected = false; + }, + Qt::QueuedConnection); } -AndroidController* AndroidController::instance() { +AndroidController *AndroidController::instance() +{ if (!s_instance) { s_instance = new AndroidController(); } @@ -143,16 +160,13 @@ AndroidController* AndroidController::instance() { return s_instance; } -bool AndroidController::initialize(StartPageLogic *startPageLogic) +bool AndroidController::initialize() { qDebug() << "Initializing"; - m_startPageLogic = startPageLogic; - // Hook in the native implementation for startActivityForResult into the JNI - JNINativeMethod methods[]{{"startActivityForResult", - "(Landroid/content/Intent;)V", - reinterpret_cast(startActivityForResult)}}; + JNINativeMethod methods[] { { "startActivityForResult", "(Landroid/content/Intent;)V", + reinterpret_cast(startActivityForResult) } }; QJniObject javaClass(PERMISSIONHELPER_CLASS); QJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); @@ -168,11 +182,9 @@ ErrorCode AndroidController::start() { qDebug() << "Prompting for VPN permission"; QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QJniObject::callStaticMethod( - PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", - appContext.object()); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + QJniObject::callStaticMethod(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", + appContext.object()); QJsonDocument doc(m_vpnConfig); AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson()); @@ -180,7 +192,8 @@ ErrorCode AndroidController::start() return NoError; } -void AndroidController::stop() { +void AndroidController::stop() +{ qDebug() << "AndroidController::stop"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString()); @@ -188,16 +201,16 @@ void AndroidController::stop() { // Activates the tunnel that is currently set // in the VPN Service -void AndroidController::resumeStart() { +void AndroidController::resumeStart() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString()); } /* * Sets the current notification text that is shown */ -void AndroidController::setNotificationText(const QString& title, - const QString& message, - int timerSec) { +void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) +{ QJsonObject args; args["title"] = title; args["message"] = message; @@ -207,7 +220,8 @@ void AndroidController::setNotificationText(const QString& title, AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson()); } -void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { +void AndroidController::shareConfig(const QString &configContent, const QString &suggestedName) +{ AndroidVPNActivity::saveFileAs(configContent, suggestedName); } @@ -216,7 +230,8 @@ void AndroidController::shareConfig(const QString& configContent, const QString& * switches into the Connected state without the app open * e.g via always-on vpn */ -void AndroidController::setFallbackConnectedNotification() { +void AndroidController::setFallbackConnectedNotification() +{ QJsonObject args; args["title"] = tr("AmneziaVPN"); //% "Ready for you to connect" @@ -227,11 +242,13 @@ void AndroidController::setFallbackConnectedNotification() { AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); } -void AndroidController::checkStatus() { +void AndroidController::checkStatus() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString()); } -void AndroidController::getBackendLogs(std::function&& a_callback) { +void AndroidController::getBackendLogs(std::function &&a_callback) +{ qDebug() << "get logs"; m_logCallback = std::move(a_callback); @@ -239,16 +256,13 @@ void AndroidController::getBackendLogs(std::function&& a_c AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString()); } -void AndroidController::cleanupBackendLogs() { +void AndroidController::cleanupBackendLogs() +{ qDebug() << "cleanup logs"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString()); } -void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importAnyFile(data); -} - const QJsonObject &AndroidController::vpnConfig() const { return m_vpnConfig; @@ -285,31 +299,29 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int qDebug() << "start vpnPermissionHelper"; Q_UNUSED(env); - QtAndroidPrivate::startActivity(intent, 1337, - [](int receiverRequestCode, int resultCode, - const QJniObject& data) { - // Currently this function just used in - // VPNService.kt::checkPermissions. So the result - // we're getting is if the User gave us the - // Vpn.bind permission. In case of NO we should - // abort. - Q_UNUSED(receiverRequestCode); - Q_UNUSED(data); + QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) { + // Currently this function just used in + // VPNService.kt::checkPermissions. So the result + // we're getting is if the User gave us the + // Vpn.bind permission. In case of NO we should + // abort. + Q_UNUSED(receiverRequestCode); + Q_UNUSED(data); - AndroidController* controller = AndroidController::instance(); - if (!controller) { - return; - } + AndroidController *controller = AndroidController::instance(); + if (!controller) { + return; + } - if (resultCode == ACTIVITY_RESULT_OK) { - qDebug() << "VPN PROMPT RESULT - Accepted"; - controller->resumeStart(); - return; - } - // If the request got rejected abort the current - // connection. - qWarning() << "VPN PROMPT RESULT - Rejected"; - emit controller->connectionStateChanged(VpnProtocol::Disconnected); - }); + if (resultCode == ACTIVITY_RESULT_OK) { + qDebug() << "VPN PROMPT RESULT - Accepted"; + controller->resumeStart(); + return; + } + // If the request got rejected abort the current + // connection. + qWarning() << "VPN PROMPT RESULT - Rejected"; + emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); return; } diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 7e5b52c8..baa0bc80 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -8,36 +8,32 @@ #include #include -#include "ui/pages_logic/StartPageLogic.h" - #include "protocols/vpnprotocol.h" using namespace amnezia; - class AndroidController : public QObject { Q_OBJECT public: explicit AndroidController(); - static AndroidController* instance(); + static AndroidController *instance(); virtual ~AndroidController() override = default; - bool initialize(StartPageLogic *startPageLogic); + bool initialize(); ErrorCode start(); void stop(); void resumeStart(); void checkStatus(); - void setNotificationText(const QString& title, const QString& message, int timerSec); - void shareConfig(const QString& data, const QString& suggestedName); + void setNotificationText(const QString &title, const QString &message, int timerSec); + void shareConfig(const QString &data, const QString &suggestedName); void setFallbackConnectedNotification(); - void getBackendLogs(std::function&& callback); + void getBackendLogs(std::function &&callback); void cleanupBackendLogs(); - void importConfig(const QString& data); const QJsonObject &vpnConfig() const; void setVpnConfig(const QJsonObject &newVpnConfig); @@ -45,18 +41,20 @@ public: void startQrReaderActivity(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); // This signal is emitted when the controller is initialized. Note that the // VPN tunnel can be already active. In this case, "connected" should be set // to true and the "connectionDate" should be set to the activation date if // known. // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, const QDateTime& connectionDate); + void initialized(bool status, bool connected, const QDateTime &connectionDate); void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); void scheduleStatusCheckSignal(); + void importConfigFromOutside(QString &data); + protected slots: void scheduleStatusCheckSlot(); @@ -65,12 +63,10 @@ private: QJsonObject m_vpnConfig; - StartPageLogic *m_startPageLogic; - bool m_serviceConnected = false; - std::function m_logCallback; + std::function m_logCallback; - static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); + static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent); bool isConnected = false; diff --git a/client/platforms/android/authResultReceiver.cpp b/client/platforms/android/authResultReceiver.cpp new file mode 100644 index 00000000..21e838a2 --- /dev/null +++ b/client/platforms/android/authResultReceiver.cpp @@ -0,0 +1,16 @@ +#include "authResultReceiver.h" + +AuthResultReceiver::AuthResultReceiver(QSharedPointer ¬ifier) : m_notifier(notifier) +{ +} + +void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) +{ + qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; + + if (resultCode == -1) { // ResultOK + emit m_notifier->authSuccessful(); + } else { + emit m_notifier->authFailed(); + } +} diff --git a/client/platforms/android/authResultReceiver.h b/client/platforms/android/authResultReceiver.h new file mode 100644 index 00000000..9a88dcf5 --- /dev/null +++ b/client/platforms/android/authResultReceiver.h @@ -0,0 +1,32 @@ +#ifndef AUTHRESULTRECEIVER_H +#define AUTHRESULTRECEIVER_H + +#include + +#include + +class AuthResultNotifier : public QObject +{ + Q_OBJECT + +public: + AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {}; + +signals: + void authFailed(); + void authSuccessful(); +}; + +/* Auth result handler for Android */ +class AuthResultReceiver final : public QAndroidActivityResultReceiver +{ +public: + AuthResultReceiver(QSharedPointer ¬ifier); + + void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; + +private: + QSharedPointer m_notifier; +}; + +#endif // AUTHRESULTRECEIVER_H diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index f48c7dfd..66fec366 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -5,27 +5,28 @@ #include #include "logger.h" -#include "wireguardprotocol.h" #include "utilities.h" +#include "wireguardprotocol.h" #include "mozilla/localsocketcontroller.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) + : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); // MZ #if defined(MZ_LINUX) - //m_impl.reset(new LinuxController()); + // m_impl.reset(new LinuxController()); #elif defined(MZ_MACOS) // || defined(MZ_WINDOWS) m_impl.reset(new LocalSocketController()); - connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) { - emit connectionStateChanged(VpnProtocol::Connected); - }); - connect(m_impl.get(), &ControllerImpl::disconnected, this, [this](){ - emit connectionStateChanged(VpnProtocol::Disconnected); - }); + connect(m_impl.get(), &ControllerImpl::connected, this, + [this](const QString &pubkey, const QDateTime &connectionTimestamp) { + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }); + connect(m_impl.get(), &ControllerImpl::disconnected, this, + [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); m_impl->initialize(nullptr, nullptr); #endif } @@ -74,9 +75,10 @@ void WireguardProtocol::stop() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; - }); + connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; + }); #ifdef Q_OS_LINUX if (IpcClient::Interface()) { @@ -143,8 +145,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura m_isConfigLoaded = true; qDebug().noquote() << QString("Set config data") << configPath(); - qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); - + qDebug().noquote() << QString("Set config data") + << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } QString WireguardProtocol::configPath() const @@ -156,7 +158,8 @@ void WireguardProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -216,13 +219,13 @@ ErrorCode WireguardProtocol::start() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; + }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(Vpn::ConnectionState::Connected); - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, + [this]() { setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAll(); @@ -250,7 +253,6 @@ ErrorCode WireguardProtocol::start() void WireguardProtocol::updateVpnGateway(const QString &line) { - } QString WireguardProtocol::serviceName() const @@ -261,9 +263,9 @@ QString WireguardProtocol::serviceName() const QStringList WireguardProtocol::stopArgs() { #ifdef Q_OS_WIN - return {"--remove", configPath()}; + return { "--remove", configPath() }; #elif defined Q_OS_LINUX - return {"down", "wg99"}; + return { "down", "wg99" }; #else return {}; #endif @@ -272,11 +274,10 @@ QStringList WireguardProtocol::stopArgs() QStringList WireguardProtocol::startArgs() { #ifdef Q_OS_WIN - return {"--add", configPath()}; + return { "--add", configPath() }; #elif defined Q_OS_LINUX - return {"up", "wg99"}; + return { "up", "wg99" }; #else return {}; #endif } - diff --git a/client/resources.qrc b/client/resources.qrc index e1afa294..85ee838f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -194,7 +194,6 @@ ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml ui/qml/Controls2/SwitcherType.qml - ui/qml/Pages2/PageTest.qml ui/qml/Controls2/TabButtonType.qml ui/qml/Pages2/PageSetupWizardProtocolSettings.qml ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -278,5 +277,6 @@ ui/qml/Pages2/PageServiceSftpSettings.qml images/controls/copy.svg ui/qml/Pages2/PageServiceTorWebsiteSettings.qml + ui/qml/Pages2/PageSetupWizardQrReader.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index c989422d..561222f2 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,9 +11,12 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" -#include "qrcodegen.hpp" - #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" + #include "platforms/android/androidutils.h" +#endif +#include "qrcodegen.hpp" ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -25,6 +28,14 @@ ExportController::ExportController(const QSharedPointer &serversMo m_settings(settings), m_configurator(configurator) { +#ifdef Q_OS_ANDROID + m_authResultNotifier.reset(new AuthResultNotifier); + m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier)); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, + [this]() { emit exportErrorOccurred(tr("Access error!")); }); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, + &ExportController::generateFullAccessConfig); +#endif } void ExportController::generateFullAccessConfig() @@ -44,6 +55,27 @@ void ExportController::generateFullAccessConfig() emit exportConfigChanged(); } +#if defined(Q_OS_ANDROID) +void ExportController::generateFullAccessConfigAndroid() +{ + /* We use builtin keyguard for ssh key export protection on Android */ + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + if (appContext.isValid()) { + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); + if (intent.isValid()) { + if (intent.object() != nullptr) { + QtAndroidPrivate::startActivity(intent.object(), 1, m_authResultReceiver.get()); + } + } else { + generateFullAccessConfig(); + } + } +} +#endif + void ExportController::generateConnectionConfig() { clearPreviousConfig(); @@ -194,6 +226,35 @@ void ExportController::saveFile() QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } +void ExportController::shareFile() +{ +#if defined Q_OS_IOS + ext.replace("*", ""); + QString fileName = QDir::tempPath() + "/" + suggestedName; + + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); + + QFile::remove(fileName); + + QFile save(fileName); + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QStringList filesToSend; + filesToSend.append(fileName); + MobileUtils::shareText(filesToSend); + return; +#endif +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(m_config, "amnezia_config"); + return; +#endif +} + QList ExportController::generateQrCodeImageSeries(const QByteArray &data) { double k = 850; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index e4a37a96..7af2cd85 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -6,6 +6,9 @@ #include "configurators/vpn_configurator.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/authResultReceiver.h" +#endif class ExportController : public QObject { @@ -22,6 +25,9 @@ public: public slots: void generateFullAccessConfig(); +#if defined(Q_OS_ANDROID) + void generateFullAccessConfigAndroid(); +#endif void generateConnectionConfig(); void generateOpenVpnConfig(); void generateWireGuardConfig(); @@ -30,6 +36,7 @@ public slots: QList getQrCodes(); void saveFile(); + void shareFile(); signals: void generateConfig(int type); @@ -52,6 +59,11 @@ private: QString m_config; QList m_qrCodes; + +#ifdef Q_OS_ANDROID + QSharedPointer m_authResultNotifier; + QSharedPointer m_authResultReceiver; +#endif }; #endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5b4b7a83..218f43cb 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -2,8 +2,15 @@ #include #include +#include #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif +#include "utilities.h" namespace { @@ -41,33 +48,58 @@ ImportController::ImportController(const QSharedPointer &serversMo const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { +#ifdef Q_OS_ANDROID + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); +#endif } -void ImportController::extractConfigFromFile(const QUrl &fileUrl) +void ImportController::extractConfigFromFile() { - QFile file(fileUrl.toLocalFile()); + QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open config file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); - auto configFormat = checkConfigFormat(data); - if (configFormat == ConfigTypes::OpenVpn) { - m_config = extractOpenVpnConfig(data); - } else if (configFormat == ConfigTypes::WireGuard) { - m_config = extractWireGuardConfig(data); - } else { - m_config = extractAmneziaConfig(data); - } - + extractConfigFromData(data); m_configFileName = QFileInfo(file.fileName()).fileName(); } } +void ImportController::extractConfigFromData(QString &data) +{ + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + m_config = extractOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + m_config = extractWireGuardConfig(data); + } else { + m_config = extractAmneziaConfig(data); + } +} + void ImportController::extractConfigFromCode(QString code) { m_config = extractAmneziaConfig(code); m_configFileName = ""; } +void ImportController::extractConfigFromQr() +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); +#endif +} + QString ImportController::getConfig() { return QJsonDocument(m_config).toJson(QJsonDocument::Indented); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 561ea19c..273b12a5 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -3,10 +3,10 @@ #include -#include "core/defs.h" #include "containers/containers_defs.h" -#include "ui/models/servers_model.h" +#include "core/defs.h" #include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" class ImportController : public QObject { @@ -14,13 +14,14 @@ class ImportController : public QObject public: explicit ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); public slots: void importConfig(); - void extractConfigFromFile(const QUrl &fileUrl); + void extractConfigFromFile(); + void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); + void extractConfigFromQr(); QString getConfig(); QString getConfigFileName(); @@ -39,7 +40,6 @@ private: QJsonObject m_config; QString m_configFileName; - }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 84633b54..68e47a89 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,6 +8,34 @@ #include "core/servercontroller.h" #include "utilities.h" +namespace +{ +#ifdef Q_OS_WINDOWS + QString getNextDriverLetter() + { + QProcess drivesProc; + drivesProc.start("wmic logicaldisk get caption"); + drivesProc.waitForFinished(); + QString drives = drivesProc.readAll(); + qDebug() << drives; + + QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; + QString letter; + for (int i = letters.size() - 1; i > 0; i--) { + letter = letters.at(i); + if (!drives.contains(letter + ":")) + break; + } + if (letter == "C:") { + // set err info + qDebug() << "Can't find free drive letter"; + return ""; + } + return letter; + } +#endif +} + InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent) @@ -15,6 +43,17 @@ InstallController::InstallController(const QSharedPointer &servers { } +InstallController::~InstallController() +{ +#ifdef Q_OS_WINDOWS + for (QSharedPointer process : m_sftpMountProcesses) { + Utils::signalCtrl(process->processId(), CTRL_C_EVENT); + process->kill(); + process->waitForFinished(); + } +#endif +} + void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 8458b46d..fdd0ebed 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -16,6 +16,7 @@ public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent = nullptr); + ~InstallController(); public slots: void install(DockerContainer container, int port, TransportProto transportProto); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 4ee9b3bf..5abeb77f 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,7 +1,9 @@ #include "pageController.h" -PageController::PageController(const QSharedPointer &serversModel, - QObject *parent) : QObject(parent), m_serversModel(serversModel) +#include + +PageController::PageController(const QSharedPointer &serversModel, QObject *parent) + : QObject(parent), m_serversModel(serversModel) { } @@ -24,3 +26,24 @@ QString PageController::getPagePath(PageLoader::PageEnum page) QString pageName = metaEnum.valueToKey(static_cast(page)); return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; } + +void PageController::closeWindow() +{ +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_serversModel->getServersCount() == 0) { + qApp->quit(); + } else { + emit hideMainWindow(); + } +#endif +} + +void PageController::keyPressEvent(Qt::Key key) +{ + switch (key) { + case Qt::Key_Back: emit closePage(); + default: return; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index d79b100a..e8452b45 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -41,6 +41,7 @@ namespace PageLoader PageSetupWizardConfigSource, PageSetupWizardTextKey, PageSetupWizardViewConfig, + PageSetupWizardQrReader, PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, @@ -67,15 +68,25 @@ public slots: QString getInitialPage(); QString getPagePath(PageLoader::PageEnum page); + void closeWindow(); + void keyPressEvent(Qt::Key key); + signals: void goToPageHome(); void goToPageSettings(); + void goToPageViewConfig(); + void closePage(); + void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); + void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); + void showBusyIndicator(bool visible); - void raise(); + + void hideMainWindow(); + void raiseMainWindow(); private: QSharedPointer m_serversModel; diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp index a17f0159..4c68b549 100644 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ b/client/ui/pages_logic/ServerSettingsLogic.cpp @@ -6,20 +6,21 @@ #include "VpnLogic.h" #include "core/errorstrings.h" -#include #include +#include #if defined(Q_OS_ANDROID) -#include "../../platforms/android/androidutils.h" + #include "../../platforms/android/androidutils.h" #endif -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearClientCacheVisible{true}, - m_pushButtonShareFullVisible{true}, - m_pushButtonClearClientCacheText{tr("Clear client cached profile")} -{ } +ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_labelWaitInfoVisible { true }, + m_pushButtonClearClientCacheVisible { true }, + m_pushButtonShareFullVisible { true }, + m_pushButtonClearClientCacheText { tr("Clear client cached profile") } +{ +} void ServerSettingsLogic::onUpdatePage() { @@ -33,11 +34,11 @@ void ServerSettingsLogic::onUpdatePage() const QString &userName = server.value(config_key::userName).toString(); const QString &hostName = server.value(config_key::hostName).toString(); QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); + .arg(userName) + .arg(userName.isEmpty() ? "" : "@") + .arg(hostName) + .arg(port.isEmpty() ? "" : ":") + .arg(port); set_labelServerText(name); set_lineEditDescriptionText(server.value(config_key::description).toString()); @@ -49,15 +50,15 @@ void ServerSettingsLogic::onUpdatePage() void ServerSettingsLogic::onPushButtonForgetServer() { - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex + && uiLogic()->m_vpnConnection->isConnected()) { uiLogic()->pageLogic()->onDisconnect(); } m_settings->removeServer(uiLogic()->m_selectedServerIndex); if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(0); - } - else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { + } else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); } @@ -65,14 +66,12 @@ void ServerSettingsLogic::onPushButtonForgetServer() m_settings->setDefaultServer(-1); } - uiLogic()->m_selectedServerIndex = -1; uiLogic()->onUpdateAllPages(); if (m_settings->serversCount() == 0) { uiLogic()->setStartPage(Page::Start); - } - else { + } else { uiLogic()->closePage(); } } @@ -86,9 +85,7 @@ void ServerSettingsLogic::onPushButtonClearClientCacheClicked() m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); } - QTimer::singleShot(3000, this, [this]() { - set_pushButtonClearClientCacheText(tr("Clear client cached profile")); - }); + QTimer::singleShot(3000, this, [this]() { set_pushButtonClearClientCacheText(tr("Clear client cached profile")); }); } void ServerSettingsLogic::onLineEditDescriptionEditingFinished() @@ -111,7 +108,7 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul { qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - if (resultCode == -1) { //ResultOK + if (resultCode == -1) { // ResultOK uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } @@ -121,26 +118,27 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul void ServerSettingsLogic::onPushButtonShareFullClicked() { #if defined(Q_OS_ANDROID) -/* We use builtin keyguard for ssh key export protection on Android */ + /* We use builtin keyguard for ssh key export protection on Android */ QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); if (appContext.isValid()) { QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod( - "org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); if (intent.isValid()) { if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + QtAndroidPrivate::startActivity(intent.object(), 1, receiver); + } + } else { + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } } #else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); #endif } diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 12490810..454b5fa3 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -1,68 +1,69 @@ #include "StartPageLogic.h" #include "ViewConfigLogic.h" -#include "core/errorstrings.h" +#include "../uilogic.h" #include "configurators/ssh_configurator.h" #include "configurators/vpn_configurator.h" -#include "../uilogic.h" -#include "utilities.h" +#include "core/errorstrings.h" #include "core/servercontroller.h" +#include "utilities.h" +#include #include #include -#include #ifdef Q_OS_ANDROID -#include -#include "../../platforms/android/androidutils.h" -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include #endif -namespace { -enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard -}; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - if (config.contains(openVpnConfigPatternCli) && - (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && - (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) && - config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; -} + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) + return ConfigTypes::WireGuard; + return ConfigTypes::Amnezia; + } } -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectEnabled{true}, - m_pushButtonConnectText{tr("Connect")}, - m_pushButtonConnectKeyChecked{false}, - m_labelWaitInfoVisible{true}, - m_pushButtonBackFromStartVisible{true}, - m_ipAddressPortRegex{Utils::ipAddressPortRegExp()} +StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_pushButtonConnectEnabled { true }, + m_pushButtonConnectText { tr("Connect") }, + m_pushButtonConnectKeyChecked { false }, + m_labelWaitInfoVisible { true }, + m_pushButtonBackFromStartVisible { true }, + m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } { #ifdef Q_OS_ANDROID // Set security screen for Android app AndroidUtils::runOnAndroidThreadSync([]() { QJniObject activity = AndroidUtils::getActivity(); QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()){ + if (window.isValid()) { const int FLAG_SECURE = 8192; window.callMethod("addFlags", "(I)V", FLAG_SECURE); } @@ -89,17 +90,13 @@ void StartPageLogic::onUpdatePage() void StartPageLogic::onPushButtonConnect() { - if (pushButtonConnectKeyChecked()){ - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - textEditSshKeyText().isEmpty() ) { + if (pushButtonConnectKeyChecked()) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } } else { - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - lineEditPasswordText().isEmpty() ) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } @@ -174,7 +171,8 @@ void StartPageLogic::onPushButtonConnect() set_pushButtonConnectText(tr("Connect")); uiLogic()->m_installCredentials = serverCredentials; - if (ok) emit uiLogic()->goToPage(Page::NewServer); + if (ok) + emit uiLogic()->goToPage(Page::NewServer); } void StartPageLogic::onPushButtonImport() @@ -185,8 +183,10 @@ void StartPageLogic::onPushButtonImport() void StartPageLogic::onPushButtonImportOpenFile() { QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) return; + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + if (fileName.isEmpty()) + return; QFile file(fileName); file.open(QIODevice::ReadOnly); @@ -226,8 +226,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile) // check config uiLogic()->pageLogic()->set_configJson(profile); emit uiLogic()->goToPage(Page::ViewConfig); - } - else { + } else { qDebug() << "Failed to import profile"; qDebug().noquote() << QJsonDocument(profile).toJson(); return false; @@ -298,7 +297,6 @@ bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-openvpn"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); if (dnsMatch.hasNext()) { @@ -345,7 +343,9 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-wireguard"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); if (dnsMatch.hasMatch()) { o[config_key::dns1] = dnsMatch.captured(1); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 05df413c..692289a8 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -54,10 +54,10 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("Save connection code") + text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code") onClicked: { - ExportController.saveFile() + Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile() } } diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 6ce5ac4e..e4b1703f 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -26,7 +26,7 @@ CheckBox { hoverEnabled: true indicator: Rectangle { - id: checkBoxBackground + id: background anchors.verticalCenter: parent.verticalCenter @@ -57,7 +57,6 @@ CheckBox { radius: 4 Image { - id: indicator anchors.centerIn: parent source: root.pressed ? imageSource : root.checked ? imageSource : "" @@ -71,31 +70,38 @@ CheckBox { } } - contentItem: ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 8 + checkBoxBackground.width + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - spacing: 4 + anchors.fill: parent + anchors.leftMargin: 8 + background.width - ListItemTitleType { - Layout.fillWidth: true -// Layout.topMargin: 16 -// Layout.bottomMargin: description.visible ? 0 : 16 + ColumnLayout { + id: content - text: root.text - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - CaptionTextType { - id: description + spacing: 4 - Layout.fillWidth: true - Layout.bottomMargin: 16 + ListItemTitleType { + Layout.fillWidth: true - text: root.descriptionText - color: "#878b91" + text: root.text + } - visible: root.descriptionText !== "" + CaptionTextType { + id: description + + Layout.fillWidth: true + + text: root.descriptionText + color: "#878b91" + + visible: root.descriptionText !== "" + } } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 046201b7..296fcc8c 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -20,7 +20,6 @@ Item { if (root.stackView.depth <= 1) { return } - root.stackView.pop() } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index c833ca27..f44fa751 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -85,44 +85,50 @@ RadioButton { } } - contentItem: ColumnLayout { - id: content - anchors.left: parent.left - anchors.right: parent.right + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent anchors.leftMargin: 8 + background.width - spacing: 4 + ColumnLayout { + id: content - ListItemTitleType { - text: root.text + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - color: { - if (root.checked) { - return selectedTextColor + spacing: 4 + + ListItemTitleType { + text: root.text + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } + + Layout.fillWidth: true + + Behavior on color { + PropertyAnimation { duration: 200 } } - return textColor } - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 + CaptionTextType { + id: description - Behavior on color { - PropertyAnimation { duration: 200 } + color: "#878B91" + text: root.descriptionText + + visible: root.descriptionText !== "" + + Layout.fillWidth: true } } - - CaptionTextType { - id: description - - color: "#878B91" - text: root.descriptionText - - visible: root.descriptionText !== "" - - Layout.fillWidth: true - Layout.bottomMargin: 16 - } } MouseArea { diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 01b11bda..1875f085 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -43,8 +43,8 @@ PageType { Layout.topMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.preferredWidth: 344 - Layout.preferredHeight: 279 + Layout.preferredWidth: 291 + Layout.preferredHeight: 224 } Header2TextType { @@ -100,7 +100,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Show other methods on Github") - onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client#donate") } ParagraphTextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45e4c29c..78bd7486 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -61,16 +61,18 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - onClicked: fileDialog.open() +// onClicked: fileDialog.open() + ImportController.extractConfigFromFile() + goToPage(PageEnum.PageSetupWizardViewConfig) } - FileDialog { - id: fileDialog - onAccepted: { - ImportController.extractConfigFromFile(selectedFile) - goToPage(PageEnum.PageSetupWizardViewConfig) - } - } +// FileDialog { +// id: fileDialog +// onAccepted: { +// ImportController.extractConfigFromFile(selectedFile) +// goToPage(PageEnum.PageSetupWizardViewConfig) +// } +// } } DividerType {} @@ -84,6 +86,8 @@ PageType { leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { + ImportController.extractConfigFromQr() +// goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml new file mode 100644 index 00000000..65d233ef --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import QRCodeReader 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + ColumnLayout { + anchors.fill: parent + + spacing: 0 + + BackButtonType { + Layout.topMargin: 20 + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("Point the camera at the QR code and hold for a couple of seconds.") + } + + ProgressBarType { + + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + QRCodeReader { + id: qrCodeReader + Component.onCompleted: { + qrCodeReader.setCameraSize(Qt.rect(parent.x, + parent.y, + parent.width, + parent.height)) + qrCodeReader.startReading() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 78ccfa95..750084c7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -20,6 +20,10 @@ PageType { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() } + + function onGoToPageViewConfig() { + goToPage(PageEnum.PageSetupWizardViewConfig) + } } FlickableType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 903e5658..201c630a 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -18,7 +18,7 @@ PageType { enum ConfigType { AmneziaConnection, - AmenziaFullAccess, + AmneziaFullAccess, OpenVpn, WireGuard } @@ -33,7 +33,14 @@ PageType { switch (type) { case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; - case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; + case PageShare.ConfigType.AmneziaFullAccess: { + if (Qt.platform.os === "android") { + ExportController.generateFullAccessConfigAndroid(); + } else { + ExportController.generateFullAccessConfig(); + } + break; + } case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; } @@ -48,6 +55,8 @@ PageType { } } + property string fullConfigServerSelectorText + property string connectionServerSelectorText property bool showContent: false property bool shareButtonEnabled: false property list connectionTypesModel: [ @@ -118,6 +127,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 0 + serverSelector.text = root.connectionServerSelectorText } } @@ -129,6 +139,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 + serverSelector.text = root.fullConfigServerSelectorText } } } @@ -176,14 +187,24 @@ PageType { currentIndex: 0 clickedFunction: function() { - serverSelector.text = selectedText - ServersModel.currentlyProcessedIndex = currentIndex - protocolSelector.visible = true - root.shareButtonEnabled = false + handler() + + if (accessTypeSelector.currentIndex === 0) { + protocolSelector.visible = true + root.shareButtonEnabled = false + } else { + serverSelector.menuVisible = false + } } Component.onCompleted: { + handler() + } + + function handler() { serverSelector.text = selectedText + root.fullConfigServerSelectorText = selectedText + root.connectionServerSelectorText = selectedText ServersModel.currentlyProcessedIndex = currentIndex } } @@ -260,7 +281,9 @@ PageType { } Component.onCompleted: { - handler() + if (accessTypeSelector.currentIndex === 0) { + handler() + } } function handler() { @@ -272,6 +295,8 @@ PageType { } serverSelector.text += ", " + selectedText + root.connectionServerSelectorText = serverSelector.text + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 09bebaa1..5d49abf7 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -25,6 +25,11 @@ PageType { tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) } + function onGoToPageViewConfig() { + var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } + function onShowErrorMessage(errorMessage) { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() @@ -35,6 +40,13 @@ PageType { tabBarStackView.enabled = !visible tabBar.enabled = !visible } + + function onClosePage() { + if (tabBarStackView.depth <= 1) { + return + } + tabBarStackView.pop() + } } StackViewType { diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml deleted file mode 100644 index d33608f8..00000000 --- a/client/ui/qml/Pages2/PageTest.qml +++ /dev/null @@ -1,282 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 - -import "./" -import "../Controls2" -import "../Config" -import "../Controls2/TextTypes" - -Item { - id: root - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - HeaderType { - id: header - - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 20 - Layout.bottomMargin: 32 - Layout.fillWidth: true - - backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Server 1" - descriptionText: "root 192.168.111.111" - } - - Item { - Layout.fillWidth: true - - TabBar { - id: tabBar - - anchors { - top: parent.top - right: parent.right - left: parent.left - } - - background: Rectangle { - color: "transparent" - } - - TabButtonType { - id: bb - isSelected: tabBar.currentIndex === 0 - text: qsTr("Протоколы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 1 - text: qsTr("Сервисы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 2 - text: qsTr("Данные") - } - } - - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex - - anchors.top: tabBar.bottom - anchors.topMargin: 16 - - width: parent.width - height: root.height - header.implicitHeight - tabBar.implicitHeight - 100 - - Item { - id: protocolsTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: protocolsTabContent.height - - ColumnLayout { - id: protocolsTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - text: qsTr("Forget this server") - } - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("Forget this server") - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - headerText: "Server IP adress [:port]" - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "IP, логин и пароль от сервера" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - LabelWithButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "QR-код, ключ или файл настроек" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - DropDownType { - Layout.fillWidth: true - - text: "IP, логин и пароль от сервера" - descriptionText: "IP, логин и пароль от сервера" - - menuModel: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - } - } - } - } - - Item { - id: servicesTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: servicesTabContent.height - - ColumnLayout { - id: servicesTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - } - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - descriptionText: qsTr("Auto-negotiate encryption") - } - - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight - - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: "#1C1D21" - radius: 16 - - RowLayout { - id: buttonGroup - - spacing: 0 - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "UDP" - } - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "TCP" - } - } - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - descriptionText: "Позволяет подключаться к одним сайтам через защищенное соединение, а к другим в обход него" - checked: true - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - SwitcherType { - text: "Auto-negotiate encryption" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - } - } - } - } - } - } -} diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 8ad71d0f..d0f9880d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -18,10 +18,9 @@ Window { color: "#0E0E11" - // todo onClosing: function() { console.debug("QML onClosing signal") - UiLogic.onCloseWindow() + PageController.closeWindow() } title: "AmneziaVPN" @@ -36,6 +35,11 @@ Window { var pagePath = PageController.getInitialPage() rootStackView.push(pagePath, { "objectName" : pagePath }) } + + Keys.onPressed: function(event) { + PageController.keyPressEvent(event.key) + event.accepted = true + } } Connections { @@ -49,10 +53,14 @@ Window { rootStackView.replace(pagePath, { "objectName" : pagePath }) } - function onRaise() { + function onRaiseMainWindow() { root.show() root.raise() root.requestActivate() } + + function onHideMainWindow() { + root.hide() + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index cd02bf41..aefcc49c 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -10,79 +10,76 @@ #include #include #include +#include +#include +#include #include #include #include -#include -#include -#include #include "amnezia_application.h" #include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" #include "configurators/openvpn_configurator.h" #include "configurators/shadowsocks_configurator.h" #include "configurators/ssh_configurator.h" +#include "configurators/vpn_configurator.h" -#include "core/servercontroller.h" -#include "core/server_defs.h" #include "core/errorstrings.h" +#include "core/server_defs.h" +#include "core/servercontroller.h" #include "containers/containers_defs.h" #include "ui/qautostart.h" #include "logger.h" -#include "version.h" #include "uilogic.h" #include "utilities.h" +#include "version.h" #include "vpnconnection.h" #include #if defined Q_OS_MAC || defined Q_OS_LINUX -#include "ui/macos_util.h" + #include "ui/macos_util.h" #endif #ifdef Q_OS_ANDROID -#include "platforms/android/android_controller.h" + #include "platforms/android/android_controller.h" #endif #include "platforms/ios/MobileUtils.h" +#include "pages_logic/AdvancedServerSettingsLogic.h" #include "pages_logic/AppSettingsLogic.h" +#include "pages_logic/ClientInfoLogic.h" +#include "pages_logic/ClientManagementLogic.h" #include "pages_logic/GeneralSettingsLogic.h" #include "pages_logic/NetworkSettingsLogic.h" #include "pages_logic/NewServerProtocolsLogic.h" #include "pages_logic/QrDecoderLogic.h" #include "pages_logic/ServerConfiguringProgressLogic.h" +#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ServerListLogic.h" #include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ShareConnectionLogic.h" #include "pages_logic/SitesLogic.h" #include "pages_logic/StartPageLogic.h" #include "pages_logic/ViewConfigLogic.h" #include "pages_logic/VpnLogic.h" #include "pages_logic/WizardLogic.h" -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/ClientInfoLogic.h" #include "pages_logic/protocols/CloakLogic.h" #include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/OtherProtocolsLogic.h" +#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/WireGuardLogic.h" using namespace amnezia; using namespace PageEnumNS; -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - QObject *parent) : - QObject(parent), - m_settings(settings), - m_configurator(configurator) +UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator) { m_protocolsModel = new ProtocolsModel(settings, this); m_clientManagementModel = new ClientManagementModel(this); @@ -90,7 +87,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptrmoveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); @@ -99,7 +95,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr()->onConnectionStateChanged(Vpn::ConnectionState::Connected); - if (m_vpnConnection) m_vpnConnection->restoreConnection(); - } - }); - if (!AndroidController::instance()->initialize(pageLogic())) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); - return; - } + connect(AndroidController::instance(), &AndroidController::initialized, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); +// if (!AndroidController::instance()->initialize(pageLogic())) { +// qCritical() << QString("Init failed"); +// if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); +// return; +// } #endif m_notificationHandler = NotificationHandler::create(qmlRoot()); - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState); + connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, + &NotificationHandler::setConnectionState); connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); + connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), + &VpnLogic::onDisconnect); -// if (m_settings->serversCount() > 0) { -// if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); -// emit goToPage(Page::PageStart, true, false); -// } -// else { -// emit goToPage(Page::PageSetupWizardStart, true, false); -// } + // if (m_settings->serversCount() > 0) { + // if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); + // emit goToPage(Page::PageStart, true, false); + // } + // else { + // emit goToPage(Page::PageSetupWizardStart, true, false); + // } m_selectedServerIndex = m_settings->defaultServerIndex(); @@ -165,14 +164,13 @@ void UiLogic::initializeUiLogic() void UiLogic::showOnStartup() { - if (! m_settings->isStartMinimized()) { + if (!m_settings->isStartMinimized()) { emit show(); - } - else { + } else { #ifdef Q_OS_WIN emit hide(); #elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); + // TODO: fix: setDockIconVisible(false); #endif } } @@ -180,7 +178,7 @@ void UiLogic::showOnStartup() void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic)) { + if (dynamic_cast(logic) || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); @@ -191,57 +189,50 @@ void UiLogic::keyPressEvent(Qt::Key key) { switch (key) { case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); - break; - case Qt::Key_L: Logger::openLogsFolder(); - break; - case Qt::Key_K: Logger::openServiceLogsFolder(); - break; + case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; + case Qt::Key_L: Logger::openLogsFolder(); break; + case Qt::Key_K: Logger::openServiceLogsFolder(); break; #ifdef QT_DEBUG - case Qt::Key_Q: - qApp->quit(); - break; + case Qt::Key_Q: qApp->quit(); break; case Qt::Key_H: m_selectedServerIndex = m_settings->defaultServerIndex(); m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); + // updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); emit goToPage(Page::ShareConnection); break; #endif case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() << m_settings->defaultContainerName(m_settings->defaultServerIndex()); + qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() + << m_settings->defaultContainerName(m_settings->defaultServerIndex()); qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); break; - case Qt::Key_A: - emit goToPage(Page::Start); - break; + case Qt::Key_A: emit goToPage(Page::Start); break; case Qt::Key_S: m_selectedServerIndex = m_settings->defaultServerIndex(); emit goToPage(Page::ServerSettings); break; - case Qt::Key_P: - onGotoCurrentProtocolsPage(); - break; + case Qt::Key_P: onGotoCurrentProtocolsPage(); break; case Qt::Key_T: m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: - if (currentPage() == Page::Vpn) break; - if (currentPage() == Page::ServerConfiguringProgress) break; + if (currentPage() == Page::Vpn) + break; + if (currentPage() == Page::ServerConfiguringProgress) + break; case Qt::Key_Back: -// if (currentPage() == Page::Start && pagesStack.size() < 2) break; -// if (currentPage() == Page::Sites && -// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { -// ui->tableView_sites->clearSelection(); -// break; -// } + // if (currentPage() == Page::Start && pagesStack.size() < 2) break; + // if (currentPage() == Page::Sites && + // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { + // ui->tableView_sites->clearSelection(); + // break; + // } - emit closePage(); + emit closePage(); //} - default: - ; + default:; } } @@ -250,8 +241,7 @@ void UiLogic::onCloseWindow() #ifdef Q_OS_ANDROID qApp->quit(); #else - if (m_settings->serversCount() == 0) - { + if (m_settings->serversCount() == 0) { qApp->quit(); } else { emit hide(); @@ -267,7 +257,6 @@ QString UiLogic::containerName(int container) QString UiLogic::containerDesc(int container) { return ContainerProps::containerDescriptions().value(static_cast(container)); - } void UiLogic::onGotoCurrentProtocolsPage() @@ -286,50 +275,50 @@ void UiLogic::installServer(QPair &container) qApp->processEvents(); ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { + pageFunc.setEnabledFunc = [this](bool enabled) -> void { pageLogic()->set_pageEnabled(enabled); }; ServerConfiguringProgressLogic::ButtonFunc noButton; ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { + waitInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelWaitInfoText(text); }; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + waitInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelWaitInfoVisible(visible); }; ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarVisible(visible); }; - progressBarFunc.setValueFunc = [this] (int value) -> void { + progressBarFunc.setValueFunc = [this](int value) -> void { pageLogic()->set_progressBarValue(value); }; - progressBarFunc.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this](void) -> int { return pageLogic()->progressBarValue(); }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { + progressBarFunc.getMaximumFunc = [this](void) -> int { return pageLogic()->progressBarMaximum(); }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setTextVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarTextVisible(visible); }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + progressBarFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_progressBarText(text); }; ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { + busyInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelServerBusyText(text); }; - busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + busyInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelServerBusyVisible(visible); }; ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + cancelButtonFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_pushButtonCancelVisible(visible); }; @@ -338,13 +327,12 @@ void UiLogic::installServer(QPair &container) if (errorCode == ErrorCode::NoError) { if (!isContainerAlreadyAddedToGui(container.first)) { progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&] () { + auto installAction = [&]() { ServerController serverController(m_settings); return serverController.setupContainer(m_installCredentials, container.first, container.second); }; - errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - noButton, waitInfoFunc, - busyInfoFunc, cancelButtonFunc); + errorCode = pageLogic()->doInstallAction( + installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); if (errorCode == ErrorCode::NoError) { if (!isServerCreated) { QJsonObject server; @@ -354,7 +342,7 @@ void UiLogic::installServer(QPair &container) server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); - server.insert(config_key::containers, QJsonArray{container.second}); + server.insert(config_key::containers, QJsonArray { container.second }); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); m_settings->addServer(server); @@ -371,22 +359,23 @@ void UiLogic::installServer(QPair &container) } } else { onUpdateAllPages(); - emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); + emit showWarningMessage( + "Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); emit setStartPage(Page::Vpn); return; } } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); + emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + + errorString(errorCode) + "\n" + tr("See logs for details.")); emit closePage(); } PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) { PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) return logic; + if (logic) + return logic; else { qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; return new PageProtocolLogicBase(this); @@ -418,7 +407,7 @@ PageEnumNS::Page UiLogic::currentPage() return static_cast(currentPageValue()); } -void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QString ext, const QString& data) +void UiLogic::saveTextFile(const QString &desc, const QString &suggestedName, QString ext, const QString &data) { #ifdef Q_OS_IOS shareTempFile(suggestedName, ext, data); @@ -429,17 +418,19 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; #ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, - QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext); + fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); + if (fileName.isEmpty()) + return; + if (!fileName.toString().endsWith(ext)) + fileName = QUrl(fileName.toString() + ext); #elif defined Q_OS_ANDROID qDebug() << "UiLogic::shareConfig" << data; AndroidController::instance()->shareConfig(data, suggestedName); return; #endif - if (fileName.isEmpty()) return; + if (fileName.isEmpty()) + return; #ifdef AMNEZIA_DESKTOP QFile save(fileName.toLocalFile()); @@ -458,11 +449,13 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) { ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName(nullptr, desc, - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); + QString fileName = QFileDialog::getSaveFileName( + nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile save(fileName); save.open(QIODevice::WriteOnly); @@ -478,12 +471,15 @@ void UiLogic::copyToClipboard(const QString &text) qApp->clipboard()->setText(text); } -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { +void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString &data) +{ ext.replace("*", ""); QString fileName = QDir::tempPath() + "/" + suggestedName; - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile::remove(fileName); @@ -497,14 +493,14 @@ void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QSt MobileUtils::shareText(filesToSend); } -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, QFileDialog::Options options) +QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) { QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); #ifdef Q_OS_ANDROID // patch for files containing spaces etc - const QString sep {"raw%3A%2F"}; + const QString sep { "raw%3A%2F" }; if (fileName.startsWith("content://") && fileName.contains(sep)) { QString contentUrl = fileName.split(sep).at(0); QString rawUrl = fileName.split(sep).at(1); @@ -590,7 +586,8 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) } if (createNewServer) { - server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); + server.insert(config_key::defaultContainer, + ContainerProps::containerToString(installedContainers.firstKey())); m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); isServerCreated = true;