update UI XRay, add new page PageProtocolXrayTransportSettings.qml PageProtocolXrayXmuxSettings.qml PageProtocolXrayXPaddingSettings.qml

This commit is contained in:
dranik
2026-03-24 10:33:41 +03:00
parent c28452a5da
commit bbd07f6bad
7 changed files with 1339 additions and 42 deletions

View File

@@ -80,7 +80,11 @@ namespace PageLoader
PageSetupWizardApiPremiumInfo,
PageSetupWizardApiTrialEmail,
PageDevMenu
PageDevMenu,
PageProtocolXrayTransportSettings,
PageProtocolXrayXmuxSettings,
PageProtocolXrayXPaddingSettings,
};
Q_ENUM_NS(PageEnum)

View 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)
}
}
}
}
}

View File

@@ -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
}
}
}
}

View 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()
}
}

View 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()
}
}

View 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()
}
}

View File

@@ -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>