Compare commits

...

39 Commits

Author SHA1 Message Date
spectrum acf8185ddb feat: add in-app review request functionality for iOS platform 2026-03-26 21:54:20 +02:00
yyy-amnezia aaf2c9ddeb feat: add Xray split tunnel support for iOS PacketTunnelProvider (#2332) 2026-03-24 16:07:36 +08:00
Mitternacht822 dbbc7119ec feat: add warning info for ssh keys (#2252)
* fix: fixed da typo

* feat: added warning about available ssh keys info
2026-03-24 16:06:40 +08:00
vkamn c57162c4cc feat: add base amnezia trial support (#2366)
* feat: add base amnezia trial support

* feat: add external-trial
2026-03-24 10:29:51 +08:00
NickVs2015 40e39895c9 fix openfile deadlock (#2373) 2026-03-21 11:46:46 +08:00
vkamn ec3ab2a03c chore: update licnese file (#2376) 2026-03-20 21:04:13 +08:00
yyy-amnezia ddecfcad26 fix: apple platform network switch fix (#2359)
* Apple platform network switch fix

* macos_ne exclusion fixed
2026-03-20 20:51:36 +08:00
NickVs2015 67bd880cdf fix: swap buffers error (#2347) 2026-03-16 13:03:20 +08:00
vkamn 477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015 f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015 9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
NickVs2015 83d045af64 fix: GP requrements (#2312) 2026-03-06 17:05:16 +08:00
NickVs2015 aea8ff4961 fix: add handle handleContextCreationFailure (#2309) 2026-03-03 22:04:45 +08:00
vkamn 1892db4375 fix: remove nested qeventloop from isConfigValid (also rename to validateConfig) (#2305)
* fix: remove nested qeventloop from isConfigValid (also rename to validateConfig)

* chore: bump version
2026-03-03 20:58:32 +08:00
NickVs2015 c86a641e05 fix: add suppord android 9 gamepad and remote control (#2302) 2026-03-03 15:14:51 +08:00
vkamn befb2bf19a chore: bump version (#2295) 2026-02-27 23:33:37 +08:00
vkamn 7ad6bc340c chore: add translations for ru (#2285)
* chore: add translations for ru

* chore: text fixes
2026-02-27 20:00:31 +08:00
vkamn 9164e38c34 fix: restore backup android (#2291)
* fix: fixed restore backup on android

* chore: add resume helper for android

* chore: add ResumeHelper.runWhenActive call after all native android dialogs

* fix: add permission for tv file picker

* fix: add file picker handler in kotlin

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-02-27 18:43:36 +08:00
vkamn 8f7559f01b chore: revert PR #2222 (#2290) 2026-02-27 14:29:25 +08:00
vkamn af56200735 fix: fixed adding s3 s4 when updating the server conf for awg lagacy (#2289) 2026-02-27 14:11:40 +08:00
vkamn 3874050fae fix: again fixed s3, s4 ranges (#2288) 2026-02-27 13:37:49 +08:00
vkamn 3087163e34 fix: fixed s3, s4 ranges (#2283) 2026-02-26 22:31:41 +08:00
Mitternacht822 1fa152845c fix: generate native awg config as qr series (#2221) 2026-02-26 22:31:18 +08:00
vkamn 50e23ef233 fix: awg config update (#2281)
* fix: fixed client config update for awg container

* chore: bump version
2026-02-26 22:12:58 +08:00
Yaroslav Gurov ea648466de chore: remove redundant VpnConnection usage from SitesController (#2278) 2026-02-26 17:55:08 +08:00
Yaroslav Gurov b782775016 fix: change event looping to mutexes for settings and secureqsettings (#2270) 2026-02-26 11:41:08 +08:00
NickVs2015 89a7fe1081 fix: fixed remote control for ATV (#2277) 2026-02-26 11:40:16 +08:00
Yaroslav Gurov e8bb096025 fix: ios wrong awg blob (#2272) 2026-02-24 17:56:17 +07:00
Mitternacht822 fd5c7c8322 fix: copy LICENSE to build as LICENSE.txt for WiX CPack (#2265)
* fix(installer): copy LICENSE to build as LICENSE.txt for WiX CPack

* fix: fixed a typo

* fix: fixed a typo
2026-02-24 14:07:48 +08:00
Yaroslav Gurov e798d0f503 feat: update amneziawg-android dependencies (#2269) 2026-02-24 00:54:55 +08:00
Yaroslav Gurov bbb0abb596 feat: update xray (#2267) 2026-02-24 00:27:29 +08:00
vkamn 0925aec86a chore: bump version (#2264) 2026-02-23 18:01:59 +08:00
Yaroslav Gurov b084c4c284 fix: ios connection status stuck (#2263) 2026-02-23 18:00:13 +08:00
vkamn 87288ebccd chore: bump version (#2262) 2026-02-23 17:16:24 +08:00
vkamn fcd7eadf4c chore: bump version (#2261) 2026-02-23 15:38:27 +08:00
Mitternacht822 0373338fb7 fix: randomized baseUrls traversal order in GatewayController::getProxyUrls (#2247) 2026-02-23 15:33:35 +08:00
Yaroslav Gurov 42f070fe9d fix: handle Android disconnected status properly (#2255) 2026-02-23 15:31:15 +08:00
47 changed files with 1221 additions and 826 deletions
+5 -3
View File
@@ -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.1) 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 2107) 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,7 +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(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") 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")
+1 -1
View File
@@ -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
+149
View File
@@ -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
View File
@@ -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})
+10
View File
@@ -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()
} }
+1 -1
View File
@@ -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"
+3 -1
View File
@@ -10,8 +10,10 @@ namespace apiDefs
AmneziaFreeV3, AmneziaFreeV3,
AmneziaPremiumV1, AmneziaPremiumV1,
AmneziaPremiumV2, AmneziaPremiumV2,
AmneziaTrialV2,
SelfHosted, SelfHosted,
ExternalPremium ExternalPremium,
ExternalTrial
}; };
enum ConfigSource { enum ConfigSource {
+11 -2
View File
@@ -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 {};
} }
+6 -2
View File
@@ -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;
@@ -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)
} }
} }
+77
View File
@@ -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;
+2
View File
@@ -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
} }
+9
View File
@@ -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);
+14 -21
View File
@@ -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();
}
+6 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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;
} }
+2 -5
View File
@@ -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();
} }
+68 -53
View File
@@ -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);
+2 -1
View File
@@ -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);
+5 -6
View File
@@ -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);
} }
+6 -25
View File
@@ -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"));
} }
+2 -5
View File
@@ -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;
}; };
+3 -1
View File
@@ -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++) {
+5 -2
View File
@@ -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] =
+3 -2
View File
@@ -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
@@ -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 doesnt 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()
}
}
} }
+7 -1
View File
@@ -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
+1 -2
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -39,7 +39,7 @@ 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
} }
@@ -464,7 +464,7 @@ void VpnConnection::disconnectFromVpn()
*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;
} }
@@ -14,8 +14,6 @@ sc stop AmneziaVPN-service
sc delete AmneziaVPN-service sc delete AmneziaVPN-service
sc stop AmneziaWGTunnel$AmneziaVPN sc stop AmneziaWGTunnel$AmneziaVPN
sc delete AmneziaWGTunnel$AmneziaVPN sc delete AmneziaWGTunnel$AmneziaVPN
sc stop AmneziaVPNSplitTunnel
sc delete AmneziaVPNSplitTunnel
taskkill /IM "AmneziaVPN-service.exe" /F taskkill /IM "AmneziaVPN-service.exe" /F
taskkill /IM "AmneziaVPN.exe" /F taskkill /IM "AmneziaVPN.exe" /F
@@ -14,8 +14,6 @@ sc stop AmneziaVPN-service
sc delete AmneziaVPN-service sc delete AmneziaVPN-service
sc stop AmneziaWGTunnel$AmneziaVPN sc stop AmneziaWGTunnel$AmneziaVPN
sc delete AmneziaWGTunnel$AmneziaVPN sc delete AmneziaWGTunnel$AmneziaVPN
sc stop AmneziaVPNSplitTunnel
sc delete AmneziaVPNSplitTunnel
taskkill /IM "AmneziaVPN-service.exe" /F taskkill /IM "AmneziaVPN-service.exe" /F
taskkill /IM "AmneziaVPN.exe" /F taskkill /IM "AmneziaVPN.exe" /F
-1
View File
@@ -71,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();
} }