mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
update UI XRay, add new page PageProtocolXrayTransportSettings.qml PageProtocolXrayXmuxSettings.qml PageProtocolXrayXPaddingSettings.qml
This commit is contained in:
@@ -80,7 +80,11 @@ namespace PageLoader
|
||||
PageSetupWizardApiPremiumInfo,
|
||||
PageSetupWizardApiTrialEmail,
|
||||
|
||||
PageDevMenu
|
||||
PageDevMenu,
|
||||
|
||||
PageProtocolXrayTransportSettings,
|
||||
PageProtocolXrayXmuxSettings,
|
||||
PageProtocolXrayXPaddingSettings,
|
||||
};
|
||||
Q_ENUM_NS(PageEnum)
|
||||
|
||||
|
||||
61
client/ui/qml/Controls2/MinMaxRowType.qml
Normal file
61
client/ui/qml/Controls2/MinMaxRowType.qml
Normal file
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
// MinMaxRowType — two side-by-side labeled text fields: Min / Max
|
||||
// Usage:
|
||||
// MinMaxRowType {
|
||||
// minValue: "0"
|
||||
// maxValue: "0"
|
||||
// onMinChanged: someProperty = val
|
||||
// onMaxChanged: someProperty = val
|
||||
// }
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string minValue: "0"
|
||||
property string maxValue: "0"
|
||||
|
||||
signal minChanged(string val)
|
||||
signal maxChanged(string val)
|
||||
|
||||
implicitHeight: row.implicitHeight
|
||||
implicitWidth: row.implicitWidth
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
// Min field
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Min")
|
||||
textField.text: root.minValue
|
||||
textField.validator: IntValidator { bottom: 0 }
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== root.minValue) {
|
||||
root.minChanged(textField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Max field
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Max")
|
||||
textField.text: root.maxValue
|
||||
textField.validator: IntValidator { bottom: 0 }
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== root.maxValue) {
|
||||
root.maxChanged(textField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,19 +46,52 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
property alias focusItemId: portTextField.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
BaseHeaderType {
|
||||
// ── Header ────────────────────────────────────────────────
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
headerText: qsTr("XRay settings")
|
||||
Layout.topMargin: 0
|
||||
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("XRay\nVLESS")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
image: "qrc:/images/controls/more-vertical.svg"
|
||||
imageColor: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
}
|
||||
|
||||
// ── "More about settings" link ────────────────────────────
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 4
|
||||
|
||||
text: qsTr("More about settings")
|
||||
color: AmneziaStyle.color.burntOrange
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally("https://docs.amnezia.org")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Port field ────────────────────────────────────────────
|
||||
TextFieldWithHeaderType {
|
||||
id: textFieldWithHeaderType
|
||||
id: portTextField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
@@ -67,40 +100,12 @@ PageType {
|
||||
|
||||
enabled: listView.enabled
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: portTextField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: listView.enabled
|
||||
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) {
|
||||
@@ -111,12 +116,70 @@ PageType {
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
// ── Transport row ─────────────────────────────────────────
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Transport")
|
||||
descriptionText: "RAW (TCP)" // TODO: model role
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
enabled: listView.enabled
|
||||
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXrayTransportSettings)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Security row ──────────────────────────────────────────
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Security")
|
||||
descriptionText: "TLS" // TODO: model role
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
enabled: listView.enabled
|
||||
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXraySecuritySettings)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Flow row ──────────────────────────────────────────────
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Flow")
|
||||
descriptionText: "xtls-rprx-vision" // TODO: model role
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
enabled: listView.enabled
|
||||
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXrayFlowSettings)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Spacer ────────────────────────────────────────────────
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 24
|
||||
}
|
||||
|
||||
// ── Save button ───────────────────────────────────────────
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -124,7 +187,7 @@ PageType {
|
||||
|
||||
text: qsTr("Save")
|
||||
|
||||
onClicked: function() {
|
||||
onClicked: function () {
|
||||
forceActiveFocus()
|
||||
|
||||
var headerText = qsTr("Save settings?")
|
||||
@@ -132,16 +195,16 @@ PageType {
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
var yesButtonFunction = function () {
|
||||
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
var noButtonFunction = function () {
|
||||
if (!GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
@@ -152,6 +215,37 @@ PageType {
|
||||
Keys.onEnterPressed: saveButton.clicked()
|
||||
Keys.onReturnPressed: saveButton.clicked()
|
||||
}
|
||||
|
||||
// ── Reset settings ────────────────────────────────────────
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Reset settings")
|
||||
textColor: AmneziaStyle.color.vibrantRed
|
||||
visible: listView.enabled
|
||||
|
||||
clickedFunction: function () {
|
||||
var headerText = qsTr("Reset settings?")
|
||||
var descriptionText = qsTr("All XRay settings will be restored to defaults.")
|
||||
var yesButtonText = qsTr("Reset")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function () {
|
||||
XrayConfigModel.resetToDefaults()
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText,
|
||||
yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bottom padding ────────────────────────────────────────
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
702
client/ui/qml/Pages2/PageProtocolXrayTransportSettings.qml
Normal file
702
client/ui/qml/Pages2/PageProtocolXrayTransportSettings.qml
Normal file
@@ -0,0 +1,702 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
// Temporary local state — will be replaced by model roles
|
||||
property int selectedTransport: 0 // 0=RAW, 1=XHTTP, 2=mKCP
|
||||
|
||||
// XHTTP fields
|
||||
property string xhttpMode: "Auto"
|
||||
property string xhttpHost: "www.googletagmanager.com"
|
||||
property string xhttpPath: ""
|
||||
property string xhttpHeadersTemplate: "HTTP"
|
||||
property string xhttpUplinkMethod: "POST"
|
||||
property bool xhttpDisableGrpc: true
|
||||
property bool xhttpDisableSse: true
|
||||
// Session & Sequence
|
||||
property string sessionPlacement: "Path"
|
||||
property string sessionKey: "Path"
|
||||
property string seqPlacement: "Path"
|
||||
property string seqKey: ""
|
||||
property string uplinkDataPlacement: "Body"
|
||||
property string uplinkDataKey: ""
|
||||
// Traffic Shaping
|
||||
property string uplinkChunkSize: "0"
|
||||
property string scMaxBufferedPosts: ""
|
||||
// mKCP fields
|
||||
property string mkcpTti: ""
|
||||
property string mkcpUplinkCapacity: ""
|
||||
property string mkcpDownlinkCapacity: ""
|
||||
property string mkcpReadBufferSize: ""
|
||||
property string mkcpWriteBufferSize: ""
|
||||
property bool mkcpCongestion: true
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
contentHeight: mainColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
width: flickable.width
|
||||
spacing: 0
|
||||
|
||||
// ── Header ────────────────────────────────────────────────
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 24
|
||||
text: qsTr("Transport")
|
||||
}
|
||||
|
||||
// ── Radio: RAW (TCP) ──────────────────────────────────────
|
||||
VerticalRadioButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("RAW (TCP)")
|
||||
checked: root.selectedTransport === 0
|
||||
onClicked: root.selectedTransport = 0
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Radio: XHTTP ──────────────────────────────────────────
|
||||
VerticalRadioButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("XHTTP")
|
||||
descriptionText: qsTr("Advanced users")
|
||||
checked: root.selectedTransport === 1
|
||||
onClicked: root.selectedTransport = 1
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Radio: mKCP ───────────────────────────────────────────
|
||||
VerticalRadioButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: qsTr("mKCP")
|
||||
checked: root.selectedTransport === 2
|
||||
onClicked: root.selectedTransport = 2
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// mKCP Settings (visible when mKCP selected)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
ColumnLayout {
|
||||
visible: root.selectedTransport === 2
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("mKCP Settings")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("TTI")
|
||||
textField.text: root.mkcpTti
|
||||
textField.onEditingFinished: root.mkcpTti = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("uplinkCapacity")
|
||||
textField.text: root.mkcpUplinkCapacity
|
||||
textField.onEditingFinished: root.mkcpUplinkCapacity = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("downlinkCapacity")
|
||||
textField.text: root.mkcpDownlinkCapacity
|
||||
textField.onEditingFinished: root.mkcpDownlinkCapacity = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("readBufferSize")
|
||||
textField.text: root.mkcpReadBufferSize
|
||||
textField.onEditingFinished: root.mkcpReadBufferSize = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("writeBufferSize")
|
||||
textField.text: root.mkcpWriteBufferSize
|
||||
textField.onEditingFinished: root.mkcpWriteBufferSize = textField.text
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
Layout.topMargin: 8
|
||||
text: qsTr("Congestion")
|
||||
checked: root.mkcpCongestion
|
||||
onToggled: root.mkcpCongestion = checked
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// XHTTP Settings (visible when XHTTP selected)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
ColumnLayout {
|
||||
visible: root.selectedTransport === 1
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
// Mode dropdown
|
||||
DropDownType {
|
||||
id: modeDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.xhttpMode
|
||||
descriptionText: qsTr("Mode")
|
||||
headerText: qsTr("Mode")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
id: modeListView
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Auto"
|
||||
}
|
||||
ListElement {
|
||||
name: "Packet-up"
|
||||
}
|
||||
ListElement {
|
||||
name: "Stream-up"
|
||||
}
|
||||
ListElement {
|
||||
name: "Stream-one"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.xhttpMode = selectedText
|
||||
modeDropDown.text = selectedText
|
||||
modeDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.xhttpMode) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Profile label
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("HTTP Profile")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Host")
|
||||
textField.text: root.xhttpHost
|
||||
textField.onEditingFinished: root.xhttpHost = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Path")
|
||||
textField.text: root.xhttpPath
|
||||
textField.onEditingFinished: root.xhttpPath = textField.text
|
||||
}
|
||||
|
||||
// Headers template dropdown
|
||||
DropDownType {
|
||||
id: headersDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.xhttpHeadersTemplate
|
||||
descriptionText: qsTr("Headers template")
|
||||
headerText: qsTr("Headers template")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "HTTP"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.xhttpHeadersTemplate = selectedText
|
||||
headersDropDown.text = selectedText
|
||||
headersDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.xhttpHeadersTemplate) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UplinkHTTPMethod dropdown
|
||||
DropDownType {
|
||||
id: uplinkMethodDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.xhttpUplinkMethod
|
||||
descriptionText: qsTr("UplinkHTTPMethod")
|
||||
headerText: qsTr("UplinkHTTPMethod")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "POST"
|
||||
}
|
||||
ListElement {
|
||||
name: "PUT"
|
||||
}
|
||||
ListElement {
|
||||
name: "PATCH"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.xhttpUplinkMethod = selectedText
|
||||
uplinkMethodDropDown.text = selectedText
|
||||
uplinkMethodDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.xhttpUplinkMethod) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable gRPC Header
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
Layout.topMargin: 16
|
||||
text: qsTr("Disable gRPC Header")
|
||||
descriptionText: qsTr("noGRPCHeader")
|
||||
checked: root.xhttpDisableGrpc
|
||||
onToggled: root.xhttpDisableGrpc = checked
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// Disable SSE Header
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
text: qsTr("Disable SSE Header")
|
||||
descriptionText: qsTr("noSSEHeader")
|
||||
checked: root.xhttpDisableSse
|
||||
onToggled: root.xhttpDisableSse = checked
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Session & Sequence ────────────────────────────────
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("Session & Sequence")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: sessionPlacementDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.sessionPlacement
|
||||
descriptionText: qsTr("SessionPlacement")
|
||||
headerText: qsTr("SessionPlacement")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.sessionPlacement = selectedText
|
||||
sessionPlacementDropDown.text = selectedText
|
||||
sessionPlacementDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.sessionPlacement) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: sessionKeyDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.sessionKey
|
||||
descriptionText: qsTr("SessionKey")
|
||||
headerText: qsTr("SessionKey")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.sessionKey = selectedText
|
||||
sessionKeyDropDown.text = selectedText
|
||||
sessionKeyDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.sessionKey) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: seqPlacementDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.seqPlacement
|
||||
descriptionText: qsTr("SeqPlacement")
|
||||
headerText: qsTr("SeqPlacement")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.seqPlacement = selectedText
|
||||
seqPlacementDropDown.text = selectedText
|
||||
seqPlacementDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.seqPlacement) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("SeqKey")
|
||||
textField.text: root.seqKey
|
||||
textField.onEditingFinished: root.seqKey = textField.text
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: uplinkDataPlacementDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.uplinkDataPlacement
|
||||
descriptionText: qsTr("UplinkDataPlacement")
|
||||
headerText: qsTr("UplinkDataPlacement")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Body"
|
||||
}
|
||||
ListElement {
|
||||
name: "Query"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.uplinkDataPlacement = selectedText
|
||||
uplinkDataPlacementDropDown.text = selectedText
|
||||
uplinkDataPlacementDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.uplinkDataPlacement) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("UplinkDataKey")
|
||||
textField.text: root.uplinkDataKey
|
||||
textField.onEditingFinished: root.uplinkDataKey = textField.text
|
||||
}
|
||||
|
||||
// ── Traffic Shaping ───────────────────────────────────
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("Traffic Shaping")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("UplinkChunkSize")
|
||||
textField.text: root.uplinkChunkSize
|
||||
textField.validator: IntValidator {
|
||||
bottom: 0
|
||||
}
|
||||
textField.onEditingFinished: root.uplinkChunkSize = textField.text
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("scMaxBufferedPosts")
|
||||
textField.text: root.scMaxBufferedPosts
|
||||
textField.onEditingFinished: root.scMaxBufferedPosts = textField.text
|
||||
}
|
||||
|
||||
// scMaxEachPostBytes → nav row
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
text: qsTr("scMaxEachPostBytes")
|
||||
descriptionText: qsTr("1—100")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () { /* navigate */
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// scMinPostsIntervalMs → nav row
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("scMinPostsIntervalMs")
|
||||
descriptionText: qsTr("100—600ms")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () { /* navigate */
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// scStreamUpServerSecs → nav row
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("scStreamUpServerSecs")
|
||||
descriptionText: qsTr("1—100")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () { /* navigate */
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Padding and multiplexing ──────────────────────────
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("Padding and multiplexing")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("xPadding")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXrayXPaddingSettings)
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("XMux")
|
||||
descriptionText: qsTr("On")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXrayXmuxSettings)
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save button ───────────────────────────────────────────────────
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
text: qsTr("Save")
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
// XrayConfigModel.setTransport(...)
|
||||
}
|
||||
Keys.onEnterPressed: clicked()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
}
|
||||
211
client/ui/qml/Pages2/PageProtocolXrayXPaddingSettings.qml
Normal file
211
client/ui/qml/Pages2/PageProtocolXrayXPaddingSettings.qml
Normal file
@@ -0,0 +1,211 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
// Temporary local state
|
||||
property string xPaddingBytes: "0—0"
|
||||
property bool xPaddingObfsMode: true
|
||||
property string xPaddingKey: "www.googletagmanager.com"
|
||||
property string xPaddingHeader: ""
|
||||
property string xPaddingPlacement: "Cookie"
|
||||
property string xPaddingMethod: "Repeat-x"
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
contentHeight: mainColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
width: flickable.width
|
||||
spacing: 0
|
||||
|
||||
// ── Header ────────────────────────────────────────────────
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 24
|
||||
text: qsTr("xPadding")
|
||||
}
|
||||
|
||||
// ── xPaddingBytes nav row ─────────────────────────────────
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("xPaddingBytes")
|
||||
descriptionText: root.xPaddingBytes
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
clickedFunction: function () {
|
||||
PageController.goToPage(PageEnum.PageProtocolXrayXPaddingBytesSettings)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── xPaddingObfsMode switcher ─────────────────────────────
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
text: qsTr("xPaddingObfsMode")
|
||||
checked: root.xPaddingObfsMode
|
||||
onToggled: root.xPaddingObfsMode = checked
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── xPaddingKey ───────────────────────────────────────────
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
headerText: qsTr("xPaddingKey")
|
||||
textField.text: root.xPaddingKey
|
||||
textField.onEditingFinished: root.xPaddingKey = textField.text
|
||||
}
|
||||
|
||||
// ── xPaddingHeader ────────────────────────────────────────
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("xPaddingHeader")
|
||||
textField.text: root.xPaddingHeader
|
||||
textField.onEditingFinished: root.xPaddingHeader = textField.text
|
||||
}
|
||||
|
||||
// ── xPaddingPlacement dropdown ────────────────────────────
|
||||
DropDownType {
|
||||
id: placementDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.xPaddingPlacement
|
||||
descriptionText: qsTr("xPaddingPlacement")
|
||||
headerText: qsTr("xPaddingPlacement")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Query"
|
||||
}
|
||||
ListElement {
|
||||
name: "Body"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.xPaddingPlacement = selectedText
|
||||
placementDropDown.text = selectedText
|
||||
placementDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.xPaddingPlacement) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── xPaddingMethod dropdown ───────────────────────────────
|
||||
DropDownType {
|
||||
id: methodDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: root.xPaddingMethod
|
||||
descriptionText: qsTr("xPaddingMethod")
|
||||
headerText: qsTr("xPaddingMethod")
|
||||
drawerParent: root
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Repeat-x"
|
||||
}
|
||||
ListElement {
|
||||
name: "Random"
|
||||
}
|
||||
ListElement {
|
||||
name: "Zero"
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
root.xPaddingMethod = selectedText
|
||||
methodDropDown.text = selectedText
|
||||
methodDropDown.closeTriggered()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
if (model.get(i).name === root.xPaddingMethod) {
|
||||
selectedIndex = i;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save button ───────────────────────────────────────────────────
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
text: qsTr("Save")
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
// XrayConfigModel.setXPadding(...)
|
||||
}
|
||||
Keys.onEnterPressed: clicked()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
}
|
||||
219
client/ui/qml/Pages2/PageProtocolXrayXmuxSettings.qml
Normal file
219
client/ui/qml/Pages2/PageProtocolXrayXmuxSettings.qml
Normal file
@@ -0,0 +1,219 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
// Temporary local state
|
||||
property bool xmuxEnabled: true
|
||||
property string maxConcurrencyMin: "0"
|
||||
property string maxConcurrencyMax: "0"
|
||||
property string maxConnectionsMin: "0"
|
||||
property string maxConnectionsMax: "0"
|
||||
property string cMaxReuseTimesMin: "0"
|
||||
property string cMaxReuseTimesMax: "0"
|
||||
property string hMaxRequestTimesMin: "0"
|
||||
property string hMaxRequestTimesMax: "0"
|
||||
property string hMaxReusableSecsMin: "0"
|
||||
property string hMaxReusableSecsMax: "0"
|
||||
property string hKeepAlivePeriod: ""
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
contentHeight: mainColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
width: flickable.width
|
||||
spacing: 0
|
||||
|
||||
// ── Header ────────────────────────────────────────────────
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 24
|
||||
text: qsTr("xmux")
|
||||
}
|
||||
|
||||
// ── xmux master switcher ──────────────────────────────────
|
||||
SwitcherType {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 16
|
||||
text: qsTr("xmux")
|
||||
checked: root.xmuxEnabled
|
||||
onToggled: root.xmuxEnabled = checked
|
||||
}
|
||||
|
||||
DividerType {
|
||||
}
|
||||
|
||||
// ── Min/Max pairs (only when enabled) ─────────────────────
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
enabled: root.xmuxEnabled
|
||||
|
||||
// maxConcurrency
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("maxConcurrency")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: root.maxConcurrencyMin
|
||||
maxValue: root.maxConcurrencyMax
|
||||
onMinChanged: root.maxConcurrencyMin = val
|
||||
onMaxChanged: root.maxConcurrencyMax = val
|
||||
}
|
||||
|
||||
// maxConnections
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("maxConnections")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: root.maxConnectionsMin
|
||||
maxValue: root.maxConnectionsMax
|
||||
onMinChanged: root.maxConnectionsMin = val
|
||||
onMaxChanged: root.maxConnectionsMax = val
|
||||
}
|
||||
|
||||
// cMaxReuseTimes
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("cMaxReuseTimes")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: root.cMaxReuseTimesMin
|
||||
maxValue: root.cMaxReuseTimesMax
|
||||
onMinChanged: root.cMaxReuseTimesMin = val
|
||||
onMaxChanged: root.cMaxReuseTimesMax = val
|
||||
}
|
||||
|
||||
// hMaxRequestTimes
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("hMaxRequestTimes")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: root.hMaxRequestTimesMin
|
||||
maxValue: root.hMaxRequestTimesMax
|
||||
onMinChanged: root.hMaxRequestTimesMin = val
|
||||
onMaxChanged: root.hMaxRequestTimesMax = val
|
||||
}
|
||||
|
||||
// hMaxReusableSecs
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("hMaxReusableSecs")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: root.hMaxReusableSecsMin
|
||||
maxValue: root.hMaxReusableSecsMax
|
||||
onMinChanged: root.hMaxReusableSecsMin = val
|
||||
onMaxChanged: root.hMaxReusableSecsMax = val
|
||||
}
|
||||
|
||||
// hKeepAlivePeriod — single field
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
headerText: qsTr("hKeepAlivePeriod")
|
||||
textField.text: root.hKeepAlivePeriod
|
||||
textField.validator: IntValidator {
|
||||
bottom: 0
|
||||
}
|
||||
textField.onEditingFinished: root.hKeepAlivePeriod = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save button ───────────────────────────────────────────────────
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
text: qsTr("Save")
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
// XrayConfigModel.setXmux(...)
|
||||
}
|
||||
Keys.onEnterPressed: clicked()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,12 @@
|
||||
<file>Pages2/PageProtocolRaw.qml</file>
|
||||
<file>Pages2/PageProtocolWireGuardSettings.qml</file>
|
||||
<file>Pages2/PageProtocolXraySettings.qml</file>
|
||||
|
||||
<file>Pages2/PageProtocolXrayTransportSettings.qml</file>
|
||||
<file>Pages2/PageProtocolXrayXmuxSettings.qml</file>
|
||||
<file>Pages2/PageProtocolXrayXPaddingSettings.qml</file>
|
||||
<file>Controls2/MinMaxRowType.qml</file>
|
||||
|
||||
<file>Pages2/PageServiceDnsSettings.qml</file>
|
||||
<file>Pages2/PageServiceSftpSettings.qml</file>
|
||||
<file>Pages2/PageServiceSocksProxySettings.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user