From 2a03834bb240d78944d77f01b7cf40e5c6b8eb54 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 9 Jan 2024 00:25:18 +0700 Subject: [PATCH 01/20] fixed current processed server selection after import/install new server --- client/ui/models/servers_model.cpp | 5 +++++ .../ui/qml/Components/HomeContainersListView.qml | 16 ++++++++++++++++ client/ui/qml/Pages2/PageHome.qml | 1 + .../ui/qml/Pages2/PageSetupWizardInstalling.qml | 1 + .../ui/qml/Pages2/PageSetupWizardViewConfig.qml | 1 + 5 files changed, 24 insertions(+) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b8bfbbc5..feaf1e8d 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -15,6 +15,10 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); emit ServersModel::defaultContainerChanged(defaultContainer); }); + connect(this, &ServersModel::currentlyProcessedServerIndexChanged, this, [this](const int serverIndex) { + auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); + emit ServersModel::defaultContainerChanged(defaultContainer); + }); } int ServersModel::rowCount(const QModelIndex &parent) const @@ -269,6 +273,7 @@ void ServersModel::removeServer() if (m_settings->serversCount() == 0) { setDefaultServerIndex(-1); } + setCurrentlyProcessedServerIndex(m_defaultServerIndex); endResetModel(); } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 78ea9330..2c07ca65 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -26,6 +26,22 @@ ListView { id: containersRadioButtonGroup } + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + menuContent.checkCurrentItem() + } + } + + function checkCurrentItem() { + var item = menuContent.itemAtIndex(currentIndex) + if (item !== null) { + var radioButton = item.children[0].children[0] + radioButton.checked = true + } + } + delegate: Item { implicitWidth: rootWidth implicitHeight: content.implicitHeight diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8374dbc3..5b44bc7c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -31,6 +31,7 @@ PageType { containersDropDown.rootButtonClickedFunction() } } + function onForceCloseDrawer() { buttonContent.state = "collapsed" } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 9811d87d..1a3e7c07 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -42,6 +42,7 @@ PageType { function onInstallServerFinished(finishedMessage) { if (!ConnectionController.isConnected) { ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex } PageController.goToStartPage() diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 65a6f319..6df26fc0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -26,6 +26,7 @@ PageType { function onImportFinished() { if (!ConnectionController.isConnected) { ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex } PageController.goToStartPage() From d527f6a5ce245b2c7310cedb5f73754ba43528c5 Mon Sep 17 00:00:00 2001 From: Morteza Sherafati Date: Mon, 15 Jan 2024 20:18:10 +0000 Subject: [PATCH 02/20] show installed protocols first in protocols tab --- client/ui/qml/Pages2/PageSettingsServerProtocols.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 21401bf5..9fc2abde 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -55,6 +55,9 @@ PageType { model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel + sorters: [ + RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder } + ] } Component.onCompleted: updateContainersModelFilters() From 60aeefe1c209624b339069a29ac192dae86b68ca Mon Sep 17 00:00:00 2001 From: albexk Date: Tue, 16 Jan 2024 15:06:34 +0300 Subject: [PATCH 03/20] Restore previous Android keyboard behaviour There is a Qt bug related to this behaviour: https://bugreports.qt.io/browse/QTBUG-41170 --- client/android/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index cf237d07..960dc87d 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -38,6 +38,7 @@ android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" android:launchMode="singleInstance" + android:windowSoftInputMode="adjustResize" android:exported="true"> From fac57ac89a0cc1efa81f6a3d77ca92e54b9141e5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 17 Jan 2024 00:34:23 +0700 Subject: [PATCH 04/20] for servers received via api, ignore the split tunneling settings --- client/configurators/openvpn_configurator.cpp | 40 ++++++++++--------- client/configurators/openvpn_configurator.h | 2 +- client/configurators/vpn_configurator.cpp | 2 +- client/ui/models/servers_model.cpp | 5 +++ client/ui/models/servers_model.h | 2 + .../qml/Pages2/PageSettingsSplitTunneling.qml | 8 +++- client/vpnconnection.cpp | 33 ++++++++------- 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index e3362236..8b201fbf 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -118,31 +118,33 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia return QJsonDocument(jConfig).toJson(); } -QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) +QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex) { QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); QString config = json[config_key::config].toString(); - QRegularExpression regex("redirect-gateway.*"); - config.replace(regex, ""); + if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) { + QRegularExpression regex("redirect-gateway.*"); + config.replace(regex, ""); - if (m_settings->routeMode() == Settings::VpnAllSites) { - config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); - // Prevent ipv6 leak - config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); - config.append("block-ipv6\n"); - } - if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { + if (m_settings->routeMode() == Settings::VpnAllSites) { + config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); + // Prevent ipv6 leak + config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); + config.append("block-ipv6\n"); + } + if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - // no redirect-gateway - } - if (m_settings->routeMode() == Settings::VpnAllExceptSites) { -#ifndef Q_OS_ANDROID - config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); -#endif - // Prevent ipv6 leak - config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); - config.append("block-ipv6\n"); + // no redirect-gateway + } + if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + #ifndef Q_OS_ANDROID + config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); + #endif + // Prevent ipv6 leak + config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); + config.append("block-ipv6\n"); + } } #ifndef MZ_WINDOWS diff --git a/client/configurators/openvpn_configurator.h b/client/configurators/openvpn_configurator.h index cc66d13f..424a20e1 100644 --- a/client/configurators/openvpn_configurator.h +++ b/client/configurators/openvpn_configurator.h @@ -26,7 +26,7 @@ public: QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); - QString processConfigWithLocalSettings(QString jsonConfig); + QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex); QString processConfigWithExportSettings(QString jsonConfig); ErrorCode signCert(DockerContainer container, diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 3018b52f..c74a3d4f 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -92,7 +92,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker processConfigWithDnsSettings(serverIndex, container, proto, config); if (proto == Proto::OpenVpn) { - config = openVpnConfigurator->processConfigWithLocalSettings(config); + config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex); } return config; } diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index feaf1e8d..1922e188 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -531,3 +531,8 @@ void ServersModel::toggleAmneziaDns(bool enabled) emit defaultServerDescriptionChanged(); } +bool ServersModel::isDefaultServerFromApi() +{ + return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt(); +} + diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index af88febb..38f2bdd4 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -97,6 +97,8 @@ public slots: void toggleAmneziaDns(bool enabled); + bool isDefaultServerFromApi(); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 873ae997..9ad88524 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -21,7 +21,13 @@ PageType { id: root property bool pageEnabled: { - return !ConnectionController.isConnected + return !ConnectionController.isConnected && !ServersModel.isDefaultServerFromApi() + } + + Component.onCompleted: { + if (ServersModel.isDefaultServerFromApi()) { + PageController.showNotificationMessage(qsTr("This server does not support split tunneling function")) + } } Connections { diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5d4d5f99..75483d89 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -64,24 +64,26 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); - if (m_settings->routeMode() != Settings::VpnAllSites) { - IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); - } - QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); - QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); + if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) { + if (m_settings->routeMode() != Settings::VpnAllSites) { + IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); + // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); + } + QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); + QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); - if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - QTimer::singleShot(1000, m_vpnProtocol.data(), - [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); - } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); + if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { + QTimer::singleShot(1000, m_vpnProtocol.data(), + [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); - addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); + IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); + addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); + } } } else if (state == Vpn::ConnectionState::Error) { @@ -296,6 +298,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerC vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString(); vpnConfiguration[config_key::description] = server.value(config_key::description).toString(); + vpnConfiguration[config_key::configVersion] = server.value(config_key::configVersion).toInt(); // TODO: try to get hostName, port, description for 3rd party configs // vpnConfiguration[config_key::port] = ...; From 301141c7557a85f6faddec2ba73a94934de3879c Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 17 Jan 2024 17:43:54 +0300 Subject: [PATCH 05/20] Remove Android TextField workaround It has been fixed in Qt 6.5.3, 6.6.0 --- client/amnezia_application.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index fe360818..72630f06 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -167,20 +167,6 @@ void AmneziaApplication::init() }); } #endif - -// Android TextField clipboard workaround -// https://bugreports.qt.io/browse/QTBUG-113461 -#ifdef Q_OS_ANDROID - QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) { - if (state == Qt::ApplicationActive) { - if (qApp->clipboard()->mimeData()->formats().contains("text/html")) { - QTextDocument doc; - doc.setHtml(qApp->clipboard()->mimeData()->html()); - qApp->clipboard()->setText(doc.toPlainText()); - } - } - }); -#endif } void AmneziaApplication::registerTypes() From 2552e33d6470d9365f34b92db3c07b0bf10a8ddd Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 17 Jan 2024 17:46:54 +0300 Subject: [PATCH 06/20] Add Android TextArea clipboard workaround --- client/amnezia_application.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 72630f06..0590b6d1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -167,6 +167,23 @@ void AmneziaApplication::init() }); } #endif + +// Android TextArea clipboard workaround +// Text from TextArea always has "text/html" mime-type: +// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865 +// Next, html is created for this mime-type: +// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885 +// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText: +// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46 +// So we catch all the copies to the clipboard and clear them from "text/html" +#ifdef Q_OS_ANDROID + connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { + auto clipboard = QGuiApplication::clipboard(); + if (clipboard->mimeData()->hasHtml()) { + clipboard->setText(clipboard->text()); + } + }); +#endif } void AmneziaApplication::registerTypes() From 33e229d0b2fdc2f027e2062a409e99d853e49750 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 17 Jan 2024 23:38:20 +0700 Subject: [PATCH 07/20] added blocking of the page with dns settings for servers received by API --- client/ui/qml/Pages2/PageSettingsDns.qml | 8 ++++++++ client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 5670464f..1970da52 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -28,6 +28,14 @@ PageType { anchors.bottom: parent.bottom contentHeight: content.height + enabled: !ServersModel.isDefaultServerFromApi() + + Component.onCompleted: { + if (ServersModel.isDefaultServerFromApi()) { + PageController.showNotificationMessage(qsTr("Default server does not support custom dns")) + } + } + ColumnLayout { id: content diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 9ad88524..4300d591 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -26,7 +26,7 @@ PageType { Component.onCompleted: { if (ServersModel.isDefaultServerFromApi()) { - PageController.showNotificationMessage(qsTr("This server does not support split tunneling function")) + PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) } } From f576eb509dc16ece9af8836187397e2bc3a08d7a Mon Sep 17 00:00:00 2001 From: Igor Sorokin Date: Thu, 18 Jan 2024 15:25:36 +0300 Subject: [PATCH 08/20] Fix IPv6 problem --- client/3rd/awg-apple | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple index 233eda67..09c0fc7f 160000 --- a/client/3rd/awg-apple +++ b/client/3rd/awg-apple @@ -1 +1 @@ -Subproject commit 233eda6760962efddc860f177a0ce2bcdf533d85 +Subproject commit 09c0fc7f70bdf68f6953ea959d8a18952603fe94 From ad21d7ab649bde4b40fb45bc407ea3348f41204e Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 17 Jan 2024 21:24:42 +0300 Subject: [PATCH 09/20] Add Android App Bundle build and upload Up upload-artifact action to version 4 --- .github/workflows/deploy.yml | 38 ++++++++++++++++++++++++------------ deploy/build_android.sh | 29 ++++++++++++++++++--------- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a218a1ad..99db8d8f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -49,13 +49,13 @@ jobs: bash deploy/build_linux.sh - name: 'Upload installer artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_Linux_installer path: deploy/AmneziaVPN_Linux_Installer retention-days: 7 - name: 'Upload unpacked artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_Linux_unpacked path: deploy/AppDir @@ -110,13 +110,13 @@ jobs: call deploy\\build_windows.bat - name: 'Upload installer artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_Windows_installer path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe retention-days: 7 - name: 'Upload unpacked artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_Windows_unpacked path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release @@ -200,7 +200,7 @@ jobs: IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }} # - name: 'Upload appstore .ipa and dSYMs to artifacts' -# uses: actions/upload-artifact@v3 +# uses: actions/upload-artifact@v4 # with: # name: app-store ipa & dsyms # path: | @@ -255,13 +255,13 @@ jobs: bash deploy/build_macos.sh - name: 'Upload installer artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_installer path: AmneziaVPN.dmg retention-days: 7 - name: 'Upload unpacked artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_unpacked path: deploy/build/client/AmneziaVPN.app @@ -277,6 +277,7 @@ jobs: ANDROID_BUILD_PLATFORM: android-34 QT_VERSION: 6.6.1 QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + BUILD_AAB: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }} steps: - name: 'Install desktop Qt' @@ -375,32 +376,45 @@ jobs: ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} shell: bash - run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} + run: ./deploy/build_android.sh ${{ env.BUILD_AAB == 'true' && '--aab' || '' }} --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} - name: 'Upload x86_64 apk' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN-android-x86_64 path: deploy/build/AmneziaVPN-x86_64-release.apk + compression-level: 0 retention-days: 7 - name: 'Upload x86 apk' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN-android-x86 path: deploy/build/AmneziaVPN-x86-release.apk + compression-level: 0 retention-days: 7 - name: 'Upload arm64-v8a apk' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN-android-arm64-v8a path: deploy/build/AmneziaVPN-arm64-v8a-release.apk + compression-level: 0 retention-days: 7 - name: 'Upload armeabi-v7a apk' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN-android-armeabi-v7a path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk + compression-level: 0 + retention-days: 7 + + - name: 'Upload aab' + if: ${{ env.BUILD_AAB == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: AmneziaVPN-android + path: deploy/build/AmneziaVPN-release.aab + compression-level: 0 retention-days: 7 diff --git a/deploy/build_android.sh b/deploy/build_android.sh index 0c5a80c8..151cabc2 100755 --- a/deploy/build_android.sh +++ b/deploy/build_android.sh @@ -7,15 +7,18 @@ usage() { cat < -Build AmneziaVPN android client. By default, a signed Android App Bundle (AAB) is built. +Build AmneziaVPN android client. -Options: - -d, --debug Build debug version +Artifact types: + -u, --aab Build Android App Bundle (AAB) -a, --apk ( | all) Build APKs for the specified ABIs or for all available ABIs Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - list of ABIs delimited by ';' + +Options: + -d, --debug Build debug version -b, --build-platform The SDK platform used for building the Java code of the application By default, the latest available platform is used -m, --move Move the build result to the root of the build directory @@ -25,14 +28,14 @@ EOT } BUILD_TYPE="release" -AAB=1 -opts=$(getopt -l debug,apk:,build-platform:,move,help -o "da:b:mh" -- "$@") +opts=$(getopt -l debug,aab,apk:,build-platform:,move,help -o "dua:b:mh" -- "$@") eval set -- "$opts" while true; do case "$1" in -d | --debug) BUILD_TYPE="debug"; shift;; - -a | --apk) ABIS=$2; unset AAB; shift 2;; + -u | --aab) AAB=1; shift;; + -a | --apk) ABIS=$2; shift 2;; -b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;; -m | --move) MOVE_RESULT=1; shift;; -h | --help) usage; exit 0;; @@ -49,6 +52,11 @@ if [[ -v ABIS && \ exit 1 fi +# At least one artifact type must be specified +if [[ ! (-v AAB || -v ABIS) ]]; then + usage; exit 0 +fi + echo "Build script started..." PROJECT_DIR=$(pwd) @@ -137,7 +145,8 @@ gradle_opts=() if [ -v AAB ]; then gradle_opts+=(bundle"${BUILD_TYPE^}") -else +fi +if [ -v ABIS ]; then gradle_opts+=(assemble"${BUILD_TYPE^}") fi @@ -151,7 +160,9 @@ if [[ -v CI || -v MOVE_RESULT ]]; then if [ -v AAB ]; then mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/AmneziaVPN-$BUILD_TYPE.aab \ $PROJECT_DIR/deploy/build/ - else + fi + + if [ -v ABIS ]; then if [ "$ABIS" = "all" ]; then ABIS="x86;x86_64;armeabi-v7a;arm64-v8a" fi From d7fbddb97f7c52ab8f0baa626d7ca5f9e839a67f Mon Sep 17 00:00:00 2001 From: Igor Sorokin Date: Thu, 18 Jan 2024 17:13:04 +0300 Subject: [PATCH 10/20] Update README.md --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94dffe09..21b01865 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ AmneziaVPN uses a number of open source projects to work: Make sure to pull all submodules after checking out the repo. ```bash -git submodule update --init +git submodule update --init --recursive ``` ## Development @@ -50,7 +50,15 @@ Look deploy folder for build scripts. 1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher. -2. We use QT to generate the XCode project. we need QT version 6.4. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) +2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules: + - macOS + - iOS + - Qt 5 Compatibility Module + - Qt Shader Tools + - Additional Libraries: + - Qt Image Formats + - Qt Multimedia + - Qt Remote Objects 3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/) @@ -66,10 +74,11 @@ gomobile init 5. Build project ```bash export QT_BIN_DIR="/Qt//ios/bin" +export QT_MACOS_ROOT_DIR="/Qt//macos" export QT_IOS_BIN=$QT_BIN_DIR export PATH=$PATH:~/go/bin mkdir build-ios -$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_BIN_DIR +$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR ``` Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment From eec81f8124ee8d655115cc7d43bc1d31bab9b078 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 18 Jan 2024 21:29:56 +0700 Subject: [PATCH 11/20] fixed append/rename/revoke client for cloak and ss on client management panel --- client/ui/models/clientManagementModel.cpp | 36 +++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 5c89382b..0b1be2cc 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -245,8 +245,13 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); ServerController serverController(m_settings); - const QString clientsTableFile = - QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks + || container == DockerContainer::Cloak) { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); + } error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { @@ -273,8 +278,13 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); ServerController serverController(m_settings); - const QString clientsTableFile = - QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks + || container == DockerContainer::Cloak) { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); + } ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); @@ -345,8 +355,13 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - const QString clientsTableFile = - QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks + || container == DockerContainer::Cloak) { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); + } error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { logger.error() << "Failed to upload the clientsTable file to the server"; @@ -395,8 +410,13 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); - const QString clientsTableFile = - QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container)); + QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); + if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks + || container == DockerContainer::Cloak) { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); + } else { + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); + } error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { logger.error() << "Failed to upload the clientsTable file to the server"; From 9371dd405e9fc78f634c5e8210bcf885293d9760 Mon Sep 17 00:00:00 2001 From: tiaga Date: Fri, 19 Jan 2024 00:16:19 +0700 Subject: [PATCH 12/20] Improve Linux installer Pack installer to a .tar archive in order to save executable bit for `AmneziaVPN_Linux_Installer.bin`. --- .github/workflows/deploy.yml | 10 ++++++++-- deploy/build_linux.sh | 4 +--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 99db8d8f..cd52e69d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,12 +48,16 @@ jobs: export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin bash deploy/build_linux.sh + - name: 'Pack installer' + run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin + - name: 'Upload installer artifact' uses: actions/upload-artifact@v4 with: - name: AmneziaVPN_Linux_installer - path: deploy/AmneziaVPN_Linux_Installer + name: AmneziaVPN_Linux_installer.tar + path: deploy/AmneziaVPN_Linux_Installer.tar retention-days: 7 + - name: 'Upload unpacked artifact' uses: actions/upload-artifact@v4 with: @@ -115,6 +119,7 @@ jobs: name: AmneziaVPN_Windows_installer path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe retention-days: 7 + - name: 'Upload unpacked artifact' uses: actions/upload-artifact@v4 with: @@ -260,6 +265,7 @@ jobs: name: AmneziaVPN_MacOS_installer path: AmneziaVPN.dmg retention-days: 7 + - name: 'Upload unpacked artifact' uses: actions/upload-artifact@v4 with: diff --git a/deploy/build_linux.sh b/deploy/build_linux.sh index 1b9c698a..c90e781a 100755 --- a/deploy/build_linux.sh +++ b/deploy/build_linux.sh @@ -83,6 +83,4 @@ ldd $CQTDEPLOYER_DIR/bin/binarycreator cp -r $PROJECT_DIR/deploy/installer $BUILD_DIR -$CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer - - +$CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer.bin From 51070635a5b1c91f4cc8a368af58f0c0396fd1ab Mon Sep 17 00:00:00 2001 From: Igor Sorokin Date: Fri, 19 Jan 2024 03:15:27 +0300 Subject: [PATCH 13/20] Change to amneziawg-apple --- .gitmodules | 6 +++--- client/3rd/amneziawg-apple | 1 + client/3rd/awg-apple | 1 - client/cmake/ios.cmake | 2 +- client/ios/networkextension/CMakeLists.txt | 2 +- .../WireGuardNetworkExtension-Bridging-Header.h | 4 ++-- client/platforms/ios/WireGuard-Bridging-Header.h | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) create mode 160000 client/3rd/amneziawg-apple delete mode 160000 client/3rd/awg-apple diff --git a/.gitmodules b/.gitmodules index c96dd6bc..ff50f897 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,6 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt -[submodule "client/3rd/awg-apple"] - path = client/3rd/awg-apple - url = https://github.com/amnezia-vpn/awg-apple +[submodule "client/3rd/amneziawg-apple"] + path = client/3rd/amneziawg-apple + url = https://github.com/amnezia-vpn/amneziawg-apple diff --git a/client/3rd/amneziawg-apple b/client/3rd/amneziawg-apple new file mode 160000 index 00000000..f23eee47 --- /dev/null +++ b/client/3rd/amneziawg-apple @@ -0,0 +1 @@ +Subproject commit f23eee4700ed4a2ef44a800d2c20466c9ab0222b diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple deleted file mode 160000 index 09c0fc7f..00000000 --- a/client/3rd/awg-apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 09c0fc7f70bdf68f6953ea959d8a18952603fe94 diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 7aa9f1a9..3234578e 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" ) -set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 16769ea3..fb1bd3c1 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) -set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources) target_sources(networkextension PRIVATE ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift diff --git a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 44d0b6b0..2cca0fc8 100644 --- a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -1,6 +1,6 @@ #include "wireguard-go-version.h" -#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h" -#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/platforms/ios/WireGuard-Bridging-Header.h b/client/platforms/ios/WireGuard-Bridging-Header.h index fbccb2d4..0183367b 100644 --- a/client/platforms/ios/WireGuard-Bridging-Header.h +++ b/client/platforms/ios/WireGuard-Bridging-Header.h @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include From b8b423ca19da4c107487f57a5712dedd920c9057 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 20 Jan 2024 16:02:35 +0300 Subject: [PATCH 14/20] Fix amnezia_application line-ending --- client/amnezia_application.cpp | 766 ++++++++++++++++----------------- client/amnezia_application.h | 254 +++++------ 2 files changed, 510 insertions(+), 510 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 0590b6d1..28b028ff 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -1,383 +1,383 @@ -#include "amnezia_application.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "logger.h" -#include "version.h" - -#include "platforms/ios/QRCodeReaderBase.h" -#if defined(Q_OS_ANDROID) - #include "platforms/android/android_controller.h" -#endif - -#include "protocols/qml_register_protocols.h" - -#if defined(Q_OS_IOS) - #include "platforms/ios/ios_controller.h" -#endif - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) -AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) -#else -AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, - int timeout, const QString &userData) - : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) -#endif -{ - setQuitOnLastWindowClosed(false); - - // Fix config file permissions -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - { - QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); - s.setValue("permFixed", true); - } - - QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" - + ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf"; - QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner); - - QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" - + ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf"; - QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner); -#endif - - m_settings = std::shared_ptr(new Settings); -} - -AmneziaApplication::~AmneziaApplication() -{ - m_vpnConnectionThread.quit(); - m_vpnConnectionThread.wait(3000); - - if (m_engine) { - QObject::disconnect(m_engine, 0, 0, 0); - delete m_engine; - } -} - -void AmneziaApplication::init() -{ - m_engine = new QQmlApplicationEngine; - - const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); - QObject::connect( - m_engine, &QQmlApplicationEngine::objectCreated, this, - [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, - Qt::QueuedConnection); - - m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); - - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); - m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); - m_vpnConnection->moveToThread(&m_vpnConnectionThread); - m_vpnConnectionThread.start(); - - initModels(); - loadTranslator(); - initControllers(); - -#ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::initConnectionState, this, - [this](Vpn::ConnectionState state) { - m_connectionController->onConnectionStateChanged(state); - 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, [this](QString data) { - m_pageController->replaceStartPage(); - m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); - }); -#endif - -#ifdef Q_OS_IOS - IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { - m_pageController->replaceStartPage(); - m_importController->extractConfigFromData(data); - m_pageController->goToPageViewConfig(); - }); - - connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { - m_pageController->replaceStartPage(); - m_pageController->goToPageSettingsBackup(); - m_settingsController->importBackupFromOutside(filePath); - }); -#endif - - m_notificationHandler.reset(NotificationHandler::create(nullptr)); - - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), - &NotificationHandler::setConnectionState); - - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), - &PageController::raiseMainWindow); - connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), - &ConnectionController::openConnection); - connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), - &ConnectionController::closeConnection); - connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), - &NotificationHandler::onTranslationsUpdated); - - m_engine->load(url); - m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); - - if (m_settings->isSaveLogs()) { - if (!Logger::init()) { - qWarning() << "Initialization of debug subsystem failed"; - } - } - -#ifdef Q_OS_WIN - if (m_parser.isSet("a")) - m_pageController->showOnStartup(); - else - emit m_pageController->raiseMainWindow(); -#else - m_pageController->showOnStartup(); -#endif - - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { - qDebug() << "Secondary instance started, showing this window instead"; - emit m_pageController->raiseMainWindow(); - }); - } -#endif - -// Android TextArea clipboard workaround -// Text from TextArea always has "text/html" mime-type: -// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865 -// Next, html is created for this mime-type: -// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885 -// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText: -// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46 -// So we catch all the copies to the clipboard and clear them from "text/html" -#ifdef Q_OS_ANDROID - connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { - auto clipboard = QGuiApplication::clipboard(); - if (clipboard->mimeData()->hasHtml()) { - clipboard->setText(clipboard->text()); - } - }); -#endif -} - -void AmneziaApplication::registerTypes() -{ - qRegisterMetaType("ServerCredentials"); - - qRegisterMetaType("DockerContainer"); - qRegisterMetaType("TransportProto"); - qRegisterMetaType("Proto"); - qRegisterMetaType("ServiceType"); - - declareQmlProtocolEnum(); - declareQmlContainerEnum(); - - qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); - - m_containerProps.reset(new ContainerProps()); - qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); - - m_protocolProps.reset(new ProtocolProps()); - qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get()); - - qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, - "ContainersModelFilters"); - - // - Vpn::declareQmlVpnConnectionStateEnum(); - PageLoader::declareQmlPageEnum(); -} - -void AmneziaApplication::loadFonts() -{ - QQuickStyle::setStyle("Basic"); - - QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); -} - -void AmneziaApplication::loadTranslator() -{ - auto locale = m_settings->getAppLanguage(); - m_translator.reset(new QTranslator()); - updateTranslator(locale); -} - -void AmneziaApplication::updateTranslator(const QLocale &locale) -{ - if (!m_translator->isEmpty()) { - QCoreApplication::removeTranslator(m_translator.get()); - } - - QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; - if (m_translator->load(strFileName)) { - if (QCoreApplication::installTranslator(m_translator.get())) { - m_settings->setAppLanguage(locale); - } - } else { - m_settings->setAppLanguage(QLocale::English); - } - - m_engine->retranslate(); - - emit translationsUpdated(); -} - -bool AmneziaApplication::parseCommands() -{ - m_parser.setApplicationDescription(APPLICATION_NAME); - m_parser.addHelpOption(); - m_parser.addVersionOption(); - - QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; - m_parser.addOption(c_autostart); - - QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; - m_parser.addOption(c_cleanup); - - m_parser.process(*this); - - if (m_parser.isSet(c_cleanup)) { - Logger::cleanUp(); - QTimer::singleShot(100, this, [this] { quit(); }); - exec(); - return false; - } - return true; -} - -QQmlApplicationEngine *AmneziaApplication::qmlEngine() const -{ - return m_engine; -} - -void AmneziaApplication::initModels() -{ - m_containersModel.reset(new ContainersModel(this)); - m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); - - m_serversModel.reset(new ServersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); - connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), - &ContainersModel::updateModel); - connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(), - &ContainersModel::setDefaultContainer); - m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better? - - m_languageModel.reset(new LanguageModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); - connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); - connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); - - m_sitesModel.reset(new SitesModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); - - m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); - - m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); - m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); - - m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); - m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); - - m_cloakConfigModel.reset(new CloakConfigModel(this)); - m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); - - m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); - - m_awgConfigModel.reset(new AwgConfigModel(this)); - m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); - -#ifdef Q_OS_WINDOWS - m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); - m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); -#endif - - m_sftpConfigModel.reset(new SftpConfigModel(this)); - m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); - - m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); - connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, - m_serversModel.get(), &ServersModel::clearCachedProfile); - - connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, - [this](const QString &clientId, const QString &clientName, const DockerContainer container, - ServerCredentials credentials) { - m_serversModel->reloadContainerConfig(); - m_clientManagementModel->appendClient(clientId, clientName, container, credentials); - emit m_configurator->clientModelUpdated(); - }); -} - -void AmneziaApplication::initControllers() -{ - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - - connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), - &ConnectionController::onTranslationsUpdated); - - m_pageController.reset(new PageController(m_serversModel, m_settings)); - m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); - m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); - connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), - &PageController::showPassphraseRequestDrawer); - connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), - &InstallController::setEncryptedPassphrase); - connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), - &ConnectionController::onCurrentContainerUpdated); - - m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, - m_settings, m_configurator)); - m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - - m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings)); - m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); - if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); - } - connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(), - &ServersModel::toggleAmneziaDns); - - m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); - m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); - - m_systemController.reset(new SystemController(m_settings)); - m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - - m_cloudController.reset(new ApiController(m_serversModel, m_containersModel)); - m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get()); -} +#include "amnezia_application.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "logger.h" +#include "version.h" + +#include "platforms/ios/QRCodeReaderBase.h" +#if defined(Q_OS_ANDROID) + #include "platforms/android/android_controller.h" +#endif + +#include "protocols/qml_register_protocols.h" + +#if defined(Q_OS_IOS) + #include "platforms/ios/ios_controller.h" +#endif + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) +#else +AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, + int timeout, const QString &userData) + : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) +#endif +{ + setQuitOnLastWindowClosed(false); + + // Fix config file permissions +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + { + QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); + s.setValue("permFixed", true); + } + + QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + + ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf"; + QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner); + + QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + + ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf"; + QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner); +#endif + + m_settings = std::shared_ptr(new Settings); +} + +AmneziaApplication::~AmneziaApplication() +{ + m_vpnConnectionThread.quit(); + m_vpnConnectionThread.wait(3000); + + if (m_engine) { + QObject::disconnect(m_engine, 0, 0, 0); + delete m_engine; + } +} + +void AmneziaApplication::init() +{ + m_engine = new QQmlApplicationEngine; + + const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); + QObject::connect( + m_engine, &QQmlApplicationEngine::objectCreated, this, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + + m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); + + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); + m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + m_vpnConnection->moveToThread(&m_vpnConnectionThread); + m_vpnConnectionThread.start(); + + initModels(); + loadTranslator(); + initControllers(); + +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initConnectionState, this, + [this](Vpn::ConnectionState state) { + m_connectionController->onConnectionStateChanged(state); + 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, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); +#endif + +#ifdef Q_OS_IOS + IosController::Instance()->initialize(); + connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); + + connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { + m_pageController->replaceStartPage(); + m_pageController->goToPageSettingsBackup(); + m_settingsController->importBackupFromOutside(filePath); + }); +#endif + + m_notificationHandler.reset(NotificationHandler::create(nullptr)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), + &NotificationHandler::setConnectionState); + + connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), + &PageController::raiseMainWindow); + connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), + &ConnectionController::openConnection); + connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), + &ConnectionController::closeConnection); + connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), + &NotificationHandler::onTranslationsUpdated); + + m_engine->load(url); + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); + + if (m_settings->isSaveLogs()) { + if (!Logger::init()) { + qWarning() << "Initialization of debug subsystem failed"; + } + } + +#ifdef Q_OS_WIN + if (m_parser.isSet("a")) + m_pageController->showOnStartup(); + else + emit m_pageController->raiseMainWindow(); +#else + m_pageController->showOnStartup(); +#endif + + // TODO - fix +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (isPrimary()) { + QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { + qDebug() << "Secondary instance started, showing this window instead"; + emit m_pageController->raiseMainWindow(); + }); + } +#endif + +// Android TextArea clipboard workaround +// Text from TextArea always has "text/html" mime-type: +// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865 +// Next, html is created for this mime-type: +// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885 +// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText: +// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46 +// So we catch all the copies to the clipboard and clear them from "text/html" +#ifdef Q_OS_ANDROID + connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { + auto clipboard = QGuiApplication::clipboard(); + if (clipboard->mimeData()->hasHtml()) { + clipboard->setText(clipboard->text()); + } + }); +#endif +} + +void AmneziaApplication::registerTypes() +{ + qRegisterMetaType("ServerCredentials"); + + qRegisterMetaType("DockerContainer"); + qRegisterMetaType("TransportProto"); + qRegisterMetaType("Proto"); + qRegisterMetaType("ServiceType"); + + declareQmlProtocolEnum(); + declareQmlContainerEnum(); + + qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); + + m_containerProps.reset(new ContainerProps()); + qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); + + m_protocolProps.reset(new ProtocolProps()); + qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get()); + + qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, + "ContainersModelFilters"); + + // + Vpn::declareQmlVpnConnectionStateEnum(); + PageLoader::declareQmlPageEnum(); +} + +void AmneziaApplication::loadFonts() +{ + QQuickStyle::setStyle("Basic"); + + QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); +} + +void AmneziaApplication::loadTranslator() +{ + auto locale = m_settings->getAppLanguage(); + m_translator.reset(new QTranslator()); + updateTranslator(locale); +} + +void AmneziaApplication::updateTranslator(const QLocale &locale) +{ + if (!m_translator->isEmpty()) { + QCoreApplication::removeTranslator(m_translator.get()); + } + + QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; + if (m_translator->load(strFileName)) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } else { + m_settings->setAppLanguage(QLocale::English); + } + + m_engine->retranslate(); + + emit translationsUpdated(); +} + +bool AmneziaApplication::parseCommands() +{ + m_parser.setApplicationDescription(APPLICATION_NAME); + m_parser.addHelpOption(); + m_parser.addVersionOption(); + + QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; + m_parser.addOption(c_autostart); + + QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; + m_parser.addOption(c_cleanup); + + m_parser.process(*this); + + if (m_parser.isSet(c_cleanup)) { + Logger::cleanUp(); + QTimer::singleShot(100, this, [this] { quit(); }); + exec(); + return false; + } + return true; +} + +QQmlApplicationEngine *AmneziaApplication::qmlEngine() const +{ + return m_engine; +} + +void AmneziaApplication::initModels() +{ + m_containersModel.reset(new ContainersModel(this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), + &ContainersModel::updateModel); + connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(), + &ContainersModel::setDefaultContainer); + m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better? + + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); + + m_sitesModel.reset(new SitesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + + m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); + m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + + m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); + m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + + m_cloakConfigModel.reset(new CloakConfigModel(this)); + m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + + m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + + m_awgConfigModel.reset(new AwgConfigModel(this)); + m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); + +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); + m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); +#endif + + m_sftpConfigModel.reset(new SftpConfigModel(this)); + m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); + + m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); + connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, + m_serversModel.get(), &ServersModel::clearCachedProfile); + + connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, + [this](const QString &clientId, const QString &clientName, const DockerContainer container, + ServerCredentials credentials) { + m_serversModel->reloadContainerConfig(); + m_clientManagementModel->appendClient(clientId, clientName, container, credentials); + emit m_configurator->clientModelUpdated(); + }); +} + +void AmneziaApplication::initControllers() +{ + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), + &ConnectionController::onTranslationsUpdated); + + m_pageController.reset(new PageController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), + &PageController::showPassphraseRequestDrawer); + connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), + &InstallController::setEncryptedPassphrase); + connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), + &ConnectionController::onCurrentContainerUpdated); + + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, + m_settings, m_configurator)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + } + connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(), + &ServersModel::toggleAmneziaDns); + + m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); + m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + + m_systemController.reset(new SystemController(m_settings)); + m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + + m_cloudController.reset(new ApiController(m_serversModel, m_containersModel)); + m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get()); +} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index aff853a6..52427281 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -1,127 +1,127 @@ -#ifndef AMNEZIA_APPLICATION_H -#define AMNEZIA_APPLICATION_H - -#include -#include -#include -#include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #include -#else - #include -#endif - -#include "settings.h" -#include "vpnconnection.h" - -#include "configurators/vpn_configurator.h" - -#include "ui/controllers/connectionController.h" -#include "ui/controllers/exportController.h" -#include "ui/controllers/importController.h" -#include "ui/controllers/installController.h" -#include "ui/controllers/pageController.h" -#include "ui/controllers/settingsController.h" -#include "ui/controllers/sitesController.h" -#include "ui/controllers/systemController.h" -#include "ui/controllers/apiController.h" -#include "ui/models/containers_model.h" -#include "ui/models/languageModel.h" -#include "ui/models/protocols/cloakConfigModel.h" -#include "ui/notificationhandler.h" -#ifdef Q_OS_WINDOWS - #include "ui/models/protocols/ikev2ConfigModel.h" -#endif -#include "ui/models/protocols/awgConfigModel.h" -#include "ui/models/protocols/openvpnConfigModel.h" -#include "ui/models/protocols/shadowsocksConfigModel.h" -#include "ui/models/protocols/wireguardConfigModel.h" -#include "ui/models/protocols_model.h" -#include "ui/models/servers_model.h" -#include "ui/models/services/sftpConfigModel.h" -#include "ui/models/sites_model.h" -#include "ui/models/clientManagementModel.h" - -#define amnApp (static_cast(QCoreApplication::instance())) - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QGuiApplication -#else - #define AMNEZIA_BASE_CLASS SingleApplication - #define QAPPLICATION_CLASS QApplication - #include "singleapplication.h" -#endif - -class AmneziaApplication : public AMNEZIA_BASE_CLASS -{ - Q_OBJECT -public: -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - AmneziaApplication(int &argc, char *argv[]); -#else - AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, - SingleApplication::Options options = SingleApplication::User, int timeout = 1000, - const QString &userData = {}); -#endif - virtual ~AmneziaApplication(); - - void init(); - void registerTypes(); - void loadFonts(); - void loadTranslator(); - void updateTranslator(const QLocale &locale); - bool parseCommands(); - - QQmlApplicationEngine *qmlEngine() const; - -signals: - void translationsUpdated(); - -private: - void initModels(); - void initControllers(); - - QQmlApplicationEngine *m_engine {}; - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - - QSharedPointer m_containerProps; - QSharedPointer m_protocolProps; - - QSharedPointer m_translator; - QCommandLineParser m_parser; - - QSharedPointer m_containersModel; - QSharedPointer m_serversModel; - QSharedPointer m_languageModel; - QSharedPointer m_protocolsModel; - QSharedPointer m_sitesModel; - QSharedPointer m_clientManagementModel; - - QScopedPointer m_openVpnConfigModel; - QScopedPointer m_shadowSocksConfigModel; - QScopedPointer m_cloakConfigModel; - QScopedPointer m_wireGuardConfigModel; - QScopedPointer m_awgConfigModel; -#ifdef Q_OS_WINDOWS - QScopedPointer m_ikev2ConfigModel; -#endif - - QScopedPointer m_sftpConfigModel; - - QSharedPointer m_vpnConnection; - QThread m_vpnConnectionThread; - QScopedPointer m_notificationHandler; - - QScopedPointer m_connectionController; - QScopedPointer m_pageController; - QScopedPointer m_installController; - QScopedPointer m_importController; - QScopedPointer m_exportController; - QScopedPointer m_settingsController; - QScopedPointer m_sitesController; - QScopedPointer m_systemController; - QScopedPointer m_cloudController; -}; - -#endif // AMNEZIA_APPLICATION_H +#ifndef AMNEZIA_APPLICATION_H +#define AMNEZIA_APPLICATION_H + +#include +#include +#include +#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif + +#include "settings.h" +#include "vpnconnection.h" + +#include "configurators/vpn_configurator.h" + +#include "ui/controllers/connectionController.h" +#include "ui/controllers/exportController.h" +#include "ui/controllers/importController.h" +#include "ui/controllers/installController.h" +#include "ui/controllers/pageController.h" +#include "ui/controllers/settingsController.h" +#include "ui/controllers/sitesController.h" +#include "ui/controllers/systemController.h" +#include "ui/controllers/apiController.h" +#include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" +#include "ui/models/protocols/cloakConfigModel.h" +#include "ui/notificationhandler.h" +#ifdef Q_OS_WINDOWS + #include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/protocols/awgConfigModel.h" +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/shadowsocksConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols_model.h" +#include "ui/models/servers_model.h" +#include "ui/models/services/sftpConfigModel.h" +#include "ui/models/sites_model.h" +#include "ui/models/clientManagementModel.h" + +#define amnApp (static_cast(QCoreApplication::instance())) + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #define AMNEZIA_BASE_CLASS QGuiApplication +#else + #define AMNEZIA_BASE_CLASS SingleApplication + #define QAPPLICATION_CLASS QApplication + #include "singleapplication.h" +#endif + +class AmneziaApplication : public AMNEZIA_BASE_CLASS +{ + Q_OBJECT +public: +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + AmneziaApplication(int &argc, char *argv[]); +#else + AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, + SingleApplication::Options options = SingleApplication::User, int timeout = 1000, + const QString &userData = {}); +#endif + virtual ~AmneziaApplication(); + + void init(); + void registerTypes(); + void loadFonts(); + void loadTranslator(); + void updateTranslator(const QLocale &locale); + bool parseCommands(); + + QQmlApplicationEngine *qmlEngine() const; + +signals: + void translationsUpdated(); + +private: + void initModels(); + void initControllers(); + + QQmlApplicationEngine *m_engine {}; + std::shared_ptr m_settings; + std::shared_ptr m_configurator; + + QSharedPointer m_containerProps; + QSharedPointer m_protocolProps; + + QSharedPointer m_translator; + QCommandLineParser m_parser; + + QSharedPointer m_containersModel; + QSharedPointer m_serversModel; + QSharedPointer m_languageModel; + QSharedPointer m_protocolsModel; + QSharedPointer m_sitesModel; + QSharedPointer m_clientManagementModel; + + QScopedPointer m_openVpnConfigModel; + QScopedPointer m_shadowSocksConfigModel; + QScopedPointer m_cloakConfigModel; + QScopedPointer m_wireGuardConfigModel; + QScopedPointer m_awgConfigModel; +#ifdef Q_OS_WINDOWS + QScopedPointer m_ikev2ConfigModel; +#endif + + QScopedPointer m_sftpConfigModel; + + QSharedPointer m_vpnConnection; + QThread m_vpnConnectionThread; + QScopedPointer m_notificationHandler; + + QScopedPointer m_connectionController; + QScopedPointer m_pageController; + QScopedPointer m_installController; + QScopedPointer m_importController; + QScopedPointer m_exportController; + QScopedPointer m_settingsController; + QScopedPointer m_sitesController; + QScopedPointer m_systemController; + QScopedPointer m_cloudController; +}; + +#endif // AMNEZIA_APPLICATION_H From 4176d0130a87520f940630d3f067f8bfe0f80161 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 20 Jan 2024 08:35:24 -0500 Subject: [PATCH 15/20] Fix import of some native WG configs --- client/ui/controllers/importController.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 44fc1cb9..734c17c3 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -239,7 +239,16 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) // && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); lastConfig[config_key::client_ip] = configMap.value("Address"); - lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); + if (!configMap.value("PresharedKey").isEmpty()) { + lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); + } else if (!configMap.value("PreSharedKey").isEmpty()) { + lastConfig[config_key::psk_key] = configMap.value("PreSharedKey"); + } else { + qDebug() << "Failed to import profile"; + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); + return QJsonObject(); + } + lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); // } else { // qDebug() << "Failed to import profile"; From 3e02dfef634f516bc936541c392a320e04b41fd4 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 20 Jan 2024 16:40:12 +0300 Subject: [PATCH 16/20] Refactoring Android logging (#511) Refactoring Android logging --- client/amnezia_application.cpp | 14 +- client/android/AndroidManifest.xml | 11 +- client/android/build.gradle.kts | 1 - .../vpn/protocol/openvpn/OpenVpnClient.kt | 78 +++--- client/android/res/xml/backup_content.xml | 4 + .../android/res/xml/data_extraction_rules.xml | 9 + .../src/org/amnezia/vpn/AmneziaActivity.kt | 60 +++-- .../src/org/amnezia/vpn/AmneziaApplication.kt | 6 + .../src/org/amnezia/vpn/AmneziaVpnService.kt | 47 ++-- .../org/amnezia/vpn/ImportConfigActivity.kt | 10 +- .../android/src/org/amnezia/vpn/IpcMessage.kt | 3 +- client/android/src/org/amnezia/vpn/Prefs.kt | 25 -- .../src/org/amnezia/vpn/VpnRequestActivity.kt | 2 +- client/android/utils/build.gradle.kts | 4 + client/android/utils/src/main/kotlin/Log.kt | 249 ++++++++++++++++-- client/android/utils/src/main/kotlin/Prefs.kt | 58 ++++ .../utils/src/main/kotlin/net/NetworkState.kt | 4 +- .../platforms/android/android_controller.cpp | 115 +++++++- client/platforms/android/android_controller.h | 16 +- client/settings.cpp | 4 +- client/settings.h | 2 +- client/ui/controllers/settingsController.cpp | 9 + 22 files changed, 577 insertions(+), 154 deletions(-) create mode 100644 client/android/res/xml/backup_content.xml create mode 100644 client/android/res/xml/data_extraction_rules.xml delete mode 100644 client/android/src/org/amnezia/vpn/Prefs.kt create mode 100644 client/android/utils/src/main/kotlin/Prefs.kt diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 28b028ff..f0a7769a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -91,6 +91,13 @@ void AmneziaApplication::init() initControllers(); #ifdef Q_OS_ANDROID + if(!AndroidController::initLogging()) { + qFatal("Android logging initialization failed"); + } + AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); + connect(m_settings.get(), &Settings::saveLogsChanged, + AndroidController::instance(), &AndroidController::setSaveLogs); + connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { m_connectionController->onConnectionStateChanged(state); @@ -98,10 +105,7 @@ void AmneziaApplication::init() m_vpnConnection->restoreConnection(); }); if (!AndroidController::instance()->initialize()) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); - return; + qFatal("Android controller initialization failed"); } connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { @@ -143,11 +147,13 @@ void AmneziaApplication::init() m_engine->load(url); m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); +#ifndef Q_OS_ANDROID if (m_settings->isSaveLogs()) { if (!Logger::init()) { qWarning() << "Initialization of debug subsystem failed"; } } +#endif #ifdef Q_OS_WIN if (m_parser.isSet("a")) diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 960dc87d..fb417f05 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -1,6 +1,8 @@ - + android:theme="@style/NoActionBar" + android:fullBackupContent="@xml/backup_content" + android:dataExtractionRules="@xml/data_extraction_rules" + tools:targetApi="s"> - + diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts index 757989aa..0a1fe83f 100644 --- a/client/android/build.gradle.kts +++ b/client/android/build.gradle.kts @@ -108,7 +108,6 @@ dependencies { implementation(project(":cloak")) implementation(libs.androidx.core) implementation(libs.androidx.activity) - implementation(libs.androidx.security.crypto) implementation(libs.kotlinx.coroutines) implementation(libs.bundles.androidx.camera) implementation(libs.google.mlkit) diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt index c716a970..f489c980 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt @@ -52,7 +52,7 @@ class OpenVpnClient( // Callback to construct a new tun builder // Should be called first. override fun tun_builder_new(): Boolean { - Log.v(TAG, "tun_builder_new") + Log.d(TAG, "tun_builder_new") configBuilder.clearAddresses() return true } @@ -60,7 +60,7 @@ class OpenVpnClient( // Callback to set MTU of the VPN interface // Never called more than once per tun_builder session. override fun tun_builder_set_mtu(mtu: Int): Boolean { - Log.v(TAG, "tun_builder_set_mtu: $mtu") + Log.d(TAG, "tun_builder_set_mtu: $mtu") configBuilder.setMtu(mtu) return true } @@ -71,7 +71,7 @@ class OpenVpnClient( address: String, prefix_length: Int, gateway: String, ipv6: Boolean, net30: Boolean ): Boolean { - Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") + Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") configBuilder.addAddress(InetNetwork(address, prefix_length)) return true } @@ -80,7 +80,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session // metric is optional and should be ignored if < 0 override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") + Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") if (address == "remote_host") return false configBuilder.addRoute(InetNetwork(address, prefix_length)) return true @@ -90,7 +90,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session // metric is optional and should be ignored if < 0 override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") + Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { configBuilder.excludeRoute(InetNetwork(address, prefix_length)) } @@ -104,7 +104,7 @@ class OpenVpnClient( // domain should be routed. // Guaranteed to be called after tun_builder_reroute_gw. override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6") + Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6") configBuilder.addDnsServer(parseInetAddress(address)) return true } @@ -119,28 +119,28 @@ class OpenVpnClient( // ignored for that family // See also Android's VPNService.Builder.allowFamily method /* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { - Log.v(TAG, "tun_builder_set_allow_family: $af, $allow") + Log.d(TAG, "tun_builder_set_allow_family: $af, $allow") return true } */ // Callback to set address of remote server // Never called more than once per tun_builder session. override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6") + Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6") return true } // Optional callback that indicates OSI layer, should be 2 or 3. // Defaults to 3. override fun tun_builder_set_layer(layer: Int): Boolean { - Log.v(TAG, "tun_builder_set_layer: $layer") + Log.d(TAG, "tun_builder_set_layer: $layer") return layer == 3 } // Callback to set the session name // Never called more than once per tun_builder session. override fun tun_builder_set_session_name(name: String): Boolean { - Log.v(TAG, "tun_builder_set_session_name: $name") + Log.d(TAG, "tun_builder_set_session_name: $name") return true } @@ -149,7 +149,7 @@ class OpenVpnClient( // if the tunnel could not be established. // Always called last after tun_builder session has been configured. override fun tun_builder_establish(): Int { - Log.v(TAG, "tun_builder_establish") + Log.d(TAG, "tun_builder_establish") return establish(configBuilder) } @@ -159,7 +159,7 @@ class OpenVpnClient( // flags are defined in RGWFlags (rgwflags.hpp). // Never called more than once per tun_builder session. override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { - Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") + Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true if (ipv4) { configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) @@ -176,7 +176,7 @@ class OpenVpnClient( // reroute_dns parameter. // Guaranteed to be called after tun_builder_reroute_gw. override fun tun_builder_add_search_domain(domain: String): Boolean { - Log.v(TAG, "tun_builder_add_search_domain: $domain") + Log.d(TAG, "tun_builder_add_search_domain: $domain") configBuilder.setSearchDomain(domain) return true } @@ -184,7 +184,7 @@ class OpenVpnClient( // Callback to set the HTTP proxy // Never called more than once per tun_builder session. override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { - Log.v(TAG, "tun_builder_set_proxy_http: $host, $port") + Log.d(TAG, "tun_builder_set_proxy_http: $host, $port") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) @@ -199,7 +199,7 @@ class OpenVpnClient( // Callback to set the HTTPS proxy // Never called more than once per tun_builder session. override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { - Log.v(TAG, "tun_builder_set_proxy_https: $host, $port") + Log.d(TAG, "tun_builder_set_proxy_https: $host, $port") return false } @@ -208,7 +208,7 @@ class OpenVpnClient( // to exclude them from the VPN network are generated // This should be a list of CIDR networks (e.g. 192.168.0.0/24) override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec { - Log.v(TAG, "tun_builder_get_local_networks: $ipv6") + Log.d(TAG, "tun_builder_get_local_networks: $ipv6") val networks = ClientAPI_StringVec() for (address in getLocalNetworks(ipv6)) { networks.add(address.toString()) @@ -222,21 +222,21 @@ class OpenVpnClient( // tun_builder_reroute_gw. Route metric is ignored // if < 0. /* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { - Log.v(TAG, "tun_builder_set_route_metric_default: $metric") + Log.d(TAG, "tun_builder_set_route_metric_default: $metric") return super.tun_builder_set_route_metric_default(metric) } */ // Callback to add a host which should bypass the proxy // May be called more than once per tun_builder session /* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { - Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host") + Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host") return super.tun_builder_add_proxy_bypass(bypass_host) } */ // Callback to set the proxy "Auto Config URL" // Never called more than once per tun_builder session. /* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { - Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url") + Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url") return super.tun_builder_set_proxy_auto_config_url(url) } */ @@ -245,7 +245,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session. // Guaranteed to be called after tun_builder_reroute_gw. /* override fun tun_builder_add_wins_server(address: String): Boolean { - Log.v(TAG, "tun_builder_add_wins_server: $address") + Log.d(TAG, "tun_builder_add_wins_server: $address") return super.tun_builder_add_wins_server(address) } */ @@ -254,7 +254,7 @@ class OpenVpnClient( // set the "Connection-specific DNS Suffix" property on // the TAP driver. /* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { - Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name") + Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name") return super.tun_builder_set_adapter_domain_suffix(name) } */ @@ -266,13 +266,13 @@ class OpenVpnClient( // tun_builder_establish_lite() will be called. Otherwise, // tun_builder_establish() will be called. /* override fun tun_builder_persist(): Boolean { - Log.v(TAG, "tun_builder_persist") + Log.d(TAG, "tun_builder_persist") return super.tun_builder_persist() } */ // Indicates a reconnection with persisted tun state. /* override fun tun_builder_establish_lite() { - Log.v(TAG, "tun_builder_establish_lite") + Log.d(TAG, "tun_builder_establish_lite") super.tun_builder_establish_lite() } */ @@ -280,7 +280,7 @@ class OpenVpnClient( // If disconnect == true, then the teardown is occurring // prior to final disconnect. /* override fun tun_builder_teardown(disconnect: Boolean) { - Log.v(TAG, "tun_builder_teardown: $disconnect") + Log.d(TAG, "tun_builder_teardown: $disconnect") super.tun_builder_teardown(disconnect) } */ @@ -290,7 +290,7 @@ class OpenVpnClient( // Parse OpenVPN configuration file. override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { - Log.v(TAG, "eval_config") + Log.d(TAG, "eval_config") return super.eval_config(arg0) } @@ -299,7 +299,7 @@ class OpenVpnClient( // to event() and log() functions. Make sure to call eval_config() // and possibly provide_creds() as well before this function. override fun connect(): ClientAPI_Status { - Log.v(TAG, "connect") + Log.d(TAG, "connect") return super.connect() } @@ -307,7 +307,7 @@ class OpenVpnClient( // Will be called from the thread executing connect(). // The remote and ipv6 are the remote host this socket will connect to override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { - Log.v(TAG, "socket_protect: $socket, $remote, $ipv6") + Log.d(TAG, "socket_protect: $socket, $remote, $ipv6") return protect(socket) } @@ -315,7 +315,7 @@ class OpenVpnClient( // May be called asynchronously from a different thread // when connect() is running. override fun stop() { - Log.v(TAG, "stop") + Log.d(TAG, "stop") super.stop() } @@ -323,21 +323,21 @@ class OpenVpnClient( // when network is down. May be called from a different thread // when connect() is running. override fun pause(reason: String) { - Log.v(TAG, "pause: $reason") + Log.d(TAG, "pause: $reason") super.pause(reason) } // Resume the client after it has been paused. May be called from a // different thread when connect() is running. override fun resume() { - Log.v(TAG, "resume") + Log.d(TAG, "resume") super.resume() } // Do a disconnect/reconnect cycle n seconds from now. May be called // from a different thread when connect() is running. override fun reconnect(seconds: Int) { - Log.v(TAG, "reconnect") + Log.d(TAG, "reconnect: $seconds") super.reconnect(seconds) } @@ -346,14 +346,14 @@ class OpenVpnClient( // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // state. override fun pause_on_connection_timeout(): Boolean { - Log.v(TAG, "pause_on_connection_timeout") + Log.d(TAG, "pause_on_connection_timeout") return false } // Return information about the most recent connection. Should be called // after an event of type "CONNECTED". /* override fun connection_info(): ClientAPI_ConnectionInfo { - Log.v(TAG, "connection_info") + Log.d(TAG, "connection_info") return super.connection_info() } */ @@ -366,7 +366,7 @@ class OpenVpnClient( override fun event(event: ClientAPI_Event) { val name = event.name val info = event.info - Log.v(TAG, "OpenVpn event: $name: $info") + Log.d(TAG, "OpenVpn event: $name: $info") when (name) { "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "CONNECTED" -> state.value = CONNECTED @@ -398,31 +398,31 @@ class OpenVpnClient( // return transport stats only override fun transport_stats(): ClientAPI_TransportStats { - Log.v(TAG, "transport_stats") + Log.d(TAG, "transport_stats") return super.transport_stats() } // return a stats value, index should be >= 0 and < stats_n() /* override fun stats_value(index: Int): Long { - Log.v(TAG, "stats_value: $index") + Log.d(TAG, "stats_value: $index") return super.stats_value(index) } */ // return all stats in a bundle /* override fun stats_bundle(): ClientAPI_LLVector { - Log.v(TAG, "stats_bundle") + Log.d(TAG, "stats_bundle") return super.stats_bundle() } */ // return tun stats only /* override fun tun_stats(): ClientAPI_InterfaceStats { - Log.v(TAG, "tun_stats") + Log.d(TAG, "tun_stats") return super.tun_stats() } */ // post control channel message /* override fun post_cc_msg(msg: String) { - Log.v(TAG, "post_cc_msg: $msg") + Log.d(TAG, "post_cc_msg: $msg") super.post_cc_msg(msg) } */ } diff --git a/client/android/res/xml/backup_content.xml b/client/android/res/xml/backup_content.xml new file mode 100644 index 00000000..33260809 --- /dev/null +++ b/client/android/res/xml/backup_content.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/client/android/res/xml/data_extraction_rules.xml b/client/android/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..f5f079cc --- /dev/null +++ b/client/android/res/xml/data_extraction_rules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 0c0ec0f9..2ed2c7f7 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -143,7 +143,7 @@ class AmneziaActivity : QtActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Create Amnezia activity: $intent") + Log.d(TAG, "Create Amnezia activity: $intent") mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) vpnServiceMessenger = IpcMessenger( onDeadObjectException = ::doUnbindService, @@ -154,7 +154,7 @@ class AmneziaActivity : QtActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.v(TAG, "onNewIntent: $intent") + Log.d(TAG, "onNewIntent: $intent") intent?.let(::processIntent) } @@ -174,7 +174,7 @@ class AmneziaActivity : QtActivity() { override fun onStart() { super.onStart() - Log.v(TAG, "Start Amnezia activity") + Log.d(TAG, "Start Amnezia activity") mainScope.launch { qtInitialized.await() doBindService() @@ -182,13 +182,13 @@ class AmneziaActivity : QtActivity() { } override fun onStop() { - Log.v(TAG, "Stop Amnezia activity") + Log.d(TAG, "Stop Amnezia activity") doUnbindService() super.onStop() } override fun onDestroy() { - Log.v(TAG, "Destroy Amnezia activity") + Log.d(TAG, "Destroy Amnezia activity") mainScope.cancel() super.onDestroy() } @@ -217,7 +217,7 @@ class AmneziaActivity : QtActivity() { CHECK_VPN_PERMISSION_ACTION_CODE -> { when (resultCode) { RESULT_OK -> { - Log.v(TAG, "Vpn permission granted") + Log.d(TAG, "Vpn permission granted") Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() checkVpnPermissionCallbacks?.run { onSuccess() } } @@ -240,7 +240,7 @@ class AmneziaActivity : QtActivity() { */ @MainThread private fun doBindService() { - Log.v(TAG, "Bind service") + Log.d(TAG, "Bind service") Intent(this, AmneziaVpnService::class.java).also { bindService(it, serviceConnection, BIND_ABOVE_CLIENT) } @@ -251,7 +251,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun doUnbindService() { if (isInBoundState) { - Log.v(TAG, "Unbind service") + Log.d(TAG, "Unbind service") isWaitingStatus = true QtAndroidController.onServiceDisconnected() vpnServiceMessenger.reset() @@ -286,7 +286,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { - Log.v(TAG, "Check VPN permission") + Log.d(TAG, "Check VPN permission") VpnService.prepare(applicationContext)?.let { checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) @@ -307,7 +307,7 @@ class AmneziaActivity : QtActivity() { } private fun connectToVpn(vpnConfig: String) { - Log.v(TAG, "Connect to VPN") + Log.d(TAG, "Connect to VPN") vpnServiceMessenger.send { Action.CONNECT.packToMessage { putString(VPN_CONFIG, vpnConfig) @@ -316,7 +316,7 @@ class AmneziaActivity : QtActivity() { } private fun startVpnService(vpnConfig: String) { - Log.v(TAG, "Start VPN service") + Log.d(TAG, "Start VPN service") Intent(this, AmneziaVpnService::class.java).apply { putExtra(VPN_CONFIG, vpnConfig) }.also { @@ -325,7 +325,7 @@ class AmneziaActivity : QtActivity() { } private fun disconnectFromVpn() { - Log.v(TAG, "Disconnect from VPN") + Log.d(TAG, "Disconnect from VPN") vpnServiceMessenger.send(Action.DISCONNECT) } @@ -369,7 +369,7 @@ class AmneziaActivity : QtActivity() { @Suppress("unused") fun saveFile(fileName: String, data: String) { - Log.v(TAG, "Save file $fileName") + Log.d(TAG, "Save file $fileName") mainScope.launch { tmpFileContentToSave = data @@ -397,7 +397,7 @@ class AmneziaActivity : QtActivity() { Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - Log.d(TAG, "File mimyType filter: $mimeTypes") + Log.v(TAG, "File mimyType filter: $mimeTypes") when (mimeTypes.size) { 1 -> type = mimeTypes.first() @@ -416,13 +416,6 @@ class AmneziaActivity : QtActivity() { @Suppress("unused") fun setNotificationText(title: String, message: String, timerSec: Int) { Log.v(TAG, "Set notification text") - Log.w(TAG, "Not yet implemented") - } - - @Suppress("unused") - fun cleanupLogs() { - Log.v(TAG, "Cleanup logs") - Log.w(TAG, "Not yet implemented") } @Suppress("unused") @@ -432,4 +425,29 @@ class AmneziaActivity : QtActivity() { startActivity(it) } } + + @Suppress("unused") + fun setSaveLogs(enabled: Boolean) { + Log.d(TAG, "Set save logs: $enabled") + mainScope.launch { + Log.saveLogs = enabled + vpnServiceMessenger.send { + Action.SET_SAVE_LOGS.packToMessage { + putBoolean(SAVE_LOGS, enabled) + } + } + } + } + + @Suppress("unused") + fun exportLogsFile(fileName: String) { + Log.v(TAG, "Export logs file") + saveFile(fileName, Log.getLogs()) + } + + @Suppress("unused") + fun clearLogs() { + Log.v(TAG, "Clear logs") + Log.clearLogs() + } } diff --git a/client/android/src/org/amnezia/vpn/AmneziaApplication.kt b/client/android/src/org/amnezia/vpn/AmneziaApplication.kt index e9d8fdfb..33182887 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaApplication.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaApplication.kt @@ -5,14 +5,20 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.CameraXConfig import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationManagerCompat +import org.amnezia.vpn.util.Log +import org.amnezia.vpn.util.Prefs import org.qtproject.qt.android.bindings.QtApplication +private const val TAG = "AmneziaApplication" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" class AmneziaApplication : QtApplication(), CameraXConfig.Provider { override fun onCreate() { super.onCreate() + Prefs.init(this) + Log.init(this) + Log.d(TAG, "Create Amnezia application") createNotificationChannel() } diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index 094874c7..ab753716 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.util.Log +import org.amnezia.vpn.util.Prefs import org.amnezia.vpn.util.net.NetworkState import org.json.JSONException import org.json.JSONObject @@ -58,6 +59,8 @@ private const val TAG = "AmneziaVpnService" const val VPN_CONFIG = "VPN_CONFIG" const val ERROR_MSG = "ERROR_MSG" +const val SAVE_LOGS = "SAVE_LOGS" + const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val NOTIFICATION_ID = 1337 @@ -118,7 +121,7 @@ class AmneziaVpnService : VpnService() { Action.CONNECT -> { val vpnConfig = msg.data.getString(VPN_CONFIG) - saveConfigToPrefs(vpnConfig) + Prefs.save(PREFS_CONFIG_KEY, vpnConfig) connect(vpnConfig) } @@ -135,6 +138,10 @@ class AmneziaVpnService : VpnService() { } } } + + Action.SET_SAVE_LOGS -> { + Log.saveLogs = msg.data.getBoolean(SAVE_LOGS) + } } } } @@ -179,7 +186,7 @@ class AmneziaVpnService : VpnService() { */ override fun onCreate() { super.onCreate() - Log.v(TAG, "Create Amnezia VPN service") + Log.d(TAG, "Create Amnezia VPN service") mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) clientMessenger = IpcMessenger(messengerName = "Client") @@ -193,15 +200,15 @@ class AmneziaVpnService : VpnService() { else intent?.component?.packageName != packageName if (isAlwaysOnCompat) { - Log.v(TAG, "Start service via Always-on") - connect(loadConfigFromPrefs()) + Log.d(TAG, "Start service via Always-on") + connect(Prefs.load(PREFS_CONFIG_KEY)) } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { - Log.v(TAG, "Start service after permission check") - connect(loadConfigFromPrefs()) + Log.d(TAG, "Start service after permission check") + connect(Prefs.load(PREFS_CONFIG_KEY)) } else { - Log.v(TAG, "Start service") + Log.d(TAG, "Start service") val vpnConfig = intent?.getStringExtra(VPN_CONFIG) - saveConfigToPrefs(vpnConfig) + Prefs.save(PREFS_CONFIG_KEY, vpnConfig) connect(vpnConfig) } ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) @@ -237,7 +244,7 @@ class AmneziaVpnService : VpnService() { } override fun onRevoke() { - Log.v(TAG, "onRevoke") + Log.d(TAG, "onRevoke") // Calls to onRevoke() method may not happen on the main thread of the process mainScope.launch { disconnect() @@ -245,7 +252,7 @@ class AmneziaVpnService : VpnService() { } override fun onDestroy() { - Log.v(TAG, "Destroy service") + Log.d(TAG, "Destroy service") runBlocking { disconnect() disconnectionJob?.join() @@ -256,7 +263,7 @@ class AmneziaVpnService : VpnService() { } private fun stopService() { - Log.v(TAG, "Stop service") + Log.d(TAG, "Stop service") // the coroutine below will be canceled during the onDestroy call mainScope.launch { delay(STOP_SERVICE_TIMEOUT) @@ -272,7 +279,7 @@ class AmneziaVpnService : VpnService() { private fun launchProtocolStateHandler() { mainScope.launch { protocolState.collect { protocolState -> - Log.d(TAG, "Protocol state: $protocolState") + Log.d(TAG, "Protocol state changed: $protocolState") when (protocolState) { CONNECTED -> { clientMessenger.send(ServiceEvent.CONNECTED) @@ -305,7 +312,7 @@ class AmneziaVpnService : VpnService() { @MainThread private fun launchSendingStatistics() { - if (isServiceBound && isConnected) { + /* if (isServiceBound && isConnected) { statisticsSendingJob = mainScope.launch { while (true) { clientMessenger.send { @@ -316,7 +323,7 @@ class AmneziaVpnService : VpnService() { delay(STATISTICS_SENDING_TIMEOUT) } } - } + } */ } @MainThread @@ -328,7 +335,7 @@ class AmneziaVpnService : VpnService() { private fun connect(vpnConfig: String?) { if (isConnected || protocolState.value == CONNECTING) return - Log.v(TAG, "Start VPN connection") + Log.d(TAG, "Start VPN connection") protocolState.value = CONNECTING @@ -357,7 +364,7 @@ class AmneziaVpnService : VpnService() { private fun disconnect() { if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return - Log.v(TAG, "Stop VPN connection") + Log.d(TAG, "Stop VPN connection") protocolState.value = DISCONNECTING @@ -383,7 +390,7 @@ class AmneziaVpnService : VpnService() { private fun reconnect() { if (!isConnected) return - Log.v(TAG, "Reconnect VPN") + Log.d(TAG, "Reconnect VPN") protocolState.value = RECONNECTING @@ -439,10 +446,4 @@ class AmneziaVpnService : VpnService() { } else { true } - - private fun loadConfigFromPrefs(): String? = - Prefs.get(this).getString(PREFS_CONFIG_KEY, null) - - private fun saveConfigToPrefs(config: String?) = - Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply() } diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt index 309333ef..cae7ab75 100644 --- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt +++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt @@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Create Import Config Activity: $intent") + Log.d(TAG, "Create Import Config Activity: $intent") intent?.let(::readConfig) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.v(TAG, "onNewIntent: $intent") + Log.d(TAG, "onNewIntent: $intent") intent?.let(::readConfig) } private fun readConfig(intent: Intent) { when (intent.action) { ACTION_SEND -> { - Log.v(TAG, "Process SEND action, type: ${intent.type}") + Log.d(TAG, "Process SEND action, type: ${intent.type}") when (intent.type) { "application/octet-stream" -> { intent.getUriCompat()?.let { uri -> @@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() { } ACTION_VIEW -> { - Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") + Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") when (intent.scheme) { "file", "content" -> { intent.data?.let { uri -> @@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() { private fun startMainActivity(config: String) { if (config.isNotBlank()) { - Log.v(TAG, "startMainActivity") + Log.d(TAG, "startMainActivity") Intent(applicationContext, AmneziaActivity::class.java).apply { action = ACTION_IMPORT_CONFIG addCategory(CATEGORY_DEFAULT) diff --git a/client/android/src/org/amnezia/vpn/IpcMessage.kt b/client/android/src/org/amnezia/vpn/IpcMessage.kt index c9d2bd3f..26c3b9de 100644 --- a/client/android/src/org/amnezia/vpn/IpcMessage.kt +++ b/client/android/src/org/amnezia/vpn/IpcMessage.kt @@ -32,7 +32,8 @@ enum class Action : IpcMessage { REGISTER_CLIENT, CONNECT, DISCONNECT, - REQUEST_STATUS + REQUEST_STATUS, + SET_SAVE_LOGS } fun T.packToMessage(): Message diff --git a/client/android/src/org/amnezia/vpn/Prefs.kt b/client/android/src/org/amnezia/vpn/Prefs.kt deleted file mode 100644 index 9b4cb2ba..00000000 --- a/client/android/src/org/amnezia/vpn/Prefs.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.amnezia.vpn - -import android.content.Context -import android.content.SharedPreferences -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey -import org.amnezia.vpn.util.Log - -private const val TAG = "Prefs" -private const val PREFS_FILE = "org.amnezia.vpn.prefs" -private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure" - -object Prefs { - fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences = - try { - EncryptedSharedPreferences( - appContext, - SECURE_PREFS_FILE, - MasterKey(appContext) - ) - } catch (e: Exception) { - Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback") - appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) - } -} diff --git a/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt b/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt index 9dce3d78..c5abbc39 100644 --- a/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt +++ b/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt @@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Start request activity") + Log.d(TAG, "Start request activity") val requestIntent = VpnService.prepare(applicationContext) if (requestIntent != null) { if (getSystemService()!!.isKeyguardLocked) { diff --git a/client/android/utils/build.gradle.kts b/client/android/utils/build.gradle.kts index ac5d1efb..2ad03d61 100644 --- a/client/android/utils/build.gradle.kts +++ b/client/android/utils/build.gradle.kts @@ -15,3 +15,7 @@ android { buildConfig = true } } + +dependencies { + implementation(libs.androidx.security.crypto) +} diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt index 82180c3d..03da4507 100644 --- a/client/android/utils/src/main/kotlin/Log.kt +++ b/client/android/utils/src/main/kotlin/Log.kt @@ -1,33 +1,252 @@ package org.amnezia.vpn.util +import android.content.Context +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.os.Build +import android.os.Process +import java.io.File +import java.io.IOException +import java.io.RandomAccessFile +import java.nio.channels.FileChannel +import java.nio.channels.FileLock +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Date +import java.util.Locale +import java.util.concurrent.locks.ReentrantLock +import org.amnezia.vpn.util.Log.Priority.D +import org.amnezia.vpn.util.Log.Priority.E +import org.amnezia.vpn.util.Log.Priority.F +import org.amnezia.vpn.util.Log.Priority.I +import org.amnezia.vpn.util.Log.Priority.V +import org.amnezia.vpn.util.Log.Priority.W import android.util.Log as NativeLog -class Log { - companion object { - fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v) +private const val TAG = "Log" +private const val LOG_FILE_NAME = "amneziaVPN.log" +private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log" +private const val LOCK_FILE_NAME = ".lock" +private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS" +private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS" +private const val LOG_MAX_FILE_SIZE = 1024 * 1024 - fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d) +/** + * | Priority | Save to file | Logcat logging | + * |-------------------|--------------|----------------------------------------------| + * | Verbose | Don't save | Only in Debug build | + * | Debug | Save | In Debug build or if log saving is enabled | + * | Info, Warn, Error | Save | Enabled | + * | Fatal (Assert) | Save | Enabled. Depending on system configuration, | + * | | | create a report and/or terminate the process | + */ +object Log { + private val dateTimeFormat: Any = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) + else object : ThreadLocal() { + override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US) + } - fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i) + private lateinit var logDir: File + private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) } + private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) } - fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w) + private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel } + private val threadLock: ReentrantLock by lazy { ReentrantLock() } - fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e) + @Volatile + private var _saveLogs: Boolean = false + var saveLogs: Boolean + get() = _saveLogs + set(value) { + if (_saveLogs != value) { + if (value && !logDir.exists() && !logDir.mkdir()) { + NativeLog.e(TAG, "Failed to create dir: $logDir") + return + } + _saveLogs = value + Prefs.save(PREFS_SAVE_LOGS_KEY, value) + } + } - fun v(tag: String, msg: Any?) = v(tag, msg.toString()) + @JvmStatic + fun v(tag: String, msg: String) = log(tag, msg, V) - fun d(tag: String, msg: Any?) = d(tag, msg.toString()) + @JvmStatic + fun d(tag: String, msg: String) = log(tag, msg, D) - fun i(tag: String, msg: Any?) = i(tag, msg.toString()) + @JvmStatic + fun i(tag: String, msg: String) = log(tag, msg, I) - fun w(tag: String, msg: Any?) = w(tag, msg.toString()) + @JvmStatic + fun w(tag: String, msg: String) = log(tag, msg, W) - fun e(tag: String, msg: Any?) = e(tag, msg.toString()) + @JvmStatic + fun e(tag: String, msg: String) = log(tag, msg, E) - private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg) + @JvmStatic + fun f(tag: String, msg: String) = log(tag, msg, F) - private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) { - if (BuildConfig.DEBUG) delegate(tag, msg) + fun v(tag: String, msg: Any?) = v(tag, msg.toString()) + + fun d(tag: String, msg: Any?) = d(tag, msg.toString()) + + fun i(tag: String, msg: Any?) = i(tag, msg.toString()) + + fun w(tag: String, msg: Any?) = w(tag, msg.toString()) + + fun e(tag: String, msg: Any?) = e(tag, msg.toString()) + + fun f(tag: String, msg: Any?) = f(tag, msg.toString()) + + fun init(context: Context) { + v(TAG, "Init Log") + logDir = File(context.cacheDir, "logs") + saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY) + } + + fun getLogs(): String = + "${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}" + + fun clearLogs() { + withLock { + logFile.delete() + rotateLogFile.delete() } } + + private fun log(tag: String, msg: String, priority: Priority) { + if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority)) + + if (priority == F) { + NativeLog.wtf(tag, msg) + } else if ( + (priority != V && priority != D) || + (priority == V && BuildConfig.DEBUG) || + (priority == D && (BuildConfig.DEBUG || saveLogs)) + ) { + NativeLog.println(priority.level, tag, msg) + } + } + + private fun saveLogMsg(msg: String) { + withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) { + logFile.renameTo(rotateLogFile) + } + try { + logFile.appendText(msg) + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to write log: $e") + } + } + + private fun formatLogMsg(tag: String, msg: String, priority: Priority): String { + val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter) + } else { + @Suppress("UNCHECKED_CAST") + (dateTimeFormat as ThreadLocal).get()?.format(Date()) + } + return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " + + "$tag: $msg\n" + } + + private fun deviceInfo(): String { + val sb = StringBuilder() + sb.append("Model: ").appendLine(Build.MODEL) + sb.append("Brand: ").appendLine(Build.BRAND) + sb.append("Product: ").appendLine(Build.PRODUCT) + sb.append("Device: ").appendLine(Build.DEVICE) + sb.append("Codename: ").appendLine(Build.VERSION.CODENAME) + sb.append("Release: ").appendLine(Build.VERSION.RELEASE) + sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT) + sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString()) + return sb.toString() + } + + private fun readLogs(): String { + var logText = "" + withLock { + try { + if (rotateLogFile.exists()) logText = rotateLogFile.readText() + if (logFile.exists()) logText += logFile.readText() + } catch (e: IOException) { + val errorMsg = "Failed to read log: $e" + NativeLog.e(TAG, errorMsg) + logText += errorMsg + } + } + return logText + } + + private fun getLogcat(): String { + try { + val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start() + return process.inputStream.reader().readText() + } catch (e: IOException) { + val errorMsg = "Failed to get logcat log: $e" + NativeLog.e(TAG, errorMsg) + return errorMsg + } + } + + private fun withLock(block: () -> Unit) { + threadLock.lock() + try { + var l: FileLock? = null + try { + l = fileLock.lock() + block() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to get file lock: $e") + } finally { + try { + l?.release() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to release file lock: $e") + } + } + } finally { + threadLock.unlock() + } + } + + private fun withTryLock(condition: () -> Boolean, block: () -> Unit) { + if (condition()) { + if (threadLock.tryLock()) { + try { + if (condition()) { + var l: FileLock? = null + try { + l = fileLock.tryLock() + if (l != null) { + if (condition()) { + block() + } + } + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to get file tryLock: $e") + } finally { + try { + l?.release() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to release file tryLock: $e") + } + } + } + } finally { + threadLock.unlock() + } + } + } + } + + private enum class Priority(val level: Int) { + V(2), + D(3), + I(4), + W(5), + E(6), + F(7) + } } diff --git a/client/android/utils/src/main/kotlin/Prefs.kt b/client/android/utils/src/main/kotlin/Prefs.kt new file mode 100644 index 00000000..9afbc7be --- /dev/null +++ b/client/android/utils/src/main/kotlin/Prefs.kt @@ -0,0 +1,58 @@ +package org.amnezia.vpn.util + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import kotlin.reflect.typeOf + +private const val TAG = "Prefs" +private const val PREFS_FILE = "org.amnezia.vpn.prefs" +private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure" + +object Prefs { + private lateinit var app: Application + val prefs: SharedPreferences + get() = try { + EncryptedSharedPreferences( + app, + SECURE_PREFS_FILE, + MasterKey(app) + ) + } catch (e: Exception) { + Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback") + app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) + } + + fun init(app: Application) { + Log.v(TAG, "Init Prefs") + this.app = app + } + + fun save(key: String, value: Boolean) = + prefs.edit().putBoolean(key, value).apply() + + fun save(key: String, value: String?) = + prefs.edit().putString(key, value).apply() + + fun save(key: String, value: Int) = + prefs.edit().putInt(key, value).apply() + + fun save(key: String, value: Long) = + prefs.edit().putLong(key, value).apply() + + fun save(key: String, value: Float) = + prefs.edit().putFloat(key, value).apply() + + inline fun load(key: String): T { + return when (typeOf()) { + typeOf() -> prefs.getBoolean(key, false) + typeOf() -> prefs.getString(key, "") + typeOf() -> prefs.getInt(key, 0) + typeOf() -> prefs.getLong(key, 0L) + typeOf() -> prefs.getFloat(key, 0f) + else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf()}") + } as T + } +} diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt index 42d7baee..957fc3cb 100644 --- a/client/android/utils/src/main/kotlin/net/NetworkState.kt +++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt @@ -82,7 +82,7 @@ class NetworkState( fun bindNetworkListener() { if (isListenerBound) return - Log.v(TAG, "Bind network listener") + Log.d(TAG, "Bind network listener") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -95,7 +95,7 @@ class NetworkState( fun unbindNetworkListener() { if (!isListenerBound) return - Log.v(TAG, "Unbind network listener") + Log.d(TAG, "Unbind network listener") connectivityManager.unregisterNetworkCallback(networkCallback) isListenerBound = false currentNetwork = null diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 225ceebe..5d442b81 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -12,13 +12,15 @@ namespace AndroidController *s_instance = nullptr; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; + constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log"; + constexpr auto TAG = "AmneziaQt"; } // namespace AndroidController::AndroidController() : QObject() { connect(this, &AndroidController::status, this, [this](AndroidController::ConnectionState state) { - qDebug() << "Android event: status; state:" << textConnectionState(state); + qDebug() << "Android event: status =" << textConnectionState(state); if (isWaitingStatus) { qDebug() << "Initialization by service status"; isWaitingStatus = false; @@ -126,24 +128,19 @@ bool AndroidController::initialize() // static template -auto AndroidController::callActivityMethod(const char *methodName, const char *signature, - const std::function &defValue, Args &&...args) +auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) { qDebug() << "Call activity method:" << methodName; QJniObject activity = AndroidUtils::getActivity(); - if (activity.isValid()) { - return activity.callMethod(methodName, signature, std::forward(args)...); - } else { - qCritical() << "Activity is not valid"; - return defValue(); - } + Q_ASSERT(activity.isValid()); + return activity.callMethod(methodName, signature, std::forward(args)...); } // static template void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) { - callActivityMethod(methodName, signature, [] {}, std::forward(args)...); + callActivityMethod(methodName, signature, std::forward(args)...); } ErrorCode AndroidController::start(const QJsonObject &vpnConfig) @@ -199,6 +196,104 @@ void AndroidController::startQrReaderActivity() callActivityMethod("startQrCodeReader", "()V"); } +void AndroidController::setSaveLogs(bool enabled) +{ + callActivityMethod("setSaveLogs", "(Z)V", enabled); +} + +void AndroidController::exportLogsFile(const QString &fileName) +{ + callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V", + QJniObject::fromString(fileName).object()); +} + +void AndroidController::clearLogs() +{ + callActivityMethod("clearLogs", "()V"); +} + +// Moving log processing to the Android side +jclass AndroidController::log; +jmethodID AndroidController::logDebug; +jmethodID AndroidController::logInfo; +jmethodID AndroidController::logWarning; +jmethodID AndroidController::logError; +jmethodID AndroidController::logFatal; + +// static +bool AndroidController::initLogging() +{ + QJniEnvironment env; + + log = env.findClass(ANDROID_LOG_CLASS); + if (log == nullptr) { + qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found"; + return false; + } + + auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V"; + + logDebug = env.findStaticMethod(log, "d", logMethodSignature); + if (logDebug == nullptr) { + qCritical() << "Android debug log method not found"; + return false; + } + + logInfo = env.findStaticMethod(log, "i", logMethodSignature); + if (logInfo == nullptr) { + qCritical() << "Android info log method not found"; + return false; + } + + logWarning = env.findStaticMethod(log, "w", logMethodSignature); + if (logWarning == nullptr) { + qCritical() << "Android warning log method not found"; + return false; + } + + logError = env.findStaticMethod(log, "e", logMethodSignature); + if (logError == nullptr) { + qCritical() << "Android error log method not found"; + return false; + } + + logFatal = env.findStaticMethod(log, "f", logMethodSignature); + if (logFatal == nullptr) { + qCritical() << "Android fatal log method not found"; + return false; + } + + qInstallMessageHandler(messageHandler); + return true; +} + +// static +void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + jmethodID logMethod = logDebug; + switch (type) { + case QtDebugMsg: + logMethod = logDebug; + break; + case QtInfoMsg: + logMethod = logInfo; + break; + case QtWarningMsg: + logMethod = logWarning; + break; + case QtCriticalMsg: + logMethod = logError; + break; + case QtFatalMsg: + logMethod = logFatal; + break; + } + QString formattedMessage = qFormatLogMessage(type, context, message); + QJniObject::callStaticMethod(log, logMethod, + QJniObject::fromString(TAG).object(), + QJniObject::fromString(formattedMessage).object()); +} + void AndroidController::qtAndroidControllerInitialized() { callActivityMethod("qtAndroidControllerInitialized", "()V"); diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 481f4b49..48044544 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -34,6 +34,12 @@ public: void saveFile(const QString &fileName, const QString &data); QString openFile(const QString &filter); void startQrReaderActivity(); + void setSaveLogs(bool enabled); + void exportLogsFile(const QString &fileName); + void clearLogs(); + + static bool initLogging(); + static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); signals: void connectionStateChanged(Vpn::ConnectionState state); @@ -53,6 +59,13 @@ signals: private: bool isWaitingStatus = true; + static jclass log; + static jmethodID logDebug; + static jmethodID logInfo; + static jmethodID logWarning; + static jmethodID logError; + static jmethodID logFatal; + void qtAndroidControllerInitialized(); static Vpn::ConnectionState convertState(ConnectionState state); @@ -72,8 +85,7 @@ private: static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); template - static auto callActivityMethod(const char *methodName, const char *signature, - const std::function &defValue, Args &&...args); + static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); template static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); }; diff --git a/client/settings.cpp b/client/settings.cpp index f3fb57a7..475c52e7 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -216,6 +216,7 @@ QString Settings::nextAvailableServerName() const void Settings::setSaveLogs(bool enabled) { setValue("Conf/saveLogs", enabled); +#ifndef Q_OS_ANDROID if (!isSaveLogs()) { Logger::deInit(); } else { @@ -223,7 +224,8 @@ void Settings::setSaveLogs(bool enabled) qWarning() << "Initialization of debug subsystem failed"; } } - emit saveLogsChanged(); +#endif + emit saveLogsChanged(enabled); } QString Settings::routeModeString(RouteMode mode) const diff --git a/client/settings.h b/client/settings.h index 50a28dc4..ed302653 100644 --- a/client/settings.h +++ b/client/settings.h @@ -190,7 +190,7 @@ public: void clearSettings(); signals: - void saveLogsChanged(); + void saveLogsChanged(bool enabled); private: QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f7345608..8ac4cca0 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,6 +8,7 @@ #include "version.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_utils.h" + #include "platforms/android/android_controller.h" #include #endif @@ -91,13 +92,21 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile(const QString &fileName) { +#ifdef Q_OS_ANDROID + AndroidController::instance()->exportLogsFile(fileName); +#else SystemController::saveFile(fileName, Logger::getLogFile()); +#endif } void SettingsController::clearLogs() { +#ifdef Q_OS_ANDROID + AndroidController::instance()->clearLogs(); +#else Logger::clearLogs(); Logger::clearServiceLogs(); +#endif } void SettingsController::backupAppConfig(const QString &fileName) From 47b03f2bf4b5dfcb05d1bf4d3e0c3d8cdec3a799 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 20 Jan 2024 08:44:48 -0500 Subject: [PATCH 17/20] PSK paramater is not mandatory --- client/ui/controllers/importController.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 734c17c3..59aab046 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -243,10 +243,6 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); } else if (!configMap.value("PreSharedKey").isEmpty()) { lastConfig[config_key::psk_key] = configMap.value("PreSharedKey"); - } else { - qDebug() << "Failed to import profile"; - emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); - return QJsonObject(); } lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); From 2b61c48303d97917c1101c8d4df99013b3a8dd70 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 20 Jan 2024 18:16:45 +0300 Subject: [PATCH 18/20] Hide "QR-code" button from config import menu for Android devices without camera --- client/android/src/org/amnezia/vpn/AmneziaActivity.kt | 4 ++++ client/platforms/android/android_controller.cpp | 5 +++++ client/platforms/android/android_controller.h | 1 + client/ui/controllers/settingsController.cpp | 11 +++++++++++ client/ui/controllers/settingsController.h | 2 ++ client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 4 ++-- 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 2ed2c7f7..3583a383 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.Intent.EXTRA_MIME_TYPES import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.ServiceConnection +import android.content.pm.PackageManager import android.net.Uri import android.net.VpnService import android.os.Bundle @@ -418,6 +419,9 @@ class AmneziaActivity : QtActivity() { Log.v(TAG, "Set notification text") } + @Suppress("unused") + fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) + @Suppress("unused") fun startQrCodeReader() { Log.v(TAG, "Start camera") diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 5d442b81..767004fc 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -191,6 +191,11 @@ void AndroidController::setNotificationText(const QString &title, const QString (jint) timerSec); } +bool AndroidController::isCameraPresent() +{ + return callActivityMethod("isCameraPresent", "()Z"); +} + void AndroidController::startQrReaderActivity() { callActivityMethod("startQrCodeReader", "()V"); diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 48044544..86b117f7 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -33,6 +33,7 @@ public: void setNotificationText(const QString &title, const QString &message, int timerSec); void saveFile(const QString &fileName, const QString &data); QString openFile(const QString &filter); + bool isCameraPresent(); void startQrReaderActivity(); void setSaveLogs(bool enabled); void exportLogsFile(const QString &fileName); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 8ac4cca0..c7306a10 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -206,3 +206,14 @@ void SettingsController::toggleScreenshotsEnabled(bool enable) }); #endif } + +bool SettingsController::isCameraPresent() +{ +#if defined Q_OS_IOS + return true; +#elif defined Q_OS_ANDROID + return AndroidController::instance()->isCameraPresent(); +#else + return false; +#endif +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index a6cbc587..5b30b095 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -59,6 +59,8 @@ public slots: bool isScreenshotsEnabled(); void toggleScreenshotsEnabled(bool enable); + bool isCameraPresent(); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 07189eb7..519aa6cc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -90,7 +90,7 @@ It's okay as long as it's from someone you trust.") LabelWithButtonType { Layout.fillWidth: true - visible: GC.isMobile() + visible: SettingsController.isCameraPresent() text: qsTr("QR-code") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -105,7 +105,7 @@ It's okay as long as it's from someone you trust.") } DividerType { - visible: GC.isMobile() + visible: SettingsController.isCameraPresent() } LabelWithButtonType { From 9b7914538fb5b7a2e387334ab4a82ce8f0aa1b41 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 20 Jan 2024 18:42:21 +0300 Subject: [PATCH 19/20] set wg PresharedKey parameter as optional --- .../org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt index 1e74e6ff..76ccd905 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt @@ -11,7 +11,7 @@ open class WireguardConfig protected constructor( val endpoint: InetEndpoint, val persistentKeepalive: Int, val publicKeyHex: String, - val preSharedKeyHex: String, + val preSharedKeyHex: String?, val privateKeyHex: String ) : ProtocolConfig(protocolConfigBuilder) { @@ -43,7 +43,8 @@ open class WireguardConfig protected constructor( appendLine("endpoint=$endpoint") if (persistentKeepalive != 0) appendLine("persistent_keepalive_interval=$persistentKeepalive") - appendLine("preshared_key=$preSharedKeyHex") + if (preSharedKeyHex != null) + appendLine("preshared_key=$preSharedKeyHex") } open class Builder : ProtocolConfig.Builder(true) { @@ -56,7 +57,7 @@ open class WireguardConfig protected constructor( internal lateinit var publicKeyHex: String private set - internal lateinit var preSharedKeyHex: String + internal var preSharedKeyHex: String? = null private set internal lateinit var privateKeyHex: String From 9dfc95bac0caaf188d9aaa93da17d0bd425ee217 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 20 Jan 2024 23:28:20 +0700 Subject: [PATCH 20/20] fixed easy setup default container selection --- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index c393d0ce..f16eec40 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -117,12 +117,14 @@ PageType { } } } + } - Component.onCompleted: { - if (index === containers.currentIndex) { - card.checked = true - card.clicked() - } + Component.onCompleted: { + var item = containers.itemAtIndex(containers.currentIndex) + if (item !== null) { + var button = item.children[0].children[0] + button.checked = true + button.clicked() } } }