mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
fixed ui
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
@@ -18,15 +19,125 @@
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
}
|
||||
namespace
|
||||
{
|
||||
Logger logger("XrayConfigurator");
|
||||
QString normalizeXhttpMode(const QString &m)
|
||||
{
|
||||
const QString t = m.trimmed();
|
||||
if (t.isEmpty() || t.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("auto");
|
||||
}
|
||||
if (t.compare(QLatin1String("Packet-up"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("packet-up");
|
||||
if (t.compare(QLatin1String("Stream-up"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("stream-up");
|
||||
if (t.compare(QLatin1String("Stream-one"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("stream-one");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
// Xray-core: empty → path; "None" in UI → omit (core default path)
|
||||
QString normalizeSessionSeqPlacement(const QString &p)
|
||||
{
|
||||
if (p.isEmpty() || p.compare(QLatin1String("None"), Qt::CaseInsensitive) == 0)
|
||||
return {};
|
||||
return p.toLower();
|
||||
}
|
||||
|
||||
QString normalizeUplinkDataPlacement(const QString &p)
|
||||
{
|
||||
if (p.isEmpty() || p.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("body");
|
||||
if (p.compare(QLatin1String("Auto"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("auto");
|
||||
if (p.compare(QLatin1String("Query"), Qt::CaseInsensitive) == 0)
|
||||
// "Query" is not valid for uplink payload in splithttp; closest documented mode
|
||||
return QStringLiteral("header");
|
||||
return p.toLower();
|
||||
}
|
||||
|
||||
// splithttp: cookie | header | query | queryInHeader (not "body")
|
||||
QString normalizeXPaddingPlacement(const QString &p)
|
||||
{
|
||||
QString t = p.trimmed();
|
||||
if (t.isEmpty())
|
||||
return QString::fromLatin1(amnezia::protocols::xray::defaultXPaddingPlacement).toLower();
|
||||
if (t.compare(QLatin1String("Body"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("queryInHeader");
|
||||
if (t.contains(QLatin1String("queryInHeader"), Qt::CaseInsensitive)
|
||||
|| t.compare(QLatin1String("Query in header"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("queryInHeader");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
// splithttp: repeat-x | tokenish
|
||||
QString normalizeXPaddingMethod(const QString &m)
|
||||
{
|
||||
QString t = m.trimmed();
|
||||
if (t.isEmpty() || t.compare(QLatin1String("Repeat-x"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("repeat-x");
|
||||
if (t.compare(QLatin1String("Tokenish"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("tokenish");
|
||||
if (t.compare(QLatin1String("Random"), Qt::CaseInsensitive) == 0
|
||||
|| t.compare(QLatin1String("Zero"), Qt::CaseInsensitive) == 0)
|
||||
return QStringLiteral("repeat-x");
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
void putIntRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
|
||||
const char *fallbackMax)
|
||||
{
|
||||
if (minV.isEmpty() && maxV.isEmpty())
|
||||
return;
|
||||
if (minV.isEmpty())
|
||||
minV = QString::fromLatin1(fallbackMin);
|
||||
if (maxV.isEmpty())
|
||||
maxV = QString::fromLatin1(fallbackMax);
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = minV.toInt();
|
||||
r[QStringLiteral("to")] = maxV.toInt();
|
||||
obj[QString::fromUtf8(key)] = r;
|
||||
}
|
||||
|
||||
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
|
||||
void sanitizeXrayNativeConfig(amnezia::ProtocolConfig &pc)
|
||||
{
|
||||
QString c = pc.nativeConfig();
|
||||
if (c.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
bool changed = false;
|
||||
if (c.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.replace(QLatin1String("Mozilla/5.0"), QString::fromLatin1(amnezia::protocols::xray::defaultFingerprint),
|
||||
Qt::CaseInsensitive);
|
||||
changed = true;
|
||||
}
|
||||
const QString legacyListen = QString::fromLatin1(amnezia::protocols::xray::defaultLocalAddr);
|
||||
const QString listenOk = QString::fromLatin1(amnezia::protocols::xray::defaultLocalListenAddr);
|
||||
if (c.contains(legacyListen)) {
|
||||
c.replace(legacyListen, listenOk);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
pc.setNativeConfig(c);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
|
||||
: ConfiguratorBase(sshSession, parent)
|
||||
{
|
||||
}
|
||||
|
||||
amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig)
|
||||
{
|
||||
applyDnsToNativeConfig(settings.dns, protocolConfig);
|
||||
sanitizeXrayNativeConfig(protocolConfig);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
@@ -143,222 +254,162 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
QJsonObject streamSettings;
|
||||
const auto &xhttp = srv.xhttp;
|
||||
const auto &mkcp = srv.mkcp;
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
// network
|
||||
QString networkValue = "tcp";
|
||||
if (srv.transport == "xhttp")
|
||||
{
|
||||
networkValue = "xhttp";
|
||||
}
|
||||
else if (srv.transport == "mkcp")
|
||||
{
|
||||
networkValue = "kcp";
|
||||
}
|
||||
streamSettings[amnezia::protocols::xray::network] = networkValue;
|
||||
QString networkValue = QStringLiteral("tcp");
|
||||
if (srv.transport == QLatin1String("xhttp"))
|
||||
networkValue = QStringLiteral("xhttp");
|
||||
else if (srv.transport == QLatin1String("mkcp"))
|
||||
networkValue = QStringLiteral("kcp");
|
||||
streamSettings[px::network] = networkValue;
|
||||
|
||||
// security
|
||||
streamSettings[amnezia::protocols::xray::security] = srv.security;
|
||||
streamSettings[px::security] = srv.security;
|
||||
|
||||
// TLS settings
|
||||
if (srv.security == "tls") {
|
||||
if (srv.security == QLatin1String("tls")) {
|
||||
QJsonObject tlsSettings;
|
||||
if (!srv.sni.isEmpty()) {
|
||||
tlsSettings[amnezia::protocols::xray::serverName] = srv.sni;
|
||||
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
|
||||
tlsSettings[px::serverName] = sniEff;
|
||||
const QString alpnEff = srv.alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : srv.alpn;
|
||||
QJsonArray alpnArray;
|
||||
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
|
||||
const QString t = a.trimmed();
|
||||
if (!t.isEmpty())
|
||||
alpnArray.append(t);
|
||||
}
|
||||
if (!srv.alpn.isEmpty()) {
|
||||
QJsonArray alpnArray;
|
||||
// alpn may be comma-separated: "HTTP/2,HTTP/1.1"
|
||||
for (const QString &a : srv.alpn.split(",")) {
|
||||
alpnArray.append(a.trimmed());
|
||||
}
|
||||
tlsSettings["alpn"] = alpnArray;
|
||||
}
|
||||
if (!srv.fingerprint.isEmpty()) {
|
||||
tlsSettings[amnezia::protocols::xray::fingerprint] = srv.fingerprint;
|
||||
}
|
||||
streamSettings["tlsSettings"] = tlsSettings;
|
||||
if (!alpnArray.isEmpty())
|
||||
tlsSettings[QStringLiteral("alpn")] = alpnArray;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
tlsSettings[px::fingerprint] = fpEff;
|
||||
streamSettings[QStringLiteral("tlsSettings")] = tlsSettings;
|
||||
}
|
||||
|
||||
// Reality settings
|
||||
if (srv.security == "reality") {
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
QJsonObject realSettings;
|
||||
if (!srv.fingerprint.isEmpty())
|
||||
{
|
||||
realSettings[amnezia::protocols::xray::fingerprint] = srv.fingerprint;
|
||||
}
|
||||
if (!srv.sni.isEmpty())
|
||||
{
|
||||
realSettings[amnezia::protocols::xray::serverName] = srv.sni;
|
||||
}
|
||||
// publicKey and shortId are filled in createConfig after fetching from server
|
||||
streamSettings[amnezia::protocols::xray::realitySettings] = realSettings;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
realSettings[px::fingerprint] = fpEff;
|
||||
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
|
||||
realSettings[px::serverName] = sniEff;
|
||||
streamSettings[px::realitySettings] = realSettings;
|
||||
}
|
||||
|
||||
// XHTTP transport settings
|
||||
if (srv.transport == "xhttp") {
|
||||
QJsonObject xhttpObj;
|
||||
|
||||
if (!xhttp.host.isEmpty())
|
||||
{
|
||||
xhttpObj["host"] = xhttp.host;
|
||||
}
|
||||
// XHTTP — JSON must match Xray-core SplitHTTPConfig (flat xPadding fields, see transport_internet.go)
|
||||
if (srv.transport == QLatin1String("xhttp")) {
|
||||
QJsonObject xo;
|
||||
const QString hostEff = xhttp.host.isEmpty() ? QString::fromLatin1(px::defaultXhttpHost) : xhttp.host;
|
||||
xo[QStringLiteral("host")] = hostEff;
|
||||
if (!xhttp.path.isEmpty())
|
||||
{
|
||||
xhttpObj["path"] = xhttp.path;
|
||||
}
|
||||
if (!xhttp.mode.isEmpty())
|
||||
{
|
||||
xhttpObj["mode"] = xhttp.mode;
|
||||
}
|
||||
xo[QStringLiteral("path")] = xhttp.path;
|
||||
xo[QStringLiteral("mode")] = normalizeXhttpMode(xhttp.mode);
|
||||
|
||||
// headers
|
||||
if (xhttp.headersTemplate == "HTTP") {
|
||||
if (xhttp.headersTemplate.compare(QLatin1String("HTTP"), Qt::CaseInsensitive) == 0) {
|
||||
QJsonObject headers;
|
||||
headers["Host"] = xhttp.host;
|
||||
xhttpObj["headers"] = headers;
|
||||
headers[QStringLiteral("Host")] = hostEff;
|
||||
xo[QStringLiteral("headers")] = headers;
|
||||
}
|
||||
|
||||
if (!xhttp.uplinkMethod.isEmpty())
|
||||
{
|
||||
xhttpObj["method"] = xhttp.uplinkMethod;
|
||||
}
|
||||
if (xhttp.disableGrpc)
|
||||
{
|
||||
xhttpObj["noGRPCHeader"] = true;
|
||||
}
|
||||
if (xhttp.disableSse)
|
||||
{
|
||||
xhttpObj["noSSEHeader"] = true;
|
||||
const QString methodEff =
|
||||
xhttp.uplinkMethod.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkMethod) : xhttp.uplinkMethod;
|
||||
xo[QStringLiteral("uplinkHTTPMethod")] = methodEff.toUpper();
|
||||
|
||||
xo[QStringLiteral("noGRPCHeader")] = xhttp.disableGrpc;
|
||||
xo[QStringLiteral("noSSEHeader")] = xhttp.disableSse;
|
||||
|
||||
const QString sessPl = normalizeSessionSeqPlacement(xhttp.sessionPlacement);
|
||||
if (!sessPl.isEmpty())
|
||||
xo[QStringLiteral("sessionPlacement")] = sessPl;
|
||||
const QString seqPl = normalizeSessionSeqPlacement(xhttp.seqPlacement);
|
||||
if (!seqPl.isEmpty())
|
||||
xo[QStringLiteral("seqPlacement")] = seqPl;
|
||||
if (!xhttp.sessionKey.isEmpty())
|
||||
xo[QStringLiteral("sessionKey")] = xhttp.sessionKey;
|
||||
if (!xhttp.seqKey.isEmpty())
|
||||
xo[QStringLiteral("seqKey")] = xhttp.seqKey;
|
||||
|
||||
xo[QStringLiteral("uplinkDataPlacement")] = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
|
||||
if (!xhttp.uplinkDataKey.isEmpty())
|
||||
xo[QStringLiteral("uplinkDataKey")] = xhttp.uplinkDataKey;
|
||||
|
||||
const QString ucs = xhttp.uplinkChunkSize.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkChunkSize)
|
||||
: xhttp.uplinkChunkSize;
|
||||
if (!ucs.isEmpty() && ucs != QLatin1String("0")) {
|
||||
const int v = ucs.toInt();
|
||||
QJsonObject chunkR;
|
||||
chunkR[QStringLiteral("from")] = v;
|
||||
chunkR[QStringLiteral("to")] = v;
|
||||
xo[QStringLiteral("uplinkChunkSize")] = chunkR;
|
||||
}
|
||||
|
||||
// Session & Sequence
|
||||
if (!xhttp.sessionPlacement.isEmpty() && xhttp.sessionPlacement != "None")
|
||||
{
|
||||
xhttpObj["scSessionPlacement"] = xhttp.sessionPlacement;
|
||||
}
|
||||
if (!xhttp.seqPlacement.isEmpty() && xhttp.seqPlacement != "None")
|
||||
{
|
||||
xhttpObj["scSeqPlacement"] = xhttp.seqPlacement;
|
||||
}
|
||||
if (!xhttp.uplinkDataPlacement.isEmpty())
|
||||
{
|
||||
xhttpObj["scUplinkDataPlacement"] = xhttp.uplinkDataPlacement;
|
||||
}
|
||||
|
||||
// Traffic shaping
|
||||
if (!xhttp.uplinkChunkSize.isEmpty() && xhttp.uplinkChunkSize != "0")
|
||||
xhttpObj["xhttpUplinkChunkSize"] = xhttp.uplinkChunkSize.toInt();
|
||||
if (!xhttp.scMaxBufferedPosts.isEmpty())
|
||||
xhttpObj["scMaxBufferedPosts"] = xhttp.scMaxBufferedPosts.toInt();
|
||||
xo[QStringLiteral("scMaxBufferedPosts")] = xhttp.scMaxBufferedPosts.toLongLong();
|
||||
|
||||
// scMaxEachPostBytes range
|
||||
if (!xhttp.scMaxEachPostBytesMin.isEmpty() || !xhttp.scMaxEachPostBytesMax.isEmpty()) {
|
||||
QJsonObject range;
|
||||
range["from"] = xhttp.scMaxEachPostBytesMin.toInt();
|
||||
range["to"] = xhttp.scMaxEachPostBytesMax.toInt();
|
||||
xhttpObj["scMaxEachPostBytes"] = range;
|
||||
putIntRangeIfAny(xo, "scMaxEachPostBytes", xhttp.scMaxEachPostBytesMin, xhttp.scMaxEachPostBytesMax,
|
||||
px::defaultXhttpScMaxEachPostBytesMin, px::defaultXhttpScMaxEachPostBytesMax);
|
||||
putIntRangeIfAny(xo, "scMinPostsIntervalMs", xhttp.scMinPostsIntervalMsMin, xhttp.scMinPostsIntervalMsMax,
|
||||
px::defaultXhttpScMinPostsIntervalMsMin, px::defaultXhttpScMinPostsIntervalMsMax);
|
||||
putIntRangeIfAny(xo, "scStreamUpServerSecs", xhttp.scStreamUpServerSecsMin, xhttp.scStreamUpServerSecsMax,
|
||||
px::defaultXhttpScStreamUpServerSecsMin, px::defaultXhttpScStreamUpServerSecsMax);
|
||||
|
||||
const auto &pad = xhttp.xPadding;
|
||||
xo[QStringLiteral("xPaddingObfsMode")] = pad.obfsMode;
|
||||
if (pad.obfsMode) {
|
||||
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
|
||||
QJsonObject br;
|
||||
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
|
||||
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
|
||||
: pad.bytesMax.toInt();
|
||||
xo[QStringLiteral("xPaddingBytes")] = br;
|
||||
}
|
||||
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
|
||||
xo[QStringLiteral("xPaddingHeader")] = pad.header.isEmpty() ? QStringLiteral("X-Padding") : pad.header;
|
||||
xo[QStringLiteral("xPaddingPlacement")] = normalizeXPaddingPlacement(
|
||||
pad.placement.isEmpty() ? QString::fromLatin1(px::defaultXPaddingPlacement) : pad.placement);
|
||||
xo[QStringLiteral("xPaddingMethod")] = normalizeXPaddingMethod(
|
||||
pad.method.isEmpty() ? QString::fromLatin1(px::defaultXPaddingMethod) : pad.method);
|
||||
}
|
||||
|
||||
// scMinPostsIntervalMs range
|
||||
if (!xhttp.scMinPostsIntervalMsMin.isEmpty() || !xhttp.scMinPostsIntervalMsMax.isEmpty()) {
|
||||
QJsonObject range;
|
||||
range["from"] = xhttp.scMinPostsIntervalMsMin.toInt();
|
||||
range["to"] = xhttp.scMinPostsIntervalMsMax.toInt();
|
||||
xhttpObj["scMinPostsIntervalMs"] = range;
|
||||
}
|
||||
|
||||
// scStreamUpServerSecs range
|
||||
if (!xhttp.scStreamUpServerSecsMin.isEmpty() || !xhttp.scStreamUpServerSecsMax.isEmpty()) {
|
||||
QJsonObject range;
|
||||
range["from"] = xhttp.scStreamUpServerSecsMin.toInt();
|
||||
range["to"] = xhttp.scStreamUpServerSecsMax.toInt();
|
||||
xhttpObj["scStreamUpServerSecs"] = range;
|
||||
}
|
||||
|
||||
// xPadding
|
||||
if (xhttp.xPadding.obfsMode) {
|
||||
QJsonObject paddingObj;
|
||||
if (!xhttp.xPadding.bytesMin.isEmpty() || !xhttp.xPadding.bytesMax.isEmpty()) {
|
||||
QJsonObject bytesRange;
|
||||
bytesRange["from"] = xhttp.xPadding.bytesMin.toInt();
|
||||
bytesRange["to"] = xhttp.xPadding.bytesMax.toInt();
|
||||
paddingObj["xPaddingBytes"] = bytesRange;
|
||||
}
|
||||
if (!xhttp.xPadding.key.isEmpty())
|
||||
{
|
||||
paddingObj["xPaddingKey"] = xhttp.xPadding.key;
|
||||
}
|
||||
if (!xhttp.xPadding.header.isEmpty())
|
||||
{
|
||||
paddingObj["xPaddingHeader"] = xhttp.xPadding.header;
|
||||
}
|
||||
if (!xhttp.xPadding.placement.isEmpty())
|
||||
{
|
||||
paddingObj["xPaddingPlacement"] = xhttp.xPadding.placement;
|
||||
}
|
||||
if (!xhttp.xPadding.method.isEmpty())
|
||||
{
|
||||
paddingObj["xPaddingMethod"] = xhttp.xPadding.method;
|
||||
}
|
||||
xhttpObj["xPadding"] = paddingObj;
|
||||
}
|
||||
|
||||
// xmux
|
||||
// xmux: Xray has no "enabled" flag; omit object when UI disables multiplex tuning.
|
||||
if (xhttp.xmux.enabled) {
|
||||
QJsonObject muxObj;
|
||||
muxObj["enabled"] = true;
|
||||
|
||||
auto addRange = [&](const char *key, const QString &minV, const QString &maxV) {
|
||||
if (!minV.isEmpty() || !maxV.isEmpty()) {
|
||||
QJsonObject r;
|
||||
r["from"] = minV.toInt();
|
||||
r["to"] = maxV.toInt();
|
||||
muxObj[key] = r;
|
||||
}
|
||||
QJsonObject mux;
|
||||
auto addMuxRange = [&](const char *key, const QString &a, const QString &b) {
|
||||
if (a.isEmpty() && b.isEmpty())
|
||||
return;
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = a.isEmpty() ? 0 : a.toInt();
|
||||
r[QStringLiteral("to")] = b.isEmpty() ? 0 : b.toInt();
|
||||
mux[QString::fromUtf8(key)] = r;
|
||||
};
|
||||
|
||||
addRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
|
||||
addRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);
|
||||
addRange("cMaxReuseTimes", xhttp.xmux.cMaxReuseTimesMin, xhttp.xmux.cMaxReuseTimesMax);
|
||||
addRange("hMaxRequestTimes", xhttp.xmux.hMaxRequestTimesMin, xhttp.xmux.hMaxRequestTimesMax);
|
||||
addRange("hMaxReusableSecs", xhttp.xmux.hMaxReusableSecsMin, xhttp.xmux.hMaxReusableSecsMax);
|
||||
|
||||
addMuxRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
|
||||
addMuxRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);
|
||||
addMuxRange("cMaxReuseTimes", xhttp.xmux.cMaxReuseTimesMin, xhttp.xmux.cMaxReuseTimesMax);
|
||||
addMuxRange("hMaxRequestTimes", xhttp.xmux.hMaxRequestTimesMin, xhttp.xmux.hMaxRequestTimesMax);
|
||||
addMuxRange("hMaxReusableSecs", xhttp.xmux.hMaxReusableSecsMin, xhttp.xmux.hMaxReusableSecsMax);
|
||||
if (!xhttp.xmux.hKeepAlivePeriod.isEmpty())
|
||||
{
|
||||
muxObj["hKeepAlivePeriod"] = xhttp.xmux.hKeepAlivePeriod.toInt();
|
||||
}
|
||||
|
||||
xhttpObj["xmux"] = muxObj;
|
||||
mux[QStringLiteral("hKeepAlivePeriod")] = xhttp.xmux.hKeepAlivePeriod.toLongLong();
|
||||
if (!mux.isEmpty())
|
||||
xo[QStringLiteral("xmux")] = mux;
|
||||
}
|
||||
|
||||
streamSettings["xhttpSettings"] = xhttpObj;
|
||||
streamSettings[QStringLiteral("xhttpSettings")] = xo;
|
||||
}
|
||||
|
||||
// mKCP transport settings
|
||||
if (srv.transport == "mkcp") {
|
||||
if (srv.transport == QLatin1String("mkcp")) {
|
||||
QJsonObject kcpObj;
|
||||
if (!mkcp.tti.isEmpty())
|
||||
{
|
||||
kcpObj["tti"] = mkcp.tti.toInt();
|
||||
}
|
||||
if (!mkcp.uplinkCapacity.isEmpty())
|
||||
{
|
||||
kcpObj["uplinkCapacity"] = mkcp.uplinkCapacity.toInt();
|
||||
}
|
||||
if (!mkcp.downlinkCapacity.isEmpty())
|
||||
{
|
||||
kcpObj["downlinkCapacity"] = mkcp.downlinkCapacity.toInt();
|
||||
}
|
||||
if (!mkcp.readBufferSize.isEmpty())
|
||||
{
|
||||
kcpObj["readBufferSize"] = mkcp.readBufferSize.toInt();
|
||||
}
|
||||
if (!mkcp.writeBufferSize.isEmpty())
|
||||
{
|
||||
kcpObj["writeBufferSize"] = mkcp.writeBufferSize.toInt();
|
||||
}
|
||||
kcpObj["congestion"] = mkcp.congestion;
|
||||
streamSettings["kcpSettings"] = kcpObj;
|
||||
const QString ttiEff = mkcp.tti.isEmpty() ? QString::fromLatin1(px::defaultMkcpTti) : mkcp.tti;
|
||||
const QString upEff = mkcp.uplinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpUplinkCapacity)
|
||||
: mkcp.uplinkCapacity;
|
||||
const QString downEff = mkcp.downlinkCapacity.isEmpty() ? QString::fromLatin1(px::defaultMkcpDownlinkCapacity)
|
||||
: mkcp.downlinkCapacity;
|
||||
const QString rbufEff = mkcp.readBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpReadBufferSize)
|
||||
: mkcp.readBufferSize;
|
||||
const QString wbufEff = mkcp.writeBufferSize.isEmpty() ? QString::fromLatin1(px::defaultMkcpWriteBufferSize)
|
||||
: mkcp.writeBufferSize;
|
||||
kcpObj[QStringLiteral("tti")] = ttiEff.toInt();
|
||||
kcpObj[QStringLiteral("uplinkCapacity")] = upEff.toInt();
|
||||
kcpObj[QStringLiteral("downlinkCapacity")] = downEff.toInt();
|
||||
kcpObj[QStringLiteral("readBufferSize")] = rbufEff.toInt();
|
||||
kcpObj[QStringLiteral("writeBufferSize")] = wbufEff.toInt();
|
||||
kcpObj[QStringLiteral("congestion")] = mkcp.congestion;
|
||||
streamSettings[QStringLiteral("kcpSettings")] = kcpObj;
|
||||
}
|
||||
|
||||
return streamSettings;
|
||||
@@ -446,7 +497,7 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
|
||||
|
||||
// Build full client config
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalAddr;
|
||||
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
|
||||
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
||||
inboundObj["protocol"] = "socks";
|
||||
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
|
||||
|
||||
@@ -20,6 +20,9 @@ public:
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode) override;
|
||||
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
|
||||
@@ -14,8 +14,18 @@
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
Logger logger("XrayInstaller");
|
||||
|
||||
// Xray expects uTLS preset names (chrome, firefox, …). Old Amnezia/server templates used "Mozilla/5.0".
|
||||
QString normalizeXrayFingerprint(const QString &fp)
|
||||
{
|
||||
if (fp.isEmpty() || fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
return QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -103,14 +113,14 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
srv.site = srv.sni;
|
||||
}
|
||||
|
||||
srv.fingerprint = rs.value(protocols::xray::fingerprint).toString("Mozilla/5.0");
|
||||
srv.fingerprint = normalizeXrayFingerprint(rs.value(protocols::xray::fingerprint).toString());
|
||||
}
|
||||
|
||||
// ── TLS settings ──────────────────────────────────────────────────
|
||||
if (srv.security == "tls") {
|
||||
QJsonObject tls = streamSettings.value("tlsSettings").toObject();
|
||||
srv.sni = tls.value(protocols::xray::serverName).toString();
|
||||
srv.fingerprint = tls.value(protocols::xray::fingerprint).toString("Mozilla/5.0");
|
||||
srv.fingerprint = normalizeXrayFingerprint(tls.value(protocols::xray::fingerprint).toString());
|
||||
|
||||
QJsonArray alpnArr = tls.value("alpn").toArray();
|
||||
QStringList alpnList;
|
||||
@@ -129,29 +139,94 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
}
|
||||
}
|
||||
|
||||
// ── XHTTP settings ────────────────────────────────────────────────
|
||||
// ── XHTTP settings (Xray-core SplitHTTPConfig + legacy Amnezia keys) ──
|
||||
if (srv.transport == "xhttp") {
|
||||
QJsonObject xhttpObj = streamSettings.value("xhttpSettings").toObject();
|
||||
srv.xhttp.mode = xhttpObj.value("mode").toString("Auto");
|
||||
{
|
||||
const QString m = xhttpObj.value("mode").toString();
|
||||
if (m.isEmpty() || m == QLatin1String("auto"))
|
||||
srv.xhttp.mode = QStringLiteral("Auto");
|
||||
else if (m == QLatin1String("packet-up"))
|
||||
srv.xhttp.mode = QStringLiteral("Packet-up");
|
||||
else if (m == QLatin1String("stream-up"))
|
||||
srv.xhttp.mode = QStringLiteral("Stream-up");
|
||||
else if (m == QLatin1String("stream-one"))
|
||||
srv.xhttp.mode = QStringLiteral("Stream-one");
|
||||
else
|
||||
srv.xhttp.mode = m;
|
||||
}
|
||||
|
||||
srv.xhttp.host = xhttpObj.value("host").toString();
|
||||
srv.xhttp.path = xhttpObj.value("path").toString();
|
||||
srv.xhttp.uplinkMethod = xhttpObj.value("method").toString("POST");
|
||||
|
||||
{
|
||||
const QJsonObject hdrs = xhttpObj.value("headers").toObject();
|
||||
if (hdrs.contains(QLatin1String("Host")) || !hdrs.isEmpty())
|
||||
srv.xhttp.headersTemplate = QStringLiteral("HTTP");
|
||||
}
|
||||
|
||||
if (xhttpObj.contains(QLatin1String("uplinkHTTPMethod")))
|
||||
srv.xhttp.uplinkMethod = xhttpObj.value("uplinkHTTPMethod").toString();
|
||||
else
|
||||
srv.xhttp.uplinkMethod = xhttpObj.value("method").toString();
|
||||
|
||||
srv.xhttp.disableGrpc = xhttpObj.value("noGRPCHeader").toBool(true);
|
||||
srv.xhttp.disableSse = xhttpObj.value("noSSEHeader").toBool(true);
|
||||
|
||||
srv.xhttp.sessionPlacement = xhttpObj.value("scSessionPlacement").toString("Path");
|
||||
srv.xhttp.seqPlacement = xhttpObj.value("scSeqPlacement").toString("Path");
|
||||
srv.xhttp.uplinkDataPlacement = xhttpObj.value("scUplinkDataPlacement").toString("Body");
|
||||
auto sessionSeqUi = [](const QString &core) -> QString {
|
||||
if (core.isEmpty() || core == QLatin1String("path"))
|
||||
return QStringLiteral("Path");
|
||||
if (core == QLatin1String("cookie"))
|
||||
return QStringLiteral("Cookie");
|
||||
if (core == QLatin1String("header"))
|
||||
return QStringLiteral("Header");
|
||||
if (core == QLatin1String("query"))
|
||||
return QStringLiteral("Query");
|
||||
return core;
|
||||
};
|
||||
QString sess = xhttpObj.value("sessionPlacement").toString();
|
||||
if (sess.isEmpty())
|
||||
sess = xhttpObj.value("scSessionPlacement").toString();
|
||||
srv.xhttp.sessionPlacement = sessionSeqUi(sess);
|
||||
|
||||
if (xhttpObj.contains("xhttpUplinkChunkSize")) {
|
||||
srv.xhttp.uplinkChunkSize = QString::number(xhttpObj["xhttpUplinkChunkSize"].toInt());
|
||||
QString seq = xhttpObj.value("seqPlacement").toString();
|
||||
if (seq.isEmpty())
|
||||
seq = xhttpObj.value("scSeqPlacement").toString();
|
||||
srv.xhttp.seqPlacement = sessionSeqUi(seq);
|
||||
|
||||
auto uplinkDataUi = [](const QString &core) -> QString {
|
||||
if (core.isEmpty() || core == QLatin1String("body"))
|
||||
return QStringLiteral("Body");
|
||||
if (core == QLatin1String("auto"))
|
||||
return QStringLiteral("Auto");
|
||||
if (core == QLatin1String("header"))
|
||||
return QStringLiteral("Header");
|
||||
if (core == QLatin1String("cookie"))
|
||||
return QStringLiteral("Cookie");
|
||||
return core;
|
||||
};
|
||||
QString udata = xhttpObj.value("uplinkDataPlacement").toString();
|
||||
if (udata.isEmpty())
|
||||
udata = xhttpObj.value("scUplinkDataPlacement").toString();
|
||||
srv.xhttp.uplinkDataPlacement = uplinkDataUi(udata);
|
||||
|
||||
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
|
||||
srv.xhttp.seqKey = xhttpObj.value("seqKey").toString();
|
||||
srv.xhttp.uplinkDataKey = xhttpObj.value("uplinkDataKey").toString();
|
||||
|
||||
if (xhttpObj.contains(QLatin1String("uplinkChunkSize"))) {
|
||||
QJsonObject uc = xhttpObj.value("uplinkChunkSize").toObject();
|
||||
if (!uc.isEmpty())
|
||||
srv.xhttp.uplinkChunkSize = QString::number(uc.value("from").toInt());
|
||||
} else if (xhttpObj.contains(QLatin1String("xhttpUplinkChunkSize"))) {
|
||||
srv.xhttp.uplinkChunkSize = QString::number(xhttpObj.value("xhttpUplinkChunkSize").toInt());
|
||||
}
|
||||
if (xhttpObj.contains("scMaxBufferedPosts")) {
|
||||
srv.xhttp.scMaxBufferedPosts = QString::number(xhttpObj["scMaxBufferedPosts"].toInt());
|
||||
if (xhttpObj.contains(QLatin1String("scMaxBufferedPosts"))) {
|
||||
srv.xhttp.scMaxBufferedPosts = QString::number(xhttpObj.value("scMaxBufferedPosts").toVariant().toLongLong());
|
||||
}
|
||||
|
||||
auto readRange = [&](const char *key, QString &minOut, QString &maxOut) {
|
||||
QJsonObject r = xhttpObj.value(key).toObject();
|
||||
QJsonObject r = xhttpObj.value(QLatin1String(key)).toObject();
|
||||
if (!r.isEmpty()) {
|
||||
minOut = QString::number(r.value("from").toInt());
|
||||
maxOut = QString::number(r.value("to").toInt());
|
||||
@@ -161,28 +236,51 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
readRange("scMinPostsIntervalMs", srv.xhttp.scMinPostsIntervalMsMin, srv.xhttp.scMinPostsIntervalMsMax);
|
||||
readRange("scStreamUpServerSecs", srv.xhttp.scStreamUpServerSecsMin, srv.xhttp.scStreamUpServerSecsMax);
|
||||
|
||||
// xPadding
|
||||
if (xhttpObj.contains("xPadding")) {
|
||||
QJsonObject pad = xhttpObj["xPadding"].toObject();
|
||||
srv.xhttp.xPadding.obfsMode = true;
|
||||
auto loadPaddingFromObject = [&](const QJsonObject &pad) {
|
||||
if (pad.contains(QLatin1String("xPaddingObfsMode")))
|
||||
srv.xhttp.xPadding.obfsMode = pad.value("xPaddingObfsMode").toBool(true);
|
||||
srv.xhttp.xPadding.key = pad.value("xPaddingKey").toString();
|
||||
srv.xhttp.xPadding.header = pad.value("xPaddingHeader").toString();
|
||||
srv.xhttp.xPadding.placement = pad.value("xPaddingPlacement").toString("Cookie");
|
||||
srv.xhttp.xPadding.method = pad.value("xPaddingMethod").toString("Repeat-x");
|
||||
srv.xhttp.xPadding.placement = pad.value("xPaddingPlacement").toString();
|
||||
srv.xhttp.xPadding.method = pad.value("xPaddingMethod").toString();
|
||||
QJsonObject bytesRange = pad.value("xPaddingBytes").toObject();
|
||||
if (!bytesRange.isEmpty()) {
|
||||
srv.xhttp.xPadding.bytesMin = QString::number(bytesRange.value("from").toInt());
|
||||
srv.xhttp.xPadding.bytesMax = QString::number(bytesRange.value("to").toInt());
|
||||
}
|
||||
QString pl = srv.xhttp.xPadding.placement.toLower();
|
||||
if (pl == QLatin1String("cookie"))
|
||||
srv.xhttp.xPadding.placement = QStringLiteral("Cookie");
|
||||
else if (pl == QLatin1String("header"))
|
||||
srv.xhttp.xPadding.placement = QStringLiteral("Header");
|
||||
else if (pl == QLatin1String("query"))
|
||||
srv.xhttp.xPadding.placement = QStringLiteral("Query");
|
||||
else if (pl == QLatin1String("queryinheader"))
|
||||
srv.xhttp.xPadding.placement = QStringLiteral("Query in header");
|
||||
QString met = srv.xhttp.xPadding.method.toLower();
|
||||
if (met == QLatin1String("repeat-x"))
|
||||
srv.xhttp.xPadding.method = QStringLiteral("Repeat-x");
|
||||
else if (met == QLatin1String("tokenish"))
|
||||
srv.xhttp.xPadding.method = QStringLiteral("Tokenish");
|
||||
};
|
||||
if (xhttpObj.contains(QLatin1String("xPaddingObfsMode")) || xhttpObj.contains(QLatin1String("xPaddingKey"))
|
||||
|| !xhttpObj.value("xPaddingBytes").toObject().isEmpty()) {
|
||||
loadPaddingFromObject(xhttpObj);
|
||||
} else if (xhttpObj.contains(QLatin1String("xPadding")) && xhttpObj.value("xPadding").isObject()) {
|
||||
const QJsonObject nested = xhttpObj.value("xPadding").toObject();
|
||||
if (!nested.isEmpty()) {
|
||||
loadPaddingFromObject(nested);
|
||||
if (!nested.contains(QLatin1String("xPaddingObfsMode")))
|
||||
srv.xhttp.xPadding.obfsMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// xmux
|
||||
if (xhttpObj.contains("xmux")) {
|
||||
QJsonObject mux = xhttpObj["xmux"].toObject();
|
||||
srv.xhttp.xmux.enabled = mux.value("enabled").toBool(true);
|
||||
if (xhttpObj.contains(QLatin1String("xmux"))) {
|
||||
QJsonObject mux = xhttpObj.value("xmux").toObject();
|
||||
srv.xhttp.xmux.enabled = true;
|
||||
|
||||
auto readMuxRange = [&](const char *key, QString &minOut, QString &maxOut) {
|
||||
QJsonObject r = mux.value(key).toObject();
|
||||
QJsonObject r = mux.value(QLatin1String(key)).toObject();
|
||||
if (!r.isEmpty()) {
|
||||
minOut = QString::number(r.value("from").toInt());
|
||||
maxOut = QString::number(r.value("to").toInt());
|
||||
@@ -194,8 +292,8 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
readMuxRange("hMaxRequestTimes", srv.xhttp.xmux.hMaxRequestTimesMin, srv.xhttp.xmux.hMaxRequestTimesMax);
|
||||
readMuxRange("hMaxReusableSecs", srv.xhttp.xmux.hMaxReusableSecsMin, srv.xhttp.xmux.hMaxReusableSecsMax);
|
||||
|
||||
if (mux.contains("hKeepAlivePeriod"))
|
||||
srv.xhttp.xmux.hKeepAlivePeriod = QString::number(mux["hKeepAlivePeriod"].toInt());
|
||||
if (mux.contains(QLatin1String("hKeepAlivePeriod")))
|
||||
srv.xhttp.xmux.hKeepAlivePeriod = QString::number(mux.value("hKeepAlivePeriod").toVariant().toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,6 @@ using namespace ProtocolUtils;
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayXPaddingConfig
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayXPaddingConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -37,17 +32,13 @@ XrayXPaddingConfig XrayXPaddingConfig::fromJson(const QJsonObject &json)
|
||||
c.bytesMin = json.value(configKey::xPaddingBytesMin).toString();
|
||||
c.bytesMax = json.value(configKey::xPaddingBytesMax).toString();
|
||||
c.obfsMode = json.value(configKey::xPaddingObfsMode).toBool(true);
|
||||
c.key = json.value(configKey::xPaddingKey).toString("www.googletagmanager.com");
|
||||
c.key = json.value(configKey::xPaddingKey).toString(protocols::xray::defaultSite);
|
||||
c.header = json.value(configKey::xPaddingHeader).toString();
|
||||
c.placement = json.value(configKey::xPaddingPlacement).toString("Cookie");
|
||||
c.method = json.value(configKey::xPaddingMethod).toString("Repeat-x");
|
||||
c.placement = json.value(configKey::xPaddingPlacement).toString(protocols::xray::defaultXPaddingPlacement);
|
||||
c.method = json.value(configKey::xPaddingMethod).toString(protocols::xray::defaultXPaddingMethod);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayXmuxConfig
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayXmuxConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -84,10 +75,6 @@ XrayXmuxConfig XrayXmuxConfig::fromJson(const QJsonObject &json)
|
||||
return c;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayXhttpConfig
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayXhttpConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -124,19 +111,19 @@ QJsonObject XrayXhttpConfig::toJson() const
|
||||
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
XrayXhttpConfig c;
|
||||
c.mode = json.value(configKey::xhttpMode).toString("Auto");
|
||||
c.host = json.value(configKey::xhttpHost).toString("www.googletagmanager.com");
|
||||
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
|
||||
c.host = json.value(configKey::xhttpHost).toString(protocols::xray::defaultSite);
|
||||
c.path = json.value(configKey::xhttpPath).toString();
|
||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString("HTTP");
|
||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString("POST");
|
||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString(protocols::xray::defaultXhttpHeadersTemplate);
|
||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString(protocols::xray::defaultXhttpUplinkMethod);
|
||||
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool(true);
|
||||
c.disableSse = json.value(configKey::xhttpDisableSse).toBool(true);
|
||||
|
||||
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString("Path");
|
||||
c.sessionKey = json.value(configKey::xhttpSessionKey).toString("Path");
|
||||
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString("Path");
|
||||
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
||||
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
|
||||
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
||||
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
|
||||
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString("Body");
|
||||
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString(protocols::xray::defaultXhttpUplinkDataPlacement);
|
||||
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
|
||||
|
||||
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString("0");
|
||||
@@ -154,10 +141,6 @@ XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
||||
return c;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayMkcpConfig
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayMkcpConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -182,10 +165,6 @@ XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
|
||||
return c;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayServerConfig
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -224,14 +203,17 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
|
||||
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
||||
|
||||
// New: Security
|
||||
c.security = json.value(configKey::xraySecurity).toString("reality");
|
||||
c.flow = json.value(configKey::xrayFlow).toString("xtls-rprx-vision");
|
||||
c.fingerprint = json.value(configKey::xrayFingerprint).toString("Mozilla/5.0");
|
||||
c.sni = json.value(configKey::xraySni).toString("cdn.example.com");
|
||||
c.alpn = json.value(configKey::xrayAlpn).toString("HTTP/2");
|
||||
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
|
||||
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
|
||||
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
|
||||
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||
}
|
||||
c.sni = json.value(configKey::xraySni).toString(protocols::xray::defaultSni);
|
||||
c.alpn = json.value(configKey::xrayAlpn).toString(protocols::xray::defaultAlpn);
|
||||
|
||||
// New: Transport
|
||||
c.transport = json.value(configKey::xrayTransport).toString("raw");
|
||||
c.transport = json.value(configKey::xrayTransport).toString(protocols::xray::defaultTransport);
|
||||
c.xhttp = XrayXhttpConfig::fromJson(json.value("xhttp").toObject());
|
||||
c.mkcp = XrayMkcpConfig::fromJson(json.value("mkcp").toObject());
|
||||
|
||||
@@ -249,10 +231,6 @@ bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) con
|
||||
&& sni == other.sni;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayClientConfig (unchanged logic, kept as-is)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayClientConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -300,10 +278,6 @@ XrayClientConfig XrayClientConfig::fromJson(const QJsonObject &json)
|
||||
return c;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// XrayProtocolConfig (unchanged logic, kept as-is)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
QJsonObject XrayProtocolConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj = serverConfig.toJson();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define XRAYPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include <QString>
|
||||
#include <optional>
|
||||
|
||||
@@ -15,8 +16,8 @@ struct XrayXPaddingConfig {
|
||||
bool obfsMode = true; // xPaddingObfsMode
|
||||
QString key; // xPaddingKey
|
||||
QString header; // xPaddingHeader
|
||||
QString placement = "Cookie"; // xPaddingPlacement: Cookie|Header|Query|Body
|
||||
QString method = "Repeat-x"; // xPaddingMethod: Repeat-x|Random|Zero
|
||||
QString placement = protocols::xray::defaultXPaddingPlacement; // xPaddingPlacement: Cookie|Header|Query|Body
|
||||
QString method = protocols::xray::defaultXPaddingMethod; // xPaddingMethod: Repeat-x|Random|Zero
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static XrayXPaddingConfig fromJson(const QJsonObject &json);
|
||||
@@ -44,31 +45,31 @@ struct XrayXmuxConfig {
|
||||
|
||||
// ── XHTTP transport ───────────────────────────────────────────────────────────
|
||||
struct XrayXhttpConfig {
|
||||
QString mode = "Auto"; // Auto|Packet-up|Stream-up|Stream-one
|
||||
QString host = "www.googletagmanager.com";
|
||||
QString mode = protocols::xray::defaultXhttpMode; // Auto|Packet-up|Stream-up|Stream-one
|
||||
QString host = protocols::xray::defaultXhttpHost;
|
||||
QString path;
|
||||
QString headersTemplate = "HTTP"; // HTTP|None
|
||||
QString uplinkMethod = "POST"; // POST|PUT|PATCH
|
||||
QString headersTemplate = protocols::xray::defaultXhttpHeadersTemplate; // HTTP|None
|
||||
QString uplinkMethod = protocols::xray::defaultXhttpUplinkMethod; // POST|PUT|PATCH
|
||||
bool disableGrpc = true;
|
||||
bool disableSse = true;
|
||||
|
||||
// Session & Sequence
|
||||
QString sessionPlacement = "Path"; // Path|Header|Cookie|None
|
||||
QString sessionKey = "Path";
|
||||
QString seqPlacement = "Path";
|
||||
QString sessionPlacement = protocols::xray::defaultXhttpSessionPlacement;
|
||||
QString sessionKey = protocols::xray::defaultXhttpSessionKey;
|
||||
QString seqPlacement = protocols::xray::defaultXhttpSeqPlacement;
|
||||
QString seqKey;
|
||||
QString uplinkDataPlacement = "Body"; // Body|Query
|
||||
QString uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement;
|
||||
QString uplinkDataKey;
|
||||
|
||||
// Traffic Shaping
|
||||
QString uplinkChunkSize = "0";
|
||||
QString uplinkChunkSize = protocols::xray::defaultXhttpUplinkChunkSize;
|
||||
QString scMaxBufferedPosts;
|
||||
QString scMaxEachPostBytesMin = "1";
|
||||
QString scMaxEachPostBytesMax = "100";
|
||||
QString scMinPostsIntervalMsMin = "100";
|
||||
QString scMinPostsIntervalMsMax = "800";
|
||||
QString scStreamUpServerSecsMin = "1";
|
||||
QString scStreamUpServerSecsMax = "100";
|
||||
QString scMaxEachPostBytesMin = protocols::xray::defaultXhttpScMaxEachPostBytesMin;
|
||||
QString scMaxEachPostBytesMax = protocols::xray::defaultXhttpScMaxEachPostBytesMax;
|
||||
QString scMinPostsIntervalMsMin = protocols::xray::defaultXhttpScMinPostsIntervalMsMin;
|
||||
QString scMinPostsIntervalMsMax = protocols::xray::defaultXhttpScMinPostsIntervalMsMax;
|
||||
QString scStreamUpServerSecsMin = protocols::xray::defaultXhttpScStreamUpServerSecsMin;
|
||||
QString scStreamUpServerSecsMax = protocols::xray::defaultXhttpScStreamUpServerSecsMax;
|
||||
|
||||
XrayXPaddingConfig xPadding;
|
||||
XrayXmuxConfig xmux;
|
||||
@@ -100,14 +101,14 @@ struct XrayServerConfig {
|
||||
bool isThirdPartyConfig = false;
|
||||
|
||||
// New: Security
|
||||
QString security = "reality"; // none|tls|reality
|
||||
QString flow = "xtls-rprx-vision"; // ""|xtls-rprx-vision|xtls-rprx-vision-udp443
|
||||
QString fingerprint = "Mozilla/5.0";
|
||||
QString sni = "cdn.example.com";
|
||||
QString alpn = "HTTP/2"; // TLS only: HTTP/2|HTTP/1.1|HTTP/2,HTTP/1.1
|
||||
QString security = protocols::xray::defaultSecurity;
|
||||
QString flow = protocols::xray::defaultFlow;
|
||||
QString fingerprint = protocols::xray::defaultFingerprint;
|
||||
QString sni = protocols::xray::defaultSni;
|
||||
QString alpn = protocols::xray::defaultAlpn;
|
||||
|
||||
// New: Transport
|
||||
QString transport = "raw"; // raw|xhttp|mkcp
|
||||
QString transport = protocols::xray::defaultTransport;
|
||||
XrayXhttpConfig xhttp;
|
||||
XrayMkcpConfig mkcp;
|
||||
|
||||
|
||||
48
client/core/protocols/xrayProtocol.cpp
Executable file → Normal file
48
client/core/protocols/xrayProtocol.cpp
Executable file → Normal file
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkInterface>
|
||||
#include <QtCore/qlogging.h>
|
||||
@@ -79,12 +81,29 @@ ErrorCode XrayProtocol::start()
|
||||
m_socksPassword = creds.password;
|
||||
m_socksPort = creds.port;
|
||||
|
||||
const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
|
||||
QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
|
||||
if (xrayConfigStr.isEmpty()) {
|
||||
qCritical() << "Xray config is empty";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
|
||||
// Fix fingerprint: old configs may contain "Mozilla/5.0" which xray-core rejects.
|
||||
// Replace with the correct default at runtime so stale stored configs still work.
|
||||
if (xrayConfigStr.contains("Mozilla/5.0", Qt::CaseInsensitive)) {
|
||||
xrayConfigStr.replace("Mozilla/5.0", amnezia::protocols::xray::defaultFingerprint,
|
||||
Qt::CaseInsensitive);
|
||||
qDebug() << "XrayProtocol: patched legacy fingerprint to"
|
||||
<< amnezia::protocols::xray::defaultFingerprint;
|
||||
}
|
||||
|
||||
// Fix inbound listen address: old configs may use "10.33.0.2" which doesn't exist
|
||||
// until TUN is created. xray must listen on 127.0.0.1 so tun2socks can connect.
|
||||
if (xrayConfigStr.contains(amnezia::protocols::xray::defaultLocalAddr)) {
|
||||
xrayConfigStr.replace(amnezia::protocols::xray::defaultLocalAddr,
|
||||
amnezia::protocols::xray::defaultLocalListenAddr);
|
||||
qDebug() << "XrayProtocol: patched legacy inbound listen address to 127.0.0.1";
|
||||
}
|
||||
|
||||
return IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
@@ -188,6 +207,33 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
||||
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
// Check stdout for "resource busy" — the TUN device was not yet released
|
||||
// by the previous tun2socks instance. Retry after a short delay.
|
||||
bool resourceBusy = false;
|
||||
if (m_tun2socksProcess) {
|
||||
auto readOut = m_tun2socksProcess->readAllStandardOutput();
|
||||
if (readOut.waitForFinished()) {
|
||||
resourceBusy = readOut.returnValue().contains("resource busy");
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||
m_tun2socksRetryCount++;
|
||||
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
|
||||
.arg(m_tun2socksRetryCount)
|
||||
.arg(maxTun2SocksRetries)
|
||||
.arg(tun2socksRetryDelayMs);
|
||||
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
|
||||
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(err);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
m_tun2socksRetryCount = 0;
|
||||
|
||||
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
||||
qCritical() << "Tun2socks process crashed!";
|
||||
} else {
|
||||
|
||||
@@ -35,6 +35,9 @@ private:
|
||||
int m_socksPort = 10808;
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
|
||||
int m_tun2socksRetryCount = 0;
|
||||
static constexpr int maxTun2SocksRetries = 5;
|
||||
static constexpr int tun2socksRetryDelayMs = 400;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
|
||||
@@ -57,6 +57,40 @@ namespace amnezia
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultLocalProxyPort[] = "10808";
|
||||
constexpr char defaultLocalAddr[] = "10.33.0.2";
|
||||
constexpr char defaultLocalListenAddr[] = "127.0.0.1";
|
||||
|
||||
constexpr char defaultSecurity[] = "reality";
|
||||
constexpr char defaultFlow[] = "xtls-rprx-vision";
|
||||
constexpr char defaultTransport[] = "raw";
|
||||
constexpr char defaultFingerprint[] = "chrome";
|
||||
constexpr char defaultSni[] = "cdn.example.com";
|
||||
constexpr char defaultAlpn[] = "HTTP/2";
|
||||
|
||||
constexpr char defaultXhttpMode[] = "Auto";
|
||||
constexpr char defaultXhttpHeadersTemplate[] = "HTTP";
|
||||
constexpr char defaultXhttpUplinkMethod[] = "POST";
|
||||
constexpr char defaultXhttpSessionPlacement[] = "Path";
|
||||
constexpr char defaultXhttpSessionKey[] = "Path";
|
||||
constexpr char defaultXhttpSeqPlacement[] = "Path";
|
||||
constexpr char defaultXhttpUplinkDataPlacement[] = "Body";
|
||||
|
||||
constexpr char defaultXhttpHost[] = "www.googletagmanager.com";
|
||||
constexpr char defaultXhttpUplinkChunkSize[] = "0";
|
||||
constexpr char defaultXhttpScMaxEachPostBytesMin[] = "1";
|
||||
constexpr char defaultXhttpScMaxEachPostBytesMax[] = "100";
|
||||
constexpr char defaultXhttpScMinPostsIntervalMsMin[] = "100";
|
||||
constexpr char defaultXhttpScMinPostsIntervalMsMax[] = "800";
|
||||
constexpr char defaultXhttpScStreamUpServerSecsMin[] = "1";
|
||||
constexpr char defaultXhttpScStreamUpServerSecsMax[] = "100";
|
||||
|
||||
constexpr char defaultXPaddingPlacement[] = "Cookie";
|
||||
constexpr char defaultXPaddingMethod[] = "Repeat-x";
|
||||
|
||||
constexpr char defaultMkcpTti[] = "50";
|
||||
constexpr char defaultMkcpUplinkCapacity[] = "5";
|
||||
constexpr char defaultMkcpDownlinkCapacity[] = "20";
|
||||
constexpr char defaultMkcpReadBufferSize[] = "2";
|
||||
constexpr char defaultMkcpWriteBufferSize[] = "2";
|
||||
|
||||
constexpr char outbounds[] = "outbounds";
|
||||
constexpr char inbounds[] = "inbounds";
|
||||
|
||||
@@ -263,31 +263,75 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container,
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig& config)
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config)
|
||||
{
|
||||
if (config.port.isEmpty())
|
||||
if (config.port.isEmpty()) {
|
||||
config.port = protocols::xray::defaultPort;
|
||||
}
|
||||
|
||||
if (config.site.isEmpty())
|
||||
if (config.site.isEmpty()) {
|
||||
config.site = protocols::xray::defaultSite;
|
||||
}
|
||||
|
||||
if (config.transport.isEmpty())
|
||||
config.transport = "raw";
|
||||
if (config.transport.isEmpty()) {
|
||||
config.transport = protocols::xray::defaultTransport;
|
||||
}
|
||||
|
||||
if (config.security.isEmpty())
|
||||
config.security = "reality";
|
||||
if (config.security.isEmpty()) {
|
||||
config.security = protocols::xray::defaultSecurity;
|
||||
}
|
||||
|
||||
if (config.flow.isEmpty())
|
||||
config.flow = "xtls-rprx-vision";
|
||||
if (config.flow.isEmpty()) {
|
||||
config.flow = protocols::xray::defaultFlow;
|
||||
}
|
||||
|
||||
if (config.fingerprint.isEmpty())
|
||||
config.fingerprint = "Mozilla/5.0";
|
||||
if (config.fingerprint.isEmpty()) {
|
||||
config.fingerprint = protocols::xray::defaultFingerprint;
|
||||
} else if (config.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
config.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||
}
|
||||
|
||||
if (config.sni.isEmpty())
|
||||
config.sni = "cdn.example.com";
|
||||
if (config.sni.isEmpty()) {
|
||||
config.sni = protocols::xray::defaultSni;
|
||||
}
|
||||
|
||||
if (config.alpn.isEmpty())
|
||||
config.alpn = "HTTP/2";
|
||||
if (config.alpn.isEmpty()) {
|
||||
config.alpn = protocols::xray::defaultAlpn;
|
||||
}
|
||||
|
||||
// XHTTP transport defaults
|
||||
if (config.xhttp.host.isEmpty()) {
|
||||
config.xhttp.host = protocols::xray::defaultXhttpHost;
|
||||
}
|
||||
if (config.xhttp.mode.isEmpty()) {
|
||||
config.xhttp.mode = protocols::xray::defaultXhttpMode;
|
||||
}
|
||||
if (config.xhttp.headersTemplate.isEmpty()) {
|
||||
config.xhttp.headersTemplate = protocols::xray::defaultXhttpHeadersTemplate;
|
||||
}
|
||||
if (config.xhttp.uplinkMethod.isEmpty()) {
|
||||
config.xhttp.uplinkMethod = protocols::xray::defaultXhttpUplinkMethod;
|
||||
}
|
||||
if (config.xhttp.sessionPlacement.isEmpty()) {
|
||||
config.xhttp.sessionPlacement = protocols::xray::defaultXhttpSessionPlacement;
|
||||
}
|
||||
if (config.xhttp.sessionKey.isEmpty()) {
|
||||
config.xhttp.sessionKey = protocols::xray::defaultXhttpSessionKey;
|
||||
}
|
||||
if (config.xhttp.seqPlacement.isEmpty()) {
|
||||
config.xhttp.seqPlacement = protocols::xray::defaultXhttpSeqPlacement;
|
||||
}
|
||||
if (config.xhttp.uplinkDataPlacement.isEmpty()) {
|
||||
config.xhttp.uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement;
|
||||
}
|
||||
|
||||
// xPadding defaults
|
||||
if (config.xhttp.xPadding.placement.isEmpty()) {
|
||||
config.xhttp.xPadding.placement = protocols::xray::defaultXPaddingPlacement;
|
||||
}
|
||||
if (config.xhttp.xPadding.method.isEmpty()) {
|
||||
config.xhttp.xPadding.method = protocols::xray::defaultXPaddingMethod;
|
||||
}
|
||||
}
|
||||
|
||||
amnezia::XrayProtocolConfig XrayConfigModel::getProtocolConfig()
|
||||
@@ -396,3 +440,104 @@ void XrayConfigModel::applyServerConfig(const amnezia::XrayServerConfig &serverC
|
||||
m_originalProtocolConfig = m_protocolConfig;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::flowOptions()
|
||||
{
|
||||
return {
|
||||
"", // Empty (no flow)
|
||||
"xtls-rprx-vision",
|
||||
"xtls-rprx-vision-udp443"
|
||||
};
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::securityOptions()
|
||||
{
|
||||
return { "none", "tls", "reality" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::transportOptions()
|
||||
{
|
||||
return { "raw", "xhttp", "mkcp" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::fingerprintOptions()
|
||||
{
|
||||
return { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::alpnOptions()
|
||||
{
|
||||
return { "HTTP/2", "HTTP/1.1", "HTTP/2,HTTP/1.1" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpModeOptions()
|
||||
{
|
||||
return { "Auto", "Packet-up", "Stream-up", "Stream-one" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpHeadersTemplateOptions()
|
||||
{
|
||||
return { "HTTP", "None" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpUplinkMethodOptions()
|
||||
{
|
||||
return { "POST", "PUT", "PATCH" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpSessionPlacementOptions()
|
||||
{
|
||||
return { "Path", "Header", "Cookie", "None" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpSessionKeyOptions()
|
||||
{
|
||||
return { "Path", "Header", "None" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpSeqPlacementOptions()
|
||||
{
|
||||
return { "Path", "Header", "Cookie", "None" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xhttpUplinkDataPlacementOptions()
|
||||
{
|
||||
// Matches splithttp uplink payload placement (packet-up / advanced)
|
||||
return { "Body", "Auto", "Header", "Cookie" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xPaddingPlacementOptions()
|
||||
{
|
||||
// Xray-core: cookie | header | query | queryInHeader (not "body")
|
||||
return { "Cookie", "Header", "Query", "Query in header" };
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::xPaddingMethodOptions()
|
||||
{
|
||||
return { "Repeat-x", "Tokenish" };
|
||||
}
|
||||
|
||||
QString XrayConfigModel::mkcpDefaultTti()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpTti);
|
||||
}
|
||||
|
||||
QString XrayConfigModel::mkcpDefaultUplinkCapacity()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpUplinkCapacity);
|
||||
}
|
||||
|
||||
QString XrayConfigModel::mkcpDefaultDownlinkCapacity()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpDownlinkCapacity);
|
||||
}
|
||||
|
||||
QString XrayConfigModel::mkcpDefaultReadBufferSize()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpReadBufferSize);
|
||||
}
|
||||
|
||||
QString XrayConfigModel::mkcpDefaultWriteBufferSize()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define XRAYCONFIGMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QStringList>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
@@ -93,6 +94,29 @@ public:
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
// ── Static option lists (for QML DropDown models) ─────────────────
|
||||
Q_INVOKABLE static QStringList flowOptions();
|
||||
Q_INVOKABLE static QStringList securityOptions();
|
||||
Q_INVOKABLE static QStringList transportOptions();
|
||||
Q_INVOKABLE static QStringList fingerprintOptions();
|
||||
Q_INVOKABLE static QStringList alpnOptions();
|
||||
Q_INVOKABLE static QStringList xhttpModeOptions();
|
||||
Q_INVOKABLE static QStringList xhttpHeadersTemplateOptions();
|
||||
Q_INVOKABLE static QStringList xhttpUplinkMethodOptions();
|
||||
Q_INVOKABLE static QStringList xhttpSessionPlacementOptions();
|
||||
Q_INVOKABLE static QStringList xhttpSessionKeyOptions();
|
||||
Q_INVOKABLE static QStringList xhttpSeqPlacementOptions();
|
||||
Q_INVOKABLE static QStringList xhttpUplinkDataPlacementOptions();
|
||||
Q_INVOKABLE static QStringList xPaddingPlacementOptions();
|
||||
Q_INVOKABLE static QStringList xPaddingMethodOptions();
|
||||
|
||||
// mKCP display defaults (protocolConstants.h — must match xrayConfigurator empty-field behavior)
|
||||
Q_INVOKABLE static QString mkcpDefaultTti();
|
||||
Q_INVOKABLE static QString mkcpDefaultUplinkCapacity();
|
||||
Q_INVOKABLE static QString mkcpDefaultDownlinkCapacity();
|
||||
Q_INVOKABLE static QString mkcpDefaultReadBufferSize();
|
||||
Q_INVOKABLE static QString mkcpDefaultWriteBufferSize();
|
||||
|
||||
public slots:
|
||||
void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig);
|
||||
amnezia::XrayProtocolConfig getProtocolConfig();
|
||||
|
||||
@@ -10,6 +10,7 @@ Item {
|
||||
id: root
|
||||
|
||||
property string headerText
|
||||
property string subtitleText // optional line under header (e.g. default value hint)
|
||||
property string headerTextDisabledColor: AmneziaStyle.color.charcoalGray
|
||||
property string headerTextColor: AmneziaStyle.color.mutedGray
|
||||
|
||||
@@ -84,6 +85,15 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SmallTextType {
|
||||
text: root.subtitleText
|
||||
visible: root.subtitleText !== ""
|
||||
color: AmneziaStyle.color.charcoalGray
|
||||
font.pixelSize: 13
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: visible ? 2 : 0
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: textField
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -86,20 +88,37 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -99,14 +101,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "HTTP/2"
|
||||
}
|
||||
ListElement {
|
||||
name: "HTTP/1.1"
|
||||
}
|
||||
ListElement {
|
||||
name: "HTTP/2,HTTP/1.1"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.alpnOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -145,35 +144,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Mozilla/5.0"
|
||||
}
|
||||
ListElement {
|
||||
name: "chrome"
|
||||
}
|
||||
ListElement {
|
||||
name: "firefox"
|
||||
}
|
||||
ListElement {
|
||||
name: "safari"
|
||||
}
|
||||
ListElement {
|
||||
name: "ios"
|
||||
}
|
||||
ListElement {
|
||||
name: "android"
|
||||
}
|
||||
ListElement {
|
||||
name: "edge"
|
||||
}
|
||||
ListElement {
|
||||
name: "360"
|
||||
}
|
||||
ListElement {
|
||||
name: "qq"
|
||||
}
|
||||
ListElement {
|
||||
name: "random"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.fingerprintOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -231,35 +206,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Mozilla/5.0"
|
||||
}
|
||||
ListElement {
|
||||
name: "chrome"
|
||||
}
|
||||
ListElement {
|
||||
name: "firefox"
|
||||
}
|
||||
ListElement {
|
||||
name: "safari"
|
||||
}
|
||||
ListElement {
|
||||
name: "ios"
|
||||
}
|
||||
ListElement {
|
||||
name: "android"
|
||||
}
|
||||
ListElement {
|
||||
name: "edge"
|
||||
}
|
||||
ListElement {
|
||||
name: "360"
|
||||
}
|
||||
ListElement {
|
||||
name: "qq"
|
||||
}
|
||||
ListElement {
|
||||
name: "random"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.fingerprintOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -304,21 +255,37 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
}
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ PageType {
|
||||
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("XRay\nVLESS")
|
||||
text: qsTr("XRay VLESS settings")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -106,6 +108,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("TTI")
|
||||
subtitleText: qsTr("Default: %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
|
||||
textField.text: mkcpTti
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== mkcpTti) mkcpTti = textField.text
|
||||
@@ -118,6 +121,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("uplinkCapacity")
|
||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
|
||||
textField.text: mkcpUplinkCapacity
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== mkcpUplinkCapacity) mkcpUplinkCapacity = textField.text
|
||||
@@ -130,6 +134,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("downlinkCapacity")
|
||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
|
||||
textField.text: mkcpDownlinkCapacity
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = textField.text
|
||||
@@ -142,6 +147,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("readBufferSize")
|
||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
|
||||
textField.text: mkcpReadBufferSize
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== mkcpReadBufferSize) mkcpReadBufferSize = textField.text
|
||||
@@ -154,6 +160,7 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("writeBufferSize")
|
||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
|
||||
textField.text: mkcpWriteBufferSize
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== mkcpWriteBufferSize) mkcpWriteBufferSize = textField.text
|
||||
@@ -178,10 +185,20 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("Transport Mode")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: modeDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.topMargin: 0
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
text: xhttpMode
|
||||
@@ -191,17 +208,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Auto"
|
||||
}
|
||||
ListElement {
|
||||
name: "Packet-up"
|
||||
}
|
||||
ListElement {
|
||||
name: "Stream-up"
|
||||
}
|
||||
ListElement {
|
||||
name: "Stream-one"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpModeOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -274,11 +285,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "HTTP"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpHeadersTemplateOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -317,14 +328,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "POST"
|
||||
}
|
||||
ListElement {
|
||||
name: "PUT"
|
||||
}
|
||||
ListElement {
|
||||
name: "PATCH"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpUplinkMethodOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -399,17 +407,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpSessionPlacementOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -448,14 +450,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpSessionKeyOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -494,17 +493,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Path"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "None"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpSeqPlacementOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -555,11 +548,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Body"
|
||||
}
|
||||
ListElement {
|
||||
name: "Query"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xhttpUplinkDataPlacementOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -654,25 +647,6 @@ PageType {
|
||||
onMaxChanged: xhttpScMaxEachPostBytesMax = val
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("scMinPostsIntervalMs")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: xhttpScMinPostsIntervalMsMin
|
||||
maxValue: xhttpScMinPostsIntervalMsMax
|
||||
onMinChanged: xhttpScMinPostsIntervalMsMin = val
|
||||
onMaxChanged: xhttpScMinPostsIntervalMsMax = val
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
@@ -692,6 +666,25 @@ PageType {
|
||||
onMaxChanged: xhttpScStreamUpServerSecsMax = val
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
text: qsTr("scMinPostsIntervalMs")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
MinMaxRowType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
minValue: xhttpScMinPostsIntervalMsMin
|
||||
maxValue: xhttpScMinPostsIntervalMsMax
|
||||
onMinChanged: xhttpScMinPostsIntervalMsMin = val
|
||||
onMaxChanged: xhttpScMinPostsIntervalMsMax = val
|
||||
}
|
||||
|
||||
// ── Padding and multiplexing ──────────────────────────
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
@@ -735,21 +728,37 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
}
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -69,20 +71,37 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -106,17 +108,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Cookie"
|
||||
}
|
||||
ListElement {
|
||||
name: "Header"
|
||||
}
|
||||
ListElement {
|
||||
name: "Query"
|
||||
}
|
||||
ListElement {
|
||||
name: "Body"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xPaddingPlacementOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -155,14 +151,11 @@ PageType {
|
||||
listView: ListViewWithRadioButtonType {
|
||||
rootWidth: root.width
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
name: "Repeat-x"
|
||||
}
|
||||
ListElement {
|
||||
name: "Random"
|
||||
}
|
||||
ListElement {
|
||||
name: "Zero"
|
||||
Component.onCompleted: {
|
||||
var opts = XrayConfigModel.xPaddingMethodOptions()
|
||||
for (var i = 0; i < opts.length; i++) {
|
||||
append({name: opts[i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
clickedFunction: function () {
|
||||
@@ -194,20 +187,37 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
@@ -25,10 +26,11 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersUiController.isProcessedServerHasWriteAccess()
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
@@ -182,21 +184,38 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
// PageController.closePage()
|
||||
// }
|
||||
// Keys.onEnterPressed: clicked()
|
||||
// Keys.onReturnPressed: clicked()
|
||||
// }
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
enabled: listView.enabled
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
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") === ServersUiController.processedContainerIndex) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
saveButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user