Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/graphview

This commit is contained in:
vladimir.kuznetsov
2024-04-01 17:52:08 +05:00
55 changed files with 1368 additions and 227 deletions
+14 -1
View File
@@ -14,10 +14,12 @@
#include "logger.h" #include "logger.h"
#include "version.h" #include "version.h"
#include "ui/models/installedAppsModel.h"
#include "platforms/ios/QRCodeReaderBase.h" #include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
#include "core/installedAppsImageProvider.h"
#endif #endif
#include "protocols/qml_register_protocols.h" #include "protocols/qml_register_protocols.h"
@@ -124,8 +126,12 @@ void AmneziaApplication::init()
m_importController->extractConfigFromData(data); m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig(); m_pageController->goToPageViewConfig();
}); });
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif #endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
IosController::Instance()->initialize(); IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
@@ -234,7 +240,8 @@ void AmneziaApplication::registerTypes()
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters"); "ContainersModelFilters");
// qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum(); Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum(); PageLoader::declareQmlPageEnum();
} }
@@ -325,6 +332,9 @@ void AmneziaApplication::initModels()
m_sitesModel.reset(new SitesModel(m_settings, this)); m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
@@ -407,6 +417,9 @@ void AmneziaApplication::initControllers()
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings)); m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
+4
View File
@@ -21,6 +21,7 @@
#include "ui/controllers/sitesController.h" #include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h" #include "ui/controllers/systemController.h"
#include "ui/controllers/apiController.h" #include "ui/controllers/apiController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h" #include "ui/models/protocols/cloakConfigModel.h"
@@ -38,6 +39,7 @@
#include "ui/models/services/sftpConfigModel.h" #include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h" #include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h" #include "ui/models/clientManagementModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@@ -94,6 +96,7 @@ private:
QSharedPointer<LanguageModel> m_languageModel; QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel; QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel; QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel; QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel; QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
@@ -121,6 +124,7 @@ private:
QScopedPointer<SitesController> m_sitesController; QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController; QScopedPointer<SystemController> m_systemController;
QScopedPointer<ApiController> m_apiController; QScopedPointer<ApiController> m_apiController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H
+1 -3
View File
@@ -24,9 +24,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
<application <application
android:name=".AmneziaApplication" android:name=".AmneziaApplication"
@@ -66,6 +66,7 @@ class Awg : Wireguard() {
return AwgConfig.build { return AwgConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) } configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) } configData["Jmin"]?.let { setJmin(it.toInt()) }
configData["Jmax"]?.let { setJmax(it.toInt()) } configData["Jmax"]?.let { setJmax(it.toInt()) }
@@ -79,6 +79,7 @@ open class OpenVpn : Protocol() {
} }
configPluggableTransport(configBuilder, config) configPluggableTransport(configBuilder, config)
configBuilder.configSplitTunneling(config) configBuilder.configSplitTunneling(config)
configBuilder.configAppSplitTunneling(config)
scope.launch { scope.launch {
val status = client.connect() val status = client.connect()
@@ -64,6 +64,22 @@ abstract class Protocol {
} }
} }
protected fun ProtocolConfig.Builder.configAppSplitTunneling(config: JSONObject) {
val splitTunnelType = config.optInt("appSplitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelApps = config.getJSONArray("splitTunnelApps")
val appHandlerFunc = when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeApplication
SPLIT_TUNNEL_EXCLUDE -> ::excludeApplication
else -> throw BadConfigException("Unexpected value of the 'appSplitTunnelType' parameter: $splitTunnelType")
}
for (i in 0 until splitTunnelApps.length()) {
appHandlerFunc(splitTunnelApps.getString(i))
}
}
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) { protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
vpnBuilder.setSession(VPN_SESSION_NAME) vpnBuilder.setSession(VPN_SESSION_NAME)
@@ -101,6 +117,11 @@ abstract class Protocol {
} }
} }
for (app in config.includedApplications) {
Log.d(TAG, "addAllowedApplication: $app")
vpnBuilder.addAllowedApplication(app)
}
for (app in config.excludedApplications) { for (app in config.excludedApplications) {
Log.d(TAG, "addDisallowedApplication: $app") Log.d(TAG, "addDisallowedApplication: $app")
vpnBuilder.addDisallowedApplication(app) vpnBuilder.addDisallowedApplication(app)
@@ -16,6 +16,7 @@ open class ProtocolConfig protected constructor(
val excludedRoutes: Set<InetNetwork>, val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>, val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>, val excludedAddresses: Set<InetNetwork>,
val includedApplications: Set<String>,
val excludedApplications: Set<String>, val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?, val httpProxy: ProxyInfo?,
val allowAllAF: Boolean, val allowAllAF: Boolean,
@@ -31,6 +32,7 @@ open class ProtocolConfig protected constructor(
builder.excludedRoutes, builder.excludedRoutes,
builder.includedAddresses, builder.includedAddresses,
builder.excludedAddresses, builder.excludedAddresses,
builder.includedApplications,
builder.excludedApplications, builder.excludedApplications,
builder.httpProxy, builder.httpProxy,
builder.allowAllAF, builder.allowAllAF,
@@ -45,6 +47,7 @@ open class ProtocolConfig protected constructor(
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf() internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf() internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf() internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val includedApplications: MutableSet<String> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf() internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null internal var searchDomain: String? = null
@@ -88,6 +91,9 @@ open class ProtocolConfig protected constructor(
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr } fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses } fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun includeApplication(application: String) = apply { this.includedApplications += application }
fun includeApplications(applications: Collection<String>) = apply { this.includedApplications += applications }
fun excludeApplication(application: String) = apply { this.excludedApplications += application } fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications } fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@@ -7,6 +7,7 @@ import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
@@ -24,12 +25,15 @@ import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.getStatistics import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController import org.amnezia.vpn.qt.QtAndroidController
@@ -476,4 +480,32 @@ class AmneziaActivity : QtActivity() {
window.setFlags(flag, LayoutParams.FLAG_SECURE) window.setFlags(flag, LayoutParams.FLAG_SECURE)
} }
} }
@Suppress("unused")
fun minimizeApp() {
Log.v(TAG, "Minimize application")
mainScope.launch {
moveTaskToBack(false)
}
}
@Suppress("unused")
fun getAppList(): String {
Log.v(TAG, "Get app list")
var appList = ""
runBlocking {
mainScope.launch {
withContext(Dispatchers.IO) {
appList = AppListProvider.getAppList(packageManager, packageName)
}
}.join()
}
return appList
}
@Suppress("unused")
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
Log.v(TAG, "Get app icon: $packageName")
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
}
} }
@@ -0,0 +1,73 @@
import android.Manifest.permission.INTERNET
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import androidx.core.graphics.drawable.toBitmapOrNull
import org.amnezia.vpn.util.Log
import org.json.JSONArray
import org.json.JSONObject
private const val TAG = "AppListProvider"
object AppListProvider {
fun getAppList(pm: PackageManager, selfPackageName: String): String {
val jsonArray = JSONArray()
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
.filter { it.packageName != selfPackageName }
.map { App(it, pm) }
.sortedWith(App::compareTo)
.map(App::toJson)
.forEach(jsonArray::put)
return jsonArray.toString()
}
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
val icon = try {
pm.getApplicationIcon(packageName)
} catch (e: NameNotFoundException) {
Log.e(TAG, "Package $packageName was not found: $e")
pm.defaultActivityIcon
}
val w: Int = if (width > 0) width else icon.intrinsicWidth
val h: Int = if (height > 0) height else icon.intrinsicHeight
return icon.toBitmapOrNull(w, h, ARGB_8888)
?: Bitmap.createBitmap(w, h, ARGB_8888)
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {
val r = other.isLaunchable.compareTo(isLaunchable)
if (r != 0) return r
if (name != other.name) {
return when {
name == null -> 1
other.name == null -> -1
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
}
}
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
}
fun toJson(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("package", packageName)
jsonObject.put("name", name)
jsonObject.put("icon", icon)
jsonObject.put("launchable", isLaunchable)
return jsonObject
}
}
@@ -95,6 +95,7 @@ open class Wireguard : Protocol() {
return WireguardConfig.build { return WireguardConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config)
} }
} }
+3 -1
View File
@@ -20,7 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate) set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
@@ -30,6 +30,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
@@ -38,6 +39,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
) )
foreach(abi IN ITEMS ${QT_ANDROID_ABIS}) foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
+14
View File
@@ -22,6 +22,20 @@ namespace amnezia
} }
}; };
struct InstalledAppInfo {
QString appName;
QString packageName;
QString appPath;
bool operator==(const InstalledAppInfo& other) const {
if (!packageName.isEmpty()) {
return packageName == other.packageName;
} else {
return appPath == other.appPath;
}
}
};
enum ErrorCode { enum ErrorCode {
// General error codes // General error codes
NoError = 0, NoError = 0,
@@ -0,0 +1,12 @@
#include "installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
InstalledAppsImageProvider::InstalledAppsImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap)
{
}
QPixmap InstalledAppsImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
return AndroidController::instance()->getAppIcon(id, size, requestedSize);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef INSTALLEDAPPSIMAGEPROVIDER_H
#define INSTALLEDAPPSIMAGEPROVIDER_H
#include <QObject>
#include <QQuickImageProvider>
class InstalledAppsImageProvider : public QQuickImageProvider
{
public:
InstalledAppsImageProvider();
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
};
#endif // INSTALLEDAPPSIMAGEPROVIDER_H
+23
View File
@@ -10,6 +10,8 @@
#include <Iptypes.h> #include <Iptypes.h>
#include <WinSock2.h> #include <WinSock2.h>
#include <winsock.h> #include <winsock.h>
#include <QNetworkInterface>
#include "qendian.h"
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <arpa/inet.h> #include <arpa/inet.h>
@@ -159,6 +161,27 @@ bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
return false; return false;
} }
// static
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
if (result != NO_ERROR) {
return -1;
}
auto adapter =
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
qDebug() << "Internet Adapter:" << adapter.name();
return routeInfo.dwForwardIfIndex;
#endif
return 0;
}
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
DWORD GetAdaptersAddressesWrapper(const ULONG Family, DWORD GetAdaptersAddressesWrapper(const ULONG Family,
const ULONG Flags, const ULONG Flags,
+4
View File
@@ -4,6 +4,8 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QRegExp> #include <QRegExp>
#include <QString> #include <QString>
#include <QHostAddress>
class NetworkUtilities : public QObject class NetworkUtilities : public QObject
{ {
@@ -14,6 +16,8 @@ public:
static bool checkIPv4Format(const QString &ip); static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip); static bool checkIpSubnetFormat(const QString &ip);
static QString getGatewayAndIface(); static QString getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
static QRegularExpression ipAddressRegExp(); static QRegularExpression ipAddressRegExp();
static QRegularExpression ipAddressPortRegExp(); static QRegularExpression ipAddressPortRegExp();
+4 -2
View File
@@ -35,8 +35,10 @@ class Daemon : public QObject {
virtual QJsonObject getStatus(); virtual QJsonObject getStatus();
// Callback before any Activating measure is done // Callback before any Activating measure is done
virtual void prepareActivation(const InterfaceConfig& config){ virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
Q_UNUSED(config)}; Q_UNUSED(config) };
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
Q_UNUSED(config) };
QString logs(); QString logs();
void cleanLogs(); void cleanLogs();
+4 -6
View File
@@ -117,6 +117,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray(); QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
QJsonObject json; QJsonObject json;
@@ -217,12 +220,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("excludedAddresses", jsExcludedAddresses); json.insert("excludedAddresses", jsExcludedAddresses);
json.insert("vpnDisabledApps", splitTunnelApps);
// QJsonArray splitTunnelApps;
// for (const auto& uri : hop.m_vpnDisabledApps) {
// splitTunnelApps.append(QJsonValue(uri));
// }
// json.insert("vpnDisabledApps", splitTunnelApps);
if (protocolName == amnezia::config_key::awg) { if (protocolName == amnezia::config_key::awg) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
@@ -2,6 +2,9 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QQmlFile> #include <QQmlFile>
#include <QEventLoop> #include <QEventLoop>
#include <QImage>
#include <android/bitmap.h>
#include "android_controller.h" #include "android_controller.h"
#include "android_utils.h" #include "android_utils.h"
@@ -209,6 +212,51 @@ void AndroidController::setScreenshotsEnabled(bool enabled)
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled); callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
} }
void AndroidController::minimizeApp()
{
callActivityMethod("minimizeApp", "()V");
}
QJsonArray AndroidController::getAppList()
{
QJniObject appList = callActivityMethod<jstring>("getAppList", "()Ljava/lang/String;");
QJsonArray jsonAppList = QJsonDocument::fromJson(appList.toString().toUtf8()).array();
return jsonAppList;
}
QPixmap AndroidController::getAppIcon(const QString &package, QSize *size, const QSize &requestedSize)
{
QJniObject bitmap = callActivityMethod<jobject>("getAppIcon", "(Ljava/lang/String;II)Landroid/graphics/Bitmap;",
QJniObject::fromString(package).object<jstring>(),
requestedSize.width(), requestedSize.height());
QJniEnvironment env;
AndroidBitmapInfo info;
if (AndroidBitmap_getInfo(env.jniEnv(), bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
void *pixels;
if (AndroidBitmap_lockPixels(env.jniEnv(), bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
int width = info.width;
int height = info.height;
size->setWidth(width);
size->setHeight(height);
QImage image(width, height, QImage::Format_RGBA8888);
if (info.stride == uint32_t(image.bytesPerLine())) {
memcpy((void *) image.constBits(), pixels, info.stride * height);
} else {
auto *bmpPtr = static_cast<uchar *>(pixels);
for (int i = 0; i < height; i++, bmpPtr += info.stride)
memcpy((void *) image.constScanLine(i), bmpPtr, width);
}
if (AndroidBitmap_unlockPixels(env.jniEnv(), bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
return QPixmap::fromImage(image);
}
// Moving log processing to the Android side // Moving log processing to the Android side
jclass AndroidController::log; jclass AndroidController::log;
jmethodID AndroidController::logDebug; jmethodID AndroidController::logDebug;
@@ -2,6 +2,7 @@
#define ANDROID_CONTROLLER_H #define ANDROID_CONTROLLER_H
#include <QJniObject> #include <QJniObject>
#include <QPixmap>
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
@@ -40,6 +41,9 @@ public:
void exportLogsFile(const QString &fileName); void exportLogsFile(const QString &fileName);
void clearLogs(); void clearLogs();
void setScreenshotsEnabled(bool enabled); void setScreenshotsEnabled(bool enabled);
void minimizeApp();
QJsonArray getAppList();
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
static bool initLogging(); static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@@ -18,6 +18,7 @@
#include "dnsutilswindows.h" #include "dnsutilswindows.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
#include "core/networkUtilities.h"
#include "platforms/windows/windowscommons.h" #include "platforms/windows/windowscommons.h"
#include "platforms/windows/windowsservicemanager.h" #include "platforms/windows/windowsservicemanager.h"
#include "windowsfirewall.h" #include "windowsfirewall.h"
@@ -43,11 +44,24 @@ WindowsDaemon::~WindowsDaemon() {
logger.debug() << "Daemon released"; logger.debug() << "Daemon released";
} }
void WindowsDaemon::prepareActivation(const InterfaceConfig& config) { void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAdapterIndex) {
// Before creating the interface we need to check which adapter // Before creating the interface we need to check which adapter
// routes to the server endpoint // routes to the server endpoint
auto serveraddr = QHostAddress(config.m_serverIpv4AddrIn); if (inetAdapterIndex == 0) {
m_inetAdapterIndex = WindowsCommons::AdapterIndexTo(serveraddr); auto serveraddr = QHostAddress(config.m_serverIpv4AddrIn);
m_inetAdapterIndex = NetworkUtilities::AdapterIndexTo(serveraddr);
} else {
m_inetAdapterIndex = inetAdapterIndex;
}
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager.stop();
}
} }
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) { bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
@@ -64,12 +78,8 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
} }
} }
if (config.m_vpnDisabledApps.length() > 0) { activateSplitTunnel(config);
m_splitTunnelManager.start(m_inetAdapterIndex);
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager.stop();
}
return true; return true;
} }
@@ -20,7 +20,8 @@ class WindowsDaemon final : public Daemon {
WindowsDaemon(); WindowsDaemon();
~WindowsDaemon(); ~WindowsDaemon();
void prepareActivation(const InterfaceConfig& config) override; void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) override;
void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) override;
protected: protected:
bool run(Op op, const InterfaceConfig& config) override; bool run(Op op, const InterfaceConfig& config) override;
@@ -134,7 +134,7 @@ void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
logger.debug() << "New Configuration applied: " << getState(); logger.debug() << "New Configuration applied: " << getState();
} }
void WindowsSplitTunnel::start(int inetAdapterIndex) { void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
// To Start we need to send 2 things: // To Start we need to send 2 things:
// Network info (what is vpn what is network) // Network info (what is vpn what is network)
logger.debug() << "Starting SplitTunnel"; logger.debug() << "Starting SplitTunnel";
@@ -171,7 +171,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex) {
} }
logger.debug() << "Driver is ready || new State:" << getState(); logger.debug() << "Driver is ready || new State:" << getState();
auto config = generateIPConfiguration(inetAdapterIndex); auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0], auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
(DWORD)config.size(), nullptr, 0, &bytesReturned, (DWORD)config.size(), nullptr, 0, &bytesReturned,
nullptr); nullptr);
@@ -270,14 +270,19 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
} }
std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration( std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
int inetAdapterIndex) { int inetAdapterIndex, int vpnAdapterIndex) {
std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG)); std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG));
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]); auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
auto ifaces = QNetworkInterface::allInterfaces(); auto ifaces = QNetworkInterface::allInterfaces();
if (vpnAdapterIndex == 0) {
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
}
// Always the VPN // Always the VPN
getAddress(WindowsCommons::VPNAdapterIndex(), &config->TunnelIpv4, getAddress(vpnAdapterIndex, &config->TunnelIpv4,
&config->TunnelIpv6); &config->TunnelIpv6);
// 2nd best route // 2nd best route
getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6); getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
@@ -132,7 +132,7 @@ class WindowsSplitTunnel final : public QObject {
void setRules(const QStringList& appPaths); void setRules(const QStringList& appPaths);
// Fetches and Pushed needed info to move to engaged mode // Fetches and Pushed needed info to move to engaged mode
void start(int inetAdapterIndex); void start(int inetAdapterIndex, int vpnAdapterIndex = 0);
// Deletes Rules and puts the driver into passive mode // Deletes Rules and puts the driver into passive mode
void stop(); void stop();
// Resets the Whole Driver // Resets the Whole Driver
@@ -164,7 +164,7 @@ class WindowsSplitTunnel final : public QObject {
// Generates a Configuration for Each APP // Generates a Configuration for Each APP
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths); std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
// Generates a Configuration which IP's are VPN and which network // Generates a Configuration which IP's are VPN and which network
std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex); std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
std::vector<uint8_t> generateProcessBlob(); std::vector<uint8_t> generateProcessBlob();
void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6); void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
@@ -88,24 +88,6 @@ QString WindowsCommons::tunnelLogFile() {
return QString(); return QString();
} }
// static
int WindowsCommons::AdapterIndexTo(const QHostAddress& dst) {
logger.debug() << "Getting Current Internet Adapter that routes to"
<< logger.sensitive(dst.toString());
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
if (result != NO_ERROR) {
return -1;
}
auto adapter =
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
logger.debug() << "Internet Adapter:" << adapter.name();
return routeInfo.dwForwardIfIndex;
}
// static // static
int WindowsCommons::VPNAdapterIndex() { int WindowsCommons::VPNAdapterIndex() {
// For someReason QNetworkInterface::fromName(MozillaVPN) does not work >:( // For someReason QNetworkInterface::fromName(MozillaVPN) does not work >:(
+1 -2
View File
@@ -19,8 +19,7 @@ class WindowsCommons final {
// Returns the Interface Index of the VPN Adapter // Returns the Interface Index of the VPN Adapter
static int VPNAdapterIndex(); static int VPNAdapterIndex();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
// Returns the Path of the Current process // Returns the Path of the Current process
static QString getCurrentPath(); static QString getCurrentPath();
}; };
+3
View File
@@ -331,13 +331,16 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
m_vpnLocalAddress = l.split(" ").at(1); m_vpnLocalAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QThread::msleep(300);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces(); QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) { for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{ {
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway); m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_configData.value(amnezia::config_key::hostName).toString());
IpcClient::Interface()->enablePeerTraffic(m_configData); IpcClient::Interface()->enablePeerTraffic(m_configData);
} }
} }
+3
View File
@@ -89,6 +89,9 @@ namespace amnezia
constexpr char splitTunnelSites[] = "splitTunnelSites"; constexpr char splitTunnelSites[] = "splitTunnelSites";
constexpr char splitTunnelType[] = "splitTunnelType"; constexpr char splitTunnelType[] = "splitTunnelType";
constexpr char splitTunnelApps[] = "splitTunnelApps";
constexpr char appSplitTunnelType[] = "appSplitTunnelType";
constexpr char crc[] = "crc"; constexpr char crc[] = "crc";
} }
+3
View File
@@ -112,6 +112,7 @@ ErrorCode XrayProtocol::startTun2Sock()
m_t2sProcess->setProgram(PermittedProcess::Tun2Socks); m_t2sProcess->setProgram(PermittedProcess::Tun2Socks);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
m_configData.insert("inetAdapterIndex", NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress)));
QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr, "-tun-post-up", QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr, "-tun-post-up",
QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255").arg(amnezia::protocols::xray::defaultLocalAddr)}); QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255").arg(amnezia::protocols::xray::defaultLocalAddr)});
#endif #endif
@@ -166,7 +167,9 @@ ErrorCode XrayProtocol::startTun2Sock()
{ {
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway); m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enablePeerTraffic(m_configData); IpcClient::Interface()->enablePeerTraffic(m_configData);
} }
} }
+2
View File
@@ -235,6 +235,8 @@
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file> <file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>images/controls/split-tunneling.svg</file> <file>images/controls/split-tunneling.svg</file>
<file>ui/qml/Controls2/DrawerType2.qml</file> <file>ui/qml/Controls2/DrawerType2.qml</file>
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
<file>images/controls/alert-circle.svg</file> <file>images/controls/alert-circle.svg</file>
<file>images/controls/file-check-2.svg</file> <file>images/controls/file-check-2.svg</file>
<file>ui/qml/Controls2/WarningType.qml</file> <file>ui/qml/Controls2/WarningType.qml</file>
+48
View File
@@ -355,6 +355,54 @@ void Settings::clearSettings()
emit settingsCleared(); emit settingsCleared();
} }
QString Settings::appsRouteModeString(AppsRouteMode mode) const
{
switch (mode) {
case VpnAllApps: return "AllApps";
case VpnOnlyForwardApps: return "ForwardApps";
case VpnAllExceptApps: return "ExceptApps";
}
}
Settings::AppsRouteMode Settings::getAppsRouteMode() const
{
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt());
}
void Settings::setAppsRouteMode(AppsRouteMode mode)
{
setValue("Conf/appsRouteMode", mode);
}
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
{
QVector<InstalledAppInfo> apps;
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray();
for (const auto &app : appsArray) {
InstalledAppInfo appInfo;
appInfo.appName = app.toObject().value("appName").toString();
appInfo.packageName = app.toObject().value("packageName").toString();
appInfo.appPath = app.toObject().value("appPath").toString();
apps.push_back(appInfo);
}
return apps;
}
void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &apps)
{
QJsonArray appsArray;
for (const auto &app : apps) {
QJsonObject appInfo;
appInfo.insert("appName", app.appName);
appInfo.insert("packageName", app.packageName);
appInfo.insert("appPath", app.appPath);
appsArray.push_back(appInfo);
}
setValue("Conf/" + appsRouteModeString(mode), appsArray);
m_settings.sync();
}
ServerCredentials Settings::defaultServerCredentials() const ServerCredentials Settings::defaultServerCredentials() const
{ {
return serverCredentials(defaultServerIndex()); return serverCredentials(defaultServerIndex());
+15
View File
@@ -193,6 +193,21 @@ public:
void clearSettings(); void clearSettings();
enum AppsRouteMode {
VpnAllApps,
VpnOnlyForwardApps,
VpnAllExceptApps
};
Q_ENUM(AppsRouteMode)
QString appsRouteModeString(AppsRouteMode mode) const;
AppsRouteMode getAppsRouteMode() const;
void setAppsRouteMode(AppsRouteMode mode);
QVector<InstalledAppInfo> getVpnApps(AppsRouteMode mode) const;
void setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &apps);
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged(bool enabled);
void screenshotsEnabledChanged(bool enabled); void screenshotsEnabledChanged(bool enabled);
@@ -0,0 +1,50 @@
#include "appSplitTunnelingController.h"
#include <QFileInfo>
#include "core/defs.h"
AppSplitTunnelingController::AppSplitTunnelingController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<AppSplitTunnelingModel> &appSplitTunnelingModel, QObject *parent)
: QObject(parent), m_settings(settings), m_appSplitTunnelingModel(appSplitTunnelingModel)
{
}
void AppSplitTunnelingController::addApp(const QString &appPath)
{
InstalledAppInfo appInfo { "", "", appPath };
if (!appPath.isEmpty()) {
QFileInfo fileInfo(appPath);
appInfo.appName = fileInfo.fileName();
}
if (m_appSplitTunnelingModel->addApp(appInfo)) {
emit finished(tr("Application added: %1").arg(appInfo.appName));
} else {
emit errorOccurred(tr("The application has already been added"));
}
}
void AppSplitTunnelingController::addApps(QVector<QPair<QString, QString>> apps)
{
qDebug() << apps;
for (const auto &app : apps) {
InstalledAppInfo appInfo { app.first, app.second, "" };
m_appSplitTunnelingModel->addApp(appInfo);
}
emit finished(tr("The selected applications have been added"));
}
void AppSplitTunnelingController::removeApp(const int index)
{
auto modelIndex = m_appSplitTunnelingModel->index(index);
auto appPath = m_appSplitTunnelingModel->data(modelIndex, AppSplitTunnelingModel::Roles::AppPathRole).toString();
m_appSplitTunnelingModel->removeApp(modelIndex);
QFileInfo fileInfo(appPath);
emit finished(tr("Application removed: %1").arg(fileInfo.fileName()));
}
@@ -0,0 +1,31 @@
#ifndef APPSPLITTUNNELINGCONTROLLER_H
#define APPSPLITTUNNELINGCONTROLLER_H
#include <QObject>
#include "settings.h"
#include "ui/models/appSplitTunnelingModel.h"
class AppSplitTunnelingController : public QObject
{
Q_OBJECT
public:
explicit AppSplitTunnelingController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<AppSplitTunnelingModel> &sitesModel, QObject *parent = nullptr);
public slots:
void addApp(const QString &appPath);
void addApps(QVector<QPair<QString, QString>> apps);
void removeApp(const int index);
signals:
void errorOccurred(const QString &errorMessage);
void finished(const QString &message);
private:
std::shared_ptr<Settings> m_settings;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
};
#endif // APPSPLITTUNNELINGCONTROLLER_H
+8
View File
@@ -7,6 +7,7 @@
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#include "platforms/android/android_utils.h" #include "platforms/android/android_utils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
@@ -74,6 +75,13 @@ void PageController::closeWindow()
#endif #endif
} }
void PageController::hideWindow()
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->minimizeApp();
#endif
}
void PageController::keyPressEvent(Qt::Key key) void PageController::keyPressEvent(Qt::Key key)
{ {
switch (key) { switch (key) {
+3
View File
@@ -29,6 +29,7 @@ namespace PageLoader
PageSettingsAbout, PageSettingsAbout,
PageSettingsLogging, PageSettingsLogging,
PageSettingsSplitTunneling, PageSettingsSplitTunneling,
PageSettingsAppSplitTunneling,
PageServiceSftpSettings, PageServiceSftpSettings,
PageServiceTorWebsiteSettings, PageServiceTorWebsiteSettings,
@@ -76,6 +77,7 @@ public slots:
QString getPagePath(PageLoader::PageEnum page); QString getPagePath(PageLoader::PageEnum page);
void closeWindow(); void closeWindow();
void hideWindow();
void keyPressEvent(Qt::Key key); void keyPressEvent(Qt::Key key);
unsigned int getInitialPageNavigationBarColor(); unsigned int getInitialPageNavigationBarColor();
@@ -110,6 +112,7 @@ signals:
void showBusyIndicator(bool visible); void showBusyIndicator(bool visible);
void disableControls(bool disabled); void disableControls(bool disabled);
void disableTabBar(bool disabled);
void hideMainWindow(); void hideMainWindow();
void raiseMainWindow(); void raiseMainWindow();
+101
View File
@@ -0,0 +1,101 @@
#include "appSplitTunnelingModel.h"
#include <QFileInfo>
AppSplitTunnelingModel::AppSplitTunnelingModel(std::shared_ptr<Settings> settings, QObject *parent)
: QAbstractListModel(parent), m_settings(settings)
{
auto routeMode = m_settings->getAppsRouteMode();
if (routeMode == Settings::AppsRouteMode::VpnAllApps) {
m_isSplitTunnelingEnabled = false;
m_currentRouteMode = Settings::AppsRouteMode::VpnAllExceptApps;
} else {
m_isSplitTunnelingEnabled = true;
m_currentRouteMode = routeMode;
}
m_apps = m_settings->getVpnApps(m_currentRouteMode);
}
int AppSplitTunnelingModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_apps.size();
}
QVariant AppSplitTunnelingModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant();
switch (role) {
case AppPathRole: {
return m_apps.at(index.row()).appName;
}
default: {
return true;
}
}
return QVariant();
}
bool AppSplitTunnelingModel::addApp(const InstalledAppInfo &appInfo)
{
if (m_apps.contains(appInfo)) {
return false;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_apps.append(appInfo);
m_settings->setVpnApps(m_currentRouteMode, m_apps);
endInsertRows();
qDebug() << "app added " << appInfo.appName;
return true;
}
void AppSplitTunnelingModel::removeApp(QModelIndex index)
{
beginRemoveRows(QModelIndex(), index.row(), index.row());
m_apps.removeAt(index.row());
m_settings->setVpnApps(m_currentRouteMode, m_apps);
endRemoveRows();
}
int AppSplitTunnelingModel::getRouteMode()
{
return m_currentRouteMode;
}
void AppSplitTunnelingModel::setRouteMode(int routeMode)
{
beginResetModel();
m_settings->setAppsRouteMode(static_cast<Settings::AppsRouteMode>(routeMode));
m_currentRouteMode = m_settings->getAppsRouteMode();
m_apps = m_settings->getVpnApps(m_currentRouteMode);
endResetModel();
emit routeModeChanged();
}
bool AppSplitTunnelingModel::isSplitTunnelingEnabled()
{
return m_isSplitTunnelingEnabled;
}
void AppSplitTunnelingModel::toggleSplitTunneling(bool enabled)
{
if (enabled) {
setRouteMode(m_currentRouteMode);
} else {
m_settings->setAppsRouteMode(Settings::AppsRouteMode::VpnAllApps);
}
m_isSplitTunnelingEnabled = enabled;
emit splitTunnelingToggled();
}
QHash<int, QByteArray> AppSplitTunnelingModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[AppPathRole] = "appPath";
return roles;
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef APPSPLITTUNNELINGMODEL_H
#define APPSPLITTUNNELINGMODEL_H
#include <QAbstractListModel>
#include "settings.h"
#include "core/defs.h"
class AppSplitTunnelingModel: public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
AppPathRole = Qt::UserRole + 1,
PackageAppNameRole,
PackageAppIconRole
};
explicit AppSplitTunnelingModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged)
Q_PROPERTY(bool isTunnelingEnabled READ isSplitTunnelingEnabled NOTIFY splitTunnelingToggled)
public slots:
bool addApp(const InstalledAppInfo &appInfo);
void removeApp(QModelIndex index);
int getRouteMode();
void setRouteMode(int routeMode);
bool isSplitTunnelingEnabled();
void toggleSplitTunneling(bool enabled);
signals:
void routeModeChanged();
void splitTunnelingToggled();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
std::shared_ptr<Settings> m_settings;
bool m_isSplitTunnelingEnabled;
Settings::AppsRouteMode m_currentRouteMode;
QVector<InstalledAppInfo> m_apps;
};
#endif // APPSPLITTUNNELINGMODEL_H
+96
View File
@@ -0,0 +1,96 @@
#include "installedAppsModel.h"
#include <QEventLoop>
#include <QtConcurrent>
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
InstalledAppsModel::InstalledAppsModel(QObject *parent) : QAbstractListModel(parent)
{
}
int InstalledAppsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_installedApps.size();
}
QVariant InstalledAppsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant();
switch (role) {
case AppNameRole: {
auto appName = m_installedApps.at(index.row()).toObject().value("name").toString();
auto packageName = m_installedApps.at(index.row()).toObject().value("package").toString();
if (appName.isEmpty()) {
appName = packageName;
}
return appName;
}
case AppIconRole: {
return m_installedApps.at(index.row()).toObject().value("package").toString();
}
case PackageNameRole: {
return m_installedApps.at(index.row()).toObject().value("package");
}
}
return QVariant();
}
void InstalledAppsModel::selectedStateChanged(const int index, const bool selected)
{
if (selected) {
m_selectedAppIndexes.insert(index);
} else {
m_selectedAppIndexes.remove(index);
}
}
QVector<QPair<QString, QString>> InstalledAppsModel::getSelectedAppsInfo()
{
QVector<QPair<QString, QString>> appsInfo;
for (const auto i : m_selectedAppIndexes) {
QString packageName = data(index(i, 0), PackageNameRole).toString();
QString appName = data(index(i, 0), AppNameRole).toString();
if (appName.isEmpty()) {
appName = packageName;
}
appsInfo.push_back({ appName, packageName });
}
return appsInfo;
}
void InstalledAppsModel::updateModel()
{
QFuture<void> future = QtConcurrent::run([this]() {
beginResetModel();
#ifdef Q_OS_ANDROID
m_installedApps = AndroidController::instance()->getAppList();
#endif
endResetModel();
});
QFutureWatcher<void> watcher;
QEventLoop wait;
connect(&watcher, &QFutureWatcher<void>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
return;
}
QHash<int, QByteArray> InstalledAppsModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[AppNameRole] = "appName";
roles[AppIconRole] = "appIcon";
roles[PackageNameRole] = "packageName";
return roles;
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef INSTALLEDAPPSMODEL_H
#define INSTALLEDAPPSMODEL_H
#include <QJsonArray>
#include <QAbstractListModel>
class InstalledAppsModel: public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
AppNameRole= Qt::UserRole + 1,
PackageNameRole,
AppIconRole
};
explicit InstalledAppsModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public slots:
void selectedStateChanged(const int index, const bool selected);
QVector<QPair<QString, QString>> getSelectedAppsInfo();
void updateModel();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QJsonArray m_installedApps;
QSet<int> m_selectedAppIndexes;
};
#endif // INSTALLEDAPPSMODEL_H
@@ -11,6 +11,8 @@ import "../Config"
DrawerType2 { DrawerType2 {
id: root id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.7 expandedHeight: parent.height * 0.7
@@ -57,7 +59,7 @@ DrawerType2 {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
enabled: ! ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi") enabled: !ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi")
text: qsTr("Site-based split tunneling") text: qsTr("Site-based split tunneling")
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
@@ -73,20 +75,22 @@ DrawerType2 {
} }
LabelWithButtonType { LabelWithButtonType {
visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true Layout.fillWidth: true
visible: false
text: qsTr("App-based split tunneling") text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
// PageController.goToPage(PageEnum.PageSetupWizardConfigSource) PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.close() root.close()
} }
} }
DividerType { DividerType {
visible: false visible: isAppSplitTinnelingEnabled
} }
} }
} }
@@ -0,0 +1,136 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Controls2"
import "../Controls2/TextTypes"
import InstalledAppsModel 1.0
DrawerType2 {
id: root
anchors.fill: parent
expandedHeight: parent.height * 0.9
onAboutToShow: {
PageController.showBusyIndicator(true)
installedAppsModel.updateModel()
PageController.showBusyIndicator(false)
}
InstalledAppsModel {
id: installedAppsModel
}
expandedContent: Item {
id: container
implicitHeight: expandedHeight
ColumnLayout {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: addButton.top
anchors.topMargin: 16
BackButtonType {
backButtonImage: "qrc:/images/controls/arrow-left.svg"
backButtonFunction: function() {
root.close()
}
}
Header2Type {
id: header
Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Choose application")
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
clip: true
interactive: true
model: installedAppsModel
ScrollBar.vertical: ScrollBar {
id: scrollBar
policy: ScrollBar.AlwaysOn
}
ButtonGroup {
id: buttonGroup
}
delegate: Item {
implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.fill: parent
RowLayout {
CheckBoxType {
Layout.fillWidth: true
text: appName
onCheckedChanged: {
listView.model.selectedStateChanged(index, checked)
}
}
Image {
source: "image://installedAppImage/" + appIcon
sourceSize.width: 24
sourceSize.height: 24
Layout.rightMargin: 48
}
}
DividerType {}
}
}
}
}
BasicButtonType {
id: addButton
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 16
anchors.rightMargin: 16
anchors.leftMargin: 16
text: qsTr("Add selected")
clickedFunc: function() {
PageController.showBusyIndicator(true)
AppSplitTunnelingController.addApps(listView.model.getSelectedAppsInfo())
PageController.showBusyIndicator(false)
root.close()
}
}
}
}
+4 -4
View File
@@ -90,12 +90,12 @@ CheckBox {
} }
contentItem: Item { contentItem: Item {
implicitWidth: content.implicitWidth anchors.left: parent.left
implicitHeight: content.implicitHeight anchors.right: parent.right
anchors.fill: parent
anchors.leftMargin: 8 + background.width anchors.leftMargin: 8 + background.width
implicitHeight: content.implicitHeight
ColumnLayout { ColumnLayout {
id: content id: content
+1 -9
View File
@@ -86,13 +86,6 @@ Item {
} }
} }
/** Set once based on first implicit height change once all children are layed out */
Component.onCompleted: {
if (root.isCollapsed && root.collapsedHeight == 0) {
root.collapsedHeight = drawerContent.implicitHeight
}
}
Rectangle { Rectangle {
id: background id: background
@@ -186,7 +179,7 @@ Item {
y: root.height - drawerContent.height y: root.height - drawerContent.height
state: root.drawerCollapsed state: root.drawerCollapsed
implicitHeight: root.isCollapsed ? collapsedLoader.implicitHeight : expandedLoader.implicitHeight implicitHeight: root.isCollapsed ? collapsedHeight : expandedHeight
onStateChanged: { onStateChanged: {
if (root.isCollapsed) { if (root.isCollapsed) {
@@ -246,7 +239,6 @@ Item {
Loader { Loader {
id: collapsedLoader id: collapsedLoader
visible: root.isCollapsed
sourceComponent: root.collapsedContent sourceComponent: root.collapsedContent
anchors.right: parent.right anchors.right: parent.right
@@ -126,32 +126,6 @@ Item {
} }
} }
} }
BasicButtonType {
visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
// defaultColor: "transparent"
// hoveredColor: Qt.rgba(1, 1, 1, 0.08)
// pressedColor: Qt.rgba(1, 1, 1, 0.12)
// disabledColor: "#878B91"
// textColor: "#D7D8DB"
// borderWidth: 0
focusPolicy: Qt.NoFocus
text: root.buttonText
imageSource: root.buttonImageSource
// Layout.rightMargin: 24
Layout.preferredHeight: content.implicitHeight
Layout.preferredWidth: content.implicitHeight
squareLeftSide: true
clickedFunc: function() {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}
} }
} }
@@ -187,6 +161,28 @@ Item {
} }
} }
BasicButtonType {
visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
focusPolicy: Qt.NoFocus
text: root.buttonText
imageSource: root.buttonImageSource
anchors.top: content.top
anchors.bottom: content.bottom
anchors.right: content.right
height: content.implicitHeight
width: content.implicitHeight
squareLeftSide: true
clickedFunc: function() {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}
function getBackgroundBorderColor(noneFocusedColor) { function getBackgroundBorderColor(noneFocusedColor) {
return textField.focus ? root.borderFocusedColor : noneFocusedColor return textField.focus ? root.borderFocusedColor : noneFocusedColor
} }
+2 -2
View File
@@ -14,8 +14,8 @@ import "../Config"
PageType { PageType {
id: root id: root
Component.onCompleted: PageController.disableControls(true) Component.onCompleted: PageController.disableTabBar(true)
Component.onDestruction: PageController.disableControls(false) Component.onDestruction: PageController.disableTabBar(false)
SortFilterProxyModel { SortFilterProxyModel {
id: proxyServersModel id: proxyServersModel
+95 -112
View File
@@ -82,7 +82,7 @@ PageType {
leftImageColor: "transparent" leftImageColor: "transparent"
borderWidth: 0 borderWidth: 0
property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || AppSplitTunnelingModel.isTunnelingEnabled ||
(ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi")) (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
@@ -107,144 +107,128 @@ PageType {
id: drawer id: drawer
anchors.fill: parent anchors.fill: parent
collapsedContent: ColumnLayout { collapsedContent: Item {
DividerType { implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
Layout.topMargin: 10 Component.onCompleted: {
Layout.fillWidth: false drawer.expandedHeight = implicitHeight
Layout.preferredWidth: 20
Layout.preferredHeight: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
} }
ColumnLayout {
id: collapsed
RowLayout { anchors.left: parent.left
Layout.topMargin: 14 anchors.right: parent.right
Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: 0 Component.onCompleted: {
drawer.collapsedHeight = collapsed.implicitHeight
Connections {
target: drawer
function onEntered() {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor
collapsedButtonHeader.opacity = 0.8
}
function onExited() {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 1
}
function onPressed(pressed, entered) {
collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 0.7
}
} }
Header1TextType { DividerType {
id: collapsedButtonHeader Layout.topMargin: 10
Layout.maximumWidth: drawer.width - 48 - 18 - 12 // todo Layout.fillWidth: false
Layout.preferredWidth: 20
maximumLineCount: 2 Layout.preferredHeight: 2
elide: Qt.ElideRight Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: ServersModel.defaultServerName
horizontalAlignment: Qt.AlignHCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
} }
ImageButtonType { RowLayout {
id: collapsedButtonChevron Layout.topMargin: 14
Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.leftMargin: 8 spacing: 0
hoverEnabled: false Connections {
image: "qrc:/images/controls/chevron-down.svg" target: drawer
imageColor: "#d7d8db" function onEntered() {
if (drawer.isCollapsed) {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor
collapsedButtonHeader.opacity = 0.8
} else {
collapsedButtonHeader.opacity = 1
}
}
icon.width: 18 function onExited() {
icon.height: 18 if (drawer.isCollapsed) {
backgroundRadius: 16 collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
horizontalPadding: 4 collapsedButtonHeader.opacity = 1
topPadding: 4 } else {
bottomPadding: 3 collapsedButtonHeader.opacity = 1
}
}
onClicked: { function onPressed(pressed, entered) {
if (drawer.isCollapsed) { if (drawer.isCollapsed) {
drawer.open() collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 0.7
} else {
collapsedButtonHeader.opacity = 1
}
}
}
Header1TextType {
id: collapsedButtonHeader
Layout.maximumWidth: drawer.isCollapsed ? drawer.width - 48 - 18 - 12 : drawer.width// todo
maximumLineCount: 2
elide: Qt.ElideRight
text: ServersModel.defaultServerName
horizontalAlignment: Qt.AlignHCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
}
ImageButtonType {
id: collapsedButtonChevron
Layout.leftMargin: 8
visible: drawer.isCollapsed
hoverEnabled: false
image: "qrc:/images/controls/chevron-down.svg"
imageColor: "#d7d8db"
icon.width: 18
icon.height: 18
backgroundRadius: 16
horizontalPadding: 4
topPadding: 4
bottomPadding: 3
onClicked: {
if (drawer.isCollapsed) {
drawer.open()
}
} }
} }
} }
}
LabelTextType { LabelTextType {
id: collapsedServerMenuDescription id: collapsedServerMenuDescription
Layout.bottomMargin: 10 Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 89 : 44
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: ServersModel.defaultServerDescriptionCollapsed text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
} }
GraphViewType {
id: graph1
Layout.preferredHeight: 50
Layout.fillWidth: true
}
}
expandedContent: Item {
id: serverMenuContainer
implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
Component.onCompleted: {
drawer.expandedHeight = serverMenuContainer.implicitHeight
} }
ColumnLayout { ColumnLayout {
id: serversMenuHeader id: serversMenuHeader
anchors.top: parent.top anchors.top: collapsed.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
Header1TextType {
Layout.fillWidth: true
Layout.topMargin: 14
Layout.leftMargin: 16
Layout.rightMargin: 16
text: ServersModel.defaultServerName
horizontalAlignment: Qt.AlignHCenter
maximumLineCount: 2
elide: Qt.ElideRight
}
LabelTextType {
id: expandedServersMenuDescription
Layout.bottomMargin: ServersModel.isDefaultServerFromApi ? 69 : 24
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
text: ServersModel.defaultServerDescriptionExpanded
}
GraphViewType {
id: graph2
Layout.preferredHeight: 50
Layout.fillWidth: true
}
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: 8 spacing: 8
visible: !ServersModel.isDefaultServerFromApi visible: !ServersModel.isDefaultServerFromApi
onVisibleChanged: expandedServersMenuDescription.Layout
DropDownType { DropDownType {
id: containersDropDown id: containersDropDown
@@ -312,7 +296,6 @@ PageType {
} }
} }
ButtonGroup { ButtonGroup {
id: serversRadioButtonGroup id: serversRadioButtonGroup
} }
@@ -0,0 +1,256 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
QtObject {
id: routeMode
property int allApps: 0
property int onlyForwardApps: 1
property int allExceptApps: 2
}
property list<QtObject> routeModesModel: [
onlyForwardApps,
allExceptApps
]
QtObject {
id: onlyForwardApps
property string name: qsTr("Only the Apps listed here will be accessed through the VPN")
property int type: routeMode.onlyForwardApps
}
QtObject {
id: allExceptApps
property string name: qsTr("Apps from the list should not be accessed via VPN")
property int type: routeMode.allExceptApps
}
function getRouteModesModelIndex() {
var currentRouteMode = AppSplitTunnelingModel.routeMode
if ((routeMode.onlyForwardApps === currentRouteMode) || (routeMode.allApps === currentRouteMode)) {
return 0
} else if (routeMode.allExceptApps === currentRouteMode) {
return 1
}
}
ColumnLayout {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
BackButtonType {
}
RowLayout {
HeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
headerText: qsTr("App split tunneling")
}
SwitcherType {
id: switcher
Layout.fillWidth: true
Layout.rightMargin: 16
checked: AppSplitTunnelingModel.isTunnelingEnabled
onToggled: {
AppSplitTunnelingModel.toggleSplitTunneling(checked)
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
}
}
}
DropDownType {
id: selector
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
drawerHeight: 0.4375
drawerParent: root
headerText: qsTr("Mode")
enabled: Qt.platform.os === "android"
listView: ListViewWithRadioButtonType {
rootWidth: root.width
model: root.routeModesModel
currentIndex: getRouteModesModelIndex()
clickedFunction: function() {
selector.text = selectedText
selector.close()
if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[currentIndex].type) {
AppSplitTunnelingModel.routeMode = root.routeModesModel[currentIndex].type
}
}
Component.onCompleted: {
if (root.routeModesModel[currentIndex].type === AppSplitTunnelingModel.routeMode) {
selector.text = selectedText
} else {
selector.text = root.routeModesModel[0].name
}
}
Connections {
target: AppSplitTunnelingModel
function onRouteModeChanged() {
currentIndex = getRouteModesModelIndex()
}
}
}
}
}
FlickableType {
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight + addAppButton.implicitHeight + addAppButton.anchors.bottomMargin + addAppButton.anchors.topMargin
Column {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView {
id: apps
width: parent.width
height: apps.contentItem.height
model: SortFilterProxyModel {
id: proxyAppSplitTunnelingModel
sourceModel: AppSplitTunnelingModel
filters: RegExpFilter {
roleName: "appPath"
pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
}
}
clip: true
interactive: false
delegate: Item {
implicitWidth: apps.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelWithButtonType {
Layout.fillWidth: true
text: appPath
rightImageSource: "qrc:/images/controls/trash.svg"
rightImageColor: "#D7D8DB"
clickedFunction: function() {
var headerText = qsTr("Remove ") + appPath + "?"
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
AppSplitTunnelingController.removeApp(proxyAppSplitTunnelingModel.mapToSource(index))
}
var noButtonFunction = function() {
}
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
DividerType {}
}
}
}
}
}
Rectangle {
anchors.fill: addAppButton
anchors.bottomMargin: -24
color: "#0E0E11"
opacity: 0.8
}
RowLayout {
id: addAppButton
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
TextFieldWithHeaderType {
id: searchField
Layout.fillWidth: true
textFieldPlaceholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() {
searchField.focus = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "windows") {
var fileName = SystemController.getFileName(qsTr("Open executable file"),
qsTr("Executable file (*.*)"))
if (fileName !== "") {
AppSplitTunnelingController.addApp(fileName)
}
} else if (Qt.platform.os === "android"){
installedAppDrawer.open()
}
PageController.showBusyIndicator(false)
}
}
}
InstalledAppsDrawer {
id: installedAppDrawer
anchors.fill: parent
}
}
@@ -11,6 +11,8 @@ import "../Config"
PageType { PageType {
id: root id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
BackButtonType { BackButtonType {
id: backButton id: backButton
@@ -73,8 +75,6 @@ PageType {
DividerType {} DividerType {}
LabelWithButtonType { LabelWithButtonType {
visible: true
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Site-based split tunneling") text: qsTr("Site-based split tunneling")
@@ -87,11 +87,11 @@ PageType {
} }
DividerType { DividerType {
visible: GC.isDesktop() visible: root.isAppSplitTinnelingEnabled
} }
LabelWithButtonType { LabelWithButtonType {
visible: false visible: root.isAppSplitTinnelingEnabled
Layout.fillWidth: true Layout.fillWidth: true
@@ -100,11 +100,12 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
} }
} }
DividerType { DividerType {
visible: false visible: root.isAppSplitTinnelingEnabled
} }
} }
} }
@@ -22,7 +22,7 @@ PageType {
property var isServerFromApi: ServersModel.getDefaultServerData("isServerFromApi") property var isServerFromApi: ServersModel.getDefaultServerData("isServerFromApi")
defaultActiveFocusItem: website_ip_field.textField defaultActiveFocusItem: searchField.textField
property bool pageEnabled: { property bool pageEnabled: {
return !ConnectionController.isConnected && !isServerFromApi return !ConnectionController.isConnected && !isServerFromApi
@@ -188,7 +188,24 @@ PageType {
width: parent.width width: parent.width
height: sites.contentItem.height height: sites.contentItem.height
model: SitesModel model: SortFilterProxyModel {
id: proxySitesModel
sourceModel: SitesModel
filters: [
AnyOf {
RegExpFilter {
roleName: "url"
pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
}
RegExpFilter {
roleName: "ip"
pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
}
}
]
}
clip: true clip: true
interactive: false interactive: false
@@ -218,7 +235,7 @@ PageType {
var noButtonText = qsTr("Cancel") var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() { var yesButtonFunction = function() {
SitesController.removeSite(index) SitesController.removeSite(proxySitesModel.mapToSource(index))
} }
var noButtonFunction = function() { var noButtonFunction = function() {
} }
@@ -255,7 +272,7 @@ PageType {
anchors.bottomMargin: 24 anchors.bottomMargin: 24
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: website_ip_field id: searchField
Layout.fillWidth: true Layout.fillWidth: true
@@ -430,8 +447,4 @@ PageType {
} }
} }
} }
QuestionDrawer {
id: questionDrawer
}
} }
@@ -14,8 +14,8 @@ import "../Config"
PageType { PageType {
id: root id: root
Component.onCompleted: PageController.disableControls(true) Component.onCompleted: PageController.disableTabBar(true)
Component.onDestruction: PageController.disableControls(false) Component.onDestruction: PageController.disableTabBar(false)
property bool isTimerRunning: true property bool isTimerRunning: true
property string progressBarText: qsTr("Usually it takes no more than 5 minutes") property string progressBarText: qsTr("Usually it takes no more than 5 minutes")
@@ -24,6 +24,7 @@ PageType {
function onClosePage() { function onClosePage() {
if (stackView.depth <= 1) { if (stackView.depth <= 1) {
PageController.hideWindow()
return return
} }
stackView.pop() stackView.pop()
@@ -48,6 +49,10 @@ PageType {
isControlsDisabled = disabled isControlsDisabled = disabled
} }
function onDisableTabBar(disabled) {
isControlsDisabled = disabled
}
function onEscapePressed() { function onEscapePressed() {
if (isControlsDisabled) { if (isControlsDisabled) {
return return
+8 -2
View File
@@ -15,6 +15,7 @@ PageType {
id: root id: root
property bool isControlsDisabled: false property bool isControlsDisabled: false
property bool isTabBarDisabled: false
Connections { Connections {
target: PageController target: PageController
@@ -38,8 +39,13 @@ PageType {
isControlsDisabled = disabled isControlsDisabled = disabled
} }
function onDisableTabBar(disabled) {
isTabBarDisabled = disabled
}
function onClosePage() { function onClosePage() {
if (tabBarStackView.depth <= 1) { if (tabBarStackView.depth <= 1) {
PageController.hideWindow()
return return
} }
tabBarStackView.pop() tabBarStackView.pop()
@@ -63,7 +69,7 @@ PageType {
} }
function onEscapePressed() { function onEscapePressed() {
if (root.isControlsDisabled) { if (root.isControlsDisabled || root.isTabBarDisabled) {
return return
} }
@@ -179,7 +185,7 @@ PageType {
leftPadding: 96 leftPadding: 96
rightPadding: 96 rightPadding: 96
enabled: !root.isControlsDisabled enabled: !root.isControlsDisabled && !root.isTabBarDisabled
background: Shape { background: Shape {
width: parent.width width: parent.width
+11
View File
@@ -428,6 +428,17 @@ void VpnConnection::appendSplitTunnelingConfig()
m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode); m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode);
m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray); m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
auto appsRouteMode = m_settings->getAppsRouteMode();
auto apps = m_settings->getVpnApps(appsRouteMode);
QJsonArray appsJsonArray;
for (const auto &app : apps) {
appsJsonArray.append(app.appPath.isEmpty() ? app.packageName : app.appPath);
}
m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode);
m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray);
} }
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
+17 -4
View File
@@ -12,6 +12,7 @@
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "tapcontroller_win.h" #include "tapcontroller_win.h"
#include "../client/platforms/windows/daemon/windowsfirewall.h" #include "../client/platforms/windows/daemon/windowsfirewall.h"
#include "../client/platforms/windows/daemon/windowsdaemon.h"
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@@ -24,6 +25,7 @@
IpcServer::IpcServer(QObject *parent): IpcServer::IpcServer(QObject *parent):
IpcInterfaceSource(parent) IpcInterfaceSource(parent)
{} {}
int IpcServer::createPrivilegedProcess() int IpcServer::createPrivilegedProcess()
@@ -313,12 +315,13 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString();
config.m_serverPublicKey = "openvpn"; config.m_serverPublicKey = "openvpn";
config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString();
config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString();
int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt();
int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt();
int splitTunnelType = configStr.value("splitTunnelType").toInt(); int splitTunnelType = configStr.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray();
qDebug() << "splitTunnelType " << splitTunnelType << "splitTunnelSites " << splitTunnelSites;
QStringList AllowedIPAddesses; QStringList AllowedIPAddesses;
// Use APP split tunnel // Use APP split tunnel
@@ -332,7 +335,7 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
if (splitTunnelType == 1) { if (splitTunnelType == 1) {
for (auto v : splitTunnelSites) { for (auto v : splitTunnelSites) {
QString ipRange = v.toString(); QString ipRange = v.toString();
if (ipRange.split('/').size() > 1){ if (ipRange.split('/').size() > 1) {
config.m_allowedIPAddressRanges.append( config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit())));
} else { } else {
@@ -350,7 +353,17 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
} }
} }
return WindowsFirewall::instance()->enablePeerTraffic(config); for (const QJsonValue& i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) {
if (!i.isString()) {
break;
}
config.m_vpnDisabledApps.append(i.toString());
}
WindowsFirewall::instance()->enablePeerTraffic(config);
WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex);
WindowsDaemon::instance()->activateSplitTunnel(config, vpnAdapterIndex);
return true;
#endif #endif
return true; return true;
} }