From 7d187bf88162295fd80ed44aafd0c0a8697fc871 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 13 Apr 2025 20:05:22 +0200 Subject: [PATCH 01/37] replace `FlickableType` and `ListView` with `ListViewType` --- .../controllers/listViewFocusController.cpp | 620 ++++++------ .../qml/Components/HomeContainersListView.qml | 10 +- .../Components/HomeSplitTunnelingDrawer.qml | 194 ++-- .../ui/qml/Components/InstalledAppsDrawer.qml | 54 +- .../qml/Components/SelectLanguageDrawer.qml | 8 +- client/ui/qml/Components/ServersListView.qml | 9 +- .../Components/SettingsContainersListView.qml | 102 +- .../qml/Components/ShareConnectionDrawer.qml | 28 +- client/ui/qml/Controls2/BasicButtonType.qml | 430 ++++----- client/ui/qml/Controls2/CardWithIconsType.qml | 388 ++++---- client/ui/qml/Controls2/CheckBoxType.qml | 331 ++++--- .../ui/qml/Controls2/LabelWithButtonType.qml | 637 ++++++------- client/ui/qml/Controls2/ListViewType.qml | 25 - .../Controls2/ListViewWithRadioButtonType.qml | 9 +- client/ui/qml/Controls2/SwitcherType.qml | 334 ++++--- client/ui/qml/Controls2/TextAreaType.qml | 227 +++-- .../qml/Controls2/TextAreaWithFooterType.qml | 351 ++++--- .../qml/Controls2/TextFieldWithHeaderType.qml | 453 +++++---- client/ui/qml/Pages2/PageDeinstalling.qml | 86 +- client/ui/qml/Pages2/PageDevMenu.qml | 16 +- .../Pages2/PageProtocolAwgClientSettings.qml | 337 ++++--- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 559 ++++++----- .../qml/Pages2/PageProtocolCloakSettings.qml | 240 +++-- .../Pages2/PageProtocolOpenVpnSettings.qml | 729 +++++++------- client/ui/qml/Pages2/PageProtocolRaw.qml | 228 +++-- .../PageProtocolShadowSocksSettings.qml | 214 ++--- .../PageProtocolWireGuardClientSettings.qml | 208 ++-- .../Pages2/PageProtocolWireGuardSettings.qml | 227 ++--- .../qml/Pages2/PageProtocolXraySettings.qml | 195 ++-- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 54 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 411 ++++---- .../Pages2/PageServiceSocksProxySettings.qml | 463 +++++---- .../Pages2/PageServiceTorWebsiteSettings.qml | 46 +- client/ui/qml/Pages2/PageSettings.qml | 323 ++++--- client/ui/qml/Pages2/PageSettingsAbout.qml | 138 ++- .../Pages2/PageSettingsApiNativeConfigs.qml | 456 ++++----- .../Pages2/PageSettingsAppSplitTunneling.qml | 104 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 59 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 44 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 38 +- client/ui/qml/Pages2/PageSettingsDns.qml | 307 +++--- client/ui/qml/Pages2/PageSettingsLogging.qml | 9 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 370 ++++---- .../qml/Pages2/PageSettingsServerProtocol.qml | 426 ++++----- .../ui/qml/Pages2/PageSettingsServersList.qml | 9 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 895 +++++++++--------- .../Pages2/PageSetupWizardApiServiceInfo.qml | 323 ++++--- .../Pages2/PageSetupWizardApiServicesList.qml | 10 +- .../Pages2/PageSetupWizardConfigSource.qml | 10 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 33 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 152 ++- .../qml/Pages2/PageSetupWizardInstalling.qml | 130 ++- .../PageSetupWizardProtocolSettings.qml | 413 ++++---- .../qml/Pages2/PageSetupWizardProtocols.qml | 15 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 74 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 92 +- client/ui/qml/Pages2/PageShare.qml | 3 - client/ui/qml/Pages2/PageShareFullAccess.qml | 327 ++++--- 58 files changed, 6221 insertions(+), 6762 deletions(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 9fa232ca..a20055d4 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -1,309 +1,311 @@ -#include "listViewFocusController.h" -#include "utils/qmlUtils.h" - -#include - -ListViewFocusController::ListViewFocusController(QQuickItem *listView, QObject *parent) - : QObject { parent }, - m_listView { listView }, - m_focusChain {}, - m_currentSection { Section::Default }, - m_header { nullptr }, - m_footer { nullptr }, - m_focusedItem { nullptr }, - m_focusedItemIndex { -1 }, - m_delegateIndex { 0 }, - m_isReturnNeeded { false }, - m_currentSectionString { "Default", "Header", "Delegate", "Footer" } -{ - QVariant headerItemProperty = m_listView->property("headerItem"); - m_header = headerItemProperty.canConvert() ? headerItemProperty.value() : nullptr; - - QVariant footerItemProperty = m_listView->property("footerItem"); - m_footer = footerItemProperty.canConvert() ? footerItemProperty.value() : nullptr; -} - -ListViewFocusController::~ListViewFocusController() -{ -} - -void ListViewFocusController::viewAtCurrentIndex() const -{ - switch (m_currentSection) { - case Section::Default: [[fallthrough]]; - case Section::Header: { - QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); - break; - } - case Section::Delegate: { - QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index - Q_ARG(int, 2)); // PositionMode (0 = Visible) - break; - } - case Section::Footer: { - QMetaObject::invokeMethod(m_listView, "positionViewAtEnd"); - break; - } - } -} - -int ListViewFocusController::size() const -{ - return m_listView->property("count").toInt(); -} - -int ListViewFocusController::currentIndex() const -{ - return m_delegateIndex; -} - -void ListViewFocusController::setDelegateIndex(int index) -{ - m_delegateIndex = index; - m_listView->setProperty("currentIndex", index); -} - -void ListViewFocusController::nextDelegate() -{ - switch (m_currentSection) { - case Section::Default: { - if (hasHeader()) { - m_currentSection = Section::Header; - viewAtCurrentIndex(); - break; - } - [[fallthrough]]; - } - case Section::Header: { - if (size() > 0) { - m_currentSection = Section::Delegate; - viewAtCurrentIndex(); - break; - } - [[fallthrough]]; - } - case Section::Delegate: - if (m_delegateIndex < (size() - 1)) { - setDelegateIndex(m_delegateIndex + 1); - viewAtCurrentIndex(); - break; - } else if (hasFooter()) { - m_currentSection = Section::Footer; - viewAtCurrentIndex(); - break; - } - [[fallthrough]]; - case Section::Footer: { - m_isReturnNeeded = true; - m_currentSection = Section::Default; - viewAtCurrentIndex(); - break; - } - default: { - qCritical() << "Current section is invalid!"; - break; - } - } -} - -void ListViewFocusController::previousDelegate() -{ - switch (m_currentSection) { - case Section::Default: { - if (hasFooter()) { - m_currentSection = Section::Footer; - break; - } - [[fallthrough]]; - } - case Section::Footer: { - if (size() > 0) { - m_currentSection = Section::Delegate; - setDelegateIndex(size() - 1); - break; - } - [[fallthrough]]; - } - case Section::Delegate: { - if (m_delegateIndex > 0) { - setDelegateIndex(m_delegateIndex - 1); - break; - } else if (hasHeader()) { - m_currentSection = Section::Header; - break; - } - [[fallthrough]]; - } - case Section::Header: { - m_isReturnNeeded = true; - m_currentSection = Section::Default; - break; - } - default: { - qCritical() << "Current section is invalid!"; - break; - } - } -} - -void ListViewFocusController::decrementIndex() -{ - m_delegateIndex--; -} - -QQuickItem *ListViewFocusController::itemAtIndex(const int index) const -{ - QQuickItem *item { nullptr }; - - QMetaObject::invokeMethod(m_listView, "itemAtIndex", Q_RETURN_ARG(QQuickItem *, item), Q_ARG(int, index)); - - return item; -} - -QQuickItem *ListViewFocusController::currentDelegate() const -{ - QQuickItem *result { nullptr }; - - switch (m_currentSection) { - case Section::Default: { - qWarning() << "No elements..."; - break; - } - case Section::Header: { - result = m_header; - break; - } - case Section::Delegate: { - result = itemAtIndex(m_delegateIndex); - break; - } - case Section::Footer: { - result = m_footer; - break; - } - } - return result; -} - -QQuickItem *ListViewFocusController::focusedItem() const -{ - return m_focusedItem; -} - -void ListViewFocusController::focusNextItem() -{ - if (m_isReturnNeeded) { - return; - } - - reloadFocusChain(); - - if (m_focusChain.empty()) { - qWarning() << "No elements found in the delegate. Going to next delegate..."; - nextDelegate(); - focusNextItem(); - return; - } - m_focusedItemIndex++; - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(Qt::TabFocusReason); -} - -void ListViewFocusController::focusPreviousItem() -{ - if (m_isReturnNeeded) { - return; - } - - if (m_focusChain.empty()) { - qInfo() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; - reloadFocusChain(); - } - if (m_focusChain.empty()) { - qWarning() << "No elements found in the delegate. Going to next delegate..."; - previousDelegate(); - focusPreviousItem(); - return; - } - if (m_focusedItemIndex == -1) { - m_focusedItemIndex = m_focusChain.size(); - } - m_focusedItemIndex--; - m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); - m_focusedItem->forceActiveFocus(Qt::TabFocusReason); -} - -void ListViewFocusController::resetFocusChain() -{ - m_focusChain.clear(); - m_focusedItem = nullptr; - m_focusedItemIndex = -1; -} - -void ListViewFocusController::reloadFocusChain() -{ - m_focusChain = FocusControl::getItemsChain(currentDelegate()); -} - -bool ListViewFocusController::isFirstFocusItemInDelegate() const -{ - return m_focusedItem && (m_focusedItem == m_focusChain.first()); -} - -bool ListViewFocusController::isLastFocusItemInDelegate() const -{ - return m_focusedItem && (m_focusedItem == m_focusChain.last()); -} - -bool ListViewFocusController::hasHeader() const -{ - return m_header && !FocusControl::getItemsChain(m_header).isEmpty(); -} - -bool ListViewFocusController::hasFooter() const -{ - return m_footer && !FocusControl::getItemsChain(m_footer).isEmpty(); -} - -bool ListViewFocusController::isFirstFocusItemInListView() const -{ - switch (m_currentSection) { - case Section::Footer: { - return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0); - } - case Section::Delegate: { - return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader(); - } - case Section::Header: { - isFirstFocusItemInDelegate(); - } - case Section::Default: { - return true; - } - default: qWarning() << "Wrong section"; return true; - } -} - -bool ListViewFocusController::isLastFocusItemInListView() const -{ - switch (m_currentSection) { - case Section::Default: { - return !hasHeader() && (size() == 0) && !hasFooter(); - } - case Section::Header: { - return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter(); - } - case Section::Delegate: { - return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter(); - } - case Section::Footer: { - return isLastFocusItemInDelegate(); - } - default: qWarning() << "Wrong section"; return true; - } -} - -bool ListViewFocusController::isReturnNeeded() const -{ - return m_isReturnNeeded; -} +#include "listViewFocusController.h" +#include "utils/qmlUtils.h" + +#include + +ListViewFocusController::ListViewFocusController(QQuickItem *listView, QObject *parent) + : QObject { parent }, + m_listView { listView }, + m_focusChain {}, + m_currentSection { Section::Default }, + m_header { nullptr }, + m_footer { nullptr }, + m_focusedItem { nullptr }, + m_focusedItemIndex { -1 }, + m_delegateIndex { 0 }, + m_isReturnNeeded { false }, + m_currentSectionString { "Default", "Header", "Delegate", "Footer" } +{ + QVariant headerItemProperty = m_listView->property("headerItem"); + m_header = headerItemProperty.canConvert() ? headerItemProperty.value() : nullptr; + + QVariant footerItemProperty = m_listView->property("footerItem"); + m_footer = footerItemProperty.canConvert() ? footerItemProperty.value() : nullptr; +} + +ListViewFocusController::~ListViewFocusController() +{ +} + +void ListViewFocusController::viewAtCurrentIndex() const +{ + switch (m_currentSection) { + case Section::Default: [[fallthrough]]; + case Section::Header: { + QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning"); + break; + } + case Section::Delegate: { + QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index + Q_ARG(int, 2)); // PositionMode (0 = Visible) + break; + } + case Section::Footer: { + QMetaObject::invokeMethod(m_listView, "positionViewAtEnd"); + break; + } + } +} + +int ListViewFocusController::size() const +{ + return m_listView->property("count").toInt(); +} + +int ListViewFocusController::currentIndex() const +{ + return m_delegateIndex; +} + +void ListViewFocusController::setDelegateIndex(int index) +{ + m_delegateIndex = index; + m_listView->setProperty("currentIndex", index); +} + +void ListViewFocusController::nextDelegate() +{ + switch (m_currentSection) { + case Section::Default: { + if (hasHeader()) { + m_currentSection = Section::Header; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + } + case Section::Header: { + if (size() > 0) { + m_currentSection = Section::Delegate; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + } + case Section::Delegate: + if (m_delegateIndex < (size() - 1)) { + setDelegateIndex(m_delegateIndex + 1); + viewAtCurrentIndex(); + break; + } else if (hasFooter()) { + m_currentSection = Section::Footer; + viewAtCurrentIndex(); + break; + } + [[fallthrough]]; + case Section::Footer: { + m_isReturnNeeded = true; + m_currentSection = Section::Default; + viewAtCurrentIndex(); + break; + } + default: { + qCritical() << "Current section is invalid!"; + break; + } + } +} + +void ListViewFocusController::previousDelegate() +{ + switch (m_currentSection) { + case Section::Default: { + if (hasFooter()) { + m_currentSection = Section::Footer; + break; + } + [[fallthrough]]; + } + case Section::Footer: { + if (size() > 0) { + m_currentSection = Section::Delegate; + setDelegateIndex(size() - 1); + break; + } + [[fallthrough]]; + } + case Section::Delegate: { + if (m_delegateIndex > 0) { + setDelegateIndex(m_delegateIndex - 1); + break; + } else if (hasHeader()) { + m_currentSection = Section::Header; + break; + } + [[fallthrough]]; + } + case Section::Header: { + m_isReturnNeeded = true; + m_currentSection = Section::Default; + break; + } + default: { + qCritical() << "Current section is invalid!"; + break; + } + } +} + +void ListViewFocusController::decrementIndex() +{ + m_delegateIndex--; +} + +QQuickItem *ListViewFocusController::itemAtIndex(const int index) const +{ + QQuickItem *item { nullptr }; + + QMetaObject::invokeMethod(m_listView, "itemAtIndex", Q_RETURN_ARG(QQuickItem *, item), Q_ARG(int, index)); + + return item; +} + +QQuickItem *ListViewFocusController::currentDelegate() const +{ + QQuickItem *result { nullptr }; + + switch (m_currentSection) { + case Section::Default: { + qWarning() << "No elements..."; + break; + } + case Section::Header: { + result = m_header; + break; + } + case Section::Delegate: { + result = itemAtIndex(m_delegateIndex); + break; + } + case Section::Footer: { + result = m_footer; + break; + } + } + return result; +} + +QQuickItem *ListViewFocusController::focusedItem() const +{ + return m_focusedItem; +} + +void ListViewFocusController::focusNextItem() +{ + if (m_isReturnNeeded) { + return; + } + + reloadFocusChain(); + + if (m_focusChain.empty()) { + qWarning() << "No elements found in the delegate. Going to next delegate..."; + nextDelegate(); + focusNextItem(); + return; + } + m_focusedItemIndex++; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); + qDebug() << "Next focus is set to item: " << m_focusedItem; +} + +void ListViewFocusController::focusPreviousItem() +{ + if (m_isReturnNeeded) { + return; + } + + if (m_focusChain.empty()) { + qInfo() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements..."; + reloadFocusChain(); + } + if (m_focusChain.empty()) { + qWarning() << "No elements found in the delegate. Going to next delegate..."; + previousDelegate(); + focusPreviousItem(); + return; + } + if (m_focusedItemIndex == -1) { + m_focusedItemIndex = m_focusChain.size(); + } + m_focusedItemIndex--; + m_focusedItem = qobject_cast(m_focusChain.at(m_focusedItemIndex)); + m_focusedItem->forceActiveFocus(Qt::TabFocusReason); +} + +void ListViewFocusController::resetFocusChain() +{ + m_focusChain.clear(); + m_focusedItem = nullptr; + m_focusedItemIndex = -1; + qDebug() << "Focus chain was resetted"; +} + +void ListViewFocusController::reloadFocusChain() +{ + m_focusChain = FocusControl::getItemsChain(currentDelegate()); +} + +bool ListViewFocusController::isFirstFocusItemInDelegate() const +{ + return m_focusedItem && (m_focusedItem == m_focusChain.first()); +} + +bool ListViewFocusController::isLastFocusItemInDelegate() const +{ + return m_focusedItem && (m_focusedItem == m_focusChain.last()); +} + +bool ListViewFocusController::hasHeader() const +{ + return m_header && !FocusControl::getItemsChain(m_header).isEmpty(); +} + +bool ListViewFocusController::hasFooter() const +{ + return m_footer && !FocusControl::getItemsChain(m_footer).isEmpty(); +} + +bool ListViewFocusController::isFirstFocusItemInListView() const +{ + switch (m_currentSection) { + case Section::Footer: { + return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0); + } + case Section::Delegate: { + return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader(); + } + case Section::Header: { + isFirstFocusItemInDelegate(); + } + case Section::Default: { + return true; + } + default: qWarning() << "Wrong section"; return true; + } +} + +bool ListViewFocusController::isLastFocusItemInListView() const +{ + switch (m_currentSection) { + case Section::Default: { + return !hasHeader() && (size() == 0) && !hasFooter(); + } + case Section::Header: { + return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter(); + } + case Section::Delegate: { + return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter(); + } + case Section::Footer: { + return isLastFocusItemInDelegate(); + } + default: qWarning() << "Wrong section"; return true; + } +} + +bool ListViewFocusController::isReturnNeeded() const +{ + return m_isReturnNeeded; +} diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 1cbdd82d..54cb27e2 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -10,8 +10,7 @@ import ProtocolEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" - -ListView { +ListViewType { id: menuContent property var rootWidth @@ -21,13 +20,6 @@ ListView { anchors.top: parent.top anchors.bottom: parent.bottom - clip: true - snapMode: ListView.SnapToItem - - ScrollBar.vertical: ScrollBarType {} - - property bool isFocusable: true - ButtonGroup { id: containersRadioButtonGroup } diff --git a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml index 097274a4..6fdb6852 100644 --- a/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml +++ b/client/ui/qml/Components/HomeSplitTunnelingDrawer.qml @@ -1,97 +1,97 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import PageEnum 1.0 - -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" - -DrawerType2 { - id: root - - property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" - - anchors.fill: parent - expandedHeight: parent.height * 0.9 - - expandedStateContent: 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 { - id: splitTunnelingSwitch - Layout.fillWidth: true - Layout.topMargin: 16 - - visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling - - 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.closeTriggered() - } - } - - DividerType { - visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling - } - - LabelWithButtonType { - id: siteBasedSplitTunnelingSwitch - Layout.fillWidth: true - Layout.topMargin: 16 - - 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.closeTriggered() - } - } - - DividerType { - } - - LabelWithButtonType { - id: appSplitTunnelingSwitch - visible: isAppSplitTinnelingEnabled - - Layout.fillWidth: true - - text: qsTr("App-based split tunneling") - descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) - root.closeTriggered() - } - } - - DividerType { - visible: isAppSplitTinnelingEnabled - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType2 { + id: root + + property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" + + anchors.fill: parent + expandedHeight: parent.height * 0.9 + + expandedStateContent: 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 { + id: splitTunnelingSwitch + Layout.fillWidth: true + Layout.topMargin: 16 + + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling + + 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.closeTriggered() + } + } + + DividerType { + visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling + } + + LabelWithButtonType { + id: siteBasedSplitTunnelingSwitch + Layout.fillWidth: true + Layout.topMargin: 16 + + 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.closeTriggered() + } + } + + DividerType { + } + + LabelWithButtonType { + id: appSplitTunnelingSwitch + visible: isAppSplitTinnelingEnabled + + Layout.fillWidth: true + + text: qsTr("App-based split tunneling") + descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) + root.closeTriggered() + } + } + + DividerType { + visible: isAppSplitTinnelingEnabled + } + } +} diff --git a/client/ui/qml/Components/InstalledAppsDrawer.qml b/client/ui/qml/Components/InstalledAppsDrawer.qml index ce8ef837..153eee31 100644 --- a/client/ui/qml/Components/InstalledAppsDrawer.qml +++ b/client/ui/qml/Components/InstalledAppsDrawer.qml @@ -57,7 +57,7 @@ DrawerType2 { headerText: qsTr("Choose application") } - ListView { + ListViewType { id: listView Layout.fillWidth: true @@ -66,11 +66,6 @@ DrawerType2 { Layout.rightMargin: 16 Layout.leftMargin: 16 - clip: true - interactive: true - - property bool isFocusable: true - model: SortFilterProxyModel { id: proxyInstalledAppsModel sourceModel: installedAppsModel @@ -81,44 +76,37 @@ DrawerType2 { } } - ScrollBar.vertical: ScrollBarType {} - ButtonGroup { id: buttonGroup } - delegate: Item { - implicitWidth: root.width - implicitHeight: delegateContent.implicitHeight + delegate: ColumnLayout { + id: delegateContent - ColumnLayout { - id: delegateContent + width: listView.width - anchors.fill: parent + RowLayout { + CheckBoxType { + Layout.fillWidth: true - RowLayout { - CheckBoxType { - Layout.fillWidth: true - - text: appName - checked: isAppSelected - onCheckedChanged: { - installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked) - } - } - - Image { - source: "image://installedAppImage/" + appIcon - - sourceSize.width: 24 - sourceSize.height: 24 - - Layout.rightMargin: 48 + text: appName + checked: isAppSelected + onCheckedChanged: { + installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked) } } - DividerType {} + Image { + source: "image://installedAppImage/" + appIcon + + sourceSize.width: 24 + sourceSize.height: 24 + + Layout.rightMargin: 48 + } } + + DividerType {} } } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index 2c026848..09829fa7 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -49,7 +49,7 @@ DrawerType2 { } } - ListView { + ListViewType { id: listView anchors.top: backButtonLayout.bottom @@ -57,14 +57,8 @@ DrawerType2 { anchors.right: parent.right anchors.bottom: parent.bottom - property bool isFocusable: true property int selectedIndex: LanguageModel.currentLanguageIndex - clip: true - reuseItems: true - - ScrollBar.vertical: ScrollBarType {} - model: LanguageModel ButtonGroup { diff --git a/client/ui/qml/Components/ServersListView.qml b/client/ui/qml/Components/ServersListView.qml index d0567a8c..4417e0b2 100644 --- a/client/ui/qml/Components/ServersListView.qml +++ b/client/ui/qml/Components/ServersListView.qml @@ -15,7 +15,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -ListView { +ListViewType { id: root property int selectedIndex: ServersModel.defaultIndex @@ -28,10 +28,6 @@ ListView { model: ServersModel - ScrollBar.vertical: ScrollBarType {} - - property bool isFocusable: true - Connections { target: ServersModel function onDefaultServerIndexChanged(serverIndex) { @@ -39,9 +35,6 @@ ListView { } } - clip: true - reuseItems: true - delegate: Item { id: menuContentDelegate objectName: "menuContentDelegate" diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 9e672130..273c9f68 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -13,78 +13,64 @@ import "../Controls2" import "../Controls2/TextTypes" -ListView { - id: root +ListViewType { + id: listView - width: parent.width - height: root.contentItem.height + anchors.fill: parent - clip: true - reuseItems: true + delegate: ColumnLayout { + width: listView.width - property bool isFocusable: false + LabelWithButtonType { + Layout.fillWidth: true - delegate: Item { - implicitWidth: root.width - implicitHeight: delegateContent.implicitHeight + text: name + descriptionText: description + rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - ColumnLayout { - id: delegateContent + clickedFunction: function() { + if (isInstalled) { + var containerIndex = root.model.mapToSource(index) + ContainersModel.setProcessedContainerIndex(containerIndex) - anchors.fill: parent - - LabelWithButtonType { - id: containerRadioButton - implicitWidth: parent.width - - text: name - descriptionText: description - rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - - clickedFunction: function() { - if (isInstalled) { - var containerIndex = root.model.mapToSource(index) - ContainersModel.setProcessedContainerIndex(containerIndex) - - if (serviceType !== ProtocolEnum.Other) { - if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolRaw) - return - } - } - - switch (containerIndex) { - case ContainerEnum.Ipsec: { + if (serviceType !== ProtocolEnum.Other) { + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolRaw) - break + return } - case ContainerEnum.Dns: { - PageController.goToPage(PageEnum.PageServiceDnsSettings) - break - } - default: { - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageSettingsServerProtocol) - } - } - - } else { - ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index)) - InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - enabled: false + switch (containerIndex) { + case ContainerEnum.Ipsec: { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + break + } + case ContainerEnum.Dns: { + PageController.goToPage(PageEnum.PageServiceDnsSettings) + break + } + default: { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) + } + } + + } else { + ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } - DividerType {} + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index dd59180b..4475875d 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -53,7 +53,7 @@ DrawerType2 { headerText: root.headerText } - ListView { + ListViewType { id: listView anchors.top: header.bottom @@ -61,14 +61,7 @@ DrawerType2 { anchors.left: parent.left anchors.right: parent.right - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - - model: 1 - - clip: true - reuseItems: true + model: 1 // fake model to force the ListView to be created without a model header: ColumnLayout { width: listView.width @@ -214,24 +207,25 @@ DrawerType2 { backButtonFunction: function() { configContentDrawer.closeTriggered() } } - FlickableType { + ListViewType { + id: configListView + anchors.top: backButton.bottom + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - ColumnLayout { - id: configContent + model: 1 // fake model to force the ListView to be created without a model - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 + header: ColumnLayout { + width: configListView.width Header2Type { id: configContentHeader Layout.fillWidth: true Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: root.configContentHeaderText } diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index b60e96cf..914fc267 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -1,220 +1,210 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Qt5Compat.GraphicalEffects - -import Style 1.0 - -import "TextTypes" - -Button { - id: root - - property string hoveredColor: AmneziaStyle.color.lightGray - property string defaultColor: AmneziaStyle.color.paleGray - property string disabledColor: AmneziaStyle.color.charcoalGray - property string pressedColor: AmneziaStyle.color.mutedGray - - property string textColor: AmneziaStyle.color.midnightBlack - - property string borderColor: AmneziaStyle.color.paleGray - property string borderFocusedColor: AmneziaStyle.color.paleGray - property int borderWidth: 0 - property int borderFocusedWidth: 1 - - property string leftImageSource - property string rightImageSource - property string leftImageColor: textColor - property bool changeLeftImageSize: true - - property bool squareLeftSide: false - - property FlickableType parentFlickable - - property var clickedFunc - - property alias buttonTextLabel: buttonText - - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - implicitHeight: 56 - - hoverEnabled: true - - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(this) - } - } - } - - background: Rectangle { - id: focusBorder - - color: AmneziaStyle.color.transparent - border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent - border.width: root.activeFocus ? root.borderFocusedWidth : 0 - - anchors.fill: parent - - radius: 16 - - Rectangle { - id: background - - anchors.fill: focusBorder - anchors.margins: root.activeFocus ? 2 : 0 - - radius: root.activeFocus ? 14 : 16 - color: { - if (root.enabled) { - if (root.pressed) { - return pressedColor - } - return root.hovered ? hoveredColor : defaultColor - } else { - return disabledColor - } - } - border.color: borderColor - border.width: borderWidth - - Behavior on color { - PropertyAnimation { duration: 200 } - } - - Rectangle { - visible: root.squareLeftSide - - z: 1 - - width: parent.radius - height: parent.radius - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - color: { - if (root.enabled) { - if (root.pressed) { - return pressedColor - } - return root.hovered ? hoveredColor : defaultColor - } else { - return disabledColor - } - } - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - } - } - - MouseArea { - anchors.fill: focusBorder - enabled: false - cursorShape: Qt.PointingHandCursor - } - - contentItem: Item { - anchors.fill: focusBorder - - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight - - RowLayout { - id: content - anchors.centerIn: parent - - Image { - id: leftImage - source: root.leftImageSource - visible: root.leftImageSource === "" ? false : true - - layer { - enabled: leftImageColor !== "" ? true : false - effect: ColorOverlay { - color: leftImageColor - } - } - - Component.onCompleted: { - if (root.changeLeftImageSize) { - leftImage.Layout.preferredHeight = 20 - leftImage.Layout.preferredWidth = 20 - } - } - } - - ButtonTextType { - id: buttonText - - color: root.textColor - text: root.text - visible: root.text === "" ? false : true - - 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 - } - } - } - } - } - - Keys.onEnterPressed: { - if (root.clickedFunc && typeof root.clickedFunc === "function") { - root.clickedFunc() - } - } - - Keys.onReturnPressed: { - if (root.clickedFunc && typeof root.clickedFunc === "function") { - root.clickedFunc() - } - } - - onClicked: { - if (root.clickedFunc && typeof root.clickedFunc === "function") { - root.clickedFunc() - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import Style 1.0 + +import "TextTypes" + +Button { + id: root + + property string hoveredColor: AmneziaStyle.color.lightGray + property string defaultColor: AmneziaStyle.color.paleGray + property string disabledColor: AmneziaStyle.color.charcoalGray + property string pressedColor: AmneziaStyle.color.mutedGray + + property string textColor: AmneziaStyle.color.midnightBlack + + property string borderColor: AmneziaStyle.color.paleGray + property string borderFocusedColor: AmneziaStyle.color.paleGray + property int borderWidth: 0 + property int borderFocusedWidth: 1 + + property string leftImageSource + property string rightImageSource + property string leftImageColor: textColor + property bool changeLeftImageSize: true + + property bool squareLeftSide: false + + property var clickedFunc + + property alias buttonTextLabel: buttonText + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + implicitHeight: 56 + + hoverEnabled: true + + background: Rectangle { + id: focusBorder + + color: AmneziaStyle.color.transparent + border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent + border.width: root.activeFocus ? root.borderFocusedWidth : 0 + + anchors.fill: parent + + radius: 16 + + Rectangle { + id: background + + anchors.fill: focusBorder + anchors.margins: root.activeFocus ? 2 : 0 + + radius: root.activeFocus ? 14 : 16 + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: borderColor + border.width: borderWidth + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + visible: root.squareLeftSide + + z: 1 + + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + } + + MouseArea { + anchors.fill: focusBorder + enabled: false + cursorShape: Qt.PointingHandCursor + } + + contentItem: Item { + anchors.fill: focusBorder + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + RowLayout { + id: content + anchors.centerIn: parent + + Image { + id: leftImage + source: root.leftImageSource + visible: root.leftImageSource === "" ? false : true + + layer { + enabled: leftImageColor !== "" ? true : false + effect: ColorOverlay { + color: leftImageColor + } + } + + Component.onCompleted: { + if (root.changeLeftImageSize) { + leftImage.Layout.preferredHeight = 20 + leftImage.Layout.preferredWidth = 20 + } + } + } + + ButtonTextType { + id: buttonText + + color: root.textColor + text: root.text + visible: root.text === "" ? false : true + + 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 + } + } + } + } + } + + Keys.onEnterPressed: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } + + Keys.onReturnPressed: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } + + onClicked: { + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() + } + } +} diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml index 4277d735..02d033d2 100644 --- a/client/ui/qml/Controls2/CardWithIconsType.qml +++ b/client/ui/qml/Controls2/CardWithIconsType.qml @@ -1,203 +1,185 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Button { - id: root - - property string headerText - property string bodyText - property string footerText - - property string hoveredColor: AmneziaStyle.color.slateGray - property string defaultColor: AmneziaStyle.color.onyxBlack - - property string textColor: AmneziaStyle.color.midnightBlack - - property string rightImageSource - property string rightImageColor: AmneziaStyle.color.paleGray - - property string leftImageSource - - property real textOpacity: 1.0 - - property alias focusItem: rightImage - - property FlickableType parentFlickable - - hoverEnabled: true - - background: Rectangle { - id: backgroundRect - - anchors.fill: parent - radius: 16 - - color: defaultColor - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - function ensureVisible(item) { - if (item.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - onFocusChanged: { - ensureVisible(root) - } - - focusItem.onFocusChanged: { - root.ensureVisible(focusItem) - } - - contentItem: Item { - anchors.left: parent.left - anchors.right: parent.right - - implicitHeight: content.implicitHeight - - RowLayout { - id: content - - anchors.fill: parent - - Image { - id: leftImage - source: leftImageSource - - visible: leftImageSource !== "" - - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.topMargin: 24 - Layout.bottomMargin: 24 - Layout.leftMargin: 24 - } - - ColumnLayout { - - ListItemTitleType { - text: root.headerText - visible: text !== "" - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: root.bodyText !== "" ? 0 : 16 - - opacity: root.textOpacity - } - - CaptionTextType { - text: root.bodyText - visible: text !== "" - - color: AmneziaStyle.color.mutedGray - textFormat: Text.RichText - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: root.footerText !== "" ? 0 : 16 - - opacity: root.textOpacity - } - - ButtonTextType { - text: root.footerText - visible: text !== "" - - color: AmneziaStyle.color.mutedGray - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: 16 - - opacity: root.textOpacity - } - } - - ImageButtonType { - id: rightImage - - implicitWidth: 40 - implicitHeight: 40 - - hoverEnabled: false - image: rightImageSource - imageColor: rightImageColor - visible: rightImageSource ? true : false - - Layout.alignment: Qt.AlignRight | Qt.AlignTop - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.rightMargin: 16 - - Rectangle { - id: rightImageBackground - - anchors.fill: parent - radius: 12 - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - onClicked: { - root.clicked() - } - } - } - } - - MouseArea { - anchors.fill: parent - - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - enabled: root.enabled - - onEntered: { - backgroundRect.color = root.hoveredColor - - if (rightImageSource) { - rightImageBackground.color = rightImage.hoveredColor - } - root.textOpacity = 0.8 - } - - onExited: { - backgroundRect.color = root.defaultColor - - if (rightImageSource) { - rightImageBackground.color = rightImage.defaultColor - } - root.textOpacity = 1 - } - - onPressedChanged: { - if (rightImageSource) { - rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } - root.textOpacity = 0.7 - } - - onClicked: { - root.clicked() - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Button { + id: root + + property string headerText + property string bodyText + property string footerText + + property string hoveredColor: AmneziaStyle.color.slateGray + property string defaultColor: AmneziaStyle.color.onyxBlack + + property string textColor: AmneziaStyle.color.midnightBlack + + property string rightImageSource + property string rightImageColor: AmneziaStyle.color.paleGray + + property string leftImageSource + + property real textOpacity: 1.0 + + property alias focusItem: rightImage + + hoverEnabled: true + + background: Rectangle { + id: backgroundRect + + anchors.fill: parent + radius: 16 + + color: defaultColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + contentItem: Item { + anchors.left: parent.left + anchors.right: parent.right + + implicitHeight: content.implicitHeight + + RowLayout { + id: content + + anchors.fill: parent + + Image { + id: leftImage + source: leftImageSource + + visible: leftImageSource !== "" + + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 24 + } + + ColumnLayout { + + ListItemTitleType { + text: root.headerText + visible: text !== "" + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: root.bodyText !== "" ? 0 : 16 + + opacity: root.textOpacity + } + + CaptionTextType { + text: root.bodyText + visible: text !== "" + + color: AmneziaStyle.color.mutedGray + textFormat: Text.RichText + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: root.footerText !== "" ? 0 : 16 + + opacity: root.textOpacity + } + + ButtonTextType { + text: root.footerText + visible: text !== "" + + color: AmneziaStyle.color.mutedGray + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + opacity: root.textOpacity + } + } + + ImageButtonType { + id: rightImage + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: false + image: rightImageSource + imageColor: rightImageColor + visible: rightImageSource ? true : false + + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.rightMargin: 16 + + Rectangle { + id: rightImageBackground + + anchors.fill: parent + radius: 12 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + onClicked: { + root.clicked() + } + } + } + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + enabled: root.enabled + + onEntered: { + backgroundRect.color = root.hoveredColor + + if (rightImageSource) { + rightImageBackground.color = rightImage.hoveredColor + } + root.textOpacity = 0.8 + } + + onExited: { + backgroundRect.color = root.defaultColor + + if (rightImageSource) { + rightImageBackground.color = rightImage.defaultColor + } + root.textOpacity = 1 + } + + onPressedChanged: { + if (rightImageSource) { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } + root.textOpacity = 0.7 + } + + onClicked: { + root.clicked() + } + } +} diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index a26a68f1..9c89986f 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -1,170 +1,161 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Qt5Compat.GraphicalEffects - -import Style 1.0 - -import "TextTypes" - -CheckBox { - id: root - - property string descriptionText - property string descriptionTextColor: AmneziaStyle.color.mutedGray - property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray - - property string textColor: AmneziaStyle.color.paleGray - property string textDisabledColor: AmneziaStyle.color.mutedGray - - property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite - property string defaultColor: AmneziaStyle.color.transparent - property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite - - property string defaultBorderColor: AmneziaStyle.color.paleGray - property string checkedBorderColor: AmneziaStyle.color.goldenApricot - property string checkedBorderDisabledColor: AmneziaStyle.color.deepBrown - - property string borderFocusedColor: AmneziaStyle.color.paleGray - - property string checkedImageColor: AmneziaStyle.color.goldenApricot - property string pressedImageColor: AmneziaStyle.color.burntOrange - property string defaultImageColor: AmneziaStyle.color.transparent - property string checkedDisabledImageColor: AmneziaStyle.color.mutedBrown - - property string imageSource: "qrc:/images/controls/check.svg" - - property var parentFlickable - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - hoverEnabled: enabled ? true : false - focusPolicy: Qt.NoFocus - - background: Rectangle { - color: AmneziaStyle.color.transparent - border.color: root.focus ? borderFocusedColor : AmneziaStyle.color.transparent - border.width: 1 - radius: 16 - } - - indicator: Rectangle { - id: background - - anchors.verticalCenter: parent.verticalCenter - - implicitWidth: 56 - implicitHeight: 56 - radius: 16 - - color: { - if (root.hovered) { - return hoveredColor - } - return defaultColor - } - - Behavior on color { - PropertyAnimation { duration: 200 } - } - - Rectangle { - id: imageBorder - - anchors.centerIn: parent - width: 24 - height: 24 - color: AmneziaStyle.color.transparent - border.color: root.checked ? - (root.enabled ? - checkedBorderColor : - checkedBorderDisabledColor) : - defaultBorderColor - border.width: 1 - radius: 4 - - Image { - anchors.centerIn: parent - - source: root.pressed ? imageSource : root.checked ? imageSource : "" - layer { - enabled: true - effect: ColorOverlay { - color: { - if (root.pressed) { - return root.pressedImageColor - } else if (root.checked) { - if (root.enabled) { - return root.checkedImageColor - } else { - return root.checkedDisabledImageColor - } - } else { - return root.defaultImageColor - } - } - } - } - } - } - } - - contentItem: Item { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 8 + background.width - - implicitHeight: content.implicitHeight - - ColumnLayout { - id: content - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - spacing: 4 - - ListItemTitleType { - Layout.fillWidth: true - - text: root.text - color: root.enabled ? root.textColor : root.textDisabledColor - } - - CaptionTextType { - id: description - - Layout.fillWidth: true - - text: root.descriptionText - color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor - - visible: root.descriptionText !== "" - } - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - enabled: false - } - - - Keys.onEnterPressed: { - root.checked = !root.checked - } - - Keys.onReturnPressed: { - root.checked = !root.checked - } - -} - - +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +import Style 1.0 + +import "TextTypes" + +CheckBox { + id: root + + property string descriptionText + property string descriptionTextColor: AmneziaStyle.color.mutedGray + property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray + + property string textColor: AmneziaStyle.color.paleGray + property string textDisabledColor: AmneziaStyle.color.mutedGray + + property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite + property string defaultColor: AmneziaStyle.color.transparent + property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite + + property string defaultBorderColor: AmneziaStyle.color.paleGray + property string checkedBorderColor: AmneziaStyle.color.goldenApricot + property string checkedBorderDisabledColor: AmneziaStyle.color.deepBrown + + property string borderFocusedColor: AmneziaStyle.color.paleGray + + property string checkedImageColor: AmneziaStyle.color.goldenApricot + property string pressedImageColor: AmneziaStyle.color.burntOrange + property string defaultImageColor: AmneziaStyle.color.transparent + property string checkedDisabledImageColor: AmneziaStyle.color.mutedBrown + + property string imageSource: "qrc:/images/controls/check.svg" + + hoverEnabled: enabled ? true : false + focusPolicy: Qt.NoFocus + + background: Rectangle { + color: AmneziaStyle.color.transparent + border.color: root.focus ? borderFocusedColor : AmneziaStyle.color.transparent + border.width: 1 + radius: 16 + } + + indicator: Rectangle { + id: background + + anchors.verticalCenter: parent.verticalCenter + + implicitWidth: 56 + implicitHeight: 56 + radius: 16 + + color: { + if (root.hovered) { + return hoveredColor + } + return defaultColor + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: imageBorder + + anchors.centerIn: parent + width: 24 + height: 24 + color: AmneziaStyle.color.transparent + border.color: root.checked ? + (root.enabled ? + checkedBorderColor : + checkedBorderDisabledColor) : + defaultBorderColor + border.width: 1 + radius: 4 + + Image { + anchors.centerIn: parent + + source: root.pressed ? imageSource : root.checked ? imageSource : "" + layer { + enabled: true + effect: ColorOverlay { + color: { + if (root.pressed) { + return root.pressedImageColor + } else if (root.checked) { + if (root.enabled) { + return root.checkedImageColor + } else { + return root.checkedDisabledImageColor + } + } else { + return root.defaultImageColor + } + } + } + } + } + } + } + + contentItem: Item { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 8 + background.width + + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + spacing: 4 + + ListItemTitleType { + Layout.fillWidth: true + + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + + text: root.descriptionText + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor + + visible: root.descriptionText !== "" + } + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } + + + Keys.onEnterPressed: { + root.checked = !root.checked + } + + Keys.onReturnPressed: { + root.checked = !root.checked + } + +} + + diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 087415f7..56d721b9 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -1,328 +1,309 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Item { - id: root - - property string text - property int textMaximumLineCount: 2 - property int textElide: Qt.ElideRight - - property string descriptionText - - property var clickedFunction - - property string buttonImageSource - property string rightImageSource - property string leftImageSource - property bool isLeftImageHoverEnabled: true - property bool isSmallLeftImage: false - - property alias rightButton: rightImage - property alias eyeButton: eyeImage - property FlickableType parentFlickable - - property string textColor: AmneziaStyle.color.paleGray - property string textDisabledColor: AmneziaStyle.color.mutedGray - property string descriptionColor: AmneziaStyle.color.mutedGray - property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray - property real textOpacity: 1.0 - - property string borderFocusedColor: AmneziaStyle.color.paleGray - property int borderFocusedWidth: 1 - - property string rightImageColor: AmneziaStyle.color.paleGray - - property bool descriptionOnTop: false - property bool hideDescription: true - - property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin - implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin - - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - Connections { - target: rightImage - function onFocusChanged() { - if (rightImage.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: root.enabled - - onEntered: { - if (rightImageSource) { - rightImageBackground.color = rightImage.hoveredColor - } else if (leftImageSource) { - leftImageBackground.color = rightImage.hoveredColor - } - root.textOpacity = 0.8 - } - - onExited: { - if (rightImageSource) { - rightImageBackground.color = rightImage.defaultColor - } else if (leftImageSource) { - leftImageBackground.color = rightImage.defaultColor - } - root.textOpacity = 1 - } - - onPressedChanged: { - if (rightImageSource) { - rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } else if (leftImageSource) { - leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } - root.textOpacity = 0.7 - } - - onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } - } - - RowLayout { - id: content - anchors.fill: parent - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - Rectangle { - id: leftImageBackground - - visible: leftImageSource ? true : false - - Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56 - Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56 - Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0 - - radius: 12 - color: AmneziaStyle.color.transparent - - Behavior on color { - PropertyAnimation { duration: 200 } - } - - Image { - id: leftImage - - anchors.centerIn: parent - source: leftImageSource - } - } - - ColumnLayout { - property real textLineHeight: 21.6 - property real descriptionTextLineHeight: 16 - - property int textPixelSize: 18 - property int descriptionTextSize: 13 - - ListItemTitleType { - text: root.text - color: { - if (root.enabled) { - return root.descriptionOnTop ? root.descriptionColor : root.textColor - } else { - return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor - } - } - - maximumLineCount: root.textMaximumLineCount - elide: root.textElide - - opacity: root.textOpacity - - Layout.fillWidth: true - - lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight - font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize - font.letterSpacing: root.descriptionOnTop ? 0.02 : 0 - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - Behavior on opacity { - PropertyAnimation { duration: 200 } - } - } - - - CaptionTextType { - id: description - - text: (eyeImage.visible && hideDescription) ? replaceWithAsterisks(root.descriptionText) : root.descriptionText - color: { - if (root.enabled) { - return root.descriptionOnTop ? root.textColor : root.descriptionColor - } else { - return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor - } - } - - opacity: root.textOpacity - - visible: root.descriptionText !== "" - - Layout.fillWidth: true - - lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight - font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize - font.letterSpacing: root.descriptionOnTop ? 0 : 0.02 - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - Behavior on opacity { - PropertyAnimation { duration: 200 } - } - - function replaceWithAsterisks(input) { - return '*'.repeat(input.length) - } - } - } - - ImageButtonType { - id: eyeImage - visible: buttonImageSource !== "" - - implicitWidth: 40 - implicitHeight: 40 - - hoverEnabled: true - image: buttonImageSource - imageColor: rightImageColor - - Layout.alignment: Qt.AlignRight - - Rectangle { - id: eyeImageBackground - anchors.fill: parent - radius: 12 - color: AmneziaStyle.color.transparent - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - onClicked: { - hideDescription = !hideDescription - } - - Keys.onEnterPressed: { - clicked() - } - - Keys.onReturnPressed: { - clicked() - } - } - - ImageButtonType { - id: rightImage - - implicitWidth: 40 - implicitHeight: 40 - - hoverEnabled: false - image: rightImageSource - imageColor: rightImageColor - visible: rightImageSource ? true : false - - Layout.alignment: Qt.AlignRight - - Rectangle { - id: rightImageBackground - anchors.fill: parent - radius: 12 - color: AmneziaStyle.color.transparent - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } - } - } - - Rectangle { - id: background - anchors.fill: root - color: AmneziaStyle.color.transparent - - border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent - border.width: root.activeFocus ? root.borderFocusedWidth : 0 - - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - Keys.onEnterPressed: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } - - Keys.onReturnPressed: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Item { + id: root + + // property alias focusObjectName: eyeImage.objectName + property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string descriptionText + + property var clickedFunction + + property string buttonImageSource + property string rightImageSource + property string leftImageSource + property bool isLeftImageHoverEnabled: true + property bool isSmallLeftImage: false + + property alias rightButton: rightImage + property alias eyeButton: eyeImage + + property string textColor: AmneziaStyle.color.paleGray + property string textDisabledColor: AmneziaStyle.color.mutedGray + property string descriptionColor: AmneziaStyle.color.mutedGray + property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray + property real textOpacity: 1.0 + + property string borderFocusedColor: AmneziaStyle.color.paleGray + property int borderFocusedWidth: 1 + + property string rightImageColor: AmneziaStyle.color.paleGray + + property bool descriptionOnTop: false + property bool hideDescription: true + + property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: root.enabled + + onEntered: { + if (rightImageSource) { + rightImageBackground.color = rightImage.hoveredColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.hoveredColor + } + root.textOpacity = 0.8 + } + + onExited: { + if (rightImageSource) { + rightImageBackground.color = rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.defaultColor + } + root.textOpacity = 1 + } + + onPressedChanged: { + if (rightImageSource) { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } + root.textOpacity = 0.7 + } + + onClicked: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } + + RowLayout { + id: content + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + Rectangle { + id: leftImageBackground + + visible: leftImageSource ? true : false + + Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56 + Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56 + Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0 + + radius: 12 + color: AmneziaStyle.color.transparent + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Image { + id: leftImage + + anchors.centerIn: parent + source: leftImageSource + } + } + + ColumnLayout { + property real textLineHeight: 21.6 + property real descriptionTextLineHeight: 16 + + property int textPixelSize: 18 + property int descriptionTextSize: 13 + + ListItemTitleType { + text: root.text + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.descriptionColor : root.textColor + } else { + return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor + } + } + + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + + opacity: root.textOpacity + + Layout.fillWidth: true + + lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight + font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize + font.letterSpacing: root.descriptionOnTop ? 0.02 : 0 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + + + CaptionTextType { + id: description + + text: (eyeImage.visible && hideDescription) ? replaceWithAsterisks(root.descriptionText) : root.descriptionText + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.textColor : root.descriptionColor + } else { + return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor + } + } + + opacity: root.textOpacity + + visible: root.descriptionText !== "" + + Layout.fillWidth: true + + lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight + font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize + font.letterSpacing: root.descriptionOnTop ? 0 : 0.02 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + + function replaceWithAsterisks(input) { + return '*'.repeat(input.length) + } + } + } + + ImageButtonType { + id: eyeImage + visible: buttonImageSource !== "" + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: true + image: buttonImageSource + imageColor: rightImageColor + + Layout.alignment: Qt.AlignRight + + Rectangle { + id: eyeImageBackground + anchors.fill: parent + radius: 12 + color: AmneziaStyle.color.transparent + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + onClicked: { + hideDescription = !hideDescription + } + + Keys.onEnterPressed: { + clicked() + } + + Keys.onReturnPressed: { + clicked() + } + } + + ImageButtonType { + id: rightImage + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: false + image: rightImageSource + imageColor: rightImageColor + visible: rightImageSource ? true : false + + Layout.alignment: Qt.AlignRight + + Rectangle { + id: rightImageBackground + anchors.fill: parent + radius: 12 + color: AmneziaStyle.color.transparent + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + onClicked: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } + } + + Rectangle { + id: background + anchors.fill: root + color: AmneziaStyle.color.transparent + + border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent + border.width: root.activeFocus ? root.borderFocusedWidth : 0 + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + Keys.onEnterPressed: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + + Keys.onReturnPressed: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } +} diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index 0de43d77..e15f4a39 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -6,33 +6,8 @@ ListView { property bool isFocusable: true - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - ScrollBar.vertical: ScrollBarType {} clip: true reuseItems: true - snapMode: ListView.SnapToItem } diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index bd7ca32e..abd4da3e 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -6,7 +6,7 @@ import Style 1.0 import "TextTypes" -ListView { +ListViewType { id: root property var rootWidth @@ -25,13 +25,6 @@ ListView { width: rootWidth height: root.contentItem.height - clip: true - reuseItems: true - - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - ButtonGroup { id: buttonGroup } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index 0651390f..db9ef755 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -1,172 +1,162 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Switch { - id: root - - property alias descriptionText: description.text - property string descriptionTextColor: AmneziaStyle.color.mutedGray - property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray - - property string textColor: AmneziaStyle.color.paleGray - property string textDisabledColor: AmneziaStyle.color.mutedGray - - property string checkedIndicatorColor: AmneziaStyle.color.richBrown - property string defaultIndicatorColor: AmneziaStyle.color.transparent - property string checkedDisabledIndicatorColor: AmneziaStyle.color.deepBrown - - property string borderFocusedColor: AmneziaStyle.color.paleGray - property int borderFocusedWidth: 1 - - property string checkedIndicatorBorderColor: AmneziaStyle.color.richBrown - property string defaultIndicatorBorderColor: AmneziaStyle.color.charcoalGray - property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.deepBrown - - property string checkedInnerCircleColor: AmneziaStyle.color.goldenApricot - property string defaultInnerCircleColor: AmneziaStyle.color.paleGray - property string checkedDisabledInnerCircleColor: AmneziaStyle.color.mutedBrown - property string defaultDisabledInnerCircleColor: AmneziaStyle.color.charcoalGray - - property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite - property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent - - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - hoverEnabled: enabled ? true : false - focusPolicy: Qt.TabFocus - - property FlickableType parentFlickable: null - - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - indicator: Rectangle { - id: switcher - - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - implicitWidth: 52 - implicitHeight: 32 - - radius: 16 - color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor) - : root.defaultIndicatorColor - - border.color: root.activeFocus ? root.borderFocusedColor : (root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor) - : root.defaultIndicatorBorderColor) - - Behavior on color { - PropertyAnimation { duration: 200 } - } - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - - Rectangle { - id: innerCircle - - anchors.verticalCenter: parent.verticalCenter - x: root.checked ? parent.width - width - 4 : 8 - width: root.checked ? 24 : 16 - height: root.checked ? 24 : 16 - radius: 23 - color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor) - : (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor) - - Behavior on x { - PropertyAnimation { duration: 200 } - } - } - - Rectangle { - anchors.centerIn: innerCircle - width: 40 - height: 40 - radius: 23 - color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - } - - contentItem: ColumnLayout { - id: content - - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - - ListItemTitleType { - Layout.fillWidth: true - rightPadding: indicator.width - - text: root.text - color: root.enabled ? root.textColor : root.textDisabledColor - } - - CaptionTextType { - id: description - - Layout.fillWidth: true - rightPadding: indicator.width - - color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor - - visible: text !== "" - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - enabled: false - } - - Keys.onEnterPressed: event => handleSwitch(event) - Keys.onReturnPressed: event => handleSwitch(event) - Keys.onSpacePressed: event => handleSwitch(event) - - function handleSwitch(event) { - if (!event.isAutoRepeat) { - root.checked = !root.checked - root.checkedChanged() - } - event.accepted = true - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Switch { + id: root + + property alias descriptionText: description.text + property string descriptionTextColor: AmneziaStyle.color.mutedGray + property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray + + property string textColor: AmneziaStyle.color.paleGray + property string textDisabledColor: AmneziaStyle.color.mutedGray + + property string checkedIndicatorColor: AmneziaStyle.color.richBrown + property string defaultIndicatorColor: AmneziaStyle.color.transparent + property string checkedDisabledIndicatorColor: AmneziaStyle.color.deepBrown + + property string borderFocusedColor: AmneziaStyle.color.paleGray + property int borderFocusedWidth: 1 + + property string checkedIndicatorBorderColor: AmneziaStyle.color.richBrown + property string defaultIndicatorBorderColor: AmneziaStyle.color.charcoalGray + property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.deepBrown + + property string checkedInnerCircleColor: AmneziaStyle.color.goldenApricot + property string defaultInnerCircleColor: AmneziaStyle.color.paleGray + property string checkedDisabledInnerCircleColor: AmneziaStyle.color.mutedBrown + property string defaultDisabledInnerCircleColor: AmneziaStyle.color.charcoalGray + + property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite + property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + + hoverEnabled: enabled ? true : false + focusPolicy: Qt.TabFocus + + indicator: Rectangle { + id: switcher + + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + implicitWidth: 52 + implicitHeight: 32 + + radius: 16 + color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor) + : root.defaultIndicatorColor + + border.color: root.activeFocus ? root.borderFocusedColor : (root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor) + : root.defaultIndicatorBorderColor) + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + anchors.verticalCenter: parent.verticalCenter + x: root.checked ? parent.width - width - 4 : 8 + width: root.checked ? 24 : 16 + height: root.checked ? 24 : 16 + radius: 23 + color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor) + : (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor) + + Behavior on x { + PropertyAnimation { duration: 200 } + } + } + + Rectangle { + anchors.centerIn: innerCircle + width: 40 + height: 40 + radius: 23 + color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: ColumnLayout { + id: content + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + + ListItemTitleType { + Layout.fillWidth: true + rightPadding: indicator.width + + text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + rightPadding: indicator.width + + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor + + visible: text !== "" + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } + + Keys.onEnterPressed: event => handleSwitch(event) + Keys.onReturnPressed: event => handleSwitch(event) + Keys.onSpacePressed: event => handleSwitch(event) + + function handleSwitch(event) { + if (!event.isAutoRepeat) { + root.checked = !root.checked + root.checkedChanged() + } + event.accepted = true + } +} diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 9359fa16..800f9000 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -1,118 +1,109 @@ -import QtQuick -import QtQuick.Controls - -import Style 1.0 - -Rectangle { - id: root - - property string placeholderText - property string text - property alias textArea: textArea - property alias textAreaText: textArea.text - - property string borderHoveredColor: AmneziaStyle.color.charcoalGray - property string borderNormalColor: AmneziaStyle.color.slateGray - property string borderFocusedColor: AmneziaStyle.color.paleGray - - height: 148 - color: AmneziaStyle.color.onyxBlack - border.width: 1 - border.color: getBorderColor(borderNormalColor) - radius: 16 - - property FlickableType parentFlickable: null - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - MouseArea { - id: parentMouse - anchors.fill: parent - cursorShape: Qt.IBeamCursor - onClicked: textArea.forceActiveFocus() - hoverEnabled: true - - FlickableType { - id: fl - interactive: false - - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: textArea.implicitHeight - TextArea { - id: textArea - - width: parent.width - - topPadding: 16 - leftPadding: 16 - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - color: AmneziaStyle.color.paleGray - selectionColor: AmneziaStyle.color.richBrown - selectedTextColor: AmneziaStyle.color.paleGray - placeholderTextColor: AmneziaStyle.color.mutedGray - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: root.placeholderText - text: root.text - - onCursorVisibleChanged: { - if (textArea.cursorVisible) { - fl.interactive = true - } else { - fl.interactive = false - } - } - - wrapMode: Text.Wrap - - MouseArea { - id: textAreaMouse - anchors.fill: parent - acceptedButtons: Qt.RightButton - hoverEnabled: true - onClicked: { - fl.interactive = true - contextMenu.open() - } - } - - onFocusChanged: { - root.border.color = getBorderColor(borderNormalColor) - } - - ContextMenuType { - id: contextMenu - textObj: textArea - } - } - } - - onPressed: { - root.border.color = getBorderColor(borderFocusedColor) - } - - onExited: { - root.border.color = getBorderColor(borderNormalColor) - } - - onEntered: { - root.border.color = getBorderColor(borderHoveredColor) - } - } - - - function getBorderColor(noneFocusedColor) { - return textArea.focus ? root.borderFocusedColor : noneFocusedColor - } -} +import QtQuick +import QtQuick.Controls + +import Style 1.0 + +Rectangle { + id: root + + property string placeholderText + property string text + property alias textArea: textArea + property alias textAreaText: textArea.text + + property string borderHoveredColor: AmneziaStyle.color.charcoalGray + property string borderNormalColor: AmneziaStyle.color.slateGray + property string borderFocusedColor: AmneziaStyle.color.paleGray + + height: 148 + color: AmneziaStyle.color.onyxBlack + border.width: 1 + border.color: getBorderColor(borderNormalColor) + radius: 16 + + MouseArea { + id: parentMouse + anchors.fill: parent + cursorShape: Qt.IBeamCursor + onClicked: textArea.forceActiveFocus() + hoverEnabled: true + + FlickableType { + id: fl + interactive: false + + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea + + width: parent.width + + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + color: AmneziaStyle.color.paleGray + selectionColor: AmneziaStyle.color.richBrown + selectedTextColor: AmneziaStyle.color.paleGray + placeholderTextColor: AmneziaStyle.color.mutedGray + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: root.placeholderText + text: root.text + + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + + wrapMode: Text.Wrap + + MouseArea { + id: textAreaMouse + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + onFocusChanged: { + root.border.color = getBorderColor(borderNormalColor) + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + } + + onPressed: { + root.border.color = getBorderColor(borderFocusedColor) + } + + onExited: { + root.border.color = getBorderColor(borderNormalColor) + } + + onEntered: { + root.border.color = getBorderColor(borderHoveredColor) + } + } + + + function getBorderColor(noneFocusedColor) { + return textArea.focus ? root.borderFocusedColor : noneFocusedColor + } +} diff --git a/client/ui/qml/Controls2/TextAreaWithFooterType.qml b/client/ui/qml/Controls2/TextAreaWithFooterType.qml index cf7b9146..2cb6a69c 100644 --- a/client/ui/qml/Controls2/TextAreaWithFooterType.qml +++ b/client/ui/qml/Controls2/TextAreaWithFooterType.qml @@ -1,180 +1,171 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Rectangle { - id: root - - property string placeholderText - property string text - property string headerText - property alias textArea: textArea - property alias textAreaText: textArea.text - - property string borderHoveredColor: AmneziaStyle.color.charcoalGray - property string borderNormalColor: AmneziaStyle.color.slateGray - property string borderFocusedColor: AmneziaStyle.color.paleGray - - property string firstButtonImage - property string secondButtonImage - - property var firstButtonClickedFunc - property var secondButtonClickedFunc - - height: 148 - color: AmneziaStyle.color.onyxBlack - border.width: 1 - border.color: getBorderColor(borderNormalColor) - radius: 16 - - property FlickableType parentFlickable: null - onFocusChanged: { - if (root.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - - MouseArea { - id: parentMouse - anchors.fill: parent - cursorShape: Qt.IBeamCursor - onClicked: textArea.forceActiveFocus() - hoverEnabled: true - - ColumnLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 0 - - LabelTextType { - Layout.fillWidth: true - text: root.headerText - } - - TextArea { - id: textArea - - Layout.fillWidth: true - Layout.fillHeight: true - - leftPadding: 0 - Layout.bottomMargin: 16 - - color: AmneziaStyle.color.paleGray - selectionColor: AmneziaStyle.color.richBrown - selectedTextColor: AmneziaStyle.color.paleGray - placeholderTextColor: AmneziaStyle.color.mutedGray - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: root.placeholderText - text: root.text - - onCursorVisibleChanged: { - if (textArea.cursorVisible) { - fl.interactive = true - } else { - fl.interactive = false - } - } - - wrapMode: Text.Wrap - - MouseArea { - id: textAreaMouse - anchors.fill: parent - acceptedButtons: Qt.RightButton - hoverEnabled: true - onClicked: { - fl.interactive = true - contextMenu.open() - } - } - - onFocusChanged: { - root.border.color = getBorderColor(borderNormalColor) - } - - ContextMenuType { - id: contextMenu - textObj: textArea - } - } - - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: -8 - spacing: 0 - ImageButtonType { - id: firstButton - visible: root.firstButtonImage !== "" - - imageColor: AmneziaStyle.color.paleGray - - image: root.firstButtonImage - onClicked: function() { - if (root.firstButtonClickedFunc && typeof root.firstButtonClickedFunc === "function") { - root.firstButtonClickedFunc() - } - } - } - - ImageButtonType { - id: secondButton - visible: root.secondButtonImage !== "" - - imageColor: AmneziaStyle.color.paleGray - - image: root.secondButtonImage - onClicked: function() { - if (root.secondButtonClickedFunc && typeof root.secondButtonClickedFunc === "function") { - root.secondButtonClickedFunc() - } - } - } - - Item { - Layout.fillWidth: true - } - - ImageButtonType { - id: resetButton - imageColor: AmneziaStyle.color.paleGray - - visible: root.textAreaText !== "" - image: "qrc:/images/controls/close.svg" - - onClicked: function() { - root.textAreaText = "" - textArea.focus = true - } - } - } - } - - onPressed: { - root.border.color = getBorderColor(borderFocusedColor) - } - - onExited: { - root.border.color = getBorderColor(borderNormalColor) - } - - onEntered: { - root.border.color = getBorderColor(borderHoveredColor) - } - } - - - function getBorderColor(noneFocusedColor) { - return textArea.focus ? root.borderFocusedColor : noneFocusedColor - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Rectangle { + id: root + + property string placeholderText + property string text + property string headerText + property alias textArea: textArea + property alias textAreaText: textArea.text + + property string borderHoveredColor: AmneziaStyle.color.charcoalGray + property string borderNormalColor: AmneziaStyle.color.slateGray + property string borderFocusedColor: AmneziaStyle.color.paleGray + + property string firstButtonImage + property string secondButtonImage + + property var firstButtonClickedFunc + property var secondButtonClickedFunc + + height: 148 + color: AmneziaStyle.color.onyxBlack + border.width: 1 + border.color: getBorderColor(borderNormalColor) + radius: 16 + + MouseArea { + id: parentMouse + anchors.fill: parent + cursorShape: Qt.IBeamCursor + onClicked: textArea.forceActiveFocus() + hoverEnabled: true + + ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 0 + + LabelTextType { + Layout.fillWidth: true + text: root.headerText + } + + TextArea { + id: textArea + + Layout.fillWidth: true + Layout.fillHeight: true + + leftPadding: 0 + Layout.bottomMargin: 16 + + color: AmneziaStyle.color.paleGray + selectionColor: AmneziaStyle.color.richBrown + selectedTextColor: AmneziaStyle.color.paleGray + placeholderTextColor: AmneziaStyle.color.mutedGray + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: root.placeholderText + text: root.text + + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + + wrapMode: Text.Wrap + + MouseArea { + id: textAreaMouse + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + onFocusChanged: { + root.border.color = getBorderColor(borderNormalColor) + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: -8 + spacing: 0 + ImageButtonType { + id: firstButton + visible: root.firstButtonImage !== "" + + imageColor: AmneziaStyle.color.paleGray + + image: root.firstButtonImage + onClicked: function() { + if (root.firstButtonClickedFunc && typeof root.firstButtonClickedFunc === "function") { + root.firstButtonClickedFunc() + } + } + } + + ImageButtonType { + id: secondButton + visible: root.secondButtonImage !== "" + + imageColor: AmneziaStyle.color.paleGray + + image: root.secondButtonImage + onClicked: function() { + if (root.secondButtonClickedFunc && typeof root.secondButtonClickedFunc === "function") { + root.secondButtonClickedFunc() + } + } + } + + Item { + Layout.fillWidth: true + } + + ImageButtonType { + id: resetButton + imageColor: AmneziaStyle.color.paleGray + + visible: root.textAreaText !== "" + image: "qrc:/images/controls/close.svg" + + onClicked: function() { + root.textAreaText = "" + textArea.focus = true + } + } + } + } + + onPressed: { + root.border.color = getBorderColor(borderFocusedColor) + } + + onExited: { + root.border.color = getBorderColor(borderNormalColor) + } + + onEntered: { + root.border.color = getBorderColor(borderHoveredColor) + } + } + + + function getBorderColor(noneFocusedColor) { + return textArea.focus ? root.borderFocusedColor : noneFocusedColor + } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index c4ed91b3..d7697867 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -1,233 +1,220 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Item { - id: root - - property string headerText - property string headerTextDisabledColor: AmneziaStyle.color.charcoalGray - property string headerTextColor: AmneziaStyle.color.mutedGray - - property alias errorText: errorField.text - property bool checkEmptyText: false - property bool rightButtonClickedOnEnter: false - - property string buttonText - property string buttonImageSource - property var clickedFunc - - property alias textField: textField - property string textFieldTextColor: AmneziaStyle.color.paleGray - property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray - - property bool textFieldEditable: true - - property string borderColor: AmneziaStyle.color.slateGray - property string borderFocusedColor: AmneziaStyle.color.paleGray - - property string backgroundColor: AmneziaStyle.color.onyxBlack - property string backgroundDisabledColor: AmneziaStyle.color.transparent - property string bgBorderHoveredColor: AmneziaStyle.color.charcoalGray - - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight - - property FlickableType parentFlickable - - Connections { - target: textField - function onFocusChanged() { - if (textField.activeFocus) { - if (root.parentFlickable) { - root.parentFlickable.ensureVisible(root) - } - } - } - } - - ColumnLayout { - id: content - anchors.fill: parent - - Rectangle { - id: backgroud - Layout.fillWidth: true - Layout.preferredHeight: input.implicitHeight - color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor - radius: 16 - border.color: getBackgroundBorderColor(root.borderColor) - border.width: 1 - - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - - RowLayout { - id: input - anchors.fill: backgroud - ColumnLayout { - Layout.margins: 16 - LabelTextType { - text: root.headerText - color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor - - visible: text !== "" - - Layout.fillWidth: true - } - - TextField { - id: textField - - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - enabled: root.textFieldEditable - color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor - - inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText - - placeholderTextColor: AmneziaStyle.color.charcoalGray - - selectionColor: AmneziaStyle.color.richBrown - selectedTextColor: AmneziaStyle.color.paleGray - - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - Layout.fillWidth: true - - topPadding: 0 - rightPadding: 0 - leftPadding: 0 - bottomPadding: 0 - - background: Rectangle { - anchors.fill: parent - color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor - } - - onTextChanged: { - root.errorText = "" - } - - onActiveFocusChanged: { - if (root.checkEmptyText && text === "") { - root.errorText = qsTr("The field can't be empty") - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() - enabled: true - } - - ContextMenuType { - id: contextMenu - textObj: textField - } - - onFocusChanged: { - backgroud.border.color = getBackgroundBorderColor(root.borderColor) - } - } - } - } - } - - SmallTextType { - id: errorField - - text: root.errorText - visible: root.errorText !== "" - color: AmneziaStyle.color.vibrantRed - - Layout.fillWidth: true - } - } - - MouseArea { - anchors.fill: root - cursorShape: Qt.IBeamCursor - - hoverEnabled: true - - onPressed: function(mouse) { - textField.forceActiveFocus() - mouse.accepted = false - - backgroud.border.color = getBackgroundBorderColor(root.borderColor) - } - - onEntered: { - backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) - } - - - onExited: { - backgroud.border.color = getBackgroundBorderColor(root.borderColor) - } - } - - BasicButtonType { - visible: (root.buttonText !== "") || (root.buttonImageSource !== "") - - focusPolicy: Qt.NoFocus - text: root.buttonText - leftImageSource: 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) { - return textField.focus ? root.borderFocusedColor : noneFocusedColor - } - - Keys.onEnterPressed: { - if (root.rightButtonClickedOnEnter && root.clickedFunc && typeof root.clickedFunc === "function") { - clickedFunc() - } - - // if (KeyNavigation.tab) { - // KeyNavigation.tab.forceActiveFocus(); - // } - } - - Keys.onReturnPressed: { - if (root.rightButtonClickedOnEnter &&root.clickedFunc && typeof root.clickedFunc === "function") { - clickedFunc() - } - - // if (KeyNavigation.tab) { - // KeyNavigation.tab.forceActiveFocus(); - // } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Item { + id: root + + property string headerText + property string headerTextDisabledColor: AmneziaStyle.color.charcoalGray + property string headerTextColor: AmneziaStyle.color.mutedGray + + property alias errorText: errorField.text + property bool checkEmptyText: false + property bool rightButtonClickedOnEnter: false + + property string buttonText + property string buttonImageSource + property var clickedFunc + + property alias textField: textField + property string textFieldTextColor: AmneziaStyle.color.paleGray + property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray + + property bool textFieldEditable: true + + property string borderColor: AmneziaStyle.color.slateGray + property string borderFocusedColor: AmneziaStyle.color.paleGray + + property string backgroundColor: AmneziaStyle.color.onyxBlack + property string backgroundDisabledColor: AmneziaStyle.color.transparent + property string bgBorderHoveredColor: AmneziaStyle.color.charcoalGray + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + Rectangle { + id: backgroud + Layout.fillWidth: true + Layout.preferredHeight: input.implicitHeight + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor + radius: 16 + border.color: getBackgroundBorderColor(root.borderColor) + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + RowLayout { + id: input + anchors.fill: backgroud + ColumnLayout { + Layout.margins: 16 + LabelTextType { + text: root.headerText + color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor + + visible: text !== "" + + Layout.fillWidth: true + } + + TextField { + id: textField + + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + enabled: root.textFieldEditable + color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor + + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText + + placeholderTextColor: AmneziaStyle.color.charcoalGray + + selectionColor: AmneziaStyle.color.richBrown + selectedTextColor: AmneziaStyle.color.paleGray + + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor + } + + onTextChanged: { + root.errorText = "" + } + + onActiveFocusChanged: { + if (root.checkEmptyText && text === "") { + root.errorText = qsTr("The field can't be empty") + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + enabled: true + } + + ContextMenuType { + id: contextMenu + textObj: textField + } + + onFocusChanged: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + } + } + } + } + + SmallTextType { + id: errorField + + text: root.errorText + visible: root.errorText !== "" + color: AmneziaStyle.color.vibrantRed + + Layout.fillWidth: true + } + } + + MouseArea { + anchors.fill: root + cursorShape: Qt.IBeamCursor + + hoverEnabled: true + + onPressed: function(mouse) { + textField.forceActiveFocus() + mouse.accepted = false + + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + + onEntered: { + backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) + } + + + onExited: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + } + + BasicButtonType { + visible: (root.buttonText !== "") || (root.buttonImageSource !== "") + + focusPolicy: Qt.NoFocus + text: root.buttonText + leftImageSource: 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) { + return textField.focus ? root.borderFocusedColor : noneFocusedColor + } + + Keys.onEnterPressed: { + if (root.rightButtonClickedOnEnter && root.clickedFunc && typeof root.clickedFunc === "function") { + clickedFunc() + } + + // if (KeyNavigation.tab) { + // KeyNavigation.tab.forceActiveFocus(); + // } + } + + Keys.onReturnPressed: { + if (root.rightButtonClickedOnEnter &&root.clickedFunc && typeof root.clickedFunc === "function") { + clickedFunc() + } + + // if (KeyNavigation.tab) { + // KeyNavigation.tab.forceActiveFocus(); + // } + } +} diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 69b1f319..03d26f8d 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -20,7 +20,9 @@ PageType { SortFilterProxyModel { id: proxyServersModel + sourceModel: ServersModel + filters: [ ValueFilter { roleName: "isCurrentlyProcessed" @@ -29,67 +31,55 @@ PageType { ] } - FlickableType { - id: fl + ListViewType { + id: listView + anchors.fill: parent - contentHeight: content.height - Column { - id: content + spacing: 16 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: proxyServersModel - spacing: 16 + delegate: ColumnLayout { + width: listView.width - Repeater { - model: proxyServersModel - delegate: Item { - implicitWidth: parent.width - implicitHeight: delegateContent.implicitHeight + BaseHeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - ColumnLayout { - id: delegateContent + headerText: qsTr("Removing services from %1").arg(name) + } - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 + ProgressBarType { + id: progressBar - BaseHeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("Removing services from %1").arg(name) - } + Timer { + id: timer - ProgressBarType { - id: progressBar - - Layout.fillWidth: true - Layout.topMargin: 32 - - Timer { - id: timer - - interval: 300 - repeat: true - running: true - onTriggered: { - progressBar.value += 0.003 - } - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 8 - - text: qsTr("Usually it takes no more than 5 minutes") - } + interval: 300 + repeat: true + running: true + onTriggered: { + progressBar.value += 0.003 } } } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Usually it takes no more than 5 minutes") + } } } } diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index 5fccb43a..9f042fad 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -25,23 +25,17 @@ PageType { anchors.topMargin: 20 } - ListView { + ListViewType { id: listView anchors.top: backButton.bottom anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - header: ColumnLayout { width: listView.width BaseHeaderType { - id: header - Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 @@ -50,16 +44,14 @@ PageType { } } - model: 1 - clip: true + model: 1 // fake model to force the ListView to be created without a model + spacing: 16 delegate: ColumnLayout { width: listView.width TextFieldWithHeaderType { - id: passwordTextField - Layout.fillWidth: true Layout.topMargin: 16 Layout.rightMargin: 16 @@ -87,8 +79,6 @@ PageType { width: listView.width SwitcherType { - id: switcher - Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index b8cf5f93..17f853a1 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -30,233 +30,223 @@ PageType { } } - ListView { - id: listview + ListViewType { + id: listView anchors.top: backButtonLayout.bottom anchors.bottom: saveButton.top width: parent.width - clip: true - - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - model: AwgConfigModel - delegate: Item { - id: delegateItem - implicitWidth: listview.width - implicitHeight: col.implicitHeight + delegate: ColumnLayout { + width: listView.width - property alias mtuTextField: mtuTextField property bool isSaveButtonEnabled: mtuTextField.errorText === "" && junkPacketMaxSizeTextField.errorText === "" && junkPacketMinSizeTextField.errorText === "" && junkPacketCountTextField.errorText === "" - ColumnLayout { - id: col + spacing: 0 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 + headerText: qsTr("AmneziaWG settings") + } - spacing: 0 + TextFieldWithHeaderType { + id: mtuTextField - BaseHeaderType { - Layout.fillWidth: true + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("AmneziaWG settings") - } + headerText: qsTr("MTU") + textField.text: clientMtu + textField.validator: IntValidator { bottom: 576; top: 65535 } - TextFieldWithHeaderType { - id: mtuTextField - Layout.fillWidth: true - Layout.topMargin: 40 - - headerText: qsTr("MTU") - textField.text: clientMtu - textField.validator: IntValidator { bottom: 576; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== clientMtu) { - clientMtu = textField.text - } + textField.onEditingFinished: { + if (textField.text !== clientMtu) { + clientMtu = textField.text } - checkEmptyText: true - KeyNavigation.tab: junkPacketCountTextField.textField } + checkEmptyText: true + } - TextFieldWithHeaderType { - id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 + TextFieldWithHeaderType { + id: junkPacketCountTextField - headerText: "Jc - Junk packet count" - textField.text: clientJunkPacketCount - textField.validator: IntValidator { bottom: 0 } + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== clientJunkPacketCount) { - clientJunkPacketCount = textField.text - } + headerText: "Jc - Junk packet count" + textField.text: clientJunkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== clientJunkPacketCount) { + clientJunkPacketCount = textField.text } - - checkEmptyText: true - - KeyNavigation.tab: junkPacketMinSizeTextField.textField } - TextFieldWithHeaderType { - id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: "Jmin - Junk packet minimum size" - textField.text: clientJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField - textField.onEditingFinished: { - if (textField.text !== clientJunkPacketMinSize) { - clientJunkPacketMinSize = textField.text - } + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Jmin - Junk packet minimum size" + textField.text: clientJunkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== clientJunkPacketMinSize) { + clientJunkPacketMinSize = textField.text } - - checkEmptyText: true - - KeyNavigation.tab: junkPacketMaxSizeTextField.textField } - TextFieldWithHeaderType { - id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: "Jmax - Junk packet maximum size" - textField.text: clientJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField - textField.onEditingFinished: { - if (textField.text !== clientJunkPacketMaxSize) { - clientJunkPacketMaxSize = textField.text - } + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Jmax - Junk packet maximum size" + textField.text: clientJunkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== clientJunkPacketMaxSize) { + clientJunkPacketMaxSize = textField.text } - - checkEmptyText: true - } - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true - text: qsTr("Server settings") - } + } - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 8 + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - enabled: false + text: qsTr("Server settings") + } - headerText: qsTr("Port") - textField.text: port - } + TextFieldWithHeaderType { + id: portTextField - TextFieldWithHeaderType { - id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - enabled: false + enabled: false - headerText: "S1 - Init packet junk size" - textField.text: serverInitPacketJunkSize - } + headerText: qsTr("Port") + textField.text: port + } - TextFieldWithHeaderType { - id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField - enabled: false + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: "S2 - Response packet junk size" - textField.text: serverResponsePacketJunkSize - } + enabled: false - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + headerText: "S1 - Init packet junk size" + textField.text: serverInitPacketJunkSize + } - enabled: false + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField - headerText: "H1 - Init packet magic header" - textField.text: serverInitPacketMagicHeader - } + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - TextFieldWithHeaderType { - id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + enabled: false - enabled: false + headerText: "S2 - Response packet junk size" + textField.text: serverResponsePacketJunkSize + } - headerText: "H2 - Response packet magic header" - textField.text: serverResponsePacketMagicHeader - } + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField - TextFieldWithHeaderType { - id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - enabled: false + enabled: false - headerText: "H3 - Underload packet magic header" - textField.text: serverUnderloadPacketMagicHeader - } + headerText: "H1 - Init packet magic header" + textField.text: serverInitPacketMagicHeader + } - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField - enabled: false + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: "H4 - Transport packet magic header" - textField.text: serverTransportPacketMagicHeader - } + enabled: false + + headerText: "H2 - Response packet magic header" + textField.text: serverResponsePacketMagicHeader + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: false + + headerText: "H3 - Underload packet magic header" + textField.text: serverUnderloadPacketMagicHeader + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: false + + headerText: "H4 - Transport packet magic header" + textField.text: serverTransportPacketMagicHeader } } } @@ -273,18 +263,17 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - enabled: listview.currentItem.isSaveButtonEnabled + enabled: listView.currentItem.isSaveButtonEnabled text: qsTr("Save") onActiveFocusChanged: { if(activeFocus) { - listview.positionViewAtEnd() + listView.positionViewAtEnd() } } clickedFunc: function() { - forceActiveFocus() var headerText = qsTr("Save settings?") var descriptionText = qsTr("Only the settings for this device will be changed") var yesButtonText = qsTr("Continue") @@ -299,11 +288,9 @@ PageType { PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.updateContainer(AwgConfigModel.getConfig()) } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveButton.forceActiveFocus() - } - } + + var noButtonFunction = function() {} + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index e8fd2b94..dd2c9b54 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -33,356 +33,339 @@ PageType { } } - ListView { - id: listview - - property bool isFocusable: true + ListViewType { + id: listView anchors.top: backButtonLayout.bottom anchors.bottom: parent.bottom width: parent.width - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - clip: true - model: AwgConfigModel - delegate: Item { + delegate: ColumnLayout { id: delegateItem - implicitWidth: listview.width - implicitHeight: col.implicitHeight + + width: listView.width property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() - ColumnLayout { - id: col + spacing: 0 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 + headerText: qsTr("AmneziaWG settings") + } - spacing: 0 + TextFieldWithHeaderType { + id: vpnAddressSubnetTextField - BaseHeaderType { - Layout.fillWidth: true + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("AmneziaWG settings") - } + enabled: delegateItem.isEnabled - TextFieldWithHeaderType { - id: vpnAddressSubnetTextField + headerText: qsTr("VPN address subnet") + textField.text: subnetAddress - Layout.fillWidth: true - Layout.topMargin: 40 - - enabled: delegateItem.isEnabled - - headerText: qsTr("VPN address subnet") - textField.text: subnetAddress - - textField.onEditingFinished: { - if (textField.text !== subnetAddress) { - subnetAddress = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: delegateItem.isEnabled - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jc - Junk packet count") - textField.text: serverJunkPacketCount - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text === "") { - textField.text = "0" - } - - if (textField.text !== serverJunkPacketCount) { - serverJunkPacketCount = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jmin - Junk packet minimum size") - textField.text: serverJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text !== serverJunkPacketMinSize) { - serverJunkPacketMinSize = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Jmax - Junk packet maximum size") - textField.text: serverJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text !== serverJunkPacketMaxSize) { - serverJunkPacketMaxSize = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("S1 - Init packet junk size") - textField.text: serverInitPacketJunkSize - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text !== serverInitPacketJunkSize) { - serverInitPacketJunkSize = textField.text - } - } - - checkEmptyText: true - - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } + textField.onEditingFinished: { + if (textField.text !== subnetAddress) { + subnetAddress = textField.text } } - TextFieldWithHeaderType { - id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: qsTr("S2 - Response packet junk size") - textField.text: serverResponsePacketJunkSize - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== serverResponsePacketJunkSize) { - serverResponsePacketJunkSize = textField.text - } - } + enabled: delegateItem.isEnabled - checkEmptyText: true + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text } } - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: qsTr("H1 - Init packet magic header") - textField.text: serverInitPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: junkPacketCountTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== serverInitPacketMagicHeader) { - serverInitPacketMagicHeader = textField.text - } + headerText: qsTr("Jc - Junk packet count") + textField.text: serverJunkPacketCount + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text === "") { + textField.text = "0" } - checkEmptyText: true + if (textField.text !== serverJunkPacketCount) { + serverJunkPacketCount = textField.text + } } - TextFieldWithHeaderType { - id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: qsTr("H2 - Response packet magic header") - textField.text: serverResponsePacketMagicHeader - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: junkPacketMinSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== serverResponsePacketMagicHeader) { - serverResponsePacketMagicHeader = textField.text - } + headerText: qsTr("Jmin - Junk packet minimum size") + textField.text: serverJunkPacketMinSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverJunkPacketMinSize) { + serverJunkPacketMinSize = textField.text } - - checkEmptyText: true } - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: qsTr("H4 - Transport packet magic header") - textField.text: serverTransportPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== serverTransportPacketMagicHeader) { - serverTransportPacketMagicHeader = textField.text - } + headerText: qsTr("Jmax - Junk packet maximum size") + textField.text: serverJunkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverJunkPacketMaxSize) { + serverJunkPacketMaxSize = textField.text } - - checkEmptyText: true } - TextFieldWithHeaderType { - id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + checkEmptyText: true + } - headerText: qsTr("H3 - Underload packet magic header") - textField.text: serverUnderloadPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } + TextFieldWithHeaderType { + id: initPacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textField.onEditingFinished: { - if (textField.text !== serverUnderloadPacketMagicHeader) { - serverUnderloadPacketMagicHeader = textField.text - } + headerText: qsTr("S1 - Init packet junk size") + textField.text: serverInitPacketJunkSize + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverInitPacketJunkSize) { + serverInitPacketJunkSize = textField.text } - - checkEmptyText: true } - BasicButtonType { - id: saveRestartButton + checkEmptyText: true - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } + } - enabled: underloadPacketMagicHeaderTextField.errorText === "" && - transportPacketMagicHeaderTextField.errorText === "" && - responsePacketMagicHeaderTextField.errorText === "" && - initPacketMagicHeaderTextField.errorText === "" && - responsePacketJunkSizeTextField.errorText === "" && - initPacketJunkSizeTextField.errorText === "" && - junkPacketMaxSizeTextField.errorText === "" && - junkPacketMinSizeTextField.errorText === "" && - junkPacketCountTextField.errorText === "" && - portTextField.errorText === "" && - vpnAddressSubnetTextField.errorText === "" + TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - text: qsTr("Save") + headerText: qsTr("S2 - Response packet junk size") + textField.text: serverResponsePacketJunkSize + textField.validator: IntValidator { bottom: 0 } - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() + textField.onEditingFinished: { + if (textField.text !== serverResponsePacketJunkSize) { + serverResponsePacketJunkSize = textField.text + } + } + + checkEmptyText: true + + onActiveFocusChanged: { + if(activeFocus) { + listview.positionViewAtEnd() + } + } + } + + TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("H1 - Init packet magic header") + textField.text: serverInitPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverInitPacketMagicHeader) { + serverInitPacketMagicHeader = textField.text + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("H2 - Response packet magic header") + textField.text: serverResponsePacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverResponsePacketMagicHeader) { + serverResponsePacketMagicHeader = textField.text + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("H4 - Transport packet magic header") + textField.text: serverTransportPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverTransportPacketMagicHeader) { + serverTransportPacketMagicHeader = textField.text + } + } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("H3 - Underload packet magic header") + textField.text: serverUnderloadPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } + + textField.onEditingFinished: { + if (textField.text !== serverUnderloadPacketMagicHeader) { + serverUnderloadPacketMagicHeader = textField.text + } + } + + checkEmptyText: true + } + + BasicButtonType { + id: saveRestartButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: underloadPacketMagicHeaderTextField.errorText === "" && + transportPacketMagicHeaderTextField.errorText === "" && + responsePacketMagicHeaderTextField.errorText === "" && + initPacketMagicHeaderTextField.errorText === "" && + responsePacketJunkSizeTextField.errorText === "" && + initPacketJunkSizeTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" && + portTextField.errorText === "" && + vpnAddressSubnetTextField.errorText === "" + + text: qsTr("Save") + + onActiveFocusChanged: { + if(activeFocus) { + listView.positionViewAtEnd() + } + } + + clickedFunc: function() { + if (delegateItem.isEnabled) { + if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text, + transportPacketMagicHeaderTextField.textField.text, + responsePacketMagicHeaderTextField.textField.text, + initPacketMagicHeaderTextField.textField.text)) { + PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique")) + return + } + + if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), + parseInt(responsePacketJunkSizeTextField.textField.text))) { + PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) + return } } - clickedFunc: function() { - forceActiveFocus() + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - if (delegateItem.isEnabled) { - if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text, - transportPacketMagicHeaderTextField.textField.text, - responsePacketMagicHeaderTextField.textField.text, - initPacketMagicHeaderTextField.textField.text)) { - PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique")) - return - } - - if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), - parseInt(responsePacketJunkSizeTextField.textField.text))) { - PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) - return - } + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return } - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(AwgConfigModel.getConfig()) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveRestartButton.forceActiveFocus() - } - } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(AwgConfigModel.getConfig()) } + + var noButtonFunction = function() {} + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 7a0fafbd..a323afa0 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -16,179 +16,161 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.left: parent.left + anchors.right: parent.right - Column { - id: content + property int selectedIndex: 0 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + enabled: ServersModel.isProcessedServerHasWriteAccess() - enabled: ServersModel.isProcessedServerHasWriteAccess() + header: ColumnLayout { - ListView { - id: listview + width: listView.width - property int selectedIndex: 0 + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - width: parent.width - height: listview.contentItem.height + headerText: qsTr("Cloak settings") + } + } - clip: true - reuseItems: true + model: CloakConfigModel - model: CloakConfigModel + delegate: ColumnLayout { - delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + width: listView.width - property alias trafficFromField: trafficFromField + property alias trafficFromField: trafficFromField - ColumnLayout { - id: col + spacing: 0 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + TextFieldWithHeaderType { + id: trafficFromField - anchors.leftMargin: 16 - anchors.rightMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - spacing: 0 + headerText: qsTr("Disguised as traffic from") + textField.text: site - BaseHeaderType { - Layout.fillWidth: true + textField.onEditingFinished: { + if (textField.text !== site) { + var tmpText = textField.text + tmpText = tmpText.toLocaleLowerCase() - headerText: qsTr("Cloak settings") + var indexHttps = tmpText.indexOf("https://") + if (indexHttps === 0) { + tmpText = textField.text.substring(8) + } else { + site = textField.text } + } + } + } - TextFieldWithHeaderType { - id: trafficFromField + TextFieldWithHeaderType { + id: portTextField - Layout.fillWidth: true - Layout.topMargin: 32 + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("Disguised as traffic from") - textField.text: site + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } - textField.onEditingFinished: { - if (textField.text !== site) { - var tmpText = textField.text - tmpText = tmpText.toLocaleLowerCase() + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + } - var indexHttps = tmpText.indexOf("https://") - if (indexHttps === 0) { - tmpText = textField.text.substring(8) - } else { - site = textField.text - } - } - } - } + DropDownType { + id: cipherDropDown - TextFieldWithHeaderType { - id: portTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - Layout.fillWidth: true - Layout.topMargin: 16 + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } + drawerParent: root - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - } + listView: ListViewWithRadioButtonType { + id: cipherListView - DropDownType { - id: cipherDropDown - Layout.fillWidth: true - Layout.topMargin: 16 + rootWidth: root.width - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } - drawerParent: root + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.closeTriggered() + } - listView: ListViewWithRadioButtonType { - id: cipherListView + Component.onCompleted: { + cipherDropDown.text = cipher - rootWidth: root.width - - model: ListModel { - ListElement { name : "chacha20-ietf-poly1305" } - ListElement { name : "xchacha20-ietf-poly1305" } - ListElement { name : "aes-256-gcm" } - ListElement { name : "aes-192-gcm" } - ListElement { name : "aes-128-gcm" } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - cipherDropDown.text = cipher - - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipherDropDown.text) { - selectedIndex = i - } - } - } - } - } - - BasicButtonType { - id: saveRestartButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("Save") - - clickedFunc: function() { - forceActiveFocus() - - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(CloakConfigModel.getConfig()) + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + selectedIndex = i } } } } } + + BasicButtonType { + id: saveRestartButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Save") + + clickedFunc: function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(CloakConfigModel.getConfig()) + } + } } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 2e00d54a..b8565d72 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -17,401 +17,380 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + enabled: ServersModel.isProcessedServerHasWriteAccess() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: OpenVpnConfigModel - enabled: ServersModel.isProcessedServerHasWriteAccess() + delegate: ColumnLayout { + width: listView.width + + property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField + + spacing: 0 + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("OpenVPN settings") + } + + TextFieldWithHeaderType { + id: vpnAddressSubnetTextField + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - ListView { - id: listview + headerText: qsTr("VPN address subnet") + textField.text: subnetAddress - width: parent.width - height: listview.contentItem.height - - clip: true - interactive: false - - model: OpenVpnConfigModel - - delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight - - property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField - - ColumnLayout { - id: col - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - - headerText: qsTr("OpenVPN settings") - } - - TextFieldWithHeaderType { - id: vpnAddressSubnetTextField - - Layout.fillWidth: true - Layout.topMargin: 32 - - headerText: qsTr("VPN address subnet") - textField.text: subnetAddress - - parentFlickable: fl - - textField.onEditingFinished: { - if (textField.text !== subnetAddress) { - subnetAddress = textField.text - } - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 32 - - text: qsTr("Network protocol") - } - - TransportProtoSelector { - id: transportProtoSelector - Layout.fillWidth: true - Layout.topMargin: 16 - rootWidth: root.width - - enabled: isTransportProtoEditable - - currentIndex: { - return transportProto === "tcp" ? 1 : 0 - } - - onCurrentIndexChanged: { - if (transportProto === "tcp" && currentIndex === 0) { - transportProto = "udp" - } else if (transportProto === "udp" && currentIndex === 1) { - transportProto = "tcp" - } - } - } - - TextFieldWithHeaderType { - id: portTextField - - Layout.fillWidth: true - Layout.topMargin: 40 - parentFlickable: fl - - enabled: isPortEditable - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - } - - SwitcherType { - id: autoNegotiateEncryprionSwitcher - - Layout.fillWidth: true - Layout.topMargin: 24 - parentFlickable: fl - - text: qsTr("Auto-negotiate encryption") - checked: autoNegotiateEncryprion - - onCheckedChanged: { - if (checked !== autoNegotiateEncryprion) { - autoNegotiateEncryprion = checked - } - } - } - - DropDownType { - id: hashDropDown - Layout.fillWidth: true - Layout.topMargin: 20 - - enabled: !autoNegotiateEncryprionSwitcher.checked - - descriptionText: qsTr("Hash") - headerText: qsTr("Hash") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - id: hashListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("SHA512") } - ListElement { name : qsTr("SHA384") } - ListElement { name : qsTr("SHA256") } - ListElement { name : qsTr("SHA3-512") } - ListElement { name : qsTr("SHA3-384") } - ListElement { name : qsTr("SHA3-256") } - ListElement { name : qsTr("whirlpool") } - ListElement { name : qsTr("BLAKE2b512") } - ListElement { name : qsTr("BLAKE2s256") } - ListElement { name : qsTr("SHA1") } - } - - clickedFunction: function() { - hashDropDown.text = selectedText - hash = hashDropDown.text - hashDropDown.closeTriggered() - } - - Component.onCompleted: { - hashDropDown.text = hash - - for (var i = 0; i < hashListView.model.count; i++) { - if (hashListView.model.get(i).name === hashDropDown.text) { - currentIndex = i - } - } - } - } - } - - DropDownType { - id: cipherDropDown - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: !autoNegotiateEncryprionSwitcher.checked - - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - id: cipherListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("AES-256-GCM") } - ListElement { name : qsTr("AES-192-GCM") } - ListElement { name : qsTr("AES-128-GCM") } - ListElement { name : qsTr("AES-256-CBC") } - ListElement { name : qsTr("AES-192-CBC") } - ListElement { name : qsTr("AES-128-CBC") } - ListElement { name : qsTr("ChaCha20-Poly1305") } - ListElement { name : qsTr("ARIA-256-CBC") } - ListElement { name : qsTr("CAMELLIA-256-CBC") } - ListElement { name : qsTr("none") } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - cipherDropDown.text = cipher - - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipherDropDown.text) { - currentIndex = i - } - } - } - } - } - - Rectangle { - id: contentRect - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.preferredHeight: checkboxLayout.implicitHeight - color: AmneziaStyle.color.onyxBlack - radius: 16 - - Connections { - target: tlsAuthCheckBox - enabled: !GC.isMobile() - - function onFocusChanged() { - if (tlsAuthCheckBox.activeFocus) { - fl.ensureVisible(contentRect) - } - } - } - - ColumnLayout { - id: checkboxLayout - - anchors.fill: parent - CheckBoxType { - id: tlsAuthCheckBox - Layout.fillWidth: true - - text: qsTr("TLS auth") - checked: tlsAuth - - onCheckedChanged: { - if (checked !== tlsAuth) { - console.log("tlsAuth changed to: " + checked) - tlsAuth = checked - } - } - } - - DividerType {} - - CheckBoxType { - id: blockDnsCheckBox - Layout.fillWidth: true - - text: qsTr("Block DNS requests outside of VPN") - checked: blockDns - - onCheckedChanged: { - if (checked !== blockDns) { - blockDns = checked - } - } - } - } - } - - SwitcherType { - id: additionalClientCommandsSwitcher - Layout.fillWidth: true - Layout.topMargin: 32 - parentFlickable: fl - - checked: additionalClientCommands !== "" - - text: qsTr("Additional client configuration commands") - - onCheckedChanged: { - if (!checked) { - additionalClientCommands = "" - } - } - } - - TextAreaType { - id: additionalClientCommandsTextArea - Layout.fillWidth: true - Layout.topMargin: 16 - - visible: additionalClientCommandsSwitcher.checked - - parentFlickable: fl - - textAreaText: additionalClientCommands - placeholderText: qsTr("Commands:") - - textArea.onEditingFinished: { - if (additionalClientCommands !== textAreaText) { - additionalClientCommands = textAreaText - } - } - } - - SwitcherType { - id: additionalServerCommandsSwitcher - Layout.fillWidth: true - Layout.topMargin: 16 - parentFlickable: fl - - checked: additionalServerCommands !== "" - - text: qsTr("Additional server configuration commands") - - onCheckedChanged: { - if (!checked) { - additionalServerCommands = "" - } - } - } - - TextAreaType { - id: additionalServerCommandsTextArea - Layout.fillWidth: true - Layout.topMargin: 16 - - visible: additionalServerCommandsSwitcher.checked - - textAreaText: additionalServerCommands - placeholderText: qsTr("Commands:") - parentFlickable: fl - textArea.onEditingFinished: { - if (additionalServerCommands !== textAreaText) { - additionalServerCommands = textAreaText - } - } - } - - BasicButtonType { - id: saveRestartButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("Save") - parentFlickable: fl - - clickedFunc: function() { - forceActiveFocus() - - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + textField.onEditingFinished: { + if (textField.text !== subnetAddress) { + subnetAddress = textField.text + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + id: transportProtoSelector + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + rootWidth: root.width + + enabled: isTransportProtoEditable + + currentIndex: { + return transportProto === "tcp" ? 1 : 0 + } + + onCurrentIndexChanged: { + if (transportProto === "tcp" && currentIndex === 0) { + transportProto = "udp" + } else if (transportProto === "udp" && currentIndex === 1) { + transportProto = "tcp" + } + } + } + + TextFieldWithHeaderType { + id: portTextField + + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: isPortEditable + + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + } + + SwitcherType { + id: autoNegotiateEncryprionSwitcher + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Auto-negotiate encryption") + checked: autoNegotiateEncryprion + + onCheckedChanged: { + if (checked !== autoNegotiateEncryprion) { + autoNegotiateEncryprion = checked + } + } + } + + DropDownType { + id: hashDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + drawerParent: root + + listView: ListViewWithRadioButtonType { + id: hashListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("SHA512") } + ListElement { name : qsTr("SHA384") } + ListElement { name : qsTr("SHA256") } + ListElement { name : qsTr("SHA3-512") } + ListElement { name : qsTr("SHA3-384") } + ListElement { name : qsTr("SHA3-256") } + ListElement { name : qsTr("whirlpool") } + ListElement { name : qsTr("BLAKE2b512") } + ListElement { name : qsTr("BLAKE2s256") } + ListElement { name : qsTr("SHA1") } + } + + clickedFunction: function() { + hashDropDown.text = selectedText + hash = hashDropDown.text + hashDropDown.closeTriggered() + } + + Component.onCompleted: { + hashDropDown.text = hash + + for (var i = 0; i < hashListView.model.count; i++) { + if (hashListView.model.get(i).name === hashDropDown.text) { + currentIndex = i } } } } } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + drawerParent: root + + listView: ListViewWithRadioButtonType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("AES-256-GCM") } + ListElement { name : qsTr("AES-192-GCM") } + ListElement { name : qsTr("AES-128-GCM") } + ListElement { name : qsTr("AES-256-CBC") } + ListElement { name : qsTr("AES-192-CBC") } + ListElement { name : qsTr("AES-128-CBC") } + ListElement { name : qsTr("ChaCha20-Poly1305") } + ListElement { name : qsTr("ARIA-256-CBC") } + ListElement { name : qsTr("CAMELLIA-256-CBC") } + ListElement { name : qsTr("none") } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.closeTriggered() + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + Rectangle { + id: contentRect + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.preferredHeight: checkboxLayout.implicitHeight + color: AmneziaStyle.color.onyxBlack + radius: 16 + + ColumnLayout { + id: checkboxLayout + + anchors.fill: parent + CheckBoxType { + id: tlsAuthCheckBox + Layout.fillWidth: true + + text: qsTr("TLS auth") + checked: tlsAuth + + onCheckedChanged: { + if (checked !== tlsAuth) { + console.log("tlsAuth changed to: " + checked) + tlsAuth = checked + } + } + } + + DividerType {} + + CheckBoxType { + id: blockDnsCheckBox + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + checked: blockDns + + onCheckedChanged: { + if (checked !== blockDns) { + blockDns = checked + } + } + } + } + } + + SwitcherType { + id: additionalClientCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + checked: additionalClientCommands !== "" + + text: qsTr("Additional client configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalClientCommands = "" + } + } + } + + TextAreaType { + id: additionalClientCommandsTextArea + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: additionalClientCommandsSwitcher.checked + + textAreaText: additionalClientCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (additionalClientCommands !== textAreaText) { + additionalClientCommands = textAreaText + } + } + } + + SwitcherType { + id: additionalServerCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + checked: additionalServerCommands !== "" + + text: qsTr("Additional server configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalServerCommands = "" + } + } + } + + TextAreaType { + id: additionalServerCommandsTextArea + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: additionalServerCommandsSwitcher.checked + + textAreaText: additionalServerCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (additionalServerCommands !== textAreaText) { + additionalServerCommands = textAreaText + } + } + } + + BasicButtonType { + id: saveRestartButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Save") + + clickedFunc: function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + } + } } } } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index bba3eafe..4360e1ce 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -19,164 +19,152 @@ import "../Components" PageType { id: root - ColumnLayout { - id: header + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton - } - - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: header.bottom - anchors.left: parent.left + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom anchors.right: parent.right - contentHeight: content.height + anchors.left: parent.left - Column { - id: content + header: ColumnLayout { + width: listView.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 32 + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - ListView { - id: listView - width: parent.width - height: contentItem.height - clip: true - interactive: false - model: ProtocolsModel + headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") + } + } - activeFocusOnTab: true - focus: true + model: ProtocolsModel - delegate: Item { - implicitWidth: parent.width - implicitHeight: delegateContent.implicitHeight + delegate: ColumnLayout { + width: listView.width - property alias focusItem: button + LabelWithButtonType { + id: button - ColumnLayout { - id: delegateContent + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - anchors.fill: parent + text: qsTr("Show connection options") - LabelWithButtonType { - id: button + clickedFunction: function() { + configContentDrawer.openTriggered() + } - Layout.fillWidth: true + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } + } - text: qsTr("Show connection options") + DividerType {} - clickedFunction: function() { - configContentDrawer.openTriggered() - } + DrawerType2 { + id: configContentDrawer - MouseArea { - anchors.fill: button - cursorShape: Qt.PointingHandCursor - enabled: false + expandedHeight: root.height * 0.9 + + parent: root + anchors.fill: parent + + expandedStateContent: Item { + implicitHeight: configContentDrawer.expandedHeight + + BackButtonType { + id: drawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.closeTriggered() + } + } + + ListViewType { + id: drawerListView + + anchors.top: drawerBackButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: drawerListView.width + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Connection options %1").arg(protocolName) } } - DividerType {} + model: 1 // fake model to force the ListView to be created without a model - DrawerType2 { - id: configContentDrawer + delegate: ColumnLayout { + width: drawerListView.width - expandedHeight: root.height * 0.9 + TextArea { + id: configText - parent: root - anchors.fill: parent + Layout.fillWidth: true + Layout.topMargin: 16 - expandedStateContent: Item { - implicitHeight: configContentDrawer.expandedHeight + padding: 0 + height: 24 - BackButtonType { - id: backButton1 + color: AmneziaStyle.color.paleGray + selectionColor: AmneziaStyle.color.richBrown + selectedTextColor: AmneziaStyle.color.paleGray - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" - backButtonFunction: function() { - configContentDrawer.closeTriggered() - } - } + text: rawConfig - FlickableType { - anchors.top: backButton1.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + wrapMode: Text.Wrap - ColumnLayout { - id: configContent - - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2Type { - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Connection options %1").arg(protocolName) - } - - TextArea { - id: configText - - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - - padding: 0 - leftPadding: 0 - height: 24 - - color: AmneziaStyle.color.paleGray - selectionColor: AmneziaStyle.color.richBrown - selectedTextColor: AmneziaStyle.color.paleGray - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - text: rawConfig - - wrapMode: Text.Wrap - - background: Rectangle { - color: AmneziaStyle.color.transparent - } - } - } + background: Rectangle { + color: AmneziaStyle.color.transparent } } } } } } + } + + footer: ColumnLayout { + width: listView.width LabelWithButtonType { id: removeButton @@ -198,11 +186,7 @@ PageType { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeProcessedContainer() } - var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } + var noButtonFunction = function() {} showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 63e60dcb..c3a8d2cc 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -16,164 +16,138 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + enabled: ServersModel.isProcessedServerHasWriteAccess() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: ShadowSocksConfigModel - enabled: ServersModel.isProcessedServerHasWriteAccess() + delegate: ColumnLayout { + width: listView.width - ListView { - id: listview + spacing: 0 - width: parent.width - height: listview.contentItem.height + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - clip: true - interactive: false + headerText: qsTr("Shadowsocks settings") + } - model: ShadowSocksConfigModel + TextFieldWithHeaderType { + id: portTextField - delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - property var focusItemId: portTextField.enabled ? - portTextField : - cipherDropDown.enabled ? - cipherDropDown : - saveRestartButton + enabled: isPortEditable - ColumnLayout { - id: col + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + } - anchors.leftMargin: 16 - anchors.rightMargin: 16 + DropDownType { + id: cipherDropDown - spacing: 0 + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - BaseHeaderType { - Layout.fillWidth: true + enabled: isCipherEditable - headerText: qsTr("Shadowsocks settings") - } + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") - TextFieldWithHeaderType { - id: portTextField + drawerParent: root - Layout.fillWidth: true - Layout.topMargin: 40 + listView: ListViewWithRadioButtonType { - enabled: isPortEditable + id: cipherListView - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } + rootWidth: root.width - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - } + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } - DropDownType { - id: cipherDropDown - Layout.fillWidth: true - Layout.topMargin: 20 + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.closeTriggered() + } - enabled: isCipherEditable + Component.onCompleted: { + cipherDropDown.text = cipher - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - - id: cipherListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : "chacha20-ietf-poly1305" } - ListElement { name : "xchacha20-ietf-poly1305" } - ListElement { name : "aes-256-gcm" } - ListElement { name : "aes-192-gcm" } - ListElement { name : "aes-128-gcm" } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - cipherDropDown.text = cipher - - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipherDropDown.text) { - currentIndex = i - } - } - } - } - } - - BasicButtonType { - id: saveRestartButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - enabled: isPortEditable | isCipherEditable - - text: qsTr("Save") - - clickedFunc: function() { - forceActiveFocus() - - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i } } } } } + + BasicButtonType { + id: saveRestartButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: isPortEditable | isCipherEditable + + text: qsTr("Save") + + clickedFunc: function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + } + } } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index 96ec1dc6..ef2490c7 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -16,160 +16,124 @@ import "../Components" PageType { id: root - Item { - id: focusItem - onFocusChanged: { - if (activeFocus) { - fl.ensureVisible(focusItem) - } - } - KeyNavigation.tab: backButton - } - - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton - KeyNavigation.tab: listview.currentItem.mtuTextField.textField + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + model: WireGuardConfigModel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + delegate: ColumnLayout { + width: listView.width - ListView { - id: listview + property alias mtuTextField: mtuTextField + property bool isSaveButtonEnabled: mtuTextField.errorText === "" - width: parent.width - height: listview.contentItem.height + spacing: 0 - clip: true - interactive: false + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - model: WireGuardConfigModel + headerText: qsTr("WG settings") + } - delegate: Item { - id: delegateItem - implicitWidth: listview.width - implicitHeight: col.implicitHeight + TextFieldWithHeaderType { + id: mtuTextField + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - property alias mtuTextField: mtuTextField - property bool isSaveButtonEnabled: mtuTextField.errorText === "" + headerText: qsTr("MTU") + textField.text: clientMtu + textField.validator: IntValidator { bottom: 576; top: 65535 } - ColumnLayout { - id: col - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - - headerText: qsTr("WG settings") - } - - TextFieldWithHeaderType { - id: mtuTextField - Layout.fillWidth: true - Layout.topMargin: 40 - - headerText: qsTr("MTU") - textField.text: clientMtu - textField.validator: IntValidator { bottom: 576; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== clientMtu) { - clientMtu = textField.text - } - } - checkEmptyText: true - KeyNavigation.tab: saveButton - } - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Server settings") - } - - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 8 - - enabled: false - - headerText: qsTr("Port") - textField.text: port - } - } + textField.onEditingFinished: { + if (textField.text !== clientMtu) { + clientMtu = textField.text + } } + checkEmptyText: true + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Server settings") + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: false + + headerText: qsTr("Port") + textField.text: port } } - } - BasicButtonType { - id: saveButton + footer: ColumnLayout { + width: listView.width - anchors.right: root.right - anchors.left: root.left - anchors.bottom: root.bottom + BasicButtonType { + id: saveButton - anchors.topMargin: 24 - anchors.bottomMargin: 24 - anchors.rightMargin: 16 - anchors.leftMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - enabled: listview.currentItem.isSaveButtonEnabled + enabled: listView.currentItem.isSaveButtonEnabled - text: qsTr("Save") + text: qsTr("Save") - clickedFunc: function() { - forceActiveFocus() - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("Only the settings for this device will be changed") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") + clickedFunc: function() { + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("Only the settings for this device will be changed") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(WireGuardConfigModel.getConfig()) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveButton.forceActiveFocus() + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(WireGuardConfigModel.getConfig()) + } + var noButtonFunction = function() {} + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 7b5180f3..fdd2ea61 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -16,153 +16,134 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + enabled: ServersModel.isProcessedServerHasWriteAccess() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: WireGuardConfigModel - enabled: ServersModel.isProcessedServerHasWriteAccess() + delegate: ColumnLayout { + width: listView.width - ListView { - id: listview + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() - width: parent.width - height: listview.contentItem.height + spacing: 0 - clip: true - interactive: false + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - model: WireGuardConfigModel + headerText: qsTr("WG settings") + } - delegate: Item { - id: delegateItem + TextFieldWithHeaderType { + id: vpnAddressSubnetTextField - property alias focusItemId: vpnAddressSubnetTextField - property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - implicitWidth: listview.width - implicitHeight: col.implicitHeight + enabled: delegateItem.isEnabled - ColumnLayout { - id: col + headerText: qsTr("VPN address subnet") + textField.text: subnetAddress - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - headerText: qsTr("WG settings") - } - - TextFieldWithHeaderType { - id: vpnAddressSubnetTextField - Layout.fillWidth: true - Layout.topMargin: 40 - - enabled: delegateItem.isEnabled - - headerText: qsTr("VPN address subnet") - textField.text: subnetAddress - - textField.onEditingFinished: { - if (textField.text !== subnetAddress) { - subnetAddress = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: delegateItem.isEnabled - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - - checkEmptyText: true - } - - BasicButtonType { - id: saveButton - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - enabled: portTextField.errorText === "" && - vpnAddressSubnetTextField.errorText === "" - - text: qsTr("Save") - - onClicked: function() { - forceActiveFocus() - - var headerText = qsTr("Save settings?") - var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(WireGuardConfigModel.getConfig()) - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - saveRestartButton.forceActiveFocus() - } - } - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - - Keys.onEnterPressed: saveButton.clicked() - Keys.onReturnPressed: saveButton.clicked() - } + textField.onEditingFinished: { + if (textField.text !== subnetAddress) { + subnetAddress = textField.text } } + + checkEmptyText: true + } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: delegateItem.isEnabled + + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + + checkEmptyText: true + } + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: portTextField.errorText === "" && + vpnAddressSubnetTextField.errorText === "" + + text: qsTr("Save") + + onClicked: function() { + forceActiveFocus() + + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(WireGuardConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveRestartButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index d22e31a2..a7174ccb 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -17,141 +17,112 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.left: parent.left + anchors.right: parent.right - Column { - id: content + enabled: ServersModel.isProcessedServerHasWriteAccess() + model: XrayConfigModel - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + delegate: ColumnLayout { - enabled: ServersModel.isProcessedServerHasWriteAccess() + width: listView.width - ListView { - id: listview + property alias focusItemId: textFieldWithHeaderType.textField - width: parent.width - height: listview.contentItem.height + spacing: 0 - clip: true - interactive: false + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + headerText: qsTr("XRay settings") + } - model: XrayConfigModel + TextFieldWithHeaderType { + id: textFieldWithHeaderType - delegate: Item { - property alias focusItemId: textFieldWithHeaderType.textField + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - implicitWidth: listview.width - implicitHeight: col.implicitHeight + headerText: qsTr("Disguised as traffic from") + textField.text: site - ColumnLayout { - id: col + textField.onEditingFinished: { + if (textField.text !== site) { + var tmpText = textField.text + tmpText = tmpText.toLocaleLowerCase() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - headerText: qsTr("XRay settings") - } - - TextFieldWithHeaderType { - id: textFieldWithHeaderType - Layout.fillWidth: true - Layout.topMargin: 32 - - headerText: qsTr("Disguised as traffic from") - textField.text: site - - textField.onEditingFinished: { - if (textField.text !== site) { - var tmpText = textField.text - tmpText = tmpText.toLocaleLowerCase() - - if (tmpText.startsWith("https://")) { - tmpText = textField.text.substring(8) - site = tmpText - } else { - site = textField.text - } - } - } - } - - TextFieldWithHeaderType { - id: portTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - enabled: delegateItem.isEnabled - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - - checkEmptyText: true - } - - BasicButtonType { - id: saveButton - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("Save") - - onClicked: { - forceActiveFocus() - - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(XrayConfigModel.getConfig()) - focusItem.forceActiveFocus() - } - - Keys.onEnterPressed: basicButton.clicked() - Keys.onReturnPressed: basicButton.clicked() + if (tmpText.startsWith("https://")) { + tmpText = textField.text.substring(8) + site = tmpText + } else { + site = textField.text } } } } + + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: delegateItem.isEnabled + + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + + checkEmptyText: true + } + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Save") + + onClicked: { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(XrayConfigModel.getConfig()) + } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() + } } } - } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index d534f991..32fa0e49 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -16,50 +16,47 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + header: ColumnLayout { + width: listView.width BaseHeaderType { - id: header - Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.bottomMargin: 24 headerText: "AmneziaDNS" descriptionText: qsTr("A DNS service is installed on your server, and it is only accessible via VPN.\n") + qsTr("The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: listView.width LabelWithButtonType { - id: removeButton - - Layout.topMargin: 24 - width: parent.width + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() textColor: AmneziaStyle.color.vibrantRed @@ -71,19 +68,14 @@ PageType { var yesButtonFunction = function() { if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected - && SettingsController.isAmneziaDnsEnabled()) { + && SettingsController.isAmneziaDnsEnabled()) { PageController.showNotificationMessage(qsTr("Cannot remove AmneziaDNS from running server")) - } else - { + } else { PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeProcessedContainer() } } - var noButtonFunction = function() { - if (!GC.isMobile()) { - removeButton.rightButton.forceActiveFocus() - } - } + var noButtonFunction = function() {} showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index b58cb2e0..9fe0bac5 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -24,258 +24,215 @@ PageType { } } - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - Column { - id: content + enabled: ServersModel.isProcessedServerHasWriteAccess() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: SftpConfigModel - enabled: ServersModel.isProcessedServerHasWriteAccess() + delegate: ColumnLayout { + width: listView.width - ListView { - id: listview + spacing: 0 - width: parent.width - height: listview.contentItem.height + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - clip: true - interactive: false + headerText: qsTr("SFTP settings") + } - model: SftpConfigModel + LabelWithButtonType { + id: hostLabel - onFocusChanged: { - if (focus) { - listview.currentItem.listViewFocusItem.forceActiveFocus() - } + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Host") + descriptionText: ServersModel.getProcessedServerData("hostName") + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + id: portLabel + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Port") + descriptionText: port + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + id: usernameLabel + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("User name") + descriptionText: username + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + id: passwordLabel + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Password") + descriptionText: password + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + buttonImageSource: hideDescription ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + BasicButtonType { + id: mountButton + + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.paleGray + borderWidth: 1 + + text: qsTr("Mount folder on device") + + clickedFunc: function() { + PageController.showBusyIndicator(true) + InstallController.mountSftpDrive(port, password, username) + PageController.showBusyIndicator(false) + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + readonly property string windowsFirstLink: "WinFsp" + readonly property string windowsSecondLink: "SSHFS-Win" + + readonly property string macosFirstLink: "macFUSE" + readonly property string macosSecondLink: "SSHFS" + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + textFormat: Text.RichText + text: { + var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") + if (Qt.platform.os === "windows") { + str += qsTr("
1. Install the latest version of ") + windowsFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + windowsSecondLink + "\n" + } else if (Qt.platform.os === "osx") { + str += qsTr("
1. Install the latest version of ") + macosFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + macosSecondLink + "\n" + } else if (Qt.platform.os === "linux") { + return "" + } else return "" + + return str } - delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } - property alias listViewFocusItem: hostLabel.rightButton + BasicButtonType { + id: detailedInstructionsButton - ColumnLayout { - id: col + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.goldenApricot - spacing: 0 + text: qsTr("Detailed instructions") - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("SFTP settings") - } - - LabelWithButtonType { - id: hostLabel - Layout.fillWidth: true - Layout.topMargin: 32 - - parentFlickable: fl - - text: qsTr("Host") - descriptionText: ServersModel.getProcessedServerData("hostName") - - descriptionOnTop: true - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } - } - } - - LabelWithButtonType { - id: portLabel - Layout.fillWidth: true - - text: qsTr("Port") - descriptionText: port - - descriptionOnTop: true - - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } - } - } - - LabelWithButtonType { - id: usernameLabel - Layout.fillWidth: true - - text: qsTr("User name") - descriptionText: username - - descriptionOnTop: true - - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } - } - } - - LabelWithButtonType { - id: passwordLabel - Layout.fillWidth: true - - text: qsTr("Password") - descriptionText: password - - descriptionOnTop: true - - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - buttonImageSource: hideDescription ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } - } - } - - BasicButtonType { - id: mountButton - visible: !GC.isMobile() - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.paleGray - borderWidth: 1 - - parentFlickable: fl - - text: qsTr("Mount folder on device") - - clickedFunc: function() { - PageController.showBusyIndicator(true) - InstallController.mountSftpDrive(port, password, username) - PageController.showBusyIndicator(false) - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - readonly property string windowsFirstLink: "WinFsp" - readonly property string windowsSecondLink: "SSHFS-Win" - - readonly property string macosFirstLink: "macFUSE" - readonly property string macosSecondLink: "SSHFS" - - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } - textFormat: Text.RichText - text: { - var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") - if (Qt.platform.os === "windows") { - str += qsTr("
1. Install the latest version of ") + windowsFirstLink + "\n" - str += qsTr("
2. Install the latest version of ") + windowsSecondLink + "\n" - } else if (Qt.platform.os === "osx") { - str += qsTr("
1. Install the latest version of ") + macosFirstLink + "\n" - str += qsTr("
2. Install the latest version of ") + macosSecondLink + "\n" - } else if (Qt.platform.os === "linux") { - return "" - } else return "" - - return str - } - - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - - BasicButtonType { - id: detailedInstructionsButton - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.leftMargin: 8 - implicitHeight: 32 - - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.goldenApricot - - text: qsTr("Detailed instructions") - - parentFlickable: fl - - clickedFunc: function() { -// Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") - } - } - } + clickedFunc: function() { + // Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") } } } diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index b1daa0fb..f96872e0 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -25,327 +25,290 @@ PageType { } } - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - BackButtonType { - id: backButton + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: listview.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - ListView { - id: listview + model: Socks5ProxyConfigModel - width: parent.width - height: listview.contentItem.height + delegate: ColumnLayout { + width: listView.width - clip: true - interactive: false + spacing: 0 - model: Socks5ProxyConfigModel + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - onFocusChanged: { - if (focus) { - listview.currentItem.focusItemId.forceActiveFocus() + headerText: qsTr("SOCKS5 settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + text: qsTr("Host") + descriptionText: ServersModel.getProcessedServerData("hostName") + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } - delegate: Item { - implicitWidth: listview.width - implicitHeight: content.implicitHeight + LabelWithButtonType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - property alias focusItemId: hostLabel.rightButton + text: qsTr("Port") + descriptionText: port - ColumnLayout { - id: content + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + text: qsTr("User name") + descriptionText: username + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.bottomMargin: 16 + + text: qsTr("Password") + descriptionText: password + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray + + buttonImageSource: hideDescription ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + } + } + + DrawerType2 { + id: changeSettingsDrawer + parent: root + + anchors.fill: parent + expandedHeight: root.height * 0.9 + + expandedStateContent: ColumnLayout { + property string tempPort: port + property string tempUsername: username + property string tempPassword: password anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - + anchors.topMargin: 32 + anchors.leftMargin: 16 + anchors.rightMargin: 16 spacing: 0 + Connections { + target: changeSettingsDrawer + function onOpened() { + tempPort = port + tempUsername = username + tempPassword = password + } + function onClosed() { + port = tempPort + username = tempUsername + password = tempPassword + portTextField.textField.text = port + usernameTextField.textField.text = username + passwordTextField.textField.text = password + } + } + BaseHeaderType { Layout.fillWidth: true - Layout.leftMargin: 16 Layout.rightMargin: 16 + Layout.bottomMargin: 16 headerText: qsTr("SOCKS5 settings") } - LabelWithButtonType { - id: hostLabel + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 40 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - parentFlickable: fl + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } - text: qsTr("Host") - descriptionText: ServersModel.getProcessedServerData("hostName") - - descriptionOnTop: true - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() + textField.onEditingFinished: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== port) { + port = textField.text } } } - LabelWithButtonType { - id: portLabel + TextFieldWithHeaderType { + id: usernameTextField + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - text: qsTr("Port") - descriptionText: port + headerText: qsTr("Username") + textField.placeholderText: "username" + textField.text: username + textField.maximumLength: 32 - descriptionOnTop: true - - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() + textField.onEditingFinished: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== username) { + username = textField.text } } } - LabelWithButtonType { - id: usernameLabel + TextFieldWithHeaderType { + id: passwordTextField + + property bool hidePassword: true + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 16 - text: qsTr("User name") - descriptionText: username + headerText: qsTr("Password") + textField.placeholderText: "password" + textField.text: password + textField.maximumLength: 32 - descriptionOnTop: true + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: textField.text !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") + : "" - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } + clickedFunc: function() { + hidePassword = !hidePassword } - } - LabelWithButtonType { - id: passwordLabel - Layout.fillWidth: true - - text: qsTr("Password") - descriptionText: password - - descriptionOnTop: true - - parentFlickable: fl - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - buttonImageSource: hideDescription ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } - } - } - - DrawerType2 { - id: changeSettingsDrawer - parent: root - - anchors.fill: parent - expandedHeight: root.height * 0.9 - - expandedStateContent: ColumnLayout { - property string tempPort: port - property string tempUsername: username - property string tempPassword: password - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 32 - anchors.leftMargin: 16 - anchors.rightMargin: 16 - spacing: 0 - - Connections { - target: changeSettingsDrawer - function onOpened() { - tempPort = port - tempUsername = username - tempPassword = password - } - function onClosed() { - port = tempPort - username = tempUsername - password = tempPassword - portTextField.textField.text = port - usernameTextField.textField.text = username - passwordTextField.textField.text = password - } - } - - BaseHeaderType { - Layout.fillWidth: true - - headerText: qsTr("SOCKS5 settings") - } - - TextFieldWithHeaderType { - id: portTextField - - Layout.fillWidth: true - Layout.topMargin: 40 - parentFlickable: fl - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - if (textField.text !== port) { - port = textField.text - } - } - } - - TextFieldWithHeaderType { - id: usernameTextField - - Layout.fillWidth: true - Layout.topMargin: 16 - parentFlickable: fl - - headerText: qsTr("Username") - textField.placeholderText: "username" - textField.text: username - textField.maximumLength: 32 - - textField.onEditingFinished: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - if (textField.text !== username) { - username = textField.text - } - } - } - - TextFieldWithHeaderType { - id: passwordTextField - - property bool hidePassword: true - - Layout.fillWidth: true - Layout.topMargin: 16 - parentFlickable: fl - - headerText: qsTr("Password") - textField.placeholderText: "password" - textField.text: password - textField.maximumLength: 32 - - textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal - buttonImageSource: textField.text !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") - : "" - - clickedFunc: function() { - hidePassword = !hidePassword - } - - textField.onFocusChanged: { - textField.text = textField.text.replace(/^\s+|\s+$/g, '') - if (textField.text !== password) { - password = textField.text - } - } - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("Change connection settings") - - clickedFunc: function() { - forceActiveFocus() - - if (!portTextField.textField.acceptableInput) { - portTextField.errorText = qsTr("The port must be in the range of 1 to 65535") - return - } - if (usernameTextField.textField.text && passwordTextField.textField.text === "") { - passwordTextField.errorText = qsTr("Password cannot be empty") - return - } else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) { - usernameTextField.errorText = qsTr("Username cannot be empty") - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling) - InstallController.updateContainer(Socks5ProxyConfigModel.getConfig()) - tempPort = portTextField.textField.text - tempUsername = usernameTextField.textField.text - tempPassword = passwordTextField.textField.text - changeSettingsDrawer.closeTriggered() - } + textField.onFocusChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + if (textField.text !== password) { + password = textField.text } } } BasicButtonType { - id: changeSettingsButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 - Layout.leftMargin: 16 Layout.rightMargin: 16 text: qsTr("Change connection settings") clickedFunc: function() { - forceActiveFocus() - changeSettingsDrawer.openTriggered() + if (!portTextField.textField.acceptableInput) { + portTextField.errorText = qsTr("The port must be in the range of 1 to 65535") + return + } + if (usernameTextField.textField.text && passwordTextField.textField.text === "") { + passwordTextField.errorText = qsTr("Password cannot be empty") + return + } else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) { + usernameTextField.errorText = qsTr("Username cannot be empty") + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.updateContainer(Socks5ProxyConfigModel.getConfig()) + tempPort = portTextField.textField.text + tempUsername = usernameTextField.textField.text + tempPassword = passwordTextField.textField.text + changeSettingsDrawer.closeTriggered() } } } } + + BasicButtonType { + id: changeSettingsButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Change connection settings") + + clickedFunc: function() { + changeSettingsDrawer.openTriggered() + } + } } } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 200beeb8..73476a23 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -25,34 +25,25 @@ PageType { } } - ColumnLayout { - id: backButtonLayout + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } } - FlickableType { - id: fl - anchors.top: backButtonLayout.bottom + ListViewType { + id: listView + + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 0 + header: ColumnLayout { + width: listView.width BaseHeaderType { Layout.fillWidth: true @@ -61,11 +52,19 @@ PageType { headerText: qsTr("Tor website settings") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: listView.width LabelWithButtonType { id: websiteName + Layout.fillWidth: true Layout.topMargin: 32 + Layout.bottomMargin: 24 text: qsTr("Website address") descriptionText: { @@ -83,15 +82,16 @@ PageType { clickedFunction: function() { GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() - } } } + } + + footer: ColumnLayout { + width: listView.width ParagraphTextType { Layout.fillWidth: true - Layout.topMargin: 40 + Layout.topMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index bb83ec92..f331f912 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -1,156 +1,167 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" - -PageType { - id: root - - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height - - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 0 - - BaseHeaderType { - id: header - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Settings") - } - - LabelWithButtonType { - id: account - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Servers") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/server.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsServersList) - } - } - - DividerType {} - - LabelWithButtonType { - id: connection - Layout.fillWidth: true - - text: qsTr("Connection") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/radio.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsConnection) - } - } - - DividerType {} - - LabelWithButtonType { - id: application - Layout.fillWidth: true - - text: qsTr("Application") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/app.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsApplication) - } - } - - DividerType {} - - LabelWithButtonType { - id: backup - Layout.fillWidth: true - - text: qsTr("Backup") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/save.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsBackup) - } - } - - DividerType {} - - LabelWithButtonType { - id: about - Layout.fillWidth: true - - text: qsTr("About AmneziaVPN") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/amnezia.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageSettingsAbout) - } - } - - DividerType {} - - LabelWithButtonType { - id: devConsole - visible: SettingsController.isDevModeEnabled - Layout.fillWidth: true - - text: qsTr("Dev console") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - leftImageSource: "qrc:/images/controls/bug.svg" - - clickedFunction: function() { - PageController.goToPage(PageEnum.PageDevMenu) - } - } - - DividerType { - visible: SettingsController.isDevModeEnabled - } - - LabelWithButtonType { - id: close - visible: GC.isDesktop() - Layout.fillWidth: true - Layout.preferredHeight: about.height - - text: qsTr("Close application") - leftImageSource: "qrc:/images/controls/x-circle.svg" - isLeftImageHoverEnabled: false - - clickedFunction: function() { - PageController.closeApplication() - } - } - - DividerType { - visible: GC.isDesktop() - } - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + ListViewType { + id: listView + + anchors.fill: parent + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + id: header + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Settings") + } + } + + model: settingsEntries + + delegate: ColumnLayout { + width: listView.width + + spacing: 0 + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: isVisible + + text: title + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: leftImagePath + + clickedFunction: clickedHandler + } + + DividerType { + visible: isVisible + } + } + + footer: ColumnLayout { + width: listView.width + + LabelWithButtonType { + id: close + + visible: GC.isDesktop() + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Close application") + leftImageSource: "qrc:/images/controls/x-circle.svg" + isLeftImageHoverEnabled: false + + clickedFunction: function() { + PageController.closeApplication() + } + } + + DividerType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: GC.isDesktop() + } + } + } + + property list settingsEntries: [ + servers, + connection, + application, + backup, + about, + devConsole + ] + + QtObject { + id: servers + + property string title: qsTr("Servers") + readonly property string leftImagePath: "qrc:/images/controls/server.svg" + property bool isVisible: true + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageSettingsServersList) + } + } + + QtObject { + id: connection + + property string title: qsTr("Connection") + readonly property string leftImagePath: "qrc:/images/controls/radio.svg" + property bool isVisible: true + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageSettingsConnection) + } + } + + QtObject { + id: application + + property string title: qsTr("Application") + readonly property string leftImagePath: "qrc:/images/controls/app.svg" + property bool isVisible: true + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageSettingsApplication) + } + } + + QtObject { + id: backup + + property string title: qsTr("Backup") + readonly property string leftImagePath: "qrc:/images/controls/save.svg" + property bool isVisible: true + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageSettingsBackup) + } + } + + QtObject { + id: about + + property string title: qsTr("About AmneziaVPN") + readonly property string leftImagePath: "qrc:/images/controls/amnezia.svg" + property bool isVisible: true + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageSettingsAbout) + } + } + + QtObject { + id: devConsole + + property string title: qsTr("Dev console") + readonly property string leftImagePath: "qrc:/images/controls/bug.svg" + property bool isVisible: SettingsController.isDevModeEnabled + readonly property var clickedHandler: function() { + PageController.goToPage(PageEnum.PageDevMenu) + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 6342ce66..e0c3c1d3 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -29,58 +29,7 @@ PageType { } } - QtObject { - id: telegramGroup - - readonly property string title: qsTr("Telegram group") - readonly property string description: qsTr("To discuss features") - readonly property string imageSource: "qrc:/images/controls/telegram.svg" - readonly property var handler: function() { - Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) - } - } - - QtObject { - id: mail - - readonly property string title: qsTr("support@amnezia.org") - readonly property string description: qsTr("For reviews and bug reports") - readonly property string imageSource: "qrc:/images/controls/mail.svg" - readonly property var handler: function() { - Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) - } - } - - QtObject { - id: github - - readonly property string title: qsTr("GitHub") - readonly property string description: qsTr("Discover the source code") - readonly property string imageSource: "qrc:/images/controls/github.svg" - readonly property var handler: function() { - Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) - } - } - - QtObject { - id: website - - readonly property string title: qsTr("Website") - readonly property string description: qsTr("Visit official website") - readonly property string imageSource: "qrc:/images/controls/amnezia.svg" - readonly property var handler: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - } - - property list contacts: [ - telegramGroup, - mail, - github, - website - ] - - ListView { + ListViewType { id: listView anchors.top: backButton.bottom @@ -88,38 +37,6 @@ PageType { anchors.right: parent.right anchors.left: parent.left - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - ScrollBar.vertical: ScrollBarType {} - - model: contacts - - clip: true - header: ColumnLayout { width: listView.width @@ -170,6 +87,8 @@ PageType { } } + model: contacts + delegate: ColumnLayout { width: listView.width @@ -257,4 +176,55 @@ PageType { } } } + + QtObject { + id: telegramGroup + + readonly property string title: qsTr("Telegram group") + readonly property string description: qsTr("To discuss features") + readonly property string imageSource: "qrc:/images/controls/telegram.svg" + readonly property var handler: function() { + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) + } + } + + QtObject { + id: mail + + readonly property string title: qsTr("support@amnezia.org") + readonly property string description: qsTr("For reviews and bug reports") + readonly property string imageSource: "qrc:/images/controls/mail.svg" + readonly property var handler: function() { + Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) + } + } + + QtObject { + id: github + + readonly property string title: qsTr("GitHub") + readonly property string description: qsTr("Discover the source code") + readonly property string imageSource: "qrc:/images/controls/github.svg" + readonly property var handler: function() { + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) + } + } + + QtObject { + id: website + + readonly property string title: qsTr("Website") + readonly property string description: qsTr("Visit official website") + readonly property string imageSource: "qrc:/images/controls/amnezia.svg" + readonly property var handler: function() { + Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) + } + } + + property list contacts: [ + telegramGroup, + mail, + github, + website + ] } diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index d2042a76..6e749e95 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -1,220 +1,236 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import QtCore - -import SortFilterProxyModel 0.2 - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - property string configExtension: ".conf" - property string configCaption: qsTr("Save AmneziaVPN config") - - ListViewType { - id: listView - - anchors.fill: parent - anchors.topMargin: 20 - anchors.bottomMargin: 24 - - model: ApiCountryModel - - header: ColumnLayout { - width: listView.width - - BackButtonType { - id: backButton - } - - BaseHeaderType { - id: header - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - headerText: qsTr("Configuration Files") - descriptionText: qsTr("For router setup or the AmneziaWG app") - } - } - - delegate: ColumnLayout { - width: listView.width - - LabelWithButtonType { - Layout.fillWidth: true - Layout.topMargin: 6 - - text: countryName - descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" - descriptionColor: AmneziaStyle.color.vibrantRed - - leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" - rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" - - clickedFunction: function() { - if (isIssued) { - moreOptionsDrawer.countryName = countryName - moreOptionsDrawer.countryCode = countryCode - moreOptionsDrawer.openTriggered() - } else { - issueConfig(countryCode) - } - } - } - - DividerType {} - } - } - - DrawerType2 { - id: moreOptionsDrawer - - property string countryName - property string countryCode - - anchors.fill: parent - expandedHeight: parent.height * 0.4375 - - expandedStateContent: Item { - implicitHeight: moreOptionsDrawer.expandedHeight - - BackButtonType { - id: moreOptionsDrawerBackButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 - - backButtonFunction: function() { - moreOptionsDrawer.closeTriggered() - } - } - - FlickableType { - anchors.top: moreOptionsDrawerBackButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - - contentHeight: moreOptionsDrawerContent.height - - ColumnLayout { - id: moreOptionsDrawerContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Header2Type { - Layout.fillWidth: true - Layout.margins: 16 - - headerText: moreOptionsDrawer.countryName + qsTr(" configuration file") - } - - LabelWithButtonType { - Layout.fillWidth: true - - text: qsTr("Generate a new configuration file") - descriptionText: qsTr("The previously created one will stop working") - - clickedFunction: function() { - showQuestion(true, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) - } - } - - DividerType {} - - LabelWithButtonType { - Layout.fillWidth: true - text: qsTr("Revoke the current configuration file") - - clickedFunction: function() { - showQuestion(false, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) - } - } - - DividerType {} - } - } - } - } - - function issueConfig(countryCode) { - var fileName = "" - if (GC.isMobile()) { - fileName = countryCode + configExtension - } else { - fileName = SystemController.getFileName(configCaption, - qsTr("Config files (*" + configExtension + ")"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, - true, - configExtension) - } - if (fileName !== "") { - PageController.showBusyIndicator(true) - let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) - if (result) { - ApiSettingsController.getAccountInfo(true) - } - - PageController.showBusyIndicator(false) - if (result) { - PageController.showNotificationMessage(qsTr("Config file saved")) - } - } - } - - function revokeConfig(countryCode) { - PageController.showBusyIndicator(true) - let result = ApiConfigsController.revokeNativeConfig(countryCode) - if (result) { - ApiSettingsController.getAccountInfo(true) - } - PageController.showBusyIndicator(false) - - if (result) { - PageController.showNotificationMessage(qsTr("The config has been revoked")) - } - } - - function showQuestion(isConfigIssue, countryCode, countryName) { - var headerText - if (isConfigIssue) { - headerText = qsTr("Generate a new %1 configuration file?").arg(countryName) - } else { - headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName) - } - - var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it") - var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (isConfigIssue) { - issueConfig(countryCode) - } else { - revokeConfig(countryCode) - } - moreOptionsDrawer.closeTriggered() - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property string configExtension: ".conf" + property string configCaption: qsTr("Save AmneziaVPN config") + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + model: ApiCountryModel + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Configuration Files") + descriptionText: qsTr("For router setup or the AmneziaWG app") + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: countryName + descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" + descriptionColor: AmneziaStyle.color.vibrantRed + + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" + rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" + + clickedFunction: function() { + if (isIssued) { + moreOptionsDrawer.countryName = countryName + moreOptionsDrawer.countryCode = countryCode + moreOptionsDrawer.openTriggered() + } else { + issueConfig(countryCode) + } + } + } + + DividerType {} + } + } + + DrawerType2 { + id: moreOptionsDrawer + + property string countryName + property string countryCode + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: Item { + implicitHeight: moreOptionsDrawer.expandedHeight + + BackButtonType { + id: moreOptionsDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + moreOptionsDrawer.closeTriggered() + } + } + + ListViewType { + id: drawerListView + + anchors.top: moreOptionsDrawerBackButton.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + header: ColumnLayout { + width: drawerListView.width + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: moreOptionsDrawer.countryName + qsTr(" configuration file") + } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: drawerListView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Generate a new configuration file") + descriptionText: qsTr("The previously created one will stop working") + + clickedFunction: function() { + showQuestion(true, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) + } + } + + DividerType {} + } + + footer: ColumnLayout { + width: drawerListView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Revoke the current configuration file") + + clickedFunction: function() { + showQuestion(false, moreOptionsDrawer.countryCode, moreOptionsDrawer.countryName) + } + } + + DividerType {} + } + } + } + } + + function issueConfig(countryCode) { + var fileName = "" + if (GC.isMobile()) { + fileName = countryCode + configExtension + } else { + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + countryCode, + true, + configExtension) + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + let result = ApiConfigsController.exportNativeConfig(countryCode, fileName) + if (result) { + ApiSettingsController.getAccountInfo(true) + } + + PageController.showBusyIndicator(false) + if (result) { + PageController.showNotificationMessage(qsTr("Config file saved")) + } + } + } + + function revokeConfig(countryCode) { + PageController.showBusyIndicator(true) + let result = ApiConfigsController.revokeNativeConfig(countryCode) + if (result) { + ApiSettingsController.getAccountInfo(true) + } + PageController.showBusyIndicator(false) + + if (result) { + PageController.showNotificationMessage(qsTr("The config has been revoked")) + } + } + + function showQuestion(isConfigIssue, countryCode, countryName) { + var headerText + if (isConfigIssue) { + headerText = qsTr("Generate a new %1 configuration file?").arg(countryName) + } else { + headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName) + } + + var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it") + var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (isConfigIssue) { + issueConfig(countryCode) + } else { + revokeConfig(countryCode) + } + moreOptionsDrawer.closeTriggered() + } + var noButtonFunction = function() {} + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } +} diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index e31c92db..30b2b59e 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -50,6 +50,7 @@ PageType { readonly property string name: qsTr("Only the apps from the list should have access via VPN") readonly property int type: routeMode.onlyForwardApps } + QtObject { id: allExceptApps @@ -146,77 +147,56 @@ PageType { } } - FlickableType { + ListViewType { + id: listView + anchors.top: header.bottom - anchors.topMargin: 16 - contentHeight: col.implicitHeight + addAppButton.implicitHeight + addAppButton.anchors.bottomMargin + addAppButton.anchors.topMargin + anchors.bottom: addAppButton.top + anchors.left: parent.left + anchors.right: parent.right - enabled: root.pageEnabled + model: SortFilterProxyModel { + id: proxyAppSplitTunnelingModel + sourceModel: AppSplitTunnelingModel + filters: RegExpFilter { + roleName: "appPath" + pattern: ".*" + searchField.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + sorters: [ + RoleSorter { roleName: "appPath"; sortOrder: Qt.AscendingOrder } + ] + } - Column { - id: col - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + delegate: ColumnLayout { + width: listView.width - ListView { - id: apps - width: parent.width - height: apps.contentItem.height + LabelWithButtonType { + Layout.fillWidth: true - model: SortFilterProxyModel { - id: proxyAppSplitTunnelingModel - sourceModel: AppSplitTunnelingModel - filters: RegExpFilter { - roleName: "appPath" - pattern: ".*" + searchField.textField.text + ".*" - caseSensitivity: Qt.CaseInsensitive + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: appPath + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + var headerText = qsTr("Remove ") + appPath + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + AppSplitTunnelingController.removeApp(proxyAppSplitTunnelingModel.mapToSource(index)) } - sorters: [ - RoleSorter { roleName: "appPath"; sortOrder: Qt.AscendingOrder } - ] - } - - 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: AmneziaStyle.color.paleGray - - 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 {} + var noButtonFunction = function() { } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } + + DividerType {} } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index cbc04075..59d7c3af 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -23,20 +23,16 @@ PageType { anchors.topMargin: 20 } - FlickableType { - id: fl + ListViewType { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.height + anchors.left: parent.left + anchors.right: parent.right - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 0 + header: ColumnLayout { + width: listView.width BaseHeaderType { Layout.fillWidth: true @@ -45,9 +41,17 @@ PageType { headerText: qsTr("Application") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + + width: listView.width SwitcherType { - id: switcher + id: switcherAllowScreenshots + visible: GC.isMobile() Layout.fillWidth: true @@ -61,10 +65,6 @@ PageType { SettingsController.toggleScreenshotsEnabled(checked) } } - - // KeyNavigation.tab: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted ? - // labelWithButtonNotification.rightButton : labelWithButtonLanguage.rightButton - parentFlickable: fl } DividerType { @@ -73,15 +73,15 @@ PageType { LabelWithButtonType { id: labelWithButtonNotification + visible: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted + Layout.fillWidth: true text: qsTr("Enable notifications") descriptionText: qsTr("Enable notifications to show the VPN state in the status bar") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { SettingsController.requestNotificationPermission() } @@ -93,6 +93,7 @@ PageType { SwitcherType { id: switcherAutoStart + visible: !GC.isMobile() Layout.fillWidth: true @@ -101,8 +102,6 @@ PageType { text: qsTr("Auto start") descriptionText: qsTr("Launch the application every time the device is starts") - parentFlickable: fl - checked: SettingsController.isAutoStartEnabled() onCheckedChanged: { if (checked !== SettingsController.isAutoStartEnabled()) { @@ -117,6 +116,7 @@ PageType { SwitcherType { id: switcherAutoConnect + visible: !GC.isMobile() Layout.fillWidth: true @@ -125,8 +125,6 @@ PageType { text: qsTr("Auto connect") descriptionText: qsTr("Connect to VPN on app start") - parentFlickable: fl - checked: SettingsController.isAutoConnectEnabled() onCheckedChanged: { if (checked !== SettingsController.isAutoConnectEnabled()) { @@ -141,6 +139,7 @@ PageType { SwitcherType { id: switcherStartMinimized + visible: !GC.isMobile() Layout.fillWidth: true @@ -149,8 +148,6 @@ PageType { text: qsTr("Start minimized") descriptionText: qsTr("Launch application minimized") - parentFlickable: fl - checked: SettingsController.isStartMinimizedEnabled() onCheckedChanged: { if (checked !== SettingsController.isStartMinimizedEnabled()) { @@ -162,17 +159,21 @@ PageType { DividerType { visible: !GC.isMobile() } + } + + footer: ColumnLayout { + + width: listView.width LabelWithButtonType { id: labelWithButtonLanguage + Layout.fillWidth: true text: qsTr("Language") descriptionText: LanguageModel.currentLanguageName rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { selectLanguageDrawer.openTriggered() } @@ -182,14 +183,13 @@ PageType { LabelWithButtonType { id: labelWithButtonLogging + Layout.fillWidth: true text: qsTr("Logging") descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsLogging) } @@ -199,14 +199,13 @@ PageType { LabelWithButtonType { id: labelWithButtonReset + Layout.fillWidth: true text: qsTr("Reset settings and remove all data from the application") rightImageSource: "qrc:/images/controls/chevron-right.svg" textColor: AmneziaStyle.color.vibrantRed - parentFlickable: fl - clickedFunction: function() { var headerText = qsTr("Reset settings and remove all data from the application?") var descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 83e0f567..475503b7 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -43,49 +43,60 @@ PageType { anchors.topMargin: 20 } - FlickableType { - id: fl + ListViewType { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.height + anchors.left: parent.left + anchors.right: parent.right - ColumnLayout { - id: content + header: ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 + width: listView.width spacing: 16 BaseHeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: qsTr("Back up your configuration") descriptionText: qsTr("You can save your settings to a backup file to restore them the next time you install the application.") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + + width: listView.width + + spacing: 16 WarningType { - Layout.topMargin: 16 Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 textString: qsTr("The backup will contain your passwords and private keys for all servers added " + - "to AmneziaVPN. Keep this information in a secure place.") + "to AmneziaVPN. Keep this information in a secure place.") iconPath: "qrc:/images/controls/alert-circle.svg" } BasicButtonType { id: makeBackupButton + Layout.fillWidth: true Layout.topMargin: 14 + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Make a backup") - parentFlickable: fl - clickedFunc: function() { var fileName = "" if (GC.isMobile()) { @@ -108,8 +119,11 @@ PageType { BasicButtonType { id: restoreBackupButton + Layout.fillWidth: true Layout.topMargin: -8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -120,8 +134,6 @@ PageType { text: qsTr("Restore from backup") - parentFlickable: fl - clickedFunc: function() { var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 84b98230..edb0d2d4 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -23,18 +23,17 @@ PageType { anchors.topMargin: 20 } - FlickableType { - id: fl + ListViewType { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.height + anchors.left: parent.left + anchors.right: parent.right - ColumnLayout { - id: content + header: ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + width: listView.width BaseHeaderType { Layout.fillWidth: true @@ -43,9 +42,17 @@ PageType { headerText: qsTr("Connection") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + + width: listView.width SwitcherType { id: amneziaDnsSwitch + Layout.fillWidth: true Layout.margins: 16 @@ -64,14 +71,13 @@ PageType { LabelWithButtonType { id: dnsServersButton + Layout.fillWidth: true text: qsTr("DNS servers") descriptionText: qsTr("When AmneziaDNS is not used or installed") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsDns) } @@ -81,14 +87,13 @@ PageType { LabelWithButtonType { id: splitTunnelingButton + Layout.fillWidth: true text: qsTr("Site-based split tunneling") descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsSplitTunneling) } @@ -96,8 +101,15 @@ PageType { DividerType {} + } + + footer: ColumnLayout { + + width: listView.width + LabelWithButtonType { id: splitTunnelingButton2 + visible: root.isAppSplitTinnelingEnabled Layout.fillWidth: true @@ -106,8 +118,6 @@ PageType { descriptionText: qsTr("Allows you to use the VPN only for certain Apps") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index d5e2c52b..a510f928 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -1,140 +1,167 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Config" -import "../Controls2/TextTypes" -import "../Components" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 - } - - FlickableType { - id: fl - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - contentHeight: content.height - - property var isServerFromApi: ServersModel.isServerFromApi(ServersModel.defaultIndex) - - enabled: !isServerFromApi - - Component.onCompleted: { - if (isServerFromApi) { - PageController.showNotificationMessage(qsTr("Default server does not support custom DNS")) - } - } - - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 - - BaseHeaderType { - Layout.fillWidth: true - - headerText: qsTr("DNS servers") - } - - ParagraphTextType { - Layout.fillWidth: true - text: qsTr("If AmneziaDNS is not used or installed") - } - - TextFieldWithHeaderType { - id: primaryDns - - Layout.fillWidth: true - headerText: qsTr("Primary DNS") - - textField.text: SettingsController.primaryDns - textField.validator: RegularExpressionValidator { - regularExpression: InstallController.ipAddressRegExp() - } - } - - TextFieldWithHeaderType { - id: secondaryDns - - Layout.fillWidth: true - headerText: qsTr("Secondary DNS") - - textField.text: SettingsController.secondaryDns - textField.validator: RegularExpressionValidator { - regularExpression: InstallController.ipAddressRegExp() - } - } - - BasicButtonType { - id: restoreDefaultButton - Layout.fillWidth: true - - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.paleGray - borderWidth: 1 - - text: qsTr("Restore default") - - clickedFunc: function() { - var headerText = qsTr("Restore default DNS settings?") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - SettingsController.primaryDns = "1.1.1.1" - primaryDns.textField.text = SettingsController.primaryDns - SettingsController.secondaryDns = "1.0.0.1" - secondaryDns.textField.text = SettingsController.secondaryDns - PageController.showNotificationMessage(qsTr("Settings have been reset")) - } - var noButtonFunction = function() { - } - - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - BasicButtonType { - id: saveButton - - Layout.fillWidth: true - - text: qsTr("Save") - - clickedFunc: function() { - if (primaryDns.textField.text !== SettingsController.primaryDns) { - SettingsController.primaryDns = primaryDns.textField.text - } - if (secondaryDns.textField.text !== SettingsController.secondaryDns) { - SettingsController.secondaryDns = secondaryDns.textField.text - } - PageController.showNotificationMessage(qsTr("Settings saved")) - } - } - } - } - -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + property var isServerFromApi: ServersModel.isServerFromApi(ServersModel.defaultIndex) + + enabled: !isServerFromApi + + Component.onCompleted: { + if (isServerFromApi) { + PageController.showNotificationMessage(qsTr("Default server does not support custom DNS")) + } + } + + header: ColumnLayout { + width: listView.width + spacing: 16 + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("DNS servers") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("If AmneziaDNS is not used or installed") + } + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Primary DNS") + + textField.text: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Secondary DNS") + + textField.text: SettingsController.secondaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } + } + } + + model: 1 // fake model to force the ListView to be created without a model + spacing: 16 + + delegate: ColumnLayout { + width: listView.width + + BasicButtonType { + id: restoreDefaultButton + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.paleGray + borderWidth: 1 + + text: qsTr("Restore default") + + clickedFunc: function() { + var headerText = qsTr("Restore default DNS settings?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SettingsController.primaryDns = "1.1.1.1" + primaryDns.textField.text = SettingsController.primaryDns + SettingsController.secondaryDns = "1.0.0.1" + secondaryDns.textField.text = SettingsController.secondaryDns + PageController.showNotificationMessage(qsTr("Settings have been reset")) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + } + + footer: ColumnLayout { + width: listView.width + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Save") + + clickedFunc: function() { + if (primaryDns.textField.text !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textField.text + } + if (secondaryDns.textField.text !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textField.text + } + PageController.showNotificationMessage(qsTr("Settings saved")) + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 5b20936c..cd48adf9 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -25,7 +25,7 @@ PageType { anchors.topMargin: 20 } - ListView { + ListViewType { id: listView anchors.top: backButton.bottom @@ -33,10 +33,6 @@ PageType { anchors.right: parent.right anchors.left: parent.left - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - header: ColumnLayout { width: listView.width @@ -101,8 +97,7 @@ PageType { } model: logTypes - clip: true - reuseItems: true + snapMode: ListView.SnapOneItem delegate: ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 995ca74b..2066ba53 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -18,10 +18,6 @@ PageType { signal lastItemTabClickedSignal() - onFocusChanged: content.isServerWithWriteAccess ? - labelWithButton.forceActiveFocus() : - labelWithButton3.forceActiveFocus() - Connections { target: InstallController @@ -67,214 +63,192 @@ PageType { } } - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height + ListViewType { + id: listView - ColumnLayout { - id: content + property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess() - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + anchors.fill: parent - property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess() + model: serverActions + + delegate: ColumnLayout { + width: listView.width LabelWithButtonType { - id: labelWithButton - visible: content.isServerWithWriteAccess Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - text: qsTr("Check the server for previously installed Amnezia services") - descriptionText: qsTr("Add them to the application if they were not displayed") + visible: isVisible + + text: title + descriptionText: description + textColor: tColor clickedFunction: function() { - PageController.showBusyIndicator(true) - InstallController.scanServerForInstalledContainers() - PageController.showBusyIndicator(false) + clickedHandler() } } DividerType { - visible: content.isServerWithWriteAccess - } - - LabelWithButtonType { - id: labelWithButton2 - visible: content.isServerWithWriteAccess - Layout.fillWidth: true - - text: qsTr("Reboot server") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Do you want to reboot the server?") - var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot reboot server during active connection")) - } else { - PageController.showBusyIndicator(true) - InstallController.rebootProcessedServer() - PageController.showBusyIndicator(false) - } - if (!GC.isMobile()) { - labelWithButton5.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - labelWithButton2.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType { - visible: content.isServerWithWriteAccess - } - - LabelWithButtonType { - id: labelWithButton3 - Layout.fillWidth: true - - text: qsTr("Remove server from application") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Do you want to remove the server from application?") - var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) - } else { - PageController.showBusyIndicator(true) - InstallController.removeProcessedServer() - PageController.showBusyIndicator(false) - } - if (!GC.isMobile()) { - labelWithButton5.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - labelWithButton3.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType {} - - LabelWithButtonType { - id: labelWithButton4 - visible: content.isServerWithWriteAccess - Layout.fillWidth: true - - text: qsTr("Clear server from Amnezia software") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Do you want to clear server from Amnezia software?") - var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot clear server from Amnezia software during active connection")) - } else { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeAllContainers() - } - if (!GC.isMobile()) { - labelWithButton5.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - labelWithButton4.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType { - visible: content.isServerWithWriteAccess - } - - LabelWithButtonType { - id: labelWithButton5 - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") - Layout.fillWidth: true - - text: qsTr("Reset API config") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Do you want to reset API config?") - var descriptionText = "" - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot reset API config during active connection")) - } else { - PageController.showBusyIndicator(true) - InstallController.removeApiConfig(ServersModel.processedIndex) - PageController.showBusyIndicator(false) - } - - if (!GC.isMobile()) { - labelWithButton5.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - labelWithButton5.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType { - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") - } - - LabelWithButtonType { - id: labelWithButton6 - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") - Layout.fillWidth: true - - text: qsTr("Switch to the new Amnezia Premium subscription") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - PageController.goToPageHome() - ApiPremV1MigrationController.showMigrationDrawer() - } - } - - DividerType { - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + visible: isVisible } } } + + property list serverActions: [ + check, + reboot, + remove, + clear, + reset, + switch_to_premium, + ] + + QtObject { + id: check + + property bool isVisible: true + readonly property string title: qsTr("Check the server for previously installed Amnezia services") + readonly property string description: qsTr("Add them to the application if they were not displayed") + readonly property var tColor: AmneziaStyle.color.paleGray + readonly property var clickedHandler: function() { + PageController.showBusyIndicator(true) + InstallController.scanServerForInstalledContainers() + PageController.showBusyIndicator(false) + } + } + + QtObject { + id: reboot + + property bool isVisible: true + readonly property string title: qsTr("Reboot server") + readonly property string description: "" + readonly property var tColor: AmneziaStyle.color.vibrantRed + readonly property var clickedHandler: function() { + var headerText = qsTr("Do you want to reboot the server?") + var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot reboot server during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.rebootProcessedServer() + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + QtObject { + id: remove + + property bool isVisible: true + readonly property string title: qsTr("Remove server from application") + readonly property string description: "" + readonly property var tColor: AmneziaStyle.color.vibrantRed + readonly property var clickedHandler: function() { + var headerText = qsTr("Do you want to remove the server from application?") + var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.removeProcessedServer() + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + QtObject { + id: clear + + property bool isVisible: true + readonly property string title: qsTr("Clear server from Amnezia software") + readonly property string description: "" + readonly property var tColor: AmneziaStyle.color.vibrantRed + readonly property var clickedHandler: function() { + var headerText = qsTr("Do you want to clear server from Amnezia software?") + var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot clear server from Amnezia software during active connection")) + } else { + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeAllContainers() + } + } + var noButtonFunction = function() { + + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + QtObject { + id: reset + + property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + readonly property string title: qsTr("Reset API config") + readonly property string description: "" + readonly property var tColor: AmneziaStyle.color.vibrantRed + readonly property var clickedHandler: function() { + var headerText = qsTr("Do you want to reset API config?") + var descriptionText = "" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot reset API config during active connection")) + } else { + PageController.showBusyIndicator(true) + InstallController.removeApiConfig(ServersModel.processedIndex) + PageController.showBusyIndicator(false) + } + } + var noButtonFunction = function() { + + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + QtObject { + id: switch_to_premium + + property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + readonly property string title: qsTr("Switch to the new Amnezia Premium subscription") + readonly property string description: "" + readonly property var tColor: AmneziaStyle.color.vibrantRed + readonly property var clickedHandler: function() { + PageController.goToPageHome() + ApiPremV1MigrationController.showMigrationDrawer() + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index fce9b2a3..88198913 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -21,249 +21,213 @@ PageType { property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex()) - ColumnLayout { - id: header + BackButtonType { + id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + } - BackButtonType { - id: backButton - } + ListViewType { + id: listView - BaseHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.bottomMargin: 32 + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left - headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") - } + header: ColumnLayout { + width: listView.width - ListView { - id: protocols - Layout.fillWidth: true - height: protocols.contentItem.height - clip: true - interactive: true + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 32 - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - model: ProtocolsModel - - delegate: Item { - implicitWidth: protocols.width - implicitHeight: delegateContent.implicitHeight - - ColumnLayout { - id: delegateContent - - anchors.fill: parent - - property bool isClientSettingsVisible: protocolIndex === ProtocolEnum.WireGuard || protocolIndex === ProtocolEnum.Awg - property bool isServerSettingsVisible: ServersModel.isProcessedServerHasWriteAccess() - - LabelWithButtonType { - id: clientSettings - - Layout.fillWidth: true - - text: protocolName + qsTr(" connection settings") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - visible: delegateContent.isClientSettingsVisible - - clickedFunction: function() { - if (isClientProtocolExists) { - switch (protocolIndex) { - case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; - } - PageController.goToPage(clientProtocolPage); - } else { - PageController.showNotificationMessage(qsTr("Click the \"connect\" button to create a connection configuration")) - } - } - - MouseArea { - anchors.fill: clientSettings - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - DividerType { - visible: delegateContent.isClientSettingsVisible - } - - LabelWithButtonType { - id: serverSettings - - Layout.fillWidth: true - - text: protocolName + qsTr(" server settings") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - visible: delegateContent.isServerSettingsVisible - - clickedFunction: function() { - switch (protocolIndex) { - case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Sftp: SftpConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ProtocolEnum.Socks5Proxy: Socks5ProxyConfigModel.updateModel(ProtocolsModel.getConfig()); break; - } - PageController.goToPage(serverProtocolPage); - } - - MouseArea { - anchors.fill: serverSettings - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - DividerType { - visible: delegateContent.isServerSettingsVisible - } - } - } - - footer: ColumnLayout { - width: header.width - - LabelWithButtonType { - id: clearCacheButton - - Layout.fillWidth: true - - visible: root.isClearCacheVisible - - text: qsTr("Clear profile") - - clickedFunction: function() { - var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName()) - var descriptionText = qsTr("The connection configuration will be deleted for this device only") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) - PageController.showNotificationMessage(message) - return - } - - PageController.showBusyIndicator(true) - InstallController.clearCachedProfile() - PageController.showBusyIndicator(false) - } - var noButtonFunction = function() { - // if (!GC.isMobile()) { - // focusItem.forceActiveFocus() - // } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - - MouseArea { - anchors.fill: clearCacheButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - visible: root.isClearCacheVisible - } - - LabelWithButtonType { - id: removeButton - - Layout.fillWidth: true - - visible: ServersModel.isProcessedServerHasWriteAccess() - - text: qsTr("Remove ") - textColor: AmneziaStyle.color.vibrantRed - - clickedFunction: function() { - var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) - var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected - && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Cannot remove active container")) - } else - { - PageController.goToPage(PageEnum.PageDeinstalling) - InstallController.removeProcessedContainer() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - focusItem.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - - MouseArea { - anchors.fill: removeButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - DividerType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - visible: ServersModel.isProcessedServerHasWriteAccess() - } + headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings") } } + model: ProtocolsModel + + delegate: ColumnLayout { + id: delegateContent + + width: listView.width + + property bool isClientSettingsVisible: protocolIndex === ProtocolEnum.WireGuard || protocolIndex === ProtocolEnum.Awg + property bool isServerSettingsVisible: ServersModel.isProcessedServerHasWriteAccess() + + LabelWithButtonType { + id: clientSettings + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: protocolName + qsTr(" connection settings") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + visible: delegateContent.isClientSettingsVisible + + clickedFunction: function() { + if (isClientProtocolExists) { + switch (protocolIndex) { + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; + } + PageController.goToPage(clientProtocolPage); + } else { + PageController.showNotificationMessage(qsTr("Click the \"connect\" button to create a connection configuration")) + } + } + + MouseArea { + anchors.fill: clientSettings + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + visible: delegateContent.isClientSettingsVisible + } + + LabelWithButtonType { + id: serverSettings + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: protocolName + qsTr(" server settings") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + visible: delegateContent.isServerSettingsVisible + + clickedFunction: function() { + switch (protocolIndex) { + case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Sftp: SftpConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Socks5Proxy: Socks5ProxyConfigModel.updateModel(ProtocolsModel.getConfig()); break; + } + PageController.goToPage(serverProtocolPage); + } + + MouseArea { + anchors.fill: serverSettings + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + visible: delegateContent.isServerSettingsVisible + } + } + + footer: ColumnLayout { + + width: listView.width + + LabelWithButtonType { + id: clearCacheButton + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: root.isClearCacheVisible + + text: qsTr("Clear profile") + + clickedFunction: function() { + var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("The connection configuration will be deleted for this device only") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) + PageController.showNotificationMessage(message) + return + } + + PageController.showBusyIndicator(true) + InstallController.clearCachedProfile() + PageController.showBusyIndicator(false) + } + + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + MouseArea { + anchors.fill: clearCacheButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + visible: root.isClearCacheVisible + } + + LabelWithButtonType { + id: removeButton + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: ServersModel.isProcessedServerHasWriteAccess() + + text: qsTr("Remove ") + textColor: AmneziaStyle.color.vibrantRed + + clickedFunction: function() { + var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) + var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected + && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Cannot remove active container")) + } else + { + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeProcessedContainer() + } + } + var noButtonFunction = function() { + + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType { + visible: ServersModel.isProcessedServerHasWriteAccess() + } + } } } - diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 57e39ae8..f7222236 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -40,25 +40,20 @@ PageType { } } - ListView { + ListViewType { id: servers objectName: "servers" width: parent.width anchors.top: header.bottom anchors.topMargin: 16 + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right - height: 500 - - property bool isFocusable: true model: ServersModel - clip: true - reuseItems: true - delegate: Item { implicitWidth: servers.width implicitHeight: delegateContent.implicitHeight diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 292f903a..eb6a8cc1 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -1,445 +1,450 @@ -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 Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - property var isServerFromTelegramApi: ServersModel.getDefaultServerData("isServerFromTelegramApi") - - property bool pageEnabled - - Component.onCompleted: { - if (ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) - root.pageEnabled = false - } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling) { - PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) - root.pageEnabled = false - } else { - root.pageEnabled = true - } - } - - Connections { - target: SitesController - - function onFinished(message) { - PageController.showNotificationMessage(message) - } - - function onErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - } - - QtObject { - id: routeMode - property int allSites: 0 - property int onlyForwardSites: 1 - property int allExceptSites: 2 - } - - property list routeModesModel: [ - onlyForwardSites, - allExceptSites - ] - - QtObject { - id: onlyForwardSites - property string name: qsTr("Only the sites listed here will be accessed through the VPN") - property int type: routeMode.onlyForwardSites - } - QtObject { - id: allExceptSites - property string name: qsTr("Addresses from the list should not be accessed via VPN") - property int type: routeMode.allExceptSites - } - - function getRouteModesModelIndex() { - var currentRouteMode = SitesModel.routeMode - if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { - return 0 - } else if (routeMode.allExceptSites === currentRouteMode) { - return 1 - } - } - - ColumnLayout { - id: header - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.topMargin: 20 - - BackButtonType { - id: backButton - } - - HeaderTypeWithSwitcher { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: qsTr("Split tunneling") - - enabled: root.pageEnabled - showSwitcher: true - switcher { - checked: SitesModel.isTunnelingEnabled - enabled: root.pageEnabled - } - switcherFunction: function(checked) { - SitesModel.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 - - enabled: root.pageEnabled - - headerText: qsTr("Mode") - - listView: ListViewWithRadioButtonType { - rootWidth: root.width - - model: root.routeModesModel - - selectedIndex: getRouteModesModelIndex() - - clickedFunction: function() { - selector.text = selectedText - selector.closeTriggered() - if (SitesModel.routeMode !== root.routeModesModel[selectedIndex].type) { - SitesModel.routeMode = root.routeModesModel[selectedIndex].type - } - } - - Component.onCompleted: { - if (root.routeModesModel[selectedIndex].type === SitesModel.routeMode) { - selector.text = selectedText - } else { - selector.text = root.routeModesModel[0].name - } - } - - Connections { - target: SitesModel - function onRouteModeChanged() { - selectedIndex = getRouteModesModelIndex() - } - } - } - } - } - - ListView { - id: listView - - anchors.top: header.bottom - anchors.topMargin: 16 - anchors.bottom: addSiteButton.top - - width: parent.width - - enabled: root.pageEnabled - - property bool isFocusable: true - - 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 - - reuseItems: true - - delegate: ColumnLayout { - id: delegateContent - - width: listView.width - - LabelWithButtonType { - id: site - Layout.fillWidth: true - - text: url - descriptionText: ip - rightImageSource: "qrc:/images/controls/trash.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - var headerText = qsTr("Remove ") + url + "?" - var yesButtonText = qsTr("Continue") - var noButtonText = qsTr("Cancel") - - var yesButtonFunction = function() { - SitesController.removeSite(proxySitesModel.mapToSource(index)) - if (!GC.isMobile()) { - site.rightButton.forceActiveFocus() - } - } - var noButtonFunction = function() { - if (!GC.isMobile()) { - site.rightButton.forceActiveFocus() - } - } - - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) - } - } - - DividerType {} - } - } - - - Rectangle { - anchors.fill: addSiteButton - anchors.bottomMargin: -24 - color: AmneziaStyle.color.midnightBlack - opacity: 0.8 - } - - RowLayout { - id: addSiteButton - - enabled: root.pageEnabled - - 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 - rightButtonClickedOnEnter: true - - textField.placeholderText: qsTr("website or IP") - buttonImageSource: "qrc:/images/controls/plus.svg" - - clickedFunc: function() { - PageController.showBusyIndicator(true) - SitesController.addSite(textField.text) - textField.text = "" - PageController.showBusyIndicator(false) - } - } - - ImageButtonType { - id: addSiteButtonImage - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/more-vertical.svg" - imageColor: AmneziaStyle.color.paleGray - - onClicked: function () { - moreActionsDrawer.openTriggered() - } - - Keys.onReturnPressed: addSiteButtonImage.clicked() - Keys.onEnterPressed: addSiteButtonImage.clicked() - } - } - - DrawerType2 { - id: moreActionsDrawer - - anchors.fill: parent - expandedHeight: parent.height * 0.4375 - - expandedStateContent: ColumnLayout { - id: moreActionsDrawerContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Header2Type { - Layout.fillWidth: true - Layout.margins: 16 - - headerText: qsTr("Import / Export Sites") - } - - LabelWithButtonType { - id: importSitesButton - Layout.fillWidth: true - - text: qsTr("Import") - rightImageSource: "qrc:/images/controls/chevron-right.svg" - - clickedFunction: function() { - importSitesDrawer.openTriggered() - } - } - - DividerType {} - - LabelWithButtonType { - id: exportSitesButton - Layout.fillWidth: true - text: qsTr("Save site list") - - clickedFunction: function() { - var fileName = "" - if (GC.isMobile()) { - fileName = "amnezia_sites.json" - } else { - fileName = SystemController.getFileName(qsTr("Save sites"), - qsTr("Sites files (*.json)"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", - true, - ".json") - } - if (fileName !== "") { - PageController.showBusyIndicator(true) - SitesController.exportSites(fileName) - moreActionsDrawer.closeTriggered() - PageController.showBusyIndicator(false) - } - } - } - - DividerType {} - } - } - - DrawerType2 { - id: importSitesDrawer - - anchors.fill: parent - expandedHeight: parent.height * 0.4375 - - expandedStateContent: Item { - implicitHeight: importSitesDrawer.expandedHeight - - BackButtonType { - id: importSitesDrawerBackButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 - - backButtonFunction: function() { - importSitesDrawer.closeTriggered() - } - } - - FlickableType { - anchors.top: importSitesDrawerBackButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - - contentHeight: importSitesDrawerContent.height - - ColumnLayout { - id: importSitesDrawerContent - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Header2Type { - Layout.fillWidth: true - Layout.margins: 16 - - headerText: qsTr("Import a list of sites") - } - - LabelWithButtonType { - id: importSitesButton2 - Layout.fillWidth: true - - text: qsTr("Replace site list") - - clickedFunction: function() { - var fileName = SystemController.getFileName(qsTr("Open sites file"), - qsTr("Sites files (*.json)")) - if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, true) - } - } - } - - DividerType {} - - LabelWithButtonType { - id: importSitesButton3 - Layout.fillWidth: true - text: qsTr("Add imported sites to existing ones") - - clickedFunction: function() { - var fileName = SystemController.getFileName(qsTr("Open sites file"), - qsTr("Sites files (*.json)")) - if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, false) - } - } - } - - function importSites(fileName, replaceExistingSites) { - PageController.showBusyIndicator(true) - SitesController.importSites(fileName, replaceExistingSites) - PageController.showBusyIndicator(false) - importSitesDrawer.closeTriggered() - moreActionsDrawer.closeTriggered() - } - - DividerType {} - } - } - } - } -} +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 Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property var isServerFromTelegramApi: ServersModel.getDefaultServerData("isServerFromTelegramApi") + + property bool pageEnabled + + Component.onCompleted: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change split tunneling settings during active connection")) + root.pageEnabled = false + } else if (ServersModel.isDefaultServerDefaultContainerHasSplitTunneling) { + PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function")) + root.pageEnabled = false + } else { + root.pageEnabled = true + } + } + + Connections { + target: SitesController + + function onFinished(message) { + PageController.showNotificationMessage(message) + } + + function onErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + + QtObject { + id: routeMode + property int allSites: 0 + property int onlyForwardSites: 1 + property int allExceptSites: 2 + } + + property list routeModesModel: [ + onlyForwardSites, + allExceptSites + ] + + QtObject { + id: onlyForwardSites + property string name: qsTr("Only the sites listed here will be accessed through the VPN") + property int type: routeMode.onlyForwardSites + } + QtObject { + id: allExceptSites + property string name: qsTr("Addresses from the list should not be accessed via VPN") + property int type: routeMode.allExceptSites + } + + function getRouteModesModelIndex() { + var currentRouteMode = SitesModel.routeMode + if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { + return 0 + } else if (routeMode.allExceptSites === currentRouteMode) { + return 1 + } + } + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + id: backButton + } + + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Split tunneling") + + enabled: root.pageEnabled + showSwitcher: true + switcher { + checked: SitesModel.isTunnelingEnabled + enabled: root.pageEnabled + } + switcherFunction: function(checked) { + SitesModel.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 + + enabled: root.pageEnabled + + headerText: qsTr("Mode") + + listView: ListViewWithRadioButtonType { + rootWidth: root.width + + model: root.routeModesModel + + selectedIndex: getRouteModesModelIndex() + + clickedFunction: function() { + selector.text = selectedText + selector.closeTriggered() + if (SitesModel.routeMode !== root.routeModesModel[selectedIndex].type) { + SitesModel.routeMode = root.routeModesModel[selectedIndex].type + } + } + + Component.onCompleted: { + if (root.routeModesModel[selectedIndex].type === SitesModel.routeMode) { + selector.text = selectedText + } else { + selector.text = root.routeModesModel[0].name + } + } + + Connections { + target: SitesModel + function onRouteModeChanged() { + selectedIndex = getRouteModesModelIndex() + } + } + } + } + } + + ListViewType { + id: listView + + anchors.top: header.bottom + anchors.topMargin: 16 + anchors.bottom: addSiteButton.top + + width: parent.width + + enabled: root.pageEnabled + + 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 + } + } + ] + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + id: site + Layout.fillWidth: true + + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + var headerText = qsTr("Remove ") + url + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SitesController.removeSite(proxySitesModel.mapToSource(index)) + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + Rectangle { + anchors.fill: addSiteButton + anchors.bottomMargin: -24 + color: AmneziaStyle.color.midnightBlack + opacity: 0.8 + } + + RowLayout { + id: addSiteButton + + enabled: root.pageEnabled + + 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 + rightButtonClickedOnEnter: true + + textField.placeholderText: qsTr("website or IP") + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + PageController.showBusyIndicator(true) + SitesController.addSite(textField.text) + textField.text = "" + PageController.showBusyIndicator(false) + } + } + + ImageButtonType { + id: addSiteButtonImage + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: AmneziaStyle.color.paleGray + + onClicked: function () { + moreActionsDrawer.openTriggered() + } + + Keys.onReturnPressed: addSiteButtonImage.clicked() + Keys.onEnterPressed: addSiteButtonImage.clicked() + } + } + + DrawerType2 { + id: moreActionsDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import / Export Sites") + } + + LabelWithButtonType { + id: importSitesButton + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.openTriggered() + } + } + + DividerType {} + + LabelWithButtonType { + id: exportSitesButton + Layout.fillWidth: true + text: qsTr("Save site list") + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "amnezia_sites.json" + } else { + fileName = SystemController.getFileName(qsTr("Save sites"), + qsTr("Sites files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", + true, + ".json") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + SitesController.exportSites(fileName) + moreActionsDrawer.closeTriggered() + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } + + DrawerType2 { + id: importSitesDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: Item { + implicitHeight: importSitesDrawer.expandedHeight + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.closeTriggered() + } + } + + ListViewType { + id: importSitesDrawerListView + + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + header: ColumnLayout { + width: importSitesDrawerListView.width + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import a list of sites") + } + } + + model: importOptions + + delegate: ColumnLayout { + width: importSitesDrawerListView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: title + + clickedFunction: function() { + clickedHandler() + } + } + + DividerType {} + } + } + } + } + + property list importOptions: [ + replaceOption, + addOption, + ] + + QtObject { + id: replaceOption + + readonly property string title: qsTr("Replace site list") + readonly property var clickedHandler: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } + } + } + + QtObject { + id: addOption + + readonly property string title: qsTr("Add imported sites to existing ones") + readonly property var clickedHandler: function() { + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } + } + } + + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + SitesController.importSites(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.closeTriggered() + moreActionsDrawer.closeTriggered() + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 30128de6..7f30efda 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -1,146 +1,177 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - FlickableType { - id: fl - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height + continueButton.implicitHeight + continueButton.anchors.bottomMargin + continueButton.anchors.topMargin - - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 0 - - BackButtonType { - id: backButton - Layout.topMargin: 20 - } - - BaseHeaderType { - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 32 - - headerText: ApiServicesModel.getSelectedServiceData("name") - descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") - } - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/map-pin.svg" - leftText: qsTr("For the region") - rightText: ApiServicesModel.getSelectedServiceData("region") - } - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/tag.svg" - leftText: qsTr("Price") - rightText: ApiServicesModel.getSelectedServiceData("price") - } - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/history.svg" - leftText: qsTr("Work period") - rightText: ApiServicesModel.getSelectedServiceData("timeLimit") - - visible: rightText !== "" - } - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/gauge.svg" - leftText: qsTr("Speed") - rightText: ApiServicesModel.getSelectedServiceData("speed") - } - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: "qrc:/images/controls/info.svg" - leftText: qsTr("Features") - rightText: "" - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } - textFormat: Text.RichText - text: { - var text = ApiServicesModel.getSelectedServiceData("features") - return text.replace("%1", LanguageModel.getCurrentSiteUrl()) - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - } - } - - BasicButtonType { - id: continueButton - - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - anchors.topMargin: 32 - anchors.rightMargin: 16 - anchors.leftMargin: 16 - anchors.bottomMargin: 32 - - text: qsTr("Connect") - - clickedFunc: function() { - var endpoint = ApiServicesModel.getStoreEndpoint() - if (endpoint !== undefined && endpoint !== "") { - Qt.openUrlExternally(endpoint) - PageController.closePage() - PageController.closePage() - } else { - PageController.showBusyIndicator(true) - ApiConfigsController.importServiceFromGateway() - PageController.showBusyIndicator(false) - } - } - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 32 + + headerText: ApiServicesModel.getSelectedServiceData("name") + descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + } + } + + model: inputFields + spacing: 0 + + delegate: ColumnLayout { + width: listView.width + + LabelWithImageType { + Layout.fillWidth: true + Layout.margins: 16 + + imageSource: imagePath + leftText: lText + rightText: rText + } + } + + footer: ColumnLayout { + width: listView.width + + spacing: 0 + + ParagraphTextType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + textFormat: Text.RichText + text: { + var text = ApiServicesModel.getSelectedServiceData("features") + return text.replace("%1", LanguageModel.getCurrentSiteUrl()) + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + + BasicButtonType { + id: continueButton + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.bottomMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Connect") + + clickedFunc: function() { + var endpoint = ApiServicesModel.getStoreEndpoint() + if (endpoint !== undefined && endpoint !== "") { + Qt.openUrlExternally(endpoint) + PageController.closePage() + PageController.closePage() + } else { + PageController.showBusyIndicator(true) + ApiConfigsController.importServiceFromGateway() + PageController.showBusyIndicator(false) + } + } + } + } + } + + property list inputFields: [ + region, + price, + timeLimit, + speed, + features + ] + + QtObject { + id: region + + readonly property string imagePath: "qrc:/images/controls/map-pin.svg" + readonly property string lText: qsTr("For the region") + readonly property string rText: ApiServicesModel.getSelectedServiceData("region") + property bool isVisible: true + } + + QtObject { + id: price + + readonly property string imagePath: "qrc:/images/controls/tag.svg" + readonly property string lText: qsTr("Price") + readonly property string rText: ApiServicesModel.getSelectedServiceData("price") + property bool isVisible: true + } + + QtObject { + id: timeLimit + + readonly property string imagePath: "qrc:/images/controls/history.svg" + readonly property string lText: qsTr("Work period") + readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit") + property bool isVisible: rText !== "" + } + + QtObject { + id: speed + + readonly property string imagePath: "qrc:/images/controls/gauge.svg" + readonly property string lText: qsTr("Speed") + readonly property string rText: ApiServicesModel.getSelectedServiceData("speed") + property bool isVisible: true + } + + QtObject { + id: features + + readonly property string imagePath: "qrc:/images/controls/info.svg" + readonly property string lText: qsTr("Features") + readonly property string rText: "" + property bool isVisible: true + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index 549eb381..d503cd62 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -40,7 +40,7 @@ PageType { } } - ListView { + ListViewType { id: servicesListView anchors.top: header.bottom @@ -48,17 +48,11 @@ PageType { anchors.left: parent.left anchors.bottom: parent.bottom anchors.topMargin: 16 + spacing: 0 - property bool isFocusable: true - - clip: true - reuseItems: true - model: ApiServicesModel - ScrollBar.vertical: ScrollBarType {} - delegate: Item { implicitWidth: servicesListView.width implicitHeight: delegateContent.implicitHeight diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 7159ab59..cf398088 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -27,21 +27,13 @@ PageType { } } - ListView { + ListViewType { id: listView anchors.fill: parent - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - model: variants - clip: true - - reuseItems: true - header: ColumnLayout { width: listView.width diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 63d4d5f6..b51afc65 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -28,41 +28,14 @@ PageType { } } - ListView { + ListViewType { id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left - property bool isFocusable: true - - Keys.onTabPressed: { - FocusController.nextKeyTabItem() - } - - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } - - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } - - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } - - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } - - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - ScrollBar.vertical: ScrollBarType {} - header: ColumnLayout { width: listView.width @@ -78,8 +51,6 @@ PageType { model: inputFields spacing: 16 - clip: true - reuseItems: true delegate: ColumnLayout { width: listView.width diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 096406e9..93759534 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -20,6 +20,7 @@ PageType { SortFilterProxyModel { id: proxyContainersModel + sourceModel: ContainersModel filters: [ ValueFilter { @@ -42,102 +43,88 @@ PageType { anchors.topMargin: 20 } - FlickableType { - id: fl + ButtonGroup { + id: buttonGroup + } + + ListViewType { + id: listView + + property int dockerContainer + property int containerDefaultPort + property int containerDefaultTransportProto + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + setupLaterButton.anchors.bottomMargin + anchors.left: parent.left + anchors.right: parent.right - Column { + spacing: 16 + + header: ColumnLayout { id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 + width: listView.width spacing: 16 BaseHeaderType { id: header - implicitWidth: parent.width + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + headerTextMaximumLineCount: 10 headerText: qsTr("Choose Installation Type") } + } - ButtonGroup { - id: buttonGroup - } + model: proxyContainersModel + currentIndex: 1 - ListView { - id: containers - width: parent.width - height: containers.contentItem.height - spacing: 16 + delegate: ColumnLayout { - currentIndex: 0 - clip: true - interactive: false - model: proxyContainersModel + width: listView.width - property int dockerContainer - property int containerDefaultPort - property int containerDefaultTransportProto + CardType { + id: card - property bool isFocusable: true + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 - delegate: Item { - implicitWidth: containers.width - implicitHeight: delegateContent.implicitHeight + headerText: easySetupHeader + bodyText: easySetupDescription - ColumnLayout { - id: delegateContent + ButtonGroup.group: buttonGroup - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + onClicked: function() { + isEasySetup = true + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) - CardType { - id: card - - Layout.fillWidth: true - - headerText: easySetupHeader - bodyText: easySetupDescription - - ButtonGroup.group: buttonGroup - - onClicked: function() { - isEasySetup = true - var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) - - containers.dockerContainer = dockerContainer - containers.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) - containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) - } - } - } - } - - Component.onCompleted: { - var item = containers.itemAtIndex(containers.currentIndex) - if (item !== null) { - var button = item.children[0].children[0] - button.checked = true - button.clicked() - } + listView.dockerContainer = dockerContainer + listView.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) + listView.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) } } + } + + footer: ColumnLayout { + + width: listView.width DividerType { - implicitWidth: parent.width + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 } CardType { - implicitWidth: parent.width + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: qsTr("Manual") bodyText: qsTr("Choose a VPN protocol") @@ -152,19 +139,19 @@ PageType { BasicButtonType { id: continueButton - implicitWidth: parent.width + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: qsTr("Continue") - - parentFlickable: fl clickedFunc: function() { if (root.isEasySetup) { - ContainersModel.setProcessedContainerIndex(containers.dockerContainer) + ContainersModel.setProcessedContainerIndex(listView.dockerContainer) PageController.goToPage(PageEnum.PageSetupWizardInstalling) - InstallController.install(containers.dockerContainer, - containers.containerDefaultPort, - containers.containerDefaultTransportProto) + InstallController.install(listView.dockerContainer, + listView.containerDefaultPort, + listView.containerDefaultTransportProto) } else { PageController.goToPage(PageEnum.PageSetupWizardProtocols) } @@ -174,9 +161,11 @@ PageType { BasicButtonType { id: setupLaterButton - implicitWidth: parent.width - anchors.topMargin: 8 - anchors.bottomMargin: 24 + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 defaultColor: AmneziaStyle.color.transparent hoveredColor: AmneziaStyle.color.translucentWhite @@ -185,9 +174,6 @@ PageType { textColor: AmneziaStyle.color.paleGray borderWidth: 1 - Keys.onTabPressed: lastItemTabClicked(focusItem) - parentFlickable: fl - visible: { if (PageController.isTriggeredByConnectButton()) { PageController.setTriggeredByConnectButton(false) @@ -205,5 +191,15 @@ PageType { } } } + + Component.onCompleted: { + var item = listView.itemAtIndex(listView.currentIndex) + if (item !== null) { + var button = item.children[0].children[0] + button.checked = true + button.clicked() + } + } } } + diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 822931b8..efabeb9f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -85,92 +85,76 @@ PageType { ] } - FlickableType { + ListViewType { + id: listView + anchors.fill: parent - contentHeight: content.height - Column { - id: content + currentIndex: -1 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + model: proxyContainersModel - spacing: 16 + delegate: ColumnLayout { + width: listView.width - ListView { - id: container - width: parent.width - height: container.contentItem.height - currentIndex: -1 - clip: true - interactive: false - model: proxyContainersModel + BaseHeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - delegate: Item { - implicitWidth: container.width - implicitHeight: delegateContent.implicitHeight + headerText: qsTr("Installing") + descriptionText: name + } - ColumnLayout { - id: delegateContent + ProgressBarType { + id: progressBar - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - BaseHeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + Timer { + id: timer - headerText: qsTr("Installing") - descriptionText: name - } - - ProgressBarType { - id: progressBar - - Layout.fillWidth: true - Layout.topMargin: 32 - - Timer { - id: timer - - interval: 300 - repeat: true - running: root.isTimerRunning - onTriggered: { - progressBar.value += 0.003 - } - } - } - - ParagraphTextType { - id: progressText - - Layout.fillWidth: true - Layout.topMargin: 8 - - text: root.progressBarText - } - - BasicButtonType { - id: cancelIntallationButton - - Layout.fillWidth: true - Layout.topMargin: 24 - - visible: root.isCancelButtonVisible - - text: qsTr("Cancel installation") - - clickedFunc: function() { - InstallController.cancelInstallation() - PageController.showBusyIndicator(true) - } - } + interval: 300 + repeat: true + running: root.isTimerRunning + onTriggered: { + progressBar.value += 0.003 } } } + + ParagraphTextType { + id: progressText + + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: root.progressBarText + } + + BasicButtonType { + id: cancelIntallationButton + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: root.isCancelButtonVisible + + text: qsTr("Cancel installation") + + clickedFunc: function() { + InstallController.cancelInstallation() + PageController.showBusyIndicator(true) + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index ac7fc4b2..af82eb3a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -29,256 +29,241 @@ PageType { ] } - FlickableType { - anchors.fill: parent - contentHeight: content.height + BackButtonType { + id: backButton - Column { - id: content + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } - ListView { - id: processedContainerListView - width: parent.width - height: contentItem.height - currentIndex: -1 - clip: true - interactive: false - model: proxyContainersModel + ListViewType { + id: listView - property bool isFocusable: true + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left - Keys.onTabPressed: { - FocusController.nextKeyTabItem() + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + id: header + + Layout.fillWidth: true + + headerText: qsTr("Installing %1").arg(name) + descriptionText: description + } + } + + currentIndex: -1 + + model: proxyContainersModel + + delegate: ColumnLayout { + width: listView.width + + BasicButtonType { + id: showDetailsButton + + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + implicitHeight: 32 + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.goldenApricot + + text: qsTr("More detailed") + + clickedFunc: function() { + showDetailsDrawer.openTriggered() } + } - Keys.onBacktabPressed: { - FocusController.previousKeyTabItem() - } + DrawerType2 { + id: showDetailsDrawer + parent: root - Keys.onUpPressed: { - FocusController.nextKeyUpItem() - } + anchors.fill: parent + expandedHeight: parent.height * 0.9 + expandedStateContent: Item { + implicitHeight: showDetailsDrawer.expandedHeight - Keys.onDownPressed: { - FocusController.nextKeyDownItem() - } + BackButtonType { + id: showDetailsBackButton - Keys.onLeftPressed: { - FocusController.nextKeyLeftItem() - } + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 - Keys.onRightPressed: { - FocusController.nextKeyRightItem() - } - - delegate: Item { - implicitWidth: processedContainerListView.width - implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height - - property alias port:port - - ColumnLayout { - id: delegateContent - - anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - BackButtonType { - id: backButton - - Layout.topMargin: 20 - Layout.rightMargin: -16 - Layout.leftMargin: -16 + backButtonFunction: function() { + showDetailsDrawer.closeTriggered() } + } - BaseHeaderType { - id: header + ListViewType { + id: showDetailsListView - Layout.fillWidth: true + anchors.top: showDetailsBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom - headerText: qsTr("Installing %1").arg(name) - descriptionText: description - } + header: ColumnLayout { + width: showDetailsListView.width - BasicButtonType { - id: showDetailsButton + Header2Type { + id: showDetailsDrawerHeader - Layout.topMargin: 16 - Layout.leftMargin: -8 + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - implicitHeight: 32 - - defaultColor: AmneziaStyle.color.transparent - hoveredColor: AmneziaStyle.color.translucentWhite - pressedColor: AmneziaStyle.color.sheerWhite - disabledColor: AmneziaStyle.color.mutedGray - textColor: AmneziaStyle.color.goldenApricot - - text: qsTr("More detailed") - KeyNavigation.tab: transportProtoSelector - - clickedFunc: function() { - showDetailsDrawer.openTriggered() + headerText: name } } - DrawerType2 { - id: showDetailsDrawer - parent: root + model: 1 // fake model to force the ListView to be created without a model - anchors.fill: parent - expandedHeight: parent.height * 0.9 - expandedStateContent: Item { - implicitHeight: showDetailsDrawer.expandedHeight + delegate: ColumnLayout { + width: showDetailsListView.width - BackButtonType { - id: showDetailsBackButton + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + text: detailedDescription + textFormat: Text.MarkdownText + } - backButtonFunction: function() { - showDetailsDrawer.closeTriggered() - } - } + Rectangle { + Layout.fillHeight: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - FlickableType { - id: fl - anchors.top: showDetailsBackButton.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - contentHeight: { - var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin - return (showDetailsDrawerContent.height > emptySpaceHeight) ? - showDetailsDrawerContent.height : emptySpaceHeight - } + color: AmneziaStyle.color.transparent + } + } - ColumnLayout { - id: showDetailsDrawerContent + footer: ColumnLayout { + width: showDetailsListView.width - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 + BasicButtonType { + id: showDetailsCloseButton + Layout.fillWidth: true + Layout.bottomMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - Header2Type { - id: showDetailsDrawerHeader - Layout.fillWidth: true - Layout.topMargin: 16 + text: qsTr("Close") - headerText: name - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - - text: detailedDescription - textFormat: Text.MarkdownText - } - - Rectangle { - Layout.fillHeight: true - color: AmneziaStyle.color.transparent - } - - BasicButtonType { - id: showDetailsCloseButton - Layout.fillWidth: true - Layout.bottomMargin: 32 - parentFlickable: fl - - text: qsTr("Close") - - clickedFunc: function() { - showDetailsDrawer.closeTriggered() - } - } - } + clickedFunc: function() { + showDetailsDrawer.closeTriggered() } } } - - ParagraphTextType { - id: transportProtoHeader - - Layout.topMargin: 16 - - text: qsTr("Network protocol") - } - - TransportProtoSelector { - id: transportProtoSelector - - Layout.fillWidth: true - rootWidth: root.width - } - - TextFieldWithHeaderType { - id: port - - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("Port") - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - } - - Rectangle { - Layout.fillHeight: true - color: AmneziaStyle.color.transparent - } - - BasicButtonType { - id: installButton - - Layout.fillWidth: true - Layout.bottomMargin: 32 - - text: qsTr("Install") - - clickedFunc: function() { - if (!port.textField.acceptableInput && - ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && - ContainerProps.containerTypeToString(dockerContainer) !== "ikev2") { - port.errorText = qsTr("The port must be in the range of 1 to 65535") - return - } - - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex) - } - } - - Component.onCompleted: { - var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) - - if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { - port.visible = false - } else { - port.textField.text = ProtocolProps.getPortForInstall(defaultContainerProto) - } - transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) - - port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) - transportProtoSelector.visible = protocolSelectorVisible - transportProtoHeader.visible = protocolSelectorVisible - } } } } + + ParagraphTextType { + id: transportProtoHeader + + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + id: transportProtoSelector + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + rootWidth: root.width + } + + TextFieldWithHeaderType { + id: port + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Port") + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + } + + Rectangle { + Layout.fillHeight: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + color: AmneziaStyle.color.transparent + } + + BasicButtonType { + id: installButton + + Layout.fillWidth: true + Layout.bottomMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + text: qsTr("Install") + + clickedFunc: function() { + if (!port.textField.acceptableInput && + ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && + ContainerProps.containerTypeToString(dockerContainer) !== "ikev2") { + port.errorText = qsTr("The port must be in the range of 1 to 65535") + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex) + } + } + + Component.onCompleted: { + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { + port.visible = false + } else { + port.textField.text = ProtocolProps.getPortForInstall(defaultContainerProto) + } + transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + + port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) + var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.visible = protocolSelectorVisible + transportProtoHeader.visible = protocolSelectorVisible + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 7afab630..e1d82e80 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -44,17 +44,13 @@ PageType { anchors.topMargin: 20 } - ListView { + ListViewType { id: listView anchors.top: backButton.bottom anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left - property bool isFocusable: true - - ScrollBar.vertical: ScrollBarType {} - header: ColumnLayout { width: listView.width @@ -72,9 +68,8 @@ PageType { } model: proxyContainersModel - clip: true + spacing: 0 - reuseItems: true snapMode: ListView.SnapToItem delegate: ColumnLayout { @@ -87,9 +82,9 @@ PageType { descriptionText: description rightImageSource: "qrc:/images/controls/chevron-right.svg" - clickedFunction: function() { - ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + clickedFunction: function () { + ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index)); + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings); } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 930efb57..3a0f7832 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -13,25 +13,31 @@ import "../Config" PageType { id: root - FlickableType { - id: fl + BackButtonType { + id: backButton + anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: content.height + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - spacing: 16 - - BackButtonType { - id: backButton - Layout.topMargin: 20 + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width BaseHeaderType { Layout.fillWidth: true @@ -41,6 +47,13 @@ PageType { headerText: qsTr("Connection key") descriptionText: qsTr("A line that starts with vpn://...") } + } + + spacing: 16 + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: listView.width TextFieldWithHeaderType { id: textKey @@ -60,23 +73,26 @@ PageType { } } } - } - BasicButtonType { - id: continueButton + footer: ColumnLayout { + width: listView.width - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - anchors.bottomMargin: 32 + BasicButtonType { + id: continueButton - text: qsTr("Continue") + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 32 - clickedFunc: function() { - if (ImportController.extractConfigFromData(textKey.textField.text)) { - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + text: qsTr("Continue") + + clickedFunc: function() { + if (ImportController.extractConfigFromData(textKey.textField.text)) { + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index cfa9c90f..47d46a11 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -46,27 +46,29 @@ PageType { } } - FlickableType { - id: fl + ListViewType { + id: listView + anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + connectButton.implicitHeight + anchors.right: parent.right + anchors.left: parent.left - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 + header: ColumnLayout { + width: listView.width BaseHeaderType { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + headerText: qsTr("New connection") } RowLayout { Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + spacing: 8 visible: fileName.text !== "" @@ -88,7 +90,9 @@ PageType { BasicButtonType { id: showContentButton Layout.topMargin: 16 - Layout.leftMargin: -8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + implicitHeight: 32 defaultColor: AmneziaStyle.color.transparent @@ -99,8 +103,6 @@ PageType { text: showContent ? qsTr("Collapse content") : qsTr("Show content") - parentFlickable: fl - clickedFunc: function() { showContent = !showContent } @@ -112,12 +114,23 @@ PageType { visible: ImportController.isNativeWireGuardConfig() Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + text: qsTr("Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.") } + } + + model: 1 // fake model to force the ListView to be created without a model + + delegate: ColumnLayout { + width: listView.width WarningType { - Layout.topMargin: 16 Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 textString: ImportController.getMaliciousWarningText() textFormat: Qt.RichText @@ -130,17 +143,25 @@ PageType { } WarningType { - Layout.topMargin: 16 Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 textString: qsTr("Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.") iconPath: "qrc:/images/controls/alert-circle.svg" } + } + + footer: ColumnLayout { + width: listView.width Rectangle { Layout.fillWidth: true Layout.bottomMargin: 48 + Layout.rightMargin: 16 + Layout.leftMargin: 16 implicitHeight: configContent.implicitHeight @@ -153,42 +174,29 @@ PageType { id: configContent anchors.fill: parent - anchors.margins: 16 wrapMode: Text.Wrap text: ImportController.getConfig() } } - } - } - Rectangle { - anchors.fill: columnContent - anchors.bottomMargin: -24 - color: AmneziaStyle.color.midnightBlack - opacity: 0.8 - } + BasicButtonType { + id: connectButton - ColumnLayout { - id: columnContent - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - BasicButtonType { - id: connectButton - Layout.fillWidth: true - Layout.bottomMargin: 32 - - text: qsTr("Connect") - clickedFunc: function() { - if (cloakingCheckBox.checked) { - ImportController.processNativeWireGuardConfig() + text: qsTr("Connect") + clickedFunc: function() { + if (cloakingCheckBox.checked) { + ImportController.processNativeWireGuardConfig() + } + ImportController.importConfig() } - ImportController.importConfig() } } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 48f74acf..71f1b3c4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -520,9 +520,6 @@ PageType { text: qsTr("Share") leftImageSource: "qrc:/images/controls/share-2.svg" - - parentFlickable: a - clickedFunc: function(){ if (clientNameTextField.textField.text !== "") { ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 82effb57..92808fbe 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -1,156 +1,171 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import SortFilterProxyModel 0.2 - -import PageEnum 1.0 -import ContainerProps 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Components" -import "../Config" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 - } - - FlickableType { - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - contentHeight: content.height - - ColumnLayout { - id: content - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - spacing: 0 - - BaseHeaderType { - Layout.fillWidth: true - Layout.topMargin: 24 - - headerText: qsTr("Full access to the server and VPN") - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 24 - - text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") + - qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ") - color: AmneziaStyle.color.mutedGray - } - - DropDownType { - id: serverSelector - - signal severSelectorIndexChanged - property int currentIndex: 0 - - Layout.fillWidth: true - Layout.topMargin: 16 - - drawerHeight: 0.4375 - drawerParent: root - - descriptionText: qsTr("Server") - headerText: qsTr("Server") - - listView: ListViewWithRadioButtonType { - id: serverSelectorListView - - rootWidth: root.width - imageSource: "qrc:/images/controls/check.svg" - - model: SortFilterProxyModel { - id: proxyServersModel - sourceModel: ServersModel - filters: [ - ValueFilter { - roleName: "hasWriteAccess" - value: true - } - ] - } - - clickedFunction: function() { - handler() - - if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { - serverSelector.currentIndex = serverSelectorListView.currentIndex - } - - shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text - serverSelector.closeTriggered() - } - - Component.onCompleted: { - serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ? - proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0 - serverSelectorListView.triggerCurrentItem() - } - - function handler() { - serverSelector.text = selectedText - ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) - } - } - } - - BasicButtonType { - id: shareButton - Layout.fillWidth: true - Layout.topMargin: 40 - - text: qsTr("Share") - leftImageSource: "qrc:/images/controls/share-2.svg" - - clickedFunc: function() { - PageController.showBusyIndicator(true) - - if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) { - PageController.showBusyIndicator(false) - ExportController.exportErrorOccurred(qsTr("Access error!")) - return - } else { - ExportController.generateFullAccessConfig() - } - - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - - shareConnectionDrawer.openTriggered() - - PageController.showBusyIndicator(false) - } - } - } - } - - ShareConnectionDrawer { - id: shareConnectionDrawer - - anchors.fill: parent - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Components" +import "../Config" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 24 + + headerText: qsTr("Full access to the server and VPN") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") + + qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ") + color: AmneziaStyle.color.mutedGray + } + + DropDownType { + id: serverSelector + + signal severSelectorIndexChanged + property int currentIndex: 0 + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 16 + + drawerHeight: 0.4375 + drawerParent: root + + descriptionText: qsTr("Server") + headerText: qsTr("Server") + + listView: ListViewWithRadioButtonType { + id: serverSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "hasWriteAccess" + value: true + } + ] + } + + clickedFunction: function() { + handler() + + if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { + serverSelector.currentIndex = serverSelectorListView.currentIndex + } + + shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text + serverSelector.closeTriggered() + } + + Component.onCompleted: { + serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ? + proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0 + serverSelectorListView.triggerCurrentItem() + } + + function handler() { + serverSelector.text = selectedText + ServersModel.processedIndex = proxyServersModel.mapToSource(currentIndex) + } + } + } + } + + model: 1 // fake model to force the ListView to be created without a model + spacing: 0 + + delegate: ColumnLayout { + width: listView.width + + BasicButtonType { + id: shareButton + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Share") + leftImageSource: "qrc:/images/controls/share-2.svg" + + clickedFunc: function() { + PageController.showBusyIndicator(true) + + if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) { + PageController.showBusyIndicator(false) + ExportController.exportErrorOccurred(qsTr("Access error!")) + return + } else { + ExportController.generateFullAccessConfig() + } + + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + + shareConnectionDrawer.openTriggered() + + PageController.showBusyIndicator(false) + } + } + } + } + + ShareConnectionDrawer { + id: shareConnectionDrawer + + anchors.fill: parent + } +} From 65e7237610be19b12d50e7d8105834ba2ff678ce Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 14 Apr 2025 11:55:09 +0200 Subject: [PATCH 02/37] Update client/ui/controllers/listViewFocusController.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- client/ui/controllers/listViewFocusController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index a20055d4..331b4748 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -239,7 +239,7 @@ void ListViewFocusController::resetFocusChain() m_focusChain.clear(); m_focusedItem = nullptr; m_focusedItemIndex = -1; - qDebug() << "Focus chain was resetted"; + qDebug() << "Focus chain was reset"; } void ListViewFocusController::reloadFocusChain() From 8b016384248dc2ea4e3f2fef2d7b7a5c3ed408bf Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 5 May 2025 23:12:35 +0200 Subject: [PATCH 03/37] fix `PageSetupWizardEasy` --- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 93759534..ca5861bc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -74,6 +74,7 @@ PageType { Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.bottomMargin: 16 headerTextMaximumLineCount: 10 @@ -82,7 +83,7 @@ PageType { } model: proxyContainersModel - currentIndex: 1 + currentIndex: 0 delegate: ColumnLayout { @@ -94,6 +95,7 @@ PageType { Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.bottomMargin: 16 headerText: easySetupHeader bodyText: easySetupDescription @@ -114,6 +116,7 @@ PageType { footer: ColumnLayout { width: listView.width + spacing: 16 DividerType { Layout.fillWidth: true @@ -195,7 +198,7 @@ PageType { Component.onCompleted: { var item = listView.itemAtIndex(listView.currentIndex) if (item !== null) { - var button = item.children[0].children[0] + var button = item.children[0] button.checked = true button.clicked() } From 111a60d81c62548b16be93ca76f4faaa78d64ca3 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Wed, 7 May 2025 19:24:26 +0200 Subject: [PATCH 04/37] fix `PageSetupWizardProtocolSettings` titles --- .../PageSetupWizardProtocolSettings.qml | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index af82eb3a..4d914a1d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -52,19 +52,6 @@ PageType { anchors.right: parent.right anchors.left: parent.left - header: ColumnLayout { - width: listView.width - - BaseHeaderType { - id: header - - Layout.fillWidth: true - - headerText: qsTr("Installing %1").arg(name) - descriptionText: description - } - } - currentIndex: -1 model: proxyContainersModel @@ -72,6 +59,17 @@ PageType { delegate: ColumnLayout { width: listView.width + BaseHeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Installing %1").arg(name) + descriptionText: description + } + BasicButtonType { id: showDetailsButton From cf5ecf1f1ac23fe627c95ef9d51008948db74523 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 10 May 2025 12:09:57 +0200 Subject: [PATCH 05/37] fix copy on share config --- .../qml/Components/ShareConnectionDrawer.qml | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 4475875d..7797cecc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -178,9 +178,21 @@ DrawerType2 { Connections { target: copyNativeConfigStringButton function onClicked() { - nativeConfigString.selectAll() - nativeConfigString.copy() - nativeConfigString.select(0, 0) + const headerItem = configListView.headerItem; + if (!headerItem) { + console.error("Failed to copy: header item not found in ListView") + return + } + + const nativeConfigStringItem = headerItem.children.find(c => c.objectName === "nativeConfigString"); + if (!nativeConfigStringItem) { + console.error("Failed to copy: nativeConfigString item not found in ListView") + return + } + + nativeConfigStringItem.selectAll() + nativeConfigStringItem.copy() + nativeConfigStringItem.select(0, 0) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -188,11 +200,22 @@ DrawerType2 { Connections { target: copyConfigTextButton function onClicked() { - configText.selectAll() - configText.copy() - configText.select(0, 0) + const headerItem = configListView.headerItem; + if (!headerItem) { + console.error("Failed to copy: header item not found in ListView") + return + } + + const configTextItem = headerItem.children.find(c => c.objectName === "configText"); + if (!configTextItem) { + console.error("Failed to copy: configText item not found in ListView") + return + } + + configTextItem.selectAll() + configTextItem.copy() + configTextItem.select(0, 0) PageController.showNotificationMessage(qsTr("Copied")) - header.forceActiveFocus() } } @@ -232,6 +255,8 @@ DrawerType2 { TextField { id: nativeConfigString + objectName: "nativeConfigString" + visible: false text: ExportController.nativeConfigString @@ -242,6 +267,7 @@ DrawerType2 { TextArea { id: configText + objectName: "configText" Layout.fillWidth: true Layout.topMargin: 16 From d110c96b5c913d659c3960b2db9ad76414aa7a12 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 10 May 2025 12:30:43 +0200 Subject: [PATCH 06/37] fix key import --- .../ui/qml/Pages2/PageSetupWizardViewConfig.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 47d46a11..fd5a665c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -110,6 +110,7 @@ PageType { CheckBoxType { id: cloakingCheckBox + objectName: "cloakingCheckBox" visible: ImportController.isNativeWireGuardConfig() @@ -192,7 +193,19 @@ PageType { text: qsTr("Connect") clickedFunc: function() { - if (cloakingCheckBox.checked) { + const headerItem = listView.headerItem; + if (!headerItem) { + console.error("Header item not found in ListView") + return + } + + const cloakingCheckBoxItem = headerItem.children.find(c => c.objectName === "cloakingCheckBox"); + if (!cloakingCheckBoxItem) { + console.error("cloakingCheckBox not found") + return + } + + if (cloakingCheckBoxItem.checked) { ImportController.processNativeWireGuardConfig() } ImportController.importConfig() From 671d762a203c8ba66397f707a38560bafa985d48 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 10 May 2025 12:38:57 +0200 Subject: [PATCH 07/37] fix `SettingsContainersListView` --- client/ui/qml/Components/SettingsContainersListView.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 273c9f68..ccf60917 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -14,12 +14,12 @@ import "../Controls2/TextTypes" ListViewType { - id: listView + id: root anchors.fill: parent delegate: ColumnLayout { - width: listView.width + width: root.width LabelWithButtonType { Layout.fillWidth: true From 06544660a4985ed635899e24008d1aeb8d6b8545 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 10 May 2025 12:58:21 +0200 Subject: [PATCH 08/37] fix full access share --- client/ui/qml/Pages2/PageShareFullAccess.qml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 92808fbe..2513d061 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -67,6 +67,7 @@ PageType { DropDownType { id: serverSelector + objectName: "serverSelector" signal severSelectorIndexChanged property int currentIndex: 0 @@ -152,8 +153,22 @@ PageType { ExportController.generateFullAccessConfig() } - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + const headerItem = listView.headerItem; + if (!headerItem) { + PageController.showBusyIndicator(false) + console.error("Failed to share: header item not found in ListView") + return + } + + const serverSelectorItem = headerItem.children.find(c => c.objectName === "serverSelector"); + if (!serverSelectorItem) { + PageController.showBusyIndicator(false) + console.error("Failed to share: serverSelector item not found in ListView") + return + } + + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelectorItem.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelectorItem.text shareConnectionDrawer.openTriggered() From 4b202500517c8cbb54459b5f0a9917768a877e41 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 10 May 2025 15:21:12 +0200 Subject: [PATCH 09/37] remove deprecated `parentFlickable` field --- client/ui/qml/Pages2/PageSettingsConnection.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index edb0d2d4..b0520bb5 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -137,8 +137,6 @@ PageType { descriptionText: qsTr("Blocks network connections without VPN") rightImageSource: "qrc:/images/controls/chevron-right.svg" - parentFlickable: fl - clickedFunction: function() { PageController.goToPage(PageEnum.PageSettingsKillSwitch) } From 5d133aa1a6896c74389c3292a61b7682c89c6758 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 16 May 2025 18:02:50 +0200 Subject: [PATCH 10/37] fix config load on Mac --- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ++-- client/ui/qml/Controls2/ListViewType.qml | 8 ++++++++ client/ui/qml/Pages2/PageSetupWizardViewConfig.qml | 2 +- client/ui/qml/Pages2/PageShareFullAccess.qml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 7797cecc..a5a5a1f3 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -184,7 +184,7 @@ DrawerType2 { return } - const nativeConfigStringItem = headerItem.children.find(c => c.objectName === "nativeConfigString"); + const nativeConfigStringItem = configListView.findChildWithObjectName(headerItem.children, "nativeConfigString"); if (!nativeConfigStringItem) { console.error("Failed to copy: nativeConfigString item not found in ListView") return @@ -206,7 +206,7 @@ DrawerType2 { return } - const configTextItem = headerItem.children.find(c => c.objectName === "configText"); + const configTextItem = configListView.findChildWithObjectName(headerItem.children, "configText"); if (!configTextItem) { console.error("Failed to copy: configText item not found in ListView") return diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index e15f4a39..8d067537 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -10,4 +10,12 @@ ListView { clip: true reuseItems: true + + function findChildWithObjectName(items, name) { + for (var i = 0; i < items.length; ++i) { + if (items[i].objectName === name) + return items[i]; + } + return null; + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index fd5a665c..ecd104a2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -199,7 +199,7 @@ PageType { return } - const cloakingCheckBoxItem = headerItem.children.find(c => c.objectName === "cloakingCheckBox"); + const cloakingCheckBoxItem = listView.findChildWithObjectName(headerItem.children, "cloakingCheckBox"); if (!cloakingCheckBoxItem) { console.error("cloakingCheckBox not found") return diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 2513d061..1912dd54 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -160,7 +160,7 @@ PageType { return } - const serverSelectorItem = headerItem.children.find(c => c.objectName === "serverSelector"); + const serverSelectorItem = listView.findChildWithObjectName(headerItem.children, "serverSelector"); if (!serverSelectorItem) { PageController.showBusyIndicator(false) console.error("Failed to share: serverSelector item not found in ListView") From 0298371a52952fe144fcd0f94237e89a232fb8a4 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 16 May 2025 22:23:10 +0200 Subject: [PATCH 11/37] fix config string view --- client/ui/qml/Components/ShareConnectionDrawer.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index a5a5a1f3..1682dab3 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -245,6 +245,7 @@ DrawerType2 { Header2Type { id: configContentHeader + Layout.fillWidth: true Layout.topMargin: 16 Layout.leftMargin: 16 @@ -257,6 +258,9 @@ DrawerType2 { id: nativeConfigString objectName: "nativeConfigString" + Layout.leftMargin: 16 + Layout.rightMargin: 16 + visible: false text: ExportController.nativeConfigString @@ -272,6 +276,8 @@ DrawerType2 { Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 padding: 0 leftPadding: 0 From b8d49abdd16e33dd6c2e20e8f9dfb9c352910c85 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sat, 17 May 2025 18:57:47 +0200 Subject: [PATCH 12/37] fix: update server access property assignment and import sites method calls --- client/ui/qml/Pages2/PageSettingsServerData.qml | 2 +- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 2066ba53..05e27211 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -59,7 +59,7 @@ PageType { target: ServersModel function onProcessedServerIndexChanged() { - content.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess() + listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess() } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index eb6a8cc1..3e81e7f4 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -422,7 +422,7 @@ PageType { var fileName = SystemController.getFileName(qsTr("Open sites file"), qsTr("Sites files (*.json)")) if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, true) + root.importSites(fileName, true) } } } @@ -435,7 +435,7 @@ PageType { var fileName = SystemController.getFileName(qsTr("Open sites file"), qsTr("Sites files (*.json)")) if (fileName !== "") { - importSitesDrawerContent.importSites(fileName, false) + root.importSites(fileName, false) } } } From 117c66cd7d0ab9f4a3d117e0981c7b071b297361 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 20 May 2025 21:22:03 +0200 Subject: [PATCH 13/37] fix margins in server info managment tab --- client/ui/qml/Pages2/PageSettingsServerData.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 05e27211..1a496b5b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -77,8 +77,6 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 visible: isVisible From ad32b68d8436716a2e34035e37587b95fbc12755 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Tue, 20 May 2025 21:56:50 +0200 Subject: [PATCH 14/37] fix layout --- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 ++ client/ui/qml/Pages2/PageSettingsServerProtocol.qml | 8 -------- client/ui/qml/Pages2/PageSetupWizardViewConfig.qml | 10 ++++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 4360e1ce..b90af6f6 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -137,6 +137,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 padding: 0 height: 24 diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 88198913..a29b97bc 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -65,8 +65,6 @@ PageType { id: clientSettings Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 text: protocolName + qsTr(" connection settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -99,8 +97,6 @@ PageType { id: serverSettings Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 text: protocolName + qsTr(" server settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -141,8 +137,6 @@ PageType { id: clearCacheButton Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 visible: root.isClearCacheVisible @@ -187,8 +181,6 @@ PageType { id: removeButton Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 visible: ServersModel.isProcessedServerHasWriteAccess() diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index ecd104a2..1fe741a6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -153,13 +153,10 @@ PageType { iconPath: "qrc:/images/controls/alert-circle.svg" } - } - - footer: ColumnLayout { - width: listView.width Rectangle { Layout.fillWidth: true + Layout.topMargin: 16 Layout.bottomMargin: 48 Layout.rightMargin: 16 Layout.leftMargin: 16 @@ -175,12 +172,17 @@ PageType { id: configContent anchors.fill: parent + anchors.margins: 16 wrapMode: Text.Wrap text: ImportController.getConfig() } } + } + + footer: ColumnLayout { + width: listView.width BasicButtonType { id: connectButton From b1627887eb4a9c0512ffb832465cd17b95e62f6d Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 13 Jun 2025 00:45:53 +0200 Subject: [PATCH 15/37] fix selection on `EasyWizardSetupPage` --- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index ca5861bc..72178c87 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -103,6 +103,7 @@ PageType { ButtonGroup.group: buttonGroup onClicked: function() { + checked = true isEasySetup = true var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) @@ -110,6 +111,9 @@ PageType { listView.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) listView.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) } + + Keys.onReturnPressed: this.clicked() + Keys.onEnterPressed: this.clicked() } } @@ -136,7 +140,11 @@ PageType { onClicked: function() { isEasySetup = false + checked = true } + + Keys.onReturnPressed: this.clicked() + Keys.onEnterPressed: this.clicked() } BasicButtonType { From 00f49dbc82a66c4c2f40c6a6f4f4e542e8963b57 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Fri, 13 Jun 2025 19:06:32 +0200 Subject: [PATCH 16/37] fix xray settings layout --- client/ui/qml/Pages2/PageProtocolXraySettings.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index a7174ccb..6a72b774 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -82,6 +82,8 @@ PageType { id: portTextField Layout.fillWidth: true Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 enabled: delegateItem.isEnabled From 727526b39a524946bf981b8c7f65b8d78dcf367e Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Sun, 15 Jun 2025 18:57:44 +0200 Subject: [PATCH 17/37] add focus to `CheckBoxType` --- client/ui/qml/Controls2/CheckBoxType.qml | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 9c89986f..451fe01d 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -34,6 +34,32 @@ CheckBox { property string imageSource: "qrc:/images/controls/check.svg" + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + hoverEnabled: enabled ? true : false focusPolicy: Qt.NoFocus From 0bc762ccbfdac9416e3a53756ef2ccc943ae0de5 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Jun 2025 01:07:54 +0200 Subject: [PATCH 18/37] update positioning in `ListViewFocusController` --- client/ui/controllers/listViewFocusController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/controllers/listViewFocusController.cpp b/client/ui/controllers/listViewFocusController.cpp index 331b4748..701d0c24 100644 --- a/client/ui/controllers/listViewFocusController.cpp +++ b/client/ui/controllers/listViewFocusController.cpp @@ -37,7 +37,7 @@ void ListViewFocusController::viewAtCurrentIndex() const } case Section::Delegate: { QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index - Q_ARG(int, 2)); // PositionMode (0 = Visible) + Q_ARG(int, 6)); // PositionMode (0 = Beginning; 1 = Center; 2 = End; 3 = Visible; 4 = Contain; 5 = SnapPosition) break; } case Section::Footer: { From 3393b9739282ffcd83aeb83b855d51867717d14a Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Jun 2025 01:08:53 +0200 Subject: [PATCH 19/37] refactor `OpenVpnConfigModel` --- .../models/protocols/openvpnConfigModel.cpp | 183 ++++++++++-------- .../ui/models/protocols/openvpnConfigModel.h | 97 +++++++--- 2 files changed, 177 insertions(+), 103 deletions(-) diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp index a04c2b1a..6c28de4d 100644 --- a/client/ui/models/protocols/openvpnConfigModel.cpp +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -2,72 +2,128 @@ #include "protocols/protocols_defs.h" -OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) : QAbstractListModel(parent) +OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) + : QObject(parent) { } -int OpenVpnConfigModel::rowCount(const QModelIndex &parent) const +QString OpenVpnConfigModel::subnetAddress() const { - Q_UNUSED(parent); - return 1; + return m_protocolConfig.value(amnezia::config_key::subnet_address).toString(amnezia::protocols::openvpn::defaultSubnetAddress); } -bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +void OpenVpnConfigModel::setSubnetAddress(const QString &subnetAddress) { - if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { - return false; - } - - switch (role) { - case Roles::SubnetAddressRole: m_protocolConfig.insert(amnezia::config_key::subnet_address, value.toString()); break; - case Roles::TransportProtoRole: m_protocolConfig.insert(config_key::transport_proto, value.toString()); break; - case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.insert(config_key::ncp_disable, !value.toBool()); break; - case Roles::HashRole: m_protocolConfig.insert(config_key::hash, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; - case Roles::TlsAuthRole: m_protocolConfig.insert(config_key::tls_auth, value.toBool()); break; - case Roles::BlockDnsRole: m_protocolConfig.insert(config_key::block_outside_dns, value.toBool()); break; - case Roles::AdditionalClientCommandsRole: m_protocolConfig.insert(config_key::additional_client_config, value.toString()); break; - case Roles::AdditionalServerCommandsRole: m_protocolConfig.insert(config_key::additional_server_config, value.toString()); break; - } - - emit dataChanged(index, index, QList { role }); - return true; + m_protocolConfig.insert(amnezia::config_key::subnet_address, subnetAddress); } -QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const +QString OpenVpnConfigModel::transportProto() const { - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { - return false; - } + return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); +} - switch (role) { - case Roles::SubnetAddressRole: - return m_protocolConfig.value(amnezia::config_key::subnet_address).toString(amnezia::protocols::openvpn::defaultSubnetAddress); - case Roles::TransportProtoRole: - return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); - case Roles::AutoNegotiateEncryprionRole: - return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - case Roles::HashRole: return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); - case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); - case Roles::TlsAuthRole: return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - case Roles::BlockDnsRole: - return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); - case Roles::AdditionalClientCommandsRole: - return m_protocolConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig); - case Roles::AdditionalServerCommandsRole: - return m_protocolConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig); - case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false; - case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false; - case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false; - } - return QVariant(); +void OpenVpnConfigModel::setTransportProto(const QString &transportProto) +{ + m_protocolConfig.insert(config_key::transport_proto, transportProto); +} + +QString OpenVpnConfigModel::port() const +{ + return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); +} + +void OpenVpnConfigModel::setPort(const QString &port) +{ + m_protocolConfig.insert(config_key::port, port); +} + +bool OpenVpnConfigModel::autoNegotiateEncryption() const +{ + return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); +} + +void OpenVpnConfigModel::setAutoNegotiateEncryption(bool enabled) +{ + m_protocolConfig.insert(config_key::ncp_disable, !enabled); +} + +QString OpenVpnConfigModel::hash() const +{ + return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); +} + +void OpenVpnConfigModel::setHash(const QString &hash) +{ + m_protocolConfig.insert(config_key::hash, hash); +} + +QString OpenVpnConfigModel::cipher() const +{ + return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); +} + +void OpenVpnConfigModel::setCipher(const QString &cipher) +{ + m_protocolConfig.insert(config_key::cipher, cipher); +} + +bool OpenVpnConfigModel::tlsAuth() const +{ + return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); +} + +void OpenVpnConfigModel::setTlsAuth(bool enabled) +{ + m_protocolConfig.insert(config_key::tls_auth, enabled); +} + +bool OpenVpnConfigModel::blockDns() const +{ + return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); +} + +void OpenVpnConfigModel::setBlockDns(bool enabled) +{ + m_protocolConfig.insert(config_key::block_outside_dns, enabled); +} + +QString OpenVpnConfigModel::additionalClientCommands() const +{ + return m_protocolConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig); +} + +void OpenVpnConfigModel::setAdditionalClientCommands(const QString &commands) +{ + m_protocolConfig.insert(config_key::additional_client_config, commands); +} + +QString OpenVpnConfigModel::additionalServerCommands() const +{ + return m_protocolConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig); +} + +void OpenVpnConfigModel::setAdditionalServerCommands(const QString &commands) +{ + m_protocolConfig.insert(config_key::additional_server_config, commands); +} + +bool OpenVpnConfigModel::isPortEditable() const +{ + return m_container == DockerContainer::OpenVpn; +} + +bool OpenVpnConfigModel::isTransportProtoEditable() const +{ + return m_container == DockerContainer::OpenVpn; +} + +bool OpenVpnConfigModel::hasRemoveButton() const +{ + return m_container == DockerContainer::OpenVpn; } void OpenVpnConfigModel::updateModel(const QJsonObject &config) { - beginResetModel(); m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); m_fullConfig = config; @@ -100,8 +156,6 @@ void OpenVpnConfigModel::updateModel(const QJsonObject &config) m_protocolConfig.insert( config_key::additional_server_config, protocolConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig)); - - endResetModel(); } QJsonObject OpenVpnConfigModel::getConfig() @@ -109,26 +163,3 @@ QJsonObject OpenVpnConfigModel::getConfig() m_fullConfig.insert(config_key::openvpn, m_protocolConfig); return m_fullConfig; } - -QHash OpenVpnConfigModel::roleNames() const -{ - QHash roles; - - roles[SubnetAddressRole] = "subnetAddress"; - roles[TransportProtoRole] = "transportProto"; - roles[PortRole] = "port"; - roles[AutoNegotiateEncryprionRole] = "autoNegotiateEncryprion"; - roles[HashRole] = "hash"; - roles[CipherRole] = "cipher"; - roles[TlsAuthRole] = "tlsAuth"; - roles[BlockDnsRole] = "blockDns"; - roles[AdditionalClientCommandsRole] = "additionalClientCommands"; - roles[AdditionalServerCommandsRole] = "additionalServerCommands"; - - roles[IsPortEditable] = "isPortEditable"; - roles[IsTransportProtoEditable] = "isTransportProtoEditable"; - - roles[HasRemoveButton] = "hasRemoveButton"; - - return roles; -} diff --git a/client/ui/models/protocols/openvpnConfigModel.h b/client/ui/models/protocols/openvpnConfigModel.h index 0357700c..e50ee4a3 100644 --- a/client/ui/models/protocols/openvpnConfigModel.h +++ b/client/ui/models/protocols/openvpnConfigModel.h @@ -1,47 +1,90 @@ #ifndef OPENVPNCONFIGMODEL_H #define OPENVPNCONFIGMODEL_H -#include +#include #include #include "containers/containers_defs.h" -class OpenVpnConfigModel : public QAbstractListModel +class OpenVpnConfigModel : public QObject { Q_OBJECT + Q_PROPERTY(QString subnetAddress READ subnetAddress WRITE setSubnetAddress NOTIFY subnetAddressChanged) + Q_PROPERTY(QString transportProto READ transportProto WRITE setTransportProto NOTIFY transportProtoChanged) + Q_PROPERTY(QString port READ port WRITE setPort NOTIFY portChanged) + Q_PROPERTY(bool autoNegotiateEncryption READ autoNegotiateEncryption WRITE setAutoNegotiateEncryption NOTIFY autoNegotiateEncryptionChanged) + Q_PROPERTY(QString hash READ hash WRITE setHash NOTIFY hashChanged) + Q_PROPERTY(QString cipher READ cipher WRITE setCipher NOTIFY cipherChanged) + Q_PROPERTY(bool tlsAuth READ tlsAuth WRITE setTlsAuth NOTIFY tlsAuthChanged) + Q_PROPERTY(bool blockDns READ blockDns WRITE setBlockDns NOTIFY blockDnsChanged) + Q_PROPERTY(QString additionalClientCommands READ additionalClientCommands WRITE setAdditionalClientCommands NOTIFY additionalClientCommandsChanged) + Q_PROPERTY(QString additionalServerCommands READ additionalServerCommands WRITE setAdditionalServerCommands NOTIFY additionalServerCommandsChanged) + Q_PROPERTY(bool isPortEditable READ isPortEditable NOTIFY isPortEditableChanged) + Q_PROPERTY(bool isTransportProtoEditable READ isTransportProtoEditable NOTIFY isTransportProtoEditableChanged) + Q_PROPERTY(bool hasRemoveButton READ hasRemoveButton NOTIFY hasRemoveButtonChanged) public: - enum Roles { - SubnetAddressRole = Qt::UserRole + 1, - TransportProtoRole, - PortRole, - AutoNegotiateEncryprionRole, - HashRole, - CipherRole, - TlsAuthRole, - BlockDnsRole, - AdditionalClientCommandsRole, - AdditionalServerCommandsRole, - - IsPortEditable, - IsTransportProtoEditable, - - HasRemoveButton - }; - explicit OpenVpnConfigModel(QObject *parent = nullptr); + ~OpenVpnConfigModel() override = default; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + OpenVpnConfigModel(const OpenVpnConfigModel &) = delete; + OpenVpnConfigModel &operator=(const OpenVpnConfigModel &) = delete; + OpenVpnConfigModel(OpenVpnConfigModel &&) = delete; + OpenVpnConfigModel &operator=(OpenVpnConfigModel &&) = delete; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QString subnetAddress() const; + void setSubnetAddress(const QString &subnetAddress); + + QString transportProto() const; + void setTransportProto(const QString &transportProto); + + QString port() const; + void setPort(const QString &port); + + bool autoNegotiateEncryption() const; + void setAutoNegotiateEncryption(bool enabled); + + QString hash() const; + void setHash(const QString &hash); + + QString cipher() const; + void setCipher(const QString &cipher); + + bool tlsAuth() const; + void setTlsAuth(bool enabled); + + bool blockDns() const; + void setBlockDns(bool enabled); + + QString additionalClientCommands() const; + void setAdditionalClientCommands(const QString &commands); + + QString additionalServerCommands() const; + void setAdditionalServerCommands(const QString &commands); + + bool isPortEditable() const; + bool isTransportProtoEditable() const; + bool hasRemoveButton() const; + + Q_INVOKABLE QJsonObject getConfig(); + +signals: + void subnetAddressChanged(const QString &); + void transportProtoChanged(const QString &); + void portChanged(const QString &); + void autoNegotiateEncryptionChanged(bool); + void hashChanged(const QString &); + void cipherChanged(const QString &); + void tlsAuthChanged(bool); + void blockDnsChanged(bool); + void additionalClientCommandsChanged(const QString &); + void additionalServerCommandsChanged(const QString &); + void isPortEditableChanged(bool); + void isTransportProtoEditableChanged(bool); + void hasRemoveButtonChanged(bool); public slots: void updateModel(const QJsonObject &config); - QJsonObject getConfig(); - -protected: - QHash roleNames() const override; private: DockerContainer m_container; From 09e3f3912228954b9d856df3e39145aec910c031 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Jun 2025 01:09:19 +0200 Subject: [PATCH 20/37] make `TextAreaType` focusable --- client/ui/qml/Controls2/TextAreaType.qml | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 800f9000..7b6721dd 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -45,6 +45,32 @@ Rectangle { anchors.topMargin: 16 anchors.bottomMargin: 16 + property bool isFocusable: true + + Keys.onTabPressed: { + FocusController.nextKeyTabItem() + } + + Keys.onBacktabPressed: { + FocusController.previousKeyTabItem() + } + + Keys.onUpPressed: { + FocusController.nextKeyUpItem() + } + + Keys.onDownPressed: { + FocusController.nextKeyDownItem() + } + + Keys.onLeftPressed: { + FocusController.nextKeyLeftItem() + } + + Keys.onRightPressed: { + FocusController.nextKeyRightItem() + } + color: AmneziaStyle.color.paleGray selectionColor: AmneziaStyle.color.richBrown selectedTextColor: AmneziaStyle.color.paleGray From 4a8877c2ed8967a749cc5244d5bbc98a5fc9a474 Mon Sep 17 00:00:00 2001 From: Cyril Anisimov Date: Mon, 16 Jun 2025 01:10:21 +0200 Subject: [PATCH 21/37] refactor `OpenVpnSettings` page for correct focus --- .../Pages2/PageProtocolOpenVpnSettings.qml | 639 ++++++++++-------- 1 file changed, 362 insertions(+), 277 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index b8565d72..5c9a78e4 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -42,15 +42,9 @@ PageType { enabled: ServersModel.isProcessedServerHasWriteAccess() - model: OpenVpnConfigModel - - delegate: ColumnLayout { + header: ColumnLayout { width: listView.width - property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField - - spacing: 0 - BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 @@ -58,318 +52,409 @@ PageType { headerText: qsTr("OpenVPN settings") } + } - TextFieldWithHeaderType { - id: vpnAddressSubnetTextField + model: ListModel { + ListElement { type: "subnetHeader" } + ListElement { type: "networkProtocolText" } + ListElement { type: "protoSelector" } + ListElement { type: "portTextField" } + ListElement { type: "encryptionSection" } + ListElement { type: "checkboxSection" } + ListElement { type: "clientCommands" } + ListElement { type: "serverCommands" } + } - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + delegate: DelegateChooser { - headerText: qsTr("VPN address subnet") - textField.text: subnetAddress - - textField.onEditingFinished: { - if (textField.text !== subnetAddress) { - subnetAddress = textField.text - } - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("Network protocol") - } - - TransportProtoSelector { - id: transportProtoSelector - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - rootWidth: root.width - - enabled: isTransportProtoEditable - - currentIndex: { - return transportProto === "tcp" ? 1 : 0 - } - - onCurrentIndexChanged: { - if (transportProto === "tcp" && currentIndex === 0) { - transportProto = "udp" - } else if (transportProto === "udp" && currentIndex === 1) { - transportProto = "tcp" - } - } - } - - TextFieldWithHeaderType { - id: portTextField - - Layout.fillWidth: true - Layout.topMargin: 40 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: isPortEditable - - headerText: qsTr("Port") - textField.text: port - textField.maximumLength: 5 - textField.validator: IntValidator { bottom: 1; top: 65535 } - - textField.onEditingFinished: { - if (textField.text !== port) { - port = textField.text - } - } - } - - SwitcherType { - id: autoNegotiateEncryprionSwitcher - - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("Auto-negotiate encryption") - checked: autoNegotiateEncryprion - - onCheckedChanged: { - if (checked !== autoNegotiateEncryprion) { - autoNegotiateEncryprion = checked - } - } - } - - DropDownType { - id: hashDropDown - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: !autoNegotiateEncryprionSwitcher.checked - - descriptionText: qsTr("Hash") - headerText: qsTr("Hash") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - id: hashListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("SHA512") } - ListElement { name : qsTr("SHA384") } - ListElement { name : qsTr("SHA256") } - ListElement { name : qsTr("SHA3-512") } - ListElement { name : qsTr("SHA3-384") } - ListElement { name : qsTr("SHA3-256") } - ListElement { name : qsTr("whirlpool") } - ListElement { name : qsTr("BLAKE2b512") } - ListElement { name : qsTr("BLAKE2s256") } - ListElement { name : qsTr("SHA1") } - } - - clickedFunction: function() { - hashDropDown.text = selectedText - hash = hashDropDown.text - hashDropDown.closeTriggered() - } - - Component.onCompleted: { - hashDropDown.text = hash - - for (var i = 0; i < hashListView.model.count; i++) { - if (hashListView.model.get(i).name === hashDropDown.text) { - currentIndex = i - } - } - } - } - } - - DropDownType { - id: cipherDropDown - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - enabled: !autoNegotiateEncryprionSwitcher.checked - - descriptionText: qsTr("Cipher") - headerText: qsTr("Cipher") - - drawerParent: root - - listView: ListViewWithRadioButtonType { - id: cipherListView - - rootWidth: root.width - - model: ListModel { - ListElement { name : qsTr("AES-256-GCM") } - ListElement { name : qsTr("AES-192-GCM") } - ListElement { name : qsTr("AES-128-GCM") } - ListElement { name : qsTr("AES-256-CBC") } - ListElement { name : qsTr("AES-192-CBC") } - ListElement { name : qsTr("AES-128-CBC") } - ListElement { name : qsTr("ChaCha20-Poly1305") } - ListElement { name : qsTr("ARIA-256-CBC") } - ListElement { name : qsTr("CAMELLIA-256-CBC") } - ListElement { name : qsTr("none") } - } - - clickedFunction: function() { - cipherDropDown.text = selectedText - cipher = cipherDropDown.text - cipherDropDown.closeTriggered() - } - - Component.onCompleted: { - cipherDropDown.text = cipher - - for (var i = 0; i < cipherListView.model.count; i++) { - if (cipherListView.model.get(i).name === cipherDropDown.text) { - currentIndex = i - } - } - } - } - } - - Rectangle { - id: contentRect - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.preferredHeight: checkboxLayout.implicitHeight - color: AmneziaStyle.color.onyxBlack - radius: 16 + role: "type" + DelegateChoice { + // property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField + roleValue: "subnetHeader" ColumnLayout { - id: checkboxLayout + width: listView.width + + TextFieldWithHeaderType { + id: vpnAddressSubnetTextField - anchors.fill: parent - CheckBoxType { - id: tlsAuthCheckBox Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - text: qsTr("TLS auth") - checked: tlsAuth + headerText: qsTr("VPN address subnet") + textField.text: OpenVpnConfigModel.subnetAddress - onCheckedChanged: { - if (checked !== tlsAuth) { - console.log("tlsAuth changed to: " + checked) - tlsAuth = checked - } - } - } - - DividerType {} - - CheckBoxType { - id: blockDnsCheckBox - Layout.fillWidth: true - - text: qsTr("Block DNS requests outside of VPN") - checked: blockDns - - onCheckedChanged: { - if (checked !== blockDns) { - blockDns = checked + textField.onEditingFinished: { + if (textField.text !== OpenVpnConfigModel.subnetAddress) { + OpenVpnConfigModel.subnetAddress = textField.text } } } } } - SwitcherType { - id: additionalClientCommandsSwitcher - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + DelegateChoice { + roleValue: "networkProtocolText" + ColumnLayout { + width: listView.width - checked: additionalClientCommands !== "" + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - text: qsTr("Additional client configuration commands") - - onCheckedChanged: { - if (!checked) { - additionalClientCommands = "" + text: qsTr("Network protocol") } } } - TextAreaType { - id: additionalClientCommandsTextArea - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + DelegateChoice { + roleValue: "protoSelector" + ColumnLayout { + width: listView.width - visible: additionalClientCommandsSwitcher.checked + TransportProtoSelector { + id: transportProtoSelector - textAreaText: additionalClientCommands - placeholderText: qsTr("Commands:") + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + rootWidth: root.width - textArea.onEditingFinished: { - if (additionalClientCommands !== textAreaText) { - additionalClientCommands = textAreaText + enabled: OpenVpnConfigModel.isTransportProtoEditable + + currentIndex: { + return OpenVpnConfigModel.transportProto === "tcp" ? 1 : 0 + } + + onCurrentIndexChanged: { + if (OpenVpnConfigModel.transportProto === "tcp" && currentIndex === 0) { + OpenVpnConfigModel.transportProto = "udp" + } else if (OpenVpnConfigModel.transportProto === "udp" && currentIndex === 1) { + OpenVpnConfigModel.transportProto = "tcp" + } + } } } } - SwitcherType { - id: additionalServerCommandsSwitcher - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + DelegateChoice { + roleValue: "portTextField" + ColumnLayout { + width: listView.width - checked: additionalServerCommands !== "" + TextFieldWithHeaderType { + id: portTextField - text: qsTr("Additional server configuration commands") + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - onCheckedChanged: { - if (!checked) { - additionalServerCommands = "" + enabled: OpenVpnConfigModel.isPortEditable + + headerText: qsTr("Port") + textField.text: OpenVpnConfigModel.port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textField.text !== OpenVpnConfigModel.port) { + OpenVpnConfigModel.port = textField.text + } + } } } } - TextAreaType { - id: additionalServerCommandsTextArea - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + DelegateChoice { + roleValue: "encryptionSection" + ColumnLayout { + width: listView.width - visible: additionalServerCommandsSwitcher.checked + SwitcherType { + id: autoNegotiateEncryprionSwitcher - textAreaText: additionalServerCommands - placeholderText: qsTr("Commands:") + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 - textArea.onEditingFinished: { - if (additionalServerCommands !== textAreaText) { - additionalServerCommands = textAreaText + text: qsTr("Auto-negotiate encryption") + checked: OpenVpnConfigModel.autoNegotiateEncryption + + onCheckedChanged: { + if (checked !== OpenVpnConfigModel.autoNegotiateEncryprion) { + OpenVpnConfigModel.autoNegotiateEncryprion = checked + } + } + } + + DropDownType { + id: hashDropDown + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + drawerParent: root + + listView: ListViewWithRadioButtonType { + id: hashListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("SHA512") } + ListElement { name : qsTr("SHA384") } + ListElement { name : qsTr("SHA256") } + ListElement { name : qsTr("SHA3-512") } + ListElement { name : qsTr("SHA3-384") } + ListElement { name : qsTr("SHA3-256") } + ListElement { name : qsTr("whirlpool") } + ListElement { name : qsTr("BLAKE2b512") } + ListElement { name : qsTr("BLAKE2s256") } + ListElement { name : qsTr("SHA1") } + } + + clickedFunction: function() { + hashDropDown.text = selectedText + OpenVpnConfigModel.hash = hashDropDown.text + hashDropDown.closeTriggered() + } + + Component.onCompleted: { + hashDropDown.text = OpenVpnConfigModel.hash + + for (var i = 0; i < hashListView.model.count; i++) { + if (hashListView.model.get(i).name === hashDropDown.text) { + currentIndex = i + } + } + } + } + } + + DropDownType { + id: cipherDropDown + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + drawerParent: root + + listView: ListViewWithRadioButtonType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : qsTr("AES-256-GCM") } + ListElement { name : qsTr("AES-192-GCM") } + ListElement { name : qsTr("AES-128-GCM") } + ListElement { name : qsTr("AES-256-CBC") } + ListElement { name : qsTr("AES-192-CBC") } + ListElement { name : qsTr("AES-128-CBC") } + ListElement { name : qsTr("ChaCha20-Poly1305") } + ListElement { name : qsTr("ARIA-256-CBC") } + ListElement { name : qsTr("CAMELLIA-256-CBC") } + ListElement { name : qsTr("none") } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + OpenVpnConfigModel.cipher = cipherDropDown.text + cipherDropDown.closeTriggered() + } + + Component.onCompleted: { + cipherDropDown.text = OpenVpnConfigModel.cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } } } } + DelegateChoice { + roleValue: "checkboxSection" + ColumnLayout { + width: listView.width + + Rectangle { + id: contentRect + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.preferredHeight: checkboxLayout.implicitHeight + color: AmneziaStyle.color.onyxBlack + radius: 16 + + ColumnLayout { + id: checkboxLayout + + anchors.fill: parent + + CheckBoxType { + id: tlsAuthCheckBox + + Layout.fillWidth: true + + text: qsTr("TLS auth") + checked: OpenVpnConfigModel.tlsAuth + + onCheckedChanged: { + if (checked !== OpenVpnConfigModel.tlsAuth) { + console.log("tlsAuth changed to: " + checked) + OpenVpnConfigModel.tlsAuth = checked + } + } + } + + DividerType {} + + CheckBoxType { + id: blockDnsCheckBox + + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + checked: OpenVpnConfigModel.blockDns + + onCheckedChanged: { + if (checked !== OpenVpnConfigModel.blockDns) { + OpenVpnConfigModel.blockDns = checked + } + } + } + } + } + } + } + + DelegateChoice { + roleValue: "clientCommands" + ColumnLayout { + + width: listView.width + + SwitcherType { + id: additionalClientCommandsSwitcher + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + checked: OpenVpnConfigModel.additionalClientCommands !== "" + + text: qsTr("Additional client configuration commands") + + onCheckedChanged: { + if (!checked) { + OpenVpnConfigModel.additionalClientCommands = "" + } + // listView.positionViewAtIndex(index, ListView.Beginning) + } + } + + TextAreaType { + id: additionalClientCommandsTextArea + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: additionalClientCommandsSwitcher.checked + + textAreaText: OpenVpnConfigModel.additionalClientCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (OpenVpnConfigModel.additionalClientCommands !== textAreaText) { + OpenVpnConfigModel.additionalClientCommands = textAreaText + } + } + } + } + } + + DelegateChoice { + roleValue: "serverCommands" + ColumnLayout { + width: listView.width + + SwitcherType { + id: additionalServerCommandsSwitcher + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + checked: OpenVpnConfigModel.additionalServerCommands !== "" + + text: qsTr("Additional server configuration commands") + + onCheckedChanged: { + if (!checked) { + OpenVpnConfigModel.additionalServerCommands = "" + } + // listView.positionViewAtIndex(index, ListView.Beginning) + } + } + + TextAreaType { + id: additionalServerCommandsTextArea + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: additionalServerCommandsSwitcher.checked + + textAreaText: OpenVpnConfigModel.additionalServerCommands + placeholderText: qsTr("Commands:") + + textArea.onEditingFinished: { + if (OpenVpnConfigModel.additionalServerCommands !== textAreaText) { + OpenVpnConfigModel.additionalServerCommands = textAreaText + } + } + } + } + } + } + + footer: ColumnLayout { + width: listView.width + BasicButtonType { id: saveRestartButton From 26059788890ed25ab10f6ae01a743b9a37926e69 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 17 Jun 2025 05:00:41 -0700 Subject: [PATCH 22/37] fix: allow internet traffic for strict mode with split tunnel (#1654) --- client/platforms/windows/daemon/wireguardutilswindows.cpp | 1 + client/protocols/openvpnprotocol.cpp | 2 +- client/protocols/xrayprotocol.cpp | 2 +- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 7 ++----- service/server/killswitch.cpp | 3 +++ 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index d01ef54a..a5c9c84d 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -130,6 +130,7 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) { // Enable the windows firewall NET_IFINDEX ifindex; ConvertInterfaceLuidToIndex(&luid, &ifindex); + m_firewall->allowAllTraffic(); m_firewall->enableInterface(ifindex); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 429b85a6..0bbdbd07 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -343,7 +343,7 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) // killSwitch toggle if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); + IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); } m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index faad8e94..9f26d1e6 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -134,7 +134,7 @@ ErrorCode XrayProtocol::startTun2Sock() // killSwitch toggle if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); + IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); } m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 1ffcc8cf..444eb415 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -81,8 +81,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: false - enabled: false //SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") @@ -104,9 +103,7 @@ PageType { } } - DividerType { - visible: false - } + DividerType {} LabelWithButtonType { Layout.topMargin: 32 diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index c44bd6a2..447be865 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -255,6 +255,9 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN + if (configStr.value("splitTunnelType").toInt() != 0) { + WindowsFirewall::create(this)->allowAllTraffic(); + } return WindowsFirewall::create(this)->enableInterface(vpnAdapterIndex); #endif From e152e84ddc949c17754509bcf5b2ddd2a4ebdcf3 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 23 Jun 2025 06:32:56 +0400 Subject: [PATCH 23/37] feat: docker pull rate limit check (#1657) * Docker pull rate limit * Error code for DockerPullRateLimit * Extended description Error 213 Extended description for the error 213: Docker Pull Rate Limit * empty line removed --- client/core/controllers/serverController.cpp | 2 ++ client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 8ff6b6c8..f86e2865 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -460,6 +460,8 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden return ErrorCode::ServerDockerOnCgroupsV2; if (stdOut.contains("cgroup mountpoint does not exist")) return ErrorCode::ServerCgroupMountpoint; + if (stdOut.contains("have reached") && stdOut.contains("pull rate limit")) + return ErrorCode::DockerPullRateLimit; return error; } diff --git a/client/core/defs.h b/client/core/defs.h index df6a1342..64f52ce6 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -60,6 +60,7 @@ namespace amnezia ServerUserPasswordRequired = 210, ServerDockerOnCgroupsV2 = 211, ServerCgroupMountpoint = 212, + DockerPullRateLimit = 213, // Ssh connection errors SshRequestDeniedError = 300, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 7cc46220..bd5ccaba 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -28,6 +28,7 @@ QString errorString(ErrorCode code) { case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break; case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break; case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break; + case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break; // Libssh errors case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; From 979ab42c5a424ccffb0fa8b843b9fcc517236f9d Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 23 Jun 2025 06:34:40 +0400 Subject: [PATCH 24/37] feat: OpenSUSE support (#1557) * LOCK_FILE for zypper Checking LOCK_FILE for zypper to support OpenSUSE * Installation for OpenSUSE Docker installation support for OpenSUSE * quiet for zypper * LOCK_CMD variable Implementing the LOCK_CMD variable for different OS. * additional exception for "server is busy" * Replacing and with or Replacing && with || * undo changes to serverController * rpm.lock rpm.lock for dnf yum and zypper * LOCK_CMD check for dnf * Added zypper in check_user_in_sudo --- client/core/controllers/serverController.cpp | 2 +- client/server_scripts/check_server_is_busy.sh | 11 ++++++----- client/server_scripts/check_user_in_sudo.sh | 1 + client/server_scripts/install_docker.sh | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index f86e2865..a61a638b 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -827,7 +827,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential if (stdOut.contains("Packet manager not found")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed")) + if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed")) return ErrorCode::NoError; if (stdOut.isEmpty()) { diff --git a/client/server_scripts/check_server_is_busy.sh b/client/server_scripts/check_server_is_busy.sh index 4e6a2c26..feddfed3 100644 --- a/client/server_scripts/check_server_is_busy.sh +++ b/client/server_scripts/check_server_is_busy.sh @@ -1,6 +1,7 @@ -if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\ -elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\ -elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\ -elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\ +if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\ +elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\ +elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\ +elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\ +elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\ else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\ -if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi +if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 685e6a18..f83f2fd7 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,6 +1,7 @@ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\ +elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\ else pm="uname"; opt="-a";\ fi;\ diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index 619b08d6..1e41bb5a 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -1,6 +1,7 @@ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ +elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\ else echo "Packet manager not found"; exit 1; fi;\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ From f0626e2ecabf1115d264834beadc59de35a6559e Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Wed, 2 Jul 2025 06:07:56 +0400 Subject: [PATCH 25/37] fix: delete premium V2 migration link from Free config Settings (#1671) * delete premium V2 update link from Free config Settings * Add debug logs * Add property for checking if server config is premium * remove debug logs --- client/ui/models/servers_model.cpp | 9 ++++++++- client/ui/models/servers_model.h | 5 ++++- client/ui/qml/Pages2/PageSettingsServerData.qml | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 5a70c16f..22813312 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -8,6 +8,8 @@ #include #endif +#include "core/api/apiUtils.h" + namespace { namespace configKey @@ -427,7 +429,7 @@ void ServersModel::updateDefaultServerContainersModel() emit defaultServerContainersUpdated(containers); } -QJsonObject ServersModel::getServerConfig(const int serverIndex) +QJsonObject ServersModel::getServerConfig(const int serverIndex) const { return m_servers.at(serverIndex).toObject(); } @@ -814,3 +816,8 @@ const QString ServersModel::getDefaultServerImagePathCollapsed() } return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper()); } + +bool ServersModel::processedServerIsPremium() const +{ + return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex)); +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index c4803708..c36b6534 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -63,6 +63,9 @@ public: Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) + Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged) + + bool processedServerIsPremium() const; public slots: void setDefaultServerIndex(const int index); @@ -92,7 +95,7 @@ public slots: void removeServer(); void removeServer(const int serverIndex); - QJsonObject getServerConfig(const int serverIndex); + QJsonObject getServerConfig(const int serverIndex) const; void reloadDefaultServerContainerConfig(); void updateContainerConfig(const int containerIndex, const QJsonObject config); diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 995ca74b..82552958 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -260,7 +260,7 @@ PageType { LabelWithButtonType { id: labelWithButton6 - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium Layout.fillWidth: true text: qsTr("Switch to the new Amnezia Premium subscription") @@ -273,7 +273,7 @@ PageType { } DividerType { - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium } } } From b0a6bcc05536c9d615b835a790a90d6d42657d1a Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Wed, 2 Jul 2025 06:11:22 +0400 Subject: [PATCH 26/37] =?UTF-8?q?fix:=20fixed=20issue=20when=20native=20co?= =?UTF-8?q?nnection=20format=20preserved=20after=20switching=20p=E2=80=A6?= =?UTF-8?q?=20(#1659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed issue when native connection format preserved after switching protocol * moved newly added code into handler section --- client/ui/qml/Pages2/PageShare.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 48f74acf..0f0976bc 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -429,6 +429,11 @@ PageType { fillConnectionTypeModel() + if (exportTypeSelector.currentIndex >= root.connectionTypesModel.length) { + exportTypeSelector.currentIndex = 0 + exportTypeSelector.text = root.connectionTypesModel[0].name + } + if (accessTypeSelector.currentIndex === 1) { PageController.showBusyIndicator(true) ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), From 9dca80de18a9f2b3fd8339044eafc0b44fd91bd6 Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Wed, 2 Jul 2025 06:11:52 +0400 Subject: [PATCH 27/37] fix: notification not showing when changed some protocols (#1666) * added notification about disconnecting users after applying changes for SS and Cloak servers pages * added notification about changing protocol data for server and some minor changes --- .../qml/Pages2/PageProtocolCloakSettings.qml | 51 +++++++++++++++---- .../Pages2/PageProtocolOpenVpnSettings.qml | 51 ++++++++++++++----- .../PageProtocolShadowSocksSettings.qml | 49 +++++++++++------- .../Pages2/PageProtocolWireGuardSettings.qml | 2 +- .../qml/Pages2/PageProtocolXraySettings.qml | 42 +++++++++++---- 5 files changed, 146 insertions(+), 49 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 7a0fafbd..8e5129b0 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -59,10 +59,13 @@ PageType { model: CloakConfigModel delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + id: delegateItem property alias trafficFromField: trafficFromField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + + implicitWidth: listview.width + implicitHeight: col.implicitHeight ColumnLayout { id: col @@ -78,7 +81,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("Cloak settings") } @@ -88,6 +90,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("Disguised as traffic from") textField.text: site @@ -104,6 +108,8 @@ PageType { } } } + + checkEmptyText: true } TextFieldWithHeaderType { @@ -112,6 +118,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + enabled: delegateItem.isEnabled + headerText: qsTr("Port") textField.text: port textField.maximumLength: 5 @@ -122,6 +130,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } DropDownType { @@ -129,6 +139,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + enabled: delegateItem.isEnabled + descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") @@ -166,25 +178,46 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: trafficFromField.errorText === "" && + portTextField.errorText === "" + text: qsTr("Save") clickedFunc: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.updateContainer(CloakConfigModel.getConfig()) } - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(CloakConfigModel.getConfig()) + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 2e00d54a..62cbd1f6 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -58,10 +58,13 @@ PageType { model: OpenVpnConfigModel delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + id: delegateItem property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + + implicitWidth: listview.width + implicitHeight: col.implicitHeight ColumnLayout { id: col @@ -77,7 +80,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("OpenVPN settings") } @@ -87,6 +89,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("VPN address subnet") textField.text: subnetAddress @@ -97,6 +101,8 @@ PageType { subnetAddress = textField.text } } + + checkEmptyText: true } ParagraphTextType { @@ -134,7 +140,7 @@ PageType { Layout.topMargin: 40 parentFlickable: fl - enabled: isPortEditable + enabled: delegateItem.isEnabled headerText: qsTr("Port") textField.text: port @@ -146,6 +152,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } SwitcherType { @@ -388,26 +396,45 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: vpnAddressSubnetTextField.errorText === "" && + portTextField.errorText === "" + text: qsTr("Save") parentFlickable: fl - clickedFunc: function() { + onClicked: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 63e60dcb..92df3ec7 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -57,15 +57,13 @@ PageType { model: ShadowSocksConfigModel delegate: Item { + id: delegateItem + + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + implicitWidth: listview.width implicitHeight: col.implicitHeight - property var focusItemId: portTextField.enabled ? - portTextField : - cipherDropDown.enabled ? - cipherDropDown : - saveRestartButton - ColumnLayout { id: col @@ -80,7 +78,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("Shadowsocks settings") } @@ -90,7 +87,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 40 - enabled: isPortEditable + enabled: delegateItem.isEnabled headerText: qsTr("Port") textField.text: port @@ -102,6 +99,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } DropDownType { @@ -109,7 +108,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - enabled: isCipherEditable + enabled: delegateItem.isEnabled descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") @@ -149,27 +148,43 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 - enabled: isPortEditable | isCipherEditable + enabled: portTextField.errorText === "" text: qsTr("Save") clickedFunc: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 7b5180f3..21b35bc1 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -152,7 +152,7 @@ PageType { } var noButtonFunction = function() { if (!GC.isMobile()) { - saveRestartButton.forceActiveFocus() + saveButton.forceActiveFocus() } } showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index d22e31a2..0bcd14de 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -58,7 +58,10 @@ PageType { model: XrayConfigModel delegate: Item { + id: delegateItem + property alias focusItemId: textFieldWithHeaderType.textField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() implicitWidth: listview.width implicitHeight: col.implicitHeight @@ -85,6 +88,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("Disguised as traffic from") textField.text: site @@ -101,6 +106,8 @@ PageType { } } } + + checkEmptyText: true } TextFieldWithHeaderType { @@ -130,23 +137,38 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: portTextField.errorText === "" + text: qsTr("Save") - onClicked: { + onClicked: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(XrayConfigModel.getConfig()) - focusItem.forceActiveFocus() + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(XrayConfigModel.getConfig()) + //focusItem.forceActiveFocus() + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - Keys.onEnterPressed: basicButton.clicked() - Keys.onReturnPressed: basicButton.clicked() + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } From 127f8ed3bbccf31383504eb2a71396015b3d67fb Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 2 Jul 2025 10:14:56 +0800 Subject: [PATCH 28/37] fix: fixed desktop entry version for linux (#1665) --- deploy/installer/config/AmneziaVPN.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/installer/config/AmneziaVPN.desktop.in b/deploy/installer/config/AmneziaVPN.desktop.in index 2a53074e..03ab570c 100755 --- a/deploy/installer/config/AmneziaVPN.desktop.in +++ b/deploy/installer/config/AmneziaVPN.desktop.in @@ -2,7 +2,7 @@ [Desktop Entry] Type=Application Name=AmneziaVPN -Version=@CMAKE_PROJECT_VERSION@ +Version=1.0 Comment=Client of your self-hosted VPN Exec=AmneziaVPN Icon=/usr/share/pixmaps/AmneziaVPN.png From b34193486300bc951219c655fd2734f6860feb9f Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 1 Jul 2025 19:16:58 -0700 Subject: [PATCH 29/37] fix: allow secondary DNS usage when AmneziaDNS is disabled (#1583) * Allow secondary DNS usage when AmneziaDNS is disabled * Don't setup secondary DNS for OpenVPN with AmneziaDNS --------- Co-authored-by: vladimir.kuznetsov --- client/configurators/openvpn_configurator.cpp | 12 +++++++++ client/daemon/daemon.cpp | 26 +++++++++++++----- client/daemon/interfaceconfig.cpp | 13 ++++++--- client/daemon/interfaceconfig.h | 3 ++- client/mozilla/localsocketcontroller.cpp | 9 ++++++- .../linux/daemon/wireguardutilslinux.cpp | 5 +++- .../macos/daemon/wireguardutilsmacos.cpp | 27 ++++++++++--------- .../windows/daemon/windowsfirewall.cpp | 23 +++++++++++++--- client/protocols/xrayprotocol.cpp | 7 ++++- service/server/killswitch.cpp | 23 +++++++++++++--- 10 files changed, 116 insertions(+), 32 deletions(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 6d6603da..f6996320 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -118,6 +118,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairisSitesSplitTunnelingEnabled()) { config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("block-ipv6\n"); @@ -161,6 +167,12 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair resolvers; - resolvers.append(QHostAddress(config.m_dnsServer)); + resolvers.append(QHostAddress(config.m_primaryDnsServer)); + if (!config.m_secondaryDnsServer.isEmpty()) { + resolvers.append(QHostAddress(config.m_secondaryDnsServer)); + } // If the DNS is not the Gateway, it's a user defined DNS // thus, not add any other :) - if (config.m_dnsServer == config.m_serverIpv4Gateway) { + if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { resolvers.append(QHostAddress(config.m_serverIpv6Gateway)); } @@ -279,15 +282,26 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString(); config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString(); - if (!obj.contains("dnsServer")) { - config.m_dnsServer = QString(); + if (!obj.contains("primaryDnsServer")) { + config.m_primaryDnsServer = QString(); } else { - QJsonValue value = obj.value("dnsServer"); + QJsonValue value = obj.value("primaryDnsServer"); if (!value.isString()) { logger.error() << "dnsServer is not a string"; return false; } - config.m_dnsServer = value.toString(); + config.m_primaryDnsServer = value.toString(); + } + + if (!obj.contains("secondaryDnsServer")) { + config.m_secondaryDnsServer = QString(); + } else { + QJsonValue value = obj.value("secondaryDnsServer"); + if (!value.isString()) { + logger.error() << "dnsServer is not a string"; + return false; + } + config.m_secondaryDnsServer = value.toString(); } if (!obj.contains("hopType")) { diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index f0adcc92..846cfebe 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -28,7 +28,8 @@ QJsonObject InterfaceConfig::toJson() const { (m_hopType == InterfaceConfig::SingleHop)) { json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway)); json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway)); - json.insert("dnsServer", QJsonValue(m_dnsServer)); + json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer)); + json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer)); } QJsonArray allowedIPAddesses; @@ -100,11 +101,15 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "MTU = " << m_deviceMTU << "\n"; } - if (!m_dnsServer.isNull()) { - QStringList dnsServers(m_dnsServer); + if (!m_primaryDnsServer.isNull()) { + QStringList dnsServers; + dnsServers.append(m_primaryDnsServer); + if (!m_secondaryDnsServer.isNull()) { + dnsServers.append(m_secondaryDnsServer); + } // If the DNS is not the Gateway, it's a user defined DNS // thus, not add any other :) - if (m_dnsServer == m_serverIpv4Gateway) { + if (m_primaryDnsServer == m_serverIpv4Gateway) { dnsServers.append(m_serverIpv6Gateway); } out << "DNS = " << dnsServers.join(", ") << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index ee43a253..6ae400c2 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -32,7 +32,8 @@ class InterfaceConfig { QString m_serverIpv4AddrIn; QString m_serverPskKey; QString m_serverIpv6AddrIn; - QString m_dnsServer; + QString m_primaryDnsServer; + QString m_secondaryDnsServer; int m_serverPort = 0; int m_deviceMTU = 1420; QList m_allowedIPAddressRanges; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index afa29c47..67924d47 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -149,7 +149,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); // json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); - json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1)); + + json.insert("primaryDnsServer", rawConfig.value(amnezia::config_key::dns1)); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!rawConfig.value(amnezia::config_key::dns1).toString(). + contains(amnezia::protocols::dns::amneziaDnsIp)) { + json.insert("secondaryDnsServer", rawConfig.value(amnezia::config_key::dns2)); + } QJsonArray jsAllowedIPAddesses; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 0fbb65a8..a12b8582 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -140,7 +140,10 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { } else { if (config.m_killSwitchEnabled) { FirewallParams params { }; - params.dnsServers.append(config.m_dnsServer); + params.dnsServers.append(config.m_primaryDnsServer); + if (!config.m_secondaryDnsServer.isEmpty()) { + params.dnsServers.append(config.m_secondaryDnsServer); + } if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { params.blockAll = true; if (config.m_excludedAddresses.size()) { diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 1d8aa6e0..37170f20 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -136,26 +136,29 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); } else { - if (config.m_killSwitchEnabled) { - FirewallParams params { }; - params.dnsServers.append(config.m_dnsServer); + if (config.m_killSwitchEnabled) { + FirewallParams params { }; + params.dnsServers.append(config.m_primaryDnsServer); + if (!config.m_secondaryDnsServer.isEmpty()) { + params.dnsServers.append(config.m_secondaryDnsServer); + } - if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { + if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { params.blockAll = true; if (config.m_excludedAddresses.size()) { - params.allowNets = true; - foreach (auto net, config.m_excludedAddresses) { - params.allowAddrs.append(net.toUtf8()); - } + params.allowNets = true; + foreach (auto net, config.m_excludedAddresses) { + params.allowAddrs.append(net.toUtf8()); + } } - } else { + } else { params.blockNets = true; foreach (auto net, config.m_allowedIPAddressRanges) { - params.blockAddrs.append(net.toString()); + params.blockAddrs.append(net.toString()); } - } - applyFirewallRules(params); } + applyFirewallRules(params); + } } return (err == 0); } diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 1834452e..2556c417 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -291,15 +291,32 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { "Block Internet", config.m_serverPublicKey)) { return false; } - if (!config.m_dnsServer.isEmpty()) { - if (!allowTrafficTo(QHostAddress(config.m_dnsServer), 53, HIGH_WEIGHT, + if (!config.m_primaryDnsServer.isEmpty()) { + if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT, "Allow DNS-Server", config.m_serverPublicKey)) { return false; } // In some cases, we might configure a 2nd DNS server for IPv6, however // this should probably be cleaned up by converting m_dnsServer into // a QStringList instead. - if (config.m_dnsServer == config.m_serverIpv4Gateway) { + if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { + if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, + HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", + config.m_serverPublicKey)) { + return false; + } + } + } + + if (!config.m_secondaryDnsServer.isEmpty()) { + if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT, + "Allow DNS-Server", config.m_serverPublicKey)) { + return false; + } + // In some cases, we might configure a 2nd DNS server for IPv6, however + // this should probably be cleaned up by converting m_dnsServer into + // a QStringList instead. + if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) { if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", config.m_serverPublicKey)) { diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index 9f26d1e6..84922634 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -98,8 +98,13 @@ ErrorCode XrayProtocol::startTun2Sock() if (vpnState == Vpn::ConnectionState::Connected) { setConnectionState(Vpn::ConnectionState::Connecting); QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); - dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!m_configData.value(amnezia::config_key::dns1).toString(). + contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + } #ifdef Q_OS_WIN QThread::msleep(8000); #endif diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index 447be865..d0cba03a 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -192,7 +192,14 @@ bool KillSwitch::addAllowedRange(const QStringList &ranges) { bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { #ifdef Q_OS_WIN InterfaceConfig config; - config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); + + config.m_primaryDnsServer = configStr.value(amnezia::config_key::dns1).toString(); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!config.m_primaryDnsServer.contains(amnezia::protocols::dns::amneziaDnsIp)) { + config.m_secondaryDnsServer = configStr.value(amnezia::config_key::dns2).toString(); + } + config.m_serverPublicKey = "openvpn"; config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); @@ -307,8 +314,14 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + } + dnsServers.append("127.0.0.1"); dnsServers.append("127.0.0.53"); @@ -345,7 +358,11 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn QStringList dnsServers; dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + } for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { if (!dns.isString()) { From 4d17e913b52a02d80381b61d94a4a767e9bc00cf Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Thu, 3 Jul 2025 04:51:11 +0300 Subject: [PATCH 30/37] feat: native macos installer distribution (#1633) * Add uninstall option and output pkg Improve installer mode detection Fix macOS installer packaging Fix default selection for uninstall choice Remove obsolete tar handling and clean script copies * Improve macOS build script * fix: update macos firewall and package scripts for better compatibility and cleanup * Add DeveloperID certificate and improve macOS signing script Use keychain option for codesign and restore login keychain to list after signing * Update build_macos.sh * feat: add script to quit GUI application during uninstall on macos * fix: handle macos post-install when app is unpacked into localized folder * fix: improve post_install script to handle missing service plist and provide error logging --- .github/workflows/deploy.yml | 18 +- .../platforms/macos/daemon/macosfirewall.cpp | 14 +- deploy/DeveloperIDG2CA.cer | Bin 0 -> 1090 bytes deploy/build_macos.sh | 258 +++++++++++------- deploy/data/macos/check_install.sh | 5 + deploy/data/macos/check_uninstall.sh | 5 + deploy/data/macos/distribution.xml | 17 ++ deploy/data/macos/distribution_uninstall.xml | 13 + deploy/data/macos/post_install.sh | 41 ++- deploy/data/macos/post_uninstall.sh | 50 ++++ deploy/data/macos/uninstall_conclusion.html | 7 + deploy/data/macos/uninstall_welcome.html | 7 + deploy/installer/config.cmake | 5 - deploy/installer/config/macos.xml.in | 27 -- 14 files changed, 311 insertions(+), 156 deletions(-) create mode 100644 deploy/DeveloperIDG2CA.cer mode change 100755 => 100644 deploy/build_macos.sh create mode 100755 deploy/data/macos/check_install.sh create mode 100755 deploy/data/macos/check_uninstall.sh create mode 100644 deploy/data/macos/distribution.xml create mode 100644 deploy/data/macos/distribution_uninstall.xml create mode 100644 deploy/data/macos/uninstall_conclusion.html create mode 100644 deploy/data/macos/uninstall_welcome.html delete mode 100644 deploy/installer/config/macos.xml.in diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 86779f33..0c9dfb32 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -255,7 +255,6 @@ jobs: env: # Keep compat with MacOS 10.15 aka Catalina by Qt 6.4 QT_VERSION: 6.4.3 - QIF_VERSION: 4.6 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} @@ -283,11 +282,6 @@ jobs: set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - - name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}' - run: | - mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework - wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip - unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/ - name: 'Get sources' uses: actions/checkout@v4 @@ -301,14 +295,13 @@ jobs: - name: 'Build project' run: | export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" - export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" bash deploy/build_macos.sh - name: 'Upload installer artifact' uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_old_installer - path: AmneziaVPN.dmg + path: deploy/build/pkg/AmneziaVPN.pkg retention-days: 7 - name: 'Upload unpacked artifact' @@ -325,7 +318,6 @@ jobs: env: QT_VERSION: 6.8.0 - QIF_VERSION: 4.8.1 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} @@ -353,11 +345,6 @@ jobs: set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - - name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}' - run: | - mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework - wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip - unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/ - name: 'Get sources' uses: actions/checkout@v4 @@ -371,14 +358,13 @@ jobs: - name: 'Build project' run: | export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" - export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" bash deploy/build_macos.sh - name: 'Upload installer artifact' uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_installer - path: AmneziaVPN.dmg + path: deploy/build/pkg/AmneziaVPN.pkg retention-days: 7 - name: 'Upload unpacked artifact' diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp index 0fe51f23..5211c440 100644 --- a/client/platforms/macos/daemon/macosfirewall.cpp +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -43,8 +43,16 @@ namespace { #include "macosfirewall.h" -#define ResourceDir qApp->applicationDirPath() + "/pf" -#define DaemonDataDir qApp->applicationDirPath() + "/pf" +#include +#include + +// Read-only rules bundled with the application. +#define ResourceDir (qApp->applicationDirPath() + "/pf") + +// Writable location that does NOT live inside the signed bundle. Using a +// constant path under /Library/Application Support keeps the signature intact +// and is accessible to the root helper. +#define DaemonDataDir QStringLiteral("/Library/Application Support/AmneziaVPN/pf") #include @@ -121,6 +129,8 @@ void MacOSFirewall::install() logger.info() << "Installing PF root anchor"; installRootAnchors(); + // Ensure writable directory exists, then store the token there. + QDir().mkpath(DaemonDataDir); execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir)); } diff --git a/deploy/DeveloperIDG2CA.cer b/deploy/DeveloperIDG2CA.cer new file mode 100644 index 0000000000000000000000000000000000000000..8cbcf6f46ce8dcd0fb6e55441867a4608c032860 GIT binary patch literal 1090 zcmXqLVzD!5Vpdzg%*4pVBvQYH!T#)Y&#KeSzLS=8RTLj;iFG#MW#iOp^Jx3d%gD&h z%3zRW$Zf#M#vIDRCd?EXY$$9X2;y)Fb2%0iSS&nCU+yalX;MxjO;0c zCPpP>Z!@woFgG#sGXTZ8n3@lXWlhc{<6@_ zAeVa$Zs~tCyBS(Lx(wQVee%4v9DmC6_sxzm_*Wacv6#I#)Z>*hPs%}3Q09g3DcH!m}M zzME}-$);}wYkbz|o;?}o+I{{v$FroX4pX<9$DEPod-(p&(x<&A1GYQN=TcU6b?Bdx zUj1OoE6d9jOHD2wI{m=7UT>Su;iqa>RnM*J)hhopPh{Hk2Vdj$KD}68xZHa2OrI*H z#lG(g1g~CeYi#?H7fwq1S7-2zs_8d`#)~n?ko^>O~S}P zBuwq0phvQHX3=yH`M&5=acZK!OOzXoLyRRCDz!!3*x{YIYWcZo(++VRH_f_`f37RD zIVso6^6S55-|LI6bjZK$IKcnor?K1?f$fK41kGK{SvD+jncI`WTU{$#cV_G457+W- zMXMT?mRx?=Uwf(Jh2ioUN9FH7YKmOf(3s#R_GaGBE{9dpS`QQ3xOxisZ+hvx@ma(w zc&)N$X|k%KGE@HK=&108*Ije(pZQ)tKmPXdw<*i>UG(#PZ7V9c!nRmnw>qJnx@hvN w+Yzt&uIxN=z~GOOl<%%dJSvtmnyePgy!ZUclRbQUTGvOdj=AvX_8Ec00Cez?EdT%j literal 0 HcmV?d00001 diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh old mode 100755 new mode 100644 index 5f6e9786..03f286fc --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -1,4 +1,15 @@ #!/bin/bash +# ----------------------------------------------------------------------------- +# Usage: +# Export the required signing credentials before running this script, e.g.: +# export MAC_APP_CERT_PW='pw-for-DeveloperID-Application' +# export MAC_INSTALL_CERT_PW='pw-for-DeveloperID-Installer' +# export MAC_SIGNER_ID='Developer ID Application: Some Company Name (XXXXXXXXXX)' +# export MAC_INSTALLER_SIGNER_ID='Developer ID Installer: Some Company Name (XXXXXXXXXX)' +# export APPLE_DEV_EMAIL='your@email.com' +# export APPLE_DEV_PASSWORD='' +# bash deploy/build_macos.sh [-n] +# ----------------------------------------------------------------------------- echo "Build script started ..." set -o errexit -o nounset @@ -14,10 +25,10 @@ done PROJECT_DIR=$(pwd) DEPLOY_DIR=$PROJECT_DIR/deploy -mkdir -p $DEPLOY_DIR/build -BUILD_DIR=$DEPLOY_DIR/build +mkdir -p "$DEPLOY_DIR/build" +BUILD_DIR="$DEPLOY_DIR/build" -echo "Project dir: ${PROJECT_DIR}" +echo "Project dir: ${PROJECT_DIR}" echo "Build dir: ${BUILD_DIR}" APP_NAME=AmneziaVPN @@ -28,39 +39,45 @@ PLIST_NAME=$APP_NAME.plist OUT_APP_DIR=$BUILD_DIR/client BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME +# Prebuilt deployment assets are available via the symlink under deploy/data PREBUILT_DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/deploy-prebuilt/macos DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/macos -INSTALLER_DATA_DIR=$BUILD_DIR/installer/packages/$APP_DOMAIN/data -INSTALLER_BUNDLE_DIR=$BUILD_DIR/installer/$APP_FILENAME -DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg # Search Qt if [ -z "${QT_VERSION+x}" ]; then -QT_VERSION=6.4.3; -QIF_VERSION=4.6 +QT_VERSION=6.8.3; QT_BIN_DIR=$HOME/Qt/$QT_VERSION/macos/bin -QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin fi echo "Using Qt in $QT_BIN_DIR" -echo "Using QIF in $QIF_BIN_DIR" # Checking env -$QT_BIN_DIR/qt-cmake --version +"$QT_BIN_DIR/qt-cmake" --version cmake --version clang -v # Build App echo "Building App..." -cd $BUILD_DIR +cd "$BUILD_DIR" -$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR +"$QT_BIN_DIR/qt-cmake" -S "$PROJECT_DIR" -B "$BUILD_DIR" cmake --build . --config release --target all # Build and run tests here +# Create a temporary keychain and import certificates +KEYCHAIN_PATH="$PROJECT_DIR/mac_sign.keychain" +trap 'echo "Cleaning up mac_sign.keychain..."; security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true; rm -f "$KEYCHAIN_PATH" 2>/dev/null || true' EXIT +KEYCHAIN=$(security default-keychain -d user | tr -d '"[:space:]"') +security list-keychains -d user -s "$KEYCHAIN_PATH" "$KEYCHAIN" "$(security list-keychains -d user | tr '\n' ' ')" +security create-keychain -p "" "$KEYCHAIN_PATH" +security import "$DEPLOY_DIR/DeveloperIdApplicationCertificate.p12" -k "$KEYCHAIN_PATH" -P "$MAC_APP_CERT_PW" -T /usr/bin/codesign +security import "$DEPLOY_DIR/DeveloperIdInstallerCertificate.p12" -k "$KEYCHAIN_PATH" -P "$MAC_INSTALL_CERT_PW" -T /usr/bin/codesign +security import "$DEPLOY_DIR/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -T /usr/bin/codesign +security list-keychains -d user -s "$KEYCHAIN_PATH" + echo "____________________________________" echo "............Deploy.................." echo "____________________________________" @@ -69,102 +86,159 @@ echo "____________________________________" echo "Packaging ..." -cp -Rv $PREBUILT_DEPLOY_DATA_DIR/* $BUNDLE_DIR/Contents/macOS -$QT_BIN_DIR/macdeployqt $OUT_APP_DIR/$APP_FILENAME -always-overwrite -qmldir=$PROJECT_DIR -cp -av $BUILD_DIR/service/server/$APP_NAME-service $BUNDLE_DIR/Contents/macOS -cp -Rv $PROJECT_DIR/deploy/data/macos/* $BUNDLE_DIR/Contents/macOS -rm -f $BUNDLE_DIR/Contents/macOS/post_install.sh $BUNDLE_DIR/Contents/macOS/post_uninstall.sh +cp -Rv "$PREBUILT_DEPLOY_DATA_DIR"/* "$BUNDLE_DIR/Contents/macOS" +"$QT_BIN_DIR/macdeployqt" "$OUT_APP_DIR/$APP_FILENAME" -always-overwrite -qmldir="$PROJECT_DIR" +cp -av "$BUILD_DIR/service/server/$APP_NAME-service" "$BUNDLE_DIR/Contents/macOS" +rsync -av --exclude="$PLIST_NAME" --exclude=post_install.sh --exclude=post_uninstall.sh "$DEPLOY_DATA_DIR/" "$BUNDLE_DIR/Contents/macOS/" -if [ "${MAC_CERT_PW+x}" ]; then +if [ "${MAC_APP_CERT_PW+x}" ]; then - CERTIFICATE_P12=$DEPLOY_DIR/PrivacyTechAppleCertDeveloperId.p12 - WWDRCA=$DEPLOY_DIR/WWDRCA.cer - KEYCHAIN=amnezia.build.macos.keychain - TEMP_PASS=tmp_pass + # Path to the p12 that contains the Developer ID *Application* certificate + CERTIFICATE_P12=$DEPLOY_DIR/DeveloperIdApplicationCertificate.p12 - security create-keychain -p $TEMP_PASS $KEYCHAIN || true - security default-keychain -s $KEYCHAIN - security unlock-keychain -p $TEMP_PASS $KEYCHAIN + # Ensure launchd plist is bundled, but place it inside Resources so that + # the bundle keeps a valid structure (nothing but `Contents` at the root). + mkdir -p "$BUNDLE_DIR/Contents/Resources" + cp "$DEPLOY_DATA_DIR/$PLIST_NAME" "$BUNDLE_DIR/Contents/Resources/$PLIST_NAME" - security default-keychain - security list-keychains - - security import $WWDRCA -k $KEYCHAIN -T /usr/bin/codesign || true - security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MAC_CERT_PW -T /usr/bin/codesign || true - - security set-key-partition-list -S apple-tool:,apple: -k $TEMP_PASS $KEYCHAIN - security find-identity -p codesigning + # Show available signing identities (useful for debugging) + security find-identity -p codesigning || true echo "Signing App bundle..." - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR - /usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true - spctl -a -vvvv $BUNDLE_DIR || true + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$BUNDLE_DIR" + /usr/bin/codesign --verify -vvvv "$BUNDLE_DIR" || true + spctl -a -vvvv "$BUNDLE_DIR" || true - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing App bundle..." - /usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip - xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - rm $PROJECT_DIR/Bundle_to_notarize.zip - sleep 300 - xcrun stapler staple $BUNDLE_DIR - xcrun stapler validate $BUNDLE_DIR - spctl -a -vvvv $BUNDLE_DIR || true - fi fi echo "Packaging installer..." -mkdir -p $INSTALLER_DATA_DIR -cp -av $PROJECT_DIR/deploy/installer $BUILD_DIR -cp -av $DEPLOY_DATA_DIR/post_install.sh $INSTALLER_DATA_DIR/post_install.sh -cp -av $DEPLOY_DATA_DIR/post_uninstall.sh $INSTALLER_DATA_DIR/post_uninstall.sh -cp -av $DEPLOY_DATA_DIR/$PLIST_NAME $INSTALLER_DATA_DIR/$PLIST_NAME +PKG_DIR=$BUILD_DIR/pkg +# Remove any stale packaging data from previous runs +rm -rf "$PKG_DIR" +PKG_ROOT=$PKG_DIR/root +SCRIPTS_DIR=$PKG_DIR/scripts +RESOURCES_DIR=$PKG_DIR/resources +INSTALL_PKG=$PKG_DIR/${APP_NAME}_install.pkg +UNINSTALL_PKG=$PKG_DIR/${APP_NAME}_uninstall.pkg +FINAL_PKG=$PKG_DIR/${APP_NAME}.pkg +UNINSTALL_SCRIPTS_DIR=$PKG_DIR/uninstall_scripts -chmod a+x $INSTALLER_DATA_DIR/post_install.sh $INSTALLER_DATA_DIR/post_uninstall.sh +mkdir -p "$PKG_ROOT/Applications" "$SCRIPTS_DIR" "$RESOURCES_DIR" "$UNINSTALL_SCRIPTS_DIR" -cd $BUNDLE_DIR -tar czf $INSTALLER_DATA_DIR/$APP_NAME.tar.gz ./ +cp -R "$BUNDLE_DIR" "$PKG_ROOT/Applications" +# launchd plist is already inside the bundle; no need to add it again after signing +/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$PKG_ROOT/Applications/$APP_FILENAME" +/usr/bin/codesign --verify --deep --strict --verbose=4 "$PKG_ROOT/Applications/$APP_FILENAME" || true +cp "$DEPLOY_DATA_DIR/post_install.sh" "$SCRIPTS_DIR/post_install.sh" +cp "$DEPLOY_DATA_DIR/post_uninstall.sh" "$UNINSTALL_SCRIPTS_DIR/postinstall" +mkdir -p "$RESOURCES_DIR/scripts" +cp "$DEPLOY_DATA_DIR/check_install.sh" "$RESOURCES_DIR/scripts/check_install.sh" +cp "$DEPLOY_DATA_DIR/check_uninstall.sh" "$RESOURCES_DIR/scripts/check_uninstall.sh" -echo "Building installer..." -$QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/macos.xml -p $BUILD_DIR/installer/packages -f $INSTALLER_BUNDLE_DIR +cat > "$SCRIPTS_DIR/postinstall" <<'EOS' +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +bash "$SCRIPT_DIR/post_install.sh" +exit 0 +EOS -if [ "${MAC_CERT_PW+x}" ]; then - echo "Signing installer bundle..." - security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR - /usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true +chmod +x "$SCRIPTS_DIR"/* +chmod +x "$UNINSTALL_SCRIPTS_DIR"/* +chmod +x "$RESOURCES_DIR/scripts"/* +cp "$PROJECT_DIR/LICENSE" "$RESOURCES_DIR/LICENSE" - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing installer bundle..." - /usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip - xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - rm $PROJECT_DIR/Installer_bundle_to_notarize.zip - sleep 300 - xcrun stapler staple $INSTALLER_BUNDLE_DIR - xcrun stapler validate $INSTALLER_BUNDLE_DIR - spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true - fi +APP_VERSION=$(grep -m1 -E 'project\(' "$PROJECT_DIR/CMakeLists.txt" | sed -E 's/.*VERSION ([0-9.]+).*/\1/') +echo "Building component package $INSTALL_PKG ..." + +# Disable bundle relocation so the app always ends up in /Applications even if +# another copy is lying around somewhere. We do this by letting pkgbuild +# analyse the contents, flipping the BundleIsRelocatable flag to false for every +# bundle it discovers and then feeding that plist back to pkgbuild. + +COMPONENT_PLIST="$PKG_DIR/component.plist" +# Create the component description plist first +pkgbuild --analyze --root "$PKG_ROOT" "$COMPONENT_PLIST" + +# Turn all `BundleIsRelocatable` keys to false (PlistBuddy is available on all +# macOS systems). We first convert to xml1 to ensure predictable formatting. + +# Turn relocation off for every bundle entry in the plist. PlistBuddy cannot +# address keys that contain slashes without quoting, so we iterate through the +# top-level keys it prints. +plutil -convert xml1 "$COMPONENT_PLIST" +for bundle_key in $(/usr/libexec/PlistBuddy -c "Print" "$COMPONENT_PLIST" | awk '/^[ \t]*[A-Za-z0-9].*\.app/ {print $1}'); do + /usr/libexec/PlistBuddy -c "Set :'${bundle_key}':BundleIsRelocatable false" "$COMPONENT_PLIST" || true +done + +# Now build the real payload package with the edited plist so that the final +# PackageInfo contains relocatable="false". +pkgbuild --root "$PKG_ROOT" \ + --identifier "$APP_DOMAIN" \ + --version "$APP_VERSION" \ + --install-location "/" \ + --scripts "$SCRIPTS_DIR" \ + --component-plist "$COMPONENT_PLIST" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$INSTALL_PKG" + +# Build uninstaller component package +UNINSTALL_COMPONENT_PKG=$PKG_DIR/${APP_NAME}_uninstall_component.pkg +echo "Building uninstaller component package $UNINSTALL_COMPONENT_PKG ..." +pkgbuild --nopayload \ + --identifier "$APP_DOMAIN.uninstall" \ + --version "$APP_VERSION" \ + --scripts "$UNINSTALL_SCRIPTS_DIR" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$UNINSTALL_COMPONENT_PKG" + +# Wrap uninstaller component in a distribution package for clearer UI +echo "Building uninstaller distribution package $UNINSTALL_PKG ..." +UNINSTALL_RESOURCES=$PKG_DIR/uninstall_resources +rm -rf "$UNINSTALL_RESOURCES" +mkdir -p "$UNINSTALL_RESOURCES" +cp "$DEPLOY_DATA_DIR/uninstall_welcome.html" "$UNINSTALL_RESOURCES" +cp "$DEPLOY_DATA_DIR/uninstall_conclusion.html" "$UNINSTALL_RESOURCES" +productbuild \ + --distribution "$DEPLOY_DATA_DIR/distribution_uninstall.xml" \ + --package-path "$PKG_DIR" \ + --resources "$UNINSTALL_RESOURCES" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$UNINSTALL_PKG" + +cp "$PROJECT_DIR/deploy/data/macos/distribution.xml" "$PKG_DIR/distribution.xml" + +echo "Creating final installer $FINAL_PKG ..." +productbuild --distribution "$PKG_DIR/distribution.xml" \ + --package-path "$PKG_DIR" \ + --resources "$RESOURCES_DIR" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$FINAL_PKG" + +if [ "${MAC_INSTALL_CERT_PW+x}" ] && [ "${NOTARIZE_APP+x}" ]; then + echo "Notarizing installer package..." + xcrun notarytool submit "$FINAL_PKG" \ + --apple-id "$APPLE_DEV_EMAIL" \ + --team-id "$MAC_TEAM_ID" \ + --password "$APPLE_DEV_PASSWORD" \ + --wait + + echo "Stapling ticket..." + xcrun stapler staple "$FINAL_PKG" + xcrun stapler validate "$FINAL_PKG" fi -echo "Building DMG installer..." -# Allow Terminal to make changes in Privacy & Security > App Management -hdiutil create -size 256mb -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app -ov -format UDZO $DMG_FILENAME - -if [ "${MAC_CERT_PW+x}" ]; then - echo "Signing DMG installer..." - security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME - /usr/bin/codesign --verify -vvvv $DMG_FILENAME || true - - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing DMG installer..." - xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - sleep 300 - xcrun stapler staple $DMG_FILENAME - xcrun stapler validate $DMG_FILENAME - fi +if [ "${MAC_INSTALL_CERT_PW+x}" ]; then + /usr/bin/codesign --verify -vvvv "$FINAL_PKG" || true + spctl -a -vvvv "$FINAL_PKG" || true fi -echo "Finished, artifact is $DMG_FILENAME" +# Sign app bundle +/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$BUNDLE_DIR" +spctl -a -vvvv "$BUNDLE_DIR" || true -# restore keychain -security default-keychain -s login.keychain +# Restore login keychain as the only user keychain and delete the temporary keychain +KEYCHAIN="$HOME/Library/Keychains/login.keychain-db" +security list-keychains -d user -s "$KEYCHAIN" +security delete-keychain "$KEYCHAIN_PATH" + +echo "Finished, artifact is $FINAL_PKG" diff --git a/deploy/data/macos/check_install.sh b/deploy/data/macos/check_install.sh new file mode 100755 index 00000000..adf63550 --- /dev/null +++ b/deploy/data/macos/check_install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +if [ -d "/Applications/AmneziaVPN.app" ] || pgrep -x "AmneziaVPN-service" >/dev/null; then + exit 1 +fi +exit 0 diff --git a/deploy/data/macos/check_uninstall.sh b/deploy/data/macos/check_uninstall.sh new file mode 100755 index 00000000..e7a6f7e0 --- /dev/null +++ b/deploy/data/macos/check_uninstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash +if [ -d "/Applications/AmneziaVPN.app" ] || pgrep -x "AmneziaVPN-service" >/dev/null; then + exit 0 +fi +exit 1 diff --git a/deploy/data/macos/distribution.xml b/deploy/data/macos/distribution.xml new file mode 100644 index 00000000..c0a1dc68 --- /dev/null +++ b/deploy/data/macos/distribution.xml @@ -0,0 +1,17 @@ + + + AmneziaVPN Installer + + + + + + + + + + + + AmneziaVPN_install.pkg + AmneziaVPN_uninstall_component.pkg + diff --git a/deploy/data/macos/distribution_uninstall.xml b/deploy/data/macos/distribution_uninstall.xml new file mode 100644 index 00000000..cf8932b9 --- /dev/null +++ b/deploy/data/macos/distribution_uninstall.xml @@ -0,0 +1,13 @@ + + Uninstall AmneziaVPN + + + + + + + + + + AmneziaVPN_uninstall_component.pkg + diff --git a/deploy/data/macos/post_install.sh b/deploy/data/macos/post_install.sh index acd3f93f..053c8e13 100755 --- a/deploy/data/macos/post_install.sh +++ b/deploy/data/macos/post_install.sh @@ -7,29 +7,42 @@ LOG_FOLDER=/var/log/$APP_NAME LOG_FILE="$LOG_FOLDER/post-install.log" APP_PATH=/Applications/$APP_NAME.app -if launchctl list "$APP_NAME-service" &> /dev/null; then - launchctl unload $LAUNCH_DAEMONS_PLIST_NAME - rm -f $LAUNCH_DAEMONS_PLIST_NAME +# Handle new installations unpacked into localized folder +if [ -d "/Applications/${APP_NAME}.localized" ]; then + echo "`date` Detected ${APP_NAME}.localized, migrating to standard path" >> $LOG_FILE + sudo rm -rf "$APP_PATH" + sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH" + sudo rm -rf "/Applications/${APP_NAME}.localized" fi -tar xzf $APP_PATH/$APP_NAME.tar.gz -C $APP_PATH -rm -f $APP_PATH/$APP_NAME.tar.gz -sudo chmod -R a-w $APP_PATH/ -sudo chown -R root $APP_PATH/ -sudo chgrp -R wheel $APP_PATH/ +if launchctl list "$APP_NAME-service" &> /dev/null; then + launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME" + rm -f "$LAUNCH_DAEMONS_PLIST_NAME" +fi + +sudo chmod -R a-w "$APP_PATH/" +sudo chown -R root "$APP_PATH/" +sudo chgrp -R wheel "$APP_PATH/" rm -rf $LOG_FOLDER mkdir -p $LOG_FOLDER echo "`date` Script started" > $LOG_FILE -killall -9 $APP_NAME-service 2>> $LOG_FILE +echo "Requesting ${APP_NAME} to quit gracefully" >> "$LOG_FILE" +osascript -e 'tell application "AmneziaVPN" to quit' -mv -f $APP_PATH/$PLIST_NAME $LAUNCH_DAEMONS_PLIST_NAME 2>> $LOG_FILE -chown root:wheel $LAUNCH_DAEMONS_PLIST_NAME -launchctl load $LAUNCH_DAEMONS_PLIST_NAME +PLIST_SOURCE="$APP_PATH/Contents/Resources/$PLIST_NAME" +if [ -f "$PLIST_SOURCE" ]; then + mv -f "$PLIST_SOURCE" "$LAUNCH_DAEMONS_PLIST_NAME" 2>> $LOG_FILE +else + echo "`date` ERROR: service plist not found at $PLIST_SOURCE" >> $LOG_FILE +fi + +chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME" +launchctl load "$LAUNCH_DAEMONS_PLIST_NAME" +echo "`date` Launching ${APP_NAME} application" >> $LOG_FILE +open -a "$APP_PATH" 2>> $LOG_FILE || true echo "`date` Service status: $?" >> $LOG_FILE echo "`date` Script finished" >> $LOG_FILE - -#rm -- "$0" diff --git a/deploy/data/macos/post_uninstall.sh b/deploy/data/macos/post_uninstall.sh index de7846db..d6c5cdbd 100755 --- a/deploy/data/macos/post_uninstall.sh +++ b/deploy/data/macos/post_uninstall.sh @@ -9,6 +9,19 @@ SYSTEM_APP_SUPPORT="/Library/Application Support/$APP_NAME" LOG_FOLDER="/var/log/$APP_NAME" CACHES_FOLDER="$HOME/Library/Caches/$APP_NAME" +# Attempt to quit the GUI application if it's currently running +if pgrep -x "$APP_NAME" > /dev/null; then + echo "Quitting $APP_NAME..." + osascript -e 'tell application "'"$APP_NAME"'" to quit' || true + # Wait up to 10 seconds for the app to terminate gracefully + for i in {1..10}; do + if ! pgrep -x "$APP_NAME" > /dev/null; then + break + fi + sleep 1 + done +fi + # Stop the running service if it exists if pgrep -x "${APP_NAME}-service" > /dev/null; then sudo killall -9 "${APP_NAME}-service" @@ -32,3 +45,40 @@ sudo rm -rf "$LOG_FOLDER" # Remove any caches left behind rm -rf "$CACHES_FOLDER" + +# Remove PF data directory created by firewall helper, if present +sudo rm -rf "/Library/Application Support/${APP_NAME}/pf" + +# ---------------- PF firewall cleanup ---------------------- +# Rules are loaded under the anchor "amn" (see macosfirewall.cpp) +# Flush only that anchor to avoid destroying user/system rules. + +PF_ANCHOR="amn" + +### Flush all PF rules, NATs, and tables under our anchor and sub-anchors ### +anchors=$(sudo pfctl -s Anchors 2>/dev/null | awk '/^'"${PF_ANCHOR}"'/ {sub(/\*$/, "", $1); print $1}') +for anc in $anchors; do + echo "Flushing PF anchor $anc" + sudo pfctl -a "$anc" -F all 2>/dev/null || true + # flush tables under this anchor + tables=$(sudo pfctl -s Tables 2>/dev/null | awk '/^'"$anc"'/ {print}') + for tbl in $tables; do + echo "Killing PF table $tbl" + sudo pfctl -t "$tbl" -T kill 2>/dev/null || true + done +done + +### Reload default PF config to restore system rules ### +if [ -f /etc/pf.conf ]; then + echo "Restoring system PF config" + sudo pfctl -f /etc/pf.conf 2>/dev/null || true +fi + +### Disable PF if no rules remain ### +if sudo pfctl -s info 2>/dev/null | grep -q '^Status: Enabled' && \ + ! sudo pfctl -sr 2>/dev/null | grep -q .; then + echo "Disabling PF" + sudo pfctl -d 2>/dev/null || true +fi + +# ----------------------------------------------------------- diff --git a/deploy/data/macos/uninstall_conclusion.html b/deploy/data/macos/uninstall_conclusion.html new file mode 100644 index 00000000..f5b8bb63 --- /dev/null +++ b/deploy/data/macos/uninstall_conclusion.html @@ -0,0 +1,7 @@ + +Uninstall Complete + +

AmneziaVPN has been uninstalled

+

Thank you for using AmneziaVPN. The application and its components have been removed.

+ + \ No newline at end of file diff --git a/deploy/data/macos/uninstall_welcome.html b/deploy/data/macos/uninstall_welcome.html new file mode 100644 index 00000000..9f3d97cb --- /dev/null +++ b/deploy/data/macos/uninstall_welcome.html @@ -0,0 +1,7 @@ + +Uninstall AmneziaVPN + +

Uninstall AmneziaVPN

+

This process will remove AmneziaVPN from your system. Click Continue to proceed.

+ + \ No newline at end of file diff --git a/deploy/installer/config.cmake b/deploy/installer/config.cmake index 13f09986..3c33a33c 100644 --- a/deploy/installer/config.cmake +++ b/deploy/installer/config.cmake @@ -4,11 +4,6 @@ if(WIN32) ${CMAKE_CURRENT_LIST_DIR}/config/windows.xml.in ${CMAKE_BINARY_DIR}/installer/config/windows.xml ) -elseif(APPLE AND NOT IOS) - configure_file( - ${CMAKE_CURRENT_LIST_DIR}/config/macos.xml.in - ${CMAKE_BINARY_DIR}/installer/config/macos.xml - ) elseif(LINUX) set(ApplicationsDir "@ApplicationsDir@") configure_file( diff --git a/deploy/installer/config/macos.xml.in b/deploy/installer/config/macos.xml.in deleted file mode 100644 index 3888d08d..00000000 --- a/deploy/installer/config/macos.xml.in +++ /dev/null @@ -1,27 +0,0 @@ - - - AmneziaVPN - @CMAKE_PROJECT_VERSION@ - AmneziaVPN - AmneziaVPN - AmneziaVPN - /Applications/AmneziaVPN.app - 600 - 380 - Mac - true - true - false - controlscript.js - false - true - false - true - - - https://amneziavpn.org/updates/macos - true - AmneziaVPN - repository for macOS - - - From efcc0b7efc9fd8f15081db44cf48e9ff205cb407 Mon Sep 17 00:00:00 2001 From: Nethius Date: Thu, 3 Jul 2025 09:58:23 +0800 Subject: [PATCH 31/37] feat: xray api support (#1679) * refactoring: moved shared code into reusable functions for ApiConfigsController * feat: add xray support in apiConfigsController * feat: added a temporary switch for the xray protocol on api settings page * feat: added supported protocols field processing * refactoring: moved IsProtocolSelectionSupported to apiAccountInfoModel --- client/core/api/apiDefs.h | 1 + .../controllers/api/apiConfigsController.cpp | 484 ++++++++++-------- .../ui/controllers/api/apiConfigsController.h | 18 +- client/ui/models/api/apiAccountInfoModel.cpp | 11 + client/ui/models/api/apiAccountInfoModel.h | 5 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 26 + 6 files changed, 330 insertions(+), 215 deletions(-) diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 4588ef04..12c8051f 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -32,6 +32,7 @@ namespace apiDefs constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String serviceType("service_type"); constexpr QLatin1String cliVersion("cli_version"); + constexpr QLatin1String supportedProtocols("supported_protocols"); constexpr QLatin1String vpnKey("vpn_key"); constexpr QLatin1String config("config"); diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 4c58140c..eb693a9a 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -18,6 +18,7 @@ namespace { constexpr char cloak[] = "cloak"; constexpr char awg[] = "awg"; + constexpr char vless[] = "vless"; constexpr char apiEndpoint[] = "api_endpoint"; constexpr char accessToken[] = "api_key"; @@ -35,10 +36,6 @@ namespace constexpr char serviceInfo[] = "service_info"; constexpr char serviceProtocol[] = "service_protocol"; - constexpr char aesKey[] = "aes_key"; - constexpr char aesIv[] = "aes_iv"; - constexpr char aesSalt[] = "aes_salt"; - constexpr char apiPayload[] = "api_payload"; constexpr char keyPayload[] = "key_payload"; @@ -47,6 +44,169 @@ namespace constexpr char config[] = "config"; } + + struct ProtocolData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + + QString xrayUuid; + }; + + struct GatewayRequestData + { + QString osVersion; + QString appVersion; + + QString installationUuid; + + QString userCountryCode; + QString serverCountryCode; + QString serviceType; + QString serviceProtocol; + + QJsonObject authData; + + QJsonObject toJsonObject() const + { + QJsonObject obj; + if (!osVersion.isEmpty()) { + obj[configKey::osVersion] = osVersion; + } + if (!appVersion.isEmpty()) { + obj[configKey::appVersion] = appVersion; + } + if (!installationUuid.isEmpty()) { + obj[configKey::uuid] = installationUuid; + } + if (!userCountryCode.isEmpty()) { + obj[configKey::userCountryCode] = userCountryCode; + } + if (!serverCountryCode.isEmpty()) { + obj[configKey::serverCountryCode] = serverCountryCode; + } + if (!serviceType.isEmpty()) { + obj[configKey::serviceType] = serviceType; + } + if (!serviceProtocol.isEmpty()) { + obj[configKey::serviceProtocol] = serviceProtocol; + } + if (!authData.isEmpty()) { + obj[configKey::authData] = authData; + } + return obj; + } + }; + + ProtocolData generateProtocolData(const QString &protocol) + { + ProtocolData protocolData; + if (protocol == configKey::cloak) { + protocolData.certRequest = OpenVpnConfigurator::createCertRequest(); + } else if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + protocolData.wireGuardClientPubKey = connData.clientPubKey; + protocolData.wireGuardClientPrivKey = connData.clientPrivKey; + } else if (protocol == configKey::vless) { + protocolData.xrayUuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + } + + return protocolData; + } + + void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload) + { + if (protocol == configKey::cloak) { + apiPayload[configKey::certificate] = protocolData.certRequest.request; + } else if (protocol == configKey::awg) { + apiPayload[configKey::publicKey] = protocolData.wireGuardClientPubKey; + } else if (protocol == configKey::vless) { + apiPayload[configKey::publicKey] = protocolData.xrayUuid; + } + } + + ErrorCode fillServerConfig(const QString &protocol, const ProtocolData &apiPayloadData, const QByteArray &apiResponseBody, + QJsonObject &serverConfig) + { + QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); + + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + if (ba.isEmpty()) { + qDebug() << "empty vpn key"; + return ErrorCode::ApiConfigEmptyError; + } + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QString configStr = ba; + if (protocol == configKey::cloak) { + configStr.replace("", "\n"); + configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); + } else if (protocol == configKey::awg) { + configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); + if (containers.isEmpty()) { + qDebug() << "missing containers field"; + return ErrorCode::ApiConfigEmptyError; + } + auto container = containers.at(0).toObject(); + QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); + auto serverProtocolConfig = container.value(containerName).toObject(); + auto clientProtocolConfig = + QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); + serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount); + serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize); + serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize); + serverProtocolConfig[config_key::initPacketJunkSize] = clientProtocolConfig.value(config_key::initPacketJunkSize); + serverProtocolConfig[config_key::responsePacketJunkSize] = clientProtocolConfig.value(config_key::responsePacketJunkSize); + serverProtocolConfig[config_key::initPacketMagicHeader] = clientProtocolConfig.value(config_key::initPacketMagicHeader); + serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader); + serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader); + serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader); + container[containerName] = serverProtocolConfig; + containers.replace(0, container); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); + } + + QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); + serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); + serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); + serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); + serverConfig[config_key::description] = newServerConfig.value(config_key::description); + serverConfig[config_key::name] = newServerConfig.value(config_key::name); + } + + auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); + serverConfig[config_key::defaultContainer] = defaultContainer; + + QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); + map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); + auto apiConfig = QJsonObject::fromVariantMap(map); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + apiConfig.insert(apiDefs::key::supportedProtocols, + QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray()); + } + + serverConfig[configKey::apiConfig] = apiConfig; + + qDebug() << serverConfig; + + return ErrorCode::NoError; + } } ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, @@ -63,24 +223,26 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + m_apiServicesModel->getSelectedServiceProtocol(), + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ProtocolData protocolData = generateProtocolData(protocol); + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError) { emit errorOccurred(errorCode); return false; @@ -88,7 +250,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); QString nativeConfig = jsonConfig.value(configKey::config).toString(); - nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey); SystemController::saveFile(fileName, nativeConfig); return true; @@ -96,24 +258,22 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + m_apiServicesModel->getSelectedServiceProtocol(), + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -144,14 +304,11 @@ void ApiConfigsController::copyVpnKeyToClipboard() bool ApiConfigsController::fillAvailableServices() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - QJsonObject apiPayload; apiPayload[configKey::osVersion] = QSysInfo::productType(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody); if (errorCode == ErrorCode::NoError) { if (!responseBody.contains("services")) { errorCode = ErrorCode::ApiServicesMissingError; @@ -170,34 +327,36 @@ bool ApiConfigsController::fillAvailableServices() bool ApiConfigsController::importServiceFromGateway() { - if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol())) { + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + m_apiServicesModel->getCountryCode(), + "", + m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol(), + QJsonObject() }; + + if (m_serversModel->isServerFromApiAlreadyExists(gatewayRequestData.userCountryCode, gatewayRequestData.serviceType, + gatewayRequestData.serviceProtocol)) { emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded); return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); + ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - auto installationUuid = m_settings->getInstallationUuid(true); - auto userCountryCode = m_apiServicesModel->getCountryCode(); - auto serviceType = m_apiServicesModel->getSelectedServiceType(); - auto serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol(); - - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); - - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::uuid] = installationUuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); QJsonObject serverConfig; if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig); + errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, serverConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode()); @@ -218,39 +377,33 @@ bool ApiConfigsController::importServiceFromGateway() bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); - auto installationUuid = m_settings->getInstallationUuid(true); - auto userCountryCode = apiConfig.value(configKey::userCountryCode).toString(); - auto serviceType = apiConfig.value(configKey::serviceType).toString(); - auto serviceProtocol = apiConfig.value(configKey::serviceProtocol).toString(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfig.value(configKey::userCountryCode).toString(), + newCountryCode, + apiConfig.value(configKey::serviceType).toString(), + apiConfig.value(configKey::serviceProtocol).toString(), + serverConfig.value(configKey::authData).toObject() }; - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::uuid] = installationUuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); - - if (!newCountryCode.isEmpty()) { - apiPayload[configKey::serverCountryCode] = newCountryCode; - } - if (!authData.isEmpty()) { - apiPayload[configKey::authData] = authData; - } + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); QJsonObject newServerConfig; if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, newServerConfig); + errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, newServerConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); @@ -259,7 +412,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); newServerConfig.insert(configKey::apiConfig, newApiConfig); - newServerConfig.insert(configKey::authData, authData); + newServerConfig.insert(configKey::authData, gatewayRequestData.authData); if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) { newServerConfig.insert(config_key::name, serverConfig.value(config_key::name)); @@ -294,10 +447,13 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) auto installationUuid = m_settings->getInstallationUuid(true); QString serviceProtocol = serverConfig.value(configKey::protocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + ProtocolData protocolData = generateProtocolData(serviceProtocol); - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); + QJsonObject apiPayload; + appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); apiPayload[configKey::uuid] = installationUuid; + apiPayload[configKey::osVersion] = QSysInfo::productType(); + apiPayload[configKey::appVersion] = QString(APP_VERSION); apiPayload[configKey::accessToken] = serverConfig.value(configKey::accessToken).toString(); apiPayload[configKey::apiEndpoint] = serverConfig.value(configKey::apiEndpoint).toString(); @@ -305,7 +461,11 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody); if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig); + errorCode = fillServerConfig(serviceProtocol, protocolData, responseBody, serverConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } m_serversModel->editServer(serverConfig, serverIndex); emit updateServerFromApiFinished(); @@ -318,9 +478,6 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) bool ApiConfigsController::deactivateDevice() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -329,19 +486,19 @@ bool ApiConfigsController::deactivateDevice() return true; } - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + apiConfigObject.value(configKey::serverCountryCode).toString(), + apiConfigObject.value(configKey::serviceType).toString(), + "", + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode); - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -355,9 +512,6 @@ bool ApiConfigsController::deactivateDevice() bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -366,19 +520,19 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q return true; } - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + uuid, + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + "", + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[configKey::uuid] = uuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -417,108 +571,29 @@ bool ApiConfigsController::isConfigValid() return true; } -ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) +void ApiConfigsController::setCurrentProtocol(const QString &protocolName) { - ApiConfigsController::ApiPayloadData apiPayload; - if (protocol == configKey::cloak) { - apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); - } else if (protocol == configKey::awg) { - auto connData = WireguardConfigurator::genClientKeys(); - apiPayload.wireGuardClientPubKey = connData.clientPubKey; - apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; - } - return apiPayload; + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + apiConfigObject[configKey::serviceProtocol] = protocolName; + + serverConfigObject.insert(configKey::apiConfig, apiConfigObject); + + m_serversModel->editServer(serverConfigObject, serverIndex); } -QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData) +bool ApiConfigsController::isVlessProtocol() { - QJsonObject obj; - if (protocol == configKey::cloak) { - obj[configKey::certificate] = apiPayloadData.certRequest.request; - } else if (protocol == configKey::awg) { - obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + if (apiConfigObject[configKey::serviceProtocol].toString() == "vless") { + return true; } - - obj[configKey::osVersion] = QSysInfo::productType(); - obj[configKey::appVersion] = QString(APP_VERSION); - - return obj; -} - -void ApiConfigsController::fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, - const QByteArray &apiResponseBody, QJsonObject &serverConfig) -{ - QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); - - data.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - if (ba.isEmpty()) { - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return; - } - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QString configStr = ba; - if (protocol == configKey::cloak) { - configStr.replace("", "\n"); - configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); - } else if (protocol == configKey::awg) { - configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = newServerConfig.value(config_key::containers).toArray(); - if (containers.isEmpty()) { - return; // todo process error - } - auto container = containers.at(0).toObject(); - QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); - auto containerConfig = container.value(containerName).toObject(); - auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); - containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount); - containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize); - containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize); - containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize); - containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize); - containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader); - containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader); - containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader); - containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); - container[containerName] = containerConfig; - containers.replace(0, container); - newServerConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(newServerConfig).toJson()); - } - - QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); - serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); - serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); - serverConfig[config_key::description] = newServerConfig.value(config_key::description); - serverConfig[config_key::name] = newServerConfig.value(config_key::name); - } - - auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); - serverConfig[config_key::defaultContainer] = defaultContainer; - - QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); - map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); - auto apiConfig = QJsonObject::fromVariantMap(map); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); - } - - serverConfig[configKey::apiConfig] = apiConfig; - - return; + return false; } QList ApiConfigsController::getQrCodes() @@ -535,3 +610,10 @@ QString ApiConfigsController::getVpnKey() { return m_vpnKey; } + +ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); + return gatewayController.post(endpoint, apiPayload, responseBody); +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 2fe981e4..a04a142c 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -35,6 +35,9 @@ public slots: bool isConfigValid(); + void setCurrentProtocol(const QString &protocolName); + bool isVlessProtocol(); + signals: void errorOccurred(ErrorCode errorCode); @@ -46,23 +49,12 @@ signals: void vpnKeyExportReady(); private: - struct ApiPayloadData - { - OpenVpnConfigurator::ConnectionData certRequest; - - QString wireGuardClientPrivKey; - QString wireGuardClientPubKey; - }; - - ApiPayloadData generateApiPayloadData(const QString &protocol); - QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); - void fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody, - QJsonObject &serverConfig); - QList getQrCodes(); int getQrCodesCount(); QString getVpnKey(); + ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody); + QList m_qrCodes; QString m_vpnKey; diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index fdd4e2ca..bd3027a4 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -75,6 +75,12 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const } return false; } + case IsProtocolSelectionSupportedRole: { + if (m_accountInfoData.supportedProtocols.size() > 1) { + return true; + } + return false; + } } return QVariant(); @@ -95,6 +101,10 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons accountInfoData.configType = apiUtils::getConfigType(serverConfig); + for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) { + accountInfoData.supportedProtocols.push_back(protocol.toString()); + } + m_accountInfoData = accountInfoData; m_supportInfo = accountInfoObject.value(apiDefs::key::supportInfo).toObject(); @@ -159,6 +169,7 @@ QHash ApiAccountInfoModel::roleNames() const roles[ServiceDescriptionRole] = "serviceDescription"; roles[IsComponentVisibleRole] = "isComponentVisible"; roles[HasExpiredWorkerRole] = "hasExpiredWorker"; + roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported"; return roles; } diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index ead92488..f0203967 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -18,7 +18,8 @@ public: ServiceDescriptionRole, EndDateRole, IsComponentVisibleRole, - HasExpiredWorkerRole + HasExpiredWorkerRole, + IsProtocolSelectionSupportedRole }; explicit ApiAccountInfoModel(QObject *parent = nullptr); @@ -51,6 +52,8 @@ private: int maxDeviceCount; apiDefs::ConfigType configType; + + QStringList supportedProtocols; }; AccountInfoData m_accountInfoData; diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 93118755..75832fa6 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -158,6 +158,32 @@ PageType { readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + SwitcherType { + id: switcher + + readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol() + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + visible: ApiAccountInfoModel.data("isProtocolSelectionSupported") + + text: qsTr("Use VLESS protocol") + checked: switcher.isVlessProtocol + onToggled: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection")) + } else { + PageController.showBusyIndicator(true) + ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless") + ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true) + PageController.showBusyIndicator(false) + } + } + } + WarningType { id: warning From f8bea71716826fe757be584b655f5d1f8133e6e2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 7 Jul 2025 10:26:16 +0800 Subject: [PATCH 32/37] chore: temporarily hide the strict killswitch --- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 444eb415..ca1cd0d4 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -62,7 +62,8 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + visible: false + // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: !SettingsController.strictKillSwitchEnabled text: qsTr("Soft KillSwitch") @@ -73,7 +74,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } VerticalRadioButton { id: strictKillSwitch From 42661618dc060d05794cc0dd503ed03cf579cf5d Mon Sep 17 00:00:00 2001 From: Nethius Date: Mon, 7 Jul 2025 10:44:35 +0800 Subject: [PATCH 33/37] chore: bump version (#1696) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 424dcf3a..fec613de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.7.2 +project(${PROJECT} VERSION 4.8.8.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2086) +set(APP_ANDROID_VERSION_CODE 2087) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 2380cd5cfbe75b610c2bba4c3748aef5982f50fa Mon Sep 17 00:00:00 2001 From: Nethius Date: Mon, 7 Jul 2025 12:03:25 +0800 Subject: [PATCH 34/37] feat: amneziawg 1.5 support (#1692) * Version bump 4.2.1.0 * feat: add special handshake params to ui * feat: finish adding params * feat: android/ios & fix qml * chore: fix android impl & update 3rd-prebuilt branch * chore: trigger build with windows build * fix: special handshake params to client * chore: update submodule * feat: s3, s4 * chore: update submodule * feat: s3 s4 cont * fix: kt set * chore: update submodule * feat: add default values for s3, s4 * fix: make new parameters optional * chore: update submodules * chore: restore translation files * fix: fixed awg native config import with new junk * chore: restore translation files * AWG v1.5 Build * refactoring: removed s3 s4 fileds from ui part * chore: update link to amneziawg-apple --------- Co-authored-by: pokamest Co-authored-by: Mark Puha Co-authored-by: albexk Co-authored-by: Mykola Baibuz --- .gitmodules | 1 + client/3rd-prebuilt | 2 +- client/3rd/amneziawg-apple | 2 +- .../vpn/protocol/wireguard/Wireguard.kt | 11 + .../vpn/protocol/wireguard/WireguardConfig.kt | 59 +++++- client/configurators/awg_configurator.cpp | 15 ++ client/core/controllers/serverController.cpp | 18 +- client/daemon/daemon.cpp | 37 +++- client/daemon/interfaceconfig.cpp | 16 ++ client/daemon/interfaceconfig.h | 5 + client/mozilla/localsocketcontroller.cpp | 39 +++- client/platforms/ios/WGConfig.swift | 22 +- client/platforms/ios/ios_controller.mm | 26 ++- .../linux/daemon/wireguardutilslinux.cpp | 16 ++ .../macos/daemon/wireguardutilsmacos.cpp | 16 ++ client/protocols/protocols_defs.h | 23 ++ client/resources.qrc | 1 + client/server_scripts/awg/Dockerfile | 5 +- .../server_scripts/awg/configure_container.sh | 1 + .../controllers/api/apiConfigsController.cpp | 18 ++ client/ui/controllers/importController.cpp | 56 +++-- client/ui/controllers/installController.cpp | 56 ++++- client/ui/models/protocols/awgConfigModel.cpp | 109 +++++++++- client/ui/models/protocols/awgConfigModel.h | 33 ++- client/ui/qml/Components/AwgTextField.qml | 15 ++ .../Pages2/PageProtocolAwgClientSettings.qml | 198 +++++++++++++----- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 144 +++++-------- 27 files changed, 758 insertions(+), 186 deletions(-) create mode 100644 client/ui/qml/Components/AwgTextField.qml diff --git a/.gitmodules b/.gitmodules index decab9b7..90edb582 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,7 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt + branch = feature/special-handshake [submodule "client/3rd/amneziawg-apple"] path = client/3rd/amneziawg-apple url = https://github.com/amnezia-vpn/amneziawg-apple diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index 0f3748ef..840b7b07 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit 0f3748efd7cc04e0c914304b68931f925bed1259 +Subproject commit 840b7b070e6ac8b90dda2fac6e98859b23727c0c diff --git a/client/3rd/amneziawg-apple b/client/3rd/amneziawg-apple index 76e7db55..811af0a8 160000 --- a/client/3rd/amneziawg-apple +++ b/client/3rd/amneziawg-apple @@ -1 +1 @@ -Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c +Subproject commit 811af0a83b3faeade89a9093a588595666d32066 diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index 80cab96d..42a27de4 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -120,10 +120,21 @@ open class Wireguard : Protocol() { configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) } configData.optStringOrNull("S1")?.let { setS1(it.toInt()) } configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } + configData.optStringOrNull("S3")?.let { setS3(it.toInt()) } + configData.optStringOrNull("S4")?.let { setS4(it.toInt()) } configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } + configData.optStringOrNull("I1")?.let { setI1(it) } + configData.optStringOrNull("I2")?.let { setI2(it) } + configData.optStringOrNull("I3")?.let { setI3(it) } + configData.optStringOrNull("I4")?.let { setI4(it) } + configData.optStringOrNull("I5")?.let { setI5(it) } + configData.optStringOrNull("J1")?.let { setJ1(it) } + configData.optStringOrNull("J2")?.let { setJ2(it) } + configData.optStringOrNull("J3")?.let { setJ3(it) } + configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) } } private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) { diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt index 7ae3d43b..2dfbbae8 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt @@ -20,10 +20,21 @@ open class WireguardConfig protected constructor( val jmax: Int?, val s1: Int?, val s2: Int?, + val s3: Int?, + val s4: Int?, val h1: Long?, val h2: Long?, val h3: Long?, - val h4: Long? + val h4: Long?, + var i1: String?, + var i2: String?, + var i3: String?, + var i4: String?, + var i5: String?, + var j1: String?, + var j2: String?, + var j3: String?, + var itime: Int? ) : ProtocolConfig(protocolConfigBuilder) { protected constructor(builder: Builder) : this( @@ -39,10 +50,21 @@ open class WireguardConfig protected constructor( builder.jmax, builder.s1, builder.s2, + builder.s3, + builder.s4, builder.h1, builder.h2, builder.h3, - builder.h4 + builder.h4, + builder.i1, + builder.i2, + builder.i3, + builder.i4, + builder.i5, + builder.j1, + builder.j2, + builder.j3, + builder.itime ) fun toWgUserspaceString(): String = with(StringBuilder()) { @@ -61,10 +83,21 @@ open class WireguardConfig protected constructor( appendLine("jmax=$jmax") appendLine("s1=$s1") appendLine("s2=$s2") + s3?.let { appendLine("s3=$it") } + s4?.let { appendLine("s4=$it") } appendLine("h1=$h1") appendLine("h2=$h2") appendLine("h3=$h3") appendLine("h4=$h4") + i1?.let { appendLine("i1=$it") } + i2?.let { appendLine("i2=$it") } + i3?.let { appendLine("i3=$it") } + i4?.let { appendLine("i4=$it") } + i5?.let { appendLine("i5=$it") } + j1?.let { appendLine("j1=$it") } + j2?.let { appendLine("j2=$it") } + j3?.let { appendLine("j3=$it") } + itime?.let { appendLine("itime=$it") } } } @@ -117,10 +150,21 @@ open class WireguardConfig protected constructor( internal var jmax: Int? = null internal var s1: Int? = null internal var s2: Int? = null + internal var s3: Int? = null + internal var s4: Int? = null internal var h1: Long? = null internal var h2: Long? = null internal var h3: Long? = null internal var h4: Long? = null + internal var i1: String? = null + internal var i2: String? = null + internal var i3: String? = null + internal var i4: String? = null + internal var i5: String? = null + internal var j1: String? = null + internal var j2: String? = null + internal var j3: String? = null + internal var itime: Int? = null fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint } @@ -139,10 +183,21 @@ open class WireguardConfig protected constructor( fun setJmax(jmax: Int) = apply { this.jmax = jmax } fun setS1(s1: Int) = apply { this.s1 = s1 } fun setS2(s2: Int) = apply { this.s2 = s2 } + fun setS3(s3: Int) = apply { this.s3 = s3 } + fun setS4(s4: Int) = apply { this.s4 = s4 } fun setH1(h1: Long) = apply { this.h1 = h1 } fun setH2(h2: Long) = apply { this.h2 = h2 } fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH4(h4: Long) = apply { this.h4 = h4 } + fun setI1(i1: String) = apply { this.i1 = i1 } + fun setI2(i2: String) = apply { this.i2 = i2 } + fun setI3(i3: String) = apply { this.i3 = i3 } + fun setI4(i4: String) = apply { this.i4 = i4 } + fun setI5(i5: String) = apply { this.i5 = i5 } + fun setJ1(j1: String) = apply { this.j1 = j1 } + fun setJ2(j2: String) = apply { this.j2 = j2 } + fun setJ3(j3: String) = apply { this.j3 = j3 } + fun setItime(itime: Int) = apply { this.itime = itime } override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } } diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 21b61ba4..f83acb19 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -1,4 +1,5 @@ #include "awg_configurator.h" +#include "protocols/protocols_defs.h" #include #include @@ -39,6 +40,20 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + // jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize); + // jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize); + + // jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1); + // jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2); + // jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3); + // jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4); + // jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5); + // jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1); + // jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2); + // jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3); + // jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout); + jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu); diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index a61a638b..3c24edea 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -349,7 +349,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) + != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) || (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount) != newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)) || (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize) @@ -366,8 +366,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c != newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)) || (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader) != newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)) - || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader) - != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))) + || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) + != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) + // || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize) + // != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)) + // || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize) + // != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)) + return true; } @@ -375,7 +380,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) || (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))) + != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))) return true; } @@ -455,7 +460,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)), cbReadStdOut, cbReadStdErr); - + if (stdOut.contains("doesn't work on cgroups v2")) return ErrorCode::ServerDockerOnCgroupsV2; if (stdOut.contains("cgroup mountpoint does not exist")) @@ -641,6 +646,9 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); + vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } }); + vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } }); + // Socks5 proxy vars vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } }); auto username = socks5ProxyConfig.value(config_key::userName).toString(); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 33ec8cbc..2faff0ef 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -405,6 +405,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!obj.value("S2").isNull()) { config.m_responsePacketJunkSize = obj.value("S2").toString(); } + if (!obj.value("S3").isNull()) { + config.m_cookieReplyPacketJunkSize = obj.value("S3").toString(); + } + if (!obj.value("S4").isNull()) { + config.m_transportPacketJunkSize = obj.value("S4").toString(); + } + if (!obj.value("H1").isNull()) { config.m_initPacketMagicHeader = obj.value("H1").toString(); } @@ -418,6 +425,34 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { config.m_transportPacketMagicHeader = obj.value("H4").toString(); } + if (!obj.value("I1").isNull()) { + config.m_specialJunk["I1"] = obj.value("I1").toString(); + } + if (!obj.value("I2").isNull()) { + config.m_specialJunk["I2"] = obj.value("I2").toString(); + } + if (!obj.value("I3").isNull()) { + config.m_specialJunk["I3"] = obj.value("I3").toString(); + } + if (!obj.value("I4").isNull()) { + config.m_specialJunk["I4"] = obj.value("I4").toString(); + } + if (!obj.value("I5").isNull()) { + config.m_specialJunk["I5"] = obj.value("I5").toString(); + } + if (!obj.value("J1").isNull()) { + config.m_controlledJunk["J1"] = obj.value("J1").toString(); + } + if (!obj.value("J2").isNull()) { + config.m_controlledJunk["J2"] = obj.value("J2").toString(); + } + if (!obj.value("J3").isNull()) { + config.m_controlledJunk["J3"] = obj.value("J3").toString(); + } + if (!obj.value("Itime").isNull()) { + config.m_specialHandshakeTimeout = obj.value("Itime").toString(); + } + return true; } @@ -460,7 +495,7 @@ bool Daemon::deactivate(bool emitSignals) { m_connections.clear(); // Delete the interface - return wgutils()->deleteInterface(); + return wgutils()->deleteInterface(); } QString Daemon::logs() { diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index 846cfebe..53da5d36 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -130,6 +130,12 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { if (!m_responsePacketJunkSize.isNull()) { out << "S2 = " << m_responsePacketJunkSize << "\n"; } + if (!m_cookieReplyPacketJunkSize.isNull()) { + out << "S3 = " << m_cookieReplyPacketJunkSize << "\n"; + } + if (!m_transportPacketJunkSize.isNull()) { + out << "S4 = " << m_transportPacketJunkSize << "\n"; + } if (!m_initPacketMagicHeader.isNull()) { out << "H1 = " << m_initPacketMagicHeader << "\n"; } @@ -143,6 +149,16 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "H4 = " << m_transportPacketMagicHeader << "\n"; } + for (const QString& key : m_specialJunk.keys()) { + out << key << " = " << m_specialJunk[key] << "\n"; + } + for (const QString& key : m_controlledJunk.keys()) { + out << key << " = " << m_controlledJunk[key] << "\n"; + } + if (!m_specialHandshakeTimeout.isNull()) { + out << "Itime = " << m_specialHandshakeTimeout << "\n"; + } + // If any extra config was provided, append it now. for (const QString& key : extra.keys()) { out << key << " = " << extra[key] << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 6ae400c2..06288e80 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -50,10 +50,15 @@ class InterfaceConfig { QString m_junkPacketMaxSize; QString m_initPacketJunkSize; QString m_responsePacketJunkSize; + QString m_cookieReplyPacketJunkSize; + QString m_transportPacketJunkSize; QString m_initPacketMagicHeader; QString m_responsePacketMagicHeader; QString m_underloadPacketMagicHeader; QString m_transportPacketMagicHeader; + QMap m_specialJunk; + QMap m_controlledJunk; + QString m_specialHandshakeTimeout; QJsonObject toJson() const; QString toWgConf( diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 67924d47..9abab81c 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -38,7 +38,7 @@ LocalSocketController::LocalSocketController() { m_socket = new QLocalSocket(this); connect(m_socket, &QLocalSocket::connected, this, &LocalSocketController::daemonConnected); - connect(m_socket, &QLocalSocket::disconnected, this, + connect(m_socket, &QLocalSocket::disconnected, this, [&] { errorOccurred(QLocalSocket::PeerClosedError); }); connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalSocketController::errorOccurred); @@ -135,7 +135,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4. - // Otherwise some OSes (Linux) try IPv6 forever and hang. + // Otherwise some OSes (Linux) try IPv6 forever and hang. // https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193) // https://man7.org/linux/man-pages/man5/gai.conf.5.html json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it @@ -244,28 +244,61 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); + json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); + json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); + json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); + json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); + json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); + json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1)); + json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2)); + json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3)); + json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout)); } else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() - && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) { + && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined() + && !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) { json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); + json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); + json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); + json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); + json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); + json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); + json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1)); + json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2)); + json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3)); + json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout)); } write(json); diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift index e3b67efe..8f693387 100644 --- a/client/platforms/ios/WGConfig.swift +++ b/client/platforms/ios/WGConfig.swift @@ -4,7 +4,10 @@ struct WGConfig: Decodable { let initPacketMagicHeader, responsePacketMagicHeader: String? let underloadPacketMagicHeader, transportPacketMagicHeader: String? let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? - let initPacketJunkSize, responsePacketJunkSize: String? + let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String? + let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String? + let controlledJunk1, controlledJunk2, controlledJunk3: String? + let specialHandshakeTimeout: String? let dns1: String let dns2: String let mtu: String @@ -23,7 +26,10 @@ struct WGConfig: Decodable { case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2" case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4" case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax" - case initPacketJunkSize = "S1", responsePacketJunkSize = "S2" + case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4" + case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5" + case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3" + case specialHandshakeTimeout = "Itime" case dns1 case dns2 case mtu @@ -47,11 +53,21 @@ struct WGConfig: Decodable { Jmax = \(junkPacketMaxSize!) S1 = \(initPacketJunkSize!) S2 = \(responsePacketJunkSize!) + S3 = \(cookieReplyPacketJunkSize!) + S4 = \(transportPacketJunkSize!) H1 = \(initPacketMagicHeader!) H2 = \(responsePacketMagicHeader!) H3 = \(underloadPacketMagicHeader!) H4 = \(transportPacketMagicHeader!) - + I1 = \(specialJunk1!) + I2 = \(specialJunk2!) + I3 = \(specialJunk3!) + I4 = \(specialJunk4!) + I5 = \(specialJunk5!) + J1 = \(controlledJunk1!) + J2 = \(controlledJunk2!) + J3 = \(controlledJunk3!) + Itime = \(specialHandshakeTimeout!) """ } diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 85fb50b7..e64c6dce 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -507,6 +507,8 @@ bool IosController::setupWireGuard() wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); + wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); + wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); @@ -605,11 +607,23 @@ bool IosController::setupAwg() wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); + wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); + wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]); + wgConfig.insert(config_key::specialJunk1, config[config_key::specialJunk1]); + wgConfig.insert(config_key::specialJunk2, config[config_key::specialJunk2]); + wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]); + wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]); + wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]); + wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]); + wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]); + wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]); + wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]); + QJsonDocument wgConfigDoc(wgConfig); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); @@ -794,9 +808,9 @@ bool IosController::shareText(const QStringList& filesToSend) { if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; - + __block bool isAccepted = false; - + [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { isAccepted = completed; emit finished(); @@ -808,11 +822,11 @@ bool IosController::shareText(const QStringList& filesToSend) { popController.sourceView = qtController.view; popController.sourceRect = CGRectMake(100, 100, 100, 100); } - + QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); - + return isAccepted; } @@ -826,7 +840,7 @@ QString IosController::openFile() { if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; - + __block QString filePath; documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { @@ -841,7 +855,7 @@ QString IosController::openFile() { QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); - + return filePath; } diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index a12b8582..cfde73e2 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -121,6 +121,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { if (!config.m_responsePacketJunkSize.isEmpty()) { out << "s2=" << config.m_responsePacketJunkSize << "\n"; } + if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { + out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; + } + if (!config.m_transportPacketJunkSize.isEmpty()) { + out << "s4=" << config.m_transportPacketJunkSize << "\n"; + } if (!config.m_initPacketMagicHeader.isEmpty()) { out << "h1=" << config.m_initPacketMagicHeader << "\n"; } @@ -134,6 +140,16 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { out << "h4=" << config.m_transportPacketMagicHeader << "\n"; } + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + } + for (const QString& key : config.m_controlledJunk.keys()) { + out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n"; + } + if (!config.m_specialHandshakeTimeout.isEmpty()) { + out << "itime=" << config.m_specialHandshakeTimeout << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 37170f20..cce4afab 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -119,6 +119,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { if (!config.m_responsePacketJunkSize.isEmpty()) { out << "s2=" << config.m_responsePacketJunkSize << "\n"; } + if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { + out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; + } + if (!config.m_transportPacketJunkSize.isEmpty()) { + out << "s4=" << config.m_transportPacketJunkSize << "\n"; + } if (!config.m_initPacketMagicHeader.isEmpty()) { out << "h1=" << config.m_initPacketMagicHeader << "\n"; } @@ -132,6 +138,16 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { out << "h4=" << config.m_transportPacketMagicHeader << "\n"; } + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + } + for (const QString& key : config.m_controlledJunk.keys()) { + out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n"; + } + if (!config.m_specialHandshakeTimeout.isEmpty()) { + out << "itime=" << config.m_specialHandshakeTimeout << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index c2d51454..b4cbb6de 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -72,10 +72,21 @@ namespace amnezia constexpr char junkPacketMaxSize[] = "Jmax"; constexpr char initPacketJunkSize[] = "S1"; constexpr char responsePacketJunkSize[] = "S2"; + constexpr char cookieReplyPacketJunkSize[] = "S3"; + constexpr char transportPacketJunkSize[] = "S4"; constexpr char initPacketMagicHeader[] = "H1"; constexpr char responsePacketMagicHeader[] = "H2"; constexpr char underloadPacketMagicHeader[] = "H3"; constexpr char transportPacketMagicHeader[] = "H4"; + constexpr char specialJunk1[] = "I1"; + constexpr char specialJunk2[] = "I2"; + constexpr char specialJunk3[] = "I3"; + constexpr char specialJunk4[] = "I4"; + constexpr char specialJunk5[] = "I5"; + constexpr char controlledJunk1[] = "J1"; + constexpr char controlledJunk2[] = "J2"; + constexpr char controlledJunk3[] = "J3"; + constexpr char specialHandshakeTimeout[] = "Itime"; constexpr char openvpn[] = "openvpn"; constexpr char wireguard[] = "wireguard"; @@ -216,10 +227,22 @@ namespace amnezia constexpr char defaultJunkPacketMaxSize[] = "30"; constexpr char defaultInitPacketJunkSize[] = "15"; constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultCookieReplyPacketJunkSize[] = "20"; + constexpr char defaultTransportPacketJunkSize[] = "23"; + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + constexpr char defaultSpecialJunk1[] = ""; + constexpr char defaultSpecialJunk2[] = ""; + constexpr char defaultSpecialJunk3[] = ""; + constexpr char defaultSpecialJunk4[] = ""; + constexpr char defaultSpecialJunk5[] = ""; + constexpr char defaultControlledJunk1[] = ""; + constexpr char defaultControlledJunk2[] = ""; + constexpr char defaultControlledJunk3[] = ""; + constexpr char defaultSpecialHandshakeTimeout[] = ""; } namespace socks5Proxy diff --git a/client/resources.qrc b/client/resources.qrc index 72eb15c7..54b5846c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -239,6 +239,7 @@ ui/qml/Components/ApiPremV1MigrationDrawer.qml ui/qml/Components/ApiPremV1SubListDrawer.qml ui/qml/Components/OtpCodeDrawer.qml + ui/qml/Components/AwgTextField.qml images/flagKit/ZW.svg diff --git a/client/server_scripts/awg/Dockerfile b/client/server_scripts/awg/Dockerfile index 8c536fc7..a6118a84 100644 --- a/client/server_scripts/awg/Dockerfile +++ b/client/server_scripts/awg/Dockerfile @@ -10,7 +10,7 @@ RUN mkdir -p /opt/amnezia RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh RUN chmod a+x /opt/amnezia/start.sh -# Tune network +# Tune network RUN echo -e " \n\ fs.file-max = 51200 \n\ \n\ @@ -40,7 +40,8 @@ RUN echo -e " \n\ echo -e " \n\ * soft nofile 51200 \n\ * hard nofile 51200 \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] CMD [ "" ] + diff --git a/client/server_scripts/awg/configure_container.sh b/client/server_scripts/awg/configure_container.sh index 2000c965..e327f080 100644 --- a/client/server_scripts/awg/configure_container.sh +++ b/client/server_scripts/awg/configure_container.sh @@ -23,4 +23,5 @@ H1 = $INIT_PACKET_MAGIC_HEADER H2 = $RESPONSE_PACKET_MAGIC_HEADER H3 = $UNDERLOAD_PACKET_MAGIC_HEADER H4 = $TRANSPORT_PACKET_MAGIC_HEADER + EOF diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index eb693a9a..0b0a9b92 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -162,6 +162,9 @@ namespace auto serverProtocolConfig = container.value(containerName).toObject(); auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); + + //TODO looks like this block can be removed after v1 configs EOL + serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount); serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize); serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize); @@ -171,6 +174,21 @@ namespace serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader); serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader); serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader); + + serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = clientProtocolConfig.value(config_key::cookieReplyPacketJunkSize); + serverProtocolConfig[config_key::transportPacketJunkSize] = clientProtocolConfig.value(config_key::transportPacketJunkSize); + serverProtocolConfig[config_key::specialJunk1] = clientProtocolConfig.value(config_key::specialJunk1); + serverProtocolConfig[config_key::specialJunk2] = clientProtocolConfig.value(config_key::specialJunk2); + serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3); + serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4); + serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5); + serverProtocolConfig[config_key::controlledJunk1] = clientProtocolConfig.value(config_key::controlledJunk1); + serverProtocolConfig[config_key::controlledJunk2] = clientProtocolConfig.value(config_key::controlledJunk2); + serverProtocolConfig[config_key::controlledJunk3] = clientProtocolConfig.value(config_key::controlledJunk3); + serverProtocolConfig[config_key::specialHandshakeTimeout] = clientProtocolConfig.value(config_key::specialHandshakeTimeout); + + // + container[containerName] = serverProtocolConfig; containers.replace(0, container); newServerConfig[config_key::containers] = containers; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index fdc06120..ea1d5d8e 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -12,6 +12,7 @@ #include "core/errorstrings.h" #include "core/qrCodeUtils.h" #include "core/serialization/serialization.h" +#include "protocols/protocols_defs.h" #include "systemController.h" #include "utilities.h" @@ -286,6 +287,19 @@ void ImportController::processNativeWireGuardConfig() clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3"; clientProtocolConfig[config_key::transportPacketMagicHeader] = "4"; + // clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0"; + // clientProtocolConfig[config_key::transportPacketJunkSize] = "0"; + + // clientProtocolConfig[config_key::specialJunk1] = ""; + // clientProtocolConfig[config_key::specialJunk2] = ""; + // clientProtocolConfig[config_key::specialJunk3] = ""; + // clientProtocolConfig[config_key::specialJunk4] = ""; + // clientProtocolConfig[config_key::specialJunk5] = ""; + // clientProtocolConfig[config_key::controlledJunk1] = ""; + // clientProtocolConfig[config_key::controlledJunk2] = ""; + // clientProtocolConfig[config_key::controlledJunk3] = ""; + // clientProtocolConfig[config_key::specialHandshakeTimeout] = "0"; + clientProtocolConfig[config_key::isObfuscationEnabled] = true; serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson()); @@ -438,21 +452,33 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; QString protocolName = "wireguard"; - if (!configMap.value(config_key::junkPacketCount).isEmpty() && !configMap.value(config_key::junkPacketMinSize).isEmpty() - && !configMap.value(config_key::junkPacketMaxSize).isEmpty() && !configMap.value(config_key::initPacketJunkSize).isEmpty() - && !configMap.value(config_key::responsePacketJunkSize).isEmpty() && !configMap.value(config_key::initPacketMagicHeader).isEmpty() - && !configMap.value(config_key::responsePacketMagicHeader).isEmpty() - && !configMap.value(config_key::underloadPacketMagicHeader).isEmpty() - && !configMap.value(config_key::transportPacketMagicHeader).isEmpty()) { - lastConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); - lastConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); - lastConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); - lastConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); - lastConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); - lastConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); - lastConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); - lastConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); - lastConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + const QStringList requiredJunkFields = { config_key::junkPacketCount, config_key::junkPacketMinSize, + config_key::junkPacketMaxSize, config_key::initPacketJunkSize, + config_key::responsePacketJunkSize, config_key::initPacketMagicHeader, + config_key::responsePacketMagicHeader, config_key::underloadPacketMagicHeader, + config_key::transportPacketMagicHeader }; + + const QStringList optionalJunkFields = { // config_key::cookieReplyPacketJunkSize, + // config_key::transportPacketJunkSize, + config_key::specialJunk1, config_key::specialJunk2, config_key::specialJunk3, + config_key::specialJunk4, config_key::specialJunk5, config_key::controlledJunk1, + config_key::controlledJunk2, config_key::controlledJunk3, config_key::specialHandshakeTimeout + }; + + bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(), + [&configMap](const QString &field) { return !configMap.value(field).isEmpty(); }); + if (hasAllRequiredFields) { + for (const QString &field : requiredJunkFields) { + lastConfig[field] = configMap.value(field); + } + + for (const QString &field : optionalJunkFields) { + if (!configMap.value(field).isEmpty()) { + lastConfig[field] = configMap.value(field); + } + } + protocolName = "awg"; m_configType = ConfigTypes::Awg; } diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index eab8979a..d7f9dfbc 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,6 +8,7 @@ #include #include +#include "core/api/apiUtils.h" #include "core/controllers/serverController.h" #include "core/controllers/vpnConfigurationController.h" #include "core/networkUtilities.h" @@ -15,7 +16,6 @@ #include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" #include "utilities.h" -#include "core/api/apiUtils.h" namespace { @@ -79,12 +79,36 @@ void InstallController::install(DockerContainer container, int port, TransportPr int s1 = QRandomGenerator::global()->bounded(15, 150); int s2 = QRandomGenerator::global()->bounded(15, 150); - while (s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) { + // int s3 = QRandomGenerator::global()->bounded(15, 150); + // int s4 = QRandomGenerator::global()->bounded(15, 150); + + // Ensure all values are unique and don't create equal packet sizes + QSet usedValues; + usedValues.insert(s1); + + while (usedValues.contains(s2) || s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) { s2 = QRandomGenerator::global()->bounded(15, 150); } + usedValues.insert(s2); + + // while (usedValues.contains(s3) + // || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize + // || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) { + // s3 = QRandomGenerator::global()->bounded(15, 150); + // } + // usedValues.insert(s3); + + // while (usedValues.contains(s4) + // || s1 + AwgConstant::messageInitiationSize == s4 + AwgConstant::messageTransportSize + // || s2 + AwgConstant::messageResponseSize == s4 + AwgConstant::messageTransportSize + // || s3 + AwgConstant::messageCookieReplySize == s4 + AwgConstant::messageTransportSize) { + // s4 = QRandomGenerator::global()->bounded(15, 150); + // } QString initPacketJunkSize = QString::number(s1); QString responsePacketJunkSize = QString::number(s2); + // QString cookieReplyPacketJunkSize = QString::number(s3); + // QString transportPacketJunkSize = QString::number(s4); QSet headersValue; while (headersValue.size() != 4) { @@ -108,6 +132,21 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + + // TODO: + // containerConfig[config_key::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; + // containerConfig[config_key::transportPacketJunkSize] = transportPacketJunkSize; + + // containerConfig[config_key::specialJunk1] = specialJunk1; + // containerConfig[config_key::specialJunk2] = specialJunk2; + // containerConfig[config_key::specialJunk3] = specialJunk3; + // containerConfig[config_key::specialJunk4] = specialJunk4; + // containerConfig[config_key::specialJunk5] = specialJunk5; + // containerConfig[config_key::controlledJunk1] = controlledJunk1; + // containerConfig[config_key::controlledJunk2] = controlledJunk2; + // containerConfig[config_key::controlledJunk3] = controlledJunk3; + // containerConfig[config_key::specialHandshakeTimeout] = specialHandshakeTimeout; + } else if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::password, Utils::getRandomString(16)); @@ -401,6 +440,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + // containerConfig[config_key::cookieReplyPacketJunkSize] = serverConfigMap.value(config_key::cookieReplyPacketJunkSize); + // containerConfig[config_key::transportPacketJunkSize] = serverConfigMap.value(config_key::transportPacketJunkSize); + + // containerConfig[config_key::specialJunk1] = serverConfigMap.value(config_key::specialJunk1); + // containerConfig[config_key::specialJunk2] = serverConfigMap.value(config_key::specialJunk2); + // containerConfig[config_key::specialJunk3] = serverConfigMap.value(config_key::specialJunk3); + // containerConfig[config_key::specialJunk4] = serverConfigMap.value(config_key::specialJunk4); + // containerConfig[config_key::specialJunk5] = serverConfigMap.value(config_key::specialJunk5); + // containerConfig[config_key::controlledJunk1] = serverConfigMap.value(config_key::controlledJunk1); + // containerConfig[config_key::controlledJunk2] = serverConfigMap.value(config_key::controlledJunk2); + // containerConfig[config_key::controlledJunk3] = serverConfigMap.value(config_key::controlledJunk3); + // containerConfig[config_key::specialHandshakeTimeout] = serverConfigMap.value(config_key::specialHandshakeTimeout); + } else if (protocol == Proto::WireGuard) { QString serverConfig = serverController->getTextFileFromContainer(container, credentials, protocols::wireguard::serverConfigPath, errorCode); diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp index 860c8395..e14a3152 100644 --- a/client/ui/models/protocols/awgConfigModel.cpp +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -28,7 +28,17 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in case Roles::ClientJunkPacketCountRole: m_clientProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::ClientJunkPacketMinSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::ClientJunkPacketMaxSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; - + case Roles::ClientSpecialJunk1Role: m_clientProtocolConfig.insert(config_key::specialJunk1, value.toString()); break; + case Roles::ClientSpecialJunk2Role: m_clientProtocolConfig.insert(config_key::specialJunk2, value.toString()); break; + case Roles::ClientSpecialJunk3Role: m_clientProtocolConfig.insert(config_key::specialJunk3, value.toString()); break; + case Roles::ClientSpecialJunk4Role: m_clientProtocolConfig.insert(config_key::specialJunk4, value.toString()); break; + case Roles::ClientSpecialJunk5Role: m_clientProtocolConfig.insert(config_key::specialJunk5, value.toString()); break; + case Roles::ClientControlledJunk1Role: m_clientProtocolConfig.insert(config_key::controlledJunk1, value.toString()); break; + case Roles::ClientControlledJunk2Role: m_clientProtocolConfig.insert(config_key::controlledJunk2, value.toString()); break; + case Roles::ClientControlledJunk3Role: m_clientProtocolConfig.insert(config_key::controlledJunk3, value.toString()); break; + case Roles::ClientSpecialHandshakeTimeoutRole: + m_clientProtocolConfig.insert(config_key::specialHandshakeTimeout, value.toString()); + break; case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; @@ -36,6 +46,12 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in case Roles::ServerResponsePacketJunkSizeRole: m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); break; + // case Roles::ServerCookieReplyPacketJunkSizeRole: + // m_serverProtocolConfig.insert(config_key::cookieReplyPacketJunkSize, value.toString()); + // break; + // case Roles::ServerTransportPacketJunkSizeRole: + // m_serverProtocolConfig.insert(config_key::transportPacketJunkSize, value.toString()); + // break; case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break; case Roles::ServerResponsePacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); @@ -66,12 +82,23 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const case Roles::ClientJunkPacketCountRole: return m_clientProtocolConfig.value(config_key::junkPacketCount); case Roles::ClientJunkPacketMinSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMinSize); case Roles::ClientJunkPacketMaxSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMaxSize); + case Roles::ClientSpecialJunk1Role: return m_clientProtocolConfig.value(config_key::specialJunk1); + case Roles::ClientSpecialJunk2Role: return m_clientProtocolConfig.value(config_key::specialJunk2); + case Roles::ClientSpecialJunk3Role: return m_clientProtocolConfig.value(config_key::specialJunk3); + case Roles::ClientSpecialJunk4Role: return m_clientProtocolConfig.value(config_key::specialJunk4); + case Roles::ClientSpecialJunk5Role: return m_clientProtocolConfig.value(config_key::specialJunk5); + case Roles::ClientControlledJunk1Role: return m_clientProtocolConfig.value(config_key::controlledJunk1); + case Roles::ClientControlledJunk2Role: return m_clientProtocolConfig.value(config_key::controlledJunk2); + case Roles::ClientControlledJunk3Role: return m_clientProtocolConfig.value(config_key::controlledJunk3); + case Roles::ClientSpecialHandshakeTimeoutRole: return m_clientProtocolConfig.value(config_key::specialHandshakeTimeout); case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount); case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize); case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize); case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize); case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize); + // case Roles::ServerCookieReplyPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize); + // case Roles::ServerTransportPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::transportPacketJunkSize); case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader); case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader); case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader); @@ -94,7 +121,8 @@ void AwgConfigModel::updateModel(const QJsonObject &config) m_serverProtocolConfig.insert(config_key::transport_proto, serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); - m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); + m_serverProtocolConfig[config_key::subnet_address] = + serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); m_serverProtocolConfig[config_key::junkPacketCount] = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); @@ -106,6 +134,10 @@ void AwgConfigModel::updateModel(const QJsonObject &config) serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); m_serverProtocolConfig[config_key::responsePacketJunkSize] = serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); + // m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = + // serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); + // m_serverProtocolConfig[config_key::transportPacketJunkSize] = + // serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); m_serverProtocolConfig[config_key::initPacketMagicHeader] = serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); m_serverProtocolConfig[config_key::responsePacketMagicHeader] = @@ -124,6 +156,24 @@ void AwgConfigModel::updateModel(const QJsonObject &config) clientProtocolConfig.value(config_key::junkPacketMinSize).toString(m_serverProtocolConfig[config_key::junkPacketMinSize].toString()); m_clientProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString()); + m_clientProtocolConfig[config_key::specialJunk1] = + clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); + m_clientProtocolConfig[config_key::specialJunk2] = + clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); + m_clientProtocolConfig[config_key::specialJunk3] = + clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); + m_clientProtocolConfig[config_key::specialJunk4] = + clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); + m_clientProtocolConfig[config_key::specialJunk5] = + clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); + m_clientProtocolConfig[config_key::controlledJunk1] = + clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1); + m_clientProtocolConfig[config_key::controlledJunk2] = + clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2); + m_clientProtocolConfig[config_key::controlledJunk3] = + clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3); + m_clientProtocolConfig[config_key::specialHandshakeTimeout] = + clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout); endResetModel(); } @@ -141,6 +191,15 @@ QJsonObject AwgConfigModel::getConfig() jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount]; jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize]; jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize]; + jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1]; + jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2]; + jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3]; + jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4]; + jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5]; + jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1]; + jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2]; + jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3]; + jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout]; m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); } @@ -159,6 +218,17 @@ bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2) return (AwgConstant::messageInitiationSize + s1 == AwgConstant::messageResponseSize + s2); } +// bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4) +// { +// int initSize = AwgConstant::messageInitiationSize + s1; +// int responseSize = AwgConstant::messageResponseSize + s2; +// int cookieSize = AwgConstant::messageCookieReplySize + s3; +// int transportSize = AwgConstant::messageTransportSize + s4; + +// return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize +// || responseSize == transportSize || cookieSize == transportSize); +// } + bool AwgConfigModel::isServerSettingsEqual() { const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); @@ -178,12 +248,24 @@ QHash AwgConfigModel::roleNames() const roles[ClientJunkPacketCountRole] = "clientJunkPacketCount"; roles[ClientJunkPacketMinSizeRole] = "clientJunkPacketMinSize"; roles[ClientJunkPacketMaxSizeRole] = "clientJunkPacketMaxSize"; + roles[ClientSpecialJunk1Role] = "clientSpecialJunk1"; + roles[ClientSpecialJunk2Role] = "clientSpecialJunk2"; + roles[ClientSpecialJunk3Role] = "clientSpecialJunk3"; + roles[ClientSpecialJunk4Role] = "clientSpecialJunk4"; + roles[ClientSpecialJunk5Role] = "clientSpecialJunk5"; + roles[ClientControlledJunk1Role] = "clientControlledJunk1"; + roles[ClientControlledJunk2Role] = "clientControlledJunk2"; + roles[ClientControlledJunk3Role] = "clientControlledJunk3"; + roles[ClientSpecialHandshakeTimeoutRole] = "clientSpecialHandshakeTimeout"; roles[ServerJunkPacketCountRole] = "serverJunkPacketCount"; roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize"; roles[ServerJunkPacketMaxSizeRole] = "serverJunkPacketMaxSize"; roles[ServerInitPacketJunkSizeRole] = "serverInitPacketJunkSize"; roles[ServerResponsePacketJunkSizeRole] = "serverResponsePacketJunkSize"; + roles[ServerCookieReplyPacketJunkSizeRole] = "serverCookieReplyPacketJunkSize"; + roles[ServerTransportPacketJunkSizeRole] = "serverTransportPacketJunkSize"; + roles[ServerInitPacketMagicHeaderRole] = "serverInitPacketMagicHeader"; roles[ServerResponsePacketMagicHeaderRole] = "serverResponsePacketMagicHeader"; roles[ServerUnderloadPacketMagicHeaderRole] = "serverUnderloadPacketMagicHeader"; @@ -200,6 +282,16 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) clientJunkPacketCount = clientProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); + clientSpecialJunk1 = clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); + clientSpecialJunk2 = clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); + clientSpecialJunk3 = clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); + clientSpecialJunk4 = clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); + clientSpecialJunk5 = clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); + clientControlledJunk1 = clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1); + clientControlledJunk2 = clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2); + clientControlledJunk3 = clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3); + clientSpecialHandshakeTimeout = + clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout); subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); @@ -209,6 +301,10 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); serverResponsePacketJunkSize = serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); + // serverCookieReplyPacketJunkSize = + // serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); + // serverTransportPacketJunkSize = + // serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); serverInitPacketMagicHeader = serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); serverResponsePacketMagicHeader = @@ -224,6 +320,8 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize + // || serverCookieReplyPacketJunkSize != other.serverCookieReplyPacketJunkSize + // || serverTransportPacketJunkSize != other.serverTransportPacketJunkSize || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader || serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader || serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader @@ -236,7 +334,12 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const { if (clientMtu != other.clientMtu || clientJunkPacketCount != other.clientJunkPacketCount - || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize) { + || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize + || clientSpecialJunk1 != other.clientSpecialJunk1 || clientSpecialJunk2 != other.clientSpecialJunk2 + || clientSpecialJunk3 != other.clientSpecialJunk3 || clientSpecialJunk4 != other.clientSpecialJunk4 + || clientSpecialJunk5 != other.clientSpecialJunk5 || clientControlledJunk1 != other.clientControlledJunk1 + || clientControlledJunk2 != other.clientControlledJunk2 || clientControlledJunk3 != other.clientControlledJunk3 + || clientSpecialHandshakeTimeout != other.clientSpecialHandshakeTimeout) { return false; } return true; diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h index c1f8bb27..0c2374fc 100644 --- a/client/ui/models/protocols/awgConfigModel.h +++ b/client/ui/models/protocols/awgConfigModel.h @@ -6,9 +6,12 @@ #include "containers/containers_defs.h" -namespace AwgConstant { +namespace AwgConstant +{ const int messageInitiationSize = 148; const int messageResponseSize = 92; + const int messageCookieReplySize = 64; + const int messageTransportSize = 32; } struct AwgConfig @@ -22,12 +25,23 @@ struct AwgConfig QString clientJunkPacketCount; QString clientJunkPacketMinSize; QString clientJunkPacketMaxSize; + QString clientSpecialJunk1; + QString clientSpecialJunk2; + QString clientSpecialJunk3; + QString clientSpecialJunk4; + QString clientSpecialJunk5; + QString clientControlledJunk1; + QString clientControlledJunk2; + QString clientControlledJunk3; + QString clientSpecialHandshakeTimeout; QString serverJunkPacketCount; QString serverJunkPacketMinSize; QString serverJunkPacketMaxSize; QString serverInitPacketJunkSize; QString serverResponsePacketJunkSize; + QString serverCookieReplyPacketJunkSize; + QString serverTransportPacketJunkSize; QString serverInitPacketMagicHeader; QString serverResponsePacketMagicHeader; QString serverUnderloadPacketMagicHeader; @@ -35,7 +49,6 @@ struct AwgConfig bool hasEqualServerSettings(const AwgConfig &other) const; bool hasEqualClientSettings(const AwgConfig &other) const; - }; class AwgConfigModel : public QAbstractListModel @@ -51,16 +64,28 @@ public: ClientJunkPacketCountRole, ClientJunkPacketMinSizeRole, ClientJunkPacketMaxSizeRole, + ClientSpecialJunk1Role, + ClientSpecialJunk2Role, + ClientSpecialJunk3Role, + ClientSpecialJunk4Role, + ClientSpecialJunk5Role, + ClientControlledJunk1Role, + ClientControlledJunk2Role, + ClientControlledJunk3Role, + ClientSpecialHandshakeTimeoutRole, ServerJunkPacketCountRole, ServerJunkPacketMinSizeRole, ServerJunkPacketMaxSizeRole, ServerInitPacketJunkSizeRole, ServerResponsePacketJunkSizeRole, + ServerCookieReplyPacketJunkSizeRole, + ServerTransportPacketJunkSizeRole, + ServerInitPacketMagicHeaderRole, ServerResponsePacketMagicHeaderRole, ServerUnderloadPacketMagicHeaderRole, - ServerTransportPacketMagicHeaderRole + ServerTransportPacketMagicHeaderRole, }; explicit AwgConfigModel(QObject *parent = nullptr); @@ -75,7 +100,7 @@ public slots: QJsonObject getConfig(); bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4); - bool isPacketSizeEqual(const int s1, const int s2); + bool isPacketSizeEqual(const int s1, const int s2/*, const int s3, const int s4*/); bool isServerSettingsEqual(); diff --git a/client/ui/qml/Components/AwgTextField.qml b/client/ui/qml/Components/AwgTextField.qml new file mode 100644 index 00000000..87b023d9 --- /dev/null +++ b/client/ui/qml/Components/AwgTextField.qml @@ -0,0 +1,15 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts + +import "../Controls2" + +TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + textField.validator: IntValidator { bottom: 0 } + + checkEmptyText: true +} diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index b8cf5f93..d97d09e8 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -115,14 +115,10 @@ PageType { KeyNavigation.tab: junkPacketCountTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jc - Junk packet count" textField.text: clientJunkPacketCount - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketCount) { @@ -130,19 +126,13 @@ PageType { } } - checkEmptyText: true - KeyNavigation.tab: junkPacketMinSizeTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jmin - Junk packet minimum size" textField.text: clientJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketMinSize) { @@ -150,28 +140,144 @@ PageType { } } - checkEmptyText: true - KeyNavigation.tab: junkPacketMaxSizeTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jmax - Junk packet maximum size" textField.text: clientJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketMaxSize) { clientJunkPacketMaxSize = textField.text } } + } - checkEmptyText: true + AwgTextField { + id: specialJunk1TextField + headerText: qsTr("I1 - First special junk packet") + textField.text: clientSpecialJunk1 + textField.validator: null + checkEmptyText: false + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk1) { + clientSpecialJunk1 = textField.text + } + } + } + + AwgTextField { + id: specialJunk2TextField + headerText: qsTr("I2 - Second special junk packet") + textField.text: clientSpecialJunk2 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk2) { + clientSpecialJunk2 = textField.text + } + } + } + + AwgTextField { + id: specialJunk3TextField + headerText: qsTr("I3 - Third special junk packet") + textField.text: clientSpecialJunk3 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk3) { + clientSpecialJunk3 = textField.text + } + } + } + + AwgTextField { + id: specialJunk4TextField + headerText: qsTr("I4 - Fourth special junk packet") + textField.text: clientSpecialJunk4 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk4) { + clientSpecialJunk4 = textField.text + } + } + } + + AwgTextField { + id: specialJunk5TextField + headerText: qsTr("I5 - Fifth special junk packet") + textField.text: clientSpecialJunk5 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk5 ) { + clientSpecialJunk5 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk1TextField + headerText: qsTr("J1 - First controlled junk packet") + textField.text: clientControlledJunk1 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk1) { + clientControlledJunk1 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk2TextField + headerText: qsTr("J2 - Second controlled junk packet") + textField.text: clientControlledJunk2 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk2) { + clientControlledJunk2 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk3TextField + headerText: qsTr("J3 - Third controlled junk packet") + textField.text: clientControlledJunk3 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk3) { + clientControlledJunk3 = textField.text + } + } + } + + AwgTextField { + id: iTimeTextField + headerText: qsTr("Itime - Special handshake timeout") + textField.text: clientSpecialHandshakeTimeout + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialHandshakeTimeout) { + clientSpecialHandshakeTimeout = textField.text + } + } } Header2TextType { @@ -181,82 +287,78 @@ PageType { text: qsTr("Server settings") } - TextFieldWithHeaderType { + AwgTextField { id: portTextField - Layout.fillWidth: true - Layout.topMargin: 8 - enabled: false headerText: qsTr("Port") textField.text: port } - TextFieldWithHeaderType { + AwgTextField { id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "S1 - Init packet junk size" textField.text: serverInitPacketJunkSize } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "S2 - Response packet junk size" textField.text: serverResponsePacketJunkSize } - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + // AwgTextField { + // id: cookieReplyPacketJunkSizeTextField + // enabled: false + // headerText: "S3 - Cookie Reply packet junk size" + // textField.text: serverCookieReplyPacketJunkSize + // } + + // AwgTextField { + // id: transportPacketJunkSizeTextField + // enabled: false + + // headerText: "S4 - Transport packet junk size" + // textField.text: serverTransportPacketJunkSize + // } + + AwgTextField { + id: initPacketMagicHeaderTextField enabled: false headerText: "H1 - Init packet magic header" textField.text: serverInitPacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H2 - Response packet magic header" textField.text: serverResponsePacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H3 - Underload packet magic header" textField.text: serverUnderloadPacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H4 - Transport packet magic header" textField.text: serverTransportPacketMagicHeader } + } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index e8fd2b94..699ae724 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -138,184 +138,139 @@ PageType { checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jc - Junk packet count") textField.text: serverJunkPacketCount - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textField.text === "") { - textField.text = "0" - } - if (textField.text !== serverJunkPacketCount) { serverJunkPacketCount = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jmin - Junk packet minimum size") textField.text: serverJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverJunkPacketMinSize) { serverJunkPacketMinSize = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jmax - Junk packet maximum size") textField.text: serverJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverJunkPacketMaxSize) { serverJunkPacketMaxSize = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("S1 - Init packet junk size") textField.text: serverInitPacketJunkSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverInitPacketJunkSize) { serverInitPacketJunkSize = textField.text } } - - checkEmptyText: true - - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } - } } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("S2 - Response packet junk size") textField.text: serverResponsePacketJunkSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverResponsePacketJunkSize) { serverResponsePacketJunkSize = textField.text } } - - checkEmptyText: true - - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } - } } - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + // AwgTextField { + // id: cookieReplyPacketJunkSizeTextField + // headerText: qsTr("S3 - Cookie reply packet junk size") + // textField.text: serverCookieReplyPacketJunkSize + // textField.onEditingFinished: { + // if (textField.text !== serverCookieReplyPacketJunkSize) { + // serverCookieReplyPacketJunkSize = textField.text + // } + // } + // } + + // AwgTextField { + // id: transportPacketJunkSizeTextField + // headerText: qsTr("S4 - Transport packet junk size") + // textField.text: serverTransportPacketJunkSize + + // textField.onEditingFinished: { + // if (textField.text !== serverTransportPacketJunkSize) { + // serverTransportPacketJunkSize = textField.text + // } + // } + // } + + AwgTextField { + id: initPacketMagicHeaderTextField headerText: qsTr("H1 - Init packet magic header") textField.text: serverInitPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverInitPacketMagicHeader) { serverInitPacketMagicHeader = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("H2 - Response packet magic header") textField.text: serverResponsePacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverResponsePacketMagicHeader) { serverResponsePacketMagicHeader = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("H4 - Transport packet magic header") - textField.text: serverTransportPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text !== serverTransportPacketMagicHeader) { - serverTransportPacketMagicHeader = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { + AwgTextField { id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("H3 - Underload packet magic header") textField.text: serverUnderloadPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverUnderloadPacketMagicHeader) { serverUnderloadPacketMagicHeader = textField.text } } - - checkEmptyText: true } + AwgTextField { + id: transportPacketMagicHeaderTextField + headerText: qsTr("H4 - Transport packet magic header") + textField.text: serverTransportPacketMagicHeader + + textField.onEditingFinished: { + if (textField.text !== serverTransportPacketMagicHeader) { + serverTransportPacketMagicHeader = textField.text + } + } + } + + BasicButtonType { id: saveRestartButton @@ -328,6 +283,8 @@ PageType { responsePacketMagicHeaderTextField.errorText === "" && initPacketMagicHeaderTextField.errorText === "" && responsePacketJunkSizeTextField.errorText === "" && + // cookieReplyHeaderJunkTextField.errorText === "" && + // transportHeaderJunkTextField.errorText === "" && initPacketJunkSizeTextField.errorText === "" && junkPacketMaxSizeTextField.errorText === "" && junkPacketMinSizeTextField.errorText === "" && @@ -360,6 +317,13 @@ PageType { PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) return } + // if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), + // parseInt(responsePacketJunkSizeTextField.textField.text), + // parseInt(cookieReplyPacketJunkSizeTextField.textField.text), + // parseInt(transportPacketJunkSizeTextField.textField.text))) { + // PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92) + S3 + cookie reply size (64) + S4 + transport packet size (32)")) + // return + // } } var headerText = qsTr("Save settings?") From 5445e6637b6f126db78c3666828e2f9ed0c5e964 Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 8 Jul 2025 14:25:03 +0800 Subject: [PATCH 35/37] chore: minor fixes (#1616) * chore: removed unnecessary qdebug * fix: return soft and hide strict killswitch --- .../ui/controllers/api/apiConfigsController.cpp | 2 -- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 15 ++++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 0b0a9b92..0f42beb7 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -221,8 +221,6 @@ namespace serverConfig[configKey::apiConfig] = apiConfig; - qDebug() << serverConfig; - return ErrorCode::NoError; } } diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index ca1cd0d4..d6d73b20 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -62,8 +62,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: false - // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: !SettingsController.strictKillSwitchEnabled text: qsTr("Soft KillSwitch") @@ -74,9 +73,7 @@ PageType { } } - DividerType { - visible: false - } + DividerType {} VerticalRadioButton { id: strictKillSwitch @@ -84,7 +81,9 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + visible: false + enabled: false + // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") @@ -106,7 +105,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } LabelWithButtonType { Layout.topMargin: 32 From 10a107716cf3a566f77e99a56066140e61bdae0b Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 8 Jul 2025 15:06:52 +0800 Subject: [PATCH 36/37] fix: fixed awg 1.5 fields processing for ios (#1700) --- client/platforms/ios/WGConfig.swift | 76 ++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift index 8f693387..537687f1 100644 --- a/client/platforms/ios/WGConfig.swift +++ b/client/platforms/ios/WGConfig.swift @@ -46,29 +46,59 @@ struct WGConfig: Decodable { } var settings: String { - junkPacketCount == nil ? "" : - """ - Jc = \(junkPacketCount!) - Jmin = \(junkPacketMinSize!) - Jmax = \(junkPacketMaxSize!) - S1 = \(initPacketJunkSize!) - S2 = \(responsePacketJunkSize!) - S3 = \(cookieReplyPacketJunkSize!) - S4 = \(transportPacketJunkSize!) - H1 = \(initPacketMagicHeader!) - H2 = \(responsePacketMagicHeader!) - H3 = \(underloadPacketMagicHeader!) - H4 = \(transportPacketMagicHeader!) - I1 = \(specialJunk1!) - I2 = \(specialJunk2!) - I3 = \(specialJunk3!) - I4 = \(specialJunk4!) - I5 = \(specialJunk5!) - J1 = \(controlledJunk1!) - J2 = \(controlledJunk2!) - J3 = \(controlledJunk3!) - Itime = \(specialHandshakeTimeout!) - """ + guard junkPacketCount != nil else { return "" } + + var settingsLines: [String] = [] + + // Required parameters when junkPacketCount is present + settingsLines.append("Jc = \(junkPacketCount!)") + settingsLines.append("Jmin = \(junkPacketMinSize!)") + settingsLines.append("Jmax = \(junkPacketMaxSize!)") + settingsLines.append("S1 = \(initPacketJunkSize!)") + settingsLines.append("S2 = \(responsePacketJunkSize!)") + + settingsLines.append("H1 = \(initPacketMagicHeader!)") + settingsLines.append("H2 = \(responsePacketMagicHeader!)") + settingsLines.append("H3 = \(underloadPacketMagicHeader!)") + settingsLines.append("H4 = \(transportPacketMagicHeader!)") + + // Optional parameters - only add if not nil and not empty + if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty { + settingsLines.append("S3 = \(s3)") + } + if let s4 = transportPacketJunkSize, !s4.isEmpty { + settingsLines.append("S4 = \(s4)") + } + + if let i1 = specialJunk1, !i1.isEmpty { + settingsLines.append("I1 = \(i1)") + } + if let i2 = specialJunk2, !i2.isEmpty { + settingsLines.append("I2 = \(i2)") + } + if let i3 = specialJunk3, !i3.isEmpty { + settingsLines.append("I3 = \(i3)") + } + if let i4 = specialJunk4, !i4.isEmpty { + settingsLines.append("I4 = \(i4)") + } + if let i5 = specialJunk5, !i5.isEmpty { + settingsLines.append("I5 = \(i5)") + } + if let j1 = controlledJunk1, !j1.isEmpty { + settingsLines.append("J1 = \(j1)") + } + if let j2 = controlledJunk2, !j2.isEmpty { + settingsLines.append("J2 = \(j2)") + } + if let j3 = controlledJunk3, !j3.isEmpty { + settingsLines.append("J3 = \(j3)") + } + if let itime = specialHandshakeTimeout, !itime.isEmpty { + settingsLines.append("Itime = \(itime)") + } + + return settingsLines.joined(separator: "\n") } var str: String {