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() {