feat: Partially implement new route GUI

This commit is contained in:
unknown 2024-06-06 05:55:34 +03:30
parent 2bc9fd210c
commit c201548aa8
No known key found for this signature in database
GPG Key ID: C2CA486E4F771093
15 changed files with 827 additions and 582 deletions

View File

@ -22,11 +22,11 @@ endif ()
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools)
if (NKR_CROSS)
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
set_property(TARGET Qt6::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
set_property(TARGET Qt6::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
set_property(TARGET Qt6::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
set_property(TARGET Qt6::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
set_property(TARGET Qt6::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
endif ()
#### Platform Variables ####
@ -250,10 +250,15 @@ set(PROJECT_SOURCES
ui/widget/GroupItem.cpp
ui/widget/GroupItem.h
ui/widget/GroupItem.ui
ui/widget/RouteItem.cpp
ui/widget/RouteItem.h
ui/widget/RouteItem.ui
res/neko.qrc
res/theme/feiyangqingyun/qss.qrc
${QV2RAY_RC}
db/RouteEntity.h
db/RouteEntity.cpp
)
# Qt exe

View File

@ -493,11 +493,6 @@ namespace NekoGui {
status->result->coreConfig.insert("inbounds", status->inbounds);
status->result->coreConfig.insert("outbounds", status->outbounds);
// user rule
if (!status->forTest) {
DOMAIN_USER_RULE
IP_USER_RULE
}
// sing-box common rule object
auto make_rule = [&](const QStringList &list, bool isIP = false) {
@ -722,12 +717,7 @@ namespace NekoGui {
if (geosite.isEmpty()) status->result->error = +"geosite.db not found";
// final add routing rule
auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray();
if (status->forTest) routingRules = {};
if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray())
QJSONARRAY_ADD(routingRules, status->routingRules)
auto routeObj = QJsonObject{
{"rules", routingRules},
{"auto_detect_interface", true},
{
"geoip",

View File

@ -37,8 +37,10 @@ namespace NekoGui {
//
profiles = {};
groups = {};
routes = {};
profilesIdOrder = filterIntJsonFile("profiles");
groupsIdOrder = filterIntJsonFile("groups");
routesIdOrder = filterIntJsonFile("routes");
// Load Proxys
QList<int> delProfile;
for (auto id: profilesIdOrder) {
@ -75,12 +77,29 @@ namespace NekoGui {
groupsTabOrder << id;
}
}
// Load Routing profiles
for (auto id : routesIdOrder) {
auto route = LoadRouteChain(QString("routes/%1.json").arg(id));
if (route == nullptr) {
MW_show_log(QString("File routes/%1.json is corrupted, consider manually handling it").arg(id));
continue;
}
routes[id] = route;
}
// First setup
if (groups.empty()) {
auto defaultGroup = NekoGui::ProfileManager::NewGroup();
defaultGroup->name = QObject::tr("Default");
NekoGui::profileManager->AddGroup(defaultGroup);
}
if (routes.empty()) {
auto defaultRoute = NekoGui::RoutingChain::GetDefaultChain();
NekoGui::profileManager->AddRouteChain(defaultRoute);
}
//
if (dataStore->flag_reorder) {
{
@ -163,6 +182,16 @@ namespace NekoGui {
return ent;
}
std::shared_ptr<RoutingChain> ProfileManager::LoadRouteChain(const QString &jsonPath) {
std::shared_ptr<RoutingChain> routingChain;
routingChain->fn = jsonPath;
if (!routingChain->Load()) {
return nullptr;
}
return routingChain;
}
// 新建的不给 fn 和 id
std::shared_ptr<ProxyEntity> ProfileManager::NewProxyEntity(const QString &type) {
@ -373,6 +402,37 @@ namespace NekoGui {
return GetGroup(dataStore->current_group);
}
std::shared_ptr<RoutingChain> ProfileManager::NewRouteChain() {
auto route = std::make_shared<RoutingChain>();
return route;
}
int ProfileManager::NewRouteChainID() const {
if (routes.empty()) {
return 0;
}
return routesIdOrder.last() + 1;
}
bool ProfileManager::AddRouteChain(const std::shared_ptr<RoutingChain> chain) {
if (chain->id >= 0) {
return false;
}
chain->id = NewRouteChainID();
routes[chain->id] = chain;
routesIdOrder.push_back(chain->id);
chain->fn = QString("routes/%1.json").arg(chain->id);
chain->Save();
return true;
}
std::shared_ptr<RoutingChain> ProfileManager::GetRouteChain(int id) {
return routes.count(id) > 0 ? routes[id] : nullptr;
}
QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {
QList<std::shared_ptr<ProxyEntity>> ret;
for (const auto &[_, profile]: profileManager->profiles) {

View File

@ -3,6 +3,7 @@
#include "main/NekoGui.hpp"
#include "ProxyEntity.hpp"
#include "Group.hpp"
#include "RouteEntity.h"
namespace NekoGui {
class ProfileManager : private JsonStore {
@ -16,6 +17,7 @@ namespace NekoGui {
std::map<int, std::shared_ptr<ProxyEntity>> profiles;
std::map<int, std::shared_ptr<Group>> groups;
std::map<int, std::shared_ptr<RoutingChain>> routes;
ProfileManager();
@ -28,6 +30,8 @@ namespace NekoGui {
[[nodiscard]] static std::shared_ptr<Group> NewGroup();
[[nodiscard]] static std::shared_ptr<RoutingChain> NewRouteChain();
bool AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid = -1);
void DeleteProfile(int id);
@ -44,18 +48,27 @@ namespace NekoGui {
std::shared_ptr<Group> CurrentGroup();
bool AddRouteChain(std::shared_ptr<RoutingChain> chain);
std::shared_ptr<RoutingChain> GetRouteChain(int id);
private:
// sort by id
QList<int> profilesIdOrder;
QList<int> groupsIdOrder;
QList<int> routesIdOrder;
[[nodiscard]] int NewProfileID() const;
[[nodiscard]] int NewGroupID() const;
[[nodiscard]] int NewRouteChainID() const;
static std::shared_ptr<ProxyEntity> LoadProxyEntity(const QString &jsonPath);
static std::shared_ptr<Group> LoadGroup(const QString &jsonPath);
static std::shared_ptr<RoutingChain> LoadRouteChain(const QString &jsonPath);
};
extern ProfileManager *profileManager;

252
db/RouteEntity.cpp Normal file
View File

@ -0,0 +1,252 @@
#include <QJsonObject>
#include <QJsonArray>
#include "RouteEntity.h"
#include "db/Database.hpp"
namespace NekoGui {
QJsonArray get_as_array(const QList<QString>& str, bool castToNum = false) {
QJsonArray res;
for (const auto &item: str) {
if (castToNum) res.append(item.toInt());
else res.append(item);
}
return res;
}
QJsonObject RouteRule::get_rule_json() const {
QJsonObject obj;
if (ip_version != "") obj["ip_version"] = ip_version.toInt();
if (network != "") obj["network"] = network;
if (protocol != "") obj["protocol"] = protocol;
if (!domain.empty()) obj["domain"] = get_as_array(domain);
if (!domain_suffix.empty()) obj["domain_suffix"] = get_as_array(domain_suffix);
if (!domain_keyword.empty()) obj["domain_keyword"] = get_as_array(domain_keyword);
if (!domain_regex.empty()) obj["domain_regex"] = get_as_array(domain_regex);
if (!source_ip_cidr.empty()) obj["source_ip_cidr"] = get_as_array(source_ip_cidr);
if (source_ip_is_private != nullptr) obj["source_ip_is_private"] = *source_ip_is_private;
if (!ip_cidr.empty()) obj["ip_cidr"] = get_as_array(ip_cidr);
if (ip_is_private != nullptr) obj["ip_is_private"] = *ip_is_private;
if (!source_port.empty()) obj["source_port"] = get_as_array(source_port, true);
if (!source_port_range.empty()) obj["source_port_range"] = get_as_array(source_port_range);
if (!port.empty()) obj["port"] = get_as_array(port, true);
if (!port_range.empty()) obj["port_range"] = get_as_array(port_range);
if (!process_name.empty()) obj["process_name"] = get_as_array(process_name);
if (!process_path.empty()) obj["process_path"] = get_as_array(process_path);
if (!rule_set.empty()) obj["rule_set"] = get_as_array(rule_set);
if (invert) obj["invert"] = invert;
switch (outboundID) { // TODO use constants
case -2:
obj["outbound"] = "direct";
case -3:
obj["outbound"] = "block";
case -4:
obj["outbound"] = "dns_out";
default:
auto prof = NekoGui::profileManager->GetProfile(outboundID);
if (prof == nullptr) {
MW_show_log("The outbound described in the rule chain is missing, maybe your data is corrupted");
return {};
}
obj["outbound"] = prof->bean->DisplayName();
}
return obj;
}
// TODO use constant for field names
QStringList RouteRule::get_attributes() {
return {
"ip_version",
"network",
"protocol",
"domain",
"domain_suffix",
"domain_keyword",
"domain_regex",
"source_ip_cidr",
"source_ip_is_private",
"ip_cidr",
"ip_is_private",
"source_port",
"source_port_range",
"port",
"port_range",
"process_name",
"process_path",
"rule_set",
"invert",
};
}
inputType RouteRule::get_input_type(const QString& fieldName) {
if (fieldName == "invert" ||
fieldName == "source_ip_is_private" ||
fieldName == "ip_is_private") return trufalse;
if (fieldName == "ip_version" ||
fieldName == "network" ||
fieldName == "protocol") return select;
return text;
}
QStringList RouteRule::get_values_for_field(const QString& fieldName) {
if (fieldName == "ip_version") {
return {"4", "6"};
}
if (fieldName == "network") {
return {"tcp", "udp"};
}
if (fieldName == "protocol") {
return {"http", "tls", "quic", "stun", "dns", "bittorrent"};
}
return {};
}
QStringList RouteRule::get_current_value_string(const QString& fieldName) {
if (fieldName == "ip_version" && ip_version != "") {
return {ip_version};
}
if (fieldName == "network" && network != "") {
return {network};
}
if (fieldName == "protocol" && protocol != "") {
return {protocol};
}
if (fieldName == "domain") return domain;
if (fieldName == "domain_suffix") return domain_suffix;
if (fieldName == "domain_keyword") return domain_keyword;
if (fieldName == "domain_regex") return domain_regex;
if (fieldName == "source_ip_cidr") return source_ip_cidr;
if (fieldName == "ip_cidr") return ip_cidr;
if (fieldName == "source_port") return source_port;
if (fieldName == "source_port_range") return source_port_range;
if (fieldName == "port") return port;
if (fieldName == "port_range") return port_range;
if (fieldName == "process_name") return process_name;
if (fieldName == "process_path") return process_path;
if (fieldName == "rule_set") return rule_set;
return {};
}
bool* RouteRule::get_current_value_bool(const QString& fieldName) const {
if (fieldName == "source_ip_is_private") {
return source_ip_is_private;
}
if (fieldName == "ip_is_private") {
return ip_is_private;
}
if (fieldName == "invert") {
return reinterpret_cast<bool*>(invert);
}
return nullptr;
}
void RouteRule::set_field_value(const QString& fieldName, const QStringList& value) {
/*
* "ip_version",
"network",
"protocol",
"domain",
"domain_suffix",
"domain_keyword",
"domain_regex",
"source_ip_cidr",
"source_ip_is_private",
"ip_cidr",
"ip_is_private",
"source_port",
"source_port_range",
"port",
"port_range",
"process_name",
"process_path",
"rule_set",
"invert",
*/
if (fieldName == "ip_version") {
ip_version = value[0];
}
if (fieldName == "network") {
network = value[0];
}
if (fieldName == "protocol") {
protocol = value[0];
}
if (fieldName == "domain") {
domain = value;
}
if (fieldName == "domain_suffix") {
domain_suffix = value;
}
if (fieldName == "domain_keyword") {
domain_keyword = value;
}
if (fieldName == "domain_regex") {
domain_regex = value;
}
if (fieldName == "source_ip_cidr") {
source_ip_cidr = value;
}
if (fieldName == "source_ip_is_private") {
source_ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
}
if (fieldName == "ip_cidr") {
ip_cidr = value;
}
if (fieldName == "ip_is_private") {
ip_is_private = reinterpret_cast<bool*>((value[0] == "true"));
}
if (fieldName == "source_port") {
source_port = value;
}
if (fieldName == "source_port_range") {
source_port_range = value;
}
if (fieldName == "port") {
port = value;
}
if (fieldName == "port_range") {
port_range = value;
}
if (fieldName == "process_name") {
process_name = value;
}
if (fieldName == "process_path") {
process_path = value;
}
if (fieldName == "rule_set") {
rule_set = value;
}
if (fieldName == "invert") {
invert = value[0]=="true";
}
}
QJsonArray RoutingChain::get_route_rules() {
QJsonArray res;
for (const auto &item: Rules) {
auto rule_json = item->get_rule_json();
if (rule_json.empty()) {
MW_show_log("Aborted generating routing section, an error has occurred");
return {};
}
res += rule_json;
}
return res;
}
std::shared_ptr<RoutingChain> RoutingChain::GetDefaultChain() {
auto defaultChain = RoutingChain();
defaultChain.name = "Default";
auto defaultRule = RouteRule();
defaultRule.protocol = {"dns"};
defaultRule.outboundID = -4;
defaultChain.Rules << std::make_shared<RouteRule>(defaultRule);
return std::make_shared<RoutingChain>(defaultChain);
}
}

View File

@ -3,11 +3,14 @@
#include "main/NekoGui.hpp"
namespace NekoGui {
enum inputType {trufalse, select, text};
class RouteRule : public JsonStore {
public:
int ip_version = 0;
QList<QString> network;
QList<QString> protocol;
QString name = "";
QString ip_version;
QString network;
QString protocol;
QList<QString> domain;
QList<QString> domain_suffix;
QList<QString> domain_keyword;
@ -24,11 +27,15 @@ namespace NekoGui {
QList<QString> process_path;
QList<QString> rule_set;
bool invert = false;
int outboundID = -1;
int outboundID = -1; // -2 is direct -3 is block -4 is dns_out
QList<QString> check_for_errors();
QJsonObject get_rule_json();
[[nodiscard]] QJsonObject get_rule_json() const;
static QStringList get_attributes();
static inputType get_input_type(const QString& fieldName);
static QStringList get_values_for_field(const QString& fieldName);
QStringList get_current_value_string(const QString& fieldName);
[[nodiscard]] bool* get_current_value_bool(const QString& fieldName) const;
void set_field_value(const QString& fieldName, const QStringList& value);
};
class RoutingChain : public JsonStore {
@ -39,6 +46,6 @@ namespace NekoGui {
QJsonArray get_route_rules();
QJsonArray get_default_route_rules();
static std::shared_ptr<RoutingChain> GetDefaultChain();
};
} // namespace NekoGui

View File

@ -327,32 +327,9 @@ namespace NekoGui {
// preset routing
Routing::Routing(int preset) : JsonStore() {
if (preset == 1) {
direct_ip =
"geoip:cn\n"
"geoip:private";
direct_domain = "geosite:cn";
proxy_ip = "";
proxy_domain = "";
block_ip = "";
block_domain =
"geosite:category-ads-all\n"
"domain:appcenter.ms\n"
"domain:firebase.io\n"
"domain:crashlytics.com\n";
}
if (IS_NEKO_BOX) {
if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = "";
if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = "";
}
_add(new configItem("direct_ip", &this->direct_ip, itemType::string));
_add(new configItem("direct_domain", &this->direct_domain, itemType::string));
_add(new configItem("proxy_ip", &this->proxy_ip, itemType::string));
_add(new configItem("proxy_domain", &this->proxy_domain, itemType::string));
_add(new configItem("block_ip", &this->block_ip, itemType::string));
_add(new configItem("block_domain", &this->block_domain, itemType::string));
_add(new configItem("def_outbound", &this->def_outbound, itemType::string));
_add(new configItem("custom", &this->custom, itemType::string));
if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = "";
if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = "";
_add(new configItem("current_route_id", &this->current_route_id, itemType::integer));
//
_add(new configItem("remote_dns", &this->remote_dns, itemType::string));
_add(new configItem("remote_dns_strategy", &this->remote_dns_strategy, itemType::string));
@ -367,18 +344,6 @@ namespace NekoGui {
_add(new configItem("dns_final_out", &this->dns_final_out, itemType::string));
}
QString Routing::DisplayRouting() const {
return QString("[Proxy] %1\n[Proxy] %2\n[Direct] %3\n[Direct] %4\n[Block] %5\n[Block] %6\n[Default Outbound] %7\n[DNS] %8")
.arg(SplitLinesSkipSharp(proxy_domain).join(","), 10)
.arg(SplitLinesSkipSharp(proxy_ip).join(","), 10)
.arg(SplitLinesSkipSharp(direct_domain).join(","), 10)
.arg(SplitLinesSkipSharp(direct_ip).join(","), 10)
.arg(SplitLinesSkipSharp(block_domain).join(","), 10)
.arg(SplitLinesSkipSharp(block_ip).join(","), 10)
.arg(def_outbound)
.arg(use_dns_object ? "DNS Object" : "Simple DNS");
}
QStringList Routing::List() {
QDir dr(ROUTES_PREFIX);
return dr.entryList(QDir::Files);

View File

@ -4,14 +4,8 @@ namespace NekoGui {
class Routing : public JsonStore {
public:
QString direct_ip;
QString direct_domain;
QString proxy_ip;
QString proxy_domain;
QString block_ip;
QString block_domain;
int current_route_id = 0;
QString def_outbound = "proxy";
QString custom = "{\"rules\": []}";
// DNS
QString remote_dns = "https://8.8.8.8/dns-query";
@ -30,8 +24,6 @@ namespace NekoGui {
explicit Routing(int preset = 0);
[[nodiscard]] QString DisplayRouting() const;
static QStringList List();
static bool SetToActive(const QString &name);

View File

@ -1,5 +1,7 @@
#include "dialog_manage_routes.h"
#include "ui_dialog_manage_routes.h"
#include "db/Database.hpp"
//#include "ui_RouteItem.h"
#include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp"
#include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp"
@ -8,38 +10,63 @@
#include <QFile>
#include <QMessageBox>
#include <QListWidget>
#include <QLineEdit>
#define REFRESH_ACTIVE_ROUTING(name, obj) \
this->active_routing = name; \
setWindowTitle(title_base + " [" + name + "]"); \
UpdateDisplayRouting(obj, false);
QList<QString> getRouteProfiles() {
auto routeProfiles = NekoGui::profileManager->routes;
QList<QString> res;
for (const auto &item: routeProfiles) {
res << item.second->name;
}
return res;
}
int getRouteID(const QString& name) {
auto routeProfiles = NekoGui::profileManager->routes;
for (const auto &item: routeProfiles) {
if (item.second->name == name) return item.first;
}
return -1;
}
QString getRouteName(int id) {
return NekoGui::profileManager->routes.count(id) ? NekoGui::profileManager->routes[id]->name : "";
}
QList<QString> deleteItemFromList(const QList<QString>& base, const QString& target) {
QList<QString> res;
for (const auto &item: base) {
if (item == target) continue;
res << item;
}
return res;
}
void DialogManageRoutes::reloadProfileItems() {
ui->route_prof->clear();
ui->route_profiles->clear();
ui->route_prof->addItems(currentRouteProfiles);
ui->route_profiles->addItems(currentRouteProfiles);
}
DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) {
ui->setupUi(this);
title_base = windowTitle();
currentRouteProfiles = getRouteProfiles();
QStringList qsValue = {""};
QString dnsHelpDocumentUrl;
if (IS_NEKO_BOX) {
ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);
ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);
qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" ");
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0="));
dnsHelpDocumentUrl = "https://sing-box.sagernet.org/configuration/dns/";
} else {
ui->outbound_domain_strategy->addItems({"AsIs", "UseIPv4", "UseIPv6", "PreferIPv4", "PreferIPv6"});
ui->domainStrategyCombo->addItems({"AsIs", "IPIfNonMatch", "IPOnDemand"});
qsValue += QString("use_ip use_ip4 use_ip6").split(" ");
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10KfQ=="));
dnsHelpDocumentUrl = "https://www.v2fly.org/config/dns.html";
}
ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);
ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);
qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" ");
ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0="));
dnsHelpDocumentUrl = "https://sing-box.sagernet.org/configuration/dns/";
ui->direct_dns_strategy->addItems(qsValue);
ui->remote_dns_strategy->addItems(qsValue);
//
D_C_LOAD_STRING(custom_route_global)
//
connect(ui->use_dns_object, &QCheckBox::stateChanged, this, [=](int state) {
auto useDNSObject = state == Qt::Checked;
ui->simple_dns_box->setDisabled(useDNSObject);
@ -57,40 +84,28 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne
ui->dns_object->setPlainText(QJsonObject2QString(obj, false));
}
});
//
connect(ui->custom_route_edit, &QPushButton::clicked, this, [=] {
C_EDIT_JSON_ALLOW_EMPTY(custom_route)
});
connect(ui->custom_route_global_edit, &QPushButton::clicked, this, [=] {
C_EDIT_JSON_ALLOW_EMPTY(custom_route_global)
});
//
builtInSchemesMenu = new QMenu(this);
builtInSchemesMenu->addActions(this->getBuiltInSchemes());
ui->preset->setMenu(builtInSchemesMenu);
ui->sniffing_mode->setCurrentIndex(NekoGui::dataStore->routing->sniffing_mode);
ui->outbound_domain_strategy->setCurrentText(NekoGui::dataStore->routing->outbound_domain_strategy);
ui->domainStrategyCombo->setCurrentText(NekoGui::dataStore->routing->domain_strategy);
ui->use_dns_object->setChecked(NekoGui::dataStore->routing->use_dns_object);
ui->dns_object->setPlainText(NekoGui::dataStore->routing->dns_object);
ui->dns_routing->setChecked(NekoGui::dataStore->routing->dns_routing);
ui->remote_dns->setCurrentText(NekoGui::dataStore->routing->remote_dns);
ui->remote_dns_strategy->setCurrentText(NekoGui::dataStore->routing->remote_dns_strategy);
ui->direct_dns->setCurrentText(NekoGui::dataStore->routing->direct_dns);
ui->direct_dns_strategy->setCurrentText(NekoGui::dataStore->routing->direct_dns_strategy);
ui->dns_final_out->setCurrentText(NekoGui::dataStore->routing->dns_final_out);
reloadProfileItems();
ui->route_prof->setCurrentText(getRouteName(NekoGui::dataStore->routing->current_route_id));
connect(ui->delete_route, &QPushButton::clicked, this, [=]{
auto current = ui->route_profiles->currentItem()->text();
currentRouteProfiles = deleteItemFromList(currentRouteProfiles, current);
reloadProfileItems();
});
QString geoipFn = NekoGui::FindCoreAsset("geoip.dat");
QString geositeFn = NekoGui::FindCoreAsset("geosite.dat");
//
const auto sourceStringsDomain = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geositeFn);
directDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
proxyDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
blockDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this);
//
const auto sourceStringsIP = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geoipFn);
directIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
proxyIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
blockIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this);
//
ui->directTxtLayout->addWidget(directDomainTxt, 0, 0);
ui->proxyTxtLayout->addWidget(proxyDomainTxt, 0, 0);
ui->blockTxtLayout->addWidget(blockDomainTxt, 0, 0);
//
ui->directIPLayout->addWidget(directIPTxt, 0, 0);
ui->proxyIPLayout->addWidget(proxyIPTxt, 0, 0);
ui->blockIPLayout->addWidget(blockIPTxt, 0, 0);
//
REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())
ADD_ASTERISK(this)
}
@ -100,158 +115,24 @@ DialogManageRoutes::~DialogManageRoutes() {
}
void DialogManageRoutes::accept() {
D_C_SAVE_STRING(custom_route_global)
bool routeChanged = false;
if (NekoGui::dataStore->active_routing != active_routing) routeChanged = true;
SaveDisplayRouting(NekoGui::dataStore->routing.get());
NekoGui::dataStore->active_routing = active_routing;
NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing;
if (NekoGui::dataStore->routing->Save()) routeChanged = true;
//
QString info = "UpdateDataStore";
if (routeChanged) info += "RouteChanged";
MW_dialog_message(Dialog_DialogManageRoutes, info);
QDialog::accept();
}
// built in settings
QList<QAction *> DialogManageRoutes::getBuiltInSchemes() {
QList<QAction *> list;
list.append(this->schemeToAction(tr("Bypass LAN and China"), routing_cn_lan));
list.append(this->schemeToAction(tr("Global"), routing_global));
return list;
}
QAction *DialogManageRoutes::schemeToAction(const QString &name, const NekoGui::Routing &scheme) {
auto *action = new QAction(name, this);
connect(action, &QAction::triggered, [this, &scheme] { this->UpdateDisplayRouting((NekoGui::Routing *) &scheme, true); });
return action;
}
void DialogManageRoutes::UpdateDisplayRouting(NekoGui::Routing *conf, bool qv) {
//
directDomainTxt->setPlainText(conf->direct_domain);
proxyDomainTxt->setPlainText(conf->proxy_domain);
blockDomainTxt->setPlainText(conf->block_domain);
//
blockIPTxt->setPlainText(conf->block_ip);
directIPTxt->setPlainText(conf->direct_ip);
proxyIPTxt->setPlainText(conf->proxy_ip);
//
CACHE.custom_route = conf->custom;
ui->def_outbound->setCurrentText(conf->def_outbound);
//
if (qv) return;
//
ui->sniffing_mode->setCurrentIndex(conf->sniffing_mode);
ui->outbound_domain_strategy->setCurrentText(conf->outbound_domain_strategy);
ui->domainStrategyCombo->setCurrentText(conf->domain_strategy);
ui->use_dns_object->setChecked(conf->use_dns_object);
ui->dns_object->setPlainText(conf->dns_object);
ui->dns_routing->setChecked(conf->dns_routing);
ui->remote_dns->setCurrentText(conf->remote_dns);
ui->remote_dns_strategy->setCurrentText(conf->remote_dns_strategy);
ui->direct_dns->setCurrentText(conf->direct_dns);
ui->direct_dns_strategy->setCurrentText(conf->direct_dns_strategy);
ui->dns_final_out->setCurrentText(conf->dns_final_out);
}
void DialogManageRoutes::SaveDisplayRouting(NekoGui::Routing *conf) {
conf->direct_ip = directIPTxt->toPlainText();
conf->direct_domain = directDomainTxt->toPlainText();
conf->proxy_ip = proxyIPTxt->toPlainText();
conf->proxy_domain = proxyDomainTxt->toPlainText();
conf->block_ip = blockIPTxt->toPlainText();
conf->block_domain = blockDomainTxt->toPlainText();
conf->def_outbound = ui->def_outbound->currentText();
conf->custom = CACHE.custom_route;
//
conf->sniffing_mode = ui->sniffing_mode->currentIndex();
conf->domain_strategy = ui->domainStrategyCombo->currentText();
conf->outbound_domain_strategy = ui->outbound_domain_strategy->currentText();
conf->use_dns_object = ui->use_dns_object->isChecked();
conf->dns_object = ui->dns_object->toPlainText();
conf->dns_routing = ui->dns_routing->isChecked();
conf->remote_dns = ui->remote_dns->currentText();
conf->remote_dns_strategy = ui->remote_dns_strategy->currentText();
conf->direct_dns = ui->direct_dns->currentText();
conf->direct_dns_strategy = ui->direct_dns_strategy->currentText();
conf->dns_final_out = ui->dns_final_out->currentText();
}
void DialogManageRoutes::on_load_save_clicked() {
auto w = new QDialog;
auto layout = new QVBoxLayout;
w->setLayout(layout);
auto lineEdit = new QLineEdit;
layout->addWidget(lineEdit);
auto list = new QListWidget;
layout->addWidget(list);
for (const auto &name: NekoGui::Routing::List()) {
list->addItem(name);
if (currentRouteProfiles.empty()) {
MessageBoxInfo("Invalid settings", "Routing profile cannot be empty");
return;
}
connect(list, &QListWidget::currentTextChanged, lineEdit, &QLineEdit::setText);
auto bottom = new QHBoxLayout;
layout->addLayout(bottom);
auto load = new QPushButton;
load->setText(tr("Load"));
bottom->addWidget(load);
auto save = new QPushButton;
save->setText(tr("Save"));
bottom->addWidget(save);
auto remove = new QPushButton;
remove->setText(tr("Remove"));
bottom->addWidget(remove);
auto cancel = new QPushButton;
cancel->setText(tr("Cancel"));
bottom->addWidget(cancel);
connect(load, &QPushButton::clicked, w, [=] {
auto fn = lineEdit->text();
if (!fn.isEmpty()) {
auto r = std::make_unique<NekoGui::Routing>();
r->load_control_must = true;
r->fn = ROUTES_PREFIX + fn;
if (r->Load()) {
if (QMessageBox::question(nullptr, software_name, tr("Load routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) {
REFRESH_ACTIVE_ROUTING(fn, r.get()) // temp save to the window
w->accept();
}
}
}
});
connect(save, &QPushButton::clicked, w, [=] {
auto fn = lineEdit->text();
if (!fn.isEmpty()) {
auto r = std::make_unique<NekoGui::Routing>();
SaveDisplayRouting(r.get());
r->fn = ROUTES_PREFIX + fn;
if (QMessageBox::question(nullptr, software_name, tr("Save routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) {
r->Save();
REFRESH_ACTIVE_ROUTING(fn, r.get())
w->accept();
}
}
});
connect(remove, &QPushButton::clicked, w, [=] {
auto fn = lineEdit->text();
if (!fn.isEmpty() && NekoGui::Routing::List().length() > 1) {
if (QMessageBox::question(nullptr, software_name, tr("Remove routing: %1").arg(fn)) == QMessageBox::Yes) {
QFile f(ROUTES_PREFIX + fn);
f.remove();
if (NekoGui::dataStore->active_routing == fn) {
NekoGui::Routing::SetToActive(NekoGui::Routing::List().first());
REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())
}
w->accept();
}
}
});
connect(cancel, &QPushButton::clicked, w, &QDialog::accept);
connect(list, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *item) {
lineEdit->setText(item->text());
emit load->clicked();
});
w->exec();
w->deleteLater();
}
NekoGui::dataStore->routing->sniffing_mode = ui->sniffing_mode->currentIndex();
NekoGui::dataStore->routing->domain_strategy = ui->domainStrategyCombo->currentText();
NekoGui::dataStore->routing->outbound_domain_strategy = ui->outbound_domain_strategy->currentText();
NekoGui::dataStore->routing->use_dns_object = ui->use_dns_object->isChecked();
NekoGui::dataStore->routing->dns_object = ui->dns_object->toPlainText();
NekoGui::dataStore->routing->dns_routing = ui->dns_routing->isChecked();
NekoGui::dataStore->routing->remote_dns = ui->remote_dns->currentText();
NekoGui::dataStore->routing->remote_dns_strategy = ui->remote_dns_strategy->currentText();
NekoGui::dataStore->routing->direct_dns = ui->direct_dns->currentText();
NekoGui::dataStore->routing->direct_dns_strategy = ui->direct_dns_strategy->currentText();
NekoGui::dataStore->routing->dns_final_out = ui->dns_final_out->currentText();
// TODO add mine
QDialog::accept();
}

View File

@ -23,37 +23,15 @@ public:
private:
Ui::DialogManageRoutes *ui;
struct {
QString custom_route;
QString custom_route_global;
} CACHE;
QMenu *builtInSchemesMenu;
Qv2ray::ui::widgets::AutoCompleteTextEdit *directDomainTxt;
Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyDomainTxt;
Qv2ray::ui::widgets::AutoCompleteTextEdit *blockDomainTxt;
//
Qv2ray::ui::widgets::AutoCompleteTextEdit *directIPTxt;
Qv2ray::ui::widgets::AutoCompleteTextEdit *blockIPTxt;
Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyIPTxt;
//
NekoGui::Routing routing_cn_lan = NekoGui::Routing(1);
NekoGui::Routing routing_global = NekoGui::Routing(0);
//
QString title_base;
QString active_routing;
void reloadProfileItems();
QList<QString> currentRouteProfiles;
public slots:
void accept() override;
QList<QAction *> getBuiltInSchemes();
void on_new_route_clicked();
QAction *schemeToAction(const QString &name, const NekoGui::Routing &scheme);
void on_edit_route_clicked();
void UpdateDisplayRouting(NekoGui::Routing *conf, bool qv);
void SaveDisplayRouting(NekoGui::Routing *conf);
void on_load_save_clicked();
void on_delete_route_clicked();
};

View File

@ -27,47 +27,18 @@
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="3" column="1">
<widget class="QComboBox" name="outbound_domain_strategy">
<property name="editable">
<bool>false</bool>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</widget>
</item>
<item row="5" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Route sets</string>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Server Address Strategy</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QPushButton" name="load_save">
<property name="text">
<string>Mange route set</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="custom_route_global_edit">
<property name="text">
<string>Custom Route (global)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Note: Other settings are independent for each route set.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
@ -81,8 +52,15 @@ For sing-box, it sets inbound.domain_strategy</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="outbound_domain_strategy">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Sniffing Mode</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="domainStrategyCombo">
<property name="editable">
<bool>false</bool>
</property>
@ -107,40 +85,16 @@ For sing-box, it sets inbound.domain_strategy</string>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<item row="4" column="1">
<widget class="QComboBox" name="route_prof"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="route_prof_l">
<property name="text">
<string>Sniffing Mode</string>
<string>Routing Profile</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Server Address Strategy</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="domainStrategyCombo">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
@ -182,6 +136,11 @@ For more information, see the document &quot;Configuration/DNS&quot;.</string>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>8.8.8.8</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">https://8.8.8.8/dns-query</string>
@ -414,188 +373,44 @@ For more information, see the document &quot;Configuration/DNS&quot;.</string>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Simple Route</string>
<string>Route</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="gb2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<widget class="QGroupBox" name="route_profiles_box">
<property name="title">
<string>Routing Profiles</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,1,1" columnstretch="0,1,1,1">
<item row="0" column="3">
<widget class="QLabel" name="label_82">
<property name="text">
<string>Block</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<layout class="QGridLayout" name="blockTxtLayout"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_80">
<property name="text">
<string>Direct</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Domain</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="directTxtLayout"/>
</item>
<item row="1" column="2">
<layout class="QGridLayout" name="proxyIPLayout"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Proxy</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<layout class="QGridLayout" name="proxyTxtLayout"/>
</item>
<item row="1" column="3">
<layout class="QGridLayout" name="blockIPLayout"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>IP</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QGridLayout" name="directIPLayout"/>
</item>
</layout>
<widget class="QListWidget" name="route_profiles"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QWidget" name="horizontalWidget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="new_route">
<property name="text">
<string>New</string>
</property>
<property name="topMargin">
<number>0</number>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit_route">
<property name="text">
<string>Edit</string>
</property>
<property name="rightMargin">
<number>0</number>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_route">
<property name="text">
<string>Delete</string>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="preset">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Preset</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="custom_route_edit">
<property name="text">
<string>Custom Route</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="horizontalWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Default Outbound</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="def_outbound">
<item>
<property name="text">
<string notr="true">proxy</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">bypass</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">block</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
@ -628,8 +443,6 @@ For more information, see the document &quot;Configuration/DNS&quot;.</string>
<tabstop>sniffing_mode</tabstop>
<tabstop>domainStrategyCombo</tabstop>
<tabstop>outbound_domain_strategy</tabstop>
<tabstop>load_save</tabstop>
<tabstop>custom_route_global_edit</tabstop>
<tabstop>remote_dns</tabstop>
<tabstop>remote_dns_strategy</tabstop>
<tabstop>direct_dns</tabstop>
@ -640,9 +453,6 @@ For more information, see the document &quot;Configuration/DNS&quot;.</string>
<tabstop>format_dns_object</tabstop>
<tabstop>dns_document</tabstop>
<tabstop>dns_object</tabstop>
<tabstop>preset</tabstop>
<tabstop>custom_route_edit</tabstop>
<tabstop>def_outbound</tabstop>
</tabstops>
<resources/>
<connections>

View File

@ -304,7 +304,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
r.load_control_must = true;
r.fn = ROUTES_PREFIX + fn;
if (r.Load()) {
if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" + r.DisplayRouting()) == QMessageBox::Yes) {
if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" ) == QMessageBox::Yes) {
NekoGui::Routing::SetToActive(fn);
if (NekoGui::dataStore->started_id >= 0) {
neko_start(NekoGui::dataStore->started_id);
@ -1577,32 +1577,6 @@ void MainWindow::on_masterLogBrowser_customContextMenuRequested(const QPoint &po
auto item = QInputDialog::getItem(GetMessageBoxParent(), tr("Save as route"),
tr("Save \"%1\" as a routing rule?").arg(newStr),
items, select, false, &ok);
if (ok) {
auto index = items.indexOf(item);
switch (index) {
case 0:
ADD_TO_CURRENT_ROUTE(proxy_ip, newStr);
break;
case 1:
ADD_TO_CURRENT_ROUTE(direct_ip, newStr);
break;
case 2:
ADD_TO_CURRENT_ROUTE(block_ip, newStr);
break;
case 3:
ADD_TO_CURRENT_ROUTE(proxy_domain, newStr);
break;
case 4:
ADD_TO_CURRENT_ROUTE(direct_domain, newStr);
break;
case 5:
ADD_TO_CURRENT_ROUTE(block_domain, newStr);
break;
default:
break;
}
MW_dialog_message("", "UpdateDataStore,RouteChanged");
}
});
menu->addAction(action_add_route);

122
ui/widget/RouteItem.cpp Normal file
View File

@ -0,0 +1,122 @@
#include "RouteItem.h"
#include "ui_RouteItem.h"
#include "db/RouteEntity.h"
#include "db/Database.hpp"
#define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this);
int RouteItem::getIndexOf(const QString& name) const {
for (int i=0;i<chain->Rules.size();i++) {
if (chain->Rules[i]->name == name) return i;
}
return -1;
}
QString get_outbound_name(int id) {
// -2 is direct -3 is block -4 is dns_out
if (id == -2) return "direct";
if (id == -3) return "block";
if (id == -4) return "dns_out";
auto profiles = NekoGui::profileManager->profiles;
if (profiles.count(id)) return profiles[id]->bean->name;
return "INVALID OUTBOUND";
}
QStringList get_all_outbounds() {
QStringList res;
auto profiles = NekoGui::profileManager->profiles;
for (const auto &item: profiles) {
res.append(item.second->bean->name);
}
return res;
}
RouteItem::RouteItem(QWidget *parent, const std::shared_ptr<NekoGui::RoutingChain>& routeChain)
: QGroupBox(parent), ui(new Ui::RouteItem) {
ui->setupUi(this);
// make a copy
chain = routeChain;
for (const auto &item: chain->Rules) {
ui->route_items->addItem(item->name);
}
QStringList outboundOptions = {"direct", "block", "dns_out"};
outboundOptions << get_all_outbounds();
ui->rule_attr->addItems(NekoGui::RouteRule::get_attributes());
ui->rule_out->addItems(outboundOptions);
ui->rule_attr_text->hide();
ui->rule_attr_data->setTitle("");
connect(ui->rule_attr_selector, &QComboBox::currentTextChanged, this, [=](const QString& text){
if (currentIndex == -1) return;
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), {text});
});
connect(ui->rule_attr_text, &QTextEdit::textChanged, this, [=] {
if (currentIndex == -1) return;
auto currentVal = ui->rule_attr_text->toPlainText().split('\n');
chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), currentVal);
});
connect(ui->route_items, &QListWidget::itemClicked, this, [=](const QListWidgetItem *item) {
auto idx = getIndexOf(item->text());
if (idx == -1) return;
currentIndex = idx;
auto ruleItem = chain->Rules[idx];
ui->rule_out->setCurrentText(get_outbound_name(ruleItem->outboundID));
});
connect(ui->rule_attr, &QComboBox::currentTextChanged, this, [=](const QString& text){
if (currentIndex == -1) return;
ui->rule_attr_data->setTitle(text);
auto inputType = NekoGui::RouteRule::get_input_type(text);
switch (inputType) {
case NekoGui::trufalse: {
QStringList items = {"", "true", "false"};
auto currentValPtr = chain->Rules[currentIndex]->get_current_value_bool(text);
QString currentVal = currentValPtr == nullptr ? "" : *currentValPtr ? "true" : "false";
showSelectItem(items, currentVal);
break;
}
case NekoGui::select: {
auto items = NekoGui::RouteRule::get_values_for_field(text);
auto currentVal = chain->Rules[currentIndex]->get_current_value_string(text)[0];
showSelectItem(items, currentVal);
break;
}
case NekoGui::text: {
auto currentItems = chain->Rules[currentIndex]->get_current_value_string(text);
showTextEnterItem(currentItems);
break;
}
}
});
}
RouteItem::~RouteItem() {
delete ui;
}
void RouteItem::showSelectItem(const QStringList& items, const QString& currentItem) {
ui->rule_attr_text->hide();
ui->rule_attr_selector->clear();
ui->rule_attr_selector->show();
QStringList fullItems = {""};
fullItems << items;
ui->rule_attr_selector->addItems(fullItems);
ui->rule_attr_selector->setCurrentText(currentItem);
adjustSize();
}
void RouteItem::showTextEnterItem(const QStringList& items) {
ui->rule_attr_selector->hide();
ui->rule_attr_text->clear();
ui->rule_attr_text->show();
ui->rule_attr_text->setText(items.join('\n'));
adjustSize();
}

43
ui/widget/RouteItem.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <QWidget>
#include <QListWidgetItem>
#include <QGroupBox>
#include "db/RouteEntity.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class RouteItem;
}
QT_END_NAMESPACE
class RouteItem : public QGroupBox {
Q_OBJECT
public:
explicit RouteItem(QWidget *parent = nullptr, const std::shared_ptr<NekoGui::RoutingChain>& routeChain = nullptr);
~RouteItem() override;
std::shared_ptr<NekoGui::RoutingChain> chain;
signals:
void settingsChanged(const std::shared_ptr<NekoGui::RoutingChain> routeChain);
private:
Ui::RouteItem *ui;
int currentIndex = -1;
[[nodiscard]] int getIndexOf(const QString& name) const;
void showSelectItem(const QStringList& items, const QString& currentItem);
void showTextEnterItem(const QStringList& items);
private slots:
void on_ok_button_clicked();
void on_cancel_button_clicked();
void on_new_route_item_clicked();
void on_moveup_route_item_clicked();
void on_movedown_route_item_clicked();
void on_route_view_json_clicked();
};

153
ui/widget/RouteItem.ui Normal file
View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RouteItem</class>
<widget class="QGroupBox" name="RouteItem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>780</width>
<height>728</height>
</rect>
</property>
<property name="windowTitle">
<string>GroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="route_name_l">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="route_name"/>
</item>
<item>
<widget class="QPushButton" name="route_view_json">
<property name="text">
<string>View JSON</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Rules</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="route_items"/>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="new_route_item">
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveup_route_item">
<property name="text">
<string>Move Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="movedown_route_item">
<property name="text">
<string>Move Down</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Rule Attributes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="rule_attr_l">
<property name="text">
<string>Attribute</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="rule_attr"/>
</item>
<item>
<widget class="QLabel" name="rule_out_l">
<property name="text">
<string>Outbound</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="rule_out"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="rule_attr_data">
<property name="title">
<string>Name_Placeholder</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QTextEdit" name="rule_attr_text"/>
</item>
<item>
<widget class="QComboBox" name="rule_attr_selector"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="ok_button">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>