diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a218a1ad..cd52e69d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,14 +48,18 @@ 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@v3 + 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@v3 + uses: actions/upload-artifact@v4 with: name: AmneziaVPN_Linux_unpacked path: deploy/AppDir @@ -110,13 +114,14 @@ 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 +205,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 +260,14 @@ 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 +283,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 +382,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/.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/CMakeLists.txt b/CMakeLists.txt index 084ff3d0..c149b3ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.1.0.1 +project(${PROJECT} VERSION 4.2.0.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) 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 diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index fcf3022a..e568e7d0 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit fcf3022a2724402f68cc11bcbed9b43ea9ffcc07 +Subproject commit e568e7d0e8defe8fe009c0127323f2c55fd9be76 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 233eda67..00000000 --- a/client/3rd/awg-apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 233eda6760962efddc860f177a0ce2bcdf533d85 diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain index 8bbaa6d8..74776e2a 160000 --- a/client/3rd/qtkeychain +++ b/client/3rd/qtkeychain @@ -1 +1 @@ -Subproject commit 8bbaa6d8302cf0747d9786ace4dd13c7fb746502 +Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index fe360818..0590b6d1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -168,16 +168,19 @@ void AmneziaApplication::init() } #endif -// Android TextField clipboard workaround -// https://bugreports.qt.io/browse/QTBUG-113461 +// 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 - 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()); - } + connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { + auto clipboard = QGuiApplication::clipboard(); + if (clipboard->mimeData()->hasHtml()) { + clipboard->setText(clipboard->text()); } }); #endif diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 30b77f09..960dc87d 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -22,7 +22,6 @@ - @@ -39,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"> diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 11497274..0c0ec0f9 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -2,6 +2,7 @@ package org.amnezia.vpn import android.content.ComponentName 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.net.Uri @@ -12,11 +13,13 @@ import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger +import android.webkit.MimeTypeMap import android.widget.Toast import androidx.annotation.MainThread import androidx.core.content.ContextCompat import java.io.IOException import kotlin.LazyThreadSafetyMode.NONE +import kotlin.text.RegexOption.IGNORE_CASE import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -35,6 +38,7 @@ private const val TAG = "AmneziaActivity" private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CREATE_FILE_ACTION_CODE = 2 +private const val OPEN_FILE_ACTION_CODE = 3 private const val BIND_SERVICE_TIMEOUT = 1000L class AmneziaActivity : QtActivity() { @@ -201,6 +205,15 @@ class AmneziaActivity : QtActivity() { } } + OPEN_FILE_ACTION_CODE -> { + when (resultCode) { + RESULT_OK -> data?.data?.toString() ?: "" + else -> "" + }.let { uri -> + QtAndroidController.onFileOpened(uri) + } + } + CHECK_VPN_PERMISSION_ACTION_CODE -> { when (resultCode) { RESULT_OK -> { @@ -370,6 +383,36 @@ class AmneziaActivity : QtActivity() { } } + @Suppress("unused") + fun openFile(filter: String?) { + Log.v(TAG, "Open file with filter: $filter") + + val mimeTypes = if (!filter.isNullOrEmpty()) { + val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE) + val mime = MimeTypeMap.getSingleton() + extensionRegex.findAll(filter).map { + mime.getMimeTypeFromExtension(it.value.drop(2)) + }.filterNotNull().toSet() + } else emptySet() + + Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + Log.d(TAG, "File mimyType filter: $mimeTypes") + when (mimeTypes.size) { + 1 -> type = mimeTypes.first() + + in 2..Int.MAX_VALUE -> { + type = "*/*" + putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) + } + + else -> type = "*/*" + } + }.also { + startActivityForResult(it, OPEN_FILE_ACTION_CODE) + } + } + @Suppress("unused") fun setNotificationText(title: String, message: String, timerSec: Int) { Log.v(TAG, "Set notification text") diff --git a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt index bc8cc425..cab810a7 100644 --- a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt +++ b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt @@ -15,6 +15,8 @@ object QtAndroidController { external fun onVpnReconnecting() external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) + external fun onFileOpened(uri: String) + external fun onConfigImported(data: String) external fun decodeQrCode(data: String): Boolean diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake index ca7d659e..ec544764 100644 --- a/client/cmake/3rdparty.cmake +++ b/client/cmake/3rdparty.cmake @@ -90,7 +90,7 @@ include_directories( ${LIBSSH_ROOT_DIR}/include ${CLIENT_ROOT_DIR}/3rd/libssh/include ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include - ${CLIENT_ROOT_DIR}/3rd/qtkeychain + ${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include ) diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index 2d08b4b6..7ffa680e 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -27,7 +27,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ) @@ -35,7 +35,7 @@ set(HEADERS ${HEADERS} set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ) 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/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/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/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index a739bee3..225ceebe 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -1,8 +1,10 @@ -#include #include #include +#include +#include #include "android_controller.h" +#include "android_utils.h" #include "ui/controllers/importController.h" namespace @@ -106,6 +108,7 @@ bool AndroidController::initialize() {"onVpnDisconnected", "()V", reinterpret_cast(onVpnDisconnected)}, {"onVpnReconnecting", "()V", reinterpret_cast(onVpnReconnecting)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast(onStatisticsUpdate)}, + {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast(onFileOpened)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast(onConfigImported)}, {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)} }; @@ -127,7 +130,7 @@ auto AndroidController::callActivityMethod(const char *methodName, const char *s const std::function &defValue, Args &&...args) { qDebug() << "Call activity method:" << methodName; - QJniObject activity = QNativeInterface::QAndroidApplication::context(); + QJniObject activity = AndroidUtils::getActivity(); if (activity.isValid()) { return activity.callMethod(methodName, signature, std::forward(args)...); } else { @@ -165,6 +168,24 @@ void AndroidController::saveFile(const QString &fileName, const QString &data) QJniObject::fromString(data).object()); } +QString AndroidController::openFile(const QString &filter) +{ + QEventLoop wait; + QString fileName; + connect(this, &AndroidController::fileOpened, this, + [&fileName, &wait](const QString &uri) { + qDebug() << "Android event: file opened; uri:" << uri; + fileName = QQmlFile::urlToLocalFileOrQrc(uri); + qDebug() << "Android opened filename:" << fileName; + wait.quit(); + }, + static_cast(Qt::QueuedConnection | Qt::SingleShotConnection)); + callActivityMethod("openFile", "(Ljava/lang/String;)V", + QJniObject::fromString(filter).object()); + wait.exec(); + return fileName; +} + void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) { callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V", @@ -285,20 +306,19 @@ void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBy } // static -void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) +void AndroidController::onFileOpened(JNIEnv *env, jobject thiz, jstring uri) { - Q_UNUSED(env); Q_UNUSED(thiz); - const char *buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - return; - } + emit AndroidController::instance()->fileOpened(AndroidUtils::convertJString(env, uri)); +} - QString config(buffer); - env->ReleaseStringUTFChars(data, buffer); +// static +void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); - emit AndroidController::instance()->configImported(config); + emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data)); } // static @@ -306,12 +326,5 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); - const char *buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - return false; - } - - QString code(buffer); - env->ReleaseStringUTFChars(data, buffer); - return ImportController::decodeQrCode(code); + return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); } diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 4e72cbdf..481f4b49 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -18,7 +18,8 @@ public: bool initialize(); // keep synchronized with org.amnezia.vpn.protocol.ProtocolState - enum class ConnectionState { + enum class ConnectionState + { CONNECTED, CONNECTING, DISCONNECTED, @@ -30,7 +31,8 @@ public: ErrorCode start(const QJsonObject &vpnConfig); void stop(); void setNotificationText(const QString &title, const QString &message, int timerSec); - void saveFile(const QString& fileName, const QString &data); + void saveFile(const QString &fileName, const QString &data); + QString openFile(const QString &filter); void startQrReaderActivity(); signals: @@ -43,6 +45,7 @@ signals: void vpnDisconnected(); void vpnReconnecting(); void statisticsUpdated(quint64 rxBytes, quint64 txBytes); + void fileOpened(QString uri); void configImported(QString config); void importConfigFromOutside(QString config); void initConnectionState(Vpn::ConnectionState state); @@ -65,6 +68,7 @@ private: static void onVpnReconnecting(JNIEnv *env, jobject thiz); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); + static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); template diff --git a/client/platforms/android/android_utils.cpp b/client/platforms/android/android_utils.cpp new file mode 100644 index 00000000..4a994ab0 --- /dev/null +++ b/client/platforms/android/android_utils.cpp @@ -0,0 +1,30 @@ +#include +#include "android_utils.h" + +namespace AndroidUtils +{ + +QJniObject getActivity() +{ + return QNativeInterface::QAndroidApplication::context(); +} + +QString convertJString(JNIEnv *env, jstring data) +{ + int len = env->GetStringLength(data); + QString res(len, Qt::Uninitialized); + env->GetStringRegion(data, 0, len, reinterpret_cast(res.data())); + return res; +} + +void runOnAndroidThreadSync(const std::function &runnable) +{ + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished(); +} + +void runOnAndroidThreadAsync(const std::function &runnable) +{ + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); +} + +} diff --git a/client/platforms/android/android_utils.h b/client/platforms/android/android_utils.h new file mode 100644 index 00000000..9ed58b75 --- /dev/null +++ b/client/platforms/android/android_utils.h @@ -0,0 +1,16 @@ +#ifndef ANDROID_UTILS_H +#define ANDROID_UTILS_H + +#include + +namespace AndroidUtils +{ +QJniObject getActivity(); + +QString convertJString(JNIEnv *env, jstring data); + +void runOnAndroidThreadSync(const std::function &runnable); +void runOnAndroidThreadAsync(const std::function &runnable); +}; + +#endif // ANDROID_UTILS_H diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp deleted file mode 100644 index 7cc39824..00000000 --- a/client/platforms/android/androidutils.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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 "androidutils.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "jni.h" - -namespace -{ - AndroidUtils *s_instance = nullptr; -} // namespace - -// static -QString AndroidUtils::GetDeviceName() -{ - QJniEnvironment env; - jclass BUILD = env->FindClass("android/os/Build"); - jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); - jstring value = (jstring)env->GetStaticObjectField(BUILD, model); - - if (!value) { - return QString("Android Device"); - } - - const char *buffer = env->GetStringUTFChars(value, nullptr); - if (!buffer) { - return QString("Android Device"); - } - - QString res(buffer); - env->ReleaseStringUTFChars(value, buffer); - - return res; -}; - -// static -AndroidUtils *AndroidUtils::instance() -{ - if (!s_instance) { - Q_ASSERT(qApp); - s_instance = new AndroidUtils(qApp); - } - - return s_instance; -} - -AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent) -{ - Q_ASSERT(!s_instance); - s_instance = this; -} - -AndroidUtils::~AndroidUtils() -{ - Q_ASSERT(s_instance == this); - s_instance = nullptr; -} - -// static -void AndroidUtils::dispatchToMainThread(std::function callback) -{ - QTimer *timer = new QTimer(); - timer->moveToThread(qApp->thread()); - timer->setSingleShot(true); - QObject::connect(timer, &QTimer::timeout, [=]() { - callback(); - timer->deleteLater(); - }); - QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection); -} - -// static -QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data) -{ - const char *buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - qDebug() << "getQByteArrayFromJString - failed to parse data."; - return QByteArray(); - } - - QByteArray out(buffer); - env->ReleaseStringUTFChars(data, buffer); - return out; -} - -// static -QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data) -{ - const char *buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - qDebug() << "getQStringFromJString - failed to parse data."; - return QString(); - } - - QString out(buffer); - env->ReleaseStringUTFChars(data, buffer); - return out; -} - -// static -QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data) -{ - QByteArray raw(getQByteArrayFromJString(env, data)); - QJsonParseError jsonError; - QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError); - if (QJsonParseError::NoError != jsonError.error) { - qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error - << "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw; - return QJsonObject(); - } - - if (!json.isObject()) { - qDebug() << "getQJsonObjectFromJString - object expected."; - return QJsonObject(); - } - - return json.object(); -} - -QJniObject AndroidUtils::getActivity() -{ - return QNativeInterface::QAndroidApplication::context(); -} - -int AndroidUtils::GetSDKVersion() -{ - QJniEnvironment env; - jclass versionClass = env->FindClass("android/os/Build$VERSION"); - jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); - int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID); - - return sdk; -} - -QString AndroidUtils::GetManufacturer() -{ - QJniEnvironment env; - jclass buildClass = env->FindClass("android/os/Build"); - jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); - jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); - - const char *buffer = env->GetStringUTFChars(value, nullptr); - - if (!buffer) { - qDebug() << "Failed to fetch MANUFACTURER"; - return QByteArray(); - } - - QString res(buffer); - qDebug() << "MANUFACTURER: " << res; - env->ReleaseStringUTFChars(value, buffer); - return res; -} - -void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) -{ - QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished(); -} - -void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) -{ - QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); -} - -// Static -// Creates a copy of the passed QByteArray in the JVM and passes back a ref -jbyteArray AndroidUtils::tojByteArray(const QByteArray &data) -{ - QJniEnvironment env; - jbyteArray out = env->NewByteArray(data.size()); - env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast(data.constData())); - return out; -} diff --git a/client/platforms/android/androidutils.h b/client/platforms/android/androidutils.h deleted file mode 100644 index 8559400c..00000000 --- a/client/platforms/android/androidutils.h +++ /dev/null @@ -1,49 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -#ifndef ANDROIDUTILS_H -#define ANDROIDUTILS_H - -#include - -#include -#include -#include -#include -#include - -class AndroidUtils final : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY_MOVE(AndroidUtils) - -public: - static QString GetDeviceName(); - - static int GetSDKVersion(); - static QString GetManufacturer(); - - static AndroidUtils* instance(); - - static void dispatchToMainThread(std::function callback); - - static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data); - - static jbyteArray tojByteArray(const QByteArray& data); - - static QString getQStringFromJString(JNIEnv* env, jstring data); - - static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data); - - static QJniObject getActivity(); - - static void runOnAndroidThreadSync(const std::function runnable); - static void runOnAndroidThreadAsync(const std::function runnable); - -private: - AndroidUtils(QObject* parent); - ~AndroidUtils(); -}; - -#endif // ANDROIDUTILS_H 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 diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts index bf93a619..e843f807 100644 --- a/client/translations/amneziavpn_fa_IR.ts +++ b/client/translations/amneziavpn_fa_IR.ts @@ -946,6 +946,11 @@ Already installed containers were found on the server. All installed containers Show other methods on Github نمایش متد‎های دیگر در گیت هاب + + + https://github.com/amnezia-vpn/amnezia-client#donate + + Contacts @@ -1854,6 +1859,11 @@ and will not be shared or disclosed to the Amnezia or any third parties I have nothing من هیچی ندارم + + + https://amnezia.org/instructions/0_starter-guide + + PageSetupWizardTextKey @@ -2178,38 +2188,38 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::DeletePasswordJobPrivate - + Password entry not found Password entry not found - + Could not decrypt data Could not decrypt data - - + + Unknown error Unknown error - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not remove private key from keystore Could not remove private key from keystore @@ -2217,12 +2227,12 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::JobPrivate - + Unknown error Unknown error - + Access to keychain denied Access to keychain denied @@ -2230,27 +2240,27 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::PlainTextStore - + Could not store data in settings: access error Could not store data in settings: access error - + Could not store data in settings: format error Could not store data in settings: format error - + Could not delete data from settings: access error Could not delete data from settings: access error - + Could not delete data from settings: format error Could not delete data from settings: format error - + Entry not found Entry not found @@ -2258,80 +2268,80 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::ReadPasswordJobPrivate - + Password entry not found Password entry not found - - + + Could not decrypt data Could not decrypt data - + D-Bus is not running D-Bus is not running - - + + Unknown error Unknown error - + No keychain service available No keychain service available - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Access to keychain denied Access to keychain denied - + Could not determine data type: %1; %2 Could not determine data type: %1; %2 - - + + Entry not found Entry not found - + Unsupported entry type 'Map' Unsupported entry type 'Map' - + Unknown kwallet entry type '%1' Unknown kwallet entry type '%1' - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not retrieve private key from keystore Could not retrieve private key from keystore - + Could not create decryption cipher Could not create decryption cipher @@ -2339,73 +2349,73 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::WritePasswordJobPrivate - + Credential size exceeds maximum size of %1 Credential size exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Writing credentials failed: Win32 error code %1 Writing credentials failed: Win32 error code %1 - + Encryption failed Encryption failed - + D-Bus is not running D-Bus is not running - - + + Unknown error Unknown error - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not create private key generator Could not create private key generator - + Could not generate new private key Could not generate new private key - + Could not retrieve private key from keystore Could not retrieve private key from keystore - + Could not create encryption cipher Could not create encryption cipher - + Could not encrypt data Could not encrypt data @@ -2855,74 +2865,72 @@ This means that AmneziaWG keeps the fast performance of the original while addin سرویس Sftp - + Entry not found Entry not found - + Access to keychain denied Access to keychain denied - + No keyring daemon No keyring daemon - + Already unlocked Already unlocked - + No such keyring No such keyring - + Bad arguments Bad arguments - + I/O error I/O error - + Cancelled Cancelled - + Keyring already exists Keyring already exists - + No match No match - + Unknown error Unknown error - + error 0x%1: %2 error 0x%1: %2 - WireGuard Configuration Highlighter - هایلایتر پیکربندی WireGuard + هایلایتر پیکربندی WireGuard - &Randomize colors - رنگ‎های تصادفی + رنگ‎های تصادفی @@ -2936,13 +2944,13 @@ This means that AmneziaWG keeps the fast performance of the original while addin Settings - + Server #1 Server #1 - - + + Server Server @@ -2950,22 +2958,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin SettingsController - + Software version نسخه نرم‎افزار - + All settings have been reset to default values تمام تنظیمات به مقادیر پیش فرض ریست شد - + Cached profiles cleared پروفایل ذخیره شده پاک شد - + Backup file is corrupted فایل بک‎آپ خراب شده است diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 58171e67..ed580ec0 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -15,15 +15,13 @@ AndroidController - AmneziaVPN - AmneziaVPN + AmneziaVPN - VPN Connected Refers to the app - which is currently running the background and waiting - VPN Подключен + VPN Подключен @@ -151,7 +149,7 @@ ImportController - + Scanned %1 of %2. Отсканировано %1 из%2. @@ -946,6 +944,11 @@ Already installed containers were found on the server. All installed containers Show other methods on Github Показать другие способы на Github + + + https://github.com/amnezia-vpn/amnezia-client#donate + + Contacts @@ -1428,22 +1431,22 @@ Already installed containers were found on the server. All installed containers Имя сервера - + Save Сохранить - + Protocols Протоколы - + Services Сервисы - + Data Данные @@ -1854,6 +1857,11 @@ and will not be shared or disclosed to the Amnezia or any third parties I have nothing У меня ничего нет + + + https://amnezia.org/instructions/0_starter-guide + + PageSetupWizardTextKey @@ -1941,8 +1949,8 @@ and will not be shared or disclosed to the Amnezia or any third parties Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. - + Server Сервер @@ -2017,7 +2025,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Users Пользователи @@ -2027,47 +2035,52 @@ and will not be shared or disclosed to the Amnezia or any third parties Имя пользователя - + Search - + + Creation date: + + + + Rename - + Client name - + Save Сохранить - + Revoke - - Revoke the config for a user - + + Revoke the config for a user - %1? - + The user will no longer be able to connect to your server. Пользователь больше не сможет подключаться к вашему серверу - + Continue Продолжить - + Cancel Отменить @@ -2081,20 +2094,20 @@ and will not be shared or disclosed to the Amnezia or any third parties Поделиться доступом к VPN, без возможности управления сервером - + Protocol Протокол - + Connection format Формат подключения - + Share Поделиться @@ -2124,12 +2137,12 @@ and will not be shared or disclosed to the Amnezia or any third parties Сервер - + Accessing Доступ - + File with accessing settings to @@ -2160,38 +2173,38 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::DeletePasswordJobPrivate - + Password entry not found Password entry not found - + Could not decrypt data Could not decrypt data - - + + Unknown error Unknown error - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not remove private key from keystore Could not remove private key from keystore @@ -2199,12 +2212,12 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::JobPrivate - + Unknown error Unknown error - + Access to keychain denied Access to keychain denied @@ -2212,27 +2225,27 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::PlainTextStore - + Could not store data in settings: access error Could not store data in settings: access error - + Could not store data in settings: format error Could not store data in settings: format error - + Could not delete data from settings: access error Could not delete data from settings: access error - + Could not delete data from settings: format error Could not delete data from settings: format error - + Entry not found Entry not found @@ -2240,80 +2253,80 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::ReadPasswordJobPrivate - + Password entry not found Password entry not found - - + + Could not decrypt data Could not decrypt data - + D-Bus is not running D-Bus is not running - - + + Unknown error Unknown error - + No keychain service available No keychain service available - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Access to keychain denied Access to keychain denied - + Could not determine data type: %1; %2 Could not determine data type: %1; %2 - - + + Entry not found Entry not found - + Unsupported entry type 'Map' Unsupported entry type 'Map' - + Unknown kwallet entry type '%1' Unknown kwallet entry type '%1' - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not retrieve private key from keystore Could not retrieve private key from keystore - + Could not create decryption cipher Could not create decryption cipher @@ -2321,73 +2334,73 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::WritePasswordJobPrivate - + Credential size exceeds maximum size of %1 Credential size exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Writing credentials failed: Win32 error code %1 Writing credentials failed: Win32 error code %1 - + Encryption failed Encryption failed - + D-Bus is not running D-Bus is not running - - + + Unknown error Unknown error - + Could not open wallet: %1; %2 Could not open wallet: %1; %2 - + Password not found Password not found - + Could not open keystore Could not open keystore - + Could not create private key generator Could not create private key generator - + Could not generate new private key Could not generate new private key - + Could not retrieve private key from keystore Could not retrieve private key from keystore - + Could not create encryption cipher Could not create encryption cipher - + Could not encrypt data Could not encrypt data @@ -2594,7 +2607,12 @@ and will not be shared or disclosed to the Amnezia or any third parties VPN pool error: no available addresses - + + VPN connection error + + + + Internal error Internal error @@ -2822,62 +2840,62 @@ This means that AmneziaWG keeps the fast performance of the original while addin Сервис SFTP - + Entry not found Entry not found - + Access to keychain denied Access to keychain denied - + No keyring daemon No keyring daemon - + Already unlocked Already unlocked - + No such keyring No such keyring - + Bad arguments Bad arguments - + I/O error I/O error - + Cancelled Cancelled - + Keyring already exists Keyring already exists - + No match No match - + Unknown error Unknown error - + error 0x%1: %2 error 0x%1: %2 @@ -2893,13 +2911,13 @@ This means that AmneziaWG keeps the fast performance of the original while addin Settings - + Server #1 Server #1 - - + + Server Server @@ -2907,22 +2925,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin SettingsController - + Software version Версия ПО - + All settings have been reset to default values Все настройки были сброшены к значению "По умолчанию" - + Cached profiles cleared Кэш профиля очищен - + Backup file is corrupted Backup файл поврежден @@ -3054,7 +3072,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin VpnConnection - + Mbps Mbps diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index e63344d8..7143c76b 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -11,15 +11,9 @@ AndroidController - - AmneziaVPN - - - - VPN Connected Refers to the app - which is currently running the background and waiting - VPN已连接 + VPN已连接 @@ -158,7 +152,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -997,6 +991,11 @@ And if you don't like the app, all the more support it - the donation will Show other methods on Github 其他捐款途径 + + + https://github.com/amnezia-vpn/amnezia-client#donate + + Contacts @@ -1511,22 +1510,22 @@ And if you don't like the app, all the more support it - the donation will 服务器名 - + Save 保存 - + Protocols 协议 - + Services 服务 - + Data 数据 @@ -1957,6 +1956,11 @@ and will not be shared or disclosed to the Amnezia or any third parties I have nothing 我没有 + + + https://amnezia.org/instructions/0_starter-guide + + PageSetupWizardTextKey @@ -2078,7 +2082,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Users @@ -2088,47 +2092,52 @@ and will not be shared or disclosed to the Amnezia or any third parties 共享 VPN 访问,无需管理服务器 - + Search - + + Creation date: + + + + Rename - + Client name - + Save 保存 - + Revoke - - Revoke the config for a user - + + Revoke the config for a user - %1? - + The user will no longer be able to connect to your server. - + Continue 继续 - + Cancel 取消 @@ -2170,8 +2179,8 @@ and will not be shared or disclosed to the Amnezia or any third parties 服务器 - + Server 服务器 @@ -2193,8 +2202,8 @@ and will not be shared or disclosed to the Amnezia or any third parties 协议 - + Protocol 协议 @@ -2214,14 +2223,14 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Connection format 连接格式 - + Share 共享 @@ -2251,12 +2260,12 @@ and will not be shared or disclosed to the Amnezia or any third parties 服务器 - + Accessing 访问 - + File with accessing settings to 访问配置文件的内容为: @@ -2287,38 +2296,38 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::DeletePasswordJobPrivate - + Password entry not found 未发现秘密 - + Could not decrypt data 数据无法加密 - - + + Unknown error 未知错误 - + Could not open wallet: %1; %2 无法打开钱包: %1; %2 - + Password not found 未发现密码 - + Could not open keystore 无法打开密钥库 - + Could not remove private key from keystore 无法从密钥库中删除私钥 @@ -2326,12 +2335,12 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::JobPrivate - + Unknown error 未知错误 - + Access to keychain denied 访问钥匙串被拒绝 @@ -2339,27 +2348,27 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::PlainTextStore - + Could not store data in settings: access error 无法在配置中存储数据:访问错误 - + Could not store data in settings: format error 无法在陪置中存储数据:格式错误 - + Could not delete data from settings: access error 无法在配置中删除数据:访问错误 - + Could not delete data from settings: format error 无法在配置中删除数据:格式错误 - + Entry not found 未找到条目 @@ -2367,80 +2376,80 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::ReadPasswordJobPrivate - + Password entry not found 未发现密码 - - + + Could not decrypt data 数据无法加密 - + D-Bus is not running D-Bus未运行 - - + + Unknown error 未知错误 - + No keychain service available 没有有效的钥匙串服务 - + Could not open wallet: %1; %2 无法打开钱包: %1; %2 - + Access to keychain denied 访问钥匙串被拒绝 - + Could not determine data type: %1; %2 无法确定数据类型: %1; %2 - - + + Entry not found 未找到记录 - + Unsupported entry type 'Map' 不支持的记录类型 'Map' - + Unknown kwallet entry type '%1' 未知钱包类型 '%1' - + Password not found 未发现密码 - + Could not open keystore 无法打开密钥库 - + Could not retrieve private key from keystore 无法从密钥存储库中检索私钥 - + Could not create decryption cipher 无法创建解密算法 @@ -2448,73 +2457,73 @@ and will not be shared or disclosed to the Amnezia or any third parties QKeychain::WritePasswordJobPrivate - + Credential size exceeds maximum size of %1 证书大小超过上限,最大为: %1 - + Credential key exceeds maximum size of %1 凭证密钥大小超过上限,最大为: %1 - + Writing credentials failed: Win32 error code %1 写入凭证失败,Win32错误码: %1 - + Encryption failed 加密失败 - + D-Bus is not running D-Bus未运行 - - + + Unknown error 未知错误 - + Could not open wallet: %1; %2 无法打开钱包: %1; %2 - + Password not found 未发现密码 - + Could not open keystore 无法打开密钥库 - + Could not create private key generator 无法创建私钥生成器 - + Could not generate new private key 无法生成新的私钥 - + Could not retrieve private key from keystore 无法从密钥库检索私钥 - + Could not create encryption cipher 无法创建加密密码 - + Could not encrypt data 无法加密数据 @@ -2666,6 +2675,11 @@ and will not be shared or disclosed to the Amnezia or any third parties Sftp error: No media was in remote drive Sftp 错误: 远程驱动器中没有媒介 + + + VPN connection error + + Failed to save config to disk 配置保存到磁盘失败 @@ -2730,7 +2744,7 @@ and will not be shared or disclosed to the Amnezia or any third parties 该配置不包含任何用于连接到服务器的容器和凭据。 - + Internal error 内部错误 @@ -2970,62 +2984,62 @@ While it offers a blend of security, stability, and speed, it's essential t Sftp 文件共享服务 - 安全的 FTP 服务 - + Entry not found 未找到记录 - + Access to keychain denied 访问钥匙串被拒绝 - + No keyring daemon 没有密钥环守护进程 - + Already unlocked 已经解锁 - + No such keyring 没有这样的密钥环 - + Bad arguments 错误参数 - + I/O error I/O错误 - + Cancelled 已取消 - + Keyring already exists 密匙环已经存在 - + No match 不匹配 - + Unknown error 未知错误 - + error 0x%1: %2 错误 0x%1: %2 @@ -3041,13 +3055,13 @@ While it offers a blend of security, stability, and speed, it's essential t Settings - + Server #1 - - + + Server 服务器 @@ -3055,22 +3069,22 @@ While it offers a blend of security, stability, and speed, it's essential t SettingsController - + Software version 软件版本 - + Backup file is corrupted 备份文件已损坏 - + All settings have been reset to default values 所配置恢复为默认值 - + Cached profiles cleared 缓存的配置文件已清除 @@ -3206,7 +3220,7 @@ While it offers a blend of security, stability, and speed, it's essential t VpnConnection - + Mbps diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 9209f4cd..4f3fe7d5 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -15,7 +15,7 @@ #include "core/errorstrings.h" #include "systemController.h" #ifdef Q_OS_ANDROID - #include "platforms/android/androidutils.h" + #include "platforms/android/android_utils.h" #endif #include "qrcodegen.hpp" diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index ed60500a..105f2115 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -7,7 +7,7 @@ #endif #ifdef Q_OS_ANDROID - #include "../../platforms/android/androidutils.h" + #include "platforms/android/android_utils.h" #include #endif #if defined Q_OS_MAC diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 73b9d276..f7345608 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -7,8 +7,7 @@ #include "ui/qautostart.h" #include "version.h" #ifdef Q_OS_ANDROID - #include "../../platforms/android/android_controller.h" - #include "../../platforms/android/androidutils.h" + #include "platforms/android/android_utils.h" #include #endif diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 96fc2792..ecd68c8f 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -60,6 +60,11 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) { QString fileName; +#ifdef Q_OS_ANDROID + Q_ASSERT(!isSaveMode); + return AndroidController::instance()->openFile(nameFilter); +#endif + #ifdef Q_OS_IOS MobileUtils mobileUtils; @@ -108,20 +113,6 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString } fileName = mainFileDialog->property("selectedFile").toString(); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep { "raw%3A%2F" }; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } - - return fileName; -#endif - return QUrl(fileName).toLocalFile(); } 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"; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b8bfbbc5..1922e188 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(); } @@ -526,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/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/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/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() diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 873ae997..4300d591 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("Default server does not support split tunneling function")) + } } Connections { 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() 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] = ...; 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 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