mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-24 10:33:15 +08:00
add speedtest querier and ui view
This commit is contained in:
parent
bd1b1b1635
commit
a2c5efc31d
@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Mahdi-zarei/speedtest-go/speedtest"
|
"github.com/Mahdi-zarei/speedtest-go/speedtest"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/metadata"
|
"github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"nekobox_core/internal/boxbox"
|
"nekobox_core/internal/boxbox"
|
||||||
@ -206,14 +205,14 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n
|
|||||||
defer func() { close(done) }()
|
defer func() { close(done) }()
|
||||||
if testDl {
|
if testDl {
|
||||||
err = srv[0].DownloadTestContext(ctx)
|
err = srv[0].DownloadTestContext(ctx)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
res.Error = err
|
res.Error = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if testUl {
|
if testUl {
|
||||||
err = srv[0].UploadTestContext(ctx)
|
err = srv[0].UploadTestContext(ctx)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
res.Error = err
|
res.Error = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -232,7 +231,7 @@ func speedTestWithDialer(ctx context.Context, dialer func(ctx context.Context, n
|
|||||||
SpTQuerier.storeResult(res)
|
SpTQuerier.storeResult(res)
|
||||||
return nil
|
return nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return E.New("test cancelled")
|
return nil
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
res.DlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMADownloadRate()).String()
|
res.DlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMADownloadRate()).String()
|
||||||
res.UlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMAUploadRate()).String()
|
res.UlSpeed = speedtest.ByteRate(srv[0].Context.GetEWMAUploadRate()).String()
|
||||||
|
|||||||
@ -42,7 +42,7 @@ namespace NekoGui_rpc {
|
|||||||
|
|
||||||
libcore::SpeedTestResponse SpeedTest(bool *rpcOK, const libcore::SpeedTestRequest &request);
|
libcore::SpeedTestResponse SpeedTest(bool *rpcOK, const libcore::SpeedTestRequest &request);
|
||||||
|
|
||||||
libcore::SpeedTestResult QueryCurrentSpeedTests(bool *rpcOK);
|
libcore::QuerySpeedTestResponse QueryCurrentSpeedTests(bool *rpcOK);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate>()> make_grpc_channel;
|
std::function<std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate>()> make_grpc_channel;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <core/server/gen/libcore.pb.h>
|
||||||
|
|
||||||
#include "include/global/NekoGui.hpp"
|
#include "include/global/NekoGui.hpp"
|
||||||
#include "include/stats/connections/connectionLister.hpp"
|
#include "include/stats/connections/connectionLister.hpp"
|
||||||
@ -86,6 +87,9 @@ public:
|
|||||||
|
|
||||||
void UpdateConnectionListWithRecreate(const QList<NekoGui_traffic::ConnectionMetadata>& connections);
|
void UpdateConnectionListWithRecreate(const QList<NekoGui_traffic::ConnectionMetadata>& connections);
|
||||||
|
|
||||||
|
// TODO maybe use a more generalized way of passing data later, for now we only need it for speedtest data
|
||||||
|
void UpdateDataView(const libcore::SpeedTestResult& result, const QString& profileName, bool clear);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void profile_selected(int id);
|
void profile_selected(int id);
|
||||||
|
|||||||
@ -165,23 +165,43 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<widget class="QTextBrowser" name="data_view">
|
||||||
<property name="orientation">
|
<property name="enabled">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="search">
|
|
||||||
<property name="clearButtonEnabled">
|
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Ignored">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="mouseTracking">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::ContextMenuPolicy::NoContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="acceptDrops">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::Shape::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Shadow::Sunken</enum>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::TextInteractionFlag::LinksAccessibleByKeyboard|Qt::TextInteractionFlag::LinksAccessibleByMouse</set>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -536,7 +556,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>25</height>
|
<height>17</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menu_program">
|
<widget class="QMenu" name="menu_program">
|
||||||
|
|||||||
@ -421,10 +421,10 @@ namespace NekoGui_rpc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libcore::SpeedTestResult Client::QueryCurrentSpeedTests(bool *rpcOK)
|
libcore::QuerySpeedTestResponse Client::QueryCurrentSpeedTests(bool *rpcOK)
|
||||||
{
|
{
|
||||||
const libcore::EmptyReq req;
|
const libcore::EmptyReq req;
|
||||||
libcore::SpeedTestResult reply;
|
libcore::QuerySpeedTestResponse reply;
|
||||||
auto status = make_grpc_channel()->Call("QuerySpeedTest", req, &reply);
|
auto status = make_grpc_channel()->Call("QuerySpeedTest", req, &reply);
|
||||||
|
|
||||||
if (status == QNetworkReply::NoError) {
|
if (status == QNetworkReply::NoError) {
|
||||||
|
|||||||
@ -310,38 +310,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
ui->proxyListTable->setTabKeyNavigation(false);
|
ui->proxyListTable->setTabKeyNavigation(false);
|
||||||
|
|
||||||
// search box
|
// search box
|
||||||
ui->search->setVisible(false);
|
|
||||||
connect(shortcut_ctrl_f, &QShortcut::activated, this, [=] {
|
|
||||||
ui->search->setVisible(true);
|
|
||||||
ui->search->setFocus();
|
|
||||||
});
|
|
||||||
connect(shortcut_esc, &QShortcut::activated, this, [=] {
|
connect(shortcut_esc, &QShortcut::activated, this, [=] {
|
||||||
if (ui->search->isVisible()) {
|
|
||||||
ui->search->setText("");
|
|
||||||
ui->search->textChanged("");
|
|
||||||
ui->search->setVisible(false);
|
|
||||||
}
|
|
||||||
if (select_mode) {
|
if (select_mode) {
|
||||||
emit profile_selected(-1);
|
emit profile_selected(-1);
|
||||||
select_mode = false;
|
select_mode = false;
|
||||||
refresh_status();
|
refresh_status();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(ui->search, &QLineEdit::textChanged, this, [=](const QString &text) {
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
for (int i = 0; i < ui->proxyListTable->rowCount(); i++) {
|
|
||||||
ui->proxyListTable->setRowHidden(i, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QList<QTableWidgetItem *> findItem = ui->proxyListTable->findItems(text, Qt::MatchContains);
|
|
||||||
for (int i = 0; i < ui->proxyListTable->rowCount(); i++) {
|
|
||||||
ui->proxyListTable->setRowHidden(i, true);
|
|
||||||
}
|
|
||||||
for (auto item: findItem) {
|
|
||||||
if (item != nullptr) ui->proxyListTable->setRowHidden(item->row(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// refresh
|
// refresh
|
||||||
this->refresh_groups();
|
this->refresh_groups();
|
||||||
@ -953,6 +928,27 @@ void MainWindow::neko_set_spmode_vpn(bool enable, bool save) {
|
|||||||
if (NekoGui::dataStore->started_id >= 0) neko_start(NekoGui::dataStore->started_id);
|
if (NekoGui::dataStore->started_id >= 0) neko_start(NekoGui::dataStore->started_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::UpdateDataView(const libcore::SpeedTestResult& result, const QString& profileName, bool clear)
|
||||||
|
{
|
||||||
|
if (clear)
|
||||||
|
{
|
||||||
|
ui->data_view->clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString html = QString(
|
||||||
|
"<p style='text-align:center;margin:0;'>Running Speedtest: %1</p>"
|
||||||
|
"<p style='text-align:center; color:#3299FF;margin:0;'>Dl↓ %2</p>"
|
||||||
|
"<p style='text-align:center; color:#86C43F;margin:0;'>Ul↑ %3</p>"
|
||||||
|
"<p style='text-align:center;margin:0;'>Server: %4, %5</p>"
|
||||||
|
).arg(profileName,
|
||||||
|
result.dl_speed().c_str(),
|
||||||
|
result.ul_speed().c_str(),
|
||||||
|
result.server_country().c_str(),
|
||||||
|
result.server_name().c_str());
|
||||||
|
ui->data_view->setHtml(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void MainWindow::setupConnectionList()
|
void MainWindow::setupConnectionList()
|
||||||
{
|
{
|
||||||
ui->connections->horizontalHeader()->setHighlightSections(false);
|
ui->connections->horizontalHeader()->setHighlightSections(false);
|
||||||
|
|||||||
@ -223,8 +223,43 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, const QStr
|
|||||||
req.set_test_download(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::DL);
|
req.set_test_download(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::DL);
|
||||||
req.set_test_upload(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::UL);
|
req.set_test_upload(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::UL);
|
||||||
|
|
||||||
|
// loop query result
|
||||||
|
auto doneMu = new QMutex;
|
||||||
|
doneMu->lock();
|
||||||
|
runOnNewThread([=]
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
while (true) {
|
||||||
|
QThread::msleep(100);
|
||||||
|
if (doneMu->tryLock())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto res = defaultClient->QueryCurrentSpeedTests(&ok);
|
||||||
|
if (!ok || !res.is_running())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto profile = NekoGui::profileManager->GetProfile(tag2entID[res.result().outbound_tag().c_str()]);
|
||||||
|
if (profile == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
runOnUiThread([=]
|
||||||
|
{
|
||||||
|
UpdateDataView(res.result(), profile->bean->name, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
runOnUiThread([=]
|
||||||
|
{
|
||||||
|
UpdateDataView({}, {}, true);
|
||||||
|
});
|
||||||
|
doneMu->unlock();
|
||||||
|
delete doneMu;
|
||||||
|
});
|
||||||
bool rpcOK;
|
bool rpcOK;
|
||||||
auto result = defaultClient->SpeedTest(&rpcOK, req);
|
auto result = defaultClient->SpeedTest(&rpcOK, req);
|
||||||
|
doneMu->unlock();
|
||||||
//
|
//
|
||||||
if (!rpcOK) return;
|
if (!rpcOK) return;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user