mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-07-03 14:07:39 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0011305ad |
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.4.1.2
|
||||
project(${PROJECT} VERSION 4.4.0.0
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 47)
|
||||
set(APP_ANDROID_VERSION_CODE 46)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -67,7 +67,6 @@ set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
@@ -96,18 +95,7 @@ void AmneziaApplication::init()
|
||||
qFatal("Android logging initialization failed");
|
||||
}
|
||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||
connect(m_settings.get(), &Settings::saveLogsChanged,
|
||||
AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
|
||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged,
|
||||
AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
|
||||
connect(m_settings.get(), &Settings::serverRemoved,
|
||||
AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
|
||||
connect(m_settings.get(), &Settings::settingsCleared,
|
||||
[](){ AndroidController::instance()->resetLastServer(-1); });
|
||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
|
||||
[this](Vpn::ConnectionState state) {
|
||||
@@ -139,14 +127,6 @@ void AmneziaApplication::init()
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this](){
|
||||
AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled());
|
||||
});
|
||||
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) {
|
||||
AmneziaVPN::toggleScreenshots(enabled);
|
||||
});
|
||||
#endif
|
||||
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
@@ -56,10 +56,6 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||
@@ -150,22 +146,6 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".AmneziaTileService"
|
||||
android:process=":amneziaTileService"
|
||||
android:icon="@drawable/ic_amnezia_round"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="org.amnezia.vpn.qtprovider"
|
||||
|
||||
@@ -111,5 +111,4 @@ dependencies {
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.bundles.androidx.camera)
|
||||
implementation(libs.google.mlkit)
|
||||
implementation(libs.androidx.datastore)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ androidx-activity = "1.8.1"
|
||||
androidx-annotation = "1.7.0"
|
||||
androidx-camera = "1.3.0"
|
||||
androidx-security-crypto = "1.1.0-alpha06"
|
||||
androidx-datastore = "1.1.0-beta01"
|
||||
kotlinx-coroutines = "1.7.3"
|
||||
google-mlkit = "17.2.0"
|
||||
|
||||
@@ -19,7 +18,6 @@ androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.r
|
||||
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-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" }
|
||||
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.amnezia.vpn.protocol
|
||||
|
||||
// keep synchronized with client/platforms/android/android_controller.h ConnectionState
|
||||
enum class ProtocolState {
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
CONNECTING,
|
||||
DISCONNECTED,
|
||||
DISCONNECTING,
|
||||
RECONNECTING,
|
||||
UNKNOWN
|
||||
|
||||
@@ -28,10 +28,6 @@ fun Bundle.putStatus(status: Status) {
|
||||
putInt(STATE_KEY, status.state.ordinal)
|
||||
}
|
||||
|
||||
fun Bundle.putStatus(state: ProtocolState) {
|
||||
putInt(STATE_KEY, state.ordinal)
|
||||
}
|
||||
|
||||
fun Bundle.getStatus(): Status =
|
||||
Status.build {
|
||||
setState(ProtocolState.entries[getInt(STATE_KEY)])
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="connecting">Подключение</string>
|
||||
<string name="disconnecting">Отключение</string>
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="disconnecting">Disconnecting</string>
|
||||
</resources>
|
||||
@@ -14,7 +14,6 @@ import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
@@ -27,7 +26,9 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.getStatistics
|
||||
import org.amnezia.vpn.protocol.getStatus
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
@@ -35,11 +36,11 @@ import org.amnezia.vpn.util.Log
|
||||
import org.qtproject.qt.android.bindings.QtActivity
|
||||
|
||||
private const val TAG = "AmneziaActivity"
|
||||
const val ACTIVITY_MESSENGER_NAME = "Activity"
|
||||
|
||||
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
||||
private const val CREATE_FILE_ACTION_CODE = 2
|
||||
private const val OPEN_FILE_ACTION_CODE = 3
|
||||
private const val BIND_SERVICE_TIMEOUT = 1000L
|
||||
|
||||
class AmneziaActivity : QtActivity() {
|
||||
|
||||
@@ -57,17 +58,25 @@ class AmneziaActivity : QtActivity() {
|
||||
val event = msg.extractIpcMessage<ServiceEvent>()
|
||||
Log.d(TAG, "Handle event: $event")
|
||||
when (event) {
|
||||
ServiceEvent.STATUS_CHANGED -> {
|
||||
msg.data?.getStatus()?.let { (state) ->
|
||||
Log.d(TAG, "Handle protocol state: $state")
|
||||
QtAndroidController.onVpnStateChanged(state.ordinal)
|
||||
}
|
||||
ServiceEvent.CONNECTED -> {
|
||||
QtAndroidController.onVpnConnected()
|
||||
}
|
||||
|
||||
ServiceEvent.DISCONNECTED -> {
|
||||
QtAndroidController.onVpnDisconnected()
|
||||
doUnbindService()
|
||||
}
|
||||
|
||||
ServiceEvent.RECONNECTING -> {
|
||||
QtAndroidController.onVpnReconnecting()
|
||||
}
|
||||
|
||||
ServiceEvent.STATUS -> {
|
||||
if (isWaitingStatus) {
|
||||
isWaitingStatus = false
|
||||
msg.data?.getStatus()?.let { QtAndroidController.onStatus(it) }
|
||||
msg.data?.getStatus()?.let { (state) ->
|
||||
QtAndroidController.onStatus(state.ordinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +87,7 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
|
||||
ServiceEvent.ERROR -> {
|
||||
msg.data?.getString(MSG_ERROR)?.let { error ->
|
||||
msg.data?.getString(ERROR_MSG)?.let { error ->
|
||||
Log.e(TAG, "From VpnService: $error")
|
||||
}
|
||||
// todo: add error reporting to Qt
|
||||
@@ -100,15 +109,14 @@ class AmneziaActivity : QtActivity() {
|
||||
// get a messenger from the service to send actions to the service
|
||||
vpnServiceMessenger.set(Messenger(service))
|
||||
// send a messenger to the service to process service events
|
||||
vpnServiceMessenger.send(
|
||||
Action.REGISTER_CLIENT.packToMessage {
|
||||
putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
|
||||
},
|
||||
replyTo = activityMessenger
|
||||
)
|
||||
vpnServiceMessenger.send {
|
||||
Action.REGISTER_CLIENT.packToMessage().apply {
|
||||
replyTo = activityMessenger
|
||||
}
|
||||
}
|
||||
isServiceConnected = true
|
||||
if (isWaitingStatus) {
|
||||
vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger)
|
||||
vpnServiceMessenger.send(Action.REQUEST_STATUS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +126,6 @@ class AmneziaActivity : QtActivity() {
|
||||
vpnServiceMessenger.reset()
|
||||
isWaitingStatus = true
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
@@ -141,11 +148,8 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
vpnServiceMessenger = IpcMessenger(
|
||||
"VpnService",
|
||||
onDeadObjectException = {
|
||||
doUnbindService()
|
||||
doBindService()
|
||||
}
|
||||
onDeadObjectException = ::doUnbindService,
|
||||
messengerName = "VpnService"
|
||||
)
|
||||
intent?.let(::processIntent)
|
||||
}
|
||||
@@ -240,9 +244,10 @@ class AmneziaActivity : QtActivity() {
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
handleBindTimeout()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -251,14 +256,26 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.d(TAG, "Unbind service")
|
||||
isWaitingStatus = true
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||
vpnServiceMessenger.reset()
|
||||
isServiceConnected = false
|
||||
isInBoundState = false
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBindTimeout() {
|
||||
mainScope.launch {
|
||||
if (isWaitingStatus) {
|
||||
delay(BIND_SERVICE_TIMEOUT)
|
||||
if (isWaitingStatus && !isServiceConnected) {
|
||||
Log.d(TAG, "Bind timeout, reset connection status")
|
||||
isWaitingStatus = false
|
||||
QtAndroidController.onStatus(ProtocolState.DISCONNECTED.ordinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods of starting and stopping VpnService
|
||||
*/
|
||||
@@ -295,7 +312,7 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.d(TAG, "Connect to VPN")
|
||||
vpnServiceMessenger.send {
|
||||
Action.CONNECT.packToMessage {
|
||||
putString(MSG_VPN_CONFIG, vpnConfig)
|
||||
putString(VPN_CONFIG, vpnConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,7 +320,7 @@ class AmneziaActivity : QtActivity() {
|
||||
private fun startVpnService(vpnConfig: String) {
|
||||
Log.d(TAG, "Start VPN service")
|
||||
Intent(this, AmneziaVpnService::class.java).apply {
|
||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||
putExtra(VPN_CONFIG, vpnConfig)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
@@ -352,22 +369,6 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun resetLastServer(index: Int) {
|
||||
Log.v(TAG, "Reset server: $index")
|
||||
mainScope.launch {
|
||||
VpnStateStore.store {
|
||||
if (index == -1 || it.serverIndex == index) {
|
||||
VpnState.defaultState
|
||||
} else if (it.serverIndex > index) {
|
||||
it.copy(serverIndex = it.serverIndex - 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun saveFile(fileName: String, data: String) {
|
||||
Log.d(TAG, "Save file $fileName")
|
||||
@@ -437,7 +438,7 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.saveLogs = enabled
|
||||
vpnServiceMessenger.send {
|
||||
Action.SET_SAVE_LOGS.packToMessage {
|
||||
putBoolean(MSG_SAVE_LOGS, enabled)
|
||||
putBoolean(SAVE_LOGS, enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,13 +455,4 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.v(TAG, "Clear logs")
|
||||
Log.clearLogs()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun setScreenshotsEnabled(enabled: Boolean) {
|
||||
Log.v(TAG, "Set screenshots enabled: $enabled")
|
||||
mainScope.launch {
|
||||
val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE
|
||||
window.setFlags(flag, LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
||||
super.onCreate()
|
||||
Prefs.init(this)
|
||||
Log.init(this)
|
||||
VpnStateStore.init(this)
|
||||
Log.d(TAG, "Create Amnezia application")
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Messenger
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "AmneziaTileService"
|
||||
private const val DEFAULT_TILE_LABEL = "AmneziaVPN"
|
||||
|
||||
class AmneziaTileService : TileService() {
|
||||
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var vpnStateListeningJob: Job? = null
|
||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||
|
||||
@Volatile
|
||||
private var isServiceConnected = false
|
||||
private var isInBoundState = false
|
||||
@Volatile
|
||||
private var isVpnConfigExists = false
|
||||
|
||||
private val serviceConnection: ServiceConnection by lazy(NONE) {
|
||||
object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
Log.d(TAG, "Service ${name?.flattenToString()} was connected")
|
||||
vpnServiceMessenger.set(Messenger(service))
|
||||
isServiceConnected = true
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
Log.w(TAG, "Service ${name?.flattenToString()} was unexpectedly disconnected")
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.reset()
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||
doUnbindService()
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "Create Amnezia Tile Service")
|
||||
scope = CoroutineScope(SupervisorJob())
|
||||
vpnServiceMessenger = IpcMessenger(
|
||||
"VpnService",
|
||||
onDeadObjectException = ::doUnbindService
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy Amnezia Tile Service")
|
||||
doUnbindService()
|
||||
scope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
// Workaround for some bugs
|
||||
override fun onBind(intent: Intent?): IBinder? =
|
||||
try {
|
||||
super.onBind(intent)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Failed to bind AmneziaTileService: $e")
|
||||
null
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
Log.d(TAG, "Start listening")
|
||||
if (AmneziaVpnService.isRunning(applicationContext)) {
|
||||
Log.d(TAG, "Vpn service is running")
|
||||
doBindService()
|
||||
} else {
|
||||
Log.d(TAG, "Vpn service is not running")
|
||||
isServiceConnected = false
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
Log.d(TAG, "Stop listening")
|
||||
vpnStateListeningJob?.cancel()
|
||||
vpnStateListeningJob = null
|
||||
doUnbindService()
|
||||
super.onStopListening()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
Log.d(TAG, "onClick")
|
||||
if (isLocked) {
|
||||
unlockAndRun { onClickInternal() }
|
||||
} else {
|
||||
onClickInternal()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClickInternal() {
|
||||
if (isVpnConfigExists) {
|
||||
Log.d(TAG, "Change VPN state")
|
||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||
Log.d(TAG, "Start VPN")
|
||||
updateVpnState(CONNECTING)
|
||||
startVpn()
|
||||
} else if (qsTile.state == Tile.STATE_ACTIVE) {
|
||||
Log.d(TAG, "Stop vpn")
|
||||
updateVpnState(DISCONNECTING)
|
||||
stopVpn()
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Start Activity")
|
||||
Intent(this, AmneziaActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
startActivityAndCollapseCompat(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
private fun doUnbindService() {
|
||||
if (isInBoundState) {
|
||||
Log.d(TAG, "Unbind service")
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.reset()
|
||||
isInBoundState = false
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpn() {
|
||||
if (isServiceConnected) {
|
||||
connectToVpn()
|
||||
} else {
|
||||
if (checkPermission()) {
|
||||
startVpnService()
|
||||
doBindService()
|
||||
} else {
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermission() =
|
||||
if (VpnService.prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
startActivityAndCollapseCompat(it)
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
private fun startVpnService() =
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
|
||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||
|
||||
private fun stopVpn() = vpnServiceMessenger.send(Action.DISCONNECT)
|
||||
|
||||
@SuppressLint("StartActivityAndCollapseDeprecated")
|
||||
private fun startActivityAndCollapseCompat(intent: Intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startActivityAndCollapse(
|
||||
PendingIntent.getActivity(
|
||||
applicationContext,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityAndCollapse(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVpnState(state: ProtocolState) {
|
||||
scope.launch {
|
||||
VpnStateStore.store { it.copy(protocolState = state) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchVpnStateListening() =
|
||||
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
||||
|
||||
private fun updateTile(vpnState: VpnState) {
|
||||
Log.d(TAG, "Update tile: $vpnState")
|
||||
isVpnConfigExists = vpnState.serverName != null
|
||||
val tile = qsTile ?: return
|
||||
tile.apply {
|
||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||
when (vpnState.protocolState) {
|
||||
CONNECTED -> {
|
||||
state = Tile.STATE_ACTIVE
|
||||
subtitleCompat = null
|
||||
}
|
||||
|
||||
DISCONNECTED, UNKNOWN -> {
|
||||
state = Tile.STATE_INACTIVE
|
||||
subtitleCompat = null
|
||||
}
|
||||
|
||||
CONNECTING, RECONNECTING -> {
|
||||
state = Tile.STATE_UNAVAILABLE
|
||||
subtitleCompat = resources.getString(R.string.connecting)
|
||||
}
|
||||
|
||||
DISCONNECTING -> {
|
||||
state = Tile.STATE_UNAVAILABLE
|
||||
subtitleCompat = resources.getString(R.string.disconnecting)
|
||||
}
|
||||
}
|
||||
updateTile()
|
||||
}
|
||||
// double update to fix weird visual glitches
|
||||
tile.updateTile()
|
||||
}
|
||||
|
||||
private var Tile.subtitleCompat: CharSequence?
|
||||
set(value) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
this.subtitle = value
|
||||
}
|
||||
}
|
||||
get() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
return this.subtitle
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
|
||||
@@ -19,7 +16,6 @@ import android.os.Process
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -30,7 +26,6 @@ import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -44,11 +39,14 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.Status
|
||||
import org.amnezia.vpn.protocol.VpnException
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.putStatistics
|
||||
import org.amnezia.vpn.protocol.putStatus
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.util.Log
|
||||
@@ -59,16 +57,12 @@ import org.json.JSONObject
|
||||
|
||||
private const val TAG = "AmneziaVpnService"
|
||||
|
||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||
const val MSG_ERROR = "ERROR"
|
||||
const val MSG_SAVE_LOGS = "SAVE_LOGS"
|
||||
const val MSG_CLIENT_NAME = "CLIENT_NAME"
|
||||
const val VPN_CONFIG = "VPN_CONFIG"
|
||||
const val ERROR_MSG = "ERROR_MSG"
|
||||
const val SAVE_LOGS = "SAVE_LOGS"
|
||||
|
||||
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
||||
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||
private const val NOTIFICATION_ID = 1337
|
||||
private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||
private const val DISCONNECT_TIMEOUT = 5000L
|
||||
@@ -82,8 +76,6 @@ class AmneziaVpnService : VpnService() {
|
||||
private var protocol: Protocol? = null
|
||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||
private var serverName: String? = null
|
||||
private var serverIndex: Int = -1
|
||||
|
||||
private val isConnected
|
||||
get() = protocolState.value == CONNECTED
|
||||
@@ -97,11 +89,8 @@ class AmneziaVpnService : VpnService() {
|
||||
private var connectionJob: Job? = null
|
||||
private var disconnectionJob: Job? = null
|
||||
private var statisticsSendingJob: Job? = null
|
||||
private lateinit var clientMessenger: IpcMessenger
|
||||
private lateinit var networkState: NetworkState
|
||||
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
|
||||
|
||||
private val isActivityConnected
|
||||
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
||||
|
||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||
protocolState.value = DISCONNECTED
|
||||
@@ -127,22 +116,13 @@ class AmneziaVpnService : VpnService() {
|
||||
Log.d(TAG, "Handle action: $action")
|
||||
when (action) {
|
||||
Action.REGISTER_CLIENT -> {
|
||||
val clientName = msg.data.getString(MSG_CLIENT_NAME)
|
||||
val messenger = IpcMessenger(msg.replyTo, clientName)
|
||||
clientMessengers[msg.replyTo] = messenger
|
||||
Log.d(TAG, "Messenger client '$clientName' was registered")
|
||||
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
|
||||
}
|
||||
|
||||
Action.UNREGISTER_CLIENT -> {
|
||||
clientMessengers.remove(msg.replyTo)?.let {
|
||||
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
|
||||
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
||||
}
|
||||
clientMessenger.set(msg.replyTo)
|
||||
}
|
||||
|
||||
Action.CONNECT -> {
|
||||
connect(msg.data.getString(MSG_VPN_CONFIG))
|
||||
val vpnConfig = msg.data.getString(VPN_CONFIG)
|
||||
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
||||
connect(vpnConfig)
|
||||
}
|
||||
|
||||
Action.DISCONNECT -> {
|
||||
@@ -150,17 +130,17 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
Action.REQUEST_STATUS -> {
|
||||
clientMessengers[msg.replyTo]?.let { clientMessenger ->
|
||||
clientMessenger.send {
|
||||
ServiceEvent.STATUS.packToMessage {
|
||||
putStatus(this@AmneziaVpnService.protocolState.value)
|
||||
}
|
||||
clientMessenger.send {
|
||||
ServiceEvent.STATUS.packToMessage {
|
||||
putStatus(Status.build {
|
||||
setState(this@AmneziaVpnService.protocolState.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Action.SET_SAVE_LOGS -> {
|
||||
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
|
||||
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +189,7 @@ class AmneziaVpnService : VpnService() {
|
||||
Log.d(TAG, "Create Amnezia VPN service")
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
|
||||
loadServerData()
|
||||
clientMessenger = IpcMessenger(messengerName = "Client")
|
||||
launchProtocolStateHandler()
|
||||
networkState = NetworkState(this, ::reconnect)
|
||||
}
|
||||
@@ -221,13 +201,15 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
if (isAlwaysOnCompat) {
|
||||
Log.d(TAG, "Start service via Always-on")
|
||||
connect()
|
||||
connect(Prefs.load(PREFS_CONFIG_KEY))
|
||||
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
||||
Log.d(TAG, "Start service after permission check")
|
||||
connect()
|
||||
connect(Prefs.load(PREFS_CONFIG_KEY))
|
||||
} else {
|
||||
Log.d(TAG, "Start service")
|
||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||
val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
|
||||
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
||||
connect(vpnConfig)
|
||||
}
|
||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
|
||||
return START_REDELIVER_INTENT
|
||||
@@ -237,16 +219,17 @@ class AmneziaVpnService : VpnService() {
|
||||
Log.d(TAG, "onBind by $intent")
|
||||
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
|
||||
isServiceBound = true
|
||||
if (isConnected) launchSendingStatistics()
|
||||
return vpnServiceMessenger.binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
Log.d(TAG, "onUnbind by $intent")
|
||||
if (intent?.action != SERVICE_INTERFACE) {
|
||||
if (clientMessengers.isEmpty()) {
|
||||
isServiceBound = false
|
||||
if (isUnknown || isDisconnected) stopService()
|
||||
}
|
||||
isServiceBound = false
|
||||
stopSendingStatistics()
|
||||
clientMessenger.reset()
|
||||
if (isUnknown || isDisconnected) stopService()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -255,6 +238,7 @@ class AmneziaVpnService : VpnService() {
|
||||
Log.d(TAG, "onRebind by $intent")
|
||||
if (intent?.action != SERVICE_INTERFACE) {
|
||||
isServiceBound = true
|
||||
if (isConnected) launchSendingStatistics()
|
||||
}
|
||||
super.onRebind(intent)
|
||||
}
|
||||
@@ -294,16 +278,17 @@ class AmneziaVpnService : VpnService() {
|
||||
*/
|
||||
private fun launchProtocolStateHandler() {
|
||||
mainScope.launch {
|
||||
// drop first default UNKNOWN state
|
||||
protocolState.drop(1).collect { protocolState ->
|
||||
protocolState.collect { protocolState ->
|
||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
clientMessenger.send(ServiceEvent.CONNECTED)
|
||||
networkState.bindNetworkListener()
|
||||
if (isActivityConnected) launchSendingStatistics()
|
||||
if (isServiceBound) launchSendingStatistics()
|
||||
}
|
||||
|
||||
DISCONNECTED -> {
|
||||
clientMessenger.send(ServiceEvent.DISCONNECTED)
|
||||
networkState.unbindNetworkListener()
|
||||
stopSendingStatistics()
|
||||
if (!isServiceBound) stopService()
|
||||
@@ -315,19 +300,12 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
RECONNECTING -> {
|
||||
clientMessenger.send(ServiceEvent.RECONNECTING)
|
||||
stopSendingStatistics()
|
||||
}
|
||||
|
||||
CONNECTING, UNKNOWN -> {}
|
||||
}
|
||||
|
||||
clientMessengers.send {
|
||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||
putStatus(protocolState)
|
||||
}
|
||||
}
|
||||
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,17 +332,7 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun connect(vpnConfig: String? = null) {
|
||||
if (vpnConfig == null) {
|
||||
connectToVpn(Prefs.load(PREFS_CONFIG_KEY))
|
||||
} else {
|
||||
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
||||
connectToVpn(vpnConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun connectToVpn(vpnConfig: String) {
|
||||
private fun connect(vpnConfig: String?) {
|
||||
if (isConnected || protocolState.value == CONNECTING) return
|
||||
|
||||
Log.d(TAG, "Start VPN connection")
|
||||
@@ -372,7 +340,6 @@ class AmneziaVpnService : VpnService() {
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
val config = parseConfigToJson(vpnConfig)
|
||||
saveServerData(config)
|
||||
if (config == null) {
|
||||
onError("Invalid VPN config")
|
||||
protocolState.value = DISCONNECTED
|
||||
@@ -450,38 +417,24 @@ class AmneziaVpnService : VpnService() {
|
||||
private fun onError(msg: String) {
|
||||
Log.e(TAG, msg)
|
||||
mainScope.launch {
|
||||
clientMessengers.send {
|
||||
clientMessenger.send {
|
||||
ServiceEvent.ERROR.packToMessage {
|
||||
putString(MSG_ERROR, msg)
|
||||
putString(ERROR_MSG, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseConfigToJson(vpnConfig: String): JSONObject? =
|
||||
if (vpnConfig.isBlank()) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
JSONObject(vpnConfig)
|
||||
} catch (e: JSONException) {
|
||||
onError("Invalid VPN config json format: ${e.message}")
|
||||
null
|
||||
private fun parseConfigToJson(vpnConfig: String?): JSONObject? =
|
||||
try {
|
||||
vpnConfig?.let {
|
||||
JSONObject(it)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
onError("Invalid VPN config json format: ${e.message}")
|
||||
null
|
||||
}
|
||||
|
||||
private fun saveServerData(config: JSONObject?) {
|
||||
serverName = config?.opt("description") as String?
|
||||
serverIndex = config?.opt("serverIndex") as Int? ?: -1
|
||||
Prefs.save(PREFS_SERVER_NAME, serverName)
|
||||
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
|
||||
}
|
||||
|
||||
private fun loadServerData() {
|
||||
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
|
||||
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
|
||||
}
|
||||
|
||||
private fun checkPermission(): Boolean =
|
||||
if (prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
@@ -493,12 +446,4 @@ class AmneziaVpnService : VpnService() {
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isRunning(context: Context): Boolean =
|
||||
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
|
||||
.runningAppProcesses.any {
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ sealed interface IpcMessage {
|
||||
}
|
||||
|
||||
enum class ServiceEvent : IpcMessage {
|
||||
STATUS_CHANGED,
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
RECONNECTING,
|
||||
STATUS,
|
||||
STATISTICS_UPDATE,
|
||||
ERROR
|
||||
@@ -28,7 +30,6 @@ enum class ServiceEvent : IpcMessage {
|
||||
|
||||
enum class Action : IpcMessage {
|
||||
REGISTER_CLIENT,
|
||||
UNREGISTER_CLIENT,
|
||||
CONNECT,
|
||||
DISCONNECT,
|
||||
REQUEST_STATUS,
|
||||
|
||||
@@ -9,21 +9,11 @@ import org.amnezia.vpn.util.Log
|
||||
private const val TAG = "IpcMessenger"
|
||||
|
||||
class IpcMessenger(
|
||||
messengerName: String? = null,
|
||||
private val onDeadObjectException: () -> Unit = {},
|
||||
private val onRemoteException: () -> Unit = {}
|
||||
private val onRemoteException: () -> Unit = {},
|
||||
private val messengerName: String = "Unknown"
|
||||
) {
|
||||
private var messenger: Messenger? = null
|
||||
val name = messengerName ?: "Unknown"
|
||||
|
||||
constructor(
|
||||
messenger: Messenger,
|
||||
messengerName: String? = null,
|
||||
onDeadObjectException: () -> Unit = {},
|
||||
onRemoteException: () -> Unit = {}
|
||||
) : this(messengerName, onDeadObjectException, onRemoteException) {
|
||||
this.messenger = messenger
|
||||
}
|
||||
|
||||
fun set(messenger: Messenger) {
|
||||
this.messenger = messenger
|
||||
@@ -35,29 +25,19 @@ class IpcMessenger(
|
||||
|
||||
fun send(msg: () -> Message) = messenger?.sendMsg(msg())
|
||||
|
||||
fun send(msg: Message, replyTo: Messenger) = messenger?.sendMsg(msg.apply { this.replyTo = replyTo })
|
||||
|
||||
fun <T> send(msg: T)
|
||||
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
|
||||
|
||||
fun <T> send(msg: T, replyTo: Messenger)
|
||||
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage().apply { this.replyTo = replyTo })
|
||||
|
||||
private fun Messenger.sendMsg(msg: Message) {
|
||||
try {
|
||||
send(msg)
|
||||
} catch (e: DeadObjectException) {
|
||||
Log.w(TAG, "$name messenger is dead")
|
||||
Log.w(TAG, "$messengerName messenger is dead")
|
||||
messenger = null
|
||||
onDeadObjectException()
|
||||
} catch (e: RemoteException) {
|
||||
Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}")
|
||||
Log.w(TAG, "Sending a message to the $messengerName messenger failed: ${e.message}")
|
||||
onRemoteException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Map<Messenger, IpcMessenger>.send(msg: () -> Message) = this.values.forEach { it.send(msg) }
|
||||
|
||||
fun <T> Map<Messenger, IpcMessenger>.send(msg: T)
|
||||
where T : Enum<T>, T : IpcMessage = this.values.forEach { it.send(msg) }
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.app.Application
|
||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.dataStoreFile
|
||||
import java.io.InputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.Serializable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "VpnState"
|
||||
private const val STORE_FILE_NAME = "vpnState"
|
||||
|
||||
data class VpnState(
|
||||
val protocolState: ProtocolState,
|
||||
val serverName: String? = null,
|
||||
val serverIndex: Int = -1
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = -1760654961004181606
|
||||
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
|
||||
object VpnStateStore {
|
||||
private lateinit var app: Application
|
||||
|
||||
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||
serializer = VpnStateSerializer(),
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
||||
)
|
||||
|
||||
fun init(app: Application) {
|
||||
Log.v(TAG, "Init VpnStateStore")
|
||||
this.app = app
|
||||
}
|
||||
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data
|
||||
|
||||
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
||||
try {
|
||||
dataStore.updateData(f)
|
||||
} catch (e : Exception) {
|
||||
Log.e(TAG, "Failed to store VpnState: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VpnStateSerializer : Serializer<VpnState> {
|
||||
override val defaultValue: VpnState = VpnState.defaultState
|
||||
|
||||
override suspend fun readFrom(input: InputStream): VpnState {
|
||||
return withContext(Dispatchers.IO) {
|
||||
ObjectInputStream(input).use {
|
||||
it.readObject() as VpnState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
ObjectOutputStream(output).use {
|
||||
it.writeObject(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,18 @@
|
||||
package org.amnezia.vpn.qt
|
||||
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.Status
|
||||
|
||||
/**
|
||||
* JNI functions of the AndroidController class from android_controller.cpp,
|
||||
* called by events in the Android part of the client
|
||||
*/
|
||||
object QtAndroidController {
|
||||
|
||||
fun onStatus(status: Status) = onStatus(status.state)
|
||||
fun onStatus(protocolState: ProtocolState) = onStatus(protocolState.ordinal)
|
||||
|
||||
external fun onStatus(stateCode: Int)
|
||||
external fun onServiceDisconnected()
|
||||
external fun onServiceError()
|
||||
|
||||
external fun onVpnPermissionRejected()
|
||||
external fun onVpnStateChanged(stateCode: Int)
|
||||
external fun onVpnConnected()
|
||||
external fun onVpnDisconnected()
|
||||
external fun onVpnReconnecting()
|
||||
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||
|
||||
external fun onFileOpened(uri: String)
|
||||
|
||||
@@ -107,7 +107,6 @@ target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
|
||||
@@ -76,7 +76,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::SshScpFailureError;
|
||||
*errorCode = ErrorCode::SshSftpFailureError;
|
||||
}
|
||||
|
||||
return connData;
|
||||
|
||||
@@ -159,7 +159,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||
|
||||
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||
libssh::SftpOverwriteMode::SftpAppendToExisting);
|
||||
|
||||
if (e) {
|
||||
if (errorCode)
|
||||
|
||||
@@ -118,7 +118,7 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
|
||||
|
||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &file, const QString &path,
|
||||
libssh::ScpOverwriteMode overwriteMode)
|
||||
libssh::SftpOverwriteMode overwriteMode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||
@@ -139,7 +139,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)),
|
||||
@@ -147,7 +147,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
|
||||
if (e)
|
||||
return e;
|
||||
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)),
|
||||
@@ -199,7 +199,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
|
||||
const QString &remotePath, libssh::ScpOverwriteMode overwriteMode)
|
||||
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
|
||||
{
|
||||
auto error = m_sshClient.connectToHost(credentials);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -211,7 +211,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
||||
localFile.write(data);
|
||||
localFile.close();
|
||||
|
||||
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
||||
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
||||
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
ErrorCode uploadTextFileToContainer(
|
||||
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, ErrorCode *errorCode = nullptr);
|
||||
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
||||
|
||||
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
|
||||
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
||||
|
||||
|
||||
+16
-11
@@ -46,12 +46,25 @@ namespace amnezia
|
||||
SshPrivateKeyFormatError = 304,
|
||||
SshTimeoutError = 305,
|
||||
|
||||
// Ssh scp errors
|
||||
SshScpFailureError = 400,
|
||||
// Ssh sftp errors
|
||||
SshSftpEofError = 400,
|
||||
SshSftpNoSuchFileError = 401,
|
||||
SshSftpPermissionDeniedError = 402,
|
||||
SshSftpFailureError = 403,
|
||||
SshSftpBadMessageError = 404,
|
||||
SshSftpNoConnectionError = 405,
|
||||
SshSftpConnectionLostError = 406,
|
||||
SshSftpOpUnsupportedError = 407,
|
||||
SshSftpInvalidHandleError = 408,
|
||||
SshSftpNoSuchPathError = 409,
|
||||
SshSftpFileAlreadyExistsError = 410,
|
||||
SshSftpWriteProtectError = 411,
|
||||
SshSftpNoMediaError = 412,
|
||||
|
||||
// Local errors
|
||||
OpenVpnConfigMissing = 500,
|
||||
OpenVpnManagementServerError = 501,
|
||||
ConfigMissing = 502,
|
||||
|
||||
// Distro errors
|
||||
OpenVpnExecutableMissing = 600,
|
||||
@@ -79,15 +92,7 @@ namespace amnezia
|
||||
|
||||
// Api errors
|
||||
ApiConfigDownloadError = 1100,
|
||||
ApiConfigAlreadyAdded = 1101,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
ReadError = 1201,
|
||||
PermissionsError = 1202,
|
||||
UnspecifiedError = 1203,
|
||||
FatalError = 1204,
|
||||
AbortError = 1205
|
||||
ApiConfigAlreadyAdded = 1101
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -28,8 +28,20 @@ QString errorString(ErrorCode code) {
|
||||
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||
|
||||
// Ssh scp errors
|
||||
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||
// Libssh sftp errors
|
||||
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
|
||||
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
|
||||
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
|
||||
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
|
||||
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
|
||||
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
|
||||
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
|
||||
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
|
||||
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
|
||||
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
|
||||
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
|
||||
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
|
||||
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
|
||||
|
||||
// Local errors
|
||||
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||
@@ -56,14 +68,6 @@ QString errorString(ErrorCode code) {
|
||||
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
||||
|
||||
// QFile errors
|
||||
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
||||
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||
|
||||
case(InternalError):
|
||||
default:
|
||||
errorMessage = QObject::tr("Internal error"); break;
|
||||
|
||||
+91
-57
@@ -10,10 +10,16 @@ const uint32_t S_IRWXU = 0644;
|
||||
#endif
|
||||
|
||||
namespace libssh {
|
||||
constexpr auto libsshTimeoutError{"Timeout connecting to"};
|
||||
const QString libsshTimeoutError = "Timeout connecting to";
|
||||
|
||||
std::function<QString()> Client::m_passphraseCallback;
|
||||
|
||||
Client::Client(QObject *parent) : QObject(parent)
|
||||
{ }
|
||||
|
||||
Client::~Client()
|
||||
{ }
|
||||
|
||||
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
|
||||
{
|
||||
auto passphrase = m_passphraseCallback();
|
||||
@@ -165,13 +171,13 @@ namespace libssh {
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
auto errorCode = readOutput(false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
auto error = readOutput(false);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
errorCode = readOutput(true);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
error = readOutput(true);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
return closeChannel();
|
||||
@@ -216,79 +222,100 @@ namespace libssh {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
|
||||
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
|
||||
{
|
||||
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
|
||||
m_sftpSession = sftp_new(m_session);
|
||||
|
||||
if (m_scpSession == nullptr) {
|
||||
return fromLibsshErrorCode();
|
||||
if (m_sftpSession == nullptr) {
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
if (ssh_scp_init(m_scpSession) != SSH_OK) {
|
||||
auto errorCode = fromLibsshErrorCode();
|
||||
closeScpSession();
|
||||
return errorCode;
|
||||
int result = sftp_init(m_sftpSession);
|
||||
|
||||
if (result != SSH_OK) {
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
QFutureWatcher<ErrorCode> watcher;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished);
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
|
||||
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
||||
const int localFileSize = QFileInfo(localPath).size();
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
|
||||
|
||||
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType);
|
||||
if (result != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
|
||||
int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
||||
sftp_file file;
|
||||
const size_t bufferSize = 16384;
|
||||
char buffer[bufferSize];
|
||||
|
||||
file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU);
|
||||
|
||||
if (file == nullptr) {
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
int localFileSize = QFileInfo(localPath).size();
|
||||
int chunksCount = localFileSize / (bufferSize);
|
||||
|
||||
QFile fin(localPath);
|
||||
|
||||
if (fin.open(QIODevice::ReadOnly)) {
|
||||
constexpr size_t bufferSize = 16384;
|
||||
int transferred = 0;
|
||||
int currentChunkSize = bufferSize;
|
||||
for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) {
|
||||
QByteArray chunk = fin.read(bufferSize);
|
||||
if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError;
|
||||
|
||||
while (transferred < localFileSize) {
|
||||
int bytesWritten = sftp_write(file, chunk.data(), chunk.size());
|
||||
|
||||
// Last Chunk
|
||||
if ((localFileSize - transferred) < bufferSize) {
|
||||
currentChunkSize = localFileSize % bufferSize;
|
||||
if (bytesWritten != chunk.size()) {
|
||||
fin.close();
|
||||
sftp_close(file);
|
||||
return closeSftpSession();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray chunk = fin.read(currentChunkSize);
|
||||
if (chunk.size() != currentChunkSize) {
|
||||
return fromFileErrorCode(fin.error());
|
||||
int lastChunkSize = localFileSize % bufferSize;
|
||||
|
||||
if (lastChunkSize != 0) {
|
||||
QByteArray lastChunk = fin.read(lastChunkSize);
|
||||
if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError;
|
||||
|
||||
int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize);
|
||||
|
||||
if (bytesWritten != lastChunkSize) {
|
||||
fin.close();
|
||||
sftp_close(file);
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size());
|
||||
if (result != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
transferred += currentChunkSize;
|
||||
}
|
||||
} else {
|
||||
return fromFileErrorCode(fin.error());
|
||||
sftp_close(file);
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
fin.close();
|
||||
|
||||
int result = sftp_close(file);
|
||||
if (result != SSH_OK) {
|
||||
return closeSftpSession();
|
||||
}
|
||||
|
||||
return closeSftpSession();
|
||||
});
|
||||
watcher.setFuture(future);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
|
||||
QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
closeScpSession();
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
void Client::closeScpSession()
|
||||
ErrorCode Client::closeSftpSession()
|
||||
{
|
||||
if (m_scpSession != nullptr) {
|
||||
ssh_scp_free(m_scpSession);
|
||||
m_scpSession = nullptr;
|
||||
auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession));
|
||||
if (m_sftpSession != nullptr) {
|
||||
sftp_free(m_sftpSession);
|
||||
m_sftpSession = nullptr;
|
||||
}
|
||||
qCritical() << ssh_get_error(m_session);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode Client::fromLibsshErrorCode()
|
||||
@@ -310,17 +337,24 @@ namespace libssh {
|
||||
default: return ErrorCode::SshInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
|
||||
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
|
||||
{
|
||||
switch (fileError) {
|
||||
case QFileDevice::NoError: return ErrorCode::NoError;
|
||||
case QFileDevice::ReadError: return ErrorCode::ReadError;
|
||||
case QFileDevice::OpenError: return ErrorCode::OpenError;
|
||||
case QFileDevice::PermissionsError: return ErrorCode::PermissionsError;
|
||||
case QFileDevice::FatalError: return ErrorCode::FatalError;
|
||||
case QFileDevice::AbortError: return ErrorCode::AbortError;
|
||||
default: return ErrorCode::UnspecifiedError;
|
||||
switch (errorCode) {
|
||||
case(SSH_FX_OK): return ErrorCode::NoError;
|
||||
case(SSH_FX_EOF): return ErrorCode::SshSftpEofError;
|
||||
case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError;
|
||||
case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError;
|
||||
case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError;
|
||||
case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError;
|
||||
case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError;
|
||||
case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError;
|
||||
case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError;
|
||||
case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError;
|
||||
case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError;
|
||||
case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError;
|
||||
case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError;
|
||||
case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError;
|
||||
default: return ErrorCode::SshSftpFailureError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -2,29 +2,29 @@
|
||||
#define SSHCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace libssh {
|
||||
enum ScpOverwriteMode {
|
||||
enum SftpOverwriteMode {
|
||||
/*! Overwrite any existing files */
|
||||
ScpOverwriteExisting = O_TRUNC,
|
||||
SftpOverwriteExisting = O_TRUNC,
|
||||
/*! Append new content if the file already exists */
|
||||
ScpAppendToExisting = O_APPEND
|
||||
SftpAppendToExisting = O_APPEND
|
||||
};
|
||||
class Client : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Client() = default;
|
||||
~Client() = default;
|
||||
Client(QObject *parent = nullptr);
|
||||
~Client();
|
||||
|
||||
ErrorCode connectToHost(const ServerCredentials &credentials);
|
||||
void disconnectFromHost();
|
||||
@@ -32,26 +32,26 @@ namespace libssh {
|
||||
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
|
||||
ErrorCode writeResponse(const QString &data);
|
||||
ErrorCode scpFileCopy(const ScpOverwriteMode overwriteMode,
|
||||
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
|
||||
const QString &localPath,
|
||||
const QString &remotePath,
|
||||
const QString &fileDesc);
|
||||
const QString& fileDesc);
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
||||
private:
|
||||
ErrorCode closeChannel();
|
||||
void closeScpSession();
|
||||
ErrorCode closeSftpSession();
|
||||
ErrorCode fromLibsshErrorCode();
|
||||
ErrorCode fromFileErrorCode(QFileDevice::FileError fileError);
|
||||
ErrorCode fromLibsshSftpErrorCode(int errorCode);
|
||||
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
|
||||
|
||||
ssh_session m_session = nullptr;
|
||||
ssh_channel m_channel = nullptr;
|
||||
ssh_scp m_scpSession = nullptr;
|
||||
sftp_session m_sftpSession = nullptr;
|
||||
|
||||
static std::function<QString()> m_passphraseCallback;
|
||||
signals:
|
||||
void writeToChannelFinished();
|
||||
void scpFileCopyFinished();
|
||||
void sftpFileCopyFinished();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -48,6 +48,10 @@ int main(int argc, char *argv[])
|
||||
AllowSetForegroundWindow(0);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
QtAppDelegateInitialize();
|
||||
#endif
|
||||
|
||||
app.registerTypes();
|
||||
|
||||
app.setApplicationName(APPLICATION_NAME);
|
||||
@@ -61,7 +65,7 @@ int main(int argc, char *argv[])
|
||||
if (doExec) {
|
||||
app.init();
|
||||
|
||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||
qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION);
|
||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||
|
||||
return app.exec();
|
||||
|
||||
@@ -56,10 +56,26 @@ AndroidController::AndroidController() : QObject()
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
this, &AndroidController::vpnStateChanged, this,
|
||||
[this](AndroidController::ConnectionState state) {
|
||||
qDebug() << "Android event: VPN state changed:" << textConnectionState(state);
|
||||
emit connectionStateChanged(convertState(state));
|
||||
this, &AndroidController::vpnConnected, this,
|
||||
[this]() {
|
||||
qDebug() << "Android event: VPN connected";
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
this, &AndroidController::vpnDisconnected, this,
|
||||
[this]() {
|
||||
qDebug() << "Android event: VPN disconnected";
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
this, &AndroidController::vpnReconnecting, this,
|
||||
[this]() {
|
||||
qDebug() << "Android event: VPN reconnecting";
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
@@ -90,7 +106,9 @@ bool AndroidController::initialize()
|
||||
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
|
||||
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
|
||||
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
||||
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
|
||||
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
|
||||
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
|
||||
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
|
||||
{"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)},
|
||||
@@ -140,11 +158,6 @@ void AndroidController::stop()
|
||||
callActivityMethod("stop", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::resetLastServer(int serverIndex)
|
||||
{
|
||||
callActivityMethod("resetLastServer", "(I)V", serverIndex);
|
||||
}
|
||||
|
||||
void AndroidController::saveFile(const QString &fileName, const QString &data)
|
||||
{
|
||||
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
@@ -204,11 +217,6 @@ void AndroidController::clearLogs()
|
||||
callActivityMethod("clearLogs", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::setScreenshotsEnabled(bool enabled)
|
||||
{
|
||||
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
|
||||
}
|
||||
|
||||
// Moving log processing to the Android side
|
||||
jclass AndroidController::log;
|
||||
jmethodID AndroidController::logDebug;
|
||||
@@ -362,14 +370,30 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
|
||||
void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
auto state = ConnectionState(stateCode);
|
||||
emit AndroidController::instance()->vpnConnected();
|
||||
}
|
||||
|
||||
emit AndroidController::instance()->vpnStateChanged(state);
|
||||
// static
|
||||
void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
emit AndroidController::instance()->vpnDisconnected();
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
emit AndroidController::instance()->vpnReconnecting();
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -20,9 +20,9 @@ public:
|
||||
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
|
||||
enum class ConnectionState
|
||||
{
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
CONNECTING,
|
||||
DISCONNECTED,
|
||||
DISCONNECTING,
|
||||
RECONNECTING,
|
||||
UNKNOWN
|
||||
@@ -30,7 +30,6 @@ public:
|
||||
|
||||
ErrorCode start(const QJsonObject &vpnConfig);
|
||||
void stop();
|
||||
void resetLastServer(int serverIndex);
|
||||
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
||||
void saveFile(const QString &fileName, const QString &data);
|
||||
QString openFile(const QString &filter);
|
||||
@@ -39,7 +38,6 @@ public:
|
||||
void setSaveLogs(bool enabled);
|
||||
void exportLogsFile(const QString &fileName);
|
||||
void clearLogs();
|
||||
void setScreenshotsEnabled(bool enabled);
|
||||
|
||||
static bool initLogging();
|
||||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||
@@ -50,7 +48,9 @@ signals:
|
||||
void serviceDisconnected();
|
||||
void serviceError();
|
||||
void vpnPermissionRejected();
|
||||
void vpnStateChanged(ConnectionState state);
|
||||
void vpnConnected();
|
||||
void vpnDisconnected();
|
||||
void vpnReconnecting();
|
||||
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||
void fileOpened(QString uri);
|
||||
void configImported(QString config);
|
||||
@@ -77,7 +77,9 @@ private:
|
||||
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
|
||||
static void onServiceError(JNIEnv *env, jobject thiz);
|
||||
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
|
||||
static void onVpnConnected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
|
||||
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
|
||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
|
||||
let qtLog = Log(String(describing: qtString))
|
||||
@@ -25,26 +24,3 @@ public func swiftDeleteLog() {
|
||||
public func toggleLogging(_ isEnabled: Bool) {
|
||||
Log.isLoggingEnabled = isEnabled
|
||||
}
|
||||
|
||||
public func clearSettings() {
|
||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
||||
if let error {
|
||||
NSLog("clearSettings removeFromPreferences error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
managers?.forEach { manager in
|
||||
manager.removeFromPreferences { error in
|
||||
if let error {
|
||||
NSLog("NE removeFromPreferences error: \(error.localizedDescription)")
|
||||
} else {
|
||||
manager.loadFromPreferences { error in
|
||||
if let error {
|
||||
NSLog("NE loadFromPreferences after remove error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,32 +18,40 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// send empty string to NEDNSSettings.matchDomains
|
||||
networkSettings?.dnsSettings?.matchDomains = [""]
|
||||
|
||||
if splitTunnelType == 1 {
|
||||
if splitTunnelType == "1" {
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
|
||||
for allowedIPString in splitTunnelSites {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||
destinationAddress: "\(allowedIP.address)",
|
||||
subnetMask: "\(allowedIP.subnetMask())"))
|
||||
let STSdata = Data(splitTunnelSites!.utf8)
|
||||
do {
|
||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
||||
for allowedIPString in STSArray {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||
destinationAddress: "\(allowedIP.address)",
|
||||
subnetMask: "\(allowedIP.subnetMask())"))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
||||
}
|
||||
|
||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||
} else {
|
||||
if splitTunnelType == 2 {
|
||||
if splitTunnelType == "2" {
|
||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||
|
||||
for excludeIPString in splitTunnelSites {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||
destinationAddress: "\(excludeIP.address)",
|
||||
subnetMask: "\(excludeIP.subnetMask())"))
|
||||
let STSdata = Data(splitTunnelSites!.utf8)
|
||||
do {
|
||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
||||
for excludeIPString in STSArray {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||
destinationAddress: "\(excludeIP.address)",
|
||||
subnetMask: "\(excludeIP.subnetMask())"))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
||||
}
|
||||
|
||||
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||
destinationAddress: "\(allIPv4.address)",
|
||||
|
||||
@@ -50,8 +50,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||
|
||||
private var openVPNConfig: Data?
|
||||
var splitTunnelType: Int!
|
||||
var splitTunnelSites: [String]!
|
||||
var splitTunnelType: String?
|
||||
var splitTunnelSites: String?
|
||||
|
||||
let vpnReachability = OpenVPNReachability()
|
||||
|
||||
@@ -81,6 +81,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
if action == Constants.kActionStatus {
|
||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
if action == Constants.kActionStart {
|
||||
splitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
|
||||
splitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
|
||||
}
|
||||
|
||||
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
|
||||
// let tunnelId = self.tunnelConfig?.id ?? ""
|
||||
let response: [String: Any] = [
|
||||
Constants.kMessageKeyAction: action,
|
||||
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
||||
Constants.kMessageKeyTunnelId: 0
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
@@ -153,118 +169,110 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||
let wgConfigStr = wgConfig.str
|
||||
log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
|
||||
guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str,
|
||||
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
else {
|
||||
wg_log(.error, message: "Can't parse WireGuard config")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
|
||||
for allowedIPString in wgConfig.splitTunnelSites {
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if splitTunnelType == "1" {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
let STSdata = Data(splitTunnelSites!.utf8)
|
||||
do {
|
||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
||||
for allowedIPString in STSArray {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
} catch {
|
||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
||||
}
|
||||
} else if wgConfig.splitTunnelType == 2 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
|
||||
for excludeIPString in wgConfig.splitTunnelSites {
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else if splitTunnelType == "2" {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
let STSdata = Data(splitTunnelSites!.utf8)
|
||||
do {
|
||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
||||
for excludeIPString in STSArray {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
} catch {
|
||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
||||
}
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
} catch {
|
||||
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
|
||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface QIOSApplicationDelegate
|
||||
@end
|
||||
|
||||
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
@interface QtAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@end
|
||||
|
||||
@@ -3,17 +3,41 @@
|
||||
|
||||
#include <QFile>
|
||||
|
||||
@implementation QtAppDelegate {
|
||||
UIView *_screen;
|
||||
}
|
||||
|
||||
+(QtAppDelegate *)sharedQtAppDelegate {
|
||||
static dispatch_once_t pred;
|
||||
static QtAppDelegate *shared = nil;
|
||||
dispatch_once(&pred, ^{
|
||||
shared = [[super alloc] init];
|
||||
});
|
||||
return shared;
|
||||
}
|
||||
|
||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||
// Override point for customization after application launch.
|
||||
NSLog(@"Application didFinishLaunchingWithOptions");
|
||||
NSLog(@"Did this launch option happen");
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application
|
||||
{
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
|
||||
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
|
||||
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
|
||||
[_screen addSubview: blurBackground];
|
||||
blurBackground.frame = _screen.frame;
|
||||
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
|
||||
[_window addSubview: _screen];
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application
|
||||
{
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
@@ -27,6 +51,17 @@
|
||||
NSLog(@"In the foreground");
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application
|
||||
{
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
[_screen removeFromSuperview];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application
|
||||
{
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
// We will add content here soon.
|
||||
NSLog(@"In the completionHandler");
|
||||
@@ -35,27 +70,31 @@
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
|
||||
NSLog(@"Application openURL: %@", url);
|
||||
if (url.fileURL) {
|
||||
QString filePath(url.path.UTF8String);
|
||||
if (filePath.isEmpty()) return NO;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
NSLog(@"Application openURL: %@", url);
|
||||
|
||||
if (filePath.contains("backup")) {
|
||||
IosController::Instance()->importBackupFromOutside(filePath);
|
||||
} else {
|
||||
QFile file(filePath);
|
||||
bool isOpenFile = file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
IosController::Instance()->importConfigFromOutside(QString(data));
|
||||
}
|
||||
});
|
||||
|
||||
if (filePath.contains("backup")) {
|
||||
IosController::Instance()->importBackupFromOutside(filePath);
|
||||
} else {
|
||||
QFile file(filePath);
|
||||
bool isOpenFile = file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
IosController::Instance()->importConfigFromOutside(QString(data));
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
void QtAppDelegateInitialize()
|
||||
{
|
||||
[[UIApplication sharedApplication] setDelegate: [QtAppDelegate sharedQtAppDelegate]];
|
||||
NSLog(@"Created a new AppDelegate");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import UIKit
|
||||
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
let window = UIApplication.shared.keyWindows.first!
|
||||
|
||||
if isEnabled {
|
||||
ScreenProtection.shared.disable(for: window.rootViewController!.view)
|
||||
} else {
|
||||
ScreenProtection.shared.enable(for: window.rootViewController!.view)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
var keyWindows: [UIWindow] {
|
||||
connectedScenes
|
||||
.compactMap {
|
||||
if #available(iOS 15.0, *) {
|
||||
($0 as? UIWindowScene)?.keyWindow
|
||||
} else {
|
||||
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenProtection {
|
||||
public static let shared = ScreenProtection()
|
||||
|
||||
var pairs = [ProtectionPair]()
|
||||
|
||||
private var blurView: UIVisualEffectView?
|
||||
private var recordingObservation: NSKeyValueObservation?
|
||||
|
||||
public func enable(for view: UIView) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
view.subviews.forEach {
|
||||
self.pairs.append(ProtectionPair(from: $0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func disable(for view: UIView) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
self.pairs.forEach {
|
||||
$0.removeProtection()
|
||||
}
|
||||
|
||||
self.pairs.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProtectionPair {
|
||||
let textField: UITextField
|
||||
let layer: CALayer
|
||||
|
||||
init(from view: UIView) {
|
||||
let secureTextField = UITextField()
|
||||
secureTextField.backgroundColor = .clear
|
||||
secureTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
secureTextField.isSecureTextEntry = true
|
||||
|
||||
view.insertSubview(secureTextField, at: 0)
|
||||
secureTextField.isUserInteractionEnabled = false
|
||||
|
||||
view.layer.superlayer?.addSublayer(secureTextField.layer)
|
||||
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
|
||||
|
||||
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
|
||||
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
|
||||
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
|
||||
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
|
||||
|
||||
self.init(textField: secureTextField, layer: view.layer)
|
||||
}
|
||||
|
||||
init(textField: UITextField, layer: CALayer) {
|
||||
self.textField = textField
|
||||
self.layer = layer
|
||||
}
|
||||
|
||||
func removeProtection() {
|
||||
textField.superview?.superview?.layer.addSublayer(layer)
|
||||
textField.layer.removeFromSuperlayer()
|
||||
textField.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
struct WGConfig: Decodable {
|
||||
struct WGConfigData: Decodable {
|
||||
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
||||
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
||||
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
||||
let initPacketJunkSize, responsePacketJunkSize: String?
|
||||
let dns1: String
|
||||
let dns2: String
|
||||
let hostName: String
|
||||
let port: Int
|
||||
let clientIP: String
|
||||
let clientPrivateKey: String
|
||||
let serverPublicKey: String
|
||||
let presharedKey: String
|
||||
var allowedIPs: [String]
|
||||
var persistentKeepAlive: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
|
||||
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
|
||||
case dns1
|
||||
case dns2
|
||||
case hostName
|
||||
case port
|
||||
case clientIP = "client_ip"
|
||||
case clientPrivateKey = "client_priv_key"
|
||||
case serverPublicKey = "server_pub_key"
|
||||
case presharedKey = "psk_key"
|
||||
case allowedIPs = "allowed_ips"
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
case splitTunnelType
|
||||
case splitTunnelSites
|
||||
}
|
||||
|
||||
var settings: String {
|
||||
junkPacketCount == nil ? "" :
|
||||
@@ -53,45 +22,114 @@ struct WGConfig: Decodable {
|
||||
"""
|
||||
}
|
||||
|
||||
var str: String {
|
||||
"""
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
DNS = \(dns1), \(dns2)
|
||||
PrivateKey = \(clientPrivateKey)
|
||||
\(settings)
|
||||
[Peer]
|
||||
PublicKey = \(serverPublicKey)
|
||||
PresharedKey = \(presharedKey)
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
"""
|
||||
let clientIP: String
|
||||
let clientPrivateKey: String
|
||||
let clientPublicKey: String
|
||||
let serverPublicKey: String
|
||||
let presharedKey: String
|
||||
let hostName: String
|
||||
let port: Int
|
||||
|
||||
var allowedIPs: [String]
|
||||
var persistentKeepAlive: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
|
||||
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
|
||||
|
||||
case clientIP = "client_ip" // "10.8.1.16"
|
||||
case clientPrivateKey = "client_priv_key"
|
||||
case clientPublicKey = "client_pub_key"
|
||||
case serverPublicKey = "server_pub_key"
|
||||
case presharedKey = "psk_key"
|
||||
|
||||
case allowedIPs = "allowed_ips"
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
case hostName
|
||||
case port
|
||||
}
|
||||
|
||||
var redux: String {
|
||||
"""
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
DNS = \(dns1), \(dns2)
|
||||
PrivateKey = ***
|
||||
\(settings)
|
||||
[Peer]
|
||||
PublicKey = ***
|
||||
PresharedKey = ***
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
"""
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.initPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .initPacketMagicHeader)
|
||||
self.responsePacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .responsePacketMagicHeader)
|
||||
self.underloadPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .underloadPacketMagicHeader)
|
||||
self.transportPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .transportPacketMagicHeader)
|
||||
self.junkPacketCount = try container.decodeIfPresent(String.self, forKey: .junkPacketCount)
|
||||
self.junkPacketMinSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMinSize)
|
||||
self.junkPacketMaxSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMaxSize)
|
||||
self.initPacketJunkSize = try container.decodeIfPresent(String.self, forKey: .initPacketJunkSize)
|
||||
self.responsePacketJunkSize = try container.decodeIfPresent(String.self, forKey: .responsePacketJunkSize)
|
||||
self.clientIP = try container.decode(String.self, forKey: .clientIP)
|
||||
self.clientPrivateKey = try container.decode(String.self, forKey: .clientPrivateKey)
|
||||
self.clientPublicKey = try container.decode(String.self, forKey: .clientPublicKey)
|
||||
self.serverPublicKey = try container.decode(String.self, forKey: .serverPublicKey)
|
||||
self.presharedKey = try container.decode(String.self, forKey: .presharedKey)
|
||||
self.allowedIPs = try container.decodeIfPresent([String].self, forKey: .allowedIPs) ?? ["0.0.0.0/0", "::/0"]
|
||||
self.persistentKeepAlive = try container.decodeIfPresent(String.self, forKey: .persistentKeepAlive) ?? "25"
|
||||
self.hostName = try container.decode(String.self, forKey: .hostName)
|
||||
self.port = try container.decode(Int.self, forKey: .port)
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
let splitTunnelType: Int
|
||||
struct WGConfig: Decodable {
|
||||
let data: WGConfigData
|
||||
let configVersion: Int
|
||||
let description: String
|
||||
let dns1: String
|
||||
let dns2: String
|
||||
let hostName: String
|
||||
let `protocol`: String
|
||||
let splitTunnelSites: [String]
|
||||
let splitTunnelType: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case awgConfigData = "awg_config_data", wgConfigData = "wireguard_config_data"
|
||||
case configData
|
||||
case configVersion = "config_version"
|
||||
case description
|
||||
case dns1
|
||||
case dns2
|
||||
case hostName
|
||||
case `protocol`
|
||||
case splitTunnelSites
|
||||
case splitTunnelType
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if container.contains(.awgConfigData) {
|
||||
self.data = try container.decode(WGConfigData.self, forKey: .awgConfigData)
|
||||
} else {
|
||||
self.data = try container.decode(WGConfigData.self, forKey: .wgConfigData)
|
||||
}
|
||||
|
||||
self.configVersion = try container.decode(Int.self, forKey: .configVersion)
|
||||
self.description = try container.decode(String.self, forKey: .description)
|
||||
self.dns1 = try container.decode(String.self, forKey: .dns1)
|
||||
self.dns2 = try container.decode(String.self, forKey: .dns2)
|
||||
self.hostName = try container.decode(String.self, forKey: .hostName)
|
||||
self.protocol = try container.decode(String.self, forKey: .protocol)
|
||||
self.splitTunnelSites = try container.decode([String].self, forKey: .splitTunnelSites)
|
||||
self.splitTunnelType = try container.decode(Int.self, forKey: .splitTunnelType)
|
||||
}
|
||||
|
||||
var str: String {
|
||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||
"""
|
||||
[Interface]
|
||||
Address = \(data.clientIP)/32
|
||||
DNS = \(dns1), \(dns2)
|
||||
PrivateKey = \(data.clientPrivateKey)
|
||||
\(data.settings)
|
||||
[Peer]
|
||||
PublicKey = \(data.serverPublicKey)
|
||||
PresharedKey = \(data.presharedKey)
|
||||
AllowedIPs = \(data.allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(data.hostName):\(data.port)
|
||||
PersistentKeepalive = \(data.persistentKeepAlive)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +235,7 @@ void IosController::checkStatus()
|
||||
m_rxBytes = rxBytes;
|
||||
m_txBytes = txBytes;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void IosController::vpnStatusDidChange(void *pNotification)
|
||||
@@ -243,13 +244,13 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||
|
||||
if (session /* && session == TunnelManager.session */ ) {
|
||||
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
|
||||
|
||||
|
||||
if (session.status == NEVPNStatusDisconnected) {
|
||||
if (@available(iOS 16.0, *)) {
|
||||
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
|
||||
|
||||
|
||||
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
|
||||
switch (error.code) {
|
||||
case NEVPNConnectionErrorOverslept:
|
||||
@@ -314,11 +315,11 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
|
||||
if (underlyingError != nil) {
|
||||
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
|
||||
|
||||
|
||||
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
|
||||
switch (underlyingError.code) {
|
||||
case 1:
|
||||
@@ -341,7 +342,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
emit connectionStateChanged(iosStatusToState(session.status));
|
||||
}
|
||||
}
|
||||
@@ -356,22 +357,7 @@ bool IosController::setupOpenVPN()
|
||||
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
|
||||
QString ovpnConfig = ovpn[config_key::config].toString();
|
||||
|
||||
QJsonObject openVPNConfig {};
|
||||
openVPNConfig.insert(config_key::config, ovpnConfig);
|
||||
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||
|
||||
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||
|
||||
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||
}
|
||||
|
||||
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
QJsonDocument openVPNConfigDoc(openVPNConfig);
|
||||
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startOpenVPN(openVPNConfigStr);
|
||||
return startOpenVPN(ovpnConfig);
|
||||
}
|
||||
|
||||
bool IosController::setupCloak()
|
||||
@@ -408,123 +394,27 @@ bool IosController::setupCloak()
|
||||
ovpnConfig.append(cloakBase64);
|
||||
ovpnConfig.append("\n</cloak>\n");
|
||||
|
||||
QJsonObject openVPNConfig {};
|
||||
openVPNConfig.insert(config_key::config, ovpnConfig);
|
||||
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||
|
||||
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||
|
||||
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||
}
|
||||
|
||||
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
QJsonDocument openVPNConfigDoc(openVPNConfig);
|
||||
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startOpenVPN(openVPNConfigStr);
|
||||
return startOpenVPN(ovpnConfig);
|
||||
}
|
||||
|
||||
bool IosController::setupWireGuard()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
|
||||
|
||||
QJsonDocument doc(m_rawConfig);
|
||||
QString wgConfig(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QJsonObject wgConfig {};
|
||||
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
|
||||
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
|
||||
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
|
||||
wgConfig.insert(config_key::port, config[config_key::port]);
|
||||
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
|
||||
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
|
||||
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
|
||||
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
|
||||
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||
|
||||
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||
|
||||
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||
}
|
||||
|
||||
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
if (config.contains(config_key::allowed_ips)) {
|
||||
QJsonArray allowed_ips;
|
||||
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
|
||||
|
||||
for(int index = 0; index < allowed_ips_list.length(); index++) {
|
||||
allowed_ips.append(allowed_ips_list[index]);
|
||||
}
|
||||
|
||||
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||
}
|
||||
|
||||
wgConfig.insert("persistent_keep_alive", "25");
|
||||
|
||||
QJsonDocument wgConfigDoc(wgConfig);
|
||||
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startWireGuard(wgConfigDocStr);
|
||||
return startWireGuard(wgConfig);
|
||||
}
|
||||
|
||||
bool IosController::setupAwg()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
|
||||
|
||||
QJsonObject wgConfig {};
|
||||
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
|
||||
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
|
||||
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
|
||||
wgConfig.insert(config_key::port, config[config_key::port]);
|
||||
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
|
||||
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
|
||||
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
|
||||
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
|
||||
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||
QJsonDocument doc(m_rawConfig);
|
||||
QString wgConfig(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||
|
||||
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||
}
|
||||
|
||||
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
if (config.contains(config_key::allowed_ips)) {
|
||||
QJsonArray allowed_ips;
|
||||
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
|
||||
|
||||
for(int index = 0; index < allowed_ips_list.length(); index++) {
|
||||
allowed_ips.append(allowed_ips_list[index]);
|
||||
}
|
||||
|
||||
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||
}
|
||||
|
||||
wgConfig.insert("persistent_keep_alive", "25");
|
||||
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
|
||||
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
|
||||
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);
|
||||
wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]);
|
||||
|
||||
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
|
||||
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
|
||||
|
||||
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
|
||||
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
|
||||
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
|
||||
|
||||
QJsonDocument wgConfigDoc(wgConfig);
|
||||
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startWireGuard(wgConfigDocStr);
|
||||
return startWireGuard(wgConfig);
|
||||
}
|
||||
|
||||
bool IosController::startOpenVPN(const QString &config)
|
||||
@@ -558,17 +448,23 @@ bool IosController::startWireGuard(const QString &config)
|
||||
void IosController::startTunnel()
|
||||
{
|
||||
NSString *protocolName = @"Unknown";
|
||||
|
||||
|
||||
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
|
||||
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
|
||||
protocolName = @"WireGuard";
|
||||
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
|
||||
protocolName = @"OpenVPN";
|
||||
}
|
||||
|
||||
|
||||
m_rxBytes = 0;
|
||||
m_txBytes = 0;
|
||||
|
||||
|
||||
int STT = m_rawConfig["splitTunnelType"].toInt();
|
||||
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
|
||||
QJsonDocument doc;
|
||||
doc.setArray(splitTunnelSites);
|
||||
QString STS(doc.toJson());
|
||||
|
||||
[m_currentTunnel setEnabled:YES];
|
||||
|
||||
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
||||
@@ -589,6 +485,23 @@ void IosController::startTunnel()
|
||||
NSError *startError = nil;
|
||||
qDebug() << iosStatusToState(m_currentTunnel.connection.status);
|
||||
|
||||
|
||||
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
|
||||
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
|
||||
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
||||
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
||||
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
|
||||
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
|
||||
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
|
||||
NSString *SplitTunnelSitesValue = STS.toNSString();
|
||||
|
||||
|
||||
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
|
||||
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
|
||||
|
||||
sendVpnExtensionMessage(message);
|
||||
|
||||
|
||||
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
|
||||
|
||||
if (!started || startError) {
|
||||
@@ -603,6 +516,7 @@ void IosController::startTunnel()
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
bool IosController::isOurManager(NETunnelProviderManager* manager) {
|
||||
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
|
||||
|
||||
@@ -664,7 +578,7 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
||||
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
|
||||
|
||||
NSError *sendError = nil;
|
||||
|
||||
|
||||
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
|
||||
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
|
||||
- (void) vpnConfigurationDidChange:(NSNotification *)notification {
|
||||
// cppController->vpnStatusDidChange(notification);
|
||||
cppController->vpnStatusDidChange(notification);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace amnezia
|
||||
constexpr char dns1[] = "dns1";
|
||||
constexpr char dns2[] = "dns2";
|
||||
|
||||
constexpr char serverIndex[] = "serverIndex";
|
||||
constexpr char description[] = "description";
|
||||
constexpr char name[] = "name";
|
||||
constexpr char cert[] = "cert";
|
||||
|
||||
@@ -1 +1 @@
|
||||
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER
|
||||
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER --build-arg SERVER_ARCH=$(uname -m)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
FROM alpine:3.15
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG SS_RELEASE="v1.18.1"
|
||||
ARG CLOAK_RELEASE="v2.8.0"
|
||||
ARG SS_RELEASE="v1.13.1"
|
||||
ARG CLOAK_RELEASE="v2.5.5"
|
||||
ARG SERVER_ARCH
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
||||
@@ -15,19 +16,20 @@ RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN SERVER_ARCH=$(uname -m) && \
|
||||
if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
|
||||
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
|
||||
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
|
||||
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
|
||||
else exit -1; fi && \
|
||||
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server && \
|
||||
chmod a+x /usr/bin/ck-server && \
|
||||
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz && \
|
||||
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/ && \
|
||||
chmod a+x /usr/bin/ssserver
|
||||
RUN if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
|
||||
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
|
||||
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
|
||||
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
|
||||
else exit -1; fi && \
|
||||
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server
|
||||
RUN chmod a+x /usr/bin/ck-server
|
||||
|
||||
# Tune network
|
||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
|
||||
|
||||
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
|
||||
RUN chmod a+x /usr/bin/ssserver
|
||||
|
||||
# Tune network
|
||||
RUN echo -e " \n\
|
||||
fs.file-max = 51200 \n\
|
||||
\n\
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
FROM alpine:3.15
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG SS_RELEASE="v1.18.1"
|
||||
ARG SS_RELEASE="v1.13.1"
|
||||
ARG SERVER_ARCH
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
|
||||
@@ -14,16 +15,7 @@ RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN SERVER_ARCH=$(uname -m); \
|
||||
SUFFIX=""; \
|
||||
if [ ! -z "$(echo ${SERVER_ARCH} | grep -i arm)" ]; then \
|
||||
if [ ! -z "$(cat /proc/cpuinfo | grep -i vfp)" ]; then \
|
||||
SUFFIX="eabihf"; \
|
||||
else \
|
||||
SUFFIX="eabi"; \
|
||||
fi; \
|
||||
fi; \
|
||||
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl${SUFFIX}.tar.xz > /usr/bin/ss.tar.xz;\
|
||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz;\
|
||||
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\
|
||||
chmod a+x /usr/bin/ssserver;
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ void Settings::removeServer(int index)
|
||||
|
||||
servers.removeAt(index);
|
||||
setServersArray(servers);
|
||||
emit serverRemoved(index);
|
||||
}
|
||||
|
||||
bool Settings::editServer(int index, const QJsonObject &server)
|
||||
@@ -339,7 +338,6 @@ QString Settings::secondaryDns() const
|
||||
void Settings::clearSettings()
|
||||
{
|
||||
m_settings.clearSettings();
|
||||
emit settingsCleared();
|
||||
}
|
||||
|
||||
ServerCredentials Settings::defaultServerCredentials() const
|
||||
|
||||
@@ -185,16 +185,12 @@ public:
|
||||
void setScreenshotsEnabled(bool enabled)
|
||||
{
|
||||
setValue("Conf/screenshotsEnabled", enabled);
|
||||
emit screenshotsEnabledChanged(enabled);
|
||||
}
|
||||
|
||||
void clearSettings();
|
||||
|
||||
signals:
|
||||
void saveLogsChanged(bool enabled);
|
||||
void screenshotsEnabledChanged(bool enabled);
|
||||
void serverRemoved(int serverIndex);
|
||||
void settingsCleared();
|
||||
|
||||
private:
|
||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
|
||||
@@ -5,45 +5,45 @@
|
||||
<name>ConnectionController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.h" line="62"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
|
||||
<source>Connect</source>
|
||||
<translation>اتصل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
|
||||
<source>VPN Protocols is not installed.
|
||||
Please install VPN container at first</source>
|
||||
<translation>لم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
|
||||
<source>Connection...</source>
|
||||
<translation>اتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
|
||||
<source>Connected</source>
|
||||
<translation>تم الاتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
|
||||
<source>Reconnection...</source>
|
||||
<translation>إعادة الاتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
|
||||
<source>Disconnection...</source>
|
||||
<translation>إنهاء الاتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
|
||||
<source>Settings updated successfully, Reconnnection...</source>
|
||||
<translation>تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
|
||||
<source>Settings updated successfully</source>
|
||||
<translation>تم تحديث الاعدادات بنجاح</translation>
|
||||
</message>
|
||||
@@ -324,17 +324,17 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>بروتوكول VPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
|
||||
<source>Servers</source>
|
||||
<translation>الخوادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
|
||||
<source>Unable change server while there is an active connection</source>
|
||||
<translation>لا يمكن تغير الخادم بينما هناك اتصال مفعل</translation>
|
||||
</message>
|
||||
@@ -1423,7 +1423,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
|
||||
<source>Do you want to clear server Amnezia-installed services?</source>
|
||||
<source>Do you want to clear server from Amnezia software?</source>
|
||||
<translation>هل تريد حذف الخادم من Amnezia?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1471,7 +1471,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
|
||||
<source>Remove this server from the app</source>
|
||||
<source>Remove server from application</source>
|
||||
<translation>احذف خادم من التطبيق</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1496,7 +1496,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
|
||||
<source>Clear server Amnezia-installed services</source>
|
||||
<source>Clear server from Amnezia software</source>
|
||||
<translation>احذف خادم من Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1703,45 +1703,41 @@ And if you don't like the app, all the more support it - the donation will
|
||||
<translation>اتصال الخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection code from public sources. It may have been created to intercept your data.
|
||||
|
||||
It's okay as long as it's from someone you trust.</source>
|
||||
<translation type="vanished">لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك
|
||||
<translation>لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك
|
||||
|
||||
لا بأس طالما انه من شخص تثق به.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
|
||||
<source>What do you have?</source>
|
||||
<translation>ماذا لديك؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings or backup</source>
|
||||
<translation>ملف إعدادات اتصال او نسخ احتياطي</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings</source>
|
||||
<translation>ملف إعدادات اتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
|
||||
<source>Open config file</source>
|
||||
<translation>افتح ملف تكوين</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
|
||||
<source>QR-code</source>
|
||||
<translation>رمز QR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
|
||||
<source>Key as text</source>
|
||||
<translation>مفتاح كنص</translation>
|
||||
</message>
|
||||
@@ -1827,7 +1823,7 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<translation>واصل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
|
||||
<source>Set up later</source>
|
||||
<translation>إعداد في وقت لاحق</translation>
|
||||
</message>
|
||||
@@ -1928,32 +1924,32 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<context>
|
||||
<name>PageSetupWizardStart</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
|
||||
<source>Settings restored from backup file</source>
|
||||
<translation>تم استرداد الإعدادات من ملف نسخة احتياطية</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
|
||||
<source>Free service for creating a personal VPN on your server.</source>
|
||||
<translation>خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
|
||||
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
|
||||
<translation> يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<source>I have the data to connect</source>
|
||||
<translation>لدي البيانات المطلوبة للأتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
|
||||
<source>I have nothing</source>
|
||||
<translation>ليس لدي اي شئ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
|
||||
<source>https://amnezia.org/instructions/0_starter-guide</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
@@ -1994,14 +1990,10 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<source>New connection</source>
|
||||
<translation>اتصال جديد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation type="vanished">لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation>لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
@@ -2093,7 +2085,7 @@ It's okay as long as it's from someone you trust.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
|
||||
<source>Users</source>
|
||||
<translation>المستخدمين</translation>
|
||||
</message>
|
||||
@@ -2103,52 +2095,52 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<translation>شارك اتصال VPN بدون القدرة علي إدارة الخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
|
||||
<source>Search</source>
|
||||
<translation>ابحث</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
|
||||
<source>Creation date: </source>
|
||||
<translation>تاريخ الإنشاء: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
|
||||
<source>Rename</source>
|
||||
<translation>إعادة التسمية</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
|
||||
<source>Client name</source>
|
||||
<translation>اسم العميل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
|
||||
<source>Save</source>
|
||||
<translation>احفظ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
|
||||
<source>Revoke</source>
|
||||
<translation>سحب وإبطال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
|
||||
<source>Revoke the config for a user - %1?</source>
|
||||
<translation>سحب وإبطال للمستخدم - %1?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
|
||||
<source>The user will no longer be able to connect to your server.</source>
|
||||
<translation>المستخدم لن يكون قادر علي الاتصال بعد الان.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
|
||||
<source>Continue</source>
|
||||
<translation>واصل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
|
||||
<source>Cancel</source>
|
||||
<translation>إلغاء</translation>
|
||||
</message>
|
||||
@@ -2181,8 +2173,8 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<translation type="obsolete">البروتوكولات</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
|
||||
<source>Protocol</source>
|
||||
<translation>بروتوكول</translation>
|
||||
</message>
|
||||
@@ -2202,14 +2194,14 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<translation>اسم المستخدم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
|
||||
<source>Connection format</source>
|
||||
<translation>تنسيق الاتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
|
||||
<source>Share</source>
|
||||
<translation>شارك</translation>
|
||||
</message>
|
||||
@@ -2591,161 +2583,147 @@ It's okay as long as it's from someone you trust.</source>
|
||||
<translation>انتهت مدة الاتصال بالخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="32"/>
|
||||
<source>Sftp error: End-of-file encountered</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="33"/>
|
||||
<source>Sftp error: File does not exist</source>
|
||||
<translation type="vanished">خطأ Sftp: الملف غير موجود</translation>
|
||||
<translation>خطأ Sftp: الملف غير موجود</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
<source>Sftp error: Permission denied</source>
|
||||
<translation type="vanished">خطأ Sftp: تم حظر الصلحيات</translation>
|
||||
<translation>خطأ Sftp: تم حظر الصلحيات</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<source>Sftp error: Generic failure</source>
|
||||
<translation type="vanished">خطأ Sftp: فشل عام</translation>
|
||||
<translation>خطأ Sftp: فشل عام</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<source>Sftp error: Garbage received from server</source>
|
||||
<translation type="vanished">خطأ Sftp: تم استلام نفايات من الخادم</translation>
|
||||
<translation>خطأ Sftp: تم استلام نفايات من الخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="37"/>
|
||||
<source>Sftp error: No connection has been set up</source>
|
||||
<translation type="vanished">خطأ Sftp: لم يتم إعداد اتصال</translation>
|
||||
<translation>خطأ Sftp: لم يتم إعداد اتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="38"/>
|
||||
<source>Sftp error: There was a connection, but we lost it</source>
|
||||
<translation type="vanished">خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation>
|
||||
<translation>خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<source>Sftp error: Operation not supported by libssh yet</source>
|
||||
<translation type="vanished">خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation>
|
||||
<translation>خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<source>Sftp error: Invalid file handle</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<source>Sftp error: No such file or directory path exists</source>
|
||||
<translation type="vanished">خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation>
|
||||
<translation>خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
|
||||
<translation type="vanished">خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation>
|
||||
<translation>خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<source>Sftp error: Write-protected filesystem</source>
|
||||
<translation type="vanished">خطأ Sftp: نظام كتابة الملفات محمي</translation>
|
||||
<translation>خطأ Sftp: نظام كتابة الملفات محمي</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="44"/>
|
||||
<source>Sftp error: No media was in remote drive</source>
|
||||
<translation type="vanished">خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation>
|
||||
<translation>خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>VPN connection error</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="56"/>
|
||||
<location filename="../core/errorstrings.cpp" line="68"/>
|
||||
<source>Error when retrieving configuration from API</source>
|
||||
<translation>خطأ عند استرداد التكوين من API</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="57"/>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<source>This config has already been added to the application</source>
|
||||
<translation>هذا التكوين بالفعل تمت إضافتة للبرنامج</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="72"/>
|
||||
<location filename="../core/errorstrings.cpp" line="76"/>
|
||||
<source>ErrorCode: %1. </source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<source>OpenVPN config missing</source>
|
||||
<translation>OpenVpn تكوين مفقود</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="32"/>
|
||||
<source>Scp error: Generic failure</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<source>OpenVPN management server error</source>
|
||||
<translation>OpenVpn خطأ في إدارة الخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<location filename="../core/errorstrings.cpp" line="51"/>
|
||||
<source>OpenVPN executable missing</source>
|
||||
<translation>OpenVpn executeable مفقود</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<location filename="../core/errorstrings.cpp" line="52"/>
|
||||
<source>ShadowSocks (ss-local) executable missing</source>
|
||||
<translation>ShadowSocks (ss-local) executable مفقود</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<source>Cloak (ck-client) executable missing</source>
|
||||
<translation>Cloak (ck-client) executable مفقود</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<location filename="../core/errorstrings.cpp" line="54"/>
|
||||
<source>Amnezia helper service error</source>
|
||||
<translation>خطأ في خدمة مٌساعد Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<location filename="../core/errorstrings.cpp" line="55"/>
|
||||
<source>OpenSSL failed</source>
|
||||
<translation>فشل OpenSSL</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="46"/>
|
||||
<location filename="../core/errorstrings.cpp" line="58"/>
|
||||
<source>Can't connect: another VPN connection is active</source>
|
||||
<translation>لا يمكن الاتصال: هناك اتصال VPN اخر بالفعل يعمل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<location filename="../core/errorstrings.cpp" line="59"/>
|
||||
<source>Can't setup OpenVPN TAP network adapter</source>
|
||||
<translation>لا يمك نتثبيت محول شبكة OpenVPN TAP</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>VPN pool error: no available addresses</source>
|
||||
<translation>VPN pool error: لا يوجد عنواين مٌتاحة</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="50"/>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>The config does not contain any containers and credentials for connecting to the server</source>
|
||||
<translation>التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>QFile error: The file could not be opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="61"/>
|
||||
<source>QFile error: An error occurred when reading from the file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>QFile error: The file could not be accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="63"/>
|
||||
<source>QFile error: An unspecified error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="64"/>
|
||||
<source>QFile error: A fatal error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>QFile error: The operation was aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<location filename="../core/errorstrings.cpp" line="73"/>
|
||||
<source>Internal error</source>
|
||||
<translation>خطأ داخلي</translation>
|
||||
</message>
|
||||
@@ -3045,8 +3023,8 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation>خادم #1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.cpp" line="207"/>
|
||||
<location filename="../settings.cpp" line="214"/>
|
||||
<location filename="../settings.cpp" line="206"/>
|
||||
<location filename="../settings.cpp" line="213"/>
|
||||
<source>Server</source>
|
||||
<translation>خادم</translation>
|
||||
</message>
|
||||
@@ -3054,17 +3032,17 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>SettingsController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
|
||||
<source>Backup file is corrupted</source>
|
||||
<translation>ملف النسخه الاحتياطيه تالف</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
|
||||
<source>All settings have been reset to default values</source>
|
||||
<translation>تم استرجاع جميع الإعدادات للإعدادات الافتراضية</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
|
||||
<source>Cached profiles cleared</source>
|
||||
<translation>تم حذف الملفات الشخصية المٌخزنة مؤقتاُ</translation>
|
||||
</message>
|
||||
@@ -3078,28 +3056,28 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation>احفظ تكوين AmneziaVPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
|
||||
<source>Share</source>
|
||||
<translation>شارك</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
|
||||
<source>Copy</source>
|
||||
<translation>انسخ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
|
||||
<source>Copied</source>
|
||||
<translation>تم النسخ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
|
||||
<source>Copy config string</source>
|
||||
<translation>انسخ نص التكوين</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<source>Show connection settings</source>
|
||||
<translation>اظهر إعدادات الاتصال</translation>
|
||||
</message>
|
||||
@@ -3108,7 +3086,7 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation type="obsolete">展示内容</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
|
||||
<source>To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"</source>
|
||||
<translation>حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار "إضافة خادم" - "لدي بيانات الاتصال" - "رمز Qr, او مفتاح تعريف او ملف إعدادات"</translation>
|
||||
</message>
|
||||
@@ -3200,7 +3178,7 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>VpnConnection</name>
|
||||
<message>
|
||||
<location filename="../vpnconnection.cpp" line="458"/>
|
||||
<location filename="../vpnconnection.cpp" line="457"/>
|
||||
<source>Mbps</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
@@ -3284,12 +3262,12 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>main2</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="179"/>
|
||||
<location filename="../ui/qml/main2.qml" line="174"/>
|
||||
<source>Private key passphrase</source>
|
||||
<translation>عبارة المرور الخاصة بالمفتاح</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="202"/>
|
||||
<location filename="../ui/qml/main2.qml" line="197"/>
|
||||
<source>Save</source>
|
||||
<translation>احفظ</translation>
|
||||
</message>
|
||||
|
||||
@@ -34,48 +34,48 @@
|
||||
<context>
|
||||
<name>ConnectionController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
|
||||
<source>VPN Protocols is not installed.
|
||||
Please install VPN container at first</source>
|
||||
<translation>پروتکل ویپیان نصب نشده است
|
||||
لطفا کانتینر ویپیان را نصب کنید</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
|
||||
<source>Connection...</source>
|
||||
<translation>در حال ارتباط...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
|
||||
<source>Connected</source>
|
||||
<translation>متصل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
|
||||
<source>Settings updated successfully, Reconnnection...</source>
|
||||
<translation>تنظیمات به روز رسانی شد
|
||||
در حال اتصال دوباره...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
|
||||
<source>Settings updated successfully</source>
|
||||
<translation>تنظیمات با موفقیت بهروزرسانی شدند</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
|
||||
<source>Reconnection...</source>
|
||||
<translation>اتصال دوباره...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.h" line="62"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
|
||||
<source>Connect</source>
|
||||
<translation>اتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
|
||||
<source>Disconnection...</source>
|
||||
<translation>قطع ارتباط...</translation>
|
||||
</message>
|
||||
@@ -335,17 +335,17 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation>تونل تقسیمشده غیرفعال شده</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>پروتکل ویپیان</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
|
||||
<source>Servers</source>
|
||||
<translation>سرورها</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
|
||||
<source>Unable change server while there is an active connection</source>
|
||||
<translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation>
|
||||
</message>
|
||||
@@ -1479,7 +1479,7 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
|
||||
<source>Do you want to clear server Amnezia-installed services?</source>
|
||||
<source>Do you want to clear server from Amnezia software?</source>
|
||||
<translation>آیا میخواهید سرور را از نرمافزار Amnezia پاک کنید؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1494,11 +1494,11 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
|
||||
<source>Remove this server from the app</source>
|
||||
<source>Remove server from application</source>
|
||||
<translation>حذف کردن سرور از نرمافزار</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove server from application?</source>
|
||||
<source>Remove server?</source>
|
||||
<translation type="vanished">حذف سرور؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1508,11 +1508,11 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
|
||||
<source>Clear server Amnezia-installed services</source>
|
||||
<source>Clear server from Amnezia software</source>
|
||||
<translation>پاک کردن سرور از نرمافزار Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear server Amnezia-installed services?</source>
|
||||
<source>Clear server from Amnezia software?</source>
|
||||
<translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1715,45 +1715,41 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation>ارتباط سرور</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection code from public sources. It may have been created to intercept your data.
|
||||
|
||||
It's okay as long as it's from someone you trust.</source>
|
||||
<translation type="vanished">از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند.
|
||||
<translation>از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند.
|
||||
|
||||
ایرادی ندارد که از طرف کسی باشد که به او اعتماد دارید.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
|
||||
<source>What do you have?</source>
|
||||
<translation>چی داری؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings</source>
|
||||
<translation>فایل شامل تنظیمات اتصال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings or backup</source>
|
||||
<translation>فایل شامل تنظیمات اتصال یا بکآپ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
|
||||
<source>Open config file</source>
|
||||
<translation>باز کردن فایل تنظیمات</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
|
||||
<source>QR-code</source>
|
||||
<translation>QR-Code</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
|
||||
<source>Key as text</source>
|
||||
<translation>متن شامل کلید</translation>
|
||||
</message>
|
||||
@@ -1856,7 +1852,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>ادامه</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
|
||||
<source>Set up later</source>
|
||||
<translation>بعدا تنظیم شود</translation>
|
||||
</message>
|
||||
@@ -1957,32 +1953,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<context>
|
||||
<name>PageSetupWizardStart</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
|
||||
<source>Settings restored from backup file</source>
|
||||
<translation>تنظیمات از فایل بکآپ بازیابی شدند</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
|
||||
<source>Free service for creating a personal VPN on your server.</source>
|
||||
<translation>سرویس رایگان برای ایجاد ویپیان شخصی بر روی سرور خودتان.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
|
||||
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
|
||||
<translation>به شما کمک میکند که بدون فاش کردن حریم شخصی خودتان حتی برای ارائه دهنده ویپیان به محتوای مسدود شده دسترسی پیدا کنید.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<source>I have the data to connect</source>
|
||||
<translation>من داده برای اتصال دارم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
|
||||
<source>I have nothing</source>
|
||||
<translation>من هیچی ندارم</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
|
||||
<source>https://amnezia.org/instructions/0_starter-guide</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2022,14 +2018,10 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<source>New connection</source>
|
||||
<translation>ارتباط جدید</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation type="vanished">از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
@@ -2167,7 +2159,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
|
||||
<source>Users</source>
|
||||
<translation>کاربران</translation>
|
||||
</message>
|
||||
@@ -2177,37 +2169,37 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>نام کاربری</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
|
||||
<source>Search</source>
|
||||
<translation>جستجو</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
|
||||
<source>Creation date: </source>
|
||||
<translation>تاریخ ایجاد:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
|
||||
<source>Rename</source>
|
||||
<translation>تغییر نام</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
|
||||
<source>Client name</source>
|
||||
<translation>نام کلاینت</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
|
||||
<source>Save</source>
|
||||
<translation>ذخیره</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
|
||||
<source>Revoke</source>
|
||||
<translation>ابطال</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
|
||||
<source>Revoke the config for a user - %1?</source>
|
||||
<translation> لغو پیکربندی برای یک کاربر %1؟</translation>
|
||||
</message>
|
||||
@@ -2216,17 +2208,17 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">ابطال تنظیمات برای کاربر </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
|
||||
<source>The user will no longer be able to connect to your server.</source>
|
||||
<translation>کاربر دیگر نمیتواند به سرور وصل شود.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
|
||||
<source>Continue</source>
|
||||
<translation>ادامه</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
|
||||
<source>Cancel</source>
|
||||
<translation>کنسل</translation>
|
||||
</message>
|
||||
@@ -2244,20 +2236,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">به اشتراک گذاری دسترسی به مدیریت سرور. کاربری که دسترسی کامل سرور با او به اشتراک گذاشته میشود میتواند پروتکلها و سرویسها را در سرور حذف یا اضافه کند و یا تنظیمات سرور را تغییر دهد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
|
||||
<source>Protocol</source>
|
||||
<translation>پروتکل</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
|
||||
<source>Connection format</source>
|
||||
<translation>فرمت ارتباط</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
|
||||
<source>Share</source>
|
||||
<translation>اشتراکگذاری</translation>
|
||||
</message>
|
||||
@@ -2635,113 +2627,91 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="32"/>
|
||||
<source>Scp error: Generic failure</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sftp error: End-of-file encountered</source>
|
||||
<translation type="vanished">Sftp error: End-of-file encountered</translation>
|
||||
<translation>Sftp error: End-of-file encountered</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="33"/>
|
||||
<source>Sftp error: File does not exist</source>
|
||||
<translation type="vanished">Sftp error: File does not exist</translation>
|
||||
<translation>Sftp error: File does not exist</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
<source>Sftp error: Permission denied</source>
|
||||
<translation type="vanished">Sftp error: Permission denied</translation>
|
||||
<translation>Sftp error: Permission denied</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<source>Sftp error: Generic failure</source>
|
||||
<translation type="vanished">Sftp error: Generic failure</translation>
|
||||
<translation>Sftp error: Generic failure</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<source>Sftp error: Garbage received from server</source>
|
||||
<translation type="vanished">Sftp error: Garbage received from server</translation>
|
||||
<translation>Sftp error: Garbage received from server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="37"/>
|
||||
<source>Sftp error: No connection has been set up</source>
|
||||
<translation type="vanished">Sftp error: No connection has been set up</translation>
|
||||
<translation>Sftp error: No connection has been set up</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="38"/>
|
||||
<source>Sftp error: There was a connection, but we lost it</source>
|
||||
<translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
|
||||
<translation>Sftp error: There was a connection, but we lost it</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<source>Sftp error: Operation not supported by libssh yet</source>
|
||||
<translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
|
||||
<translation>Sftp error: Operation not supported by libssh yet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<source>Sftp error: Invalid file handle</source>
|
||||
<translation type="vanished">Sftp error: Invalid file handle</translation>
|
||||
<translation>Sftp error: Invalid file handle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<source>Sftp error: No such file or directory path exists</source>
|
||||
<translation type="vanished">Sftp error: No such file or directory path exists</translation>
|
||||
<translation>Sftp error: No such file or directory path exists</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
|
||||
<translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
|
||||
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<source>Sftp error: Write-protected filesystem</source>
|
||||
<translation type="vanished">Sftp error: Write-protected filesystem</translation>
|
||||
<translation>Sftp error: Write-protected filesystem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="44"/>
|
||||
<source>Sftp error: No media was in remote drive</source>
|
||||
<translation type="vanished">Sftp error: No media was in remote drive</translation>
|
||||
<translation>Sftp error: No media was in remote drive</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="50"/>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>The config does not contain any containers and credentials for connecting to the server</source>
|
||||
<translation>تنظیمات شامل هیچ کانتینر یا اعتبارنامهای برای اتصال به سرور نیست</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>VPN connection error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="56"/>
|
||||
<location filename="../core/errorstrings.cpp" line="68"/>
|
||||
<source>Error when retrieving configuration from API</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="57"/>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<source>This config has already been added to the application</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>QFile error: The file could not be opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="61"/>
|
||||
<source>QFile error: An error occurred when reading from the file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>QFile error: The file could not be accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="63"/>
|
||||
<source>QFile error: An unspecified error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="64"/>
|
||||
<source>QFile error: A fatal error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>QFile error: The operation was aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="72"/>
|
||||
<location filename="../core/errorstrings.cpp" line="76"/>
|
||||
<source>ErrorCode: %1. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2750,52 +2720,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">Failed to save config to disk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<source>OpenVPN config missing</source>
|
||||
<translation>OpenVPN config missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<source>OpenVPN management server error</source>
|
||||
<translation>OpenVPN management server error</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<location filename="../core/errorstrings.cpp" line="51"/>
|
||||
<source>OpenVPN executable missing</source>
|
||||
<translation>OpenVPN executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<location filename="../core/errorstrings.cpp" line="52"/>
|
||||
<source>ShadowSocks (ss-local) executable missing</source>
|
||||
<translation>ShadowSocks (ss-local) executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<source>Cloak (ck-client) executable missing</source>
|
||||
<translation>Cloak (ck-client) executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<location filename="../core/errorstrings.cpp" line="54"/>
|
||||
<source>Amnezia helper service error</source>
|
||||
<translation>Amnezia helper service error</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<location filename="../core/errorstrings.cpp" line="55"/>
|
||||
<source>OpenSSL failed</source>
|
||||
<translation>OpenSSL failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="46"/>
|
||||
<location filename="../core/errorstrings.cpp" line="58"/>
|
||||
<source>Can't connect: another VPN connection is active</source>
|
||||
<translation>Can't connect: another VPN connection is active</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<location filename="../core/errorstrings.cpp" line="59"/>
|
||||
<source>Can't setup OpenVPN TAP network adapter</source>
|
||||
<translation>Can't setup OpenVPN TAP network adapter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>VPN pool error: no available addresses</source>
|
||||
<translation>VPN pool error: no available addresses</translation>
|
||||
</message>
|
||||
@@ -2804,7 +2774,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">The config does not contain any containers and credentiaks for connecting to the server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<location filename="../core/errorstrings.cpp" line="73"/>
|
||||
<source>Internal error</source>
|
||||
<translation>Internal error</translation>
|
||||
</message>
|
||||
@@ -3183,8 +3153,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<translation>Server #1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.cpp" line="207"/>
|
||||
<location filename="../settings.cpp" line="214"/>
|
||||
<location filename="../settings.cpp" line="206"/>
|
||||
<location filename="../settings.cpp" line="213"/>
|
||||
<source>Server</source>
|
||||
<translation>Server</translation>
|
||||
</message>
|
||||
@@ -3192,17 +3162,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>SettingsController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
|
||||
<source>All settings have been reset to default values</source>
|
||||
<translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
|
||||
<source>Cached profiles cleared</source>
|
||||
<translation>پروفایل ذخیره شده پاک شد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
|
||||
<source>Backup file is corrupted</source>
|
||||
<translation>فایل بکآپ خراب شده است</translation>
|
||||
</message>
|
||||
@@ -3216,33 +3186,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<translation>ذخیره تنظیمات AmneziaVPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
|
||||
<source>Share</source>
|
||||
<translation>اشتراکگذاری</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
|
||||
<source>Copy</source>
|
||||
<translation>کپی</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
|
||||
<source>Copied</source>
|
||||
<translation>کپی شد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
|
||||
<source>Copy config string</source>
|
||||
<translation>کپیکردن متن تنظیمات</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<source>Show connection settings</source>
|
||||
<translation>نمایش تنظیمات ارتباط</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
|
||||
<source>To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"</source>
|
||||
<translation>برای خواندن QR Code در نرمافزار AmneziaVPN "اضافه کردن سرور" -> "من داده برای اتصال دارم" -> "QR Code، کلید یا فایل تنظیمات"</translation>
|
||||
</message>
|
||||
@@ -3334,7 +3304,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>VpnConnection</name>
|
||||
<message>
|
||||
<location filename="../vpnconnection.cpp" line="458"/>
|
||||
<location filename="../vpnconnection.cpp" line="457"/>
|
||||
<source>Mbps</source>
|
||||
<translation>Mbps</translation>
|
||||
</message>
|
||||
@@ -3438,12 +3408,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>main2</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="179"/>
|
||||
<location filename="../ui/qml/main2.qml" line="174"/>
|
||||
<source>Private key passphrase</source>
|
||||
<translation>عبارت کلید خصوصی</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="202"/>
|
||||
<location filename="../ui/qml/main2.qml" line="197"/>
|
||||
<source>Save</source>
|
||||
<translation>ذخیره</translation>
|
||||
</message>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,47 +27,47 @@
|
||||
<context>
|
||||
<name>ConnectionController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
|
||||
<source>VPN Protocols is not installed.
|
||||
Please install VPN container at first</source>
|
||||
<translation>VPN протоколы не установлены.
|
||||
Пожалуйста, установите протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
|
||||
<source>Connection...</source>
|
||||
<translation>Подключение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
|
||||
<source>Connected</source>
|
||||
<translation>Подключено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
|
||||
<source>Settings updated successfully, Reconnnection...</source>
|
||||
<translation>Настройки успешно обновлены, Подключение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
|
||||
<source>Settings updated successfully</source>
|
||||
<translation>Настройки успешно обновлены.</translation>
|
||||
<translation type="unfinished">Настройки успешно обновлены.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
|
||||
<source>Reconnection...</source>
|
||||
<translation>Переподключение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.h" line="62"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
|
||||
<source>Connect</source>
|
||||
<translation>Подключиться</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
|
||||
<source>Disconnection...</source>
|
||||
<translation>Отключение...</translation>
|
||||
</message>
|
||||
@@ -143,7 +143,7 @@
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="32"/>
|
||||
<source>Split tunneling</source>
|
||||
<translation>Раздельное-туннелирование</translation>
|
||||
<translation type="unfinished">Раздельное VPN-туннелирование</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/>
|
||||
@@ -153,34 +153,33 @@
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/>
|
||||
<source>Split tunneling on the server</source>
|
||||
<translation>Раздельное-туннелирование на сервере</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="43"/>
|
||||
<source>Enabled
|
||||
Can't be disabled for current server</source>
|
||||
<translation>Включено.
|
||||
Не может быть отключено для текущего сервера.</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="62"/>
|
||||
<source>Site-based split tunneling</source>
|
||||
<translation>Раздельное туннелирование по сайтам</translation>
|
||||
<translation type="unfinished">Раздельное туннелирование сайтов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
|
||||
<source>Enabled</source>
|
||||
<translation>Включено</translation>
|
||||
<translation type="unfinished">Включено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
|
||||
<source>Disabled</source>
|
||||
<translation>Отключено</translation>
|
||||
<translation type="unfinished">Отключено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="79"/>
|
||||
<source>App-based split tunneling</source>
|
||||
<translation>Раздельное VPN-туннелирование по приложениям</translation>
|
||||
<translation type="unfinished">Раздельное VPN-туннелирование приложений</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -209,7 +208,7 @@ Can't be disabled for current server</source>
|
||||
<location filename="../ui/controllers/installController.cpp" line="149"/>
|
||||
<source>
|
||||
Added containers that were already installed on the server</source>
|
||||
<translation>Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
|
||||
<translation type="unfinished">Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/installController.cpp" line="213"/>
|
||||
@@ -226,7 +225,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/controllers/installController.cpp" line="305"/>
|
||||
<source>Server '%1' was rebooted</source>
|
||||
<translation>Сервер '%1' перезагружен</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/installController.cpp" line="314"/>
|
||||
@@ -327,17 +326,17 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation type="unfinished">Раздельное туннелирование выключено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>VPN протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
|
||||
<source>Servers</source>
|
||||
<translation>Серверы</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
|
||||
<source>Unable change server while there is an active connection</source>
|
||||
<translation>Невозможно изменить сервер при активном соединении</translation>
|
||||
</message>
|
||||
@@ -932,7 +931,7 @@ Already installed containers were found on the server. All installed containers
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="56"/>
|
||||
<source>Support Amnezia</source>
|
||||
<translation>Поддержите Amnezia</translation>
|
||||
<translation type="unfinished">Поддержите Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="71"/>
|
||||
@@ -1459,7 +1458,7 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
|
||||
<source>Do you want to clear server Amnezia-installed services?</source>
|
||||
<source>Do you want to clear server from Amnezia software?</source>
|
||||
<translation type="unfinished">Вы хотите очистить сервер от всех сервисов Amnezia?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1474,12 +1473,12 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
|
||||
<source>Remove this server from the app</source>
|
||||
<source>Remove server from application</source>
|
||||
<translation>Удалить сервер из приложения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove server from application?</source>
|
||||
<translation type="vanished">Удалить сервер из приложения?</translation>
|
||||
<source>Remove server?</source>
|
||||
<translation type="vanished">Удалить сервер?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="174"/>
|
||||
@@ -1488,11 +1487,11 @@ Already installed containers were found on the server. All installed containers
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
|
||||
<source>Clear server Amnezia-installed services</source>
|
||||
<source>Clear server from Amnezia software</source>
|
||||
<translation>Очистить сервер от протоколов и сервисов Amnezia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear server Amnezia-installed services?</source>
|
||||
<source>Clear server from Amnezia software?</source>
|
||||
<translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1691,45 +1690,41 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation>Подключение к серверу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection code from public sources. It may have been created to intercept your data.
|
||||
|
||||
It's okay as long as it's from someone you trust.</source>
|
||||
<translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.
|
||||
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.
|
||||
|
||||
Всё в порядке, если кодом поделился пользователь, которому вы доверяете.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
|
||||
<source>What do you have?</source>
|
||||
<translation>Выберите что у вас есть</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings</source>
|
||||
<translation>Файл с настройками подключения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings or backup</source>
|
||||
<translation>Файл с настройками подключения или бэкап</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
|
||||
<source>Open config file</source>
|
||||
<translation>Открыть файл с конфигурацией</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
|
||||
<source>QR-code</source>
|
||||
<translation>QR-код</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
|
||||
<source>Key as text</source>
|
||||
<translation>Ключ в виде текста</translation>
|
||||
</message>
|
||||
@@ -1776,7 +1771,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="46"/>
|
||||
<source>Configure your server</source>
|
||||
<translation>Настроить свой сервер</translation>
|
||||
<translation>Настроить ваш сервер</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="54"/>
|
||||
@@ -1832,7 +1827,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
|
||||
<source>Set up later</source>
|
||||
<translation>Настроить позднее</translation>
|
||||
</message>
|
||||
@@ -1941,32 +1936,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<context>
|
||||
<name>PageSetupWizardStart</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
|
||||
<source>Settings restored from backup file</source>
|
||||
<translation>Восстановление настроек из бэкап файла</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
|
||||
<source>Free service for creating a personal VPN on your server.</source>
|
||||
<translation>Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
|
||||
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
|
||||
<translation> Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<source>I have the data to connect</source>
|
||||
<translation>У меня есть данные для подключения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
|
||||
<source>I have nothing</source>
|
||||
<translation>У меня ничего нет</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
|
||||
<source>https://amnezia.org/instructions/0_starter-guide</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2006,14 +2001,10 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<source>New connection</source>
|
||||
<translation>Новое соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
@@ -2147,7 +2138,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
|
||||
<source>Users</source>
|
||||
<translation type="unfinished">Пользователи</translation>
|
||||
</message>
|
||||
@@ -2157,52 +2148,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="unfinished">Имя пользователя</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
|
||||
<source>Creation date: </source>
|
||||
<translation type="unfinished">Дата создания</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
|
||||
<source>Rename</source>
|
||||
<translation type="unfinished">Переименовать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
|
||||
<source>Client name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
|
||||
<source>Save</source>
|
||||
<translation type="unfinished">Сохранить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
|
||||
<source>Revoke</source>
|
||||
<translation type="unfinished">Отозвать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
|
||||
<source>Revoke the config for a user - %1?</source>
|
||||
<translation type="unfinished">Отозвать доступ для пользователя - %1?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
|
||||
<source>The user will no longer be able to connect to your server.</source>
|
||||
<translation type="unfinished">Пользователь больше не сможет подключаться к вашему серверу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
|
||||
<source>Continue</source>
|
||||
<translation type="unfinished">Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished">Отменить</translation>
|
||||
</message>
|
||||
@@ -2216,20 +2207,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>Поделиться доступом к VPN, без возможности управления сервером</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
|
||||
<source>Protocol</source>
|
||||
<translation>Протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
|
||||
<source>Connection format</source>
|
||||
<translation>Формат подключения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
|
||||
<source>Share</source>
|
||||
<translation>Поделиться</translation>
|
||||
</message>
|
||||
@@ -2606,78 +2597,86 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="32"/>
|
||||
<source>Scp error: Generic failure</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sftp error: End-of-file encountered</source>
|
||||
<translation type="vanished">Sftp error: End-of-file encountered</translation>
|
||||
<translation>Sftp error: End-of-file encountered</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="33"/>
|
||||
<source>Sftp error: File does not exist</source>
|
||||
<translation type="vanished">Sftp error: File does not exist</translation>
|
||||
<translation>Sftp error: File does not exist</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
<source>Sftp error: Permission denied</source>
|
||||
<translation type="vanished">Sftp error: Permission denied</translation>
|
||||
<translation>Sftp error: Permission denied</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<source>Sftp error: Generic failure</source>
|
||||
<translation type="vanished">Sftp error: Generic failure</translation>
|
||||
<translation>Sftp error: Generic failure</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<source>Sftp error: Garbage received from server</source>
|
||||
<translation type="vanished">Sftp error: Garbage received from server</translation>
|
||||
<translation>Sftp error: Garbage received from server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="37"/>
|
||||
<source>Sftp error: No connection has been set up</source>
|
||||
<translation type="vanished">Sftp error: No connection has been set up</translation>
|
||||
<translation>Sftp error: No connection has been set up</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="38"/>
|
||||
<source>Sftp error: There was a connection, but we lost it</source>
|
||||
<translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
|
||||
<translation>Sftp error: There was a connection, but we lost it</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<source>Sftp error: Operation not supported by libssh yet</source>
|
||||
<translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
|
||||
<translation>Sftp error: Operation not supported by libssh yet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<source>Sftp error: Invalid file handle</source>
|
||||
<translation type="vanished">Sftp error: Invalid file handle</translation>
|
||||
<translation>Sftp error: Invalid file handle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<source>Sftp error: No such file or directory path exists</source>
|
||||
<translation type="vanished">Sftp error: No such file or directory path exists</translation>
|
||||
<translation>Sftp error: No such file or directory path exists</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
|
||||
<translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
|
||||
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<source>Sftp error: Write-protected filesystem</source>
|
||||
<translation type="vanished">Sftp error: Write-protected filesystem</translation>
|
||||
<translation>Sftp error: Write-protected filesystem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="44"/>
|
||||
<source>Sftp error: No media was in remote drive</source>
|
||||
<translation type="vanished">Sftp error: No media was in remote drive</translation>
|
||||
<translation>Sftp error: No media was in remote drive</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="50"/>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>The config does not contain any containers and credentials for connecting to the server</source>
|
||||
<translation type="unfinished">Конфиг не содержит контейнеров и учетных данных для подключения к серверу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="56"/>
|
||||
<location filename="../core/errorstrings.cpp" line="68"/>
|
||||
<source>Error when retrieving configuration from API</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="57"/>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<source>This config has already been added to the application</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="72"/>
|
||||
<location filename="../core/errorstrings.cpp" line="76"/>
|
||||
<source>ErrorCode: %1. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2686,92 +2685,62 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">Failed to save config to disk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<source>OpenVPN config missing</source>
|
||||
<translation>OpenVPN config missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<source>OpenVPN management server error</source>
|
||||
<translation>OpenVPN management server error</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<location filename="../core/errorstrings.cpp" line="51"/>
|
||||
<source>OpenVPN executable missing</source>
|
||||
<translation>OpenVPN executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<location filename="../core/errorstrings.cpp" line="52"/>
|
||||
<source>ShadowSocks (ss-local) executable missing</source>
|
||||
<translation>ShadowSocks (ss-local) executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<source>Cloak (ck-client) executable missing</source>
|
||||
<translation>Cloak (ck-client) executable missing</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<location filename="../core/errorstrings.cpp" line="54"/>
|
||||
<source>Amnezia helper service error</source>
|
||||
<translation>Amnezia helper service error</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<location filename="../core/errorstrings.cpp" line="55"/>
|
||||
<source>OpenSSL failed</source>
|
||||
<translation>OpenSSL failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="46"/>
|
||||
<location filename="../core/errorstrings.cpp" line="58"/>
|
||||
<source>Can't connect: another VPN connection is active</source>
|
||||
<translation>Can't connect: another VPN connection is active</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<location filename="../core/errorstrings.cpp" line="59"/>
|
||||
<source>Can't setup OpenVPN TAP network adapter</source>
|
||||
<translation>Can't setup OpenVPN TAP network adapter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>VPN pool error: no available addresses</source>
|
||||
<translation>VPN pool error: no available addresses</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>VPN connection error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>QFile error: The file could not be opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="61"/>
|
||||
<source>QFile error: An error occurred when reading from the file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>QFile error: The file could not be accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="63"/>
|
||||
<source>QFile error: An unspecified error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="64"/>
|
||||
<source>QFile error: A fatal error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>QFile error: The operation was aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<location filename="../core/errorstrings.cpp" line="73"/>
|
||||
<source>Internal error</source>
|
||||
<translation>Internal error</translation>
|
||||
</message>
|
||||
@@ -3108,8 +3077,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<translation>Server #1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.cpp" line="207"/>
|
||||
<location filename="../settings.cpp" line="214"/>
|
||||
<location filename="../settings.cpp" line="206"/>
|
||||
<location filename="../settings.cpp" line="213"/>
|
||||
<source>Server</source>
|
||||
<translation>Server</translation>
|
||||
</message>
|
||||
@@ -3117,17 +3086,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>SettingsController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
|
||||
<source>All settings have been reset to default values</source>
|
||||
<translation>Все настройки были сброшены к значению "По умолчанию"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
|
||||
<source>Cached profiles cleared</source>
|
||||
<translation>Кэш профиля очищен</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
|
||||
<source>Backup file is corrupted</source>
|
||||
<translation>Backup файл поврежден</translation>
|
||||
</message>
|
||||
@@ -3141,33 +3110,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<translation>Сохранить config AmneziaVPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
|
||||
<source>Share</source>
|
||||
<translation>Поделиться</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
|
||||
<source>Copy</source>
|
||||
<translation>Скопировать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
|
||||
<source>Copied</source>
|
||||
<translation>Скопировано</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
|
||||
<source>Copy config string</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<source>Show connection settings</source>
|
||||
<translation type="unfinished">Показать настройки подключения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
|
||||
<source>To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"</source>
|
||||
<translation>Для считывания QR-кода в приложении Amnezia выберите "Добавить сервер" → "У меня есть данные для подключения" → "QR-код, ключ или файл настроек"</translation>
|
||||
</message>
|
||||
@@ -3259,7 +3228,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>VpnConnection</name>
|
||||
<message>
|
||||
<location filename="../vpnconnection.cpp" line="458"/>
|
||||
<location filename="../vpnconnection.cpp" line="457"/>
|
||||
<source>Mbps</source>
|
||||
<translation>Mbps</translation>
|
||||
</message>
|
||||
@@ -3363,12 +3332,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
|
||||
<context>
|
||||
<name>main2</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="179"/>
|
||||
<location filename="../ui/qml/main2.qml" line="174"/>
|
||||
<source>Private key passphrase</source>
|
||||
<translation>Кодовая фраза для закрытого ключа</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="202"/>
|
||||
<location filename="../ui/qml/main2.qml" line="197"/>
|
||||
<source>Save</source>
|
||||
<translation>Сохранить</translation>
|
||||
</message>
|
||||
|
||||
@@ -20,45 +20,45 @@
|
||||
<name>ConnectionController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.h" line="62"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="84"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="98"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="104"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="83"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="97"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="103"/>
|
||||
<source>Connect</source>
|
||||
<translation>连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="41"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="40"/>
|
||||
<source>VPN Protocols is not installed.
|
||||
Please install VPN container at first</source>
|
||||
<translation>请先安装VPN协议</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="64"/>
|
||||
<source>Connection...</source>
|
||||
<translation>连接中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="70"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="69"/>
|
||||
<source>Connected</source>
|
||||
<translation>已连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="78"/>
|
||||
<source>Reconnection...</source>
|
||||
<translation>重连中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="88"/>
|
||||
<source>Disconnection...</source>
|
||||
<translation>断开中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="115"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="114"/>
|
||||
<source>Settings updated successfully, Reconnnection...</source>
|
||||
<translation>配置已更新,重连中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="118"/>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="117"/>
|
||||
<source>Settings updated successfully</source>
|
||||
<translation type="unfinished">配置更新成功</translation>
|
||||
</message>
|
||||
@@ -353,17 +353,17 @@ Already installed containers were found on the server. All installed containers
|
||||
<translation>分隔隧道已禁用</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>VPN协议</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/>
|
||||
<source>Servers</source>
|
||||
<translation>服务器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/>
|
||||
<source>Unable change server while there is an active connection</source>
|
||||
<translation>已建立连接时无法更改服务器配置</translation>
|
||||
</message>
|
||||
@@ -1493,7 +1493,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
|
||||
<source>Do you want to clear server Amnezia-installed services?</source>
|
||||
<source>Do you want to clear server from Amnezia software?</source>
|
||||
<translation>您要清除服务器上的Amnezia软件吗?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1541,7 +1541,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
|
||||
<source>Remove this server from the app</source>
|
||||
<source>Remove server from application</source>
|
||||
<translation>移除本地服务器信息</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1560,7 +1560,7 @@ And if you don't like the app, all the more support it - the donation will
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove server from application?</source>
|
||||
<source>Remove server?</source>
|
||||
<translation type="vanished">移除本地服务器信息?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1570,11 +1570,11 @@ And if you don't like the app, all the more support it - the donation will
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
|
||||
<source>Clear server Amnezia-installed services</source>
|
||||
<source>Clear server from Amnezia software</source>
|
||||
<translation>清理Amnezia中服务器信息</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear server Amnezia-installed services?</source>
|
||||
<source>Clear server from Amnezia software?</source>
|
||||
<translation type="vanished">清理Amnezia中服务器信息</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1797,44 +1797,40 @@ And if you don't like the app, all the more support it - the donation will
|
||||
<translation>服务器连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection code from public sources. It may have been created to intercept your data.
|
||||
|
||||
It's okay as long as it's from someone you trust.</source>
|
||||
<translation type="vanished">请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。
|
||||
<translation>请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。
|
||||
请确保连接码来源可信。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/>
|
||||
<source>What do you have?</source>
|
||||
<translation>你用什么方式创建连接?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings or backup</source>
|
||||
<translation>包含连接配置或备份的文件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/>
|
||||
<source>File with connection settings</source>
|
||||
<translation>包含连接配置的文件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/>
|
||||
<source>Open config file</source>
|
||||
<translation>打开配置文件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/>
|
||||
<source>QR-code</source>
|
||||
<translation>二维码</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/>
|
||||
<source>Key as text</source>
|
||||
<translation>授权码文本</translation>
|
||||
</message>
|
||||
@@ -1934,7 +1930,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>继续</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/>
|
||||
<source>Set up later</source>
|
||||
<translation>稍后设置</translation>
|
||||
</message>
|
||||
@@ -2043,32 +2039,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<context>
|
||||
<name>PageSetupWizardStart</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/>
|
||||
<source>Settings restored from backup file</source>
|
||||
<translation>从备份文件还原配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/>
|
||||
<source>Free service for creating a personal VPN on your server.</source>
|
||||
<translation>在您的服务器上架设私人免费VPN服务。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/>
|
||||
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
|
||||
<translation>帮助您访问受限内容,保护您的隐私,即使是VPN提供商也无法获取。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
|
||||
<source>I have the data to connect</source>
|
||||
<translation>我有连接配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/>
|
||||
<source>I have nothing</source>
|
||||
<translation>我没有</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/>
|
||||
<source>https://amnezia.org/instructions/0_starter-guide</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2108,14 +2104,10 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<source>New connection</source>
|
||||
<translation>新连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation type="vanished">请勿使用公共来源的连接码。它可以被创建来拦截您的数据。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
|
||||
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Do not use connection code from public sources. It could be created to intercept your data.</source>
|
||||
<translation>请勿使用公共来源的连接码。它可以被创建来拦截您的数据。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
@@ -2207,7 +2199,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/>
|
||||
<source>Users</source>
|
||||
<translation>用户</translation>
|
||||
</message>
|
||||
@@ -2217,52 +2209,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>共享 VPN 访问,无需管理服务器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/>
|
||||
<source>Creation date: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/>
|
||||
<source>Rename</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/>
|
||||
<source>Client name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/>
|
||||
<source>Save</source>
|
||||
<translation type="unfinished">保存</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/>
|
||||
<source>Revoke</source>
|
||||
<translation>撤销</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/>
|
||||
<source>Revoke the config for a user - %1?</source>
|
||||
<translation>撤销用户的配置- %1?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/>
|
||||
<source>The user will no longer be able to connect to your server.</source>
|
||||
<translation type="unfinished">该用户将无法再连接到您的服务器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/>
|
||||
<source>Continue</source>
|
||||
<translation type="unfinished">继续</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished">取消</translation>
|
||||
</message>
|
||||
@@ -2327,8 +2319,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="obsolete">协议</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/>
|
||||
<source>Protocol</source>
|
||||
<translation>协议</translation>
|
||||
</message>
|
||||
@@ -2348,14 +2340,14 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation>用户名</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/>
|
||||
<source>Connection format</source>
|
||||
<translation>连接格式</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
|
||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/>
|
||||
<source>Share</source>
|
||||
<translation>共享</translation>
|
||||
</message>
|
||||
@@ -2737,108 +2729,86 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="32"/>
|
||||
<source>Scp error: Generic failure</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sftp error: End-of-file encountered</source>
|
||||
<translation type="vanished">Sftp错误: End-of-file encountered</translation>
|
||||
<translation>Sftp错误: End-of-file encountered</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="33"/>
|
||||
<source>Sftp error: File does not exist</source>
|
||||
<translation type="vanished">Sftp错误: 文件不存在</translation>
|
||||
<translation>Sftp错误: 文件不存在</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="34"/>
|
||||
<source>Sftp error: Permission denied</source>
|
||||
<translation type="vanished">Sftp错误: 权限不足</translation>
|
||||
<translation>Sftp错误: 权限不足</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<source>Sftp error: Generic failure</source>
|
||||
<translation type="vanished">Sftp错误: 一般失败</translation>
|
||||
<translation>Sftp错误: 一般失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<source>Sftp error: Garbage received from server</source>
|
||||
<translation type="vanished">Sftp错误: 从服务器收到垃圾信息</translation>
|
||||
<translation>Sftp错误: 从服务器收到垃圾信息</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="37"/>
|
||||
<source>Sftp error: No connection has been set up</source>
|
||||
<translation type="vanished">Sftp 错误: 未建立连接</translation>
|
||||
<translation>Sftp 错误: 未建立连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="38"/>
|
||||
<source>Sftp error: There was a connection, but we lost it</source>
|
||||
<translation type="vanished">Sftp 错误: 已有连接丢失</translation>
|
||||
<translation>Sftp 错误: 已有连接丢失</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<source>Sftp error: Operation not supported by libssh yet</source>
|
||||
<translation type="vanished">Sftp error: libssh不支持该操作</translation>
|
||||
<translation>Sftp error: libssh不支持该操作</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<source>Sftp error: Invalid file handle</source>
|
||||
<translation type="vanished">Sftp error: 无效的文件句柄</translation>
|
||||
<translation>Sftp error: 无效的文件句柄</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<source>Sftp error: No such file or directory path exists</source>
|
||||
<translation type="vanished">Sftp 错误: 文件夹或文件不存在</translation>
|
||||
<translation>Sftp 错误: 文件夹或文件不存在</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<source>Sftp error: An attempt to create an already existing file or directory has been made</source>
|
||||
<translation type="vanished">Sftp 错误: 文件或目录已存在</translation>
|
||||
<translation>Sftp 错误: 文件或目录已存在</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<source>Sftp error: Write-protected filesystem</source>
|
||||
<translation type="vanished">Sftp 错误: 文件系统写保护</translation>
|
||||
<translation>Sftp 错误: 文件系统写保护</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="44"/>
|
||||
<source>Sftp error: No media was in remote drive</source>
|
||||
<translation type="vanished">Sftp 错误: 远程驱动器中没有媒介</translation>
|
||||
<translation>Sftp 错误: 远程驱动器中没有媒介</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>VPN connection error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="56"/>
|
||||
<location filename="../core/errorstrings.cpp" line="68"/>
|
||||
<source>Error when retrieving configuration from API</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="57"/>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<source>This config has already been added to the application</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>QFile error: The file could not be opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="61"/>
|
||||
<source>QFile error: An error occurred when reading from the file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>QFile error: The file could not be accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="63"/>
|
||||
<source>QFile error: An unspecified error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="64"/>
|
||||
<source>QFile error: A fatal error occurred</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="65"/>
|
||||
<source>QFile error: The operation was aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="72"/>
|
||||
<location filename="../core/errorstrings.cpp" line="76"/>
|
||||
<source>ErrorCode: %1. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -2847,57 +2817,57 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">配置保存到磁盘失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="35"/>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<source>OpenVPN config missing</source>
|
||||
<translation>OpenVPN配置丢失</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="36"/>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<source>OpenVPN management server error</source>
|
||||
<translation>OpenVPN 管理服务器错误</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="39"/>
|
||||
<location filename="../core/errorstrings.cpp" line="51"/>
|
||||
<source>OpenVPN executable missing</source>
|
||||
<translation>OpenVPN 可执行文件丢失</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="40"/>
|
||||
<location filename="../core/errorstrings.cpp" line="52"/>
|
||||
<source>ShadowSocks (ss-local) executable missing</source>
|
||||
<translation>ShadowSocks (ss-local) 执行文件丢失</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="41"/>
|
||||
<location filename="../core/errorstrings.cpp" line="53"/>
|
||||
<source>Cloak (ck-client) executable missing</source>
|
||||
<translation>Cloak (ck-client) 执行文件丢失</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="42"/>
|
||||
<location filename="../core/errorstrings.cpp" line="54"/>
|
||||
<source>Amnezia helper service error</source>
|
||||
<translation>Amnezia 服务连接失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="43"/>
|
||||
<location filename="../core/errorstrings.cpp" line="55"/>
|
||||
<source>OpenSSL failed</source>
|
||||
<translation>OpenSSL错误</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="46"/>
|
||||
<location filename="../core/errorstrings.cpp" line="58"/>
|
||||
<source>Can't connect: another VPN connection is active</source>
|
||||
<translation>无法连接:另一个VPN连接处于活跃状态</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="47"/>
|
||||
<location filename="../core/errorstrings.cpp" line="59"/>
|
||||
<source>Can't setup OpenVPN TAP network adapter</source>
|
||||
<translation>无法设置 OpenVPN TAP 网络适配器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="48"/>
|
||||
<location filename="../core/errorstrings.cpp" line="60"/>
|
||||
<source>VPN pool error: no available addresses</source>
|
||||
<translation>VPN 池错误:没有可用地址</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="50"/>
|
||||
<location filename="../core/errorstrings.cpp" line="62"/>
|
||||
<source>The config does not contain any containers and credentials for connecting to the server</source>
|
||||
<translation>配置不包含任何用于连接服务器的容器和凭据</translation>
|
||||
</message>
|
||||
@@ -2906,7 +2876,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
|
||||
<translation type="vanished">该配置不包含任何用于连接到服务器的容器和凭据。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../core/errorstrings.cpp" line="69"/>
|
||||
<location filename="../core/errorstrings.cpp" line="73"/>
|
||||
<source>Internal error</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
@@ -3275,8 +3245,8 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.cpp" line="207"/>
|
||||
<location filename="../settings.cpp" line="214"/>
|
||||
<location filename="../settings.cpp" line="206"/>
|
||||
<location filename="../settings.cpp" line="213"/>
|
||||
<source>Server</source>
|
||||
<translation>服务器</translation>
|
||||
</message>
|
||||
@@ -3284,17 +3254,17 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>SettingsController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="123"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="139"/>
|
||||
<source>Backup file is corrupted</source>
|
||||
<translation>备份文件已损坏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="140"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="155"/>
|
||||
<source>All settings have been reset to default values</source>
|
||||
<translation>所配置恢复为默认值</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="150"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="161"/>
|
||||
<source>Cached profiles cleared</source>
|
||||
<translation>缓存的配置文件已清除</translation>
|
||||
</message>
|
||||
@@ -3308,28 +3278,28 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation>保存配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/>
|
||||
<source>Share</source>
|
||||
<translation>共享</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/>
|
||||
<source>Copy</source>
|
||||
<translation>拷贝</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/>
|
||||
<source>Copied</source>
|
||||
<translation>已拷贝</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/>
|
||||
<source>Copy config string</source>
|
||||
<translation>复制配置字符串</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
|
||||
<source>Show connection settings</source>
|
||||
<translation>显示连接配置</translation>
|
||||
</message>
|
||||
@@ -3338,7 +3308,7 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<translation type="obsolete">展示内容</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
|
||||
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/>
|
||||
<source>To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"</source>
|
||||
<translation>要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件”</translation>
|
||||
</message>
|
||||
@@ -3430,7 +3400,7 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>VpnConnection</name>
|
||||
<message>
|
||||
<location filename="../vpnconnection.cpp" line="458"/>
|
||||
<location filename="../vpnconnection.cpp" line="457"/>
|
||||
<source>Mbps</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
@@ -3534,12 +3504,12 @@ While it offers a blend of security, stability, and speed, it's essential t
|
||||
<context>
|
||||
<name>main2</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="179"/>
|
||||
<location filename="../ui/qml/main2.qml" line="174"/>
|
||||
<source>Private key passphrase</source>
|
||||
<translation>私钥密码</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/main2.qml" line="202"/>
|
||||
<location filename="../ui/qml/main2.qml" line="197"/>
|
||||
<source>Save</source>
|
||||
<translation>保存</translation>
|
||||
</message>
|
||||
|
||||
@@ -90,7 +90,7 @@ void ApiController::updateServerConfigFromApi()
|
||||
request.setRawHeader("Authorization",
|
||||
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||
request.setUrl(endpoint);
|
||||
request.setUrl(endpoint.replace("https", "http")); // todo remove
|
||||
|
||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||
|
||||
@@ -138,10 +138,7 @@ void ApiController::updateServerConfigFromApi()
|
||||
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
||||
m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex());
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
emit errorOccurred(errorString(ApiConfigDownloadError));
|
||||
m_isConfigUpdateStarted = false;
|
||||
|
||||
@@ -25,13 +25,12 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||
|
||||
void ConnectionController::openConnection()
|
||||
{
|
||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||
|
||||
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||
if (!m_containersModel->isAnyContainerInstalled()) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||
|
||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||
|
||||
@@ -418,6 +418,4 @@ void ExportController::clearPreviousConfig()
|
||||
m_config.clear();
|
||||
m_nativeConfigString.clear();
|
||||
m_qrCodes.clear();
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
// return QJsonObject();
|
||||
// }
|
||||
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(", "));
|
||||
|
||||
lastConfig[config_key::allowed_ips] = allowedIpsJsonArray;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ bool PageController::isTriggeredByConnectButton()
|
||||
return m_isTriggeredByConnectButton;
|
||||
}
|
||||
|
||||
void PageController::setTriggeredByConnectButton(bool trigger)
|
||||
void PageController::setTriggeredBtConnectButton(bool trigger)
|
||||
{
|
||||
m_isTriggeredByConnectButton = trigger;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public slots:
|
||||
void showOnStartup();
|
||||
|
||||
bool isTriggeredByConnectButton();
|
||||
void setTriggeredByConnectButton(bool trigger);
|
||||
void setTriggeredBtConnectButton(bool trigger);
|
||||
|
||||
void closeApplication();
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include "ui/qautostart.h"
|
||||
#include "version.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_utils.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
#include <QJniObject>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
@@ -27,6 +29,20 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
||||
m_settings(settings)
|
||||
{
|
||||
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!m_settings->isScreenshotsEnabled()) {
|
||||
// Set security screen for Android app
|
||||
AndroidUtils::runOnAndroidThreadSync([]() {
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
if (window.isValid()) {
|
||||
const int FLAG_SECURE = 8192;
|
||||
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
@@ -136,12 +152,7 @@ void SettingsController::clearSettings()
|
||||
m_languageModel->changeLanguage(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
|
||||
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
|
||||
|
||||
emit changeSettingsFinished(tr("All settings have been reset to default values"));
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
AmneziaVPN::clearSettings();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::clearCachedProfiles()
|
||||
@@ -188,6 +199,19 @@ bool SettingsController::isScreenshotsEnabled()
|
||||
void SettingsController::toggleScreenshotsEnabled(bool enable)
|
||||
{
|
||||
m_settings->setScreenshotsEnabled(enable);
|
||||
#ifdef Q_OS_ANDROID
|
||||
std::string command = enable ? "clearFlags" : "addFlags";
|
||||
|
||||
// Set security screen for Android app
|
||||
AndroidUtils::runOnAndroidThreadSync([&command]() {
|
||||
QJniObject activity = AndroidUtils::getActivity();
|
||||
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
if (window.isValid()) {
|
||||
const int FLAG_SECURE = 8192;
|
||||
window.callMethod<void>(command.c_str(), "(I)V", FLAG_SECURE);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::isCameraPresent()
|
||||
|
||||
@@ -83,6 +83,20 @@ QJsonObject ContainersModel::getContainerConfig(const int containerIndex)
|
||||
return qvariant_cast<QJsonObject>(data(index(containerIndex), ConfigRole));
|
||||
}
|
||||
|
||||
bool ContainersModel::isAnyContainerInstalled()
|
||||
{
|
||||
for (int row=0; row < rowCount(); row++) {
|
||||
QModelIndex idx = this->index(row, 0);
|
||||
|
||||
if (this->data(idx, IsInstalledRole).toBool() &&
|
||||
this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
@@ -49,6 +49,8 @@ public slots:
|
||||
|
||||
QJsonObject getContainerConfig(const int containerIndex);
|
||||
|
||||
bool isAnyContainerInstalled();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan
|
||||
case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -62,7 +61,6 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum
|
||||
case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break;
|
||||
case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break;
|
||||
default: emit updateTranslations(QLocale::English); break;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +74,6 @@ int LanguageModel::getCurrentLanguageIndex()
|
||||
case QLocale::Chinese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::China_cn); break;
|
||||
case QLocale::Persian: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Persian); break;
|
||||
case QLocale::Arabic: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Arabic); break;
|
||||
case QLocale::Burmese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Burmese); break;
|
||||
default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ namespace LanguageSettings
|
||||
Russian,
|
||||
China_cn,
|
||||
Persian,
|
||||
Arabic,
|
||||
Burmese
|
||||
Arabic
|
||||
};
|
||||
Q_ENUM_NS(AvailableLanguageEnum)
|
||||
|
||||
|
||||
@@ -87,9 +87,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
||||
case DefaultContainerRole: {
|
||||
return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
|
||||
}
|
||||
case HasInstalledContainers: {
|
||||
return serverHasInstalledContainers(index.row());
|
||||
}
|
||||
case IsServerFromApiRole: {
|
||||
return server.value(config_key::configVersion).toInt();
|
||||
}
|
||||
@@ -305,7 +302,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
||||
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
|
||||
|
||||
roles[DefaultContainerRole] = "defaultContainer";
|
||||
roles[HasInstalledContainers] = "hasInstalledContainers";
|
||||
|
||||
roles[IsServerFromApiRole] = "isServerFromApi";
|
||||
return roles;
|
||||
@@ -552,19 +548,6 @@ bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
||||
{
|
||||
QJsonObject server = m_servers.at(serverIndex).toObject();
|
||||
const auto containers = server.value(config_key::containers).toArray();
|
||||
for (auto it = containers.begin(); it != containers.end(); it++) {
|
||||
auto container = ContainerProps::containerFromString(it->toObject().value(config_key::container).toString());
|
||||
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant ServersModel::getDefaultServerData(const QString roleString)
|
||||
{
|
||||
auto roles = roleNames();
|
||||
@@ -577,6 +560,11 @@ QVariant ServersModel::getDefaultServerData(const QString roleString)
|
||||
return {};
|
||||
}
|
||||
|
||||
void ServersModel::setDefaultServerData(const QString roleString, const QVariant &value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariant ServersModel::getProcessedServerData(const QString roleString)
|
||||
{
|
||||
auto roles = roleNames();
|
||||
@@ -589,6 +577,11 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
|
||||
return {};
|
||||
}
|
||||
|
||||
void ServersModel::setProcessedServerData(const QString roleString, const QVariant &value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
|
||||
{
|
||||
auto server = m_servers.at(m_defaultServerIndex).toObject();
|
||||
|
||||
@@ -28,7 +28,6 @@ public:
|
||||
|
||||
DefaultContainerRole,
|
||||
|
||||
HasInstalledContainers,
|
||||
IsServerFromApiRole,
|
||||
|
||||
HasAmneziaDns
|
||||
@@ -102,8 +101,10 @@ public slots:
|
||||
bool isServerFromApiAlreadyExists(const quint16 crc);
|
||||
|
||||
QVariant getDefaultServerData(const QString roleString);
|
||||
void setDefaultServerData(const QString roleString, const QVariant &value);
|
||||
|
||||
QVariant getProcessedServerData(const QString roleString);
|
||||
void setProcessedServerData(const QString roleString, const QVariant &value);
|
||||
|
||||
bool isDefaultServerDefaultContainerHasSplitTunneling();
|
||||
|
||||
@@ -122,7 +123,6 @@ signals:
|
||||
|
||||
private:
|
||||
ServerCredentials serverCredentials(int index) const;
|
||||
|
||||
void updateContainersModel();
|
||||
void updateDefaultServerContainersModel();
|
||||
|
||||
@@ -130,8 +130,6 @@ private:
|
||||
|
||||
bool isAmneziaDnsContainerInstalled(const int serverIndex) const;
|
||||
|
||||
bool serverHasInstalledContainers(const int serverIndex) const;
|
||||
|
||||
QJsonArray m_servers;
|
||||
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
@@ -26,14 +26,6 @@ DrawerType2 {
|
||||
root.expandedHeight = content.implicitHeight + 32
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
config.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
@@ -45,7 +37,7 @@ DrawerType2 {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: config
|
||||
id: ip
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -56,14 +48,11 @@ DrawerType2 {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardCredentials)
|
||||
root.close()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: configFromFile.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: configFromFile
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Open config file, key or QR code")
|
||||
@@ -73,8 +62,6 @@ DrawerType2 {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
|
||||
root.close()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: config.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
@@ -22,18 +22,6 @@ DrawerType2 {
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
if (splitTunneling.visible) {
|
||||
splitTunneling.rightButton.forceActiveFocus()
|
||||
} else {
|
||||
splitTunnelingSiteBased.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
@@ -46,7 +34,6 @@ DrawerType2 {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: splitTunneling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -60,8 +47,6 @@ DrawerType2 {
|
||||
// PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
|
||||
// root.close()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: splitTunnelingSiteBased.rightButton
|
||||
}
|
||||
|
||||
DividerType {
|
||||
@@ -69,7 +54,6 @@ DrawerType2 {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: splitTunnelingSiteBased
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -83,14 +67,12 @@ DrawerType2 {
|
||||
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
|
||||
root.close()
|
||||
}
|
||||
KeyNavigation.tab: appBasedSplitTunneling.visible ? appBasedSplitTunneling.rightButton : null
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: appBasedSplitTunneling
|
||||
Layout.fillWidth: true
|
||||
visible: false
|
||||
|
||||
@@ -101,8 +83,6 @@ DrawerType2 {
|
||||
// PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
|
||||
root.close()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: splitTunneling.rightButton
|
||||
}
|
||||
|
||||
DividerType {
|
||||
|
||||
@@ -4,7 +4,6 @@ import QtQuick.Layouts
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
DrawerType2 {
|
||||
id: root
|
||||
@@ -30,14 +29,6 @@ DrawerType2 {
|
||||
root.expandedHeight = content.implicitHeight + 32
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
yesButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
@@ -57,7 +48,6 @@ DrawerType2 {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: yesButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
@@ -70,11 +60,9 @@ DrawerType2 {
|
||||
yesButtonFunction()
|
||||
}
|
||||
}
|
||||
KeyNavigation.tab: noButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: noButton
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
@@ -93,7 +81,6 @@ DrawerType2 {
|
||||
noButtonFunction()
|
||||
}
|
||||
}
|
||||
KeyNavigation.tab: yesButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,6 @@ DrawerType2 {
|
||||
expandedContent: Item {
|
||||
implicitHeight: root.expandedHeight
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
shareButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
id: header
|
||||
anchors.top: parent.top
|
||||
@@ -76,7 +68,6 @@ DrawerType2 {
|
||||
visible: root.contentVisible
|
||||
|
||||
BasicButtonType {
|
||||
id: shareButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -100,8 +91,6 @@ DrawerType2 {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: copyConfigTextButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
@@ -118,8 +107,6 @@ DrawerType2 {
|
||||
|
||||
text: qsTr("Copy")
|
||||
imageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
KeyNavigation.tab: copyNativeConfigStringButton.visible ? copyNativeConfigStringButton : showSettingsButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
@@ -138,13 +125,9 @@ DrawerType2 {
|
||||
|
||||
text: qsTr("Copy config string")
|
||||
imageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
KeyNavigation.tab: showSettingsButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: showSettingsButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
@@ -160,8 +143,6 @@ DrawerType2 {
|
||||
clickedFunc: function() {
|
||||
configContentDrawer.open()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: shareButton
|
||||
}
|
||||
|
||||
DrawerType2 {
|
||||
@@ -277,8 +258,6 @@ DrawerType2 {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: qrCodeContainer
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width
|
||||
Layout.topMargin: 20
|
||||
|
||||
@@ -33,23 +33,22 @@ Button {
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle {
|
||||
id: focusBorder
|
||||
id: background_border
|
||||
|
||||
color: "transparent"
|
||||
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
|
||||
border.width: root.activeFocus ? root.borderFocusedWidth : "transparent"
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
radius: 16
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
anchors.fill: focusBorder
|
||||
anchors.margins: root.activeFocus ? 2 : 0
|
||||
anchors.fill: background_border
|
||||
anchors.margins: root.activeFocus ? 2: 0
|
||||
|
||||
radius: root.activeFocus ? 14 : 16
|
||||
radius: 16
|
||||
color: {
|
||||
if (root.enabled) {
|
||||
if (root.pressed) {
|
||||
@@ -60,8 +59,8 @@ Button {
|
||||
return disabledColor
|
||||
}
|
||||
}
|
||||
border.color: borderColor
|
||||
border.width: borderWidth
|
||||
border.color: root.activeFocus ? "transparent" : borderColor
|
||||
border.width: root.activeFocus ? 0 : borderWidth
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation { duration: 200 }
|
||||
@@ -96,13 +95,13 @@ Button {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: focusBorder
|
||||
anchors.fill: background_border
|
||||
enabled: false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: focusBorder
|
||||
anchors.fill: background_border
|
||||
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
@@ -12,9 +12,6 @@ Button {
|
||||
property string pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
property string disableColor: "#2C2D30"
|
||||
|
||||
property string borderFocusedColor: "#D7D8DB"
|
||||
property int borderFocusedWidth: 1
|
||||
|
||||
property string imageColor: "#878B91"
|
||||
property string disableImageColor: "#2C2D30"
|
||||
|
||||
@@ -34,9 +31,6 @@ Button {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
|
||||
border.width: root.activeFocus ? root.borderFocusedWidth : "transparent"
|
||||
|
||||
color: {
|
||||
if (root.enabled) {
|
||||
if (root.pressed) {
|
||||
|
||||
@@ -27,8 +27,6 @@ Item {
|
||||
|
||||
property string rightImageColor: "#d7d8db"
|
||||
|
||||
property alias rightButton: rightImage
|
||||
|
||||
property bool descriptionOnTop: false
|
||||
|
||||
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
|
||||
@@ -209,16 +207,4 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (clickedFunction && typeof clickedFunction === "function") {
|
||||
clickedFunction()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (clickedFunction && typeof clickedFunction === "function") {
|
||||
clickedFunction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import "../Config"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -11,12 +9,6 @@ Item {
|
||||
|
||||
property var defaultActiveFocusItem: null
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && !GC.isMobile()) {
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea {
|
||||
// id: globalMouseArea
|
||||
// z: 99
|
||||
@@ -40,6 +32,6 @@ Item {
|
||||
}
|
||||
}
|
||||
repeat: false // Stop the timer after one trigger
|
||||
running: !GC.isMobile() // Start the timer
|
||||
running: true // Start the timer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,6 @@ Item {
|
||||
// textColor: "#D7D8DB"
|
||||
// borderWidth: 0
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
text: root.buttonText
|
||||
imageSource: root.buttonImageSource
|
||||
|
||||
@@ -192,12 +191,10 @@ Item {
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
if (KeyNavigation.tab)
|
||||
KeyNavigation.tab.forceActiveFocus();
|
||||
KeyNavigation.tab.forceActiveFocus();
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (KeyNavigation.tab)
|
||||
KeyNavigation.tab.forceActiveFocus();
|
||||
KeyNavigation.tab.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,11 +160,12 @@ PageType {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
text: ServersModel.defaultServerDescriptionCollapsed
|
||||
}
|
||||
|
||||
}
|
||||
expandedContent: Item {
|
||||
expandedContent: Item {
|
||||
id: serverMenuContainer
|
||||
|
||||
implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
|
||||
implicitHeight: root.height * 0.9
|
||||
|
||||
Component.onCompleted: {
|
||||
drawer.expandedHeight = serverMenuContainer.implicitHeight
|
||||
@@ -251,7 +252,7 @@ PageType {
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyDefaultServerContainersModel
|
||||
sourceModel: DefaultServerContainersModel
|
||||
|
||||
|
||||
sorters: [
|
||||
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
|
||||
]
|
||||
|
||||
@@ -28,8 +28,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
defaultActiveFocusItem: removeButton.rightButton
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: backButton.bottom
|
||||
|
||||
@@ -13,8 +13,6 @@ import "../Config"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: servers.rightButton
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: parent.top
|
||||
@@ -40,7 +38,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: servers
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -51,14 +48,11 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsServersList)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: connection.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: connection
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Connection")
|
||||
@@ -68,13 +62,11 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnection)
|
||||
}
|
||||
KeyNavigation.tab: application.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: application
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Application")
|
||||
@@ -84,13 +76,11 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsApplication)
|
||||
}
|
||||
KeyNavigation.tab: backup.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: backup
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Backup")
|
||||
@@ -100,7 +90,6 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsBackup)
|
||||
}
|
||||
KeyNavigation.tab: about.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
@@ -116,7 +105,6 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsAbout)
|
||||
}
|
||||
KeyNavigation.tab: root.defaultActiveFocusItem
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
@@ -13,8 +13,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: donateButton
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -76,7 +74,6 @@ PageType {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: donateButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.leftMargin: 16
|
||||
@@ -87,12 +84,9 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn"))
|
||||
}
|
||||
|
||||
KeyNavigation.tab: donateButton2
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: donateButton2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -110,8 +104,6 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client#donate"))
|
||||
}
|
||||
|
||||
KeyNavigation.tab: donateButton
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -13,8 +13,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: languageLabel.rightButton
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -129,7 +127,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: languageLabel
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Language")
|
||||
@@ -139,15 +136,12 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
selectLanguageDrawer.open()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: loggingLabel.rightButton
|
||||
}
|
||||
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: loggingLabel
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Logging")
|
||||
@@ -157,14 +151,11 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsLogging)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: resetLabel.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: resetLabel
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Reset settings and remove all data from the application")
|
||||
@@ -180,21 +171,12 @@ PageType {
|
||||
var yesButtonFunction = function() {
|
||||
SettingsController.clearSettings()
|
||||
PageController.replaceStartPage()
|
||||
|
||||
if (!GC.isMobile()) {
|
||||
languageLabel.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
languageLabel.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: defaultActiveFocusItem
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
@@ -34,8 +34,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
defaultActiveFocusItem: makeBackupButton
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -108,12 +106,9 @@ PageType {
|
||||
PageController.showNotificationMessage(qsTr("Backup file saved"))
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: restoreBackupButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: restoreBackupButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -8
|
||||
|
||||
@@ -133,8 +128,6 @@ PageType {
|
||||
restoreBackup(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: makeBackupButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import "../Config"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: dnsServersButton.rightButton
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -61,7 +59,6 @@ PageType {
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: dnsServersButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("DNS servers")
|
||||
@@ -71,14 +68,11 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsDns)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: splitTunnelingButton.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: splitTunnelingButton
|
||||
visible: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -90,7 +84,6 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
|
||||
}
|
||||
KeyNavigation.tab: splitTunnelingButton2.visible ? splitTunnelingButton2.rightButton : root.defaultActiveFocusItem
|
||||
}
|
||||
|
||||
DividerType {
|
||||
@@ -98,7 +91,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: splitTunnelingButton2
|
||||
visible: false
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -109,7 +101,6 @@ PageType {
|
||||
|
||||
clickedFunction: function() {
|
||||
}
|
||||
KeyNavigation.tab: root.defaultActiveFocusItem
|
||||
}
|
||||
|
||||
DividerType {
|
||||
|
||||
@@ -87,11 +87,10 @@ PageType {
|
||||
regularExpression: InstallController.ipAddressRegExp()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: restoreDefaultButton
|
||||
KeyNavigation.tab: saveButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: restoreDefaultButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
defaultColor: "transparent"
|
||||
@@ -114,19 +113,12 @@ PageType {
|
||||
SettingsController.secondaryDns = "1.0.0.1"
|
||||
secondaryDns.textFieldText = SettingsController.secondaryDns
|
||||
PageController.showNotificationMessage(qsTr("Settings have been reset"))
|
||||
if (!GC.isMobile()) {
|
||||
primaryDns.textField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
primaryDns.textField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
KeyNavigation.tab: saveButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
@@ -145,7 +137,6 @@ PageType {
|
||||
}
|
||||
PageController.showNotificationMessage(qsTr("Settings saved"))
|
||||
}
|
||||
KeyNavigation.tab: primaryDns.textField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Remove this server from the app")
|
||||
text: qsTr("Remove server from application")
|
||||
textColor: "#EB5757"
|
||||
|
||||
clickedFunction: function() {
|
||||
@@ -196,11 +196,11 @@ PageType {
|
||||
visible: content.isServerWithWriteAccess
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Clear server Amnezia-installed services")
|
||||
text: qsTr("Clear server from Amnezia software")
|
||||
textColor: "#EB5757"
|
||||
|
||||
clickedFunction: function() {
|
||||
var headerText = qsTr("Do you want to clear server Amnezia-installed services?")
|
||||
var headerText = qsTr("Do you want to clear server from Amnezia software?")
|
||||
var descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
@@ -93,7 +93,6 @@ PageType {
|
||||
|
||||
Connections {
|
||||
target: serverNameEditDrawer
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
serverName.textField.forceActiveFocus()
|
||||
}
|
||||
@@ -128,12 +127,10 @@ PageType {
|
||||
}
|
||||
serverNameEditDrawer.close()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: serverName.textField
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (header.itemAt(0) && !GC.isMobile()) {
|
||||
if (header.itemAt(0)) {
|
||||
defaultActiveFocusItem = serverName.textField
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: servers.currentItem.focusItem
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
|
||||
@@ -65,8 +63,6 @@ PageType {
|
||||
implicitWidth: servers.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
|
||||
property alias focusItem: server.rightButton
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
|
||||
@@ -296,14 +296,6 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
Connections{
|
||||
target: moreActionsDrawer
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened(){
|
||||
importSitesButton.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
@@ -312,7 +304,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: importSitesButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Import")
|
||||
@@ -326,7 +317,6 @@ PageType {
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: saveSitesButton
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Save site list")
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ import "../Config"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem:fileButton.rightButton
|
||||
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
@@ -50,7 +48,8 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("Server connection")
|
||||
descriptionText: qsTr("Do not use connection codes from untrusted sources, as they may be created to intercept your data.")
|
||||
descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n
|
||||
It's okay as long as it's from someone you trust.")
|
||||
}
|
||||
|
||||
Header2TextType {
|
||||
@@ -63,7 +62,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: fileButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -86,14 +84,11 @@ PageType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: qrButton.visible ? qrButton.rightButton : textButton.rightButton
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: qrButton
|
||||
Layout.fillWidth: true
|
||||
visible: SettingsController.isCameraPresent()
|
||||
|
||||
@@ -107,8 +102,6 @@ PageType {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardQrReader)
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: textButton.rightButton
|
||||
}
|
||||
|
||||
DividerType {
|
||||
@@ -116,7 +109,6 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: textButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Key as text")
|
||||
@@ -126,8 +118,6 @@ PageType {
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardTextKey)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: defaultActiveFocusItem
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
@@ -121,8 +121,6 @@ PageType {
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardEasy)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: hostname.textField
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
|
||||
@@ -17,8 +17,6 @@ PageType {
|
||||
|
||||
property bool isEasySetup: true
|
||||
|
||||
defaultActiveFocusItem: continueButton
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyContainersModel
|
||||
sourceModel: ContainersModel
|
||||
@@ -171,7 +169,6 @@ PageType {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocols)
|
||||
}
|
||||
}
|
||||
KeyNavigation.tab: setupLaterButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
@@ -190,8 +187,9 @@ PageType {
|
||||
|
||||
visible: {
|
||||
if (PageController.isTriggeredByConnectButton()) {
|
||||
PageController.setTriggeredByConnectButton(false)
|
||||
return false
|
||||
PageController.setTriggeredBtConnectButton(false)
|
||||
|
||||
return ContainersModel.isAnyContainerInstalled()
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -203,8 +201,6 @@ PageType {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.addEmptyServer()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: continueButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ PageType {
|
||||
|
||||
property bool isControlsDisabled: false
|
||||
|
||||
defaultActiveFocusItem: startButton
|
||||
|
||||
Connections {
|
||||
target: PageController
|
||||
|
||||
@@ -24,6 +22,10 @@ PageType {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
|
||||
function onShowBusyIndicator(visible) {
|
||||
busyIndicator.visible = visible
|
||||
}
|
||||
|
||||
function onClosePage() {
|
||||
if (stackView.depth <= 1) {
|
||||
return
|
||||
@@ -51,7 +53,7 @@ PageType {
|
||||
}
|
||||
|
||||
function onEscapePressed() {
|
||||
if (isControlsDisabled) {
|
||||
if (isControlsDisabled || busyIndicator.visible) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,7 +122,6 @@ PageType {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: startButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
@@ -131,11 +132,9 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
connectionTypeSelection.open()
|
||||
}
|
||||
KeyNavigation.tab: startButton2
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: startButton2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -153,7 +152,6 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally(qsTr("https://amnezia.org/instructions/0_starter-guide"))
|
||||
}
|
||||
KeyNavigation.tab: startButton
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,4 +159,10 @@ PageType {
|
||||
ConnectionTypeSelectionDrawer {
|
||||
id: connectionTypeSelection
|
||||
}
|
||||
|
||||
BusyIndicatorType {
|
||||
id: busyIndicator
|
||||
anchors.centerIn: parent
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,5 @@ PageType {
|
||||
ImportController.extractConfigFromCode(textKey.textFieldText)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
KeyNavigation.tab: textKey.textField
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ PageType {
|
||||
|
||||
property bool showContent: false
|
||||
|
||||
defaultActiveFocusItem: showContentButton
|
||||
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
@@ -55,7 +53,7 @@ PageType {
|
||||
id: fl
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.implicitHeight + connectButtonLayout.implicitHeight
|
||||
contentHeight: content.implicitHeight + connectButton.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
@@ -94,12 +92,11 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Do not use connection codes from untrusted sources, as they may be created to intercept your data.")
|
||||
text: qsTr("Do not use connection code from public sources. It could be created to intercept your data.")
|
||||
color: "#878B91"
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: showContentButton
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: -8
|
||||
implicitHeight: 32
|
||||
@@ -115,8 +112,6 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
showContent = !showContent
|
||||
}
|
||||
|
||||
KeyNavigation.tab: connectButton
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -143,7 +138,7 @@ PageType {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: connectButtonLayout
|
||||
id: connectButton
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
@@ -152,7 +147,6 @@ PageType {
|
||||
anchors.leftMargin: 16
|
||||
|
||||
BasicButtonType {
|
||||
id: connectButton
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
@@ -160,8 +154,6 @@ PageType {
|
||||
clickedFunc: function() {
|
||||
ImportController.importConfig()
|
||||
}
|
||||
|
||||
KeyNavigation.tab: defaultActiveFocusItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
@@ -182,14 +181,6 @@ PageType {
|
||||
|
||||
spacing: 0
|
||||
|
||||
Connections {
|
||||
target: shareFullAccessDrawer
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
shareFullAccessButton.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 16
|
||||
@@ -202,7 +193,6 @@ PageType {
|
||||
|
||||
|
||||
LabelWithButtonType {
|
||||
id: shareFullAccessButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Share")
|
||||
@@ -318,10 +308,6 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "hasWriteAccess"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "hasInstalledContainers"
|
||||
value: true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -338,12 +324,8 @@ PageType {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (ServersModel.isDefaultServerHasWriteAccess() && ServersModel.getDefaultServerData("hasInstalledContainers")) {
|
||||
serverSelectorListView.currentIndex = proxyServersModel.mapFromSource(ServersModel.defaultIndex)
|
||||
} else {
|
||||
serverSelectorListView.currentIndex = 0
|
||||
}
|
||||
|
||||
serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ?
|
||||
proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0
|
||||
serverSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
|
||||
@@ -498,7 +480,6 @@ PageType {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 40
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
enabled: shareButtonEnabled
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
@@ -509,12 +490,8 @@ PageType {
|
||||
clickedFunc: function(){
|
||||
if (clientNameTextField.textFieldText !== "") {
|
||||
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
|
||||
} else{
|
||||
clientNameTextField.textField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: clientNameTextField.textField
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
@@ -528,7 +505,6 @@ PageType {
|
||||
actionButtonImage: "qrc:/images/controls/search.svg"
|
||||
actionButtonFunction: function() {
|
||||
root.isSearchBarVisible = true
|
||||
searchTextField.textField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +566,6 @@ PageType {
|
||||
anchors.leftMargin: -16
|
||||
|
||||
LabelWithButtonType {
|
||||
id: clientItem
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: clientName
|
||||
@@ -621,14 +596,6 @@ PageType {
|
||||
|
||||
spacing: 8
|
||||
|
||||
Connections {
|
||||
target: clientInfoDrawer
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
renameButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 24
|
||||
@@ -638,7 +605,6 @@ PageType {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: renameButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
|
||||
@@ -673,7 +639,6 @@ PageType {
|
||||
|
||||
Connections {
|
||||
target: clientNameEditDrawer
|
||||
enabled: !GC.isMobile()
|
||||
function onOpened() {
|
||||
clientNameEditor.textField.forceActiveFocus()
|
||||
}
|
||||
@@ -712,17 +677,12 @@ PageType {
|
||||
clientNameEditDrawer.close()
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: clientNameEditor.textField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: revokeButton
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: revokeButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
defaultColor: "transparent"
|
||||
@@ -749,8 +709,6 @@ PageType {
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
|
||||
KeyNavigation.tab: renameButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
defaultActiveFocusItem: shareButton
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -119,7 +117,6 @@ PageType {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: shareButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 40
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isControlsDisabled: false
|
||||
|
||||
Connections {
|
||||
target: PageController
|
||||
|
||||
@@ -34,11 +32,20 @@ PageType {
|
||||
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition)
|
||||
}
|
||||
|
||||
function onShowBusyIndicator(visible) {
|
||||
busyIndicator.visible = visible
|
||||
tabBarStackView.enabled = !visible
|
||||
tabBar.enabled = !visible
|
||||
}
|
||||
|
||||
function onDisableControls(disabled) {
|
||||
isControlsDisabled = disabled
|
||||
tabBar.enabled = !disabled
|
||||
}
|
||||
|
||||
function onClosePage() {
|
||||
tabBar.isServerInfoShow = tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsServerInfo)
|
||||
&& tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsSplitTunneling)
|
||||
|
||||
if (tabBarStackView.depth <= 1) {
|
||||
return
|
||||
}
|
||||
@@ -53,6 +60,8 @@ PageType {
|
||||
} else {
|
||||
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate)
|
||||
}
|
||||
|
||||
tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || PageEnum.PageSettingsSplitTunneling || tabBar.isServerInfoShow
|
||||
}
|
||||
|
||||
function onGoToStartPage() {
|
||||
@@ -63,7 +72,7 @@ PageType {
|
||||
}
|
||||
|
||||
function onEscapePressed() {
|
||||
if (root.isControlsDisabled) {
|
||||
if (!tabBar.enabled || busyIndicator.visible) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -113,7 +122,7 @@ PageType {
|
||||
}
|
||||
|
||||
function onNoInstalledContainers() {
|
||||
PageController.setTriggeredByConnectButton(true)
|
||||
PageController.setTriggeredBtConnectButton(true)
|
||||
|
||||
ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
|
||||
InstallController.setShouldCreateServer(false)
|
||||
@@ -132,14 +141,13 @@ PageType {
|
||||
width: parent.width
|
||||
height: root.height - tabBar.implicitHeight
|
||||
|
||||
enabled: !root.isControlsDisabled
|
||||
|
||||
function goToTabBarPage(page) {
|
||||
connectionTypeSelection.close()
|
||||
|
||||
var pagePath = PageController.getPagePath(page)
|
||||
tabBarStackView.clear(StackView.Immediate)
|
||||
tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate)
|
||||
tabBar.isServerInfoShow = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -153,6 +161,7 @@ PageType {
|
||||
id: tabBar
|
||||
|
||||
property int previousIndex: 0
|
||||
property bool isServerInfoShow: false
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
@@ -163,8 +172,6 @@ PageType {
|
||||
leftPadding: 96
|
||||
rightPadding: 96
|
||||
|
||||
enabled: !root.isControlsDisabled
|
||||
|
||||
background: Shape {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
@@ -185,7 +192,7 @@ PageType {
|
||||
}
|
||||
|
||||
TabImageButtonType {
|
||||
isSelected: tabBar.currentIndex === 0
|
||||
isSelected: tabBar.isServerInfoShow ? false : tabBar.currentIndex === 0
|
||||
image: "qrc:/images/controls/home.svg"
|
||||
onClicked: {
|
||||
tabBarStackView.goToTabBarPage(PageEnum.PageHome)
|
||||
@@ -219,7 +226,7 @@ PageType {
|
||||
}
|
||||
|
||||
TabImageButtonType {
|
||||
isSelected: tabBar.currentIndex === 2
|
||||
isSelected: tabBar.isServerInfoShow ? true : tabBar.currentIndex === 2
|
||||
image: "qrc:/images/controls/settings-2.svg"
|
||||
onClicked: {
|
||||
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
|
||||
@@ -236,6 +243,12 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicatorType {
|
||||
id: busyIndicator
|
||||
anchors.centerIn: parent
|
||||
z: 1
|
||||
}
|
||||
|
||||
ConnectionTypeSelectionDrawer {
|
||||
id: connectionTypeSelection
|
||||
|
||||
|
||||
@@ -86,11 +86,6 @@ Window {
|
||||
function onGoToPageSettingsBackup() {
|
||||
PageController.goToPage(PageEnum.PageSettingsBackup)
|
||||
}
|
||||
|
||||
function onShowBusyIndicator(visible) {
|
||||
busyIndicator.visible = visible
|
||||
PageController.disableControls(visible)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -220,16 +215,6 @@ Window {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
BusyIndicatorType {
|
||||
id: busyIndicator
|
||||
anchors.centerIn: parent
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
|
||||
function showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) {
|
||||
questionDrawer.headerText = headerText
|
||||
questionDrawer.descriptionText = descriptionText
|
||||
|
||||
@@ -270,7 +270,6 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerC
|
||||
ErrorCode *errorCode)
|
||||
{
|
||||
QJsonObject vpnConfiguration;
|
||||
vpnConfiguration[config_key::serverIndex] = serverIndex;
|
||||
|
||||
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
|
||||
auto s = m_settings->server(serverIndex);
|
||||
@@ -393,18 +392,11 @@ void VpnConnection::appendSplitTunnelingConfig()
|
||||
auto protocolName = m_vpnConfiguration.value(config_key::vpnproto).toString();
|
||||
if (protocolName == ProtocolProps::protoToString(Proto::Awg)) {
|
||||
auto configData = m_vpnConfiguration.value(protocolName + "_config_data").toObject();
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value("allowed_ips").toString().split(","));
|
||||
QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(","));
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value("allowed_ips").toString().split(", "));
|
||||
|
||||
if (allowedIpsJsonArray != defaultAllowedIP) {
|
||||
allowedIpsJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
|
||||
allowedIpsJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
|
||||
|
||||
m_vpnConfiguration.insert(config_key::splitTunnelType, Settings::RouteMode::VpnOnlyForwardSites);
|
||||
m_vpnConfiguration.insert(config_key::splitTunnelSites, allowedIpsJsonArray);
|
||||
|
||||
return;
|
||||
}
|
||||
configData[config_key::allowed_ips] = allowedIpsJsonArray;
|
||||
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,15 +464,10 @@ void VpnConnection::disconnectFromVpn()
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_vpnProtocol && m_vpnProtocol.data()) {
|
||||
auto *const connection = new QMetaObject::Connection;
|
||||
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
|
||||
[this, connection](AndroidController::ConnectionState state) {
|
||||
if (state == AndroidController::ConnectionState::DISCONNECTED) {
|
||||
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
disconnect(*connection);
|
||||
delete connection;
|
||||
}
|
||||
});
|
||||
connect(AndroidController::instance(), &AndroidController::vpnDisconnected, this,
|
||||
[this]() {
|
||||
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
}, Qt::SingleShotConnection);
|
||||
m_vpnProtocol.data()->stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user