Compare commits

..

1 Commits

Author SHA1 Message Date
Mitternacht822
76f090ea11 added proxy endpoint and proxy storage endpoint into dev menu 2026-02-16 17:08:31 +04:00
79 changed files with 1151 additions and 1877 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.14.5)
set(AMNEZIAVPN_VERSION 4.8.13.0)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2118)
set(APP_ANDROID_VERSION_CODE 2106)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -61,9 +61,6 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")

View File

@@ -179,7 +179,7 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
## License
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).
GPL v3.0
## Donate

View File

@@ -1,149 +0,0 @@
# 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

View File

@@ -78,7 +78,6 @@ set(AMNEZIAVPN_TS_FILES
)
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})

View File

@@ -109,16 +109,6 @@ void AmneziaApplication::init()
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
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();
}
},

View File

@@ -75,8 +75,6 @@ private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
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() {
@@ -92,12 +90,10 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) {
@@ -200,18 +196,11 @@ class AmneziaActivity : QtActivity() {
doBindService()
}
)
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
openFileDeliveryScheduled = false
registerBroadcastReceivers()
intent?.let(::processIntent)
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() {
listOf(
"rsapss",
@@ -281,7 +270,6 @@ class AmneziaActivity : QtActivity() {
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Stop Amnezia activity")
doUnbindService()
mainScope.launch {
@@ -295,55 +283,43 @@ class AmneziaActivity : QtActivity() {
super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) {
// Cancel pending operations if window loses focus
resumeHandler.removeCallbacksAndMessages(null)
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 50)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
requestLayout()
invalidate()
}
}, 150)
}
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT -> {
nativeGamepadKeyEvent(0, keyCode, pressed)
if (deviceId < 0 && pressed) {
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true
}
}
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
}
// Real gamepad events (deviceId >= 0)
if (deviceId >= 0) {
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
if (isGamepad || isJoystick || isDpad) {
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
return true
}
}
@@ -353,18 +329,10 @@ class AmneziaActivity : QtActivity() {
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
// Using a coroutine here would be too late — the surface is gone by the time
// the coroutine runs. A direct synchronous call gives Qt's render thread the
// best chance to process visible=false before surface destruction.
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityPaused()
}
super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Pause Amnezia activity")
}
@@ -372,24 +340,6 @@ class AmneziaActivity : QtActivity() {
super.onResume()
isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityResumed()
}
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) {
window.decorView.apply {
@@ -401,13 +351,13 @@ class AmneziaActivity : QtActivity() {
sendTouch(1f, 1f)
}
}, 100)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
}, 200)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
requestLayout()
@@ -453,25 +403,25 @@ class AmneziaActivity : QtActivity() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
}
@@ -807,13 +757,9 @@ class AmneziaActivity : QtActivity() {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
if (uri.isNotEmpty()) {
pendingOpenFileUri = uri
} else {
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
))
@@ -842,7 +788,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun getFd(fileName: String): Int {
Log.v(TAG, "Get fd for $fileName")
return blockingCall(Dispatchers.IO) {
return blockingCall {
try {
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
pfd?.fd ?: -1

View File

@@ -33,10 +33,7 @@ class TvFilePicker : ComponentActivity() {
return intent
}
}) {
setResult(RESULT_OK, Intent().apply {
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}

View File

@@ -31,7 +31,4 @@ object QtAndroidController {
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
external fun onActivityPaused()
external fun onActivityResumed()
}

View File

@@ -163,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<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}
COMMENT "Signing OpenVPNAdapter framework"

View File

@@ -10,10 +10,8 @@ namespace apiDefs
AmneziaFreeV3,
AmneziaPremiumV1,
AmneziaPremiumV2,
AmneziaTrialV2,
SelfHosted,
ExternalPremium,
ExternalTrial
ExternalPremium
};
enum ConfigSource {
@@ -34,7 +32,6 @@ namespace apiDefs
constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String serviceType("service_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String cliName("cli_name");
constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String vpnKey("vpn_key");

View File

@@ -58,24 +58,18 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
};
case apiDefs::ConfigSource::AmneziaGateway: {
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceTrial("amnezia-trial");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
constexpr QLatin1String serviceExternalTrial("external-trial");
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
if (serviceType == servicePremium) {
return apiDefs::ConfigType::AmneziaPremiumV2;
} else if (serviceType == serviceTrial) {
return apiDefs::ConfigType::AmneziaTrialV2;
} else if (serviceType == serviceFree) {
return apiDefs::ConfigType::AmneziaFreeV3;
} else if (serviceType == serviceExternalPremium) {
return apiDefs::ConfigType::ExternalPremium;
} else if (serviceType == serviceExternalTrial) {
return apiDefs::ConfigType::ExternalTrial;
}
}
default: {
@@ -96,7 +90,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
const int httpStatusCodeUnprocessableEntity = 422;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
@@ -129,8 +122,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
} else if (httpStatusFromBody == httpStatusCodeUnprocessableEntity) {
return amnezia::ErrorCode::ApiSubscriptionExpiredError;
}
return amnezia::ErrorCode::ApiConfigDownloadError;
}
@@ -142,8 +133,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
{
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium,
apiDefs::ConfigType::ExternalTrial };
apiDefs::ConfigType::ExternalPremium };
return premiumTypes.contains(getConfigType(serverConfigObject));
}
@@ -187,9 +177,7 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
auto configType = apiUtils::getConfigType(serverConfigObject);
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2
&& configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) {
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return {};
}

View File

@@ -135,7 +135,7 @@ void CoreController::initControllers()
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
@@ -153,8 +153,6 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded,
this, [this]() { m_apiSettingsController->getAccountInfo(false); });
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
@@ -370,11 +368,7 @@ void CoreController::initPrepareConfigHandler()
return;
}
m_installController->validateConfig();
});
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
if (!isValid) {
if (!m_installController->isConfigValid()) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}

View File

@@ -46,13 +46,25 @@ namespace
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
constexpr int httpStatusCodeUnprocessableEntity = 422;
QStringList splitUrls(const QString &urls)
{
QStringList parsedUrls = urls.split(",", Qt::SkipEmptyParts);
for (QString &url : parsedUrls) {
url = url.trimmed();
}
parsedUrls.removeAll("");
return parsedUrls;
}
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent)
const bool isStrictKillSwitchEnabled, const QString &proxyStorageOverride,
const QString &proxyUrlOverride, QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_proxyStorageOverride(proxyStorageOverride),
m_proxyUrlOverride(proxyUrlOverride),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
@@ -72,7 +84,8 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
QString selectedProxyUrl = m_proxyUrlOverride.isEmpty() ? m_proxyUrl : m_proxyUrlOverride;
encRequestData.request.setUrl(endpoint.arg(selectedProxyUrl.isEmpty() ? m_gatewayEndpoint : selectedProxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
@@ -284,9 +297,9 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
baseUrls = m_proxyStorageOverride.isEmpty() ? splitUrls(DEV_S3_ENDPOINT) : splitUrls(m_proxyStorageOverride);
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
baseUrls = splitUrls(PROD_S3_ENDPOINT);
}
QStringList proxyStorageUrls;
@@ -334,13 +347,10 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
baseUrls = m_proxyStorageOverride.isEmpty() ? splitUrls(DEV_S3_ENDPOINT) : splitUrls(m_proxyStorageOverride);
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
baseUrls = splitUrls(PROD_S3_ENDPOINT);
}
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;
@@ -452,8 +462,6 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
}
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (httpStatus == httpStatusCodeUnprocessableEntity) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
return true;
@@ -490,7 +498,9 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
return result;
};
if (m_proxyUrl.isEmpty()) {
QString selectedProxyUrl = m_proxyUrlOverride.isEmpty() ? m_proxyUrl : m_proxyUrlOverride;
if (selectedProxyUrl.isEmpty()) {
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -512,6 +522,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
m_proxyUrl = proxyUrl;
if (!m_proxyUrl.isEmpty()) {
selectedProxyUrl = m_proxyUrl;
break;
}
} else {
@@ -520,8 +531,8 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
}
}
if (!m_proxyUrl.isEmpty()) {
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
if (!selectedProxyUrl.isEmpty()) {
if (bypassFunction(endpoint, selectedProxyUrl, requestFunction, replyProcessingFunction)) {
return;
}
}
@@ -607,6 +618,11 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
std::function<void(const QString &)> onComplete)
{
if (!m_proxyUrlOverride.isEmpty()) {
onComplete(m_proxyUrlOverride);
return;
}
if (currentProxyIndex >= proxyUrls.size()) {
onComplete("");
return;

View File

@@ -20,7 +20,8 @@ class GatewayController : public QObject
public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
const bool isStrictKillSwitchEnabled, const QString &proxyStorageOverride = "",
const QString &proxyUrlOverride = "", QObject *parent = nullptr);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
@@ -61,6 +62,8 @@ private:
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
QString m_proxyStorageOverride;
QString m_proxyUrlOverride;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;

View File

@@ -479,8 +479,6 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
return ErrorCode::ServerCgroupMountpoint;
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
return ErrorCode::DockerPullRateLimit;
if (stdOut.contains("we now block all IP addresses"))
return ErrorCode::ServerIPaddressBlockedByDocker;
return error;
}

View File

@@ -62,7 +62,6 @@ namespace amnezia
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
ServerIPaddressBlockedByDocker = 215,
// Ssh connection errors
SshRequestDeniedError = 300,

View File

@@ -30,7 +30,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
case(ErrorCode::ServerIPaddressBlockedByDocker): errorMessage = QObject::tr("Docker error: Server IP address blocked by Docker"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;

View File

@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChanged);
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
&NetworkWatcher::wakeup);
&NetworkWatcher::networkChange);
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
&NetworkWatcher::onSleepMode);
m_impl->initialize();
// Enable sleep/wake monitoring for VPN auto-reconnection
@@ -97,6 +97,12 @@ void NetworkWatcher::settingsChanged() {
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
}
void NetworkWatcher::onSleepMode()
{
logger.debug() << "Resumed from sleep mode";
emit sleepMode();
}
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)

View File

@@ -29,11 +29,13 @@ public:
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability();
signals:
void networkChanged();
void wakeup();
void networkChange();
void sleepMode();
private:
void settingsChanged();

View File

@@ -41,7 +41,7 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
void wakeup();
void sleepMode();
private:

View File

@@ -101,9 +101,7 @@ bool AndroidController::initialize()
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)},
{"onActivityPaused", "()V", reinterpret_cast<void *>(onActivityPaused)},
{"onActivityResumed", "()V", reinterpret_cast<void *>(onActivityResumed)}
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
};
QJniEnvironment env;
@@ -560,22 +558,3 @@ void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jin
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
}
// static
void AndroidController::onActivityPaused(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->activityPaused();
}
// static
void AndroidController::onActivityResumed(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->activityResumed();
}

View File

@@ -75,8 +75,6 @@ signals:
void authenticationResult(bool result);
void imeInsetsChanged(int heightDp);
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
void activityPaused();
void activityResumed();
private:
bool isWaitingStatus = true;
@@ -107,8 +105,6 @@ private:
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
static void onActivityPaused(JNIEnv *env, jobject thiz);
static void onActivityResumed(JNIEnv *env, jobject thiz);
template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);

View File

@@ -126,7 +126,8 @@ extension PacketTunnelProvider {
}
vpnReachability.startTracking { [weak self] status in
self?.handleOpenVPNReachabilityChange(status)
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler

View File

@@ -21,44 +21,6 @@ extension Constants {
}
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) {
// Xray configuration
@@ -110,7 +72,6 @@ extension PacketTunnelProvider {
settings.dnsSettings = !dnsArray.isEmpty
? NEDNSSettings(servers: dnsArray)
: NEDNSSettings(servers: ["1.1.1.1"])
applyXraySplitTunnel(xrayConfig, settings: settings)
let xrayConfigData = xrayConfig.config.data(using: .utf8)

View File

@@ -41,15 +41,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
private var pendingOpenVPNReconnectWorkItem: DispatchWorkItem?
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
private var isApplyingNetworkChange = false
private var lastOpenVPNReachabilityStatus: OpenVPNReachabilityStatus?
var splitTunnelType: Int?
var splitTunnelSites: [String]?
@@ -83,22 +78,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard hasMeaningfulChange, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally in its own adapter.
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
if proto == .wireguard {
return
}
if proto == .openvpn {
self.scheduleOpenVPNReconnect(reason: "NWPath changed")
return
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
if self.isApplyingNetworkChange || self.reasserting {
xrayLog(.debug, message: "Ignoring path change while xray restart is in progress")
return
}
self.scheduleNetworkChangeHandling(for: proto, path: path)
}
pathMonitor.start(queue: pathMonitorQueue)
@@ -210,8 +197,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
cancelPendingOpenVPNReconnect()
cancelPendingNetworkChangeHandling()
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
@@ -230,9 +215,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
cancelPendingOpenVPNReconnect()
cancelPendingNetworkChangeHandling()
guard let protoType else {
completionHandler()
return
@@ -277,111 +259,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
guard protoType == .xray else {
updateActiveInterfaceIndex(for: changePath)
completion(nil)
return
}
updateActiveInterfaceIndex(for: changePath)
reasserting = true
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 }
self.pendingNetworkChangeWorkItem = nil
if self.isApplyingNetworkChange || self.reasserting {
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)
}
private func scheduleOpenVPNReconnect(reason: String) {
guard protoType == .openvpn else { return }
pendingOpenVPNReconnectWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
guard let self else { return }
self.pendingOpenVPNReconnectWorkItem = nil
guard self.protoType == .openvpn else { return }
if self.reasserting {
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
return
}
DispatchQueue.main.async { [weak self] in
guard let self else { return }
guard !self.reasserting else {
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
return
}
ovpnLog(.info, message: "\(reason), reconnecting OpenVPN session")
self.ovpnAdapter?.reconnect(afterTimeInterval: 1)
}
}
pendingOpenVPNReconnectWorkItem = workItem
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
}
func handleOpenVPNReachabilityChange(_ status: OpenVPNReachabilityStatus) {
defer { lastOpenVPNReachabilityStatus = status }
guard let previousStatus = lastOpenVPNReachabilityStatus else {
return
}
guard previousStatus != status else {
return
}
switch status {
case .reachableViaWiFi, .reachableViaWWAN:
scheduleOpenVPNReconnect(reason: "Reachability changed")
default:
break
}
}
private func cancelPendingOpenVPNReconnect() {
pendingOpenVPNReconnectWorkItem?.cancel()
pendingOpenVPNReconnectWorkItem = nil
lastOpenVPNReachabilityStatus = nil
}
private func cancelPendingNetworkChangeHandling() {
pendingNetworkChangeWorkItem?.cancel()
pendingNetworkChangeWorkItem = nil
isApplyingNetworkChange = false
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
@@ -391,14 +271,8 @@ private extension PacketTunnelProvider {
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
// Ignore loopback and tunnel-style `.other` interfaces so Xray does not
// react to its own utun lifecycle as if the physical uplink changed.
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular]
let externalInterfaces = path.availableInterfaces.filter { interface in
interface.type == .wiredEthernet || interface.type == .wifi || interface.type == .cellular
}
let sortedInterfaces = externalInterfaces.sorted { lhs, rhs in
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
@@ -419,8 +293,8 @@ private extension PacketTunnelProvider {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback, .other:
continue
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")

View File

@@ -3,7 +3,5 @@ import Foundation
struct XrayConfig: Decodable {
let dns1: String?
let dns2: String?
let splitTunnelType: Int?
let splitTunnelSites: [String]?
let config: String
}

View File

@@ -684,15 +684,6 @@ bool IosController::setupXray()
QJsonObject finalConfig;
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::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);
QJsonDocument finalConfigDoc(finalConfig);

View File

@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup);
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
&NetworkWatcherImpl::sleepMode);
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because

View File

@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit wakeup();
emit sleepMode();
}
logger.debug() << "NMStateChanged " << state;

View File

@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
void wakeup();
void sleepMode();
public slots:
void initialize();

View File

@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
}
break;

View File

@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->wakeup();
emit obj->sleepMode();
}
break;
default:

View File

@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
setConnectionState(Vpn::ConnectionState::Connected);
emit connectionStateChanged(Vpn::ConnectionState::Connected);
});
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway,
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
});
connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr);
}

View File

@@ -52,6 +52,7 @@ XrayProtocol::~XrayProtocol()
ErrorCode XrayProtocol::start()
{
qDebug() << "XrayProtocol::start()";
setConnectionState(Vpn::ConnectionState::Connecting);
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
@@ -68,6 +69,7 @@ ErrorCode XrayProtocol::start()
void XrayProtocol::stop()
{
qDebug() << "XrayProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto disableKillSwitch = iface->disableKillSwitch();

View File

@@ -135,7 +135,6 @@
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
<file>ui/qml/Components/QuestionDrawer.qml</file>
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/SubscriptionExpiredDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>

View File

@@ -35,12 +35,13 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
}
}
m_settings.setValue("Conf/encrypted", true);
m_settings.sync();
}
}
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
{
QMutexLocker locker(&m_mutex);
QMutexLocker locker(&mutex);
if (m_cache.contains(key)) {
return m_cache.value(key);
@@ -84,7 +85,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
void SecureQSettings::setValue(const QString &key, const QVariant &value)
{
QMutexLocker locker(&m_mutex);
QMutexLocker locker(&mutex);
if (encryptionRequired() && encryptedKeys.contains(key)) {
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
@@ -106,20 +107,26 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
}
m_cache.insert(key, value);
sync();
}
void SecureQSettings::remove(const QString &key)
{
QMutexLocker locker(&m_mutex);
QMutexLocker locker(&mutex);
m_settings.remove(key);
m_cache.remove(key);
sync();
}
void SecureQSettings::sync()
{
m_settings.sync();
}
QByteArray SecureQSettings::backupAppConfig() const
{
QMutexLocker locker(&m_mutex);
QJsonObject cfg;
const auto needToBackup = [this](const auto &key) {
@@ -154,8 +161,6 @@ QByteArray SecureQSettings::backupAppConfig() const
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
{
QMutexLocker locker(&m_mutex);
QJsonObject cfg = QJsonDocument::fromJson(json).object();
if (cfg.isEmpty())
return false;
@@ -168,16 +173,10 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
setValue(key, cfg.value(key).toVariant());
}
sync();
return true;
}
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&m_mutex);
m_settings.clear();
m_cache.clear();
}
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QSimpleCrypto::QBlockCipher cipher;
@@ -295,3 +294,11 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
}
}
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&mutex);
m_settings.clear();
m_cache.clear();
sync();
}

View File

@@ -16,16 +16,14 @@ public:
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
QObject *parent = nullptr);
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
void remove(const QString &key);
void sync();
QByteArray backupAppConfig() const;
bool restoreAppConfig(const QByteArray &json);
void clearSettings();
private:
QByteArray encryptText(const QByteArray &value) const;
QByteArray decryptText(const QByteArray &ba) const;
@@ -37,6 +35,9 @@ private:
static QByteArray getSecTag(const QString &tag);
static void setSecTag(const QString &tag, const QByteArray &data);
void clearSettings();
private:
QSettings m_settings;
mutable QHash<QString, QVariant> m_cache;
@@ -52,7 +53,7 @@ private:
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
mutable QRecursiveMutex m_mutex;
mutable QMutex mutex;
};
#endif // SECUREQSETTINGS_H

View File

@@ -21,10 +21,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
{
// Import old settings
if (serversCount() == 0) {
QString user = m_settings.value("Server/userName").toString();
QString password = m_settings.value("Server/password").toString();
QString serverName = m_settings.value("Server/serverName").toString();
int port = m_settings.value("Server/serverPort").toInt();
QString user = value("Server/userName").toString();
QString password = value("Server/password").toString();
QString serverName = value("Server/serverName").toString();
int port = value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
QJsonObject server;
@@ -222,7 +222,7 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled)
{
m_settings.setValue("Conf/saveLogs", enabled);
setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) {
Logger::deInit();
@@ -242,12 +242,12 @@ void Settings::setSaveLogs(bool enabled)
QDateTime Settings::getLogEnableDate()
{
return m_settings.value("Conf/logEnableDate").toDateTime();
return value("Conf/logEnableDate").toDateTime();
}
void Settings::setLogEnableDate(QDateTime date)
{
m_settings.setValue("Conf/logEnableDate", date);
setValue("Conf/logEnableDate", date);
}
QString Settings::routeModeString(RouteMode mode) const
@@ -261,17 +261,17 @@ QString Settings::routeModeString(RouteMode mode) const
Settings::RouteMode Settings::routeMode() const
{
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
}
bool Settings::isSitesSplitTunnelingEnabled() const
{
return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool();
return value("Conf/sitesSplitTunnelingEnabled", false).toBool();
}
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
{
m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled);
setValue("Conf/sitesSplitTunnelingEnabled", enabled);
}
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
@@ -359,12 +359,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
QString Settings::primaryDns() const
{
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
return value("Conf/primaryDns", cloudFlareNs1).toString();
}
QString Settings::secondaryDns() const
{
return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString();
return value("Conf/secondaryDns", cloudFlareNs2).toString();
}
void Settings::clearSettings()
@@ -386,18 +386,18 @@ QString Settings::appsRouteModeString(AppsRouteMode mode) const
Settings::AppsRouteMode Settings::getAppsRouteMode() const
{
return static_cast<AppsRouteMode>(m_settings.value("Conf/appsRouteMode", 0).toInt());
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt());
}
void Settings::setAppsRouteMode(AppsRouteMode mode)
{
m_settings.setValue("Conf/appsRouteMode", mode);
setValue("Conf/appsRouteMode", mode);
}
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
{
QVector<InstalledAppInfo> apps;
auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray();
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray();
for (const auto &app : appsArray) {
InstalledAppInfo appInfo;
appInfo.appName = app.toObject().value("appName").toString();
@@ -419,42 +419,43 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &a
appInfo.insert("appPath", app.appPath);
appsArray.push_back(appInfo);
}
m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray);
setValue("Conf/" + appsRouteModeString(mode), appsArray);
m_settings.sync();
}
bool Settings::isAppsSplitTunnelingEnabled() const
{
return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool();
return value("Conf/appsSplitTunnelingEnabled", false).toBool();
}
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
{
m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled);
setValue("Conf/appsSplitTunnelingEnabled", enabled);
}
bool Settings::isKillSwitchEnabled() const
{
return m_settings.value("Conf/killSwitchEnabled", true).toBool();
return value("Conf/killSwitchEnabled", true).toBool();
}
void Settings::setKillSwitchEnabled(bool enabled)
{
m_settings.setValue("Conf/killSwitchEnabled", enabled);
setValue("Conf/killSwitchEnabled", enabled);
}
bool Settings::isStrictKillSwitchEnabled() const
{
return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool();
return value("Conf/strictKillSwitchEnabled", false).toBool();
}
void Settings::setStrictKillSwitchEnabled(bool enabled)
{
m_settings.setValue("Conf/strictKillSwitchEnabled", enabled);
setValue("Conf/strictKillSwitchEnabled", enabled);
}
QString Settings::getInstallationUuid(const bool needCreate)
{
auto uuid = m_settings.value("Conf/installationUuid", "").toString();
auto uuid = value("Conf/installationUuid", "").toString();
if (needCreate && uuid.isEmpty()) {
uuid = QUuid::createUuid().toString();
@@ -475,7 +476,7 @@ QString Settings::getInstallationUuid(const bool needCreate)
void Settings::setInstallationUuid(const QString &uuid)
{
m_settings.setValue("Conf/installationUuid", uuid);
setValue("Conf/installationUuid", uuid);
}
ServerCredentials Settings::defaultServerCredentials() const
@@ -496,6 +497,28 @@ ServerCredentials Settings::serverCredentials(int index) const
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()
{
m_gatewayEndpoint = gatewayEndpoint;
@@ -516,52 +539,72 @@ QString Settings::getGatewayEndpoint(bool isTestPurchase)
return isTestPurchase ? DEV_AGW_ENDPOINT : m_gatewayEndpoint;
}
QString Settings::getDevProxyStorageEndpoint() const
{
return value("Conf/devProxyStorageEndpoint", "").toString();
}
void Settings::setDevProxyStorageEndpoint(const QString &endpoint)
{
setValue("Conf/devProxyStorageEndpoint", endpoint);
}
QString Settings::getDevProxyUrl() const
{
return value("Conf/devProxyUrl", "").toString();
}
void Settings::setDevProxyUrl(const QString &url)
{
setValue("Conf/devProxyUrl", url);
}
bool Settings::isDevGatewayEnv(bool isTestPurchase)
{
return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool();
return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool();
}
void Settings::toggleDevGatewayEnv(bool enabled)
{
m_settings.setValue("Conf/devGatewayEnv", enabled);
setValue("Conf/devGatewayEnv", enabled);
}
bool Settings::isHomeAdLabelVisible()
{
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
return value("Conf/homeAdLabelVisible", true).toBool();
}
void Settings::disableHomeAdLabel()
{
m_settings.setValue("Conf/homeAdLabelVisible", false);
setValue("Conf/homeAdLabelVisible", false);
}
bool Settings::isPremV1MigrationReminderActive()
{
return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool();
return value("Conf/premV1MigrationReminderActive", true).toBool();
}
void Settings::disablePremV1MigrationReminder()
{
m_settings.setValue("Conf/premV1MigrationReminderActive", false);
setValue("Conf/premV1MigrationReminderActive", false);
}
QStringList Settings::allowedDnsServers() const
{
return m_settings.value("Conf/allowedDnsServers").toStringList();
return value("Conf/allowedDnsServers").toStringList();
}
void Settings::setAllowedDnsServers(const QStringList &servers)
{
m_settings.setValue("Conf/allowedDnsServers", servers);
setValue("Conf/allowedDnsServers", servers);
}
QStringList Settings::readNewsIds() const
{
return m_settings.value("News/readIds").toStringList();
return value("News/readIds").toStringList();
}
void Settings::setReadNewsIds(const QStringList &ids)
{
m_settings.setValue("News/readIds", ids);
setValue("News/readIds", ids);
}

View File

@@ -29,11 +29,11 @@ public:
QJsonArray serversArray() const
{
return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array();
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
}
void setServersArray(const QJsonArray &servers)
{
m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson());
setValue("Servers/serversList", QJsonDocument(servers).toJson());
}
// Servers section
@@ -45,11 +45,11 @@ public:
int defaultServerIndex() const
{
return m_settings.value("Servers/defaultServerIndex", 0).toInt();
return value("Servers/defaultServerIndex", 0).toInt();
}
void setDefaultServer(int index)
{
m_settings.setValue("Servers/defaultServerIndex", index);
setValue("Servers/defaultServerIndex", index);
}
QJsonObject defaultServer() const
{
@@ -78,34 +78,34 @@ public:
// App settings section
bool isAutoConnect() const
{
return m_settings.value("Conf/autoConnect", false).toBool();
return value("Conf/autoConnect", false).toBool();
}
void setAutoConnect(bool enabled)
{
m_settings.setValue("Conf/autoConnect", enabled);
setValue("Conf/autoConnect", enabled);
}
bool isStartMinimized() const
{
return m_settings.value("Conf/startMinimized", false).toBool();
return value("Conf/startMinimized", false).toBool();
}
void setStartMinimized(bool enabled)
{
m_settings.setValue("Conf/startMinimized", enabled);
setValue("Conf/startMinimized", enabled);
}
bool isNewsNotifications() const
{
return m_settings.value("Conf/newsNotifications", true).toBool();
return value("Conf/newsNotifications", true).toBool();
}
void setNewsNotifications(bool enabled)
{
m_settings.setValue("Conf/newsNotifications", enabled);
setValue("Conf/newsNotifications", enabled);
}
bool isSaveLogs() const
{
return m_settings.value("Conf/saveLogs", false).toBool();
return value("Conf/saveLogs", false).toBool();
}
void setSaveLogs(bool enabled);
@@ -122,18 +122,19 @@ public:
QString routeModeString(RouteMode mode) const;
RouteMode routeMode() const;
void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); }
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
bool isSitesSplitTunnelingEnabled() const;
void setSitesSplitTunnelingEnabled(bool enabled);
QVariantMap vpnSites(RouteMode mode) const
{
return m_settings.value("Conf/" + routeModeString(mode)).toMap();
return value("Conf/" + routeModeString(mode)).toMap();
}
void setVpnSites(RouteMode mode, const QVariantMap &sites)
{
m_settings.setValue("Conf/" + routeModeString(mode), sites);
setValue("Conf/" + routeModeString(mode), sites);
m_settings.sync();
}
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
@@ -146,11 +147,11 @@ public:
bool useAmneziaDns() const
{
return m_settings.value("Conf/useAmneziaDns", true).toBool();
return value("Conf/useAmneziaDns", true).toBool();
}
void setUseAmneziaDns(bool enabled)
{
m_settings.setValue("Conf/useAmneziaDns", enabled);
setValue("Conf/useAmneziaDns", enabled);
}
QString primaryDns() const;
@@ -159,13 +160,13 @@ public:
// QString primaryDns() const { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns)
{
m_settings.setValue("Conf/primaryDns", primaryDns);
setValue("Conf/primaryDns", primaryDns);
}
// QString secondaryDns() const { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns)
{
m_settings.setValue("Conf/secondaryDns", secondaryDns);
setValue("Conf/secondaryDns", secondaryDns);
}
// static constexpr char openNicNs5[] = "94.103.153.176";
@@ -187,16 +188,16 @@ public:
};
void setAppLanguage(QLocale locale)
{
m_settings.setValue("Conf/appLanguage", locale.name());
setValue("Conf/appLanguage", locale.name());
};
bool isScreenshotsEnabled() const
{
return m_settings.value("Conf/screenshotsEnabled", true).toBool();
return value("Conf/screenshotsEnabled", true).toBool();
}
void setScreenshotsEnabled(bool enabled)
{
m_settings.setValue("Conf/screenshotsEnabled", enabled);
setValue("Conf/screenshotsEnabled", enabled);
emit screenshotsEnabledChanged(enabled);
}
@@ -232,6 +233,10 @@ public:
void setGatewayEndpoint(const QString &endpoint);
void setDevGatewayEndpoint();
QString getGatewayEndpoint(bool isTestPurchase = false);
QString getDevProxyStorageEndpoint() const;
void setDevProxyStorageEndpoint(const QString &endpoint);
QString getDevProxyUrl() const;
void setDevProxyUrl(const QString &url);
bool isDevGatewayEnv(bool isTestPurchase = false);
void toggleDevGatewayEnv(bool enabled);
@@ -254,6 +259,9 @@ signals:
void settingsCleared();
private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
void setInstallationUuid(const QString &uuid);
mutable SecureQSettings m_settings;

File diff suppressed because it is too large Load Diff

View File

@@ -366,8 +366,6 @@ bool ApiConfigsController::fillAvailableServices()
{
QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[configKey::appVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody;
@@ -449,7 +447,7 @@ bool ApiConfigsController::importService()
importSerivceFromAppStore();
return true;
}
} else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) {
} else {
importServiceFromGateway();
return true;
}
@@ -723,7 +721,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
}
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
bool wasSubscriptionExpired = m_serversModel->data(serverIndex, ServersModel::IsSubscriptionExpiredRole).toBool();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
@@ -750,11 +747,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
newServerConfig.insert(config_key::nameOverriddenByUser, true);
}
m_serversModel->editServer(newServerConfig, serverIndex);
if (wasSubscriptionExpired) {
emit subscriptionRefreshNeeded();
}
if (reloadServiceConfig) {
emit reloadServerFromApiFinished(tr("API config reloaded"));
} else if (newCountryName.isEmpty()) {
@@ -764,11 +756,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
}
return true;
} else {
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
emit subscriptionExpiredOnServer();
} else {
emit errorOccurred(errorCode);
}
emit errorOccurred(errorCode);
return false;
}
}
@@ -781,7 +769,8 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
#endif
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
m_settings->isStrictKillSwitchEnabled(), m_settings->getDevProxyStorageEndpoint(),
m_settings->getDevProxyUrl());
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto installationUuid = m_settings->getInstallationUuid(true);
@@ -1006,6 +995,7 @@ ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJ
bool isTestPurchase)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled(),
m_settings->getDevProxyStorageEndpoint(), m_settings->getDevProxyUrl());
return gatewayController.post(endpoint, apiPayload, responseBody);
}

View File

@@ -43,8 +43,6 @@ public slots:
signals:
void errorOccurred(ErrorCode errorCode);
void subscriptionExpiredOnServer();
void subscriptionRefreshNeeded();
void installServerFromApiFinished(const QString &message);
void changeApiCountryFinished(const QString &message);

View File

@@ -32,7 +32,10 @@ void ApiNewsController::fetchNews(bool showError)
}
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled(),
m_settings->getDevProxyStorageEndpoint(),
m_settings->getDevProxyUrl());
QJsonObject payload;
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());

View File

@@ -1,7 +1,6 @@
#include "apiSettingsController.h"
#include <QEventLoop>
#include <QJsonDocument>
#include <QTimer>
#include "core/api/apiUtils.h"
@@ -58,7 +57,8 @@ bool ApiSettingsController::getAccountInfo(bool reload)
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled(),
m_settings->getDevProxyStorageEndpoint(), m_settings->getDevProxyUrl());
QJsonObject apiPayload;
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
@@ -78,13 +78,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object();
m_apiAccountInfoModel->updateModel(accountInfo, serverConfig);
QString subscriptionEndDate = accountInfo.value(apiDefs::key::subscriptionEndDate).toString();
if (!subscriptionEndDate.isEmpty()) {
apiConfig.insert(apiDefs::key::subscriptionEndDate, subscriptionEndDate);
serverConfig.insert(configKey::apiConfig, apiConfig);
m_serversModel->editServer(serverConfig, processedIndex);
}
if (reload) {
updateApiCountryModel();
updateApiDevicesModel();
@@ -93,42 +86,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
return true;
}
void ApiSettingsController::getRenewalLink()
{
auto processedIndex = m_serversModel->getProcessedServerIndex();
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto authData = serverConfig.value(configKey::authData).toObject();
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(isTestPurchase),
m_settings->isDevGatewayEnv(isTestPurchase),
requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
auto future = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
future.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
auto [errorCode, responseBody] = result;
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return;
}
QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object();
QString url = responseJson.value("renewal_url").toString();
if (!url.isEmpty()) {
emit renewalLinkReceived(url);
}
});
}
void ApiSettingsController::updateApiCountryModel()
{
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");

View File

@@ -21,11 +21,9 @@ public slots:
bool getAccountInfo(bool reload);
void updateApiCountryModel();
void updateApiDevicesModel();
void getRenewalLink();
signals:
void errorOccurred(ErrorCode errorCode);
void renewalLinkReceived(const QString &url);
private:
QSharedPointer<ServersModel> m_serversModel;

View File

@@ -7,6 +7,7 @@
#include <QFileInfo>
#include <QImage>
#include <QStandardPaths>
#include "core/controllers/vpnConfigurationController.h"
#include "core/qrCodeUtils.h"
#include "core/serialization/serialization.h"
@@ -169,7 +170,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
m_config.append(line + "\n");
}
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged();
}
@@ -189,7 +191,8 @@ void ExportController::generateAwgConfig(const QString &clientName)
m_config.append(line + "\n");
}
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged();
}

View File

@@ -83,8 +83,8 @@ void InstallController::install(DockerContainer container, int port, TransportPr
int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150);
int s3 = QRandomGenerator::global()->bounded(1, 64);
int s4 = QRandomGenerator::global()->bounded(1, 20);
int s3 = QRandomGenerator::global()->bounded(0, 64);
int s4 = QRandomGenerator::global()->bounded(0, 20);
// Ensure all values are unique and don't create equal packet sizes
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
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
s3 = QRandomGenerator::global()->bounded(1, 64);
s3 = QRandomGenerator::global()->bounded(0, 64);
}
usedValues.insert(s3);
while (usedValues.contains(s4)) {
s4 = QRandomGenerator::global()->bounded(1, 20);
s4 = QRandomGenerator::global()->bounded(0, 20);
}
QString initPacketJunkSize = QString::number(s1);
@@ -987,94 +987,79 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully"));
}
void InstallController::validateConfig()
bool InstallController::isConfigValid()
{
int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
if (apiUtils::isServerFromApi(serverConfigObject)) {
emit configValidated(true);
return;
return true;
}
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit noInstalledContainers();
emit configValidated(false);
return;
return false;
}
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
if (container == DockerContainer::None) {
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
emit configValidated(false);
return;
return false;
}
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
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();
QFutureWatcher<ErrorCode> watcher;
if (protocolConfig.isEmpty()) {
return false;
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
ErrorCode errorCode = ErrorCode::NoError;
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 true;
};
return errorCode;
});
if (isProtocolConfigExists(containerConfig, container)) {
emit configValidated(true);
return;
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
ErrorCode errorCode = watcher.result();
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
}
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);
return true;
}
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
@@ -1085,7 +1070,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (ContainerProps::isAwgContainer(container)) {
if (container == DockerContainer::Awg2) {
const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig);

View File

@@ -50,10 +50,9 @@ public slots:
void addEmptyServer();
void validateConfig();
bool isConfigValid();
signals:
void configValidated(bool isValid);
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
void installServerFinished(const QString &finishMessage);

View File

@@ -45,8 +45,6 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
emit safeAreaBottomMarginChanged();
emit safeAreaTopMarginChanged();
});
connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsController::activityPaused);
connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsController::activityResumed);
#endif
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
@@ -180,11 +178,12 @@ void SettingsController::backupAppConfig(const QString &fileName)
void SettingsController::restoreAppConfig(const QString &fileName)
{
QByteArray data;
if (!SystemController::readFile(fileName, data)) {
emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName));
return;
}
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
restoreAppConfigFromData(data);
}
@@ -427,6 +426,28 @@ QString SettingsController::getGatewayEndpoint()
return m_settings->isDevGatewayEnv() ? "Dev endpoint" : m_settings->getGatewayEndpoint();
}
void SettingsController::setDevProxyStorageEndpoint(const QString &endpoint)
{
m_settings->setDevProxyStorageEndpoint(endpoint);
emit devProxyStorageEndpointChanged(endpoint);
}
QString SettingsController::getDevProxyStorageEndpoint()
{
return m_settings->getDevProxyStorageEndpoint();
}
void SettingsController::setDevProxyUrl(const QString &url)
{
m_settings->setDevProxyUrl(url);
emit devProxyUrlChanged(url);
}
QString SettingsController::getDevProxyUrl()
{
return m_settings->getDevProxyUrl();
}
bool SettingsController::isDevGatewayEnv()
{
return m_settings->isDevGatewayEnv();

View File

@@ -30,6 +30,9 @@ public:
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(QString devProxyStorageEndpoint READ getDevProxyStorageEndpoint WRITE setDevProxyStorageEndpoint NOTIFY
devProxyStorageEndpointChanged)
Q_PROPERTY(QString devProxyUrl READ getDevProxyUrl WRITE setDevProxyUrl NOTIFY devProxyUrlChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
@@ -98,6 +101,10 @@ public slots:
void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint);
QString getGatewayEndpoint();
void setDevProxyStorageEndpoint(const QString &endpoint);
QString getDevProxyStorageEndpoint();
void setDevProxyUrl(const QString &url);
QString getDevProxyUrl();
bool isDevGatewayEnv();
void toggleDevGatewayEnv(bool enabled);
@@ -136,14 +143,13 @@ signals:
void devModeEnabled();
void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled);
void devProxyStorageEndpointChanged(const QString &endpoint);
void devProxyUrlChanged(const QString &url);
void imeHeightChanged(int height);
void safeAreaTopMarginChanged();
void safeAreaBottomMarginChanged();
void activityPaused();
void activityResumed();
void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged();

View File

@@ -7,8 +7,10 @@
#include "systemController.h"
#include "core/networkUtilities.h"
SitesController::SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_sitesModel(sitesModel)
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
{
}
@@ -32,20 +34,32 @@ void SitesController::addSite(QString hostname)
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
}
const auto &resolveCallback = [this](const QHostInfo &hostInfo) {
const auto &processSite = [this](const QString &hostname, const QString &ip) {
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();
for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
m_sitesModel->addSite(hostInfo.hostName(), addr.toString());
processSite(hostInfo.hostName(), addr.toString());
break;
}
}
};
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
m_sitesModel->addSite(hostname, "");
processSite(hostname, "");
} else {
m_sitesModel->addSite(hostname, "");
processSite(hostname, "");
QHostInfo::lookupHost(hostname, this, resolveCallback);
}
@@ -58,6 +72,9 @@ void SitesController::removeSite(int index)
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
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));
}
@@ -111,6 +128,8 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
m_sitesModel->addSites(sites, replaceExisting);
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
emit finished(tr("Import completed"));
}

View File

@@ -11,8 +11,9 @@ class SitesController : public QObject
{
Q_OBJECT
public:
explicit SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel,
QObject *parent = nullptr);
explicit SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
public slots:
void addSite(QString hostname);
@@ -30,6 +31,8 @@ signals:
private:
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<SitesModel> m_sitesModel;
};

View File

@@ -1,6 +1,5 @@
#include "apiAccountInfoModel.h"
#include <QDateTime>
#include <QJsonObject>
#include "core/api/apiUtils.h"
@@ -33,7 +32,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
}
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
: tr("<p><a style=\"color: #28c840;\">Active</a>");
: tr("Active");
}
case EndDateRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
@@ -53,9 +52,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
}
case IsComponentVisibleRole: {
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|| m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium;
}
case HasExpiredWorkerRole: {
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
@@ -76,18 +73,6 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
}
return false;
}
case IsSubscriptionExpiredRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) return false;
if (m_accountInfoData.subscriptionEndDate.isEmpty()) return false;
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate);
}
case IsSubscriptionExpiringSoonRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) return false;
if (m_accountInfoData.subscriptionEndDate.isEmpty()) return false;
if (apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)) return false;
QDateTime endDate = QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODateWithMs);
return endDate <= QDateTime::currentDateTimeUtc().addDays(10);
}
}
return QVariant();
@@ -179,8 +164,6 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
roles[IsComponentVisibleRole] = "isComponentVisible";
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
return roles;
}

View File

@@ -19,9 +19,7 @@ public:
EndDateRole,
IsComponentVisibleRole,
HasExpiredWorkerRole,
IsProtocolSelectionSupportedRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole
IsProtocolSelectionSupportedRole
};
explicit ApiAccountInfoModel(QObject *parent = nullptr);
@@ -33,6 +31,7 @@ public:
public slots:
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
QVariant data(const QString &roleString);
QJsonArray getAvailableCountries();
QJsonArray getIssuedConfigsInfo();

View File

@@ -41,7 +41,6 @@ namespace
{
constexpr char amneziaFree[] = "amnezia-free";
constexpr char amneziaPremium[] = "amnezia-premium";
constexpr char amneziaTrial[] = "amnezia-trial";
}
}
@@ -70,7 +69,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
}
case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium || serviceType == serviceType::amneziaTrial) {
if (serviceType == serviceType::amneziaPremium) {
return apiServiceData.serviceInfo.cardDescription.arg(speed);
} else if (serviceType == serviceType::amneziaFree) {
QString description = apiServiceData.serviceInfo.cardDescription;
@@ -125,10 +124,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case OrderRole: {
if (serviceType == serviceType::amneziaPremium) {
return 0;
} else if (serviceType == serviceType::amneziaTrial) {
return 1;
} else if (serviceType == serviceType::amneziaFree) {
return 2;
return 1;
}
}
}

View File

@@ -141,12 +141,10 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
if (protocolVersion == protocols::awg::awgV2) {
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
}
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =

View File

@@ -179,20 +179,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
case AdEndpointRole: {
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
}
case IsSubscriptionExpiredRole: {
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) return false;
QString endDate = apiConfig.value(apiDefs::key::subscriptionEndDate).toString();
if (endDate.isEmpty()) return false;
return apiUtils::isSubscriptionExpired(endDate);
}
case IsSubscriptionExpiringSoonRole: {
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) return false;
QString endDate = apiConfig.value(apiDefs::key::subscriptionEndDate).toString();
if (endDate.isEmpty()) return false;
if (apiUtils::isSubscriptionExpired(endDate)) return false;
QDateTime endDateTime = QDateTime::fromString(endDate, Qt::ISODateWithMs);
return endDateTime <= QDateTime::currentDateTimeUtc().addDays(10);
}
}
return QVariant();
@@ -457,9 +443,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[AdDescriptionRole] = "adDescription";
roles[AdEndpointRole] = "adEndpoint";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
return roles;
}

View File

@@ -52,9 +52,6 @@ public:
AdDescriptionRole,
AdEndpointRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
HasAmneziaDns
};

View File

@@ -126,18 +126,6 @@ ListViewType {
}
}
CaptionTextType {
visible: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon)
Layout.fillWidth: true
Layout.leftMargin: 64
Layout.bottomMargin: 8
text: isSubscriptionExpired ? qsTr("Subscription expired. Please renew.") : qsTr("Subscription expiring soon.")
color: isSubscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot
wrapMode: Text.WordWrap
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 0

View File

@@ -1,93 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
DrawerType2 {
id: root
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
onImplicitHeightChanged: {
root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin
}
Item {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
implicitHeight: titleText.implicitHeight
Header2TextType {
id: titleText
anchors.left: parent.left
anchors.right: parent.right
text: qsTr("Amnezia Premium subscription has expired")
horizontalAlignment: Text.AlignLeft
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew your subscription to continue using VPN")
horizontalAlignment: Text.AlignLeft
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
BasicButtonType {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8
Layout.bottomMargin: 8
implicitHeight: 25
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Support")
clickedFunc: function() {
root.closeTriggered()
PageController.goToPage(PageEnum.PageSettingsApiSupport)
}
}
}
}

View File

@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
@@ -38,7 +37,6 @@ Item {
property int borderFocusedWidth: 1
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageColor: ""
property bool descriptionOnTop: false
property bool hideDescription: true
@@ -142,14 +140,6 @@ Item {
anchors.centerIn: parent
source: leftImageSource
visible: leftImageColor === ""
}
ColorOverlay {
anchors.fill: leftImage
source: leftImage
color: leftImageColor
visible: leftImageColor !== ""
}
}

View File

@@ -8,10 +8,9 @@ Item {
id: root
property StackView stackView: StackView.view
property bool enableTimer: true
onVisibleChanged: {
if (visible && enableTimer) {
if (visible) {
timer.start()
}
}
@@ -25,6 +24,6 @@ Item {
FocusController.setFocusOnDefaultItem()
}
repeat: false // Stop the timer after one trigger
running: enableTimer // Start the timer
running: true // Start the timer
}
}

View File

@@ -73,6 +73,50 @@ PageType {
}
}
}
TextFieldWithHeaderType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Proxy storage endpoint")
textField.text: SettingsController.devProxyStorageEndpoint
buttonImageSource: textField.text !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
clickedFunc: function() {
SettingsController.devProxyStorageEndpoint = ""
}
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== SettingsController.devProxyStorageEndpoint) {
SettingsController.devProxyStorageEndpoint = textField.text
}
}
}
TextFieldWithHeaderType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Proxy endpoint")
textField.text: SettingsController.devProxyUrl
buttonImageSource: textField.text !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
clickedFunc: function() {
SettingsController.devProxyUrl = ""
}
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== SettingsController.devProxyUrl) {
SettingsController.devProxyUrl = textField.text
}
}
}
}
footer: ColumnLayout {

View File

@@ -330,8 +330,6 @@ PageType {
AwgTextField {
id: cookieReplyPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16
Layout.rightMargin: 16
@@ -344,8 +342,6 @@ PageType {
AwgTextField {
id: transportPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16
Layout.rightMargin: 16

View File

@@ -18,23 +18,12 @@ PageType {
id: root
property var processedServer
property bool subscriptionExpired: false
property bool subscriptionExpiringSoon: false
function updateSubscriptionState() {
root.subscriptionExpired = ServersModel.getProcessedServerData("isSubscriptionExpired")
root.subscriptionExpiringSoon = ServersModel.getProcessedServerData("isSubscriptionExpiringSoon")
}
Component.onCompleted: {
root.updateSubscriptionState()
}
Connections {
target: ServersModel
function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0)
root.updateSubscriptionState()
}
}
@@ -87,11 +76,12 @@ PageType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 4
Layout.bottomMargin: 10
actionButtonImage: "qrc:/images/controls/settings.svg"
headerText: root.processedServer.name
descriptionText: qsTr("Location for connection")
actionButtonFunction: function() {
PageController.showBusyIndicator(true)
@@ -104,50 +94,6 @@ PageType {
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
}
}
CaptionTextType {
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 4
text: root.subscriptionExpired ? qsTr("Subscription expired") : qsTr("Subscription expiring soon")
color: root.subscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot
}
BasicButtonType {
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 4
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
text: qsTr("Renew subscription")
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: (root.subscriptionExpired || root.subscriptionExpiringSoon) ? 8 : 4
Layout.bottomMargin: 8
text: qsTr("Location for connection")
color: AmneziaStyle.color.mutedGray
}
}
delegate: ColumnLayout {

View File

@@ -2,7 +2,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
import SortFilterProxyModel 0.2
@@ -53,26 +52,6 @@ PageType {
property var processedServer
property bool isSubscriptionExpired: false
property bool isSubscriptionExpiringSoon: false
function updateSubscriptionState() {
root.isSubscriptionExpired = ApiAccountInfoModel.data("isSubscriptionExpired")
root.isSubscriptionExpiringSoon = ApiAccountInfoModel.data("isSubscriptionExpiringSoon")
}
Component.onCompleted: {
root.updateSubscriptionState()
}
Connections {
target: ApiAccountInfoModel
function onModelReset() {
root.updateSubscriptionState()
}
}
Connections {
target: ServersModel
@@ -129,66 +108,12 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name
descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() {
serverNameEditDrawer.openTriggered()
}
}
Text {
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 4
text: root.isSubscriptionExpired
? qsTr("Subscription expired")
: qsTr("Subscription expiring soon")
color: root.isSubscriptionExpired
? AmneziaStyle.color.vibrantRed
: AmneziaStyle.color.goldenApricot
font.pixelSize: 14
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
ParagraphTextType {
visible: ApiAccountInfoModel.data("serviceDescription") !== ""
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon ? 0 : 10
text: ApiAccountInfoModel.data("serviceDescription")
color: AmneziaStyle.color.mutedGray
}
BasicButtonType {
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 8
text: qsTr("Renew subscription")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
}
delegate: ColumnLayout {
@@ -226,54 +151,6 @@ PageType {
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
Item {
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
Layout.fillWidth: true
implicitHeight: renewRow.implicitHeight + 32
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: ApiSettingsController.getRenewalLink()
}
Row {
id: renewRow
anchors.centerIn: parent
spacing: 12
Item {
width: renewIcon.implicitWidth
height: renewIcon.implicitHeight
anchors.verticalCenter: parent.verticalCenter
Image {
id: renewIcon
source: "qrc:/images/controls/refresh-cw.svg"
}
ColorOverlay {
anchors.fill: renewIcon
source: renewIcon
color: AmneziaStyle.color.goldenApricot
}
}
Text {
text: qsTr("Renew subscription")
color: AmneziaStyle.color.goldenApricot
font.pixelSize: 18
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
DividerType {
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
}
SwitcherType {
id: switcher
@@ -300,14 +177,10 @@ PageType {
}
}
DividerType {
visible: footer.isVisibleForAmneziaFree
}
WarningType {
id: warning
Layout.topMargin: 24
Layout.topMargin: 32
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
@@ -331,7 +204,7 @@ PageType {
id: vpnKey
Layout.fillWidth: true
Layout.topMargin: warning.visible ? 16 : 0
Layout.topMargin: warning.visible ? 16 : 32
visible: footer.isVisibleForAmneziaFree
@@ -523,7 +396,9 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
} else {
PageController.showBusyIndicator(true)
InstallController.removeProcessedServer()
if (ApiConfigsController.deactivateDevice(true)) {
InstallController.removeProcessedServer()
}
PageController.showBusyIndicator(false)
}
}

View File

@@ -178,7 +178,7 @@ PageType {
Layout.margins: 16
text: qsTr("News Notification")
descriptionText: qsTr("Show a notification icon for unread news")
descriptionText: qsTr("Show notification icon when has unread news")
checked: SettingsController.isNewsNotificationsEnabled()
onToggled: function() {

View File

@@ -1,226 +1,226 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
listView.positionViewAtBeginning()
}
}
}
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
header: ColumnLayout {
width: listView.width
BaseHeaderType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 32
headerText: ApiServicesModel.getSelectedServiceData("name")
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
}
}
model: inputFields
spacing: 0
delegate: ColumnLayout {
width: listView.width
LabelWithImageType {
Layout.fillWidth: true
Layout.margins: 16
imageSource: imagePath
leftText: lText
rightText: rText
visible: isVisible
}
}
footer: ColumnLayout {
width: listView.width
spacing: 0
ParagraphTextType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
textFormat: Text.RichText
text: {
var text = ApiServicesModel.getSelectedServiceData("features")
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.PlainText
color: AmneziaStyle.color.mutedGray
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.")
}
BasicButtonType {
id: continueButton
Layout.fillWidth: true
Layout.topMargin: 32
Layout.bottomMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : (ApiServicesModel.getSelectedServiceType() === "amnezia-trial" ? qsTr("Try Trial") : qsTr("Connect"))
clickedFunc: function() {
PageController.showBusyIndicator(true)
var result = ApiConfigsController.importService()
PageController.showBusyIndicator(false)
if (!result) {
var endpoint = ApiServicesModel.getStoreEndpoint()
Qt.openUrlExternally(endpoint)
PageController.closePage()
PageController.closePage()
}
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 32
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
color: AmneziaStyle.color.mutedGray
font.pixelSize: 12
text: {
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
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)
}
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
}
property list<QtObject> inputFields: [
region,
price,
timeLimit,
speed,
features
]
QtObject {
id: region
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
readonly property string lText: qsTr("For the region")
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
property bool isVisible: true
}
QtObject {
id: price
readonly property string imagePath: "qrc:/images/controls/tag.svg"
readonly property string lText: qsTr("Price")
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
property bool isVisible: true
}
QtObject {
id: timeLimit
readonly property string imagePath: "qrc:/images/controls/history.svg"
readonly property string lText: qsTr("Work period")
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
property bool isVisible: rText !== ""
}
QtObject {
id: speed
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
readonly property string lText: qsTr("Speed")
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
property bool isVisible: true
}
QtObject {
id: features
readonly property string imagePath: "qrc:/images/controls/info.svg"
readonly property string lText: qsTr("Features")
readonly property string rText: ""
property bool isVisible: true
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
listView.positionViewAtBeginning()
}
}
}
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
header: ColumnLayout {
width: listView.width
BaseHeaderType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 32
headerText: ApiServicesModel.getSelectedServiceData("name")
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
}
}
model: inputFields
spacing: 0
delegate: ColumnLayout {
width: listView.width
LabelWithImageType {
Layout.fillWidth: true
Layout.margins: 16
imageSource: imagePath
leftText: lText
rightText: rText
visible: isVisible
}
}
footer: ColumnLayout {
width: listView.width
spacing: 0
ParagraphTextType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
textFormat: Text.RichText
text: {
var text = ApiServicesModel.getSelectedServiceData("features")
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.PlainText
color: AmneziaStyle.color.mutedGray
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.")
}
BasicButtonType {
id: continueButton
Layout.fillWidth: true
Layout.topMargin: 32
Layout.bottomMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect")
clickedFunc: function() {
PageController.showBusyIndicator(true)
var result = ApiConfigsController.importService()
PageController.showBusyIndicator(false)
if (!result) {
var endpoint = ApiServicesModel.getStoreEndpoint()
Qt.openUrlExternally(endpoint)
PageController.closePage()
PageController.closePage()
}
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 32
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
color: AmneziaStyle.color.mutedGray
font.pixelSize: 12
text: {
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
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)
}
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
}
property list<QtObject> inputFields: [
region,
price,
timeLimit,
speed,
features
]
QtObject {
id: region
readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
readonly property string lText: qsTr("For the region")
readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
property bool isVisible: true
}
QtObject {
id: price
readonly property string imagePath: "qrc:/images/controls/tag.svg"
readonly property string lText: qsTr("Price")
readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
property bool isVisible: true
}
QtObject {
id: timeLimit
readonly property string imagePath: "qrc:/images/controls/history.svg"
readonly property string lText: qsTr("Work period")
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
property bool isVisible: rText !== ""
}
QtObject {
id: speed
readonly property string imagePath: "qrc:/images/controls/gauge.svg"
readonly property string lText: qsTr("Speed")
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
property bool isVisible: true
}
QtObject {
id: features
readonly property string imagePath: "qrc:/images/controls/info.svg"
readonly property string lText: qsTr("Features")
readonly property string rText: ""
property bool isVisible: true
}
}

View File

@@ -79,23 +79,11 @@ PageType {
}
textField.onTextChanged: {
if (headerText === qsTr("Password or SSH private key")) {
if (headerText == qsTr("Password or SSH private key")) {
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 {

View File

@@ -13,7 +13,6 @@ import "../Components"
PageType {
id: root
enableTimer: (SettingsController.isOnTv()) ? false : true
ColumnLayout {
id: content
@@ -46,22 +45,4 @@ PageType {
}
}
}
Timer {
interval: 250
running: SettingsController.isOnTv()
repeat: true
onTriggered: {
startButton.forceActiveFocus()
if (startButton.activeFocus) {
running = false
}
}
}
onVisibleChanged: {
if (visible && SettingsController.isOnTv()) {
startButton.forceActiveFocus()
}
}
}

View File

@@ -505,13 +505,7 @@ PageType {
exportTypeSelector.currentIndex = 0
}
selectedIndex = exportTypeSelector.currentIndex
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 = ""
}
exportTypeSelector.text = selectedText
}
rootWidth: root.width

View File

@@ -278,6 +278,7 @@ PageType {
}
Keys.onPressed: function(event) {
console.debug(">>>> ", event.key, " Event is caught by StartPage")
switch (event.key) {
case Qt.Key_Tab:
case Qt.Key_Down:
@@ -303,7 +304,7 @@ PageType {
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
// Also adjust TabBar position when keyboard appears (Android 14+ workaround)
anchors.bottomMargin: SettingsController.imeHeight

View File

@@ -21,27 +21,15 @@ Window {
function onStateChanged() {
if (Qt.platform.os === "android") {
if (Qt.application.state === Qt.ApplicationActive) {
root.visible = true
refreshTimer.restart()
} else if (Qt.application.state === Qt.ApplicationSuspended ||
Qt.application.state === Qt.ApplicationInactive) {
console.log("QML: Application going to background, state:", Qt.application.state)
}
}
}
}
// Hide the window immediately when Android Activity.onPause() fires so that
// Qt's render loop stops before the EGL surface is disconnected. This
// prevents "QRhiGles2: Failed to make context current" and the resulting
// black screen that appears after swiping home and returning.
Connections {
target: SettingsController
function onActivityPaused() {
if (Qt.platform.os === "android") root.visible = false
}
function onActivityResumed() {
if (Qt.platform.os === "android") root.visible = true
}
}
Timer {
id: refreshTimer
interval: 150
@@ -68,11 +56,6 @@ Window {
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"
Item { // This item is needed for focus handling
@@ -288,34 +271,6 @@ Window {
}
}
Item {
objectName: "subscriptionExpiredDrawerItem"
anchors.fill: parent
SubscriptionExpiredDrawer {
id: subscriptionExpiredDrawer
anchors.fill: parent
}
}
Connections {
target: ApiConfigsController
function onSubscriptionExpiredOnServer() {
subscriptionExpiredDrawer.openTriggered()
}
}
Connections {
target: ApiSettingsController
function onRenewalLinkReceived(url) {
Qt.openUrlExternally(url)
}
}
Item {
objectName: "busyIndicatorItem"

View File

@@ -39,8 +39,9 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings, QObject *parent
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
m_checkTimer.setInterval(1000);
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged);
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
#endif
}
@@ -58,7 +59,7 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled);
if (reply.waitForFinished() && reply.returnValue())
if (reply.waitForFinished(1000) && reply.returnValue())
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
else
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call";
@@ -72,57 +73,40 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
switch (state) {
case Vpn::ConnectionState::Connected: {
iface->resetIpStack();
if (state == Vpn::ConnectionState::Connected) {
iface->resetIpStack();
iface->flushDns();
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
if (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
if (m_settings->isSitesSplitTunnelingEnabled()) {
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
// TODO: add error code handling for all routeAddList (or rework the code below)
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (m_settings->isSitesSplitTunnelingEnabled()) {
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
}
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
}
}
} break;
case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error: {
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
}
} else if (state == Vpn::ConnectionState::Error) {
iface->flushDns();
auto clearSavedRoutes = iface->clearSavedRoutes();
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
} break;
default:
break;
if (m_settings->isSitesSplitTunnelingEnabled()) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->clearSavedRoutes();
}
}
}
});
#endif
@@ -136,6 +120,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
m_checkTimer.stop();
}
#endif
emit connectionStateChanged(state);
}
const QString &VpnConnection::remoteAddress() const
@@ -180,11 +165,7 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode)
});
m_settings->addVpnSite(mode, site, ip);
}
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (reply.waitForFinished() || !reply.returnValue())
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
});
flushDns();
break;
}
}
@@ -199,6 +180,48 @@ QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
return m_vpnProtocol;
}
void VpnConnection::addRoutes(const QStringList &ips)
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (connectionState() == Vpn::ConnectionState::Connected) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), ips);
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->routeGateway(), ips);
}
}
});
#endif
}
void VpnConnection::deleteRoutes(const QStringList &ips)
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (connectionState() == Vpn::ConnectionState::Connected) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->routeDeleteList(vpnProtocol()->vpnGateway(), ips);
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeDeleteList(m_vpnProtocol->routeGateway(), ips);
}
}
});
#endif
}
void VpnConnection::flushDns()
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnConnection::flushDns(): Failed to flush DNS";
}
});
#endif
}
void VpnConnection::disconnectSlots()
{
if (m_vpnProtocol) {
@@ -228,7 +251,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
<< m_settings->routeMode();
m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName);
setConnectionState(Vpn::ConnectionState::Connecting);
emit connectionStateChanged(Vpn::ConnectionState::Connecting);
m_vpnConfiguration = vpnConfiguration;
@@ -246,7 +269,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
if (!m_vpnProtocol) {
setConnectionState(Vpn::ConnectionState::Error);
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;
}
m_vpnProtocol->prepare();
@@ -264,24 +287,17 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
createProtocolConnections();
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(err);
}
ErrorCode errorCode = m_vpnProtocol->start();
if (errorCode != ErrorCode::NoError)
emit connectionStateChanged(Vpn::ConnectionState::Error);
}
void VpnConnection::createProtocolConnections()
{
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this,
SLOT(onConnectionStateChanged(Vpn::ConnectionState)));
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
});
#endif
}
void VpnConnection::appendKillSwitchConfig()
@@ -423,27 +439,6 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s
}
void VpnConnection::reconnectToVpn() {
if (m_vpnProtocol.isNull())
return;
if (m_connectionState != Vpn::ConnectionState::Connected) {
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
return;
}
qDebug() << "Reconnect triggered. Reconnecting to the server";
setConnectionState(Vpn::ConnectionState::Reconnecting);
m_vpnProtocol->stop();
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(err);
}
}
void VpnConnection::disconnectFromVpn()
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
@@ -453,25 +448,40 @@ void VpnConnection::disconnectFromVpn()
#endif
if (m_vpnProtocol.isNull()) {
setConnectionState(Vpn::ConnectionState::Disconnected);
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
setConnectionState(Vpn::ConnectionState::Disconnecting);
m_vpnProtocol->stop();
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> flushReply = iface->flushDns();
if (flushReply.waitForFinished(5000) && flushReply.returnValue())
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully flushed DNS";
else
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to flush DNS";
QRemoteObjectPendingReply<bool> clearSavedRoutesReply = iface->clearSavedRoutes();
if (clearSavedRoutesReply.waitForFinished(5000) && clearSavedRoutesReply.returnValue())
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully cleared saved routes";
else
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to clear saved routes";
});
#endif
#ifdef Q_OS_ANDROID
auto *const connection = new QMetaObject::Connection;
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
[this, connection](AndroidController::ConnectionState state) {
if (state == AndroidController::ConnectionState::DISCONNECTED) {
setConnectionState(Vpn::ConnectionState::Disconnected);
onConnectionStateChanged(Vpn::ConnectionState::Disconnected);
disconnect(*connection);
delete connection;
}
});
#endif
m_vpnProtocol->stop();
#endif
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
m_vpnProtocol->deleteLater();
@@ -480,12 +490,27 @@ void VpnConnection::disconnectFromVpn()
m_vpnProtocol = nullptr;
}
void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
onConnectionStateChanged(state);
if (state == Vpn::Disconnected && m_connectionState == Vpn::Reconnecting)
return;
m_connectionState = state;
emit connectionStateChanged(state);
Vpn::ConnectionState VpnConnection::connectionState()
{
if (!m_vpnProtocol)
return Vpn::ConnectionState::Disconnected;
return m_vpnProtocol->connectionState();
}
bool VpnConnection::isConnected() const
{
if (m_vpnProtocol.isNull()) {
return false;
}
return m_vpnProtocol->isConnected();
}
bool VpnConnection::isDisconnected() const
{
if (m_vpnProtocol.isNull()) {
return true;
}
return m_vpnProtocol->isDisconnected();
}

View File

@@ -34,6 +34,10 @@ public:
ErrorCode lastError() const;
bool isConnected() const;
bool isDisconnected() const;
Vpn::ConnectionState connectionState();
QSharedPointer<VpnProtocol> vpnProtocol() const;
const QString &remoteAddress() const;
@@ -44,10 +48,14 @@ public:
#endif
public slots:
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
void reconnectToVpn();
void connectToVpn(int serverIndex,
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
void disconnectFromVpn();
void addRoutes(const QStringList &ips);
void deleteRoutes(const QStringList &ips);
void flushDns();
void onKillSwitchModeChanged(bool enabled);
void disconnectSlots();
@@ -62,8 +70,6 @@ protected slots:
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void onConnectionStateChanged(Vpn::ConnectionState state);
void setConnectionState(Vpn::ConnectionState state);
protected:
QSharedPointer<VpnProtocol> m_vpnProtocol;
@@ -83,8 +89,6 @@ private:
void createAndroidConnections();
#endif
Vpn::ConnectionState m_connectionState;
void createProtocolConnections();
void appendSplitTunnelingConfig();

View File

@@ -45,6 +45,5 @@ class IpcInterface
SLOT( bool stopNetworkCheck() );
SIGNAL( connectionLose() );
SIGNAL( wakeup() );
SIGNAL( networkChanged() );
SIGNAL( networkChange() );
};

View File

@@ -33,10 +33,18 @@ KillSwitch* KillSwitch::instance()
bool KillSwitch::init()
{
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
#ifdef Q_OS_LINUX
if (!LinuxFirewall::isInstalled()) {
LinuxFirewall::install();
}
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
#endif
#ifdef Q_OS_MACOS
if (!MacOSFirewall::isInstalled()) {
MacOSFirewall::install();
}
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
#endif
if (isStrictKillSwitchEnabled()) {
return disableAllTraffic();
}
@@ -71,6 +79,7 @@ bool KillSwitch::isStrictKillSwitchEnabled()
+ "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat);
return RegHLM.value("strictKillSwitchEnabled", false).toBool();
#endif
m_appSettigns->sync();
return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool();
}

View File

@@ -50,8 +50,8 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
}
m_networkWatcher.initialize();
connect(&m_networkWatcher, &NetworkWatcher::networkChanged, &m_ipcServer, &IpcServer::networkChanged);
connect(&m_networkWatcher, &NetworkWatcher::wakeup, &m_ipcServer, &IpcServer::wakeup);
connect(&m_networkWatcher, &NetworkWatcher::sleepMode, &m_ipcServer, &IpcServer::networkChange);
connect(&m_networkWatcher, &NetworkWatcher::networkChange, &m_ipcServer, &IpcServer::networkChange);
KillSwitch::instance()->init();
#ifdef Q_OS_LINUX