Merge branch 'dev' into feature/linux-ipsec

This commit is contained in:
Pokamest Nikak 2024-09-11 19:41:20 +01:00
commit 1438a21902
68 changed files with 1608 additions and 1842 deletions

View file

@ -297,24 +297,24 @@ jobs:
env:
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.6.2
QT_VERSION: 6.7.2
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
steps:
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -325,7 +325,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -336,7 +336,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
@ -347,7 +347,7 @@ jobs:
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'linux'

@ -1 +1 @@
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88

View file

@ -27,6 +27,9 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
@ -110,6 +113,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
@ -131,7 +135,6 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
@ -140,6 +143,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
)
# Mozilla headres
@ -190,6 +194,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
)
# Mozilla sources

View file

@ -164,7 +164,7 @@ void AmneziaApplication::init()
bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID
if (enabled) {
if (!Logger::init()) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}

View file

@ -3,7 +3,6 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:installLocation="auto">
@ -46,7 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:exported="true">
<intent-filter>
@ -68,9 +67,6 @@
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
</activity>
<activity
@ -88,6 +84,13 @@
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".AuthActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"

View file

@ -3,3 +3,6 @@
// android.bundle.enableUncompressedNativeLibs is deprecated
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
useLegacyPackaging
// package name for androiddeployqt
namespace = "org.amnezia.vpn"

View file

@ -115,9 +115,11 @@ dependencies {
implementation(project(":xray"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.fragment)
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.protobuf)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
implementation(libs.androidx.biometric)
}

View file

@ -1,24 +1,28 @@
[versions]
agp = "8.2.0"
kotlin = "1.9.20"
androidx-core = "1.12.0"
androidx-activity = "1.8.1"
androidx-annotation = "1.7.0"
androidx-camera = "1.3.0"
agp = "8.5.2"
kotlin = "1.9.24"
androidx-core = "1.13.1"
androidx-activity = "1.9.1"
androidx-annotation = "1.8.2"
androidx-biometric = "1.2.0-alpha05"
androidx-camera = "1.3.4"
androidx-fragment = "1.8.2"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3"
androidx-datastore = "1.1.1"
kotlinx-coroutines = "1.8.1"
kotlinx-serialization = "1.6.3"
google-mlkit = "17.2.0"
google-mlkit = "17.3.0"
[libraries]
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }

Binary file not shown.

View file

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

View file

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View file

@ -21,5 +21,5 @@ android {
}
dependencies {
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
}

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF0E0E11</color>
<style name="NoActionBar">
<item name="android:windowBackground">@color/black</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>

View file

@ -22,7 +22,7 @@ dependencyResolutionManagement {
includeBuild("./gradle/plugins")
plugins {
id("com.android.settings") version "8.2.0"
id("com.android.settings") version "8.5.2"
id("settings-property-delegate")
}

View file

@ -158,6 +158,10 @@ class AmneziaActivity : QtActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity: $intent")
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@ -610,6 +614,14 @@ class AmneziaActivity : QtActivity() {
}
}
@Suppress("unused")
fun setNavigationBarColor(color: Int) {
Log.v(TAG, "Change navigation bar color: ${"#%08X".format(color)}")
mainScope.launch {
window.navigationBarColor = color
}
}
@Suppress("unused")
fun minimizeApp() {
Log.v(TAG, "Minimize application")
@ -684,6 +696,17 @@ class AmneziaActivity : QtActivity() {
.show()
}
@Suppress("unused")
fun requestAuthentication() {
Log.v(TAG, "Request authentication")
mainScope.launch {
qtInitialized.await()
Intent(this@AmneziaActivity, AuthActivity::class.java).also {
startActivity(it)
}
}
}
/**
* Utils methods
*/

View file

@ -0,0 +1,97 @@
package org.amnezia.vpn
import android.os.Build
import android.os.Bundle
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationResult
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import org.amnezia.vpn.qt.QtAndroidController
import org.amnezia.vpn.util.Log
private const val TAG = "AuthActivity"
private const val AUTHENTICATORS = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
class AuthActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val biometricManager = BiometricManager.from(applicationContext)
when (biometricManager.canAuthenticate(AUTHENTICATORS)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
showBiometricPrompt(biometricManager)
return
}
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
Log.w(TAG, "Unknown biometric status")
showBiometricPrompt(biometricManager)
return
}
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
Log.e(TAG, "The specified options are incompatible with the current Android " +
"version ${Build.VERSION.SDK_INT}")
}
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
Log.w(TAG, "The hardware is unavailable")
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
Log.w(TAG, "No biometric or device credential is enrolled")
}
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
Log.w(TAG, "There is no suitable hardware")
}
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
Log.w(TAG, "A security vulnerability has been discovered with one or " +
"more hardware sensors")
}
}
QtAndroidController.onAuthResult(true)
finish()
}
private fun showBiometricPrompt(biometricManager: BiometricManager) {
val executor = ContextCompat.getMainExecutor(applicationContext)
val biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.d(TAG, "Authentication succeeded")
QtAndroidController.onAuthResult(true)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.w(TAG, "Authentication failed")
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.e(TAG, "Authentication error $errorCode: $errString")
QtAndroidController.onAuthResult(false)
finish()
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setAllowedAuthenticators(AUTHENTICATORS)
.setTitle("AmneziaVPN")
.setSubtitle(biometricManager.getStrings(AUTHENTICATORS)?.promptMessage)
.build()
biometricPrompt.authenticate(promptInfo)
}
}

View file

@ -1,24 +0,0 @@
package org.amnezia.vpn;
import android.content.Context;
import android.app.KeyguardManager;
import android.content.Intent;
import org.qtproject.qt.android.bindings.QtActivity;
import static android.content.Context.KEYGUARD_SERVICE;
public class AuthHelper extends QtActivity {
static final String TAG = "AuthHelper";
public static Intent getAuthIntent(Context context) {
KeyguardManager mKeyguardManager = (KeyguardManager)context.getSystemService(KEYGUARD_SERVICE);
if (mKeyguardManager.isDeviceSecure()) {
return mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
} else {
return null;
}
}
}

View file

@ -33,10 +33,10 @@ class ImportConfigActivity : ComponentActivity() {
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent")
intent?.let(::readConfig)
intent.let(::readConfig)
}
private fun readConfig(intent: Intent) {

View file

@ -25,5 +25,7 @@ object QtAndroidController {
external fun onConfigImported(data: String)
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
}

View file

@ -88,7 +88,7 @@ class NetworkState(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val numberAttempts = 3
val numberAttempts = 300
var attemptCount = 0
while(true) {
try {

View file

@ -27,7 +27,6 @@ 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_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
)
@ -35,7 +34,6 @@ set(HEADERS ${HEADERS}
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.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
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
)

View file

@ -9,8 +9,8 @@
#include "QRsa.h"
#include "amnezia_application.h"
#include "core/enums/apiEnums.h"
#include "configurators/wireguard_configurator.h"
#include "core/enums/apiEnums.h"
#include "version.h"
namespace
@ -42,7 +42,7 @@ namespace
constexpr char keyPayload[] = "key_payload";
}
const QStringList proxyStorageUrl = {""};
const QStringList proxyStorageUrl = { "" };
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
{
@ -65,7 +65,8 @@ namespace
}
}
ApiController::ApiController(const QString &gatewayEndpoint, QObject *parent) : QObject(parent), m_gatewayEndpoint(gatewayEndpoint)
ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment)
{
}
@ -143,7 +144,7 @@ QStringList ApiController::getProxyUrls()
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply* reply;
QNetworkReply *reply;
for (const auto &proxyStorageUrl : proxyStorageUrl) {
request.setUrl(proxyStorageUrl);
@ -281,7 +282,7 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
QNetworkReply* reply;
QNetworkReply *reply;
reply = amnApp->manager()->get(request);
QEventLoop wait;
@ -300,7 +301,8 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() != QNetworkReply::NetworkError::TimeoutError && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
break;
}
reply->deleteLater();
@ -355,7 +357,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
EVP_PKEY *publicKey = nullptr;
try {
QByteArray key = PROD_AGW_PUBLIC_KEY;
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(key);
} catch (...) {
@ -375,7 +377,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply* reply = manager.post(request, QJsonDocument(requestBody).toJson());
QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson());
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
@ -395,7 +397,8 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() != QNetworkReply::NetworkError::TimeoutError && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
break;
}
reply->deleteLater();

View file

@ -14,7 +14,7 @@ class ApiController : public QObject
Q_OBJECT
public:
explicit ApiController(const QString &gatewayEndpoint, QObject *parent = nullptr);
explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr);
public slots:
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
@ -44,6 +44,7 @@ private:
QString m_gatewayEndpoint;
QStringList m_proxyUrls;
bool m_isDevEnvironment;
};
#endif // APICONTROLLER_H

View file

@ -83,7 +83,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
}
qDebug().noquote() << lineToExec;
Logger::appendSshLog("Run command:" + lineToExec);
error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr);
if (error != ErrorCode::NoError) {
@ -100,7 +99,6 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
{
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
if (e)

View file

@ -1,107 +0,0 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QString>
#include <QTextStream>
#include "ui/property_helper.h"
#include "mozilla/shared/loglevel.h"
class Logger : public QObject
{
Q_OBJECT
AUTO_PROPERTY(QString, sshLog)
AUTO_PROPERTY(QString, allLog)
public:
static Logger& Instance();
static void appendSshLog(const QString &log);
static void appendAllLog(const QString &log);
static bool init();
static void deInit();
static bool setServiceLogsEnabled(bool enabled);
static bool openLogsFolder();
static bool openServiceLogsFolder();
static QString appLogFileNamePath();
static void clearLogs();
static void clearServiceLogs();
static void cleanUp();
static QString userLogsFilePath();
static QString getLogFile();
// compat with Mozilla logger
Logger(const QString &className) { m_className = className; }
const QString& className() const { return m_className; }
class Log {
public:
Log(Logger* logger, LogLevel level);
~Log();
Log& operator<<(uint64_t t);
Log& operator<<(const char* t);
Log& operator<<(const QString& t);
Log& operator<<(const QStringList& t);
Log& operator<<(const QByteArray& t);
Log& operator<<(const QJsonObject& t);
Log& operator<<(QTextStreamFunction t);
Log& operator<<(const void* t);
// Q_ENUM
template <typename T>
typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, Log&>::type
operator<<(T t) {
const QMetaObject* meta = qt_getEnumMetaObject(t);
const char* name = qt_getEnumName(t);
addMetaEnum(typename QFlags<T>::Int(t), meta, name);
return *this;
}
private:
void addMetaEnum(quint64 value, const QMetaObject* meta, const char* name);
Logger* m_logger;
LogLevel m_logLevel;
struct Data {
Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {}
QString m_buffer;
QTextStream m_ts;
};
Data* m_data;
};
Log error();
Log warning();
Log info();
Log debug();
QString sensitive(const QString& input);
private:
Logger() {}
Logger(Logger const &) = delete;
Logger& operator= (Logger const&) = delete;
static QString userLogsDir();
static QFile m_file;
static QTextStream m_textStream;
static QString m_logFileName;
friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
// compat with Mozilla logger
QString m_className;
};
#endif // LOGGER_H

View file

@ -98,6 +98,7 @@ bool AndroidController::initialize()
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
};
@ -210,6 +211,11 @@ void AndroidController::setScreenshotsEnabled(bool enabled)
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
}
void AndroidController::setNavigationBarColor(unsigned int color)
{
callActivityMethod("setNavigationBarColor", "(I)V", color);
}
void AndroidController::minimizeApp()
{
callActivityMethod("minimizeApp", "()V");
@ -265,6 +271,22 @@ void AndroidController::requestNotificationPermission()
callActivityMethod("requestNotificationPermission", "()V");
}
bool AndroidController::requestAuthentication()
{
QEventLoop wait;
bool result;
connect(this, &AndroidController::authenticationResult, this,
[&result, &wait](const bool &authResult){
qDebug() << "Android authentication result:" << authResult;
result = authResult;
wait.quit();
},
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
callActivityMethod("requestAuthentication", "()V");
wait.exec();
return result;
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
@ -462,6 +484,14 @@ void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data
emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data));
}
// static
void AndroidController::onAuthResult(JNIEnv *env, jobject thiz, jboolean result)
{
Q_UNUSED(thiz);
emit AndroidController::instance()->authenticationResult(result);
}
// static
bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
{

View file

@ -41,11 +41,13 @@ public:
void exportLogsFile(const QString &fileName);
void clearLogs();
void setScreenshotsEnabled(bool enabled);
void setNavigationBarColor(unsigned int color);
void minimizeApp();
QJsonArray getAppList();
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
bool isNotificationPermissionGranted();
void requestNotificationPermission();
bool requestAuthentication();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@ -63,6 +65,7 @@ signals:
void configImported(QString config);
void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result);
private:
bool isWaitingStatus = true;
@ -89,6 +92,7 @@ private:
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 void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args>

View file

@ -1,16 +0,0 @@
#include "authResultReceiver.h"
AuthResultReceiver::AuthResultReceiver(QSharedPointer<AuthResultNotifier> &notifier) : m_notifier(notifier)
{
}
void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
{
qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode;
if (resultCode == -1) { // ResultOK
emit m_notifier->authSuccessful();
} else {
emit m_notifier->authFailed();
}
}

View file

@ -1,32 +0,0 @@
#ifndef AUTHRESULTRECEIVER_H
#define AUTHRESULTRECEIVER_H
#include <QJniObject>
#include <private/qandroidextras_p.h>
class AuthResultNotifier : public QObject
{
Q_OBJECT
public:
AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {};
signals:
void authFailed();
void authSuccessful();
};
/* Auth result handler for Android */
class AuthResultReceiver final : public QAndroidActivityResultReceiver
{
public:
AuthResultReceiver(QSharedPointer<AuthResultNotifier> &notifier);
void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override;
private:
QSharedPointer<AuthResultNotifier> m_notifier;
};
#endif // AUTHRESULTRECEIVER_H

View file

@ -174,13 +174,25 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QSimpleCrypto::QBlockCipher cipher;
return cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv());
QByteArray result;
try {
result = cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when encrypting the settings value";
}
return result;
}
QByteArray SecureQSettings::decryptText(const QByteArray &ba) const
{
QSimpleCrypto::QBlockCipher cipher;
return cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv());
QByteArray result;
try {
result = cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when decrypting the settings value";
}
return result;
}
bool SecureQSettings::encryptionRequired() const

View file

@ -227,7 +227,7 @@ void Settings::setSaveLogs(bool enabled)
if (!isSaveLogs()) {
Logger::deInit();
} else {
if (!Logger::init()) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
@ -519,7 +519,22 @@ void Settings::setGatewayEndpoint(const QString &endpoint)
m_gatewayEndpoint = endpoint;
}
void Settings::setDevGatewayEndpoint()
{
m_gatewayEndpoint = DEV_AGW_ENDPOINT;
}
QString Settings::getGatewayEndpoint()
{
return m_gatewayEndpoint;
}
bool Settings::isDevGatewayEnv()
{
return m_isDevGatewayEnv;
}
void Settings::toggleDevGatewayEnv(bool enabled)
{
m_isDevGatewayEnv = enabled;
}

View file

@ -183,7 +183,7 @@ public:
bool isScreenshotsEnabled() const
{
return value("Conf/screenshotsEnabled", false).toBool();
return value("Conf/screenshotsEnabled", true).toBool();
}
void setScreenshotsEnabled(bool enabled)
{
@ -217,7 +217,10 @@ public:
void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint);
void setDevGatewayEndpoint();
QString getGatewayEndpoint();
bool isDevGatewayEnv();
void toggleDevGatewayEnv(bool enabled);
signals:
void saveLogsChanged(bool enabled);
@ -234,6 +237,7 @@ private:
mutable SecureQSettings m_settings;
QString m_gatewayEndpoint;
bool m_isDevGatewayEnv;
};
#endif // SETTINGS_H

View file

@ -6,47 +6,47 @@
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="63"/>
<source>Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/s</source>
<translation type="unfinished"></translation>
<translation>شبكة VPN كلاسيكية للعمل المريح وتنزيل الملفات الكبيرة ومشاهدة مقاطع الفيديو. تعمل مع أي موقع. تصل السرعة إلى %1 ميجابت/ثانية</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="67"/>
<source>VPN to access blocked sites in regions with high levels of Internet censorship. </source>
<translation type="unfinished"></translation>
<translation>شبكة VPN للولوج للمواقع المحظورة في بلاد ذو مستوي عالي من الرقابة علي الانترنت. </translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="72"/>
<source>Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship.</source>
<translation type="unfinished"></translation>
<translation>Amenzia Premium - شبكة VPN للعمل المريح, تحميل ملفات كبيرة الحجم, ومشاهدة مقاطع الفيديو ب جودة عالية. تعمل لجميع المواقع, حتي في البلاد ذو مستوي عالي من الرقابة علي الانترنت</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="75"/>
<source>Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship</source>
<translation type="unfinished"></translation>
<translation>Amnezia Free هو VPN مجاني لتخطي الحظر في البلاد ذو مستوي عالي من الرقابة علي الانترنت</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="80"/>
<source>%1 MBit/s</source>
<translation type="unfinished"></translation>
<translation>%1 ميجابت/ثانية</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="87"/>
<source>%1 days</source>
<translation type="unfinished"></translation>
<translation>%1 ايام</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="96"/>
<source>VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, &lt;a href=&quot;%1/free&quot; style=&quot;color: #FBB26A;&quot;&gt;more details on the website.&lt;/a&gt;</source>
<translation type="unfinished"></translation>
<translation>سيقوم VPN فقط بفتح المواقع المشهورة المحظورة في بلدك, مثل Instagram, Facebook, Twitter و مواقع اخري. المواقع الاخري ستٌفتح من عنوان ال IP الحقيقي الخاص بك, &lt;a href=&quot;%1/free&quot; style=&quot;color: #FBB26A;&quot;&gt;معلومات اخري علي الموقع.&lt;/a&gt;</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="104"/>
<source>Free</source>
<translation type="unfinished"></translation>
<translation>مجاني</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="106"/>
<source>%1 $/month</source>
<translation type="unfinished"></translation>
<translation>%1 دولار/الشهر</translation>
</message>
</context>
<context>
@ -325,7 +325,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/controllers/installController.cpp" line="608"/>
<source>Api config removed</source>
<translation type="unfinished"></translation>
<translation>تم حذف تكوين Api</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="630"/>
@ -345,17 +345,17 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/controllers/installController.cpp" line="845"/>
<source>%1 installed successfully.</source>
<translation type="unfinished"></translation>
<translation>تم تحميل %1 بنجاح</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="877"/>
<source>API config reloaded</source>
<translation type="unfinished"></translation>
<translation>تمت إعادة تحميل تكوين API</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="881"/>
<source>Successfully changed the country of connection to %1</source>
<translation type="unfinished"></translation>
<translation>تم تغيير بلد الاتصال بنجاح إلى %1</translation>
</message>
</context>
<context>
@ -441,7 +441,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageDevMenu.qml" line="74"/>
<source>Gateway endpoint</source>
<translation type="unfinished"></translation>
<translation>نقطة نهاية البوابة</translation>
</message>
</context>
<context>
@ -507,47 +507,47 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="147"/>
<source>Jc - Junk packet count</source>
<translation type="unfinished"></translation>
<translation>Jc - عدد الحزم غير المرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="172"/>
<source>Jmin - Junk packet minimum size</source>
<translation type="unfinished"></translation>
<translation>Jmin - الحجم الادني للحزم الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="193"/>
<source>Jmax - Junk packet maximum size</source>
<translation type="unfinished"></translation>
<translation>Jmax - الحجم الاقصي للحزم الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="214"/>
<source>S1 - Init packet junk size</source>
<translation type="unfinished"></translation>
<translation>S1 - حجم حزمة البيانات العشوائية الأولية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="235"/>
<source>S2 - Response packet junk size</source>
<translation type="unfinished"></translation>
<translation>S2 - حجم حزمة الاستجابة غير المرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="256"/>
<source>H1 - Init packet magic header</source>
<translation type="unfinished"></translation>
<translation>H1 - حزمة رأس سحرية مبدئية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="277"/>
<source>H2 - Response packet magic header</source>
<translation type="unfinished"></translation>
<translation>H2 - رأس حزمة الاستجابة السحرية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="298"/>
<source>H4 - Transport packet magic header</source>
<translation type="unfinished"></translation>
<translation>H4 - رأس حزمة النقل السحرية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="320"/>
<source>H3 - Underload packet magic header</source>
<translation type="unfinished"></translation>
<translation>H3 - رأس حزمة السحر غير المحمل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="363"/>
@ -1164,7 +1164,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="137"/>
<source>Dev console</source>
<translation type="unfinished"></translation>
<translation>وحدة تحكم التطوير</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="158"/>
@ -1250,74 +1250,74 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="45"/>
<source>For the region</source>
<translation type="unfinished"></translation>
<translation>للمنطقة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="54"/>
<source>Price</source>
<translation type="unfinished"></translation>
<translation>السعر</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="63"/>
<source>Work period</source>
<translation type="unfinished"></translation>
<translation>مدة العمل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="74"/>
<source>Speed</source>
<translation type="unfinished"></translation>
<translation>السرعة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="106"/>
<source>Support tag</source>
<translation type="unfinished"></translation>
<translation>علامة الدعم</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="119"/>
<source>Copied</source>
<translation type="unfinished"></translation>
<translation>تم النسخ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="139"/>
<source>Reload API config</source>
<translation type="unfinished"></translation>
<translation>إعادة تحميل تكوين API</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="144"/>
<source>Reload API config?</source>
<translation type="unfinished"></translation>
<translation>إعادة تحميل تكوين API</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="185"/>
<source>Continue</source>
<translation type="unfinished">واصل</translation>
<translation>واصل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="146"/>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="186"/>
<source>Cancel</source>
<translation type="unfinished">إلغاء</translation>
<translation>إلغاء</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="150"/>
<source>Cannot reload API config during active connection</source>
<translation type="unfinished"></translation>
<translation>لا يمكن إعادة تحميل تكوين API اثناء تواجد اتصال نشط</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="179"/>
<source>Remove from application</source>
<translation type="unfinished"></translation>
<translation>احذف من التطبيق</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="184"/>
<source>Remove from application?</source>
<translation type="unfinished"></translation>
<translation>احذف من التطبيق؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="190"/>
<source>Cannot remove server during active connection</source>
<translation type="unfinished">لا يمكن إزالة الخادم أثناء الاتصال النشط</translation>
<translation>لا يمكن إزالة الخادم أثناء الاتصال النشط</translation>
</message>
</context>
<context>
@ -1769,7 +1769,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="33"/>
<source>No new installed containers found</source>
<translation>لم يتم العثور علي اي حاويات جديدة مٌثبتة</translation>
<translation>لم يتم العثور علي اي خدمات مٌثبتة سابقاً</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="127"/>
@ -1941,7 +1941,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="190"/>
<source>Remove %1 from server?</source>
<translation type="unfinished"></translation>
<translation>احذف %1 من الخادم؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="191"/>
@ -2080,32 +2080,32 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="62"/>
<source>For the region</source>
<translation type="unfinished"></translation>
<translation>للمنطقة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="71"/>
<source>Price</source>
<translation type="unfinished"></translation>
<translation>السعر</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="80"/>
<source>Work period</source>
<translation type="unfinished"></translation>
<translation>مدة العمل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="91"/>
<source>Speed</source>
<translation type="unfinished"></translation>
<translation>السرعة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="100"/>
<source>Features</source>
<translation type="unfinished"></translation>
<translation>المميزات</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="139"/>
<source>Connect</source>
<translation type="unfinished">اتصل</translation>
<translation>اتصل</translation>
</message>
</context>
<context>
@ -2113,96 +2113,80 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServicesList.qml" line="52"/>
<source>VPN by Amnezia</source>
<translation type="unfinished"></translation>
<translation>VPN بواسطة Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServicesList.qml" line="53"/>
<source>Choose a VPN service that suits your needs.</source>
<translation type="unfinished"></translation>
<translation>اختر خدمة VPN تلبي احتياجاتك</translation>
</message>
</context>
<context>
<name>PageSetupWizardConfigSource</name>
<message>
<source>Server connection</source>
<translation type="vanished">اتصال الخادم</translation>
</message>
<message>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="vanished">لا تستخدم رموز اتصال من مصادر غير موثوقة, حيث قد يكون تم إنشاؤها لاعتراض بياناتك.</translation>
</message>
<message>
<source>What do you have?</source>
<translation type="vanished">ماذا لديك؟</translation>
</message>
<message>
<source>File with connection settings or backup</source>
<translation type="vanished">ملف إعدادات اتصال او نسخ احتياطي</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="57"/>
<source>Connection</source>
<translation type="unfinished">الاتصال</translation>
<translation>الاتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>Insert the key, add a configuration file or scan the QR-code</source>
<translation type="unfinished"></translation>
<translation>أدخل المفتاح، أضف ملف تكوين أو امسح رمز الاستجابة السريعة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="77"/>
<source>Insert key</source>
<translation type="unfinished"></translation>
<translation>أدخل مفتاح</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="78"/>
<source>Insert</source>
<translation type="unfinished">ادخل</translation>
<translation>أدخل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="98"/>
<source>Continue</source>
<translation type="unfinished">واصل</translation>
<translation>واصل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="116"/>
<source>Other connection options</source>
<translation type="unfinished"></translation>
<translation>اختيارات اتصال اخري</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="129"/>
<source>VPN by Amnezia</source>
<translation type="unfinished"></translation>
<translation>VPN بواسطة Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="130"/>
<source>Connect to classic paid and free VPN services from Amnezia</source>
<translation type="unfinished"></translation>
<translation>اتصل بخدمات VPN الكلاسيكية المدفوعة والمجانية من Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="153"/>
<source>Self-hosted VPN</source>
<translation type="unfinished"></translation>
<translation>VPN ذاتية الاستضافة</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="154"/>
<source>Configure Amnezia VPN on your own server</source>
<translation type="unfinished"></translation>
<translation>قم بتكوين Amnezia VPN على الخادم الخاص بك</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="174"/>
<source>Restore from backup</source>
<translation type="unfinished">استرجاع من ملف يحتوي علي نسخة احتياطية</translation>
<translation>استرجاع من ملف يحتوي علي نسخة احتياطية</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="180"/>
<source>Open backup file</source>
<translation type="unfinished">افتح ملف نسخ احتياطي</translation>
<translation>افتح ملف نسخ احتياطي</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="181"/>
<source>Backup files (*.backup)</source>
<translation type="unfinished">ملفات نٌسخ احتياطية (*.backup)</translation>
<translation>ملفات نٌسخ احتياطية (*.backup)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="198"/>
@ -2222,11 +2206,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="248"/>
<source>I have nothing</source>
<translation type="unfinished">ليس لدي اي شئ</translation>
</message>
<message>
<source>Key as text</source>
<translation type="vanished">مفتاح كنص</translation>
<translation>ليس لدي اي شئ</translation>
</message>
</context>
<context>
@ -2269,12 +2249,12 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="153"/>
<source>How to run your VPN server</source>
<translation type="unfinished"></translation>
<translation>كيف تقوم بتشغيل خادم ال VPN الخاص بك</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/>
<source>Where to get connection data, step-by-step instructions for buying a VPS</source>
<translation type="unfinished"></translation>
<translation>اين تحصل علي بيانات الاتصال, تعليمات خطوة ب خطوة لشراء VPS</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/>
@ -2392,7 +2372,7 @@ Already installed containers were found on the server. All installed containers
<translation>تثبيت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardProtocolSettings.qml" line="267"/>
<location filename="../ui/qml/Pages2/PageSetupWizardProtocolSettings.qml" line="268"/>
<source>The port must be in the range of 1 to 65535</source>
<translation>يجب أن يكون المنفذ في النطاق من 1 إلى 65535</translation>
</message>
@ -2420,30 +2400,10 @@ Already installed containers were found on the server. All installed containers
</context>
<context>
<name>PageSetupWizardStart</name>
<message>
<source>Settings restored from backup file</source>
<translation type="vanished">تم استرداد الإعدادات من ملف نسخة احتياطية</translation>
</message>
<message>
<source>Free service for creating a personal VPN on your server.</source>
<translation type="vanished">خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي.</translation>
</message>
<message>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation type="vanished"> يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN.</translation>
</message>
<message>
<source>I have the data to connect</source>
<translation type="vanished">لدي البيانات المطلوبة للأتصال</translation>
</message>
<message>
<source>I have nothing</source>
<translation type="vanished">ليس لدي اي شئ</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="48"/>
<source>Let&apos;s get started</source>
<translation type="unfinished"></translation>
<translation>هيا نبدأ</translation>
</message>
</context>
<context>
@ -2777,7 +2737,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageStart.qml" line="202"/>
<source>Settings restored from backup file</source>
<translation type="unfinished"></translation>
<translation>تم تحميل الإعدادات من ملف نسخة احتياطية</translation>
</message>
</context>
<context>
@ -3221,7 +3181,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>Missing AGW public key</source>
<translation type="unfinished"></translation>
<translation>مفتاح AGW عام مفقود</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="66"/>
@ -3299,7 +3259,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../containers/containers_defs.cpp" line="127"/>
<source>XRay with REALITY - Suitable for countries with the highest level of internet censorship. Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.</source>
<translation>الأشعة السينية مع الواقع - مناسبة للبلدان التي لديها أعلى مستوى من الرقابة على الإنترنت. إخفاء حركة المرور كحركة مرور على الويب على مستوى TLS، والحماية من الكشف عن طريق طرق التحقيق النشطة.</translation>
<translation>XRay مع REALITY - مناسبة للبلدان التي لديها أعلى مستوى من الرقابة على الإنترنت. إخفاء حركة المرور كحركة مرور على الويب على مستوى TLS، والحماية من الكشف عن طريق طرق التحقيق النشطة.</translation>
</message>
<message>
<location filename="../containers/containers_defs.cpp" line="130"/>

View file

@ -6,47 +6,47 @@
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="63"/>
<source>Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/s</source>
<translation type="unfinished"></translation>
<translation>برای کار راحت، دانلود فایلهای بزرگ و تماشای ویدیوها، از VPN کلاسیک استفاده کنید. این VPN برای هر سایتی کار میکند و سرعت آن تا %1 مگابیت بر ثانیه است.</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="67"/>
<source>VPN to access blocked sites in regions with high levels of Internet censorship. </source>
<translation type="unfinished"></translation>
<translation>وی پی ان برای دسترسی به سایتهای مسدود شده در مناطق با سانسور شدید اینترنت.</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="72"/>
<source>Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship.</source>
<translation type="unfinished"></translation>
<translation>امنزیا پریمیوم - یک وی پی ان کلاسیک برای کار راحت، دانلود فایلهای بزرگ و تماشای ویدیو با کیفیت بالا. قابل استفاده برای تمامی سایتها، حتی در کشورهایی با بالاترین سطح سانسور اینترنت.</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="75"/>
<source>Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship</source>
<translation type="unfinished"></translation>
<translation>امنزیا رایگان یک وی پی ان رایگان برای دور زدن مسدودیتها در کشورهایی با سطح بالای سانسور اینترنت است.</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="80"/>
<source>%1 MBit/s</source>
<translation type="unfinished"></translation>
<translation>%1 MBit/s</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="87"/>
<source>%1 days</source>
<translation type="unfinished"></translation>
<translation>%1 روز</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="96"/>
<source>VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, &lt;a href=&quot;%1/free&quot; style=&quot;color: #FBB26A;&quot;&gt;more details on the website.&lt;/a&gt;</source>
<translation type="unfinished"></translation>
<translation>وی پی ان فقط سایتهای محبوبی را که در منطقه شما مسدود شدهاند، مانند اینستاگرام، فیسبوک، توییتر و غیره باز میکند. سایر سایتها با آدرس آیپی واقعی شما باز خواهند شد. &lt;a href=&quot;%1/free&quot; style=&quot;color: #FBB26A;&quot;&gt;more details on the website.&lt;/a&gt;</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="104"/>
<source>Free</source>
<translation type="unfinished"></translation>
<translation>رایگان</translation>
</message>
<message>
<location filename="../ui/models/apiServicesModel.cpp" line="106"/>
<source>%1 $/month</source>
<translation type="unfinished"></translation>
<translation>%1 $/ماه</translation>
</message>
</context>
<context>
@ -54,22 +54,22 @@
<message>
<location filename="../ui/controllers/appSplitTunnelingController.cpp" line="23"/>
<source>Application added: %1</source>
<translation type="unfinished"></translation>
<translation>برنامه اضافه شد: %1</translation>
</message>
<message>
<location filename="../ui/controllers/appSplitTunnelingController.cpp" line="26"/>
<source>The application has already been added</source>
<translation type="unfinished"></translation>
<translation>برنامه از قبل اضافه شده است</translation>
</message>
<message>
<location filename="../ui/controllers/appSplitTunnelingController.cpp" line="37"/>
<source>The selected applications have been added</source>
<translation type="unfinished"></translation>
<translation>برنامههای انتخاب شده اضافه شدند</translation>
</message>
<message>
<location filename="../ui/controllers/appSplitTunnelingController.cpp" line="48"/>
<source>Application removed: %1</source>
<translation type="unfinished"></translation>
<translation>برنامه حذف شد: %1</translation>
</message>
</context>
<context>
@ -77,7 +77,7 @@
<message>
<location filename="../ui/qml/Components/ConnectButton.qml" line="27"/>
<source>Unable to disconnect during configuration preparation</source>
<translation type="unfinished"></translation>
<translation>در هنگام آمادهسازی پیکربندی، نمیتوان از اتصال خارج شد.</translation>
</message>
</context>
<context>
@ -102,7 +102,7 @@
<message>
<location filename="../ui/controllers/connectionController.cpp" line="110"/>
<source>Preparing...</source>
<translation type="unfinished"></translation>
<translation>در حال آمادهسازی...</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="132"/>
@ -118,12 +118,12 @@
<message>
<location filename="../ui/controllers/connectionController.cpp" line="209"/>
<source>The selected protocol is not supported on the current platform</source>
<translation type="unfinished">پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمیشود</translation>
<translation>پروتکل انتخابشده در پلتفرم فعلی پشتیبانی نمیشود.</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="233"/>
<source>unable to create configuration</source>
<translation type="unfinished"></translation>
<translation>نمیتوان پیکربندی را ایجاد کرد.</translation>
</message>
<message>
<location filename="../ui/controllers/connectionController.cpp" line="95"/>
@ -256,13 +256,13 @@ Can&apos;t be disabled for current server</source>
<message>
<location filename="../ui/controllers/importController.cpp" line="87"/>
<source>Unable to open file</source>
<translation type="unfinished"></translation>
<translation>نمیتوان فایل را باز کرد.</translation>
</message>
<message>
<location filename="../ui/controllers/importController.cpp" line="186"/>
<location filename="../ui/controllers/importController.cpp" line="191"/>
<source>Invalid configuration file</source>
<translation type="unfinished"></translation>
<translation>فایل پیکربندی نامعتبر است.</translation>
</message>
<message>
<location filename="../ui/controllers/importController.cpp" line="606"/>
@ -272,7 +272,7 @@ Can&apos;t be disabled for current server</source>
<message>
<location filename="../ui/controllers/importController.cpp" line="641"/>
<source>In the imported configuration, potentially dangerous lines were found:</source>
<translation type="unfinished"></translation>
<translation>در پیکربندی وارد شده، خطوطی که ممکن است خطرناک باشند، یافت شدند:</translation>
</message>
</context>
<context>
@ -329,12 +329,12 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/controllers/installController.cpp" line="608"/>
<source>Api config removed</source>
<translation type="unfinished"></translation>
<translation>پیکربندی API حذف شد.</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="630"/>
<source>%1 cached profile cleared</source>
<translation type="unfinished"></translation>
<translation>%1 پروفایل ذخیره شده پاک شد.</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="769"/>
@ -349,17 +349,17 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/controllers/installController.cpp" line="845"/>
<source>%1 installed successfully.</source>
<translation type="unfinished"></translation>
<translation>%1 با موفقیت نصب شد.</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="877"/>
<source>API config reloaded</source>
<translation type="unfinished"></translation>
<translation>پیکربندی API دوباره بارگذاری شد.</translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="881"/>
<source>Successfully changed the country of connection to %1</source>
<translation type="unfinished"></translation>
<translation>کشور اتصال با موفقیت به %1 تغییر یافت.</translation>
</message>
</context>
<context>
@ -367,17 +367,17 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Components/InstalledAppsDrawer.qml" line="57"/>
<source>Choose application</source>
<translation type="unfinished"></translation>
<translation>انتخاب برنامه</translation>
</message>
<message>
<location filename="../ui/qml/Components/InstalledAppsDrawer.qml" line="139"/>
<source>application name</source>
<translation type="unfinished"></translation>
<translation>نام برنامه</translation>
</message>
<message>
<location filename="../ui/qml/Components/InstalledAppsDrawer.qml" line="152"/>
<source>Add selected</source>
<translation type="unfinished"></translation>
<translation>اضافه کردن انتخاب شده</translation>
</message>
</context>
<context>
@ -453,7 +453,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="67"/>
<source>Logging enabled</source>
<translation type="unfinished"></translation>
<translation>لاگبرداری فعال شد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="111"/>
@ -589,7 +589,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="382"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
</context>
<context>
@ -623,7 +623,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolCloakSettings.qml" line="193"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
</context>
<context>
@ -794,7 +794,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="445"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
<message>
<source>Remove OpenVPN</source>
@ -895,7 +895,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolShadowSocksSettings.qml" line="181"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
</context>
<context>
@ -903,22 +903,22 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolWireGuardSettings.qml" line="94"/>
<source>WG settings</source>
<translation type="unfinished"></translation>
<translation>تنظیمات WG</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolWireGuardSettings.qml" line="102"/>
<source>Port</source>
<translation type="unfinished">پورت</translation>
<translation>پورت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolWireGuardSettings.qml" line="123"/>
<source>MTU</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolWireGuardSettings.qml" line="157"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
<message>
<source>All users with whom you shared a connection will no longer be able to connect to it.</source>
@ -939,22 +939,22 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolXraySettings.qml" line="95"/>
<source>XRay settings</source>
<translation type="unfinished"></translation>
<translation>تنظیمات XRay</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolXraySettings.qml" line="103"/>
<source>Disguised as traffic from</source>
<translation type="unfinished">پنهان کردن به عنوان ترافیک از</translation>
<translation>بهعنوان ترافیک از طرف زیر نمایش داده میشود</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolXraySettings.qml" line="129"/>
<source>Save</source>
<translation type="unfinished">ذخیره</translation>
<translation>ذخیره</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolXraySettings.qml" line="137"/>
<source>Unable change settings while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.</translation>
</message>
</context>
<context>
@ -1001,7 +1001,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageServiceDnsSettings.qml" line="85"/>
<source>Cannot remove AmneziaDNS from running server</source>
<translation type="unfinished"></translation>
<translation>نمیتوان AmneziaDNS را از سرور در حال اجرا حذف کرد.</translation>
</message>
</context>
<context>
@ -1093,18 +1093,18 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="26"/>
<source>Settings updated successfully</source>
<translation type="unfinished"></translation>
<translation>تنظیمات با موفقیت بهروزرسانی شد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="93"/>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="250"/>
<source>SOCKS5 settings</source>
<translation type="unfinished"></translation>
<translation>تنظیمات SOCKS5</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="104"/>
<source>Host</source>
<translation type="unfinished">هاست</translation>
<translation>هاستمیزبان</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="114"/>
@ -1112,50 +1112,50 @@ Already installed containers were found on the server. All installed containers
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="162"/>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="189"/>
<source>Copied</source>
<translation type="unfinished">کپی شد</translation>
<translation>کپی شد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="125"/>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="260"/>
<source>Port</source>
<translation type="unfinished">پورت</translation>
<translation>پورت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="149"/>
<source>User name</source>
<translation type="unfinished">نام کاربری</translation>
<translation>نام کاربری</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="173"/>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="306"/>
<source>Password</source>
<translation type="unfinished">رمز عبور</translation>
<translation>رمز عبور</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="282"/>
<source>Username</source>
<translation type="unfinished"></translation>
<translation>نام کاربری</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="336"/>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="374"/>
<source>Change connection settings</source>
<translation type="unfinished"></translation>
<translation>تغییر تنظیمات اتصال</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="343"/>
<source>The port must be in the range of 1 to 65535</source>
<translation type="unfinished"></translation>
<translation>پورت باید در محدوده ۱ تا ۶۵۵۳۵ باشد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="347"/>
<source>Password cannot be empty</source>
<translation type="unfinished"></translation>
<translation>رمز عبور نمیتواند خالی باشد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageServiceSocksProxySettings.qml" line="350"/>
<source>Username cannot be empty</source>
<translation type="unfinished"></translation>
<translation>نام کاربری نمیتواند خالی باشد</translation>
</message>
</context>
<context>
@ -1285,7 +1285,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/>
<source>https://t.me/amnezia_vpn_en</source>
<translation>https://t.me/amnezia_vpn</translation>
<translation>https://t.me/amnezia_vpn_ir</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="123"/>
@ -1337,22 +1337,22 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="45"/>
<source>For the region</source>
<translation type="unfinished"></translation>
<translation>برای منطقه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="54"/>
<source>Price</source>
<translation type="unfinished"></translation>
<translation>قیمت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="63"/>
<source>Work period</source>
<translation type="unfinished"></translation>
<translation>مدت زمان کار</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="74"/>
<source>Speed</source>
<translation type="unfinished"></translation>
<translation>سرعت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="106"/>
@ -1362,49 +1362,49 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="119"/>
<source>Copied</source>
<translation type="unfinished">کپی شد</translation>
<translation>کپی شد</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="139"/>
<source>Reload API config</source>
<translation type="unfinished"></translation>
<translation>بارگذاری مجدد پیکربندی API</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="144"/>
<source>Reload API config?</source>
<translation type="unfinished"></translation>
<translation>آیا میخواهید پیکربندی API را دوباره بارگذاری کنید؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="185"/>
<source>Continue</source>
<translation type="unfinished"></translation>
<translation>ادامه دهید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="146"/>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="186"/>
<source>Cancel</source>
<translation type="unfinished">کنسل</translation>
<translation>لغو</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="150"/>
<source>Cannot reload API config during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان پیکربندی API را در حین اتصال فعال دوباره بارگذاری کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="179"/>
<source>Remove from application</source>
<translation type="unfinished"></translation>
<translation>حذف از برنامه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="184"/>
<source>Remove from application?</source>
<translation type="unfinished"></translation>
<translation>آیا میخواهید از برنامه حذف کنید؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="190"/>
<source>Cannot remove server during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان سرور را در حین اتصال فعال حذف کرد.</translation>
</message>
</context>
<context>
@ -1412,57 +1412,57 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="30"/>
<source>Cannot change split tunneling settings during active connection</source>
<translation type="unfinished">نمی توان تنظیمات تونل تقسیم را در طول اتصال فعال تغییر داد</translation>
<translation>نمی توان تنظیمات تونل تقسیم را در طول اتصال فعال تغییر دادنمیتوان تنظیمات تقسیم تونلینگ را در حین اتصال فعال تغییر داد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="51"/>
<source>Only the apps from the list should have access via VPN</source>
<translation type="unfinished"></translation>
<translation>فقط برنامههای موجود در لیست باید از طریق VPN دسترسی داشته باشند.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="56"/>
<source>Apps from the list should not have access via VPN</source>
<translation type="unfinished"></translation>
<translation>برنامههای موجود در لیست نباید از طریق VPN دسترسی داشته باشند.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="93"/>
<source>App split tunneling</source>
<translation type="unfinished"></translation>
<translation>تقسیم تونلینگ برنامهها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="129"/>
<source>Mode</source>
<translation type="unfinished">حالت</translation>
<translation>حالت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="221"/>
<source>Remove </source>
<translation type="unfinished"></translation>
<translation>حذف </translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="222"/>
<source>Continue</source>
<translation type="unfinished"></translation>
<translation>ادامه دهید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="223"/>
<source>Cancel</source>
<translation type="unfinished">کنسل</translation>
<translation>کنسل</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="267"/>
<source>application name</source>
<translation type="unfinished"></translation>
<translation>نام برنامه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="278"/>
<source>Open executable file</source>
<translation type="unfinished"></translation>
<translation>فایل اجرایی را باز کنید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="279"/>
<source>Executable files (*.*)</source>
<translation type="unfinished"></translation>
<translation>فایلهای اجرایی (*.*)</translation>
</message>
</context>
<context>
@ -1480,12 +1480,12 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="94"/>
<source>Enable notifications</source>
<translation type="unfinished"></translation>
<translation>فعال کردن اعلانها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="95"/>
<source>Enable notifications to show the VPN state in the status bar</source>
<translation type="unfinished"></translation>
<translation>اعلان ها را فعال کنید تا وضعیت VPN را در نوار وضعیت ببینید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="117"/>
@ -1565,7 +1565,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsApplication.qml" line="240"/>
<source>Cannot reset settings during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات را در حین اتصال فعال بازنشانی کرد.</translation>
</message>
</context>
<context>
@ -1644,7 +1644,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="153"/>
<source>Cannot restore backup settings during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات پشتیبان را در حین اتصال فعال بازیابی کرد.</translation>
</message>
</context>
<context>
@ -1682,17 +1682,17 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="154"/>
<source>KillSwitch</source>
<translation type="unfinished"></translation>
<translation>KillSwitch</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="155"/>
<source>Disables your internet if your encrypted VPN connection drops out for any reason.</source>
<translation type="unfinished"></translation>
<translation>اگر به هر دلیلی اتصال VPN رمزگذاری شده شما قطع شود، اینترنت شما را غیرفعال میکند.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="166"/>
<source>Cannot change killSwitch settings during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان تنظیمات Kill Switch را در حین اتصال فعال تغییر داد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="97"/>
@ -1778,7 +1778,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="24"/>
<source>Logging is enabled. Note that logs will be automatically disabled after 14 days, and all log files will be deleted.</source>
<translation type="unfinished"></translation>
<translation>ثبت وقایع فعال است. توجه داشته باشید که ثبت وقایع بهطور خودکار پس از ۱۴ روز غیرفعال شده و تمام فایلهای ثبت وقایع حذف خواهند شد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="69"/>
@ -1919,7 +1919,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/>
<source>Cannot reboot server during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان سرور را در حین اتصال فعال راهاندازی مجدد کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/>
@ -1929,7 +1929,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="183"/>
<source>Cannot remove server during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان سرور را در حین اتصال فعال حذف کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="218"/>
@ -1944,7 +1944,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="225"/>
<source>Cannot clear server from Amnezia software during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان سرور را در حین اتصال فعال از نرمافزار Amnezia پاک کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="253"/>
@ -1959,7 +1959,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="266"/>
<source>Cannot reset API config during active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان پیکربندی API را در حین اتصال فعال بازنشانی کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="162"/>
@ -2015,12 +2015,12 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="135"/>
<source>Clear %1 profile</source>
<translation type="unfinished"></translation>
<translation>پاک کردن پروفایل %1</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="138"/>
<source>Clear %1 profile?</source>
<translation type="unfinished"></translation>
<translation>آیا میخواهید پروفایل %1 را پاک کنید؟</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="137"/>
@ -2030,7 +2030,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="145"/>
<source>Unable to clear %1 profile while there is an active connection</source>
<translation type="unfinished"></translation>
<translation>نمیتوان پروفایل %1 را در حین اتصال فعال پاک کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="186"/>
@ -2050,7 +2050,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="198"/>
<source>Cannot remove active container</source>
<translation type="unfinished"></translation>
<translation>نمیتوان کانتینر فعال را حذف کرد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerProtocol.qml" line="140"/>
@ -2179,32 +2179,32 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="62"/>
<source>For the region</source>
<translation type="unfinished"></translation>
<translation>برای منطقه</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="71"/>
<source>Price</source>
<translation type="unfinished"></translation>
<translation>قیمت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="80"/>
<source>Work period</source>
<translation type="unfinished"></translation>
<translation>مدت زمان کار</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="91"/>
<source>Speed</source>
<translation type="unfinished"></translation>
<translation>سرعت</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="100"/>
<source>Features</source>
<translation type="unfinished"></translation>
<translation>ویژگیها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml" line="139"/>
<source>Connect</source>
<translation type="unfinished">اتصال</translation>
<translation>اتصال</translation>
</message>
</context>
<context>
@ -2212,12 +2212,12 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServicesList.qml" line="52"/>
<source>VPN by Amnezia</source>
<translation type="unfinished"></translation>
<translation>VPN توسط Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardApiServicesList.qml" line="53"/>
<source>Choose a VPN service that suits your needs.</source>
<translation type="unfinished"></translation>
<translation>یک سرویس VPN که مناسب نیازهای شما باشد را انتخاب کنید.</translation>
</message>
</context>
<context>
@ -2254,67 +2254,67 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="57"/>
<source>Connection</source>
<translation type="unfinished">ارتباط</translation>
<translation>ارتباط</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>Insert the key, add a configuration file or scan the QR-code</source>
<translation type="unfinished"></translation>
<translation>کلید را وارد کنید، فایل پیکربندی را اضافه کنید یا کد QR را اسکن کنید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="77"/>
<source>Insert key</source>
<translation type="unfinished"></translation>
<translation>کلید را وارد کنید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="78"/>
<source>Insert</source>
<translation type="unfinished">وارد کردن</translation>
<translation>وارد کردن</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="98"/>
<source>Continue</source>
<translation type="unfinished"></translation>
<translation>ادامه دهید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="116"/>
<source>Other connection options</source>
<translation type="unfinished"></translation>
<translation>گزینههای اتصال دیگر</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="129"/>
<source>VPN by Amnezia</source>
<translation type="unfinished"></translation>
<translation>VPN توسط Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="130"/>
<source>Connect to classic paid and free VPN services from Amnezia</source>
<translation type="unfinished"></translation>
<translation>اتصال به سرویسهای VPN کلاسیک پولی و رایگان از Amnezia</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="153"/>
<source>Self-hosted VPN</source>
<translation type="unfinished"></translation>
<translation>Self-hosted VPN</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="154"/>
<source>Configure Amnezia VPN on your own server</source>
<translation type="unfinished"></translation>
<translation>پیکربندی VPN Amnezia بر روی سرور خودتان</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="174"/>
<source>Restore from backup</source>
<translation type="unfinished">بازیابی از پشتیبان</translation>
<translation>بازیابی از پشتیبان</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="180"/>
<source>Open backup file</source>
<translation type="unfinished">باز کردن فایل پشتیبان</translation>
<translation>باز کردن فایل پشتیبان</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="181"/>
<source>Backup files (*.backup)</source>
<translation type="unfinished">Backup files (*.backup)</translation>
<translation>Backup files (*.backup)</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="206"/>
@ -2329,7 +2329,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="248"/>
<source>I have nothing</source>
<translation type="unfinished">من هیچی ندارم</translation>
<translation>من هیچی ندارم</translation>
</message>
<message>
<source>Key as text</source>
@ -2381,12 +2381,12 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="153"/>
<source>How to run your VPN server</source>
<translation type="unfinished"></translation>
<translation>چگونه سرور VPN خود را اجرا کنید</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/>
<source>Where to get connection data, step-by-step instructions for buying a VPS</source>
<translation type="unfinished"></translation>
<translation>دادههای اتصال را از کجا دریافت کنید و دستورالعملهای مرحله به مرحله برای خرید یک VPS</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/>
@ -2501,7 +2501,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardProtocolSettings.qml" line="267"/>
<source>The port must be in the range of 1 to 65535</source>
<translation type="unfinished"></translation>
<translation>پورت باید در محدوده ۱ تا ۶۵۵۳۵ باشد</translation>
</message>
</context>
<context>
@ -2550,7 +2550,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="48"/>
<source>Let&apos;s get started</source>
<translation type="unfinished"></translation>
<translation>بیایید شروع کنیم</translation>
</message>
</context>
<context>
@ -2601,7 +2601,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="123"/>
<source>Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.</source>
<translation type="unfinished"></translation>
<translation>فعالسازی استتار WireGuard. این ممکن است مفید باشد اگر WireGuard توسط ارائهدهنده شما مسدود شده باشد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="144"/>
@ -2680,7 +2680,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="96"/>
<source>Save XRay config</source>
<translation type="unfinished"></translation>
<translation>ذخیره پیکربندی XRay</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="122"/>
@ -2705,7 +2705,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="152"/>
<source>XRay native format</source>
<translation type="unfinished"></translation>
<translation>فرمت بومی XRay</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="190"/>
@ -2741,22 +2741,22 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="817"/>
<source>Creation date: %1</source>
<translation type="unfinished"></translation>
<translation>تاریخ ایجاد: %1</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="825"/>
<source>Latest handshake: %1</source>
<translation type="unfinished"></translation>
<translation>آخرین ارتباط: %1</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="833"/>
<source>Data received: %1</source>
<translation type="unfinished"></translation>
<translation>دادههای دریافت شده: %1</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="841"/>
<source>Data sent: %1</source>
<translation type="unfinished"></translation>
<translation>دادههای ارسال شده: %1</translation>
</message>
<message>
<source>Creation date: </source>
@ -2882,12 +2882,12 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../ui/qml/Pages2/PageStart.qml" line="198"/>
<source>Logging was disabled after 14 days, log files were deleted</source>
<translation type="unfinished"></translation>
<translation>ثبت وقایع پس از ۱۴ روز غیرفعال شد و فایلهای ثبت وقایع حذف شدند</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageStart.qml" line="202"/>
<source>Settings restored from backup file</source>
<translation type="unfinished"></translation>
<translation>تنظیمات از فایل پشتیبان بازیابی شد</translation>
</message>
</context>
<context>
@ -3296,17 +3296,17 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../core/errorstrings.cpp" line="14"/>
<source>Background service is not running</source>
<translation type="unfinished"></translation>
<translation>Background service is not running</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source>
<translation type="unfinished"></translation>
<translation>Server error: Packet manager error</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>SCP error: Generic failure</source>
<translation type="unfinished"></translation>
<translation>SCP error: Generic failure</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="38"/>
@ -3356,17 +3356,17 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../core/errorstrings.cpp" line="60"/>
<source>In the response from the server, an empty config was received</source>
<translation type="unfinished"></translation>
<translation>در پاسخ از سرور، پیکربندی خالی دریافت شد</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>SSL error occurred</source>
<translation type="unfinished"></translation>
<translation type="unfinished">SSL error occurred</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>Server response timeout on api request</source>
<translation type="unfinished"></translation>
<translation>Server response timeout on api request</translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
@ -3426,12 +3426,12 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message>
<location filename="../containers/containers_defs.cpp" line="127"/>
<source>XRay with REALITY - Suitable for countries with the highest level of internet censorship. Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.</source>
<translation type="unfinished"></translation>
<translation>XRay با REALITY - مناسب برای کشورهایی با بالاترین سطح سانسور اینترنت. استتار ترافیک به عنوان ترافیک وب در سطح TLS و حفاظت در برابر شناسایی با روشهای پروب فعال.</translation>
</message>
<message>
<location filename="../containers/containers_defs.cpp" line="130"/>
<source>IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
<translation type="unfinished"></translation>
<translation>IKEv2/IPsec - پروتکل مدرن و پایدار، کمی سریعتر از سایرین است و پس از قطع شدن سیگنال، اتصال را بازیابی میکند. از پشتیبانی بومی در آخرین نسخههای Android و iOS برخوردار است.</translation>
</message>
<message>
<location filename="../containers/containers_defs.cpp" line="137"/>
@ -3504,7 +3504,11 @@ WireGuard به دلیل امضاهای بسته متمایز خود، بسیار
It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, thus presenting an authentic TLS certificate and data.
This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, legitimate sites without the need for specific configurations.
Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REALITY&apos;s innovative &quot;friend or foe&quot; recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.</source>
<translation type="unfinished"></translation>
<translation>پروتکل REALITY، یک توسعه پیشگامانه توسط خالقان XRay، بهطور خاص برای مقابله با بالاترین سطح سانسور اینترنتی طراحی شده است و از رویکرد نوآورانهای برای دور زدن محدودیتها استفاده میکند.
REALITY بهطور منحصربهفردی سانسورچیان را در مرحله دستدهی TLS شناسایی میکند و بهصورت یکپارچه بهعنوان پراکسی برای کاربران قانونی عمل میکند، در حالی که سانسورچیان را به سایتهای معتبر مانند google.com هدایت میکند و در نتیجه یک گواهی TLS واقعی و دادههای اصلی ارائه میدهد.
این قابلیت پیشرفته، REALITY را از فناوریهای مشابه متمایز میکند، زیرا میتواند ترافیک وب را بدون نیاز به پیکربندیهای خاص، بهعنوان ترافیک از سایتهای تصادفی و معتبر جا بزند. برخلاف پروتکلهای قدیمیتر مانند VMess، VLESS و انتقال XTLS-Vision، تشخیص نوآورانه &quot;دوست یا دشمن&quot; REALITY در مرحله دستدهی TLS امنیت را افزایش داده و از شناسایی توسط سیستمهای پیشرفته DPI که از تکنیکهای پروب فعال استفاده میکنند، جلوگیری میکند. این ویژگی REALITY را به یک راهحل قوی برای حفظ آزادی اینترنت در محیطهایی با سانسور شدید تبدیل میکند.</translation>
</message>
<message>
<location filename="../containers/containers_defs.cpp" line="230"/>
@ -3665,7 +3669,7 @@ For more detailed information, you can
<location filename="../containers/containers_defs.cpp" line="104"/>
<location filename="../containers/containers_defs.cpp" line="248"/>
<source>SOCKS5 proxy server</source>
<translation type="unfinished"></translation>
<translation>سرور پروکسی SOCKS5</translation>
</message>
<message>
<location filename="../3rd/qtkeychain/qtkeychain/libsecret.cpp" line="119"/>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -10,9 +10,6 @@
#include "core/controllers/vpnConfigurationController.h"
#include "systemController.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h"
#endif
#include "qrcodegen.hpp"
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
@ -24,12 +21,6 @@ ExportController::ExportController(const QSharedPointer<ServersModel> &serversMo
m_clientManagementModel(clientManagementModel),
m_settings(settings)
{
#ifdef Q_OS_ANDROID
m_authResultNotifier.reset(new AuthResultNotifier);
m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier));
connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, [this]() { emit exportErrorOccurred(tr("Access error!")); });
connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, &ExportController::generateFullAccessConfig);
#endif
}
void ExportController::generateFullAccessConfig()
@ -63,26 +54,6 @@ void ExportController::generateFullAccessConfig()
emit exportConfigChanged();
}
#if defined(Q_OS_ANDROID)
void ExportController::generateFullAccessConfigAndroid()
{
/* We use builtin keyguard for ssh key export protection on Android */
QJniObject activity = AndroidUtils::getActivity();
auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;");
if (appContext.isValid()) {
auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent",
"(Landroid/content/Context;)Landroid/content/Intent;", appContext.object());
if (intent.isValid()) {
if (intent.object<jobject>() != nullptr) {
QtAndroidPrivate::startActivity(intent.object<jobject>(), 1, m_authResultReceiver.get());
}
} else {
generateFullAccessConfig();
}
}
}
#endif
void ExportController::generateConnectionConfig(const QString &clientName)
{
clearPreviousConfig();

View file

@ -6,9 +6,6 @@
#include "ui/models/clientManagementModel.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/authResultReceiver.h"
#endif
class ExportController : public QObject
{
@ -25,9 +22,6 @@ public:
public slots:
void generateFullAccessConfig();
#if defined(Q_OS_ANDROID)
void generateFullAccessConfigAndroid();
#endif
void generateConnectionConfig(const QString &clientName);
void generateOpenVpnConfig(const QString &clientName);
void generateWireGuardConfig(const QString &clientName);
@ -74,11 +68,6 @@ private:
QString m_config;
QString m_nativeConfigString;
QList<QString> m_qrCodes;
#ifdef Q_OS_ANDROID
QSharedPointer<AuthResultNotifier> m_authResultNotifier;
QSharedPointer<QAndroidActivityResultReceiver> m_authResultReceiver;
#endif
};
#endif // EXPORTCONTROLLER_H

View file

@ -799,7 +799,7 @@ void InstallController::addEmptyServer()
bool InstallController::fillAvailableServices()
{
ApiController apiController(m_settings->getGatewayEndpoint());
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
QByteArray responseBody;
ErrorCode errorCode = apiController.getServicesList(responseBody);
@ -821,7 +821,7 @@ bool InstallController::installServiceFromApi()
return false;
}
ApiController apiController(m_settings->getGatewayEndpoint());
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
QJsonObject serverConfig;
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
@ -849,7 +849,7 @@ bool InstallController::installServiceFromApi()
bool InstallController::updateServiceFromApi(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig)
{
ApiController apiController(m_settings->getGatewayEndpoint());
ApiController apiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
@ -885,7 +885,7 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
void InstallController::updateServiceFromTelegram(const int serverIndex)
{
ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint());
ApiController *apiController = new ApiController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv());
auto serverConfig = m_serversModel->getServerConfig(serverIndex);

View file

@ -10,8 +10,6 @@
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#include "platforms/android/android_utils.h"
#include <QJniObject>
#endif
#if defined Q_OS_MAC
#include "ui/macos_util.h"
@ -22,18 +20,8 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
#ifdef Q_OS_ANDROID
// Change color of navigation and status bar's
auto initialPageNavigationBarColor = getInitialPageNavigationBarColor();
AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
window.callMethod<void>("addFlags", "(I)V", 0x80000000);
window.callMethod<void>("clearFlags", "(I)V", 0x04000000);
window.callMethod<void>("setStatusBarColor", "(I)V", 0xFF0E0E11);
window.callMethod<void>("setNavigationBarColor", "(I)V", initialPageNavigationBarColor);
}
});
AndroidController::instance()->setNavigationBarColor(initialPageNavigationBarColor);
#endif
#if defined Q_OS_MACX
@ -115,14 +103,7 @@ unsigned int PageController::getInitialPageNavigationBarColor()
void PageController::updateNavigationBarColor(const int color)
{
#ifdef Q_OS_ANDROID
// Change color of navigation bar
AndroidUtils::runOnAndroidThreadSync([&color]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
window.callMethod<void>("setNavigationBarColor", "(I)V", color);
}
});
AndroidController::instance()->setNavigationBarColor(color);
#endif
}
@ -131,7 +112,7 @@ void PageController::showOnStartup()
if (!m_settings->isStartMinimized()) {
emit raiseMainWindow();
} else {
#ifdef Q_OS_WIN
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
emit hideMainWindow();
#elif defined Q_OS_MACX
setDockIconVisible(false);

View file

@ -88,7 +88,12 @@ void SettingsController::toggleLogging(bool enable)
void SettingsController::openLogsFolder()
{
Logger::openLogsFolder();
Logger::openLogsFolder(false);
}
void SettingsController::openServiceLogsFolder()
{
Logger::openLogsFolder(true);
}
void SettingsController::exportLogsFile(const QString &fileName)
@ -100,12 +105,21 @@ void SettingsController::exportLogsFile(const QString &fileName)
#endif
}
void SettingsController::exportServiceLogsFile(const QString &fileName)
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getServiceLogFile());
#endif
}
void SettingsController::clearLogs()
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs();
Logger::clearLogs(false);
Logger::clearServiceLogs();
#endif
}
@ -283,5 +297,31 @@ void SettingsController::setGatewayEndpoint(const QString &endpoint)
QString SettingsController::getGatewayEndpoint()
{
return m_settings->getGatewayEndpoint();
return m_settings->isDevGatewayEnv() ? "Dev endpoint" : m_settings->getGatewayEndpoint();
}
bool SettingsController::isDevGatewayEnv()
{
return m_settings->isDevGatewayEnv();
}
void SettingsController::toggleDevGatewayEnv(bool enabled)
{
m_settings->toggleDevGatewayEnv(enabled);
if (enabled) {
m_settings->setDevGatewayEndpoint();
} else {
m_settings->resetGatewayEndpoint();
}
emit gatewayEndpointChanged(m_settings->getGatewayEndpoint());
emit devGatewayEnvChanged(enabled);
}
bool SettingsController::isOnTv()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->isOnTv();
#else
return false;
#endif
}

View file

@ -27,6 +27,7 @@ public:
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
public slots:
void toggleAmneziaDns(bool enable);
@ -42,7 +43,9 @@ public slots:
void toggleLogging(bool enable);
void openLogsFolder();
void openServiceLogsFolder();
void exportLogsFile(const QString &fileName);
void exportServiceLogsFile(const QString &fileName);
void clearLogs();
void backupAppConfig(const QString &fileName);
@ -81,6 +84,10 @@ public slots:
void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint);
QString getGatewayEndpoint();
bool isDevGatewayEnv();
void toggleDevGatewayEnv(bool enabled);
bool isOnTv();
signals:
void primaryDnsChanged();
@ -103,6 +110,7 @@ signals:
void devModeEnabled();
void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled);
private:
QSharedPointer<ServersModel> m_serversModel;

View file

@ -125,3 +125,12 @@ void SystemController::setQmlRoot(QObject *qmlRoot)
{
m_qmlRoot = qmlRoot;
}
bool SystemController::isAuthenticated()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->requestAuthentication();
#else
return true;
#endif
}

View file

@ -19,6 +19,7 @@ public slots:
void setQmlRoot(QObject *qmlRoot);
bool isAuthenticated();
signals:
void fileDialogClosed(const bool isAccepted);

View file

@ -25,6 +25,8 @@ namespace
constexpr char availableCountries[] = "available_countries";
constexpr char storeEndpoint[] = "store_endpoint";
constexpr char isAvailable[] = "is_available";
}
namespace serviceType
@ -63,8 +65,12 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s")
.arg(speed);
} else {
return tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
} else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
}
return description;
}
}
case ServiceDescriptionRole: {
@ -75,6 +81,14 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship");
}
}
case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) {
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
return false;
}
}
return true;
}
case SpeedRole: {
auto speed = serviceInfo.value(configKey::speed).toString();
return tr("%1 MBit/s").arg(speed);
@ -193,6 +207,7 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
roles[NameRole] = "name";
roles[CardDescriptionRole] = "cardDescription";
roles[ServiceDescriptionRole] = "serviceDescription";
roles[IsServiceAvailableRole] = "isServiceAvailable";
roles[SpeedRole] = "speed";
roles[WorkPeriodRole] = "workPeriod";
roles[RegionRole] = "region";

View file

@ -13,6 +13,7 @@ public:
NameRole = Qt::UserRole + 1,
CardDescriptionRole,
ServiceDescriptionRole,
IsServiceAvailableRole,
SpeedRole,
WorkPeriodRole,
RegionRole,

View file

@ -1,27 +0,0 @@
#ifndef PROPERTY_HELPER_H
#define PROPERTY_HELPER_H
#include <QObject>
#define AUTO_PROPERTY(TYPE, NAME) \
Q_PROPERTY(TYPE NAME READ NAME WRITE set_ ## NAME NOTIFY NAME ## Changed ) \
public: \
TYPE NAME() const { return m_ ## NAME ; } \
void set_ ## NAME(TYPE value) { \
if (m_ ## NAME == value) return; \
m_ ## NAME = value; \
emit NAME ## Changed(value); \
} \
Q_SIGNAL void NAME ## Changed(TYPE value);\
private: \
TYPE m_ ## NAME{};
#define READONLY_PROPERTY(TYPE, NAME) \
Q_PROPERTY(TYPE NAME READ NAME CONSTANT ) \
public: \
TYPE NAME() const { return m_ ## NAME ; } \
private: \
void NAME(TYPE value) {m_ ## NAME = value; } \
TYPE m_ ## NAME{};
#endif // PROPERTY_HELPER_H

View file

@ -14,6 +14,7 @@ Button {
property string defaultButtonColor: AmneziaStyle.color.paleGray
property string progressButtonColor: AmneziaStyle.color.paleGray
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
implicitWidth: 190
implicitHeight: 190
@ -50,14 +51,14 @@ Button {
verticalOffset: 0
radius: 10
samples: 25
color: root.activeFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
source: backgroundCircle
}
ShapePath {
fillColor: AmneziaStyle.color.transparent
strokeColor: AmneziaStyle.color.paleGray
strokeWidth: root.activeFocus ? 1 : 0
strokeWidth: root.buttonActiveFocus ? 1 : 0
capStyle: ShapePath.RoundCap
PathAngleArc {
@ -81,14 +82,14 @@ Button {
return defaultButtonColor
}
}
strokeWidth: root.activeFocus ? 2 : 3
strokeWidth: root.buttonActiveFocus ? 2 : 3
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: 93 - (root.activeFocus ? 2 : 0)
radiusY: 93 - (root.activeFocus ? 2 : 0)
radiusX: 93 - (root.buttonActiveFocus ? 2 : 0)
radiusY: 93 - (root.buttonActiveFocus ? 2 : 0)
startAngle: 0
sweepAngle: 360
}

View file

@ -79,6 +79,7 @@ Button {
visible: text !== ""
color: AmneziaStyle.color.mutedGray
textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16

View file

@ -20,7 +20,8 @@ Item {
property string buttonImageSource
property string rightImageSource
property string leftImageSource
property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3
property bool isLeftImageHoverEnabled: true
property bool isSmallLeftImage: false
property alias rightButton: rightImage
property alias eyeButton: eyeImage
@ -114,9 +115,9 @@ Item {
visible: leftImageSource ? true : false
Layout.preferredHeight: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitHeight : 56
Layout.preferredWidth: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitWidth : 56
Layout.rightMargin: rightImageSource || !isLeftImageHoverEnabled ? 16 : 0
Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56
Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56
Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0
radius: 12
color: AmneziaStyle.color.transparent

View file

@ -102,8 +102,7 @@ Switch {
contentItem: ColumnLayout {
id: content
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
ListItemTitleType {

View file

@ -89,6 +89,21 @@ PageType {
// KeyNavigation.tab: saveButton
}
SwitcherType {
id: switcher
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
text: qsTr("Dev gateway environment")
checked: SettingsController.isDevGatewayEnv
onToggled: function() {
SettingsController.isDevGatewayEnv = checked
}
}
}
}
}

View file

@ -120,7 +120,7 @@ PageType {
id: mailButton
Layout.fillWidth: true
text: qsTr("Mail")
text: qsTr("support@amnezia.org")
descriptionText: qsTr("For reviews and bug reports")
leftImageSource: "qrc:/images/controls/mail.svg"
@ -128,6 +128,8 @@ PageType {
parentFlickable: fl
clickedFunction: function() {
GC.copyToClipBoard(text)
PageController.showNotificationMessage(qsTr("Copied"))
}
}

View file

@ -16,18 +16,6 @@ import "../Controls2/TextTypes"
PageType {
id: root
Connections {
target: SettingsController
function onLoggingStateChanged() {
if (SettingsController.isLoggingEnabled) {
var message = qsTr("Logging is enabled. Note that logs will be automatically \
disabled after 14 days, and all log files will be deleted.")
PageController.showNotificationMessage(message)
}
}
}
defaultActiveFocusItem: focusItem
Item {
@ -58,13 +46,12 @@ disabled after 14 days, and all log files will be deleted.")
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16
spacing: 0
HeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Logging")
descriptionText: qsTr("Enabling this function will save application's logs automatically. " +
@ -75,11 +62,13 @@ disabled after 14 days, and all log files will be deleted.")
id: switcher
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Save logs")
text: qsTr("Enable logs")
checked: SettingsController.isLoggingEnabled
KeyNavigation.tab: openFolderButton
//KeyNavigation.tab: openFolderButton
onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) {
SettingsController.isLoggingEnabled = checked
@ -87,132 +76,200 @@ disabled after 14 days, and all log files will be deleted.")
}
}
RowLayout {
DividerType {}
LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true
Layout.topMargin: -8
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3
visible: !GC.isMobile()
text: qsTr("Clear logs")
leftImageSource: "qrc:/images/controls/trash.svg"
isSmallLeftImage: true
ImageButtonType {
id: openFolderButton
Layout.alignment: Qt.AlignHCenter
// KeyNavigation.tab: labelWithButton3
implicitWidth: 56
implicitHeight: 56
clickedFunction: function() {
var headerText = qsTr("Clear logs?")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
image: "qrc:/images/controls/folder-open.svg"
KeyNavigation.tab: saveButton
onClicked: SettingsController.openLogsFolder()
Keys.onReturnPressed: openFolderButton.clicked()
Keys.onEnterPressed: openFolderButton.clicked()
var yesButtonFunction = function() {
PageController.showBusyIndicator(true)
SettingsController.clearLogs()
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
CaptionTextType {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
text: qsTr("Open folder with logs")
color: AmneziaStyle.color.paleGray
}
}
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
ImageButtonType {
id: saveButton
Layout.alignment: Qt.AlignHCenter
implicitWidth: 56
implicitHeight: 56
image: "qrc:/images/controls/save.svg"
KeyNavigation.tab: clearButton
Keys.onReturnPressed: saveButton.clicked()
Keys.onEnterPressed: saveButton.clicked()
onClicked: {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
true,
".log")
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
SettingsController.exportLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
CaptionTextType {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
text: qsTr("Save logs to file")
color: AmneziaStyle.color.paleGray
ListItemTitleType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Client logs")
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
color: AmneziaStyle.color.mutedGray
text: qsTr("AmneziaVPN logs")
}
LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Open logs folder")
leftImageSource: "qrc:/images/controls/folder-open.svg"
isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() {
SettingsController.openLogsFolder()
}
}
DividerType {}
LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Export logs")
leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
true,
".log")
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
SettingsController.exportLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
DividerType {}
ImageButtonType {
id: clearButton
Layout.alignment: Qt.AlignHCenter
ListItemTitleType {
visible: !GC.isMobile()
implicitWidth: 56
implicitHeight: 56
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
image: "qrc:/images/controls/delete.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem)
text: qsTr("Service logs")
}
Keys.onReturnPressed: clearButton.clicked()
Keys.onEnterPressed: clearButton.clicked()
onClicked: function() {
var headerText = qsTr("Clear logs?")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
ParagraphTextType {
visible: !GC.isMobile()
var yesButtonFunction = function() {
PageController.showBusyIndicator(true)
SettingsController.clearLogs()
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
color: AmneziaStyle.color.mutedGray
text: qsTr("AmneziaVPN-service logs")
}
LabelWithButtonType {
// id: labelWithButton2
visible: !GC.isMobile()
Layout.fillWidth: true
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Open logs folder")
leftImageSource: "qrc:/images/controls/folder-open.svg"
isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() {
SettingsController.openServiceLogsFolder()
}
}
DividerType {
visible: !GC.isMobile()
}
LabelWithButtonType {
// id: labelWithButton2
visible: !GC.isMobile()
Layout.fillWidth: true
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Export logs")
leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN-service.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
true,
".log")
}
CaptionTextType {
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
text: qsTr("Clear logs")
color: AmneziaStyle.color.paleGray
if (fileName !== "") {
PageController.showBusyIndicator(true)
SettingsController.exportServiceLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
}
DividerType {
visible: !GC.isMobile()
}
}
}
}

View file

@ -88,8 +88,10 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
onClicked: {
ApiServicesModel.setServiceIndex(index)
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
if (isServiceAvailable) {
ApiServicesModel.setServiceIndex(index)
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
}
}
}
}

View file

@ -55,6 +55,51 @@ PageType {
Layout.leftMargin: 16
headerText: qsTr("Connection")
actionButtonImage: PageController.isStartPageVisible() ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() {
moreActionsDrawer.open()
}
DrawerType2 {
id: moreActionsDrawer
parent: root
anchors.fill: parent
expandedHeight: root.height * 0.35
expandedContent: ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
HeaderType {
Layout.fillWidth: true
Layout.topMargin: 32
headerText: qsTr("Settings")
}
SwitcherType {
id: switcher
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Enable logs")
checked: SettingsController.isLoggingEnabled
onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) {
SettingsController.isLoggingEnabled = checked
}
}
}
}
}
}
ParagraphTextType {
@ -119,8 +164,6 @@ PageType {
CardWithIconsType {
id: apiInstalling
visible: false
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16

View file

@ -140,22 +140,23 @@ PageType {
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
PageController.showBusyIndicator(true)
if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) {
PageController.showBusyIndicator(false)
ExportController.exportErrorOccurred(qsTr("Access error!"))
return
} else {
ExportController.generateFullAccessConfig()
}
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.open()
shareConnectionDrawer.contentVisible = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "android") {
ExportController.generateFullAccessConfigAndroid();
} else {
ExportController.generateFullAccessConfig();
}
shareConnectionDrawer.contentVisible = true
PageController.showBusyIndicator(false)
shareConnectionDrawer.contentVisible = true
}
}
}

View file

@ -202,6 +202,14 @@ PageType {
PageController.showNotificationMessage(qsTr("Settings restored from backup file"))
PageController.goToPageHome()
}
function onLoggingStateChanged() {
if (SettingsController.isLoggingEnabled) {
var message = qsTr("Logging is enabled. Note that logs will be automatically" +
"disabled after 14 days, and all log files will be deleted.")
PageController.showNotificationMessage(message)
}
}
}
StackViewType {

View file

@ -69,22 +69,6 @@ QString Utils::JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat f
return doc.toJson(format);
}
QString Utils::systemLogPath()
{
#ifdef Q_OS_WIN
QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
QString primaryLocation = "ProgramData";
foreach (const QString &location, locationList) {
if (location.contains(primaryLocation)) {
return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME);
}
}
return QString();
#else
return QString("/var/log/%1").arg(APPLICATION_NAME);
#endif
}
bool Utils::initializePath(const QString &path)
{
QDir dir;

View file

@ -23,7 +23,6 @@ public:
static QJsonObject JsonFromString(const QString &string);
static QString executable(const QString &baseName, bool absPath);
static QString usrExecutable(const QString &baseName);
static QString systemLogPath();
static bool createEmptyFile(const QString &path);
static bool initializePath(const QString &path);

View file

@ -4,18 +4,18 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QMetaEnum>
#include <QJsonDocument>
#include <QMetaEnum>
#include <QStandardPaths>
#include <QUrl>
#include <iostream>
#include "version.h"
#include "utilities.h"
#include "version.h"
#ifdef AMNEZIA_DESKTOP
#include <core/ipcclient.h>
#include <core/ipcclient.h>
#endif
#ifdef Q_OS_IOS
@ -25,8 +25,9 @@
QFile Logger::m_file;
QTextStream Logger::m_textStream;
QString Logger::m_logFileName = QString("%1.log").arg(APPLICATION_NAME);
QString Logger::m_serviceLogFileName = QString("%1.log").arg(SERVICE_NAME);
void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
void debugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (msg.simplified().isEmpty()) {
return;
@ -37,12 +38,12 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
return;
}
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) {
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")
|| msg.startsWith("stale focus object")) {
return;
}
Logger::m_textStream << qFormatLogMessage(type, context, msg) << Qt::endl << Qt::flush;
Logger::appendAllLog(qFormatLogMessage(type, context, msg));
std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush;
}
@ -53,36 +54,24 @@ Logger &Logger::Instance()
return s;
}
void Logger::appendSshLog(const QString &log)
bool Logger::init(bool isServiceLogger)
{
QString dt = QDateTime::currentDateTime().toString();
Instance().m_sshLog.append(dt + ": " + log + "\n");
emit Instance().sshLogChanged(Instance().sshLog());
}
void Logger::appendAllLog(const QString &log)
{
Instance().m_allLog.append(log + "\n");
emit Instance().allLogChanged(Instance().allLog());
}
bool Logger::init()
{
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}");
QString path = userLogsDir();
QString path = isServiceLogger ? systemLogDir() : userLogsDir();
QString logFileName = isServiceLogger ? m_serviceLogFileName : m_logFileName ;
QDir appDir(path);
if (!appDir.mkpath(path)) {
return false;
}
m_file.setFileName(appDir.filePath(m_logFileName));
m_file.setFileName(appDir.filePath(logFileName));
if (!m_file.open(QIODevice::Append)) {
qWarning() << "Cannot open log file:" << m_logFileName;
qWarning() << "Cannot open log file:" << logFileName;
return false;
}
m_file.setTextModeEnabled(true);
m_textStream.setDevice(&m_file);
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}");
#if !defined(QT_DEBUG) || defined(Q_OS_IOS)
qInstallMessageHandler(debugMessageHandler);
@ -99,7 +88,8 @@ void Logger::deInit()
m_file.close();
}
bool Logger::setServiceLogsEnabled(bool enabled) {
bool Logger::setServiceLogsEnabled(bool enabled)
{
#ifdef AMNEZIA_DESKTOP
IpcClient *m_IpcClient = new IpcClient;
@ -112,8 +102,7 @@ bool Logger::setServiceLogsEnabled(bool enabled) {
if (m_IpcClient->Interface()) {
m_IpcClient->Interface()->setLogsEnabled(enabled);
}
else {
} else {
qWarning() << "Error occurred setting up service logs";
return false;
}
@ -127,11 +116,32 @@ QString Logger::userLogsDir()
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log";
}
QString Logger::systemLogDir()
{
#ifdef Q_OS_WIN
QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
QString primaryLocation = "ProgramData";
foreach (const QString &location, locationList) {
if (location.contains(primaryLocation)) {
return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME);
}
}
return QString();
#else
return QString("/var/log/%1").arg(APPLICATION_NAME);
#endif
}
QString Logger::userLogsFilePath()
{
return userLogsDir() + QDir::separator() + m_logFileName;
}
QString Logger::serviceLogsFilePath()
{
return systemLogDir() + QDir::separator() + m_serviceLogFileName;
}
QString Logger::getLogFile()
{
m_file.flush();
@ -145,12 +155,26 @@ QString Logger::getLogFile()
#else
return qtLog;
#endif
}
bool Logger::openLogsFolder()
QString Logger::getServiceLogFile()
{
QString path = userLogsDir();
m_file.flush();
QFile file(serviceLogsFilePath());
file.open(QIODevice::ReadOnly);
QString qtLog = file.readAll();
#ifdef Q_OS_IOS
return QString().fromStdString(AmneziaVPN::swiftUpdateLogData(qtLog.toStdString()));
#else
return qtLog;
#endif
}
bool Logger::openLogsFolder(bool isServiceLogger)
{
QString path = isServiceLogger ? systemLogDir() : userLogsDir();
#ifdef Q_OS_WIN
path = "file:///" + path;
#endif
@ -161,27 +185,12 @@ bool Logger::openLogsFolder()
return true;
}
bool Logger::openServiceLogsFolder()
{
QString path = Utils::systemLogPath();
#ifdef Q_OS_WIN
path = "file:///" + path;
#endif
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
return true;
}
QString Logger::appLogFileNamePath()
{
return m_file.fileName();
}
void Logger::clearLogs()
void Logger::clearLogs(bool isServiceLogger)
{
bool isLogActive = m_file.isOpen();
m_file.close();
QFile file(userLogsFilePath());
QFile file(isServiceLogger ? serviceLogsFilePath() : userLogsFilePath());
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.resize(0);
@ -192,7 +201,7 @@ void Logger::clearLogs()
#endif
if (isLogActive) {
init();
init(isServiceLogger);
}
}
@ -210,8 +219,7 @@ void Logger::clearServiceLogs()
if (m_IpcClient->Interface()) {
m_IpcClient->Interface()->clearLogs();
}
else {
} else {
qWarning() << "Error occurred cleaning up service logs";
}
#endif
@ -219,26 +227,41 @@ void Logger::clearServiceLogs()
void Logger::cleanUp()
{
clearLogs();
clearLogs(false);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
dir.removeRecursively();
clearServiceLogs();
clearLogs(true);
}
Logger::Log::Log(Logger* logger, LogLevel logLevel)
: m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {}
Logger::Log::Log(Logger *logger, LogLevel logLevel) : m_logger(logger), m_logLevel(logLevel), m_data(new Data())
{
}
Logger::Log::~Log() {
Logger::Log::~Log()
{
qDebug() << "Amnezia" << m_logger->className() << m_data->m_buffer.trimmed();
delete m_data;
}
Logger::Log Logger::error() { return Log(this, LogLevel::Error); }
Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); }
Logger::Log Logger::info() { return Log(this, LogLevel::Info); }
Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); }
QString Logger::sensitive(const QString& input) {
Logger::Log Logger::error()
{
return Log(this, LogLevel::Error);
}
Logger::Log Logger::warning()
{
return Log(this, LogLevel::Warning);
}
Logger::Log Logger::info()
{
return Log(this, LogLevel::Info);
}
Logger::Log Logger::debug()
{
return Log(this, LogLevel::Debug);
}
QString Logger::sensitive(const QString &input)
{
#ifdef Q_DEBUG
return input;
#else
@ -247,48 +270,51 @@ QString Logger::sensitive(const QString& input) {
#endif
}
#define CREATE_LOG_OP_REF(x) \
Logger::Log& Logger::Log::operator<<(x t) { \
m_data->m_ts << t << ' '; \
return *this; \
}
#define CREATE_LOG_OP_REF(x) \
Logger::Log &Logger::Log::operator<<(x t) \
{ \
m_data->m_ts << t << ' '; \
return *this; \
}
CREATE_LOG_OP_REF(uint64_t);
CREATE_LOG_OP_REF(const char*);
CREATE_LOG_OP_REF(const QString&);
CREATE_LOG_OP_REF(const QByteArray&);
CREATE_LOG_OP_REF(const void*);
CREATE_LOG_OP_REF(const char *);
CREATE_LOG_OP_REF(const QString &);
CREATE_LOG_OP_REF(const QByteArray &);
CREATE_LOG_OP_REF(const void *);
#undef CREATE_LOG_OP_REF
Logger::Log& Logger::Log::operator<<(const QStringList& t) {
Logger::Log &Logger::Log::operator<<(const QStringList &t)
{
m_data->m_ts << '[' << t.join(",") << ']' << ' ';
return *this;
}
Logger::Log& Logger::Log::operator<<(const QJsonObject& t) {
Logger::Log &Logger::Log::operator<<(const QJsonObject &t)
{
m_data->m_ts << QJsonDocument(t).toJson(QJsonDocument::Indented) << ' ';
return *this;
}
Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) {
Logger::Log &Logger::Log::operator<<(QTextStreamFunction t)
{
m_data->m_ts << t;
return *this;
}
void Logger::Log::addMetaEnum(quint64 value, const QMetaObject* meta,
const char* name) {
void Logger::Log::addMetaEnum(quint64 value, const QMetaObject *meta, const char *name)
{
QMetaEnum me = meta->enumerator(meta->indexOfEnumerator(name));
QString out;
QTextStream ts(&out);
if (const char* scope = me.scope()) {
if (const char *scope = me.scope()) {
ts << scope << "::";
}
const char* key = me.valueToKey(static_cast<int>(value));
const char *key = me.valueToKey(static_cast<int>(value));
const bool scoped = me.isScoped();
if (scoped || !key) {
ts << me.enumName() << (!key ? "(" : "::");

114
common/logger/logger.h Normal file
View file

@ -0,0 +1,114 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QString>
#include <QTextStream>
#include "mozilla/shared/loglevel.h"
class Logger : public QObject
{
Q_OBJECT
public:
static Logger &Instance();
static bool init(bool isServiceLogger);
static void deInit();
static bool setServiceLogsEnabled(bool enabled);
static bool openLogsFolder(bool isServiceLogger);
static void clearLogs(bool isServiceLogger);
static void clearServiceLogs();
static void cleanUp();
static QString userLogsFilePath();
static QString serviceLogsFilePath();
static QString systemLogDir();
static QString getLogFile();
static QString getServiceLogFile();
// compat with Mozilla logger
Logger(const QString &className)
{
m_className = className;
}
const QString &className() const
{
return m_className;
}
class Log
{
public:
Log(Logger *logger, LogLevel level);
~Log();
Log &operator<<(uint64_t t);
Log &operator<<(const char *t);
Log &operator<<(const QString &t);
Log &operator<<(const QStringList &t);
Log &operator<<(const QByteArray &t);
Log &operator<<(const QJsonObject &t);
Log &operator<<(QTextStreamFunction t);
Log &operator<<(const void *t);
// Q_ENUM
template<typename T> typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, Log &>::type operator<<(T t)
{
const QMetaObject *meta = qt_getEnumMetaObject(t);
const char *name = qt_getEnumName(t);
addMetaEnum(typename QFlags<T>::Int(t), meta, name);
return *this;
}
private:
void addMetaEnum(quint64 value, const QMetaObject *meta, const char *name);
Logger *m_logger;
LogLevel m_logLevel;
struct Data
{
Data() : m_ts(&m_buffer, QIODevice::WriteOnly)
{
}
QString m_buffer;
QTextStream m_ts;
};
Data *m_data;
};
Log error();
Log warning();
Log info();
Log debug();
QString sensitive(const QString &input);
private:
Logger() {};
Logger(Logger const &) = delete;
Logger &operator=(Logger const &) = delete;
static QString userLogsDir();
static QFile m_file;
static QTextStream m_textStream;
static QString m_logFileName;
static QString m_serviceLogFileName;
friend void debugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
// compat with Mozilla logger
QString m_className;
};
#endif // LOGGER_H

View file

@ -1,33 +1,33 @@
#include "ipcserver.h"
#include <QObject>
#include <QDateTime>
#include <QLocalSocket>
#include <QFileInfo>
#include <QLocalSocket>
#include <QObject>
#include <QJsonArray>
#include "qjsonarray.h"
#include "router.h"
#include "logger.h"
#include "router.h"
#include "../client/protocols/protocols_defs.h"
#ifdef Q_OS_WIN
#include "tapcontroller_win.h"
#include "../client/platforms/windows/daemon/windowsfirewall.h"
#include "../client/platforms/windows/daemon/windowsdaemon.h"
#include "../client/platforms/windows/daemon/windowsdaemon.h"
#include "../client/platforms/windows/daemon/windowsfirewall.h"
#include "tapcontroller_win.h"
#endif
#ifdef Q_OS_LINUX
#include "../client/platforms/linux/daemon/linuxfirewall.h"
#include "../client/platforms/linux/daemon/linuxfirewall.h"
#endif
#ifdef Q_OS_MACOS
#include "../client/platforms/macos/daemon/macosfirewall.h"
#include "../client/platforms/macos/daemon/macosfirewall.h"
#endif
IpcServer::IpcServer(QObject *parent):
IpcInterfaceSource(parent)
IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
{}
{
}
int IpcServer::createPrivilegedProcess()
{
@ -59,23 +59,10 @@ int IpcServer::createPrivilegedProcess()
}
});
QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::error, this, [pd](QRemoteObjectNode::ErrorCode errorCode) {
qDebug() << "QRemoteObjectHost::error" << errorCode;
});
QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::error, this,
[pd](QRemoteObjectNode::ErrorCode errorCode) { qDebug() << "QRemoteObjectHost::error" << errorCode; });
QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::destroyed, this, [pd]() {
qDebug() << "QRemoteObjectHost::destroyed";
});
// connect(pd.ipcProcess.data(), &IpcServerProcess::finished, this, [this, pid=m_localpid](int exitCode, QProcess::ExitStatus exitStatus){
// qDebug() << "IpcServerProcess finished" << exitCode << exitStatus;
//// if (m_processes.contains(pid)) {
//// m_processes[pid].ipcProcess.reset();
//// m_processes[pid].serverNode.reset();
//// m_processes[pid].localServer.reset();
//// m_processes.remove(pid);
//// }
// });
QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::destroyed, this, [pd]() { qDebug() << "QRemoteObjectHost::destroyed"; });
m_processes.insert(m_localpid, pd);
@ -106,7 +93,7 @@ bool IpcServer::routeDeleteList(const QString &gw, const QStringList &ips)
qDebug() << "IpcServer::routeDeleteList";
#endif
return Router::routeDeleteList(gw ,ips);
return Router::routeDeleteList(gw, ips);
}
void IpcServer::flushDns()
@ -159,12 +146,13 @@ void IpcServer::cleanUp()
qDebug() << "IpcServer::cleanUp";
#endif
Logger::deinit();
Logger::deInit();
Logger::cleanUp();
}
void IpcServer::clearLogs() {
Logger::clearLogs();
void IpcServer::clearLogs()
{
Logger::clearLogs(true);
}
bool IpcServer::createTun(const QString &dev, const QString &subnet)
@ -177,7 +165,7 @@ bool IpcServer::deleteTun(const QString &dev)
return Router::deleteTun(dev);
}
bool IpcServer::updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers)
bool IpcServer::updateResolvers(const QString &ifname, const QList<QHostAddress> &resolvers)
{
return Router::updateResolvers(ifname, resolvers);
}
@ -199,10 +187,9 @@ void IpcServer::setLogsEnabled(bool enabled)
#endif
if (enabled) {
Logger::init();
}
else {
Logger::deinit();
Logger::init(true);
} else {
Logger::deInit();
}
}
@ -221,13 +208,11 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd
QStringList allownets;
QStringList blocknets;
if (splitTunnelType == 0)
{
if (splitTunnelType == 0) {
blockAll = true;
allowNets = true;
allownets.append(configStr.value(amnezia::config_key::hostName).toString());
} else if (splitTunnelType == 1)
{
} else if (splitTunnelType == 1) {
blockNets = true;
for (auto v : splitTunnelSites) {
blocknets.append(v.toString());
@ -269,18 +254,17 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd
// double-check + ensure our firewall is installed and enabled. This is necessary as
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
if (!MacOSFirewall::isInstalled())
MacOSFirewall::install();
MacOSFirewall::ensureRootAnchorPriority();
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets,
QStringLiteral("allownets"), allownets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets);
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets,
QStringLiteral("blocknets"), blocknets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets);
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
@ -520,10 +504,8 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
// Use APP split tunnel
if (splitTunnelType == 0 || splitTunnelType == 2) {
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress("0.0.0.0"), 0));
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress("::"), 0));
config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0));
config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0));
}
if (splitTunnelType == 1) {
@ -531,10 +513,9 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
QString ipRange = v.toString();
if (ipRange.split('/').size() > 1) {
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit())));
IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit())));
} else {
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress(ipRange), 32));
config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32));
}
}
}
@ -547,7 +528,7 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
}
}
for (const QJsonValue& i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) {
for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) {
if (!i.isString()) {
break;
}

View file

@ -19,7 +19,7 @@ set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h
${CMAKE_CURRENT_LIST_DIR}/localserver.h
${CMAKE_CURRENT_LIST_DIR}/logger.h
${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.h
${CMAKE_CURRENT_LIST_DIR}/router.h
${CMAKE_CURRENT_LIST_DIR}/systemservice.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
@ -31,7 +31,7 @@ set(SOURCES
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp
${CMAKE_CURRENT_LIST_DIR}/localserver.cpp
${CMAKE_CURRENT_LIST_DIR}/logger.cpp
${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.cpp
${CMAKE_CURRENT_LIST_DIR}/main.cpp
${CMAKE_CURRENT_LIST_DIR}/router.cpp
${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp
@ -238,6 +238,7 @@ include_directories(
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../../client
${CMAKE_CURRENT_LIST_DIR}/../../ipc
${CMAKE_CURRENT_LIST_DIR}/../../common/logger
${CMAKE_CURRENT_BINARY_DIR}
)

View file

@ -1,185 +0,0 @@
#include "logger.h"
#include <QDir>
#include <QJsonDocument>
#include <QMetaEnum>
#include <QStandardPaths>
#include <iostream>
#include "version.h"
#include "utilities.h"
QFile Logger::m_file;
QTextStream Logger::m_textStream;
QString Logger::m_logFileName = QString("%1.log").arg(SERVICE_NAME);
void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
if (msg.simplified().isEmpty()) {
return;
}
Logger::m_textStream << qFormatLogMessage(type, context, msg) << Qt::endl << Qt::flush;
std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush;
}
bool Logger::init()
{
if (m_file.isOpen()) return true;
QString path = Utils::systemLogPath();
QDir appDir(path);
if (!appDir.mkpath(path)) {
return false;
}
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}");
m_file.setFileName(appDir.filePath(m_logFileName));
if (!m_file.open(QIODevice::Append)) {
qWarning() << "Cannot open log file:" << m_logFileName;
return false;
}
m_file.setTextModeEnabled(true);
m_textStream.setDevice(&m_file);
qInstallMessageHandler(debugMessageHandler);
return true;
}
void Logger::deinit()
{
m_file.close();
m_textStream.setDevice(nullptr);
qInstallMessageHandler(nullptr);
}
QString Logger::serviceLogFileNamePath()
{
return m_file.fileName();
}
void Logger::clearLogs()
{
bool isLogActive = m_file.isOpen();
m_file.close();
QString path = Utils::systemLogPath();
QDir appDir(path);
QFile file;
file.setFileName(appDir.filePath(m_logFileName));
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.resize(0);
file.close();
if (isLogActive) {
init();
}
}
void Logger::cleanUp()
{
clearLogs();
deinit();
QString path = Utils::systemLogPath();
QDir appDir(path);
{
QFile file;
file.setFileName(appDir.filePath(m_logFileName));
file.remove();
}
{
QFile file;
file.setFileName(appDir.filePath("openvpn.log"));
file.remove();
}
#ifdef Q_OS_WINDOWS
QDir dir(Utils::systemLogPath());
dir.removeRecursively();
#endif
}
Logger::Log::Log(Logger* logger, LogLevel logLevel)
: m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {}
Logger::Log::~Log() {
qDebug() << "Amnezia" << m_logger->className() << m_data->m_buffer.trimmed();
delete m_data;
}
Logger::Log Logger::error() { return Log(this, LogLevel::Error); }
Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); }
Logger::Log Logger::info() { return Log(this, LogLevel::Info); }
Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); }
QString Logger::sensitive(const QString& input) {
#ifdef Q_DEBUG
return input;
#else
Q_UNUSED(input);
return QString(8, 'X');
#endif
}
#define CREATE_LOG_OP_REF(x) \
Logger::Log& Logger::Log::operator<<(x t) { \
m_data->m_ts << t << ' '; \
return *this; \
}
CREATE_LOG_OP_REF(uint64_t);
CREATE_LOG_OP_REF(const char*);
CREATE_LOG_OP_REF(const QString&);
CREATE_LOG_OP_REF(const QByteArray&);
CREATE_LOG_OP_REF(const void*);
#undef CREATE_LOG_OP_REF
Logger::Log& Logger::Log::operator<<(const QStringList& t) {
m_data->m_ts << '[' << t.join(",") << ']' << ' ';
return *this;
}
Logger::Log& Logger::Log::operator<<(const QJsonObject& t) {
m_data->m_ts << QJsonDocument(t).toJson(QJsonDocument::Indented) << ' ';
return *this;
}
Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) {
m_data->m_ts << t;
return *this;
}
void Logger::Log::addMetaEnum(quint64 value, const QMetaObject* meta,
const char* name) {
QMetaEnum me = meta->enumerator(meta->indexOfEnumerator(name));
QString out;
QTextStream ts(&out);
if (const char* scope = me.scope()) {
ts << scope << "::";
}
const char* key = me.valueToKey(static_cast<int>(value));
const bool scoped = me.isScoped();
if (scoped || !key) {
ts << me.enumName() << (!key ? "(" : "::");
}
if (key) {
ts << key;
} else {
ts << value << ")";
}
m_data->m_ts << out;
}

View file

@ -1,83 +0,0 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <QDebug>
#include <QFile>
#include <QString>
#include <QTextStream>
#include "mozilla/shared/loglevel.h"
class Logger
{
public:
static bool init();
static void deinit();
static QString serviceLogFileNamePath();
static void clearLogs();
static void cleanUp();
// compat with Mozilla logger
Logger(const QString &className) { m_className = className; }
const QString& className() const { return m_className; }
class Log {
public:
Log(Logger* logger, LogLevel level);
~Log();
Log& operator<<(uint64_t t);
Log& operator<<(const char* t);
Log& operator<<(const QString& t);
Log& operator<<(const QStringList& t);
Log& operator<<(const QByteArray& t);
Log& operator<<(const QJsonObject& t);
Log& operator<<(QTextStreamFunction t);
Log& operator<<(const void* t);
// Q_ENUM
template <typename T>
typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, Log&>::type
operator<<(T t) {
const QMetaObject* meta = qt_getEnumMetaObject(t);
const char* name = qt_getEnumName(t);
addMetaEnum(typename QFlags<T>::Int(t), meta, name);
return *this;
}
private:
void addMetaEnum(quint64 value, const QMetaObject* meta, const char* name);
Logger* m_logger;
LogLevel m_logLevel;
struct Data {
Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {}
QString m_buffer;
QTextStream m_ts;
};
Data* m_data;
};
Log error();
Log warning();
Log info();
Log debug();
QString sensitive(const QString& input);
private:
friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
static QFile m_file;
static QString m_logFileName;
static QTextStream m_textStream;
// compat with Mozilla logger
QString m_className;
};
#endif // LOGGER_H

View file

@ -44,7 +44,7 @@ int runApplication(int argc, char** argv)
int main(int argc, char **argv)
{
Utils::initializePath(Utils::systemLogPath());
Utils::initializePath(Logger::systemLogDir());
if (argc >= 2) {
qInfo() << "Started as console application";