mirror of
https://github.com/MatsuriDayo/nekoray.git
synced 2025-12-17 20:44:38 +03:00
feat: inbound authorization
This commit is contained in:
@@ -135,6 +135,7 @@ set(PROJECT_SOURCES
|
||||
main/NekoRay.cpp
|
||||
main/NekoRay_Utils.cpp
|
||||
main/QJS.cpp
|
||||
main/HTTPRequestHelper.cpp
|
||||
|
||||
3rdparty/base64.cpp
|
||||
3rdparty/qrcodegen.cpp
|
||||
@@ -142,7 +143,6 @@ set(PROJECT_SOURCES
|
||||
|
||||
qv2ray/v2/ui/LogHighlighter.cpp
|
||||
qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp
|
||||
qv2ray/v2/utils/HTTPRequestHelper.cpp
|
||||
qv2ray/v2/components/proxy/QvProxyConfigurator.cpp
|
||||
qv2ray/v2/ui/widgets/common/QJsonModel.cpp
|
||||
qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp
|
||||
@@ -174,7 +174,7 @@ set(PROJECT_SOURCES
|
||||
sys/AutoRun.cpp
|
||||
|
||||
ui/ThemeManager.cpp
|
||||
ui/TrayIcon.cpp
|
||||
ui/Icon.cpp
|
||||
|
||||
ui/mainwindow_grpc.cpp
|
||||
ui/mainwindow.cpp
|
||||
|
||||
@@ -101,32 +101,26 @@ namespace NekoRay {
|
||||
}
|
||||
|
||||
#define DOMAIN_USER_RULE \
|
||||
for (const auto &line: SplitLines(dataStore->routing->proxy_domain)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) { \
|
||||
if (dataStore->dns_routing) status->domainListDNSRemote += line; \
|
||||
status->domainListRemote += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLines(dataStore->routing->direct_domain)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \
|
||||
if (dataStore->dns_routing) status->domainListDNSDirect += line; \
|
||||
status->domainListDirect += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLines(dataStore->routing->block_domain)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) { \
|
||||
status->domainListBlock += line; \
|
||||
}
|
||||
|
||||
#define IP_USER_RULE \
|
||||
for (const auto &line: SplitLines(dataStore->routing->block_ip)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) { \
|
||||
status->ipListBlock += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLines(dataStore->routing->proxy_ip)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) { \
|
||||
status->ipListRemote += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLines(dataStore->routing->direct_ip)) { \
|
||||
if (line.startsWith("#")) continue; \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \
|
||||
status->ipListDirect += line; \
|
||||
}
|
||||
|
||||
@@ -147,32 +141,49 @@ namespace NekoRay {
|
||||
};
|
||||
|
||||
// socks-in
|
||||
if (InRange(dataStore->inbound_socks_port, 1, 65535) && !status->forTest) {
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["tag"] = "socks-in";
|
||||
socksInbound["protocol"] = "socks";
|
||||
socksInbound["listen"] = dataStore->inbound_address;
|
||||
socksInbound["port"] = dataStore->inbound_socks_port;
|
||||
socksInbound["settings"] = QJsonObject{
|
||||
{"auth", "noauth"},
|
||||
{"udp", true},
|
||||
};
|
||||
if (IsValidPort(dataStore->inbound_socks_port) && !status->forTest) {
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["tag"] = "socks-in";
|
||||
inboundObj["protocol"] = "socks";
|
||||
inboundObj["listen"] = dataStore->inbound_address;
|
||||
inboundObj["port"] = dataStore->inbound_socks_port;
|
||||
QJsonObject socksSettings = {{"udp", true}};
|
||||
if (dataStore->fake_dns || dataStore->sniffing_mode != SniffingMode::DISABLE) {
|
||||
socksInbound["sniffing"] = sniffing;
|
||||
inboundObj["sniffing"] = sniffing;
|
||||
}
|
||||
status->inbounds += socksInbound;
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
socksSettings["auth"] = "password";
|
||||
socksSettings["accounts"] = QJsonArray{
|
||||
QJsonObject{
|
||||
{"user", dataStore->inbound_auth->username},
|
||||
{"pass", dataStore->inbound_auth->password},
|
||||
},
|
||||
};
|
||||
}
|
||||
inboundObj["settings"] = socksSettings;
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
// http-in
|
||||
if (InRange(dataStore->inbound_http_port, 1, 65535) && !status->forTest) {
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["tag"] = "http-in";
|
||||
socksInbound["protocol"] = "http";
|
||||
socksInbound["listen"] = dataStore->inbound_address;
|
||||
socksInbound["port"] = dataStore->inbound_http_port;
|
||||
if (IsValidPort(dataStore->inbound_http_port) && !status->forTest) {
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["tag"] = "http-in";
|
||||
inboundObj["protocol"] = "http";
|
||||
inboundObj["listen"] = dataStore->inbound_address;
|
||||
inboundObj["port"] = dataStore->inbound_http_port;
|
||||
if (dataStore->sniffing_mode != SniffingMode::DISABLE) {
|
||||
socksInbound["sniffing"] = sniffing;
|
||||
inboundObj["sniffing"] = sniffing;
|
||||
}
|
||||
status->inbounds += socksInbound;
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
inboundObj["settings"] = QJsonObject{
|
||||
{"accounts", QJsonArray{
|
||||
QJsonObject{
|
||||
{"user", dataStore->inbound_auth->username},
|
||||
{"pass", dataStore->inbound_auth->password},
|
||||
},
|
||||
}},
|
||||
};
|
||||
}
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
|
||||
// Outbounds
|
||||
@@ -431,12 +442,12 @@ namespace NekoRay {
|
||||
if (thisExternalStat > 0) {
|
||||
if (ent->type == "custom") {
|
||||
auto bean = ent->CustomBean();
|
||||
if (InRange(bean->mapping_port, 1, 65535)) {
|
||||
if (IsValidPort(bean->mapping_port)) {
|
||||
ext_mapping_port = bean->mapping_port;
|
||||
} else {
|
||||
ext_mapping_port = MkPort();
|
||||
}
|
||||
if (InRange(bean->socks_port, 1, 65535)) {
|
||||
if (IsValidPort(bean->socks_port)) {
|
||||
ext_socks_port = bean->socks_port;
|
||||
} else {
|
||||
ext_socks_port = MkPort();
|
||||
@@ -617,17 +628,25 @@ namespace NekoRay {
|
||||
// Inbounds
|
||||
|
||||
// mixed-in
|
||||
if (InRange(dataStore->inbound_socks_port, 1, 65535) && !status->forTest) {
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["tag"] = "mixed-in";
|
||||
socksInbound["type"] = "mixed";
|
||||
socksInbound["listen"] = dataStore->inbound_address;
|
||||
socksInbound["listen_port"] = dataStore->inbound_socks_port;
|
||||
if (IsValidPort(dataStore->inbound_socks_port) && !status->forTest) {
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["tag"] = "mixed-in";
|
||||
inboundObj["type"] = "mixed";
|
||||
inboundObj["listen"] = dataStore->inbound_address;
|
||||
inboundObj["listen_port"] = dataStore->inbound_socks_port;
|
||||
if (dataStore->sniffing_mode != SniffingMode::DISABLE) {
|
||||
socksInbound["sniff"] = true;
|
||||
socksInbound["sniff_override_destination"] = dataStore->sniffing_mode == SniffingMode::FOR_DESTINATION;
|
||||
inboundObj["sniff"] = true;
|
||||
inboundObj["sniff_override_destination"] = dataStore->sniffing_mode == SniffingMode::FOR_DESTINATION;
|
||||
}
|
||||
status->inbounds += socksInbound;
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
inboundObj["users"] = QJsonArray{
|
||||
QJsonObject{
|
||||
{"username", dataStore->inbound_auth->username},
|
||||
{"password", dataStore->inbound_auth->password},
|
||||
},
|
||||
};
|
||||
}
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
|
||||
// Outbounds
|
||||
@@ -839,26 +858,32 @@ namespace NekoRay {
|
||||
}
|
||||
|
||||
QString WriteVPNSingBoxConfig() {
|
||||
//
|
||||
// user rule
|
||||
QString process_name_rule = dataStore->vpn_bypass_process.trimmed();
|
||||
if (!process_name_rule.isEmpty()) {
|
||||
auto arr = SplitLines(process_name_rule);
|
||||
auto arr = SplitLinesSkipSharp(process_name_rule);
|
||||
QJsonObject rule{{"outbound", "direct"},
|
||||
{"process_name", QList2QJsonArray(arr)}};
|
||||
process_name_rule = "," + QJsonObject2QString(rule, false);
|
||||
}
|
||||
QString cidr_rule = dataStore->vpn_bypass_cidr.trimmed();
|
||||
if (!cidr_rule.isEmpty()) {
|
||||
auto arr = SplitLines(cidr_rule);
|
||||
auto arr = SplitLinesSkipSharp(cidr_rule);
|
||||
QJsonObject rule{{"outbound", "direct"},
|
||||
{"ip_cidr", QList2QJsonArray(arr)}};
|
||||
cidr_rule = "," + QJsonObject2QString(rule, false);
|
||||
}
|
||||
//
|
||||
// tun name
|
||||
auto tun_name = "nekoray-tun";
|
||||
#ifdef Q_OS_MACOS
|
||||
tun_name = "utun9";
|
||||
#endif
|
||||
// auth
|
||||
QString socks_user_pass;
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
socks_user_pass = R"( "username": "%1", "password": "%2", )";
|
||||
socks_user_pass = socks_user_pass.arg(dataStore->inbound_auth->username, dataStore->inbound_auth->password);
|
||||
}
|
||||
// gen config
|
||||
auto configFn = ":/neko/vpn/sing-box-vpn.json";
|
||||
if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json";
|
||||
@@ -870,6 +895,7 @@ namespace NekoRay {
|
||||
.replace("%CIDR_RULE%", cidr_rule)
|
||||
.replace("%TUN_NAME%", tun_name)
|
||||
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
|
||||
.replace("%SOCKS_USER_PASS%", socks_user_pass)
|
||||
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
|
||||
// hook.js
|
||||
auto source = qjs::ReadHookJS();
|
||||
@@ -891,11 +917,13 @@ namespace NekoRay {
|
||||
}
|
||||
|
||||
QString WriteVPNLinuxScript(const QString &protectPath, const QString &configPath) {
|
||||
#ifdef Q_OS_WIN
|
||||
return {};
|
||||
#endif
|
||||
// gen script
|
||||
auto scriptFn = ":/neko/vpn/vpn-run-root.sh";
|
||||
if (QFile::exists("vpn/vpn-run-root.sh")) scriptFn = "vpn/vpn-run-root.sh";
|
||||
auto script = ReadFileText(scriptFn)
|
||||
.replace("$PORT", Int2String(dataStore->inbound_socks_port))
|
||||
.replace("./nekobox_core", QApplication::applicationDirPath() + "/nekobox_core")
|
||||
.replace("$PROTECT_LISTEN_PATH", protectPath)
|
||||
.replace("$CONFIG_PATH", configPath)
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
#include "qv2ray/wrapper.hpp"
|
||||
|
||||
namespace Qv2ray::common::network {
|
||||
namespace NekoRay::network {
|
||||
|
||||
NekoHTTPResponse NetworkRequestHelper::HttpGet(const QUrl &url) {
|
||||
QNetworkRequest request;
|
||||
@@ -22,13 +20,16 @@ namespace Qv2ray::common::network {
|
||||
p.setType(IS_NEKO_BOX ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy);
|
||||
p.setHostName("127.0.0.1");
|
||||
p.setPort(NekoRay::dataStore->inbound_socks_port);
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
p.setUser(dataStore->inbound_auth->username);
|
||||
p.setPassword(dataStore->inbound_auth->password);
|
||||
}
|
||||
accessManager.setProxy(p);
|
||||
if (NekoRay::dataStore->started_id < 0) {
|
||||
return NekoHTTPResponse{QObject::tr("Request with proxy but no profile started.")};
|
||||
}
|
||||
}
|
||||
if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) {
|
||||
DEBUG("Adding HostNameLookupCapability to proxy.");
|
||||
auto cap = accessManager.proxy().capabilities();
|
||||
accessManager.proxy().setCapabilities(cap | QNetworkProxy::HostNameLookupCapability);
|
||||
}
|
||||
@@ -69,4 +70,4 @@ namespace Qv2ray::common::network {
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace Qv2ray::common::network
|
||||
} // namespace NekoRay::network
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace Qv2ray::common::network {
|
||||
namespace NekoRay::network {
|
||||
struct NekoHTTPResponse {
|
||||
QString error;
|
||||
QByteArray data;
|
||||
@@ -26,6 +26,6 @@ namespace Qv2ray::common::network {
|
||||
|
||||
static QString GetHeader(const QList<QPair<QByteArray, QByteArray>> &header, const QString &name);
|
||||
};
|
||||
} // namespace Qv2ray::common::network
|
||||
} // namespace NekoRay::network
|
||||
|
||||
using namespace Qv2ray::common::network;
|
||||
using namespace NekoRay::network;
|
||||
@@ -17,6 +17,7 @@ namespace NekoRay {
|
||||
|
||||
DataStore::DataStore() : JsonStore() {
|
||||
_add(new configItem("extraCore", dynamic_cast<JsonStore *>(extraCore), itemType::jsonStore));
|
||||
_add(new configItem("inbound_auth", dynamic_cast<JsonStore *>(inbound_auth), itemType::jsonStore));
|
||||
|
||||
_add(new configItem("user_agent", &user_agent, itemType::string));
|
||||
_add(new configItem("test_url", &test_url, itemType::string));
|
||||
@@ -112,12 +113,12 @@ namespace NekoRay {
|
||||
|
||||
QString Routing::DisplayRouting() const {
|
||||
return QString("[Proxy] %1\n[Proxy] %2\n[Direct] %3\n[Direct] %4\n[Block] %5\n[Block] %6")
|
||||
.arg(SplitLines(proxy_domain).join(","))
|
||||
.arg(SplitLines(proxy_ip).join(","))
|
||||
.arg(SplitLines(direct_domain).join(","))
|
||||
.arg(SplitLines(direct_ip).join(","))
|
||||
.arg(SplitLines(block_domain).join(","))
|
||||
.arg(SplitLines(block_ip).join(","));
|
||||
.arg(SplitLinesSkipSharp(proxy_domain).join(","))
|
||||
.arg(SplitLinesSkipSharp(proxy_ip).join(","))
|
||||
.arg(SplitLinesSkipSharp(direct_domain).join(","))
|
||||
.arg(SplitLinesSkipSharp(direct_ip).join(","))
|
||||
.arg(SplitLinesSkipSharp(block_domain).join(","))
|
||||
.arg(SplitLinesSkipSharp(block_ip).join(","));
|
||||
}
|
||||
|
||||
QStringList Routing::List() {
|
||||
@@ -163,6 +164,15 @@ namespace NekoRay {
|
||||
core_map = QJsonObject2QString(obj, true);
|
||||
}
|
||||
|
||||
InboundAuthorization::InboundAuthorization() : JsonStore() {
|
||||
_add(new configItem("user", &this->username, itemType::string));
|
||||
_add(new configItem("pass", &this->password, itemType::string));
|
||||
}
|
||||
|
||||
bool InboundAuthorization::NeedAuth() const {
|
||||
return !username.trimmed().isEmpty() && !password.trimmed().isEmpty();
|
||||
}
|
||||
|
||||
// 添加关联
|
||||
void JsonStore::_add(configItem *item) {
|
||||
_map.insert(item->name, QSharedPointer<configItem>(item));
|
||||
|
||||
@@ -34,6 +34,16 @@ namespace NekoRay {
|
||||
void Delete(const QString &id);
|
||||
};
|
||||
|
||||
class InboundAuthorization : public JsonStore {
|
||||
public:
|
||||
QString username;
|
||||
QString password;
|
||||
|
||||
InboundAuthorization();
|
||||
|
||||
[[nodiscard]] bool NeedAuth() const;
|
||||
};
|
||||
|
||||
class DataStore : public JsonStore {
|
||||
public:
|
||||
// Running
|
||||
@@ -98,6 +108,7 @@ namespace NekoRay {
|
||||
QString inbound_address = "127.0.0.1";
|
||||
int inbound_socks_port = 2080; // or Mixed
|
||||
int inbound_http_port = -2081;
|
||||
InboundAuthorization *inbound_auth = new InboundAuthorization;
|
||||
QString custom_inbound = "{\"inbounds\": []}";
|
||||
|
||||
// DNS
|
||||
|
||||
@@ -25,6 +25,16 @@ QStringList SplitLines(const QString &_string) {
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList SplitLinesSkipSharp(const QString &_string) {
|
||||
auto lines = SplitLines(_string);
|
||||
QStringList newLines;
|
||||
for (const auto &line: lines) {
|
||||
if (line.trimmed().startsWith("#")) continue;
|
||||
newLines << line;
|
||||
}
|
||||
return newLines;
|
||||
}
|
||||
|
||||
QString DecodeB64IfValid(const QString &input, QByteArray::Base64Options options) {
|
||||
Qt515Base64::Base64Options newOptions = Qt515Base64::Base64Option::AbortOnBase64DecodingErrors;
|
||||
if (options.testFlag(QByteArray::Base64UrlEncoding)) newOptions |= Qt515Base64::Base64Option::Base64UrlEncoding;
|
||||
@@ -134,6 +144,28 @@ int MkPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
QString ReadableSize(const qint64 &size) {
|
||||
double sizeAsDouble = size;
|
||||
static QStringList measures;
|
||||
if (measures.isEmpty())
|
||||
measures << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB"
|
||||
<< "TiB"
|
||||
<< "PiB"
|
||||
<< "EiB"
|
||||
<< "ZiB"
|
||||
<< "YiB";
|
||||
QStringListIterator it(measures);
|
||||
QString measure(it.next());
|
||||
while (sizeAsDouble >= 1024.0 && it.hasNext()) {
|
||||
measure = it.next();
|
||||
sizeAsDouble /= 1024.0;
|
||||
}
|
||||
return QString::fromLatin1("%1 %2").arg(sizeAsDouble, 0, 'f', 2).arg(measure);
|
||||
}
|
||||
|
||||
bool IsIpAddress(const QString &str) {
|
||||
auto address = QHostAddress(str);
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
|
||||
@@ -43,6 +43,8 @@ QString QStringList2Command(const QStringList &list);
|
||||
|
||||
QStringList SplitLines(const QString &_string);
|
||||
|
||||
QStringList SplitLinesSkipSharp(const QString &_string);
|
||||
|
||||
// Base64
|
||||
|
||||
QString DecodeB64IfValid(const QString &input, QByteArray::Base64Options options = QByteArray::Base64Option::Base64Encoding);
|
||||
@@ -120,32 +122,16 @@ int MkPort();
|
||||
|
||||
QString DisplayTime(long long time, int formatType = 0);
|
||||
|
||||
inline QString ReadableSize(const qint64 &size) {
|
||||
double sizeAsDouble = size;
|
||||
static QStringList measures;
|
||||
if (measures.isEmpty())
|
||||
measures << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB"
|
||||
<< "TiB"
|
||||
<< "PiB"
|
||||
<< "EiB"
|
||||
<< "ZiB"
|
||||
<< "YiB";
|
||||
QStringListIterator it(measures);
|
||||
QString measure(it.next());
|
||||
while (sizeAsDouble >= 1024.0 && it.hasNext()) {
|
||||
measure = it.next();
|
||||
sizeAsDouble /= 1024.0;
|
||||
}
|
||||
return QString::fromLatin1("%1 %2").arg(sizeAsDouble, 0, 'f', 2).arg(measure);
|
||||
}
|
||||
QString ReadableSize(const qint64 &size);
|
||||
|
||||
inline bool InRange(unsigned x, unsigned low, unsigned high) {
|
||||
return (low <= x && x <= high);
|
||||
}
|
||||
|
||||
inline bool IsValidPort(int port) {
|
||||
return InRange(port, 1, 65535);
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
QWidget *GetMessageBoxParent();
|
||||
|
||||
1
res/icon/material/lock-open-outline.svg
Normal file
1
res/icon/material/lock-open-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,1 10,15A2,2 0 0,1 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17Z" /></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
1
res/icon/material/lock-outline.svg
Normal file
1
res/icon/material/lock-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" /></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -6,6 +6,8 @@
|
||||
<file alias="icon/b-network-server.svg">icon/network-server.svg</file>
|
||||
<file alias="icon/b-dialog-question.svg">icon/dialog-question.svg</file>
|
||||
<file alias="icon/b-system-software-update.svg">icon/system-software-update.svg</file>
|
||||
<file>icon/material/lock-open-outline.svg</file>
|
||||
<file>icon/material/lock-outline.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/neko">
|
||||
<file alias="nekobox.png">public/nekobox.png</file>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"type": "socks",
|
||||
"tag": "nekoray-socks",
|
||||
"udp_fragment": true,
|
||||
%SOCKS_USER_PASS%
|
||||
"server": "127.0.0.1",
|
||||
"server_port": %PORT%
|
||||
},
|
||||
|
||||
@@ -11,7 +11,6 @@ if [ "$(uname)" == "Darwin" ]; then
|
||||
IS_MACOS=1
|
||||
fi
|
||||
|
||||
[ -z $PORT ] && echo "Please set env PORT" && exit
|
||||
[ -z $TABLE_FWMARK ] && echo "Please set env TABLE_FWMARK" && exit
|
||||
command -v pkill >/dev/null 2>&1 || exit
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#include "qv2ray/v2/utils/HTTPRequestHelper.hpp"
|
||||
|
||||
#include "db/Database.hpp"
|
||||
#include "db/ProfileFilter.hpp"
|
||||
#include "fmt/includes.h"
|
||||
#include "fmt/Preset.hpp"
|
||||
#include "main/QJS.hpp"
|
||||
#include "main/HTTPRequestHelper.hpp"
|
||||
|
||||
#include "GroupUpdater.hpp"
|
||||
|
||||
|
||||
@@ -217,6 +217,18 @@
|
||||
<source>Max log lines</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Inbound Auth</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username</source>
|
||||
<translation type="unfinished">نام کاربری</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogEditGroup</name>
|
||||
|
||||
@@ -215,6 +215,18 @@
|
||||
<source>Max log lines</source>
|
||||
<translation>日志最大行数限制</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Inbound Auth</source>
|
||||
<translation>入站认证设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username</source>
|
||||
<translation>用户名</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<translation>密码</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogEditGroup</name>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "TrayIcon.hpp"
|
||||
#include "Icon.hpp"
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
QIcon TrayIcon::GetIcon(TrayIcon::TrayIconStatus status) {
|
||||
QIcon Icon::GetTrayIcon(Icon::TrayIconStatus status) {
|
||||
QPixmap pixmap;
|
||||
|
||||
// software embedded icon
|
||||
@@ -46,3 +46,8 @@ QIcon TrayIcon::GetIcon(TrayIcon::TrayIconStatus status) {
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QIcon Icon::GetMaterialIcon(const QString &name) {
|
||||
QPixmap pixmap(":/icon/material/" + name + ".svg");
|
||||
return pixmap;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
namespace TrayIcon {
|
||||
namespace Icon {
|
||||
|
||||
enum TrayIconStatus {
|
||||
NONE,
|
||||
@@ -11,6 +11,8 @@ namespace TrayIcon {
|
||||
VPN,
|
||||
};
|
||||
|
||||
QIcon GetIcon(TrayIconStatus status);
|
||||
QIcon GetTrayIcon(TrayIconStatus status);
|
||||
|
||||
} // namespace TrayIcon
|
||||
QIcon GetMaterialIcon(const QString &name);
|
||||
|
||||
} // namespace Icon
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp"
|
||||
#include "fmt/Preset.hpp"
|
||||
#include "ui/ThemeManager.hpp"
|
||||
#include "ui/Icon.hpp"
|
||||
#include "main/GuiUtils.hpp"
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
@@ -66,6 +67,8 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
||||
ui->log_level->addItems({"debug", "info", "warning", "none"});
|
||||
}
|
||||
|
||||
refresh_auth();
|
||||
|
||||
ui->socks_ip->setText(NekoRay::dataStore->inbound_address);
|
||||
ui->log_level->setCurrentText(NekoRay::dataStore->log_level);
|
||||
CACHE.custom_inbound = NekoRay::dataStore->custom_inbound;
|
||||
@@ -298,6 +301,17 @@ void DialogBasicSettings::accept() {
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
// slots
|
||||
|
||||
void DialogBasicSettings::refresh_auth() {
|
||||
ui->inbound_auth->setText({});
|
||||
if (NekoRay::dataStore->inbound_auth->NeedAuth()) {
|
||||
ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-outline"));
|
||||
} else {
|
||||
ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-open-outline"));
|
||||
}
|
||||
}
|
||||
|
||||
void DialogBasicSettings::on_set_custom_icon_clicked() {
|
||||
auto title = ui->set_custom_icon->text();
|
||||
QString user_icon_path = "./" + software_name.toLower() + ".png";
|
||||
@@ -319,3 +333,37 @@ void DialogBasicSettings::on_set_custom_icon_clicked() {
|
||||
}
|
||||
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateIcon");
|
||||
}
|
||||
|
||||
void DialogBasicSettings::on_inbound_auth_clicked() {
|
||||
auto w = new QDialog(this);
|
||||
w->setWindowTitle(tr("Inbound Auth"));
|
||||
auto layout = new QGridLayout;
|
||||
w->setLayout(layout);
|
||||
//
|
||||
auto user_l = new QLabel(tr("Username"));
|
||||
auto pass_l = new QLabel(tr("Password"));
|
||||
auto user = new MyLineEdit;
|
||||
auto pass = new MyLineEdit;
|
||||
user->setText(NekoRay::dataStore->inbound_auth->username);
|
||||
pass->setText(NekoRay::dataStore->inbound_auth->password);
|
||||
//
|
||||
layout->addWidget(user_l, 0, 0);
|
||||
layout->addWidget(user, 0, 1);
|
||||
layout->addWidget(pass_l, 1, 0);
|
||||
layout->addWidget(pass, 1, 1);
|
||||
auto box = new QDialogButtonBox;
|
||||
box->setOrientation(Qt::Horizontal);
|
||||
box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(box, &QDialogButtonBox::accepted, w, [=] {
|
||||
NekoRay::dataStore->inbound_auth->username = user->text();
|
||||
NekoRay::dataStore->inbound_auth->password = pass->text();
|
||||
NekoRay::dataStore->Save();
|
||||
w->accept();
|
||||
});
|
||||
connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);
|
||||
layout->addWidget(box, 2, 1);
|
||||
//
|
||||
w->exec();
|
||||
w->deleteLater();
|
||||
refresh_auth();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,11 @@ private:
|
||||
|
||||
private slots:
|
||||
|
||||
void refresh_auth();
|
||||
|
||||
void on_set_custom_icon_clicked();
|
||||
|
||||
void on_inbound_auth_clicked();
|
||||
};
|
||||
|
||||
#endif // DIALOG_BASIC_SETTINGS_H
|
||||
|
||||
@@ -45,6 +45,13 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="socks_ip"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="inbound_auth">
|
||||
<property name="text">
|
||||
<string notr="true">Auth</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -199,6 +206,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="sys_proxy_format_vline">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sys_proxy_format">
|
||||
<property name="text">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "sys/AutoRun.hpp"
|
||||
|
||||
#include "ui/ThemeManager.hpp"
|
||||
#include "ui/TrayIcon.hpp"
|
||||
#include "ui/Icon.hpp"
|
||||
#include "ui/edit/dialog_edit_profile.h"
|
||||
#include "ui/dialog_basic_settings.h"
|
||||
#include "ui/dialog_manage_groups.h"
|
||||
@@ -223,7 +223,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
|
||||
// Setup Tray
|
||||
tray = new QSystemTrayIcon(this); // 初始化托盘对象tray
|
||||
tray->setIcon(TrayIcon::GetIcon(TrayIcon::NONE));
|
||||
tray->setIcon(Icon::GetTrayIcon(Icon::NONE));
|
||||
tray->setContextMenu(ui->menu_program); // 创建托盘菜单
|
||||
tray->show(); // 让托盘图标显示在系统托盘上
|
||||
connect(tray, &QSystemTrayIcon::activated, this, [=](QSystemTrayIcon::ActivationReason reason) {
|
||||
@@ -587,8 +587,7 @@ void MainWindow::neko_set_spmode(int mode, bool save) {
|
||||
|
||||
if (mode == NekoRay::SystemProxyMode::SYSTEM_PROXY) {
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
if (mode == NekoRay::SystemProxyMode::SYSTEM_PROXY && !IS_NEKO_BOX &&
|
||||
!InRange(NekoRay::dataStore->inbound_http_port, 1, 65535)) {
|
||||
if (mode == NekoRay::SystemProxyMode::SYSTEM_PROXY && !IS_NEKO_BOX && !IsValidPort(NekoRay::dataStore->inbound_http_port)) {
|
||||
auto btn = QMessageBox::warning(this, software_name,
|
||||
tr("Http inbound is not enabled, can't set system proxy."),
|
||||
"OK", tr("Settings"), "", 0, 0);
|
||||
@@ -646,7 +645,7 @@ void MainWindow::refresh_status(const QString &traffic_update) {
|
||||
}
|
||||
//
|
||||
auto display_http = tr("None");
|
||||
if (InRange(NekoRay::dataStore->inbound_http_port, 1, 65535)) {
|
||||
if (IsValidPort(NekoRay::dataStore->inbound_http_port)) {
|
||||
display_http = DisplayAddress(NekoRay::dataStore->inbound_address, NekoRay::dataStore->inbound_http_port);
|
||||
}
|
||||
auto display_socks = DisplayAddress(NekoRay::dataStore->inbound_address, NekoRay::dataStore->inbound_socks_port);
|
||||
@@ -676,21 +675,23 @@ void MainWindow::refresh_status(const QString &traffic_update) {
|
||||
return tt.join(isTray ? "\n" : " ");
|
||||
};
|
||||
|
||||
auto icon_status_new = TrayIcon::NONE;
|
||||
auto icon_status_new = Icon::NONE;
|
||||
if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::SYSTEM_PROXY) {
|
||||
icon_status_new = TrayIcon::SYSTEM_PROXY;
|
||||
icon_status_new = Icon::SYSTEM_PROXY;
|
||||
} else if (NekoRay::dataStore->running_spmode == NekoRay::SystemProxyMode::VPN) {
|
||||
icon_status_new = TrayIcon::VPN;
|
||||
icon_status_new = Icon::VPN;
|
||||
} else if (!running.isNull()) {
|
||||
icon_status_new = TrayIcon::RUNNING;
|
||||
icon_status_new = Icon::RUNNING;
|
||||
}
|
||||
|
||||
// refresh title & window icon
|
||||
setWindowTitle(make_title(false));
|
||||
if (icon_status_new != icon_status) QApplication::setWindowIcon(TrayIcon::GetIcon(TrayIcon::NONE));
|
||||
if (icon_status_new != icon_status) QApplication::setWindowIcon(Icon::GetTrayIcon(Icon::NONE));
|
||||
|
||||
// refresh tray
|
||||
if (tray != nullptr) {
|
||||
tray->setToolTip(make_title(true));
|
||||
if (icon_status_new != icon_status) tray->setIcon(TrayIcon::GetIcon(icon_status_new));
|
||||
if (icon_status_new != icon_status) tray->setIcon(Icon::GetTrayIcon(icon_status_new));
|
||||
}
|
||||
|
||||
icon_status = icon_status_new;
|
||||
@@ -1310,7 +1311,7 @@ void MainWindow::show_log_impl(const QString &log) {
|
||||
}
|
||||
}
|
||||
|
||||
#define ADD_TO_CURRENT_ROUTE(a, b) NekoRay::dataStore->routing->a = (SplitLines(NekoRay::dataStore->routing->a) << b).join("\n");
|
||||
#define ADD_TO_CURRENT_ROUTE(a, b) NekoRay::dataStore->routing->a = (SplitLines(NekoRay::dataStore->routing->a) << (b)).join("\n");
|
||||
|
||||
void MainWindow::on_masterLogBrowser_customContextMenuRequested(const QPoint &pos) {
|
||||
QMenu *menu = ui->masterLogBrowser->createStandardContextMenu();
|
||||
|
||||
Reference in New Issue
Block a user