mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-24 02:00:24 +07:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| acf8185ddb | |||
| aaf2c9ddeb | |||
| dbbc7119ec | |||
| c57162c4cc | |||
| 40e39895c9 | |||
| ec3ab2a03c | |||
| ddecfcad26 | |||
| 67bd880cdf | |||
| 477afb9d85 | |||
| f969fcdbb8 | |||
| b0ca16d861 | |||
| 9963359948 | |||
| ca639d293d | |||
| 83d045af64 | |||
| aea8ff4961 | |||
| 1892db4375 | |||
| c86a641e05 | |||
| befb2bf19a | |||
| 7ad6bc340c | |||
| 9164e38c34 | |||
| 8f7559f01b | |||
| af56200735 | |||
| 3874050fae | |||
| 3087163e34 | |||
| 1fa152845c | |||
| 50e23ef233 | |||
| ea648466de | |||
| b782775016 | |||
| 89a7fe1081 | |||
| e8bb096025 | |||
| fd5c7c8322 | |||
| e798d0f503 | |||
| bbb0abb596 | |||
| 0925aec86a | |||
| b084c4c284 | |||
| 87288ebccd | |||
| fcd7eadf4c | |||
| 0373338fb7 | |||
| 42f070fe9d | |||
| 02be6dc5f9 | |||
| bfcf7f0305 | |||
| 2bce595ade | |||
| cd1e561fd4 | |||
| 9bd1e6a0f5 | |||
| 5058c9aa6f |
+5
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.13.0)
|
set(AMNEZIAVPN_VERSION 4.8.14.5)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2106)
|
set(APP_ANDROID_VERSION_CODE 2118)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
@@ -61,6 +61,9 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
|||||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
||||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
||||||
|
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
|
||||||
|
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
|
||||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
||||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL v3.0
|
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Third-Party Licenses
|
||||||
|
|
||||||
|
This project is licensed under the GNU General Public License v3.0.
|
||||||
|
This file lists third-party software components used by this repository.
|
||||||
|
Each component is distributed under its own license as linked below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QtKeychain
|
||||||
|
|
||||||
|
- Source: https://github.com/frankosterfeld/qtkeychain
|
||||||
|
- License: BSD License
|
||||||
|
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QSimpleCrypto
|
||||||
|
|
||||||
|
- Source: https://github.com/n1flh31mur/QSimpleCrypto
|
||||||
|
- License: Apache License 2.0
|
||||||
|
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SortFilterProxyModel
|
||||||
|
|
||||||
|
- Source: https://github.com/oKcerG/SortFilterProxyModel
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QJsonStruct
|
||||||
|
|
||||||
|
- Source: https://github.com/Qv2ray/QJsonStruct
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QR Code Generator (qrcodegen)
|
||||||
|
|
||||||
|
- Source: https://github.com/nayuki/QR-Code-generator
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://www.nayuki.io/page/qr-code-generator-library
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Qt Gamepad
|
||||||
|
|
||||||
|
- Source: https://github.com/qt/qtgamepad
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||||
|
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AmneziaWG Apple (WireGuard)
|
||||||
|
|
||||||
|
- Source: https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AmneziaWG Android
|
||||||
|
|
||||||
|
- Source: https://github.com/amnezia-vpn/amneziawg-go
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Xray Core
|
||||||
|
|
||||||
|
- Source: https://github.com/XTLS/Xray-core
|
||||||
|
- License: Mozilla Public License 2.0 (MPL-2.0)
|
||||||
|
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cloak
|
||||||
|
|
||||||
|
- Source: https://github.com/cbeuw/Cloak
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||||
|
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shadowsocks
|
||||||
|
|
||||||
|
- Source: https://github.com/shadowsocks/shadowsocks-libev
|
||||||
|
- License: GPL-3.0-or-later
|
||||||
|
- License Text: http://www.gnu.org/licenses/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenSSL
|
||||||
|
|
||||||
|
- Source: https://github.com/openssl/openssl
|
||||||
|
- License: Apache License 2.0
|
||||||
|
- License Text: https://www.openssl.org/source/license.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## libssh
|
||||||
|
|
||||||
|
- Source: https://www.libssh.org/
|
||||||
|
- License: GNU Lesser General Public License (LGPL)
|
||||||
|
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenVPNAdapter
|
||||||
|
|
||||||
|
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
|
||||||
|
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
|
||||||
|
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wintun
|
||||||
|
|
||||||
|
- Source: https://www.wintun.net/
|
||||||
|
- License: Prebuilt Binaries License
|
||||||
|
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mullvad Split Tunnel Driver
|
||||||
|
|
||||||
|
- Source: https://github.com/mullvad/win-split-tunnel
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
|
||||||
|
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## tun2socks
|
||||||
|
|
||||||
|
- Source: https://github.com/eycorsican/go-tun2socks
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TAP-Windows Driver
|
||||||
|
|
||||||
|
- Source: https://github.com/OpenVPN/tap-windows6
|
||||||
|
- License: tap-windows6 license
|
||||||
|
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
|
||||||
+1
-1
Submodule client/3rd-prebuilt updated: b8c229288d...51bb4703a4
@@ -78,6 +78,7 @@ set(AMNEZIAVPN_TS_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
|
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
||||||
|
|
||||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,16 @@ void AmneziaApplication::init()
|
|||||||
// install filter on main window
|
// install filter on main window
|
||||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||||
win->installEventFilter(this);
|
win->installEventFilter(this);
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
QObject::connect(win, &QQuickWindow::sceneGraphError,
|
||||||
|
[](QQuickWindow::SceneGraphError, const QString &msg) {
|
||||||
|
qWarning() << "Scene graph error (suppressed):" << msg;
|
||||||
|
});
|
||||||
|
// Keep graphics context alive across hide/show cycles to avoid
|
||||||
|
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
|
||||||
|
win->setPersistentSceneGraph(true);
|
||||||
|
win->setPersistentGraphics(true);
|
||||||
|
#endif
|
||||||
win->show();
|
win->show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
|
|||||||
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
||||||
|
|
||||||
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
||||||
|
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
|
||||||
|
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
|
||||||
|
|
||||||
class AmneziaActivity : QtActivity() {
|
class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
@@ -90,10 +92,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
|
|
||||||
private var isActivityResumed = false
|
private var isActivityResumed = false
|
||||||
private var hasWindowFocus = false
|
private var hasWindowFocus = false
|
||||||
private val resumeHandler = Handler(Looper.getMainLooper())
|
private val resumeHandler = Handler(Looper.getMainLooper())
|
||||||
|
private var pendingOpenFileUri: String? = null
|
||||||
|
private var openFileDeliveryScheduled = false
|
||||||
|
|
||||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||||
object : Handler(Looper.getMainLooper()) {
|
object : Handler(Looper.getMainLooper()) {
|
||||||
@@ -196,11 +200,18 @@ class AmneziaActivity : QtActivity() {
|
|||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
registerBroadcastReceivers()
|
registerBroadcastReceivers()
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
runBlocking { vpnProto = proto.await() }
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadLibs() {
|
private fun loadLibs() {
|
||||||
listOf(
|
listOf(
|
||||||
"rsapss",
|
"rsapss",
|
||||||
@@ -270,6 +281,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
hasWindowFocus = false
|
hasWindowFocus = false
|
||||||
// Cancel all pending operations when activity stops
|
// Cancel all pending operations when activity stops
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
Log.d(TAG, "Stop Amnezia activity")
|
Log.d(TAG, "Stop Amnezia activity")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
@@ -283,7 +295,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onWindowFocusChanged(hasFocus)
|
super.onWindowFocusChanged(hasFocus)
|
||||||
hasWindowFocus = hasFocus
|
hasWindowFocus = hasFocus
|
||||||
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
||||||
|
|
||||||
// Cancel pending operations if window loses focus
|
// Cancel pending operations if window loses focus
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
@@ -291,35 +303,31 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
val deviceId = event.deviceId
|
|
||||||
val keyCode = event.keyCode
|
val keyCode = event.keyCode
|
||||||
val pressed = event.action == KeyEvent.ACTION_DOWN
|
val pressed = event.action == KeyEvent.ACTION_DOWN
|
||||||
val source = event.source
|
|
||||||
|
|
||||||
if (deviceId < 0 && pressed) {
|
when (keyCode) {
|
||||||
when (keyCode) {
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
KeyEvent.KEYCODE_BUTTON_A,
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
KeyEvent.KEYCODE_BUTTON_B,
|
KeyEvent.KEYCODE_BUTTON_X,
|
||||||
KeyEvent.KEYCODE_BUTTON_X,
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
KeyEvent.KEYCODE_BUTTON_Y,
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
KeyEvent.KEYCODE_BUTTON_START,
|
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
||||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
nativeGamepadKeyEvent(0, keyCode, pressed)
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
|
||||||
nativeGamepadKeyEvent(0, keyCode, true)
|
|
||||||
nativeGamepadKeyEvent(0, keyCode, false)
|
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
// Real gamepad events (deviceId >= 0)
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
if (deviceId >= 0) {
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
||||||
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
|
val synthetic = KeyEvent(
|
||||||
if (isGamepad || isJoystick || isDpad) {
|
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
||||||
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
|
event.repeatCount, event.metaState, -1, event.scanCode,
|
||||||
return true
|
event.flags, InputDevice.SOURCE_KEYBOARD
|
||||||
|
)
|
||||||
|
return super.dispatchKeyEvent(synthetic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +341,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
isActivityResumed = false
|
isActivityResumed = false
|
||||||
// Cancel all pending operations when activity pauses
|
// Cancel all pending operations when activity pauses
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
Log.d(TAG, "Pause Amnezia activity")
|
Log.d(TAG, "Pause Amnezia activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +350,21 @@ class AmneziaActivity : QtActivity() {
|
|||||||
isActivityResumed = true
|
isActivityResumed = true
|
||||||
Log.d(TAG, "Resume Amnezia activity")
|
Log.d(TAG, "Resume Amnezia activity")
|
||||||
|
|
||||||
|
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
|
||||||
|
val uri = pendingOpenFileUri!!
|
||||||
|
openFileDeliveryScheduled = true
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
pendingOpenFileUri = null
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onFileOpened(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
window.decorView.apply {
|
window.decorView.apply {
|
||||||
invalidate()
|
invalidate()
|
||||||
@@ -351,13 +375,13 @@ class AmneziaActivity : QtActivity() {
|
|||||||
sendTouch(1f, 1f)
|
sendTouch(1f, 1f)
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
resumeHandler.postDelayed({
|
resumeHandler.postDelayed({
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
sendTouch(2f, 2f)
|
sendTouch(2f, 2f)
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
resumeHandler.postDelayed({
|
resumeHandler.postDelayed({
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
requestLayout()
|
requestLayout()
|
||||||
@@ -403,25 +427,25 @@ class AmneziaActivity : QtActivity() {
|
|||||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
||||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
||||||
|
|
||||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
||||||
|
|
||||||
val density = resources.displayMetrics.density
|
val density = resources.displayMetrics.density
|
||||||
val imeHeightDp = (imeHeight / density).toInt()
|
val imeHeightDp = (imeHeight / density).toInt()
|
||||||
|
|
||||||
// Also track system bars (navigation bar, status bar) changes
|
// Also track system bars (navigation bar, status bar) changes
|
||||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
val navBarHeight = systemBarsInsets.bottom
|
val navBarHeight = systemBarsInsets.bottom
|
||||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
val navBarHeightDp = (navBarHeight / density).toInt()
|
||||||
val statusBarHeight = systemBarsInsets.top
|
val statusBarHeight = systemBarsInsets.top
|
||||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
||||||
|
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
qtInitialized.await()
|
qtInitialized.await()
|
||||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
||||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
// Return windowInsets instead of CONSUMED to allow proper handling
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
@@ -757,9 +781,13 @@ class AmneziaActivity : QtActivity() {
|
|||||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}?.toString() ?: ""
|
}?.toString() ?: ""
|
||||||
Log.v(TAG, "Open file: $uri")
|
Log.v(TAG, "Open file: $uri")
|
||||||
mainScope.launch {
|
if (uri.isNotEmpty()) {
|
||||||
qtInitialized.await()
|
pendingOpenFileUri = uri
|
||||||
QtAndroidController.onFileOpened(uri)
|
} else {
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onFileOpened(uri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -788,7 +816,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun getFd(fileName: String): Int {
|
fun getFd(fileName: String): Int {
|
||||||
Log.v(TAG, "Get fd for $fileName")
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
return blockingCall {
|
return blockingCall(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||||
pfd?.fd ?: -1
|
pfd?.fd ?: -1
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ class TvFilePicker : ComponentActivity() {
|
|||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
setResult(RESULT_OK, Intent().apply { data = it })
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
data = it
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
})
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
|
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
||||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
COMMENT "Signing OpenVPNAdapter framework"
|
COMMENT "Signing OpenVPNAdapter framework"
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ namespace apiDefs
|
|||||||
AmneziaFreeV3,
|
AmneziaFreeV3,
|
||||||
AmneziaPremiumV1,
|
AmneziaPremiumV1,
|
||||||
AmneziaPremiumV2,
|
AmneziaPremiumV2,
|
||||||
|
AmneziaTrialV2,
|
||||||
SelfHosted,
|
SelfHosted,
|
||||||
ExternalPremium
|
ExternalPremium,
|
||||||
|
ExternalTrial
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigSource {
|
enum ConfigSource {
|
||||||
|
|||||||
@@ -58,18 +58,24 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
|
|||||||
};
|
};
|
||||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
|
constexpr QLatin1String serviceTrial("amnezia-trial");
|
||||||
constexpr QLatin1String serviceFree("amnezia-free");
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
|
constexpr QLatin1String serviceExternalTrial("external-trial");
|
||||||
|
|
||||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
if (serviceType == servicePremium) {
|
if (serviceType == servicePremium) {
|
||||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
|
} else if (serviceType == serviceTrial) {
|
||||||
|
return apiDefs::ConfigType::AmneziaTrialV2;
|
||||||
} else if (serviceType == serviceFree) {
|
} else if (serviceType == serviceFree) {
|
||||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
} else if (serviceType == serviceExternalPremium) {
|
} else if (serviceType == serviceExternalPremium) {
|
||||||
return apiDefs::ConfigType::ExternalPremium;
|
return apiDefs::ConfigType::ExternalPremium;
|
||||||
|
} else if (serviceType == serviceExternalTrial) {
|
||||||
|
return apiDefs::ConfigType::ExternalTrial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -133,7 +139,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||||
apiDefs::ConfigType::ExternalPremium };
|
apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium,
|
||||||
|
apiDefs::ConfigType::ExternalTrial };
|
||||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +184,9 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
auto configType = apiUtils::getConfigType(serverConfigObject);
|
||||||
|
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2
|
||||||
|
&& configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ void CoreController::initControllers()
|
|||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||||
@@ -368,7 +368,11 @@ void CoreController::initPrepareConfigHandler()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_installController->isConfigValid()) {
|
m_installController->validateConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
|
||||||
|
if (!isValid) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,6 +337,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
} else {
|
} else {
|
||||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
}
|
}
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
|
|||||||
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
||||||
&NetworkWatcher::unsecuredNetwork);
|
&NetworkWatcher::unsecuredNetwork);
|
||||||
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
||||||
&NetworkWatcher::networkChange);
|
&NetworkWatcher::networkChanged);
|
||||||
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
|
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
|
||||||
&NetworkWatcher::onSleepMode);
|
&NetworkWatcher::wakeup);
|
||||||
m_impl->initialize();
|
m_impl->initialize();
|
||||||
|
|
||||||
// Enable sleep/wake monitoring for VPN auto-reconnection
|
// Enable sleep/wake monitoring for VPN auto-reconnection
|
||||||
@@ -97,12 +97,6 @@ void NetworkWatcher::settingsChanged() {
|
|||||||
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkWatcher::onSleepMode()
|
|
||||||
{
|
|
||||||
logger.debug() << "Resumed from sleep mode";
|
|
||||||
emit sleepMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||||
const QString& networkId) {
|
const QString& networkId) {
|
||||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||||
|
|||||||
@@ -29,13 +29,11 @@ public:
|
|||||||
// false to restore.
|
// false to restore.
|
||||||
void simulateDisconnection(bool simulatedDisconnection);
|
void simulateDisconnection(bool simulatedDisconnection);
|
||||||
|
|
||||||
void onSleepMode();
|
|
||||||
|
|
||||||
QNetworkInformation::Reachability getReachability();
|
QNetworkInformation::Reachability getReachability();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void networkChange();
|
void networkChanged();
|
||||||
void sleepMode();
|
void wakeup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void settingsChanged();
|
void settingsChanged();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ signals:
|
|||||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||||
// too.
|
// too.
|
||||||
void networkChanged(QString newBSSID);
|
void networkChanged(QString newBSSID);
|
||||||
void sleepMode();
|
void wakeup();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -126,8 +126,13 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
guard status == .reachableViaWiFi else { return }
|
switch status {
|
||||||
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
|
case .reachableViaWiFi, .reachableViaWWAN:
|
||||||
|
ovpnLog(.info, message: "Reachability changed, reconnecting OpenVPN session")
|
||||||
|
self?.ovpnAdapter?.reconnect(afterTimeInterval: 1)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
|
|||||||
@@ -21,6 +21,44 @@ extension Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PacketTunnelProvider {
|
extension PacketTunnelProvider {
|
||||||
|
private func applyXraySplitTunnel(_ xrayConfig: XrayConfig,
|
||||||
|
settings: NEPacketTunnelNetworkSettings) {
|
||||||
|
guard let splitTunnelType = xrayConfig.splitTunnelType else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let splitTunnelSites = xrayConfig.splitTunnelSites else {
|
||||||
|
xrayLog(.error, message: "Split tunnel sites are not set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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())"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||||
|
} else if splitTunnelType == 2 {
|
||||||
|
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||||
|
|
||||||
|
for excludedIPString in splitTunnelSites {
|
||||||
|
if let excludedIP = IPAddressRange(from: excludedIPString) {
|
||||||
|
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||||
|
destinationAddress: "\(excludedIP.address)",
|
||||||
|
subnetMask: "\(excludedIP.subnetMask())"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func startXray(completionHandler: @escaping (Error?) -> Void) {
|
func startXray(completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
|
||||||
// Xray configuration
|
// Xray configuration
|
||||||
@@ -72,6 +110,7 @@ extension PacketTunnelProvider {
|
|||||||
settings.dnsSettings = !dnsArray.isEmpty
|
settings.dnsSettings = !dnsArray.isEmpty
|
||||||
? NEDNSSettings(servers: dnsArray)
|
? NEDNSSettings(servers: dnsArray)
|
||||||
: NEDNSSettings(servers: ["1.1.1.1"])
|
: NEDNSSettings(servers: ["1.1.1.1"])
|
||||||
|
applyXraySplitTunnel(xrayConfig, settings: settings)
|
||||||
|
|
||||||
let xrayConfigData = xrayConfig.config.data(using: .utf8)
|
let xrayConfigData = xrayConfig.config.data(using: .utf8)
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
var ovpnAdapter: OpenVPNAdapter?
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
||||||
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
||||||
|
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
|
||||||
private let pathMonitor = NWPathMonitor()
|
private let pathMonitor = NWPathMonitor()
|
||||||
private var didReceiveInitialPathUpdate = false
|
private var didReceiveInitialPathUpdate = false
|
||||||
private var currentPath: Network.NWPath?
|
private var currentPath: Network.NWPath?
|
||||||
private var currentPathSignature: String?
|
private var currentPathSignature: String?
|
||||||
|
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
|
||||||
|
private var isApplyingNetworkChange = false
|
||||||
|
|
||||||
var splitTunnelType: Int?
|
var splitTunnelType: Int?
|
||||||
var splitTunnelSites: [String]?
|
var splitTunnelSites: [String]?
|
||||||
@@ -78,14 +81,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
||||||
|
|
||||||
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
|
// OpenVPN and WireGuard/AWG handle network changes internally.
|
||||||
if proto == .wireguard {
|
// Restarting them here can race their own reconnect logic and break tunnel setup.
|
||||||
|
if proto == .wireguard || proto == .openvpn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.scheduleNetworkChangeHandling(for: proto, path: path)
|
||||||
self.handle(networkChange: path) { _ in }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pathMonitor.start(queue: pathMonitorQueue)
|
pathMonitor.start(queue: pathMonitorQueue)
|
||||||
|
|
||||||
@@ -259,9 +261,47 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
|
guard protoType == .xray else {
|
||||||
|
updateActiveInterfaceIndex(for: changePath)
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updateActiveInterfaceIndex(for: changePath)
|
updateActiveInterfaceIndex(for: changePath)
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
reasserting = true
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
xrayLog(.info, message: "Applying network change to xray tunnel")
|
||||||
|
stopXray { }
|
||||||
|
startXray { [weak self] error in
|
||||||
|
self?.reasserting = false
|
||||||
|
completion(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scheduleNetworkChangeHandling(for proto: TunnelProtoType, path: Network.NWPath) {
|
||||||
|
guard proto == .xray else { return }
|
||||||
|
|
||||||
|
pendingNetworkChangeWorkItem?.cancel()
|
||||||
|
|
||||||
|
let workItem = DispatchWorkItem { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
if self.isApplyingNetworkChange {
|
||||||
|
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isApplyingNetworkChange = true
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.handle(networkChange: path) { [weak self] _ in
|
||||||
|
self?.networkChangeQueue.async {
|
||||||
|
self?.isApplyingNetworkChange = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingNetworkChangeWorkItem = workItem
|
||||||
|
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,91 @@
|
|||||||
#import "QtAppDelegate.h"
|
#import "QtAppDelegate.h"
|
||||||
#import "ios_controller.h"
|
#import "ios_controller.h"
|
||||||
|
#import <StoreKit/StoreKit.h>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr NSInteger kReviewRequestOpenInterval = 20;
|
||||||
|
NSString *const kAppOpenCountKey = @"AmneziaVPN.AppOpenCount";
|
||||||
|
BOOL gShouldRequestReviewOnBecomeActive = NO;
|
||||||
|
id gSceneWillEnterForegroundObserver = nil;
|
||||||
|
id gSceneDidActivateObserver = nil;
|
||||||
|
|
||||||
|
void scheduleAppStoreReviewIfNeededOnOpen()
|
||||||
|
{
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
const NSInteger openCount = [defaults integerForKey:kAppOpenCountKey] + 1;
|
||||||
|
[defaults setInteger:openCount forKey:kAppOpenCountKey];
|
||||||
|
|
||||||
|
gShouldRequestReviewOnBecomeActive =
|
||||||
|
(openCount > 0 && openCount % kReviewRequestOpenInterval == 0);
|
||||||
|
|
||||||
|
NSLog(@"[Review] scene open count=%ld interval=%ld scheduled=%@",
|
||||||
|
(long)openCount,
|
||||||
|
(long)kReviewRequestOpenInterval,
|
||||||
|
gShouldRequestReviewOnBecomeActive ? @"YES" : @"NO");
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestAppStoreReviewForScene(UIScene *scene)
|
||||||
|
{
|
||||||
|
if (@available(iOS 14.0, *)) {
|
||||||
|
if ([scene isKindOfClass:[UIWindowScene class]] &&
|
||||||
|
scene.activationState == UISceneActivationStateForegroundActive) {
|
||||||
|
[SKStoreReviewController requestReviewInScene:(UIWindowScene *)scene];
|
||||||
|
NSLog(@"[Review] requestReviewInScene invoked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@available(iOS 10.3, *)) {
|
||||||
|
[SKStoreReviewController requestReview];
|
||||||
|
NSLog(@"[Review] requestReview fallback invoked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupSceneReviewObservers()
|
||||||
|
{
|
||||||
|
if (gSceneWillEnterForegroundObserver || gSceneDidActivateObserver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||||
|
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
gSceneWillEnterForegroundObserver =
|
||||||
|
[center addObserverForName:UISceneWillEnterForegroundNotification
|
||||||
|
object:nil
|
||||||
|
queue:[NSOperationQueue mainQueue]
|
||||||
|
usingBlock:^(__unused NSNotification *note) {
|
||||||
|
scheduleAppStoreReviewIfNeededOnOpen();
|
||||||
|
}];
|
||||||
|
|
||||||
|
gSceneDidActivateObserver =
|
||||||
|
[center addObserverForName:UISceneDidActivateNotification
|
||||||
|
object:nil
|
||||||
|
queue:[NSOperationQueue mainQueue]
|
||||||
|
usingBlock:^(NSNotification *note) {
|
||||||
|
if (!gShouldRequestReviewOnBecomeActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gShouldRequestReviewOnBecomeActive = NO;
|
||||||
|
UIScene *scene = [note.object isKindOfClass:[UIScene class]] ? (UIScene *)note.object : nil;
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
requestAppStoreReviewForScene(scene);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||||
#if !MACOS_NE
|
#if !MACOS_NE
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||||
{
|
{
|
||||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||||
|
setupSceneReviewObservers();
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
NSLog(@"Application didFinishLaunchingWithOptions");
|
NSLog(@"Application didFinishLaunchingWithOptions");
|
||||||
return YES;
|
return YES;
|
||||||
|
|||||||
@@ -3,5 +3,7 @@ import Foundation
|
|||||||
struct XrayConfig: Decodable {
|
struct XrayConfig: Decodable {
|
||||||
let dns1: String?
|
let dns1: String?
|
||||||
let dns2: String?
|
let dns2: String?
|
||||||
|
let splitTunnelType: Int?
|
||||||
|
let splitTunnelSites: [String]?
|
||||||
let config: String
|
let config: String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -684,6 +684,15 @@ bool IosController::setupXray()
|
|||||||
QJsonObject finalConfig;
|
QJsonObject finalConfig;
|
||||||
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
|
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
|
||||||
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
|
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
|
||||||
|
finalConfig.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(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
finalConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||||
finalConfig.insert(config_key::config, xrayConfigStr);
|
finalConfig.insert(config_key::config, xrayConfigStr);
|
||||||
|
|
||||||
QJsonDocument finalConfigDoc(finalConfig);
|
QJsonDocument finalConfigDoc(finalConfig);
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
|
|||||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||||
|
|
||||||
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
|
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
|
||||||
&NetworkWatcherImpl::sleepMode);
|
&NetworkWatcherImpl::wakeup);
|
||||||
|
|
||||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||||
// This is not strictly needed, but it's better for user experience because
|
// This is not strictly needed, but it's better for user experience because
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
|
|||||||
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
||||||
{
|
{
|
||||||
if (state == NM_STATE_ASLEEP) {
|
if (state == NM_STATE_ASLEEP) {
|
||||||
emit sleepMode();
|
emit wakeup();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "NMStateChanged " << state;
|
logger.debug() << "NMStateChanged " << state;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||||
void sleepMode();
|
void wakeup();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|||||||
@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
|
|||||||
|
|
||||||
case kIOMessageSystemHasPoweredOn:
|
case kIOMessageSystemHasPoweredOn:
|
||||||
/* Announces that the system and its devices have woken up. */
|
/* Announces that the system and its devices have woken up. */
|
||||||
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
|
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
|
||||||
if (listener->m_watcher) {
|
if (listener->m_watcher) {
|
||||||
// Use QMetaObject::invokeMethod for thread-safe signal emission
|
// Use QMetaObject::invokeMethod for thread-safe signal emission
|
||||||
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
|
|||||||
switch (uMsg) {
|
switch (uMsg) {
|
||||||
case WM_POWERBROADCAST:
|
case WM_POWERBROADCAST:
|
||||||
if (wParam == PBT_APMRESUMESUSPEND) {
|
if (wParam == PBT_APMRESUMESUSPEND) {
|
||||||
emit obj->sleepMode();
|
emit obj->wakeup();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
m_impl.reset(new LocalSocketController());
|
m_impl.reset(new LocalSocketController());
|
||||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
setConnectionState(Vpn::ConnectionState::Connected);
|
||||||
});
|
});
|
||||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||||
[this](const QString& serverIpv4Gateway,
|
[this](const QString& serverIpv4Gateway,
|
||||||
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||||
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||||
m_impl->initialize(nullptr, nullptr);
|
m_impl->initialize(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ XrayProtocol::~XrayProtocol()
|
|||||||
ErrorCode XrayProtocol::start()
|
ErrorCode XrayProtocol::start()
|
||||||
{
|
{
|
||||||
qDebug() << "XrayProtocol::start()";
|
qDebug() << "XrayProtocol::start()";
|
||||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
|
||||||
|
|
||||||
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
|
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
|
||||||
@@ -69,7 +68,6 @@ ErrorCode XrayProtocol::start()
|
|||||||
void XrayProtocol::stop()
|
void XrayProtocol::stop()
|
||||||
{
|
{
|
||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
|
||||||
|
|
||||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
auto disableKillSwitch = iface->disableKillSwitch();
|
auto disableKillSwitch = iface->disableKillSwitch();
|
||||||
|
|||||||
+14
-21
@@ -35,13 +35,12 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_settings.setValue("Conf/encrypted", true);
|
m_settings.setValue("Conf/encrypted", true);
|
||||||
m_settings.sync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
|
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
if (m_cache.contains(key)) {
|
if (m_cache.contains(key)) {
|
||||||
return m_cache.value(key);
|
return m_cache.value(key);
|
||||||
@@ -85,7 +84,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
|
|||||||
|
|
||||||
void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
if (encryptionRequired() && encryptedKeys.contains(key)) {
|
if (encryptionRequired() && encryptedKeys.contains(key)) {
|
||||||
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
|
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
|
||||||
@@ -107,26 +106,20 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_cache.insert(key, value);
|
m_cache.insert(key, value);
|
||||||
sync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureQSettings::remove(const QString &key)
|
void SecureQSettings::remove(const QString &key)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
m_settings.remove(key);
|
m_settings.remove(key);
|
||||||
m_cache.remove(key);
|
m_cache.remove(key);
|
||||||
|
|
||||||
sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SecureQSettings::sync()
|
|
||||||
{
|
|
||||||
m_settings.sync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SecureQSettings::backupAppConfig() const
|
QByteArray SecureQSettings::backupAppConfig() const
|
||||||
{
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
QJsonObject cfg;
|
QJsonObject cfg;
|
||||||
|
|
||||||
const auto needToBackup = [this](const auto &key) {
|
const auto needToBackup = [this](const auto &key) {
|
||||||
@@ -161,6 +154,8 @@ QByteArray SecureQSettings::backupAppConfig() const
|
|||||||
|
|
||||||
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
||||||
{
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
QJsonObject cfg = QJsonDocument::fromJson(json).object();
|
QJsonObject cfg = QJsonDocument::fromJson(json).object();
|
||||||
if (cfg.isEmpty())
|
if (cfg.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
@@ -173,10 +168,16 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
|||||||
setValue(key, cfg.value(key).toVariant());
|
setValue(key, cfg.value(key).toVariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
sync();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SecureQSettings::clearSettings()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_settings.clear();
|
||||||
|
m_cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
|
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
|
||||||
{
|
{
|
||||||
QSimpleCrypto::QBlockCipher cipher;
|
QSimpleCrypto::QBlockCipher cipher;
|
||||||
@@ -294,11 +295,3 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
|
|||||||
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
|
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureQSettings::clearSettings()
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
m_settings.clear();
|
|
||||||
m_cache.clear();
|
|
||||||
sync();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ public:
|
|||||||
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
|
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
|
||||||
QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||||
Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
|
void setValue(const QString &key, const QVariant &value);
|
||||||
void remove(const QString &key);
|
void remove(const QString &key);
|
||||||
void sync();
|
|
||||||
|
|
||||||
QByteArray backupAppConfig() const;
|
QByteArray backupAppConfig() const;
|
||||||
bool restoreAppConfig(const QByteArray &json);
|
bool restoreAppConfig(const QByteArray &json);
|
||||||
|
|
||||||
|
void clearSettings();
|
||||||
|
|
||||||
|
private:
|
||||||
QByteArray encryptText(const QByteArray &value) const;
|
QByteArray encryptText(const QByteArray &value) const;
|
||||||
QByteArray decryptText(const QByteArray &ba) const;
|
QByteArray decryptText(const QByteArray &ba) const;
|
||||||
|
|
||||||
@@ -35,9 +37,6 @@ public:
|
|||||||
static QByteArray getSecTag(const QString &tag);
|
static QByteArray getSecTag(const QString &tag);
|
||||||
static void setSecTag(const QString &tag, const QByteArray &data);
|
static void setSecTag(const QString &tag, const QByteArray &data);
|
||||||
|
|
||||||
void clearSettings();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
|
|
||||||
mutable QHash<QString, QVariant> m_cache;
|
mutable QHash<QString, QVariant> m_cache;
|
||||||
@@ -53,7 +52,7 @@ private:
|
|||||||
|
|
||||||
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
|
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
|
||||||
|
|
||||||
mutable QMutex mutex;
|
mutable QRecursiveMutex m_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SECUREQSETTINGS_H
|
#endif // SECUREQSETTINGS_H
|
||||||
|
|||||||
+34
-57
@@ -21,10 +21,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
|
|||||||
{
|
{
|
||||||
// Import old settings
|
// Import old settings
|
||||||
if (serversCount() == 0) {
|
if (serversCount() == 0) {
|
||||||
QString user = value("Server/userName").toString();
|
QString user = m_settings.value("Server/userName").toString();
|
||||||
QString password = value("Server/password").toString();
|
QString password = m_settings.value("Server/password").toString();
|
||||||
QString serverName = value("Server/serverName").toString();
|
QString serverName = m_settings.value("Server/serverName").toString();
|
||||||
int port = value("Server/serverPort").toInt();
|
int port = m_settings.value("Server/serverPort").toInt();
|
||||||
|
|
||||||
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
|
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
|
||||||
QJsonObject server;
|
QJsonObject server;
|
||||||
@@ -222,7 +222,7 @@ QString Settings::nextAvailableServerName() const
|
|||||||
|
|
||||||
void Settings::setSaveLogs(bool enabled)
|
void Settings::setSaveLogs(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/saveLogs", enabled);
|
m_settings.setValue("Conf/saveLogs", enabled);
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
if (!isSaveLogs()) {
|
if (!isSaveLogs()) {
|
||||||
Logger::deInit();
|
Logger::deInit();
|
||||||
@@ -242,12 +242,12 @@ void Settings::setSaveLogs(bool enabled)
|
|||||||
|
|
||||||
QDateTime Settings::getLogEnableDate()
|
QDateTime Settings::getLogEnableDate()
|
||||||
{
|
{
|
||||||
return value("Conf/logEnableDate").toDateTime();
|
return m_settings.value("Conf/logEnableDate").toDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setLogEnableDate(QDateTime date)
|
void Settings::setLogEnableDate(QDateTime date)
|
||||||
{
|
{
|
||||||
setValue("Conf/logEnableDate", date);
|
m_settings.setValue("Conf/logEnableDate", date);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::routeModeString(RouteMode mode) const
|
QString Settings::routeModeString(RouteMode mode) const
|
||||||
@@ -261,17 +261,17 @@ QString Settings::routeModeString(RouteMode mode) const
|
|||||||
|
|
||||||
Settings::RouteMode Settings::routeMode() const
|
Settings::RouteMode Settings::routeMode() const
|
||||||
{
|
{
|
||||||
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
|
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isSitesSplitTunnelingEnabled() const
|
bool Settings::isSitesSplitTunnelingEnabled() const
|
||||||
{
|
{
|
||||||
return value("Conf/sitesSplitTunnelingEnabled", false).toBool();
|
return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
|
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/sitesSplitTunnelingEnabled", enabled);
|
m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
|
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
|
||||||
@@ -359,12 +359,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
|
|||||||
|
|
||||||
QString Settings::primaryDns() const
|
QString Settings::primaryDns() const
|
||||||
{
|
{
|
||||||
return value("Conf/primaryDns", cloudFlareNs1).toString();
|
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::secondaryDns() const
|
QString Settings::secondaryDns() const
|
||||||
{
|
{
|
||||||
return value("Conf/secondaryDns", cloudFlareNs2).toString();
|
return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::clearSettings()
|
void Settings::clearSettings()
|
||||||
@@ -386,18 +386,18 @@ QString Settings::appsRouteModeString(AppsRouteMode mode) const
|
|||||||
|
|
||||||
Settings::AppsRouteMode Settings::getAppsRouteMode() const
|
Settings::AppsRouteMode Settings::getAppsRouteMode() const
|
||||||
{
|
{
|
||||||
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt());
|
return static_cast<AppsRouteMode>(m_settings.value("Conf/appsRouteMode", 0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAppsRouteMode(AppsRouteMode mode)
|
void Settings::setAppsRouteMode(AppsRouteMode mode)
|
||||||
{
|
{
|
||||||
setValue("Conf/appsRouteMode", mode);
|
m_settings.setValue("Conf/appsRouteMode", mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
|
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
|
||||||
{
|
{
|
||||||
QVector<InstalledAppInfo> apps;
|
QVector<InstalledAppInfo> apps;
|
||||||
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray();
|
auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray();
|
||||||
for (const auto &app : appsArray) {
|
for (const auto &app : appsArray) {
|
||||||
InstalledAppInfo appInfo;
|
InstalledAppInfo appInfo;
|
||||||
appInfo.appName = app.toObject().value("appName").toString();
|
appInfo.appName = app.toObject().value("appName").toString();
|
||||||
@@ -419,43 +419,42 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &a
|
|||||||
appInfo.insert("appPath", app.appPath);
|
appInfo.insert("appPath", app.appPath);
|
||||||
appsArray.push_back(appInfo);
|
appsArray.push_back(appInfo);
|
||||||
}
|
}
|
||||||
setValue("Conf/" + appsRouteModeString(mode), appsArray);
|
m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray);
|
||||||
m_settings.sync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isAppsSplitTunnelingEnabled() const
|
bool Settings::isAppsSplitTunnelingEnabled() const
|
||||||
{
|
{
|
||||||
return value("Conf/appsSplitTunnelingEnabled", false).toBool();
|
return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
|
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/appsSplitTunnelingEnabled", enabled);
|
m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isKillSwitchEnabled() const
|
bool Settings::isKillSwitchEnabled() const
|
||||||
{
|
{
|
||||||
return value("Conf/killSwitchEnabled", true).toBool();
|
return m_settings.value("Conf/killSwitchEnabled", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setKillSwitchEnabled(bool enabled)
|
void Settings::setKillSwitchEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/killSwitchEnabled", enabled);
|
m_settings.setValue("Conf/killSwitchEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isStrictKillSwitchEnabled() const
|
bool Settings::isStrictKillSwitchEnabled() const
|
||||||
{
|
{
|
||||||
return value("Conf/strictKillSwitchEnabled", false).toBool();
|
return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setStrictKillSwitchEnabled(bool enabled)
|
void Settings::setStrictKillSwitchEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/strictKillSwitchEnabled", enabled);
|
m_settings.setValue("Conf/strictKillSwitchEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::getInstallationUuid(const bool needCreate)
|
QString Settings::getInstallationUuid(const bool needCreate)
|
||||||
{
|
{
|
||||||
auto uuid = value("Conf/installationUuid", "").toString();
|
auto uuid = m_settings.value("Conf/installationUuid", "").toString();
|
||||||
if (needCreate && uuid.isEmpty()) {
|
if (needCreate && uuid.isEmpty()) {
|
||||||
uuid = QUuid::createUuid().toString();
|
uuid = QUuid::createUuid().toString();
|
||||||
|
|
||||||
@@ -476,7 +475,7 @@ QString Settings::getInstallationUuid(const bool needCreate)
|
|||||||
|
|
||||||
void Settings::setInstallationUuid(const QString &uuid)
|
void Settings::setInstallationUuid(const QString &uuid)
|
||||||
{
|
{
|
||||||
setValue("Conf/installationUuid", uuid);
|
m_settings.setValue("Conf/installationUuid", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerCredentials Settings::defaultServerCredentials() const
|
ServerCredentials Settings::defaultServerCredentials() const
|
||||||
@@ -497,28 +496,6 @@ ServerCredentials Settings::serverCredentials(int index) const
|
|||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
|
|
||||||
{
|
|
||||||
QVariant returnValue;
|
|
||||||
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
|
|
||||||
returnValue = m_settings.value(key, defaultValue);
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(&m_settings, "value", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, returnValue),
|
|
||||||
Q_ARG(const QString &, key), Q_ARG(const QVariant &, defaultValue));
|
|
||||||
}
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setValue(const QString &key, const QVariant &value)
|
|
||||||
{
|
|
||||||
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
|
|
||||||
m_settings.setValue(key, value);
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(&m_settings, "setValue", Qt::BlockingQueuedConnection, Q_ARG(const QString &, key),
|
|
||||||
Q_ARG(const QVariant &, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::resetGatewayEndpoint()
|
void Settings::resetGatewayEndpoint()
|
||||||
{
|
{
|
||||||
m_gatewayEndpoint = gatewayEndpoint;
|
m_gatewayEndpoint = gatewayEndpoint;
|
||||||
@@ -541,50 +518,50 @@ QString Settings::getGatewayEndpoint(bool isTestPurchase)
|
|||||||
|
|
||||||
bool Settings::isDevGatewayEnv(bool isTestPurchase)
|
bool Settings::isDevGatewayEnv(bool isTestPurchase)
|
||||||
{
|
{
|
||||||
return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool();
|
return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::toggleDevGatewayEnv(bool enabled)
|
void Settings::toggleDevGatewayEnv(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/devGatewayEnv", enabled);
|
m_settings.setValue("Conf/devGatewayEnv", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isHomeAdLabelVisible()
|
bool Settings::isHomeAdLabelVisible()
|
||||||
{
|
{
|
||||||
return value("Conf/homeAdLabelVisible", true).toBool();
|
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::disableHomeAdLabel()
|
void Settings::disableHomeAdLabel()
|
||||||
{
|
{
|
||||||
setValue("Conf/homeAdLabelVisible", false);
|
m_settings.setValue("Conf/homeAdLabelVisible", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isPremV1MigrationReminderActive()
|
bool Settings::isPremV1MigrationReminderActive()
|
||||||
{
|
{
|
||||||
return value("Conf/premV1MigrationReminderActive", true).toBool();
|
return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::disablePremV1MigrationReminder()
|
void Settings::disablePremV1MigrationReminder()
|
||||||
{
|
{
|
||||||
setValue("Conf/premV1MigrationReminderActive", false);
|
m_settings.setValue("Conf/premV1MigrationReminderActive", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Settings::allowedDnsServers() const
|
QStringList Settings::allowedDnsServers() const
|
||||||
{
|
{
|
||||||
return value("Conf/allowedDnsServers").toStringList();
|
return m_settings.value("Conf/allowedDnsServers").toStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAllowedDnsServers(const QStringList &servers)
|
void Settings::setAllowedDnsServers(const QStringList &servers)
|
||||||
{
|
{
|
||||||
setValue("Conf/allowedDnsServers", servers);
|
m_settings.setValue("Conf/allowedDnsServers", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Settings::readNewsIds() const
|
QStringList Settings::readNewsIds() const
|
||||||
{
|
{
|
||||||
return value("News/readIds").toStringList();
|
return m_settings.value("News/readIds").toStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setReadNewsIds(const QStringList &ids)
|
void Settings::setReadNewsIds(const QStringList &ids)
|
||||||
{
|
{
|
||||||
setValue("News/readIds", ids);
|
m_settings.setValue("News/readIds", ids);
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-25
@@ -29,11 +29,11 @@ public:
|
|||||||
|
|
||||||
QJsonArray serversArray() const
|
QJsonArray serversArray() const
|
||||||
{
|
{
|
||||||
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array();
|
||||||
}
|
}
|
||||||
void setServersArray(const QJsonArray &servers)
|
void setServersArray(const QJsonArray &servers)
|
||||||
{
|
{
|
||||||
setValue("Servers/serversList", QJsonDocument(servers).toJson());
|
m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Servers section
|
// Servers section
|
||||||
@@ -45,11 +45,11 @@ public:
|
|||||||
|
|
||||||
int defaultServerIndex() const
|
int defaultServerIndex() const
|
||||||
{
|
{
|
||||||
return value("Servers/defaultServerIndex", 0).toInt();
|
return m_settings.value("Servers/defaultServerIndex", 0).toInt();
|
||||||
}
|
}
|
||||||
void setDefaultServer(int index)
|
void setDefaultServer(int index)
|
||||||
{
|
{
|
||||||
setValue("Servers/defaultServerIndex", index);
|
m_settings.setValue("Servers/defaultServerIndex", index);
|
||||||
}
|
}
|
||||||
QJsonObject defaultServer() const
|
QJsonObject defaultServer() const
|
||||||
{
|
{
|
||||||
@@ -78,34 +78,34 @@ public:
|
|||||||
// App settings section
|
// App settings section
|
||||||
bool isAutoConnect() const
|
bool isAutoConnect() const
|
||||||
{
|
{
|
||||||
return value("Conf/autoConnect", false).toBool();
|
return m_settings.value("Conf/autoConnect", false).toBool();
|
||||||
}
|
}
|
||||||
void setAutoConnect(bool enabled)
|
void setAutoConnect(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/autoConnect", enabled);
|
m_settings.setValue("Conf/autoConnect", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStartMinimized() const
|
bool isStartMinimized() const
|
||||||
{
|
{
|
||||||
return value("Conf/startMinimized", false).toBool();
|
return m_settings.value("Conf/startMinimized", false).toBool();
|
||||||
}
|
}
|
||||||
void setStartMinimized(bool enabled)
|
void setStartMinimized(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/startMinimized", enabled);
|
m_settings.setValue("Conf/startMinimized", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isNewsNotifications() const
|
bool isNewsNotifications() const
|
||||||
{
|
{
|
||||||
return value("Conf/newsNotifications", true).toBool();
|
return m_settings.value("Conf/newsNotifications", true).toBool();
|
||||||
}
|
}
|
||||||
void setNewsNotifications(bool enabled)
|
void setNewsNotifications(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/newsNotifications", enabled);
|
m_settings.setValue("Conf/newsNotifications", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSaveLogs() const
|
bool isSaveLogs() const
|
||||||
{
|
{
|
||||||
return value("Conf/saveLogs", false).toBool();
|
return m_settings.value("Conf/saveLogs", false).toBool();
|
||||||
}
|
}
|
||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
|
|
||||||
@@ -122,19 +122,18 @@ public:
|
|||||||
QString routeModeString(RouteMode mode) const;
|
QString routeModeString(RouteMode mode) const;
|
||||||
|
|
||||||
RouteMode routeMode() const;
|
RouteMode routeMode() const;
|
||||||
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
|
void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); }
|
||||||
|
|
||||||
bool isSitesSplitTunnelingEnabled() const;
|
bool isSitesSplitTunnelingEnabled() const;
|
||||||
void setSitesSplitTunnelingEnabled(bool enabled);
|
void setSitesSplitTunnelingEnabled(bool enabled);
|
||||||
|
|
||||||
QVariantMap vpnSites(RouteMode mode) const
|
QVariantMap vpnSites(RouteMode mode) const
|
||||||
{
|
{
|
||||||
return value("Conf/" + routeModeString(mode)).toMap();
|
return m_settings.value("Conf/" + routeModeString(mode)).toMap();
|
||||||
}
|
}
|
||||||
void setVpnSites(RouteMode mode, const QVariantMap &sites)
|
void setVpnSites(RouteMode mode, const QVariantMap &sites)
|
||||||
{
|
{
|
||||||
setValue("Conf/" + routeModeString(mode), sites);
|
m_settings.setValue("Conf/" + routeModeString(mode), sites);
|
||||||
m_settings.sync();
|
|
||||||
}
|
}
|
||||||
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
|
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
|
||||||
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
|
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
|
||||||
@@ -147,11 +146,11 @@ public:
|
|||||||
|
|
||||||
bool useAmneziaDns() const
|
bool useAmneziaDns() const
|
||||||
{
|
{
|
||||||
return value("Conf/useAmneziaDns", true).toBool();
|
return m_settings.value("Conf/useAmneziaDns", true).toBool();
|
||||||
}
|
}
|
||||||
void setUseAmneziaDns(bool enabled)
|
void setUseAmneziaDns(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/useAmneziaDns", enabled);
|
m_settings.setValue("Conf/useAmneziaDns", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString primaryDns() const;
|
QString primaryDns() const;
|
||||||
@@ -160,13 +159,13 @@ public:
|
|||||||
// QString primaryDns() const { return m_primaryDns; }
|
// QString primaryDns() const { return m_primaryDns; }
|
||||||
void setPrimaryDns(const QString &primaryDns)
|
void setPrimaryDns(const QString &primaryDns)
|
||||||
{
|
{
|
||||||
setValue("Conf/primaryDns", primaryDns);
|
m_settings.setValue("Conf/primaryDns", primaryDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// QString secondaryDns() const { return m_secondaryDns; }
|
// QString secondaryDns() const { return m_secondaryDns; }
|
||||||
void setSecondaryDns(const QString &secondaryDns)
|
void setSecondaryDns(const QString &secondaryDns)
|
||||||
{
|
{
|
||||||
setValue("Conf/secondaryDns", secondaryDns);
|
m_settings.setValue("Conf/secondaryDns", secondaryDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static constexpr char openNicNs5[] = "94.103.153.176";
|
// static constexpr char openNicNs5[] = "94.103.153.176";
|
||||||
@@ -188,16 +187,16 @@ public:
|
|||||||
};
|
};
|
||||||
void setAppLanguage(QLocale locale)
|
void setAppLanguage(QLocale locale)
|
||||||
{
|
{
|
||||||
setValue("Conf/appLanguage", locale.name());
|
m_settings.setValue("Conf/appLanguage", locale.name());
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isScreenshotsEnabled() const
|
bool isScreenshotsEnabled() const
|
||||||
{
|
{
|
||||||
return value("Conf/screenshotsEnabled", true).toBool();
|
return m_settings.value("Conf/screenshotsEnabled", true).toBool();
|
||||||
}
|
}
|
||||||
void setScreenshotsEnabled(bool enabled)
|
void setScreenshotsEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/screenshotsEnabled", enabled);
|
m_settings.setValue("Conf/screenshotsEnabled", enabled);
|
||||||
emit screenshotsEnabledChanged(enabled);
|
emit screenshotsEnabledChanged(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,9 +254,6 @@ signals:
|
|||||||
void settingsCleared();
|
void settingsCleared();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
|
||||||
void setValue(const QString &key, const QVariant &value);
|
|
||||||
|
|
||||||
void setInstallationUuid(const QString &uuid);
|
void setInstallationUuid(const QString &uuid);
|
||||||
|
|
||||||
mutable SecureQSettings m_settings;
|
mutable SecureQSettings m_settings;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -447,7 +447,7 @@ bool ApiConfigsController::importService()
|
|||||||
importSerivceFromAppStore();
|
importSerivceFromAppStore();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) {
|
||||||
importServiceFromGateway();
|
importServiceFromGateway();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/qrCodeUtils.h"
|
#include "core/qrCodeUtils.h"
|
||||||
#include "core/serialization/serialization.h"
|
#include "core/serialization/serialization.h"
|
||||||
@@ -170,8 +169,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
|
|||||||
m_config.append(line + "\n");
|
m_config.append(line + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
|
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
|
||||||
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
|
||||||
|
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
@@ -191,8 +189,7 @@ void ExportController::generateAwgConfig(const QString &clientName)
|
|||||||
m_config.append(line + "\n");
|
m_config.append(line + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
|
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
|
||||||
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
|
||||||
|
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
|
|
||||||
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
||||||
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||||
int s3 = QRandomGenerator::global()->bounded(0, 64);
|
int s3 = QRandomGenerator::global()->bounded(1, 64);
|
||||||
int s4 = QRandomGenerator::global()->bounded(0, 20);
|
int s4 = QRandomGenerator::global()->bounded(1, 20);
|
||||||
|
|
||||||
// Ensure all values are unique and don't create equal packet sizes
|
// Ensure all values are unique and don't create equal packet sizes
|
||||||
QSet<int> usedValues;
|
QSet<int> usedValues;
|
||||||
@@ -97,12 +97,12 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
|
|
||||||
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|
||||||
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
|
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
|
||||||
s3 = QRandomGenerator::global()->bounded(0, 64);
|
s3 = QRandomGenerator::global()->bounded(1, 64);
|
||||||
}
|
}
|
||||||
usedValues.insert(s3);
|
usedValues.insert(s3);
|
||||||
|
|
||||||
while (usedValues.contains(s4)) {
|
while (usedValues.contains(s4)) {
|
||||||
s4 = QRandomGenerator::global()->bounded(0, 20);
|
s4 = QRandomGenerator::global()->bounded(1, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString initPacketJunkSize = QString::number(s1);
|
QString initPacketJunkSize = QString::number(s1);
|
||||||
@@ -987,79 +987,94 @@ void InstallController::addEmptyServer()
|
|||||||
emit installServerFinished(tr("Server added successfully"));
|
emit installServerFinished(tr("Server added successfully"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstallController::isConfigValid()
|
void InstallController::validateConfig()
|
||||||
{
|
{
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
|
||||||
if (apiUtils::isServerFromApi(serverConfigObject)) {
|
if (apiUtils::isServerFromApi(serverConfigObject)) {
|
||||||
return true;
|
emit configValidated(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
emit noInstalledContainers();
|
emit noInstalledContainers();
|
||||||
return false;
|
emit configValidated(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
|
|
||||||
if (container == DockerContainer::None) {
|
if (container == DockerContainer::None) {
|
||||||
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
|
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
|
||||||
return false;
|
emit configValidated(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
|
||||||
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
|
||||||
|
|
||||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
|
|
||||||
QFutureWatcher<ErrorCode> watcher;
|
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
|
||||||
|
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
QString protocolConfig =
|
||||||
|
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
|
||||||
|
|
||||||
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
|
if (protocolConfig.isEmpty()) {
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
return false;
|
||||||
|
|
||||||
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
|
|
||||||
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QString protocolConfig =
|
|
||||||
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
|
|
||||||
|
|
||||||
if (protocolConfig.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isProtocolConfigExists(containerConfig, container)) {
|
|
||||||
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
|
||||||
errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
m_serversModel->updateContainerConfig(container, containerConfig);
|
|
||||||
|
|
||||||
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
|
|
||||||
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errorCode;
|
return true;
|
||||||
});
|
};
|
||||||
|
|
||||||
QEventLoop wait;
|
if (isProtocolConfigExists(containerConfig, container)) {
|
||||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
emit configValidated(true);
|
||||||
watcher.setFuture(future);
|
return;
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
ErrorCode errorCode = watcher.result();
|
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit installationErrorOccurred(errorCode);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
struct ValidationResult {
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
QJsonObject containerConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
QFuture<ValidationResult> future =
|
||||||
|
QtConcurrent::run([settings = m_settings, serverController, credentials, containerConfig, container]() mutable {
|
||||||
|
ValidationResult result;
|
||||||
|
result.containerConfig = containerConfig;
|
||||||
|
|
||||||
|
VpnConfigurationsController vpnConfigurationController(settings, serverController);
|
||||||
|
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container,
|
||||||
|
result.containerConfig);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto *watcher = new QFutureWatcher<ValidationResult>(this);
|
||||||
|
connect(watcher, &QFutureWatcher<ValidationResult>::finished, this,
|
||||||
|
[this, watcher, container, credentials, serverController]() {
|
||||||
|
auto result = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
|
||||||
|
if (result.errorCode != ErrorCode::NoError) {
|
||||||
|
emit installationErrorOccurred(result.errorCode);
|
||||||
|
emit configValidated(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_serversModel->updateContainerConfig(container, result.containerConfig);
|
||||||
|
|
||||||
|
ErrorCode appendError = m_clientManagementModel->appendClient(
|
||||||
|
container, credentials, result.containerConfig,
|
||||||
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
||||||
|
|
||||||
|
if (appendError != ErrorCode::NoError) {
|
||||||
|
emit installationErrorOccurred(appendError);
|
||||||
|
emit configValidated(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit configValidated(true);
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
|
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
|
||||||
@@ -1070,7 +1085,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
|
|||||||
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
|
|
||||||
if (container == DockerContainer::Awg2) {
|
if (ContainerProps::isAwgContainer(container)) {
|
||||||
const AwgConfig oldConfig(oldProtoConfig);
|
const AwgConfig oldConfig(oldProtoConfig);
|
||||||
const AwgConfig newConfig(newProtoConfig);
|
const AwgConfig newConfig(newProtoConfig);
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ public slots:
|
|||||||
|
|
||||||
void addEmptyServer();
|
void addEmptyServer();
|
||||||
|
|
||||||
bool isConfigValid();
|
void validateConfig();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void configValidated(bool isValid);
|
||||||
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
|
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
|
||||||
void installServerFinished(const QString &finishMessage);
|
void installServerFinished(const QString &finishMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -178,12 +178,11 @@ void SettingsController::backupAppConfig(const QString &fileName)
|
|||||||
|
|
||||||
void SettingsController::restoreAppConfig(const QString &fileName)
|
void SettingsController::restoreAppConfig(const QString &fileName)
|
||||||
{
|
{
|
||||||
QFile file(fileName);
|
QByteArray data;
|
||||||
|
if (!SystemController::readFile(fileName, data)) {
|
||||||
file.open(QIODevice::ReadOnly);
|
emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName));
|
||||||
|
return;
|
||||||
QByteArray data = file.readAll();
|
}
|
||||||
|
|
||||||
restoreAppConfigFromData(data);
|
restoreAppConfigFromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#include "core/networkUtilities.h"
|
#include "core/networkUtilities.h"
|
||||||
|
|
||||||
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
|
SitesController::SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
|
||||||
const QSharedPointer<VpnConnection> &vpnConnection,
|
: QObject(parent), m_settings(settings), m_sitesModel(sitesModel)
|
||||||
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
|
|
||||||
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,32 +32,20 @@ void SitesController::addSite(QString hostname)
|
|||||||
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
|
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &processSite = [this](const QString &hostname, const QString &ip) {
|
const auto &resolveCallback = [this](const QHostInfo &hostInfo) {
|
||||||
m_sitesModel->addSite(hostname, ip);
|
|
||||||
|
|
||||||
if (!ip.isEmpty()) {
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
|
||||||
Q_ARG(QStringList, QStringList() << ip));
|
|
||||||
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
|
||||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||||
for (const QHostAddress &addr : hostInfo.addresses()) {
|
for (const QHostAddress &addr : hostInfo.addresses()) {
|
||||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||||
processSite(hostInfo.hostName(), addr.toString());
|
m_sitesModel->addSite(hostInfo.hostName(), addr.toString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||||
processSite(hostname, "");
|
m_sitesModel->addSite(hostname, "");
|
||||||
} else {
|
} else {
|
||||||
processSite(hostname, "");
|
m_sitesModel->addSite(hostname, "");
|
||||||
QHostInfo::lookupHost(hostname, this, resolveCallback);
|
QHostInfo::lookupHost(hostname, this, resolveCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +58,6 @@ void SitesController::removeSite(int index)
|
|||||||
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
|
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
|
||||||
m_sitesModel->removeSite(modelIndex);
|
m_sitesModel->removeSite(modelIndex);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
|
||||||
Q_ARG(QStringList, QStringList() << hostname));
|
|
||||||
|
|
||||||
emit finished(tr("Site removed: %1").arg(hostname));
|
emit finished(tr("Site removed: %1").arg(hostname));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +111,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
|||||||
|
|
||||||
m_sitesModel->addSites(sites, replaceExisting);
|
m_sitesModel->addSites(sites, replaceExisting);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
|
||||||
|
|
||||||
emit finished(tr("Import completed"));
|
emit finished(tr("Import completed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ class SitesController : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SitesController(const std::shared_ptr<Settings> &settings,
|
explicit SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel,
|
||||||
const QSharedPointer<VpnConnection> &vpnConnection,
|
QObject *parent = nullptr);
|
||||||
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void addSite(QString hostname);
|
void addSite(QString hostname);
|
||||||
@@ -31,8 +30,6 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium;
|
|| m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|
||||||
|
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium
|
||||||
|
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
|
||||||
}
|
}
|
||||||
case HasExpiredWorkerRole: {
|
case HasExpiredWorkerRole: {
|
||||||
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace
|
|||||||
{
|
{
|
||||||
constexpr char amneziaFree[] = "amnezia-free";
|
constexpr char amneziaFree[] = "amnezia-free";
|
||||||
constexpr char amneziaPremium[] = "amnezia-premium";
|
constexpr char amneziaPremium[] = "amnezia-premium";
|
||||||
|
constexpr char amneziaTrial[] = "amnezia-trial";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
auto speed = apiServiceData.serviceInfo.speed;
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium || serviceType == serviceType::amneziaTrial) {
|
||||||
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = apiServiceData.serviceInfo.cardDescription;
|
QString description = apiServiceData.serviceInfo.cardDescription;
|
||||||
@@ -124,8 +125,10 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
case OrderRole: {
|
case OrderRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaTrial) {
|
||||||
return 1;
|
return 1;
|
||||||
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,10 +141,12 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
|
|||||||
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
|
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
|
||||||
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
|
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
|
||||||
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
|
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
|
||||||
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
|
if (protocolVersion == protocols::awg::awgV2) {
|
||||||
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
|
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
|
||||||
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
|
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
|
||||||
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
|
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
|
||||||
|
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
|
||||||
|
}
|
||||||
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
|
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
|
||||||
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
|
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
|
||||||
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
|
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property StackView stackView: StackView.view
|
property StackView stackView: StackView.view
|
||||||
|
property bool enableTimer: true
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible && enableTimer) {
|
||||||
timer.start()
|
timer.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,6 @@ Item {
|
|||||||
FocusController.setFocusOnDefaultItem()
|
FocusController.setFocusOnDefaultItem()
|
||||||
}
|
}
|
||||||
repeat: false // Stop the timer after one trigger
|
repeat: false // Stop the timer after one trigger
|
||||||
running: true // Start the timer
|
running: enableTimer // Start the timer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,6 +330,8 @@ PageType {
|
|||||||
AwgTextField {
|
AwgTextField {
|
||||||
id: cookieReplyPacketJunkSizeTextField
|
id: cookieReplyPacketJunkSizeTextField
|
||||||
|
|
||||||
|
visible: isAwg2
|
||||||
|
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
@@ -342,6 +344,8 @@ PageType {
|
|||||||
AwgTextField {
|
AwgTextField {
|
||||||
id: transportPacketJunkSizeTextField
|
id: transportPacketJunkSizeTextField
|
||||||
|
|
||||||
|
visible: isAwg2
|
||||||
|
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
|||||||
@@ -396,9 +396,7 @@ PageType {
|
|||||||
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
|
||||||
} else {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice(true)) {
|
InstallController.removeProcessedServer()
|
||||||
InstallController.removeProcessedServer()
|
|
||||||
}
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ PageType {
|
|||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
text: qsTr("News Notification")
|
text: qsTr("News Notification")
|
||||||
descriptionText: qsTr("Show notification icon when has unread news")
|
descriptionText: qsTr("Show a notification icon for unread news")
|
||||||
|
|
||||||
checked: SettingsController.isNewsNotificationsEnabled()
|
checked: SettingsController.isNewsNotificationsEnabled()
|
||||||
onToggled: function() {
|
onToggled: function() {
|
||||||
|
|||||||
@@ -1,226 +1,226 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
import PageEnum 1.0
|
import PageEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
import "./"
|
import "./"
|
||||||
import "../Controls2"
|
import "../Controls2"
|
||||||
import "../Controls2/TextTypes"
|
import "../Controls2/TextTypes"
|
||||||
import "../Config"
|
import "../Config"
|
||||||
import "../Components"
|
import "../Components"
|
||||||
|
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
listView.positionViewAtBeginning()
|
listView.positionViewAtBeginning()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButton.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
header: ColumnLayout {
|
header: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
BaseHeaderType {
|
BaseHeaderType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.bottomMargin: 32
|
Layout.bottomMargin: 32
|
||||||
|
|
||||||
headerText: ApiServicesModel.getSelectedServiceData("name")
|
headerText: ApiServicesModel.getSelectedServiceData("name")
|
||||||
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
|
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model: inputFields
|
model: inputFields
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
LabelWithImageType {
|
LabelWithImageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
imageSource: imagePath
|
imageSource: imagePath
|
||||||
leftText: lText
|
leftText: lText
|
||||||
rightText: rText
|
rightText: rText
|
||||||
|
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: ColumnLayout {
|
footer: ColumnLayout {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
text: {
|
text: {
|
||||||
var text = ApiServicesModel.getSelectedServiceData("features")
|
var text = ApiServicesModel.getSelectedServiceData("features")
|
||||||
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
|
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
color: AmneziaStyle.color.mutedGray
|
color: AmneziaStyle.color.mutedGray
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
|
||||||
text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.")
|
text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.")
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: continueButton
|
id: continueButton
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: 32
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect")
|
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : (ApiServicesModel.getSelectedServiceType() === "amnezia-trial" ? qsTr("Try Trial") : qsTr("Connect"))
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
var result = ApiConfigsController.importService()
|
var result = ApiConfigsController.importService()
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
var endpoint = ApiServicesModel.getStoreEndpoint()
|
var endpoint = ApiServicesModel.getStoreEndpoint()
|
||||||
Qt.openUrlExternally(endpoint)
|
Qt.openUrlExternally(endpoint)
|
||||||
PageController.closePage()
|
PageController.closePage()
|
||||||
PageController.closePage()
|
PageController.closePage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 32
|
Layout.bottomMargin: 32
|
||||||
|
|
||||||
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: AmneziaStyle.color.mutedGray
|
color: AmneziaStyle.color.mutedGray
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
||||||
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
|
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
|
||||||
return qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").arg(termsUrl).arg(privacyUrl)
|
return qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").arg(termsUrl).arg(privacyUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkActivated: function(link) {
|
onLinkActivated: function(link) {
|
||||||
Qt.openUrlExternally(link)
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property list<QtObject> inputFields: [
|
property list<QtObject> inputFields: [
|
||||||
region,
|
region,
|
||||||
price,
|
price,
|
||||||
timeLimit,
|
timeLimit,
|
||||||
speed,
|
speed,
|
||||||
features
|
features
|
||||||
]
|
]
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: region
|
id: region
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
|
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
|
||||||
readonly property string lText: qsTr("For the region")
|
readonly property string lText: qsTr("For the region")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: price
|
id: price
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/tag.svg"
|
readonly property string imagePath: "qrc:/images/controls/tag.svg"
|
||||||
readonly property string lText: qsTr("Price")
|
readonly property string lText: qsTr("Price")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: timeLimit
|
id: timeLimit
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/history.svg"
|
readonly property string imagePath: "qrc:/images/controls/history.svg"
|
||||||
readonly property string lText: qsTr("Work period")
|
readonly property string lText: qsTr("Work period")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
|
||||||
property bool isVisible: rText !== ""
|
property bool isVisible: rText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: speed
|
id: speed
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
|
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
|
||||||
readonly property string lText: qsTr("Speed")
|
readonly property string lText: qsTr("Speed")
|
||||||
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
|
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: features
|
id: features
|
||||||
|
|
||||||
readonly property string imagePath: "qrc:/images/controls/info.svg"
|
readonly property string imagePath: "qrc:/images/controls/info.svg"
|
||||||
readonly property string lText: qsTr("Features")
|
readonly property string lText: qsTr("Features")
|
||||||
readonly property string rText: ""
|
readonly property string rText: ""
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,11 +79,23 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textField.onTextChanged: {
|
textField.onTextChanged: {
|
||||||
if (headerText == qsTr("Password or SSH private key")) {
|
if (headerText === qsTr("Password or SSH private key")) {
|
||||||
buttonImageSource = textField.text !== "" ? imageSource : ""
|
buttonImageSource = textField.text !== "" ? imageSource : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WarningType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 8
|
||||||
|
|
||||||
|
visible: title === qsTr("Password or SSH private key")
|
||||||
|
backGroundColor: AmneziaStyle.color.translucentWhite
|
||||||
|
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||||
|
textString: qsTr("SSH key requirements: supported ED25519 or RSA in PEM. Paste the private key including BEGIN/END lines. If your key doesn’t work, generate a compatible one.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: ColumnLayout {
|
footer: ColumnLayout {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import "../Components"
|
|||||||
|
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
enableTimer: (SettingsController.isOnTv()) ? false : true
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
@@ -45,4 +46,22 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 250
|
||||||
|
running: SettingsController.isOnTv()
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
startButton.forceActiveFocus()
|
||||||
|
if (startButton.activeFocus) {
|
||||||
|
running = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible && SettingsController.isOnTv()) {
|
||||||
|
startButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,7 +505,13 @@ PageType {
|
|||||||
exportTypeSelector.currentIndex = 0
|
exportTypeSelector.currentIndex = 0
|
||||||
}
|
}
|
||||||
selectedIndex = exportTypeSelector.currentIndex
|
selectedIndex = exportTypeSelector.currentIndex
|
||||||
exportTypeSelector.text = selectedText
|
if (model.length > 0 && model[selectedIndex] && model[selectedIndex].name !== undefined) {
|
||||||
|
exportTypeSelectorListView.selectedText = model[selectedIndex].name
|
||||||
|
exportTypeSelector.text = model[selectedIndex].name
|
||||||
|
} else {
|
||||||
|
exportTypeSelectorListView.selectedText = ""
|
||||||
|
exportTypeSelector.text = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootWidth: root.width
|
rootWidth: root.width
|
||||||
|
|||||||
@@ -278,7 +278,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function(event) {
|
||||||
console.debug(">>>> ", event.key, " Event is caught by StartPage")
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Tab:
|
case Qt.Key_Tab:
|
||||||
case Qt.Key_Down:
|
case Qt.Key_Down:
|
||||||
@@ -304,7 +303,7 @@ PageType {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
// Also adjust TabBar position when keyboard appears (Android 14+ workaround)
|
// Also adjust TabBar position when keyboard appears (Android 14+ workaround)
|
||||||
anchors.bottomMargin: SettingsController.imeHeight
|
anchors.bottomMargin: SettingsController.imeHeight
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -21,10 +21,14 @@ Window {
|
|||||||
function onStateChanged() {
|
function onStateChanged() {
|
||||||
if (Qt.platform.os === "android") {
|
if (Qt.platform.os === "android") {
|
||||||
if (Qt.application.state === Qt.ApplicationActive) {
|
if (Qt.application.state === Qt.ApplicationActive) {
|
||||||
|
root.visible = true
|
||||||
refreshTimer.restart()
|
refreshTimer.restart()
|
||||||
} else if (Qt.application.state === Qt.ApplicationSuspended ||
|
} else if (Qt.application.state === Qt.ApplicationSuspended) {
|
||||||
Qt.application.state === Qt.ApplicationInactive) {
|
// Hide window to stop the Qt render loop and prevent
|
||||||
console.log("QML: Application going to background, state:", Qt.application.state)
|
// eglSwapBuffers from being called on a lost EGL context.
|
||||||
|
// NOTE: Do NOT hide on ApplicationInactive — that fires on any
|
||||||
|
// focus change (IME, notifications) and would blank the screen.
|
||||||
|
root.visible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +60,11 @@ Window {
|
|||||||
PageController.closeWindow()
|
PageController.closeWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSceneGraphError: function(error, message) {
|
||||||
|
// Prevent qFatal crash on Android when EGL context is lost
|
||||||
|
console.warn("Scene graph error:", error, message)
|
||||||
|
}
|
||||||
|
|
||||||
title: "AmneziaVPN"
|
title: "AmneziaVPN"
|
||||||
|
|
||||||
Item { // This item is needed for focus handling
|
Item { // This item is needed for focus handling
|
||||||
|
|||||||
+99
-124
@@ -39,9 +39,8 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings, QObject *parent
|
|||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
m_checkTimer.setInterval(1000);
|
m_checkTimer.setInterval(1000);
|
||||||
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged);
|
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::setConnectionState);
|
||||||
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
|
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
|
|||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
|
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
|
||||||
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled);
|
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled);
|
||||||
if (reply.waitForFinished(1000) && reply.returnValue())
|
if (reply.waitForFinished() && reply.returnValue())
|
||||||
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
|
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
|
||||||
else
|
else
|
||||||
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call";
|
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call";
|
||||||
@@ -73,40 +72,57 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
|||||||
auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
|
auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
|
||||||
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
if (state == Vpn::ConnectionState::Connected) {
|
switch (state) {
|
||||||
iface->resetIpStack();
|
case Vpn::ConnectionState::Connected: {
|
||||||
iface->flushDns();
|
iface->resetIpStack();
|
||||||
|
|
||||||
if (!ContainerProps::isAwgContainer(container) &&
|
auto flushDns = iface->flushDns();
|
||||||
container != DockerContainer::WireGuard) {
|
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||||
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
|
||||||
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
else
|
||||||
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
|
||||||
|
|
||||||
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
|
||||||
|
|
||||||
if (m_settings->isSitesSplitTunnelingEnabled()) {
|
if (!ContainerProps::isAwgContainer(container) &&
|
||||||
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
|
container != DockerContainer::WireGuard) {
|
||||||
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
|
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
||||||
QTimer::singleShot(1000, m_vpnProtocol.data(),
|
|
||||||
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
|
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
|
||||||
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
|
|
||||||
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
|
|
||||||
|
|
||||||
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
|
// TODO: add error code handling for all routeAddList (or rework the code below)
|
||||||
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||||
|
|
||||||
|
if (m_settings->isSitesSplitTunnelingEnabled()) {
|
||||||
|
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
|
||||||
|
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
|
||||||
|
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
|
QTimer::singleShot(1000, m_vpnProtocol.data(),
|
||||||
|
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
|
||||||
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
|
||||||
|
|
||||||
|
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
|
||||||
|
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} break;
|
||||||
} else if (state == Vpn::ConnectionState::Error) {
|
case Vpn::ConnectionState::Disconnected:
|
||||||
iface->flushDns();
|
case Vpn::ConnectionState::Error: {
|
||||||
|
auto flushDns = iface->flushDns();
|
||||||
|
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||||
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
|
||||||
|
else
|
||||||
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
|
||||||
|
|
||||||
if (m_settings->isSitesSplitTunnelingEnabled()) {
|
auto clearSavedRoutes = iface->clearSavedRoutes();
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
|
||||||
iface->clearSavedRoutes();
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
|
||||||
}
|
else
|
||||||
}
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
@@ -120,7 +136,6 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
|||||||
m_checkTimer.stop();
|
m_checkTimer.stop();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
emit connectionStateChanged(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &VpnConnection::remoteAddress() const
|
const QString &VpnConnection::remoteAddress() const
|
||||||
@@ -165,7 +180,11 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode)
|
|||||||
});
|
});
|
||||||
m_settings->addVpnSite(mode, site, ip);
|
m_settings->addVpnSite(mode, site, ip);
|
||||||
}
|
}
|
||||||
flushDns();
|
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
auto reply = iface->flushDns();
|
||||||
|
if (reply.waitForFinished() || !reply.returnValue())
|
||||||
|
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,48 +199,6 @@ QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
|
|||||||
return m_vpnProtocol;
|
return m_vpnProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::addRoutes(const QStringList &ips)
|
|
||||||
{
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
if (connectionState() == Vpn::ConnectionState::Connected) {
|
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
|
||||||
iface->routeAddList(m_vpnProtocol->vpnGateway(), ips);
|
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
|
||||||
iface->routeAddList(m_vpnProtocol->routeGateway(), ips);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::deleteRoutes(const QStringList &ips)
|
|
||||||
{
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
if (connectionState() == Vpn::ConnectionState::Connected) {
|
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
|
||||||
iface->routeDeleteList(vpnProtocol()->vpnGateway(), ips);
|
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
|
||||||
iface->routeDeleteList(m_vpnProtocol->routeGateway(), ips);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::flushDns()
|
|
||||||
{
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
auto reply = iface->flushDns();
|
|
||||||
if (reply.waitForFinished(1000) || !reply.returnValue()) {
|
|
||||||
qWarning() << "VpnConnection::flushDns(): Failed to flush DNS";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::disconnectSlots()
|
void VpnConnection::disconnectSlots()
|
||||||
{
|
{
|
||||||
if (m_vpnProtocol) {
|
if (m_vpnProtocol) {
|
||||||
@@ -251,7 +228,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
|||||||
<< m_settings->routeMode();
|
<< m_settings->routeMode();
|
||||||
|
|
||||||
m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName);
|
m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Connecting);
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
|
|
||||||
m_vpnConfiguration = vpnConfiguration;
|
m_vpnConfiguration = vpnConfiguration;
|
||||||
|
|
||||||
@@ -269,7 +246,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
|||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
||||||
if (!m_vpnProtocol) {
|
if (!m_vpnProtocol) {
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_vpnProtocol->prepare();
|
m_vpnProtocol->prepare();
|
||||||
@@ -287,17 +264,24 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
|
|||||||
|
|
||||||
createProtocolConnections();
|
createProtocolConnections();
|
||||||
|
|
||||||
ErrorCode errorCode = m_vpnProtocol->start();
|
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||||
if (errorCode != ErrorCode::NoError)
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
emit vpnProtocolError(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::createProtocolConnections()
|
void VpnConnection::createProtocolConnections()
|
||||||
{
|
{
|
||||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this,
|
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
|
||||||
SLOT(onConnectionStateChanged(Vpn::ConnectionState)));
|
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||||
|
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
|
||||||
|
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||||
|
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::appendKillSwitchConfig()
|
void VpnConnection::appendKillSwitchConfig()
|
||||||
@@ -439,6 +423,27 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
|
|||||||
return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s
|
return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VpnConnection::reconnectToVpn() {
|
||||||
|
if (m_vpnProtocol.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_connectionState != Vpn::ConnectionState::Connected) {
|
||||||
|
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
|
||||||
|
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Reconnect triggered. Reconnecting to the server";
|
||||||
|
|
||||||
|
setConnectionState(Vpn::ConnectionState::Reconnecting);
|
||||||
|
|
||||||
|
m_vpnProtocol->stop();
|
||||||
|
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||||
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
|
emit vpnProtocolError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VpnConnection::disconnectFromVpn()
|
void VpnConnection::disconnectFromVpn()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
@@ -448,41 +453,26 @@ void VpnConnection::disconnectFromVpn()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_vpnProtocol.isNull()) {
|
if (m_vpnProtocol.isNull()) {
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_vpnProtocol->stop();
|
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
QRemoteObjectPendingReply<bool> flushReply = iface->flushDns();
|
|
||||||
if (flushReply.waitForFinished(5000) && flushReply.returnValue())
|
|
||||||
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully flushed DNS";
|
|
||||||
else
|
|
||||||
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to flush DNS";
|
|
||||||
|
|
||||||
QRemoteObjectPendingReply<bool> clearSavedRoutesReply = iface->clearSavedRoutes();
|
|
||||||
if (clearSavedRoutesReply.waitForFinished(5000) && clearSavedRoutesReply.returnValue())
|
|
||||||
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully cleared saved routes";
|
|
||||||
else
|
|
||||||
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to clear saved routes";
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
auto *const connection = new QMetaObject::Connection;
|
auto *const connection = new QMetaObject::Connection;
|
||||||
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
|
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
|
||||||
[this, connection](AndroidController::ConnectionState state) {
|
[this, connection](AndroidController::ConnectionState state) {
|
||||||
if (state == AndroidController::ConnectionState::DISCONNECTED) {
|
if (state == AndroidController::ConnectionState::DISCONNECTED) {
|
||||||
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
disconnect(*connection);
|
disconnect(*connection);
|
||||||
delete connection;
|
delete connection;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_vpnProtocol->stop();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
m_vpnProtocol->stop();
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
|
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
|
||||||
m_vpnProtocol->deleteLater();
|
m_vpnProtocol->deleteLater();
|
||||||
#endif
|
#endif
|
||||||
@@ -490,27 +480,12 @@ void VpnConnection::disconnectFromVpn()
|
|||||||
m_vpnProtocol = nullptr;
|
m_vpnProtocol = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vpn::ConnectionState VpnConnection::connectionState()
|
void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
|
||||||
{
|
onConnectionStateChanged(state);
|
||||||
if (!m_vpnProtocol)
|
|
||||||
return Vpn::ConnectionState::Disconnected;
|
if (state == Vpn::Disconnected && m_connectionState == Vpn::Reconnecting)
|
||||||
return m_vpnProtocol->connectionState();
|
return;
|
||||||
}
|
|
||||||
|
m_connectionState = state;
|
||||||
bool VpnConnection::isConnected() const
|
emit connectionStateChanged(state);
|
||||||
{
|
|
||||||
if (m_vpnProtocol.isNull()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_vpnProtocol->isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VpnConnection::isDisconnected() const
|
|
||||||
{
|
|
||||||
if (m_vpnProtocol.isNull()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_vpnProtocol->isDisconnected();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-10
@@ -34,10 +34,6 @@ public:
|
|||||||
|
|
||||||
ErrorCode lastError() const;
|
ErrorCode lastError() const;
|
||||||
|
|
||||||
bool isConnected() const;
|
|
||||||
bool isDisconnected() const;
|
|
||||||
|
|
||||||
Vpn::ConnectionState connectionState();
|
|
||||||
QSharedPointer<VpnProtocol> vpnProtocol() const;
|
QSharedPointer<VpnProtocol> vpnProtocol() const;
|
||||||
|
|
||||||
const QString &remoteAddress() const;
|
const QString &remoteAddress() const;
|
||||||
@@ -48,14 +44,10 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void connectToVpn(int serverIndex,
|
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
|
void reconnectToVpn();
|
||||||
|
|
||||||
void disconnectFromVpn();
|
void disconnectFromVpn();
|
||||||
|
|
||||||
void addRoutes(const QStringList &ips);
|
|
||||||
void deleteRoutes(const QStringList &ips);
|
|
||||||
void flushDns();
|
|
||||||
void onKillSwitchModeChanged(bool enabled);
|
void onKillSwitchModeChanged(bool enabled);
|
||||||
void disconnectSlots();
|
void disconnectSlots();
|
||||||
|
|
||||||
@@ -70,6 +62,8 @@ protected slots:
|
|||||||
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||||
|
|
||||||
|
void setConnectionState(Vpn::ConnectionState state);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSharedPointer<VpnProtocol> m_vpnProtocol;
|
QSharedPointer<VpnProtocol> m_vpnProtocol;
|
||||||
|
|
||||||
@@ -89,6 +83,8 @@ private:
|
|||||||
void createAndroidConnections();
|
void createAndroidConnections();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Vpn::ConnectionState m_connectionState;
|
||||||
|
|
||||||
void createProtocolConnections();
|
void createProtocolConnections();
|
||||||
|
|
||||||
void appendSplitTunnelingConfig();
|
void appendSplitTunnelingConfig();
|
||||||
|
|||||||
@@ -45,5 +45,6 @@ class IpcInterface
|
|||||||
SLOT( bool stopNetworkCheck() );
|
SLOT( bool stopNetworkCheck() );
|
||||||
|
|
||||||
SIGNAL( connectionLose() );
|
SIGNAL( connectionLose() );
|
||||||
SIGNAL( networkChange() );
|
SIGNAL( wakeup() );
|
||||||
|
SIGNAL( networkChanged() );
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,18 +33,10 @@ KillSwitch* KillSwitch::instance()
|
|||||||
|
|
||||||
bool KillSwitch::init()
|
bool KillSwitch::init()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_LINUX
|
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
if (!LinuxFirewall::isInstalled()) {
|
|
||||||
LinuxFirewall::install();
|
|
||||||
}
|
|
||||||
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
if (!MacOSFirewall::isInstalled()) {
|
|
||||||
MacOSFirewall::install();
|
|
||||||
}
|
|
||||||
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
|
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (isStrictKillSwitchEnabled()) {
|
if (isStrictKillSwitchEnabled()) {
|
||||||
return disableAllTraffic();
|
return disableAllTraffic();
|
||||||
}
|
}
|
||||||
@@ -79,7 +71,6 @@ bool KillSwitch::isStrictKillSwitchEnabled()
|
|||||||
+ "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat);
|
+ "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat);
|
||||||
return RegHLM.value("strictKillSwitchEnabled", false).toBool();
|
return RegHLM.value("strictKillSwitchEnabled", false).toBool();
|
||||||
#endif
|
#endif
|
||||||
m_appSettigns->sync();
|
|
||||||
return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool();
|
return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_networkWatcher.initialize();
|
m_networkWatcher.initialize();
|
||||||
connect(&m_networkWatcher, &NetworkWatcher::sleepMode, &m_ipcServer, &IpcServer::networkChange);
|
connect(&m_networkWatcher, &NetworkWatcher::networkChanged, &m_ipcServer, &IpcServer::networkChanged);
|
||||||
connect(&m_networkWatcher, &NetworkWatcher::networkChange, &m_ipcServer, &IpcServer::networkChange);
|
connect(&m_networkWatcher, &NetworkWatcher::wakeup, &m_ipcServer, &IpcServer::wakeup);
|
||||||
KillSwitch::instance()->init();
|
KillSwitch::instance()->init();
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
|
|||||||
Reference in New Issue
Block a user