nekoray_Mahdi-zarei/ui/dialog_basic_settings.cpp

411 lines
16 KiB
C++

#include "dialog_basic_settings.h"
#include "ui_dialog_basic_settings.h"
#include "3rdparty/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/NekoGui.hpp"
#include "main/HTTPRequestHelper.hpp"
#include <QStyleFactory>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QTimer>
class ExtraCoreWidget : public QWidget {
public:
QString coreName;
QLabel *label_name;
MyLineEdit *lineEdit_path;
QPushButton *pushButton_pick;
explicit ExtraCoreWidget(QJsonObject *extraCore, const QString &coreName_,
QWidget *parent = nullptr)
: QWidget(parent) {
coreName = coreName_;
label_name = new QLabel;
label_name->setText(coreName);
lineEdit_path = new MyLineEdit;
lineEdit_path->setText(extraCore->value(coreName).toString());
pushButton_pick = new QPushButton;
pushButton_pick->setText(QObject::tr("Select"));
auto layout = new QHBoxLayout;
layout->addWidget(label_name);
layout->addWidget(lineEdit_path);
layout->addWidget(pushButton_pick);
setLayout(layout);
setContentsMargins(0, 0, 0, 0);
//
connect(pushButton_pick, &QPushButton::clicked, this, [=] {
auto fn = QFileDialog::getOpenFileName(this, QObject::tr("Select"), QDir::currentPath(),
"", nullptr, QFileDialog::Option::ReadOnly);
if (!fn.isEmpty()) {
lineEdit_path->setText(fn);
}
});
connect(lineEdit_path, &QLineEdit::textChanged, this, [=](const QString &newTxt) {
extraCore->insert(coreName, newTxt);
});
}
};
DialogBasicSettings::DialogBasicSettings(QWidget *parent)
: QDialog(parent), ui(new Ui::DialogBasicSettings) {
ui->setupUi(this);
ADD_ASTERISK(this);
// Common
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"});
ui->disable_stats->setChecked(NekoGui::dataStore->disable_traffic_stats);
D_LOAD_STRING(inbound_address)
D_LOAD_COMBO_STRING(log_level)
CACHE.custom_inbound = NekoGui::dataStore->custom_inbound;
D_LOAD_INT(inbound_socks_port)
D_LOAD_INT(test_concurrent)
D_LOAD_STRING(test_latency_url)
connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] {
C_EDIT_JSON_ALLOW_EMPTY(custom_inbound)
});
// Style
ui->connection_statistics_box->setDisabled(true);
//
D_LOAD_BOOL(start_minimal)
D_LOAD_INT(max_log_line)
//
if (NekoGui::dataStore->traffic_loop_interval == 500) {
ui->rfsh_r->setCurrentIndex(0);
} else if (NekoGui::dataStore->traffic_loop_interval == 1000) {
ui->rfsh_r->setCurrentIndex(1);
} else if (NekoGui::dataStore->traffic_loop_interval == 2000) {
ui->rfsh_r->setCurrentIndex(2);
} else if (NekoGui::dataStore->traffic_loop_interval == 3000) {
ui->rfsh_r->setCurrentIndex(3);
} else if (NekoGui::dataStore->traffic_loop_interval == 5000) {
ui->rfsh_r->setCurrentIndex(4);
} else {
ui->rfsh_r->setCurrentIndex(5);
}
//
ui->language->setCurrentIndex(NekoGui::dataStore->language);
connect(ui->language, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
CACHE.needRestart = true;
});
//
ui->theme->addItems(QStyleFactory::keys());
ui->theme->addItem("QDarkStyle");
//
bool ok;
auto themeId = NekoGui::dataStore->theme.toInt(&ok);
if (ok) {
ui->theme->setCurrentIndex(themeId);
} else {
ui->theme->setCurrentText(NekoGui::dataStore->theme);
}
//
connect(ui->theme, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
themeManager->ApplyTheme(ui->theme->currentText());
NekoGui::dataStore->theme = ui->theme->currentText();
NekoGui::dataStore->Save();
});
// Subscription
ui->user_agent->setText(NekoGui::dataStore->user_agent);
ui->user_agent->setPlaceholderText(NekoGui::dataStore->GetUserAgent(true));
D_LOAD_BOOL(sub_use_proxy)
D_LOAD_BOOL(sub_clear)
D_LOAD_BOOL(sub_insecure)
D_LOAD_INT_ENABLE(sub_auto_update, sub_auto_update_enable)
// Core
ui->groupBox_core->setTitle(software_core_name);
// Assets
ui->geoip_url->setText(NekoGui::dataStore->geoip_download_url);
ui->geosite_url->setText(NekoGui::dataStore->geosite_download_url);
connect(ui->geoip_auto_btn, &QPushButton::clicked, this, [=](){
bool success;
auto resp = NetworkRequestHelper::GetLatestDownloadURL("https://api.github.com/repos/SagerNet/sing-geoip/releases/latest", "geoip.db", &success);
if (!success) {
runOnUiThread([=](){
MessageBoxWarning("Error", resp);
});
return;
}
ui->geoip_url->setText(resp);
});
connect(ui->geosite_auto_btn, &QPushButton::clicked, this, [=](){
bool success;
auto resp = NetworkRequestHelper::GetLatestDownloadURL("https://api.github.com/repos/SagerNet/sing-geosite/releases/latest", "geosite.db", &success);
if (!success) {
runOnUiThread([=](){
MessageBoxWarning("Error", resp);
});
return;
}
ui->geosite_url->setText(resp);
});
connect(ui->download_geo_btn, &QPushButton::clicked, this, [=]() {
MW_dialog_message(Dialog_DialogBasicSettings, "DownloadAssets;"+ui->geoip_url->text()+";"+ui->geosite_url->text());
});
connect(ui->remove_srs_btn, &QPushButton::clicked, this, [=](){
auto rsDir = QDir(RULE_SETS_DIR);
auto entries = rsDir.entryList(QDir::Files);
for (const auto &item: entries) {
if (!QFile(RULE_SETS_DIR + "/" + item).remove()) {
MW_show_log("Failed to remove " + item + ", stop the core then try again");
}
}
MW_show_log("Removed all rule-set files");
});
//
CACHE.extraCore = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map);
if (!CACHE.extraCore.contains("naive")) CACHE.extraCore.insert("naive", "");
if (!CACHE.extraCore.contains("hysteria")) CACHE.extraCore.insert("hysteria", "");
if (!CACHE.extraCore.contains("hysteria2")) CACHE.extraCore.insert("hysteria2", "");
if (!CACHE.extraCore.contains("tuic")) CACHE.extraCore.insert("tuic", "");
//
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));
}
//
connect(ui->extra_core_add, &QPushButton::clicked, this, [=] {
bool ok;
auto s = QInputDialog::getText(nullptr, tr("Add"),
tr("Please input the core name."),
QLineEdit::Normal, "", &ok)
.trimmed();
if (s.isEmpty() || !ok) return;
if (CACHE.extraCore.contains(s)) return;
extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s));
CACHE.extraCore.insert(s, "");
});
connect(ui->extra_core_del, &QPushButton::clicked, this, [=] {
bool ok;
auto s = QInputDialog::getItem(nullptr, tr("Delete"),
tr("Please select the core name."),
CACHE.extraCore.keys(), 0, false, &ok);
if (s.isEmpty() || !ok) return;
for (int i = 0; i < extra_core_layout->count(); i++) {
auto item = extra_core_layout->itemAt(i);
auto ecw = dynamic_cast<ExtraCoreWidget *>(item->widget());
if (ecw != nullptr && ecw->coreName == s) {
ecw->deleteLater();
CACHE.extraCore.remove(s);
return;
}
}
});
// Mux
D_LOAD_INT(mux_concurrency)
D_LOAD_COMBO_STRING(mux_protocol)
D_LOAD_BOOL(mux_padding)
D_LOAD_BOOL(mux_default_on)
// NTP
ui->ntp_enable->setChecked(NekoGui::dataStore->enable_ntp);
ui->ntp_server->setEnabled(NekoGui::dataStore->enable_ntp);
ui->ntp_port->setEnabled(NekoGui::dataStore->enable_ntp);
ui->ntp_interval->setEnabled(NekoGui::dataStore->enable_ntp);
ui->ntp_server->setText(NekoGui::dataStore->ntp_server_address);
ui->ntp_port->setText(Int2String(NekoGui::dataStore->ntp_server_port));
ui->ntp_interval->setCurrentText(NekoGui::dataStore->ntp_interval);
connect(ui->ntp_enable, &QCheckBox::stateChanged, this, [=](const bool &state) {
ui->ntp_server->setEnabled(state);
ui->ntp_port->setEnabled(state);
ui->ntp_interval->setEnabled(state);
});
// Security
ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint);
D_LOAD_BOOL(skip_cert)
ui->utlsFingerprint->setCurrentText(NekoGui::dataStore->utlsFingerprint);
}
DialogBasicSettings::~DialogBasicSettings() {
delete ui;
}
void DialogBasicSettings::accept() {
// Common
D_SAVE_STRING(inbound_address)
D_SAVE_COMBO_STRING(log_level)
NekoGui::dataStore->custom_inbound = CACHE.custom_inbound;
D_SAVE_INT(inbound_socks_port)
D_SAVE_INT(test_concurrent)
D_SAVE_STRING(test_latency_url)
// Style
NekoGui::dataStore->language = ui->language->currentIndex();
D_SAVE_BOOL(start_minimal)
D_SAVE_INT(max_log_line)
if (NekoGui::dataStore->max_log_line <= 0) {
NekoGui::dataStore->max_log_line = 200;
}
if (ui->rfsh_r->currentIndex() == 0) {
NekoGui::dataStore->traffic_loop_interval = 500;
} else if (ui->rfsh_r->currentIndex() == 1) {
NekoGui::dataStore->traffic_loop_interval = 1000;
} else if (ui->rfsh_r->currentIndex() == 2) {
NekoGui::dataStore->traffic_loop_interval = 2000;
} else if (ui->rfsh_r->currentIndex() == 3) {
NekoGui::dataStore->traffic_loop_interval = 3000;
} else if (ui->rfsh_r->currentIndex() == 4) {
NekoGui::dataStore->traffic_loop_interval = 5000;
} else {
NekoGui::dataStore->traffic_loop_interval = 0;
}
// Subscription
if (ui->sub_auto_update_enable->isChecked()) {
TM_auto_update_subsctiption_Reset_Minute(ui->sub_auto_update->text().toInt());
} else {
TM_auto_update_subsctiption_Reset_Minute(0);
}
NekoGui::dataStore->user_agent = ui->user_agent->text();
D_SAVE_BOOL(sub_use_proxy)
D_SAVE_BOOL(sub_clear)
D_SAVE_BOOL(sub_insecure)
D_SAVE_INT_ENABLE(sub_auto_update, sub_auto_update_enable)
// Core
NekoGui::dataStore->extraCore->core_map = QJsonObject2QString(CACHE.extraCore, true);
NekoGui::dataStore->disable_traffic_stats = ui->disable_stats->isChecked();
// Assets
NekoGui::dataStore->geoip_download_url = ui->geoip_url->text();
NekoGui::dataStore->geosite_download_url = ui->geosite_url->text();
// Mux
D_SAVE_INT(mux_concurrency)
D_SAVE_COMBO_STRING(mux_protocol)
D_SAVE_BOOL(mux_padding)
D_SAVE_BOOL(mux_default_on)
// NTP
NekoGui::dataStore->enable_ntp = ui->ntp_enable->isChecked();
NekoGui::dataStore->ntp_server_address = ui->ntp_server->text();
NekoGui::dataStore->ntp_server_port = ui->ntp_port->text().toInt();
NekoGui::dataStore->ntp_interval = ui->ntp_interval->currentText();
// Security
D_SAVE_BOOL(skip_cert)
NekoGui::dataStore->utlsFingerprint = ui->utlsFingerprint->currentText();
QStringList str{"UpdateDataStore"};
if (CACHE.needRestart) str << "NeedRestart";
MW_dialog_message(Dialog_DialogBasicSettings, str.join(","));
QDialog::accept();
}
void DialogBasicSettings::on_set_custom_icon_clicked() {
auto title = ui->set_custom_icon->text();
QString user_icon_path = "./" + software_name.toLower() + ".png";
auto c = QMessageBox::question(this, title, tr("Please select a PNG file."),
tr("Select"), tr("Reset"), tr("Cancel"), 2, 2);
if (c == 0) {
auto fn = QFileDialog::getOpenFileName(this, QObject::tr("Select"), QDir::currentPath(),
"*.png", nullptr, QFileDialog::Option::ReadOnly);
QImage img(fn);
if (img.isNull() || img.height() != img.width()) {
MessageBoxWarning(title, tr("Please select a valid square image."));
return;
}
QFile::remove(user_icon_path);
QFile::copy(fn, user_icon_path);
} else if (c == 1) {
QFile::remove(user_icon_path);
} else {
return;
}
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateIcon");
}
void DialogBasicSettings::on_core_settings_clicked() {
auto w = new QDialog(this);
w->setWindowTitle(software_core_name + " Core Options");
auto layout = new QGridLayout;
w->setLayout(layout);
//
auto line = -1;
QCheckBox *core_box_enable_clash_api;
MyLineEdit *core_box_clash_api;
MyLineEdit *core_box_clash_api_secret;
MyLineEdit *core_box_underlying_dns;
MyLineEdit *core_box_clash_listen_addr;
//
auto core_box_underlying_dns_l = new QLabel(tr("Override underlying DNS"));
core_box_underlying_dns_l->setToolTip(tr(
"It is recommended to leave it blank, but it sometimes does not work, at this time you can set this option.\n"
"For NekoRay, this rewrites the underlying(localhost) DNS in Tun Mode.\n"
"For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mode, and also URL Test."));
core_box_underlying_dns = new MyLineEdit;
core_box_underlying_dns->setText(NekoGui::dataStore->core_box_underlying_dns);
core_box_underlying_dns->setMinimumWidth(300);
layout->addWidget(core_box_underlying_dns_l, ++line, 0);
layout->addWidget(core_box_underlying_dns, line, 1);
//
auto core_box_enable_clash_api_l = new QLabel("Enable Clash API");
core_box_enable_clash_api = new QCheckBox;
core_box_enable_clash_api->setChecked(NekoGui::dataStore->core_box_clash_api > 0);
layout->addWidget(core_box_enable_clash_api_l, ++line, 0);
layout->addWidget(core_box_enable_clash_api, line, 1);
//
auto core_box_clash_listen_addr_l = new QLabel("Clash Api Listen Address");
core_box_clash_listen_addr = new MyLineEdit;
core_box_clash_listen_addr->setText(NekoGui::dataStore->core_box_clash_listen_addr);
layout->addWidget(core_box_clash_listen_addr_l, ++line, 0);
layout->addWidget(core_box_clash_listen_addr, line, 1);
//
auto core_box_clash_api_l = new QLabel("Clash API Listen Port");
core_box_clash_api = new MyLineEdit;
core_box_clash_api->setText(Int2String(std::abs(NekoGui::dataStore->core_box_clash_api)));
layout->addWidget(core_box_clash_api_l, ++line, 0);
layout->addWidget(core_box_clash_api, line, 1);
//
auto core_box_clash_api_secret_l = new QLabel("Clash API Secret");
core_box_clash_api_secret = new MyLineEdit;
core_box_clash_api_secret->setText(NekoGui::dataStore->core_box_clash_api_secret);
layout->addWidget(core_box_clash_api_secret_l, ++line, 0);
layout->addWidget(core_box_clash_api_secret, line, 1);
//
auto box = new QDialogButtonBox;
box->setOrientation(Qt::Horizontal);
box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
connect(box, &QDialogButtonBox::accepted, w, [=] {
NekoGui::dataStore->core_box_underlying_dns = core_box_underlying_dns->text();
NekoGui::dataStore->core_box_clash_api = core_box_clash_api->text().toInt() * (core_box_enable_clash_api->isChecked() ? 1 : -1);
NekoGui::dataStore->core_box_clash_listen_addr = core_box_clash_listen_addr->text();
NekoGui::dataStore->core_box_clash_api_secret = core_box_clash_api_secret->text();
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");
w->accept();
});
connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);
layout->addWidget(box, ++line, 1);
//
ADD_ASTERISK(w)
w->exec();
w->deleteLater();
}