diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 9fb07d61..439e10c5 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -15,6 +15,15 @@ set(PACKAGES
Core5Compat Concurrent LinguistTools
)
+execute_process(
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ COMMAND git rev-parse --short HEAD
+ OUTPUT_VARIABLE GIT_COMMIT_HASH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
+
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
diff --git a/client/images/controls/split-tunneling.svg b/client/images/controls/split-tunneling.svg
new file mode 100644
index 00000000..3062054d
--- /dev/null
+++ b/client/images/controls/split-tunneling.svg
@@ -0,0 +1,6 @@
+
diff --git a/client/resources.qrc b/client/resources.qrc
index 59a540cc..b9a69023 100644
--- a/client/resources.qrc
+++ b/client/resources.qrc
@@ -224,6 +224,8 @@
ui/qml/Pages2/PageShareFullAccess.qml
images/controls/close.svg
images/controls/search.svg
+ ui/qml/Components/HomeSplitTunnelingDrawer.qml
+ images/controls/split-tunneling.svg
ui/qml/Controls2/DrawerType2.qml
diff --git a/client/translations/amneziavpn_ar.ts b/client/translations/amneziavpn_ar.ts
index 14b6ed03..18af5612 100644
--- a/client/translations/amneziavpn_ar.ts
+++ b/client/translations/amneziavpn_ar.ts
@@ -997,6 +997,11 @@ And if you don't like the app, all the more support it - the donation will
https://amnezia.org
+
+
+ Software version: %1
+ %1 :إصدار البرنامج
+
Check for updates
@@ -2999,11 +3004,6 @@ While it offers a blend of security, stability, and speed, it's essential t
SettingsController
-
-
- Software version
- إصدار البرنامج
-
Backup file is corrupted
diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts
index 7a2172a8..9ae98d19 100644
--- a/client/translations/amneziavpn_fa_IR.ts
+++ b/client/translations/amneziavpn_fa_IR.ts
@@ -1022,6 +1022,11 @@ Already installed containers were found on the server. All installed containers
https://amnezia.org
https://amnezia.org
+
+
+ Software version: %1
+ %1 :نسخه نرمافزار
+
Check for updates
@@ -3107,11 +3112,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin
SettingsController
-
-
- Software version
- نسخه نرمافزار
-
All settings have been reset to default values
diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts
index e0ae9b79..1425197e 100644
--- a/client/translations/amneziavpn_ru.ts
+++ b/client/translations/amneziavpn_ru.ts
@@ -1020,6 +1020,11 @@ Already installed containers were found on the server. All installed containers
https://amnezia.org
https://amnezia.org
+
+
+ Software version: %1
+ Версия ПО: %1
+
Check for updates
@@ -3058,11 +3063,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin
SettingsController
-
-
- Software version
- Версия ПО
-
All settings have been reset to default values
diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts
index 8eacc183..ad996ced 100644
--- a/client/translations/amneziavpn_zh_CN.ts
+++ b/client/translations/amneziavpn_zh_CN.ts
@@ -1067,6 +1067,11 @@ And if you don't like the app, all the more support it - the donation will
https://amnezia.org
+
+
+ Software version: %1
+ 软件版本: %1
+
Check for updates
@@ -3206,11 +3211,6 @@ While it offers a blend of security, stability, and speed, it's essential t
SettingsController
-
-
- Software version
- 软件版本
-
Backup file is corrupted
diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp
index 99645cde..6ec55321 100644
--- a/client/ui/controllers/settingsController.cpp
+++ b/client/ui/controllers/settingsController.cpp
@@ -28,7 +28,7 @@ SettingsController::SettingsController(const QSharedPointer &serve
m_sitesModel(sitesModel),
m_settings(settings)
{
- m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__);
+ m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
#ifdef Q_OS_ANDROID
if (!m_settings->isScreenshotsEnabled()) {
diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp
index a4420255..3c72ee49 100644
--- a/client/ui/models/servers_model.cpp
+++ b/client/ui/models/servers_model.cpp
@@ -220,6 +220,11 @@ bool ServersModel::isDefaultServerCurrentlyProcessed()
return m_defaultServerIndex == m_processedServerIndex;
}
+bool ServersModel::isDefaultServerFromApi()
+{
+ return qvariant_cast(data(m_defaultServerIndex, IsServerFromApiRole));
+}
+
bool ServersModel::isProcessedServerHasWriteAccess()
{
return qvariant_cast(data(m_processedServerIndex, HasWriteAccessRole));
@@ -249,7 +254,7 @@ void ServersModel::editServer(const QJsonObject &server, const int serverIndex)
}
updateContainersModel();
- if (isDefaultServerCurrentlyProcessed()) {
+ if (serverIndex == m_defaultServerIndex) {
auto defaultContainer = qvariant_cast(getDefaultServerData("defaultContainer"));
emit defaultServerDefaultContainerChanged(defaultContainer);
}
@@ -577,3 +582,18 @@ void ServersModel::setProcessedServerData(const QString roleString, const QVaria
}
+bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
+{
+ auto server = m_servers.at(m_defaultServerIndex).toObject();
+ auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
+ auto containerConfig = server.value(config_key::containers).toArray().at(defaultContainer).toObject();
+ auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(defaultContainer)).toObject();
+
+ if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) {
+ return !(protocolConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0"));
+ } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn || defaultContainer == DockerContainer::ShadowSocks) {
+ return !(protocolConfig.value(config_key::last_config).toString().contains("redirect-gateway"));
+ }
+
+ return false;
+}
diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h
index 1adaebae..3e24e46c 100644
--- a/client/ui/models/servers_model.h
+++ b/client/ui/models/servers_model.h
@@ -48,6 +48,8 @@ public:
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged)
Q_PROPERTY(QString defaultServerDescriptionCollapsed READ getDefaultServerDescriptionCollapsed NOTIFY defaultServerDefaultContainerChanged)
Q_PROPERTY(QString defaultServerDescriptionExpanded READ getDefaultServerDescriptionExpanded NOTIFY defaultServerDefaultContainerChanged)
+ Q_PROPERTY(bool isDefaultServerDefaultContainerHasSplitTunneling READ isDefaultServerDefaultContainerHasSplitTunneling NOTIFY defaultServerDefaultContainerChanged)
+ Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged)
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
@@ -59,6 +61,7 @@ public slots:
const QString getDefaultServerDescriptionExpanded();
const QString getDefaultServerDefaultContainerName();
bool isDefaultServerCurrentlyProcessed();
+ bool isDefaultServerFromApi();
bool isProcessedServerHasWriteAccess();
bool isDefaultServerHasWriteAccess();
@@ -103,6 +106,8 @@ public slots:
QVariant getProcessedServerData(const QString roleString);
void setProcessedServerData(const QString roleString, const QVariant &value);
+ bool isDefaultServerDefaultContainerHasSplitTunneling();
+
protected:
QHash roleNames() const override;
diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp
index f6cb9b13..96b6ca60 100644
--- a/client/ui/models/sites_model.cpp
+++ b/client/ui/models/sites_model.cpp
@@ -113,6 +113,7 @@ void SitesModel::toggleSplitTunneling(bool enabled)
m_settings->setRouteMode(Settings::RouteMode::VpnAllSites);
}
m_isSplitTunnelingEnabled = enabled;
+ emit splitTunnelingToggled();
}
QVector > SitesModel::getCurrentSites()
diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h
index ad16b7a3..803b7fd1 100644
--- a/client/ui/models/sites_model.h
+++ b/client/ui/models/sites_model.h
@@ -22,6 +22,7 @@ public:
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 addSite(const QString &hostname, const QString &ip);
@@ -38,6 +39,7 @@ public slots:
signals:
void routeModeChanged();
+ void splitTunnelingToggled();
protected:
QHash roleNames() const override;
diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml
new file mode 100644
index 00000000..bc1f1008
--- /dev/null
+++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml
@@ -0,0 +1,92 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import PageEnum 1.0
+
+import "../Controls2"
+import "../Controls2/TextTypes"
+import "../Config"
+
+DrawerType2 {
+ id: root
+
+ anchors.fill: parent
+ expandedHeight: parent.height * 0.7
+
+ expandedContent: ColumnLayout {
+ id: content
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 0
+
+ Header2Type {
+ Layout.fillWidth: true
+ Layout.topMargin: 24
+ Layout.rightMargin: 16
+ Layout.leftMargin: 16
+ Layout.bottomMargin: 16
+
+ headerText: qsTr("Split tunneling")
+ descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
+ }
+
+ LabelWithButtonType {
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi")
+
+ text: qsTr("Split tunneling on the server")
+ descriptionText: qsTr("Enabled \nCan't be disabled for current server")
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+
+ clickedFunction: function() {
+// PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
+// root.close()
+ }
+ }
+
+ DividerType {
+ visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi")
+ }
+
+ LabelWithButtonType {
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: ! ServersModel.isDefaultServerDefaultContainerHasSplitTunneling || !ServersModel.getDefaultServerData("isServerFromApi")
+
+ text: qsTr("Site-based split tunneling")
+ descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+
+ clickedFunction: function() {
+ PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
+ root.close()
+ }
+ }
+
+ DividerType {
+ }
+
+ LabelWithButtonType {
+ Layout.fillWidth: true
+ visible: false
+
+ text: qsTr("App-based split tunneling")
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+
+ clickedFunction: function() {
+// PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
+ root.close()
+ }
+ }
+
+ DividerType {
+ visible: false
+ }
+ }
+}
diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml
index 77d4b5fb..257486d6 100644
--- a/client/ui/qml/Controls2/BasicButtonType.qml
+++ b/client/ui/qml/Controls2/BasicButtonType.qml
@@ -21,6 +21,8 @@ Button {
property int borderFocusedWidth: 1
property string imageSource
+ property string rightImageSource
+ property string leftImageColor: textColor
property bool squareLeftSide: false
@@ -118,7 +120,7 @@ Button {
layer {
enabled: true
effect: ColorOverlay {
- color: textColor
+ color: leftImageColor
}
}
}
@@ -131,6 +133,21 @@ Button {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
+
+ Image {
+ Layout.preferredHeight: 20
+ Layout.preferredWidth: 20
+
+ source: root.rightImageSource
+ visible: root.rightImageSource === "" ? false : true
+
+ layer {
+ enabled: true
+ effect: ColorOverlay {
+ color: textColor
+ }
+ }
+ }
}
}
diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml
index 4b5ba53d..536fc951 100644
--- a/client/ui/qml/Pages2/PageHome.qml
+++ b/client/ui/qml/Pages2/PageHome.qml
@@ -34,8 +34,45 @@ PageType {
anchors.bottomMargin: drawer.collapsedHeight
ConnectButton {
+ id: connectButton
anchors.centerIn: parent
}
+
+ BasicButtonType {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 34
+ leftPadding: 16
+ rightPadding: 16
+
+ implicitHeight: 36
+
+ defaultColor: "transparent"
+ hoveredColor: Qt.rgba(1, 1, 1, 0.08)
+ pressedColor: Qt.rgba(1, 1, 1, 0.12)
+ disabledColor: "#878B91"
+ textColor: "#878B91"
+ leftImageColor: "transparent"
+ borderWidth: 0
+
+ property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled ||
+ (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
+
+ text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
+
+ imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
+ rightImageSource: "qrc:/images/controls/chevron-down.svg"
+
+ onClicked: {
+ homeSplitTunnelingDrawer.open()
+ }
+
+ HomeSplitTunnelingDrawer {
+ id: homeSplitTunnelingDrawer
+
+ parent: root
+ }
+ }
}
@@ -156,7 +193,7 @@ PageType {
LabelTextType {
id: expandedServersMenuDescription
- Layout.bottomMargin: 24
+ Layout.bottomMargin: ServersModel.isDefaultServerFromApi ? 69 : 24
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
@@ -167,6 +204,9 @@ PageType {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: 8
+ visible: !ServersModel.isDefaultServerFromApi
+ onVisibleChanged: expandedServersMenuDescription.Layout
+
DropDownType {
id: containersDropDown
diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml
index b20c7440..b912e2cd 100644
--- a/client/ui/qml/Pages2/PageSettingsAbout.qml
+++ b/client/ui/qml/Pages2/PageSettingsAbout.qml
@@ -175,7 +175,7 @@ PageType {
horizontalAlignment: Text.AlignHCenter
- text: SettingsController.getAppVersion()
+ text: qsTr("Software version: %1").arg(SettingsController.getAppVersion())
color: "#878B91"
}
diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml
index 729a6e9d..1ce3cd64 100644
--- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml
+++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml
@@ -29,8 +29,14 @@ PageType {
}
Component.onCompleted: {
- if (isServerFromApi) {
+ if (ConnectionController.isConnected) {
+ PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection"))
+ root.pageEnabled = false
+ } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && isServerFromApi) {
PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function"))
+ root.pageEnabled = false
+ } else {
+ root.pageEnabled = true
}
}
@@ -108,7 +114,7 @@ PageType {
Layout.fillWidth: true
Layout.rightMargin: 16
- checked: SitesModel.isSplitTunnelingEnabled()
+ checked: SitesModel.isTunnelingEnabled
onToggled: {
SitesModel.toggleSplitTunneling(checked)
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml
index 38a8e0b8..dac9db93 100644
--- a/client/ui/qml/Pages2/PageStart.qml
+++ b/client/ui/qml/Pages2/PageStart.qml
@@ -44,6 +44,7 @@ PageType {
function onClosePage() {
tabBar.isServerInfoShow = tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsServerInfo)
+ && tabBarStackView.currentItem.objectName !== PageController.getPagePath(PageEnum.PageSettingsSplitTunneling)
if (tabBarStackView.depth <= 1) {
return
@@ -60,7 +61,7 @@ PageType {
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate)
}
- tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || tabBar.isServerInfoShow
+ tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || PageEnum.PageSettingsSplitTunneling || tabBar.isServerInfoShow
}
function onGoToStartPage() {