mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-18 20:50:09 +08:00
add custom timeout to url and speed test
This commit is contained in:
parent
1c4429f4aa
commit
808c4f31a9
@ -53,6 +53,7 @@ message TestReq {
|
||||
optional string url = 4 [default = ""];
|
||||
optional bool test_current = 5 [default = false];
|
||||
optional int32 max_concurrency = 6 [default = 0];
|
||||
optional int32 test_timeout_ms = 7 [default = 0];
|
||||
}
|
||||
|
||||
message TestResp {
|
||||
@ -98,6 +99,7 @@ message SpeedTestRequest {
|
||||
optional bool test_upload = 6 [default = false];
|
||||
optional bool simple_download = 7 [default = false];
|
||||
optional string simple_download_addr = 8 [default = ""];
|
||||
optional int32 timeout_ms = 9 [default = 0];
|
||||
}
|
||||
|
||||
message SpeedTestResult {
|
||||
|
||||
@ -182,7 +182,7 @@ func (s *server) Test(in *gen.TestReq, out *gen.TestResp) error {
|
||||
if maxConcurrency >= 500 || maxConcurrency == 0 {
|
||||
maxConcurrency = MaxConcurrentTests
|
||||
}
|
||||
results := BatchURLTest(testCtx, testInstance, outboundTags, *in.Url, int(maxConcurrency), twice)
|
||||
results := BatchURLTest(testCtx, testInstance, outboundTags, *in.Url, int(maxConcurrency), twice, time.Duration(*in.TestTimeoutMs)*time.Millisecond)
|
||||
|
||||
res := make([]*gen.URLTestResp, 0)
|
||||
for idx, data := range results {
|
||||
@ -339,7 +339,7 @@ func (s *server) SpeedTest(in *gen.SpeedTestRequest, out *gen.SpeedTestResponse)
|
||||
outboundTags = []string{outbound.Tag()}
|
||||
}
|
||||
|
||||
results := BatchSpeedTest(testCtx, testInstance, outboundTags, *in.TestDownload, *in.TestUpload, *in.SimpleDownload, *in.SimpleDownloadAddr)
|
||||
results := BatchSpeedTest(testCtx, testInstance, outboundTags, *in.TestDownload, *in.TestUpload, *in.SimpleDownload, *in.SimpleDownloadAddr, time.Duration(*in.TimeoutMs)*time.Millisecond)
|
||||
|
||||
res := make([]*gen.SpeedTestResult, 0)
|
||||
for _, data := range results {
|
||||
|
||||
@ -24,6 +24,7 @@ var SpTQuerier SpeedTestResultQuerier
|
||||
var URLReporter URLTestReporter
|
||||
|
||||
const URLTestTimeout = 3 * time.Second
|
||||
const FetchServersTimeout = 8 * time.Second
|
||||
const MaxConcurrentTests = 100
|
||||
|
||||
type URLTestResult struct {
|
||||
@ -84,7 +85,10 @@ func (s *SpeedTestResultQuerier) setIsRunning(isRunning bool) {
|
||||
s.isRunning = isRunning
|
||||
}
|
||||
|
||||
func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool) []*URLTestResult {
|
||||
func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool, timeout time.Duration) []*URLTestResult {
|
||||
if timeout <= 0 {
|
||||
timeout = URLTestTimeout
|
||||
}
|
||||
outbounds := service.FromContext[adapter.OutboundManager](i.Context())
|
||||
resMap := make(map[string]*URLTestResult)
|
||||
resAccess := sync.Mutex{}
|
||||
@ -113,11 +117,11 @@ func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, network string, addr string) (net.Conn, error) {
|
||||
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
return outbound.DialContext(ctx, "tcp", metadata.ParseSocksaddr(addr))
|
||||
},
|
||||
},
|
||||
Timeout: URLTestTimeout,
|
||||
Timeout: timeout,
|
||||
}
|
||||
// to properly measure muxed configs, let's do the test twice
|
||||
duration, err := urlTest(ctx, client, url)
|
||||
@ -148,8 +152,6 @@ func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url
|
||||
}
|
||||
|
||||
func urlTest(ctx context.Context, client *http.Client, url string) (time.Duration, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, URLTestTimeout)
|
||||
defer cancel()
|
||||
begin := time.Now()
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
@ -169,7 +171,7 @@ func getNetDialer(dialer func(ctx context.Context, network string, destination m
|
||||
}
|
||||
}
|
||||
|
||||
func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, testDl, testUl bool, simpleDL bool, simpleAddress string) []*SpeedTestResult {
|
||||
func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, testDl, testUl bool, simpleDL bool, simpleAddress string, timeout time.Duration) []*SpeedTestResult {
|
||||
outbounds := service.FromContext[adapter.OutboundManager](i.Context())
|
||||
results := make([]*SpeedTestResult, 0)
|
||||
|
||||
@ -189,9 +191,9 @@ func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, t
|
||||
|
||||
var err error
|
||||
if simpleDL {
|
||||
err = simpleDownloadTest(ctx, getNetDialer(outbound.DialContext), res, simpleAddress)
|
||||
err = simpleDownloadTest(ctx, getNetDialer(outbound.DialContext), res, simpleAddress, timeout)
|
||||
} else {
|
||||
err = speedTestWithDialer(ctx, getNetDialer(outbound.DialContext), res, testDl, testUl)
|
||||
err = speedTestWithDialer(ctx, getNetDialer(outbound.DialContext), res, testDl, testUl, timeout)
|
||||
}
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
res.Error = err
|
||||
@ -208,14 +210,17 @@ func BatchSpeedTest(ctx context.Context, i *boxbox.Box, outboundTags []string, t
|
||||
return results
|
||||
}
|
||||
|
||||
func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testURL string) error {
|
||||
func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testURL string, timeout time.Duration) error {
|
||||
if timeout <= 0 {
|
||||
timeout = URLTestTimeout
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
return dialer(ctx, network, addr)
|
||||
},
|
||||
},
|
||||
Timeout: URLTestTimeout,
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
res.ServerName = "N/A"
|
||||
@ -268,13 +273,15 @@ func simpleDownloadTest(ctx context.Context, dialer func(ctx context.Context, ne
|
||||
}
|
||||
}
|
||||
|
||||
func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testDl, testUl bool) error {
|
||||
func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, network string, address string) (net.Conn, error), res *SpeedTestResult, testDl, testUl bool, timeout time.Duration) error {
|
||||
clt := speedtest.New(speedtest.WithUserConfig(&speedtest.UserConfig{
|
||||
DialContextFunc: dialer,
|
||||
PingMode: speedtest.HTTP,
|
||||
MaxConnections: 8,
|
||||
}))
|
||||
srv, err := clt.FetchServerListContext(ctx)
|
||||
fetchCtx, cancel := context.WithTimeout(ctx, FetchServersTimeout)
|
||||
defer cancel()
|
||||
srv, err := clt.FetchServerListContext(fetchCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -297,14 +304,18 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n
|
||||
go func() {
|
||||
defer func() { close(done) }()
|
||||
if testDl {
|
||||
err = srv[0].DownloadTestContext(ctx)
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
err = srv[0].DownloadTestContext(timeoutCtx)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
res.Error = err
|
||||
return
|
||||
}
|
||||
}
|
||||
if testUl {
|
||||
err = srv[0].UploadTestContext(ctx)
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
err = srv[0].UploadTestContext(timeoutCtx)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
res.Error = err
|
||||
return
|
||||
|
||||
@ -79,6 +79,7 @@ namespace Configs {
|
||||
// Misc
|
||||
QString log_level = "info";
|
||||
QString test_latency_url = "http://cp.cloudflare.com/";
|
||||
int url_test_timeout_ms = 3000;
|
||||
bool disable_tray = false;
|
||||
int test_concurrent = 10;
|
||||
bool disable_traffic_stats = false;
|
||||
@ -99,6 +100,7 @@ namespace Configs {
|
||||
bool enable_stats = true;
|
||||
int stats_tab = 0; // either connection or log
|
||||
int speed_test_mode = TestConfig::FULL;
|
||||
int speed_test_timeout_ms = 3000;
|
||||
QString simple_dl_url = "http://cachefly.cachefly.net/1mb.test";
|
||||
bool allow_beta_update = false;
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>728</width>
|
||||
<width>767</width>
|
||||
<height>472</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -20,7 +20,7 @@
|
||||
<string>Basic Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="8" column="3">
|
||||
<item row="9" column="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
@ -165,27 +165,52 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,8,1,1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Latency Test URL</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Latency Test URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_latency_url"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_latency_url"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Concurrent</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Concurrent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_concurrent"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Timeout for URLtest in ms<br/>Note that muxed connections take a much longer time for their initial request, and setting this value too low will cause the test to falsely report that the config is not working</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Timeout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="url_timeout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_concurrent"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -260,6 +285,19 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>timeout in milliseconds<br/>applies to all tests individually</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Timeout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_timeout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@ -534,80 +572,80 @@
|
||||
<string>Subscription</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="sub_auto_update_enable">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Interval (minute, invalid if less than 30)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="sub_auto_update">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="MyLineEdit" name="user_agent"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="sub_clear">
|
||||
<property name="text">
|
||||
<string>Clear servers before updating subscription</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="sub_send_hwid">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>HWID=%1</p><p>OS=%2</p><p>OS Version=%3</p><p>Model=%4</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable sending HWID, device model, and OS version when updating subscription</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatic update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>User Agent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="sub_auto_update_enable">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Interval (minute, invalid if less than 30)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="sub_auto_update">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="MyLineEdit" name="user_agent"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="sub_clear">
|
||||
<property name="text">
|
||||
<string>Clear servers before updating subscription</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="sub_send_hwid">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>HWID=%1</p><p>OS=%2</p><p>OS Version=%3</p><p>Model=%4</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable sending HWID, device model, and OS version when updating subscription</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatic update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>User Agent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@ -318,6 +318,8 @@ namespace Configs {
|
||||
_add(new configItem("use_mozilla_certs", &use_mozilla_certs, itemType::boolean));
|
||||
_add(new configItem("allow_beta_update", &allow_beta_update, itemType::boolean));
|
||||
_add(new configItem("adblock_enable", &adblock_enable, itemType::boolean));
|
||||
_add(new configItem("speed_test_timeout_ms", &speed_test_timeout_ms, itemType::integer));
|
||||
_add(new configItem("url_test_timeout_ms", &url_test_timeout_ms, itemType::integer));
|
||||
}
|
||||
|
||||
void DataStore::UpdateStartedId(int id) {
|
||||
|
||||
@ -2384,6 +2384,7 @@ void MainWindow::loadShortcuts()
|
||||
auto mp = Configs::dataStore->shortcuts->shortcuts;
|
||||
for (QList<QAction *> actions = findChildren<QAction *>(); QAction *action : actions)
|
||||
{
|
||||
if (action->data().isNull() || action->data().toString().isEmpty()) continue;
|
||||
if (mp.count(action->data().toString()) == 0) action->setShortcut(QKeySequence());
|
||||
else action->setShortcut(mp[action->data().toString()]);
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin
|
||||
req.url = Configs::dataStore->test_latency_url.toStdString();
|
||||
req.use_default_outbound = useDefault;
|
||||
req.max_concurrency = Configs::dataStore->test_concurrent;
|
||||
req.test_timeout_ms = Configs::dataStore->url_test_timeout_ms;
|
||||
|
||||
auto done = new QMutex;
|
||||
done->lock();
|
||||
@ -51,7 +52,7 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin
|
||||
bool ok;
|
||||
while (true)
|
||||
{
|
||||
QThread::msleep(1500);
|
||||
QThread::msleep(200);
|
||||
if (done->try_lock()) break;
|
||||
auto resp = defaultClient->QueryURLTest(&ok);
|
||||
if (!ok || resp.results.empty())
|
||||
@ -285,6 +286,7 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC
|
||||
req.simple_download = speedtestConf == Configs::TestConfig::SIMPLEDL;
|
||||
req.simple_download_addr = Configs::dataStore->simple_dl_url.toStdString();
|
||||
req.test_current = testCurrent;
|
||||
req.timeout_ms = Configs::dataStore->speed_test_timeout_ms;
|
||||
|
||||
// loop query result
|
||||
auto doneMu = new QMutex;
|
||||
|
||||
@ -36,7 +36,9 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
||||
D_LOAD_INT(test_concurrent)
|
||||
D_LOAD_STRING(test_latency_url)
|
||||
D_LOAD_BOOL(disable_tray)
|
||||
ui->url_timeout->setText(Int2String(Configs::dataStore->url_test_timeout_ms));
|
||||
ui->speedtest_mode->setCurrentIndex(Configs::dataStore->speed_test_mode);
|
||||
ui->test_timeout->setText(Int2String(Configs::dataStore->speed_test_timeout_ms));
|
||||
ui->simple_down_url->setText(Configs::dataStore->simple_dl_url);
|
||||
ui->allow_beta->setChecked(Configs::dataStore->allow_beta_update);
|
||||
|
||||
@ -184,6 +186,8 @@ void DialogBasicSettings::accept() {
|
||||
Configs::dataStore->proxy_scheme = ui->proxy_scheme->currentText().toLower();
|
||||
Configs::dataStore->speed_test_mode = ui->speedtest_mode->currentIndex();
|
||||
Configs::dataStore->simple_dl_url = ui->simple_down_url->text();
|
||||
Configs::dataStore->url_test_timeout_ms = ui->url_timeout->text().toInt();
|
||||
Configs::dataStore->speed_test_timeout_ms = ui->test_timeout->text().toInt();
|
||||
Configs::dataStore->allow_beta_update = ui->allow_beta->isChecked();
|
||||
|
||||
// Style
|
||||
|
||||
Loading…
Reference in New Issue
Block a user