From 7c74480b0674f91f21b2add46ca4cca550da210d Mon Sep 17 00:00:00 2001 From: arm64v8a <48624112+arm64v8a@users.noreply.github.com> Date: Sun, 14 May 2023 16:37:20 +0900 Subject: [PATCH] feat: multiplex default & apply to group --- db/ConfigBuilder.cpp | 5 + fmt/V2RayStreamSettings.hpp | 3 + main/NekoRay.cpp | 33 +++++ main/NekoRay_ConfigItem.hpp | 4 + main/NekoRay_DataStore.hpp | 1 + translations/fa_IR.ts | 36 +++++ translations/zh_CN.ts | 36 +++++ ui/dialog_basic_settings.cpp | 19 ++- ui/dialog_basic_settings.ui | 238 +++++++++++++++++--------------- ui/edit/dialog_edit_profile.cpp | 110 ++++++++++++++- ui/edit/dialog_edit_profile.h | 11 ++ ui/edit/dialog_edit_profile.ui | 93 +++++++++---- ui/mainwindow.cpp | 2 + ui/mainwindow.ui | 8 +- ui/widget/FloatCheckBox.h | 16 +++ 15 files changed, 463 insertions(+), 152 deletions(-) create mode 100644 ui/widget/FloatCheckBox.h diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 9913e4d..ea168b7 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -620,6 +620,11 @@ namespace NekoRay { needMux = false; } } + if (stream->multiplex_status == 0) { + if (!dataStore->mux_default_on) needMux = false; + } else if (stream->multiplex_status == 2) { + needMux = false; + } } if (ent->type == "shadowsocks") { diff --git a/fmt/V2RayStreamSettings.hpp b/fmt/V2RayStreamSettings.hpp index 27495bb..9d9c0b9 100644 --- a/fmt/V2RayStreamSettings.hpp +++ b/fmt/V2RayStreamSettings.hpp @@ -25,6 +25,8 @@ namespace NekoRay::fmt { // reality QString reality_pbk = ""; QString reality_sid = ""; + // multiplex + int multiplex_status = 0; V2rayStreamSettings() : JsonStore() { _add(new configItem("net", &network, itemType::string)); @@ -42,6 +44,7 @@ namespace NekoRay::fmt { _add(new configItem("utls", &utlsFingerprint, itemType::string)); _add(new configItem("pbk", &reality_pbk, itemType::string)); _add(new configItem("sid", &reality_sid, itemType::string)); + _add(new configItem("mux_s", &multiplex_status, itemType::integer)); } QJsonObject BuildStreamSettingsV2Ray(); diff --git a/main/NekoRay.cpp b/main/NekoRay.cpp index 9c14e41..df9a00b 100644 --- a/main/NekoRay.cpp +++ b/main/NekoRay.cpp @@ -34,6 +34,7 @@ namespace NekoRay { _add(new configItem("log_level", &log_level, itemType::string)); _add(new configItem("mux_protocol", &mux_protocol, itemType::string)); _add(new configItem("mux_concurrency", &mux_concurrency, itemType::integer)); + _add(new configItem("mux_default_on", &mux_default_on, itemType::boolean)); _add(new configItem("traffic_loop_interval", &traffic_loop_interval, itemType::integer)); _add(new configItem("test_concurrent", &test_concurrent, itemType::integer)); _add(new configItem("theme", &theme, itemType::string)); @@ -201,6 +202,13 @@ namespace NekoRay { _map.insert(item->name, QSharedPointer(item)); } + QString JsonStore::_name(void *p) { + for (const auto &_item: _map) { + if (_item->ptr == p) return _item->name; + } + return {}; + } + QSharedPointer JsonStore::_get(const QString &name) { // 直接 [] 会设置一个 nullptr ,所以先判断是否存在 if (_map.contains(name)) { @@ -209,6 +217,31 @@ namespace NekoRay { return nullptr; } + void JsonStore::_setValue(const QString &name, void *p) { + auto item = _get(name); + if (item == nullptr) return; + + switch (item->type) { + case NekoRay::itemType::string: + *(QString *) item->ptr = *(QString *) p; + break; + case NekoRay::itemType::boolean: + *(bool *) item->ptr = *(bool *) p; + break; + case NekoRay::itemType::integer: + *(int *) item->ptr = *(int *) p; + break; + case NekoRay::itemType::integer64: + *(long long *) item->ptr = *(long long *) p; + break; + // others... + case stringList: + case integerList: + case jsonStore: + break; + } + } + QJsonObject JsonStore::ToJson() { QJsonObject object; for (const auto &_item: _map) { diff --git a/main/NekoRay_ConfigItem.hpp b/main/NekoRay_ConfigItem.hpp index d3c437c..354e1d9 100644 --- a/main/NekoRay_ConfigItem.hpp +++ b/main/NekoRay_ConfigItem.hpp @@ -46,8 +46,12 @@ namespace NekoRay { void _add(configItem *item); + QString _name(void *p); + QSharedPointer _get(const QString &name); + void _setValue(const QString &name, void *p); + QJsonObject ToJson(); QByteArray ToJsonBytes(); diff --git a/main/NekoRay_DataStore.hpp b/main/NekoRay_DataStore.hpp index cc493b1..101b885 100644 --- a/main/NekoRay_DataStore.hpp +++ b/main/NekoRay_DataStore.hpp @@ -99,6 +99,7 @@ namespace NekoRay { int current_group = 0; // group id QString mux_protocol = ""; int mux_concurrency = 8; + bool mux_default_on = false; QString theme = "0"; QString v2ray_asset_dir = ""; int language = 0; diff --git a/translations/fa_IR.ts b/translations/fa_IR.ts index e5fc85b..3059713 100644 --- a/translations/fa_IR.ts +++ b/translations/fa_IR.ts @@ -235,6 +235,14 @@ For NekoBox, this rewrites the underlying(localhost) DNS in VPN mode, normal mod If you VPN mode is not working, try to change this option. + + Default On + + + + Multiplex (mux) + + DialogEditGroup @@ -409,6 +417,30 @@ For NekoBox, this rewrites the underlying(localhost) DNS in VPN mode, normal mod Custom Config Settings + + Apply settings to this group + + + + Multiplex + + + + Keep Default + + + + On + + + + Off + خاموش + + + Confirm + + DialogFirstSetup @@ -1418,6 +1450,10 @@ End: %2 Please run NekoBox as admin + + Restart Proxy + + ProxyItem diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index f67d1c4..12d7bb9 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -235,6 +235,14 @@ For NekoBox, this rewrites the underlying(localhost) DNS in VPN mode, normal mod If you VPN mode is not working, try to change this option. 如果您的VPN模式有问题,请尝试更改此选项。 + + Default On + 默认开启 + + + Multiplex (mux) + 多路复用 Mux + DialogEditGroup @@ -409,6 +417,30 @@ For NekoBox, this rewrites the underlying(localhost) DNS in VPN mode, normal mod Custom Config Settings 自定义配置 JSON 设置 + + Apply settings to this group + 将设置应用于该组 + + + Multiplex + 多路复用 + + + Keep Default + 保持默认 + + + On + 开启 + + + Off + 关闭 + + + Confirm + 确认 + DialogFirstSetup @@ -1425,6 +1457,10 @@ Split by line. Please run NekoBox as admin 请以管理员权限运行 NekoBox + + Restart Proxy + 重启代理 + ProxyItem diff --git a/ui/dialog_basic_settings.cpp b/ui/dialog_basic_settings.cpp index 4391e7d..cd0cab7 100644 --- a/ui/dialog_basic_settings.cpp +++ b/ui/dialog_basic_settings.cpp @@ -60,8 +60,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) if (IS_NEKO_BOX) { ui->groupBox_http->hide(); - ui->inbound_socks_port_l->setText(ui->inbound_socks_port_l->text().replace("Socks", "Mixed")); - ui->hlayout_l2->addWidget(ui->groupbox_custom_inbound); + ui->inbound_socks_port_l->setText(ui->inbound_socks_port_l->text().replace("Socks", "Mixed (SOCKS+HTTP)")); ui->log_level->addItems(QString("trace debug info warn error fatal panic").split(" ")); ui->mux_protocol->addItems({"", "h2mux", "smux", "yamux"}); } else { @@ -76,8 +75,6 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) CACHE.custom_inbound = NekoRay::dataStore->custom_inbound; D_LOAD_INT(inbound_socks_port) D_LOAD_INT_ENABLE(inbound_http_port, http_enable) - D_LOAD_INT(mux_concurrency) - D_LOAD_COMBO_STRING(mux_protocol) D_LOAD_INT(test_concurrent) D_LOAD_STRING(test_url) @@ -176,7 +173,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) if (!CACHE.extraCore.contains("naive")) CACHE.extraCore.insert("naive", ""); if (!CACHE.extraCore.contains("hysteria")) CACHE.extraCore.insert("hysteria", ""); // - auto extra_core_layout = ui->extra_core_box->layout(); + auto extra_core_layout = ui->extra_core_box_scrollAreaWidgetContents->layout(); for (const auto &s: CACHE.extraCore.keys()) { extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s)); } @@ -246,6 +243,11 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) connect(ui->switch_core_v2ray, &QRadioButton::clicked, this, switch_core_on_click); connect(ui->switch_core_sing_box, &QRadioButton::clicked, this, switch_core_on_click); + // Mux + D_LOAD_INT(mux_concurrency) + D_LOAD_COMBO_STRING(mux_protocol) + D_LOAD_BOOL(mux_default_on) + // Security ui->utlsFingerprint->addItems(IS_NEKO_BOX ? Preset::SingBox::UtlsFingerPrint : Preset::V2Ray::UtlsFingerPrint); @@ -267,8 +269,6 @@ void DialogBasicSettings::accept() { NekoRay::dataStore->custom_inbound = CACHE.custom_inbound; D_SAVE_INT(inbound_socks_port) D_SAVE_INT_ENABLE(inbound_http_port, http_enable) - D_SAVE_INT(mux_concurrency) - D_SAVE_COMBO_STRING(mux_protocol) D_SAVE_INT(test_concurrent) D_SAVE_STRING(test_url) @@ -304,6 +304,11 @@ void DialogBasicSettings::accept() { NekoRay::dataStore->v2ray_asset_dir = ui->core_v2ray_asset->text(); NekoRay::dataStore->extraCore->core_map = QJsonObject2QString(CACHE.extraCore, true); + // Mux + D_SAVE_INT(mux_concurrency) + D_SAVE_COMBO_STRING(mux_protocol) + D_SAVE_BOOL(mux_default_on) + // Security D_SAVE_BOOL(skip_cert) diff --git a/ui/dialog_basic_settings.ui b/ui/dialog_basic_settings.ui index 15a8adf..a3160e9 100644 --- a/ui/dialog_basic_settings.ui +++ b/ui/dialog_basic_settings.ui @@ -6,7 +6,7 @@ 0 0 - 500 + 600 400 @@ -20,6 +20,16 @@ Basic Settings + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -120,6 +130,26 @@ + + + + + + + + + Loglevel + + + + + + + + + + + @@ -146,52 +176,6 @@ - - - - - - - - - Loglevel - - - - - - - - - - - - - - - - Mux - - - - - - - - - - concurrency - - - - - - - - - - - @@ -253,8 +237,8 @@ - - + + @@ -586,72 +570,112 @@ - - - - Qt::Vertical - - - - - - - Core Options - - - - - - Extra Core + + + + 0 + 0 + - - - - - - 0 - 0 - + + + + + Multiplex (mux) + + + + + + + + + + concurrency + + + + + + + + + + Default On - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Add - - - - - - - Delete - - - - + + + + Core Options + + + + + + + + Extra Core + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 568 + 297 + + + + + + + + 0 + 0 + + + + + + + Add + + + + + + + Delete + + + + + + + + + + @@ -734,16 +758,6 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - diff --git a/ui/edit/dialog_edit_profile.cpp b/ui/edit/dialog_edit_profile.cpp index 7a343a9..0a33ab4 100644 --- a/ui/edit/dialog_edit_profile.cpp +++ b/ui/edit/dialog_edit_profile.cpp @@ -132,6 +132,8 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, connect(ui->type, QOverload::of(&QComboBox::currentIndexChanged), this, [=](int index) { typeSelected(ui->type->itemData(index).toString()); }); + + ui->apply_to_group->hide(); } else { this->ent = NekoRay::profileManager->GetProfile(profileOrGroupId); if (this->ent == nullptr) return; @@ -226,6 +228,7 @@ void DialogEditProfile::typeSelected(const QString &newType) { ui->ws_early_data_length->setText(Int2String(stream->ws_early_data_length)); ui->reality_pbk->setText(stream->reality_pbk); ui->reality_sid->setText(stream->reality_sid); + ui->multiplex->setCurrentIndex(stream->multiplex_status); CACHE.certificate = stream->certificate; } else { ui->right_all_w->setVisible(false); @@ -299,6 +302,13 @@ void DialogEditProfile::typeSelected(const QString &newType) { ui->security->setVisible(false); ui->security_l->setVisible(false); } + if (type == "vmess" || type == "vless" || type == "trojan" || type == "shadowsocks") { + ui->multiplex->setVisible(true); + ui->multiplex_l->setVisible(true); + } else { + ui->multiplex->setVisible(false); + ui->multiplex_l->setVisible(false); + } // 设置 是否可见 int streamBoxVisible = 0; for (auto label: ui->stream_box->findChildren()) { @@ -322,7 +332,7 @@ void DialogEditProfile::typeSelected(const QString &newType) { } } -void DialogEditProfile::accept() { +bool DialogEditProfile::onEnd() { // 左边 ent->bean->name = ui->name->text(); ent->bean->serverAddress = ui->address->text(); @@ -330,7 +340,7 @@ void DialogEditProfile::accept() { // bean if (!innerEditor->onEnd()) { - return; + return false; } // 右边 stream @@ -348,15 +358,25 @@ void DialogEditProfile::accept() { stream->header_type = ui->header_type->currentText(); stream->ws_early_data_name = ui->ws_early_data_name->text(); stream->ws_early_data_length = ui->ws_early_data_length->text().toInt(); - stream->certificate = CACHE.certificate; stream->reality_pbk = ui->reality_pbk->text(); stream->reality_sid = ui->reality_sid->text(); + stream->multiplex_status = ui->multiplex->currentIndex(); + stream->certificate = CACHE.certificate; } // cached custom ent->bean->custom_outbound = CACHE.custom_outbound; ent->bean->custom_config = CACHE.custom_config; + return true; +} + +void DialogEditProfile::accept() { + // save to ent + if (!onEnd()) { + return; + } + // finish QStringList msg = {"accept"}; @@ -421,3 +441,87 @@ void DialogEditProfile::on_certificate_edit_clicked() { editor_cache_updated_impl(); } } + +void DialogEditProfile::on_apply_to_group_clicked() { + if (apply_to_group_ui.empty()) { + apply_to_group_ui[ui->multiplex] = new FloatCheckBox(ui->multiplex, this); + apply_to_group_ui[ui->sni] = new FloatCheckBox(ui->sni, this); + apply_to_group_ui[ui->alpn] = new FloatCheckBox(ui->alpn, this); + apply_to_group_ui[ui->host] = new FloatCheckBox(ui->host, this); + apply_to_group_ui[ui->path] = new FloatCheckBox(ui->path, this); + apply_to_group_ui[ui->utlsFingerprint] = new FloatCheckBox(ui->utlsFingerprint, this); + apply_to_group_ui[ui->insecure] = new FloatCheckBox(ui->insecure, this); + apply_to_group_ui[ui->certificate_edit] = new FloatCheckBox(ui->certificate_edit, this); + apply_to_group_ui[ui->custom_config_edit] = new FloatCheckBox(ui->custom_config_edit, this); + apply_to_group_ui[ui->custom_outbound_edit] = new FloatCheckBox(ui->custom_outbound_edit, this); + ui->apply_to_group->setText(tr("Confirm")); + } else { + auto group = NekoRay::profileManager->GetGroup(ent->gid); + if (group == nullptr) { + MessageBoxWarning("failed", "unknown group"); + return; + } + // save this + if (onEnd()) { + ent->Save(); + } else { + MessageBoxWarning("failed", "failed to save"); + return; + } + // copy keys + for (const auto &pair: apply_to_group_ui) { + if (pair.second->isChecked()) { + do_apply_to_group(group, pair.first); + } + delete pair.second; + } + apply_to_group_ui.clear(); + ui->apply_to_group->setText(tr("Apply settings to this group")); + } +} + +void DialogEditProfile::do_apply_to_group(const QSharedPointer &group, QWidget *key) { + auto stream = GetStreamSettings(ent->bean.data()); + + auto copyStream = [=](void *p) { + for (const auto &profile: group->Profiles()) { + auto newStream = GetStreamSettings(profile->bean.data()); + if (newStream == nullptr) continue; + if (stream == newStream) continue; + newStream->_setValue(stream->_name(p), p); + // qDebug() << newStream->ToJsonBytes(); + profile->Save(); + } + }; + + auto copyBean = [=](void *p) { + for (const auto &profile: group->Profiles()) { + if (profile == ent) continue; + profile->bean->_setValue(ent->bean->_name(p), p); + // qDebug() << profile->bean->ToJsonBytes(); + profile->Save(); + } + }; + + if (key == ui->multiplex) { + copyStream(&stream->multiplex_status); + } else if (key == ui->sni) { + copyStream(&stream->sni); + } else if (key == ui->alpn) { + copyStream(&stream->alpn); + } else if (key == ui->host) { + copyStream(&stream->host); + } else if (key == ui->path) { + copyStream(&stream->path); + } else if (key == ui->utlsFingerprint) { + copyStream(&stream->utlsFingerprint); + } else if (key == ui->insecure) { + copyStream(&stream->allow_insecure); + } else if (key == ui->certificate_edit) { + copyStream(&stream->certificate); + } else if (key == ui->custom_config_edit) { + copyBean(&ent->bean->custom_config); + } else if (key == ui->custom_outbound_edit) { + copyBean(&ent->bean->custom_outbound); + } +} diff --git a/ui/edit/dialog_edit_profile.h b/ui/edit/dialog_edit_profile.h index 815c5e5..6095e14 100644 --- a/ui/edit/dialog_edit_profile.h +++ b/ui/edit/dialog_edit_profile.h @@ -5,6 +5,8 @@ #include "db/Database.hpp" #include "profile_editor.h" +#include "ui/widget/FloatCheckBox.h" + namespace Ui { class DialogEditProfile; } @@ -29,8 +31,13 @@ private slots: void on_certificate_edit_clicked(); + void on_apply_to_group_clicked(); + private: Ui::DialogEditProfile *ui; + + std::map apply_to_group_ui; + QWidget *innerWidget{}; ProfileEditor *innerEditor{}; @@ -49,7 +56,11 @@ private: void typeSelected(const QString &newType); + bool onEnd(); + void editor_cache_updated_impl(); + + void do_apply_to_group(const QSharedPointer &group, QWidget *key); }; #endif // DIALOG_EDIT_PROFILE_H diff --git a/ui/edit/dialog_edit_profile.ui b/ui/edit/dialog_edit_profile.ui index 48d3086..a75ebfc 100644 --- a/ui/edit/dialog_edit_profile.ui +++ b/ui/edit/dialog_edit_profile.ui @@ -157,6 +157,13 @@ + + + + Apply settings to this group + + + @@ -211,30 +218,6 @@ - - - - - - - - - - tls - - - - - - - - The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. - - - Network - - - @@ -275,6 +258,49 @@ + + + + + + + + + + packet + + + + + xudp + + + + + + + + The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. + + + Network + + + + + + + + + + + + + tls + + + + @@ -295,21 +321,28 @@ - - + + + + Multiplex + + + + + - + Keep Default - packet + On - xudp + Off @@ -588,9 +621,11 @@ security (QUIC) port custom_outbound_edit custom_config_edit + apply_to_group network security packet_encoding + multiplex header_type path host diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index f02208b..22d6458 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -197,6 +197,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + ui->proxyListTable->verticalHeader()->setDefaultSectionSize(24); // search box ui->search->setVisible(false); @@ -258,6 +259,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi connect(ui->menu_open_config_folder, &QAction::triggered, this, [=] { QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::currentPath())); }); ui->menu_program_preference->addActions(ui->menu_preferences->actions()); connect(ui->menu_add_from_clipboard2, &QAction::triggered, ui->menu_add_from_clipboard, &QAction::trigger); + connect(ui->actionRestart_Proxy, &QAction::triggered, this, [=] { if (NekoRay::dataStore->started_id>=0) neko_start(NekoRay::dataStore->started_id); }); connect(ui->actionRestart_Program, &QAction::triggered, this, [=] { MW_dialog_message("", "RestartProgram"); }); connect(ui->actionShow_window, &QAction::triggered, this, [=] { tray->activated(QSystemTrayIcon::ActivationReason::Trigger); }); // diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index aba7363..6196df7 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -266,7 +266,7 @@ false - 26 + 30 @@ -496,6 +496,7 @@ + @@ -885,6 +886,11 @@ false + + + Restart Proxy + + diff --git a/ui/widget/FloatCheckBox.h b/ui/widget/FloatCheckBox.h new file mode 100644 index 0000000..90d48df --- /dev/null +++ b/ui/widget/FloatCheckBox.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class FloatCheckBox : public QCheckBox { +public: + explicit FloatCheckBox(QWidget *parent, QWidget *window) : QCheckBox(window) { + setFixedSize(24, 24); + auto pos = parent->rect().topRight(); + pos = parent->mapTo(window, pos); + pos.setX(pos.x() - 48); // ? + move(pos); + raise(); + if (parent->isVisible()) show(); + }; +};