refactor: C++ 后端文件移入 src/ 目录,优化项目结构层级
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
// SPDX-FileCopyrightText: 2024 MyCompany
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "networkmonitorapplet.h"
|
||||
#include <pluginfactory.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
DS_BEGIN_NAMESPACE
|
||||
|
||||
NetworkMonitorApplet::NetworkMonitorApplet(QObject *parent)
|
||||
: DApplet(parent)
|
||||
, m_refreshTimer(nullptr)
|
||||
, m_lastRxBytes(0)
|
||||
, m_lastTxBytes(0)
|
||||
, m_downloadSpeed(0)
|
||||
, m_uploadSpeed(0)
|
||||
, m_totalDownload(0)
|
||||
, m_totalUpload(0)
|
||||
, m_ready(false)
|
||||
, m_firstUpdate(true)
|
||||
{
|
||||
}
|
||||
|
||||
NetworkMonitorApplet::~NetworkMonitorApplet()
|
||||
{
|
||||
}
|
||||
|
||||
bool NetworkMonitorApplet::load()
|
||||
{
|
||||
return DApplet::load();
|
||||
}
|
||||
|
||||
bool NetworkMonitorApplet::init()
|
||||
{
|
||||
detectInterfaces();
|
||||
|
||||
// 启动定时刷新
|
||||
m_refreshTimer = new QTimer(this);
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &NetworkMonitorApplet::refresh);
|
||||
m_refreshTimer->start(1000); // 每秒刷新一次
|
||||
|
||||
// 初始读取
|
||||
readNetworkStats();
|
||||
|
||||
return DApplet::init();
|
||||
}
|
||||
|
||||
double NetworkMonitorApplet::downloadSpeed() const
|
||||
{
|
||||
return static_cast<double>(m_downloadSpeed);
|
||||
}
|
||||
|
||||
double NetworkMonitorApplet::uploadSpeed() const
|
||||
{
|
||||
return static_cast<double>(m_uploadSpeed);
|
||||
}
|
||||
|
||||
double NetworkMonitorApplet::totalDownload() const
|
||||
{
|
||||
return static_cast<double>(m_totalDownload);
|
||||
}
|
||||
|
||||
double NetworkMonitorApplet::totalUpload() const
|
||||
{
|
||||
return static_cast<double>(m_totalUpload);
|
||||
}
|
||||
|
||||
QStringList NetworkMonitorApplet::networkInterfaces() const
|
||||
{
|
||||
return m_interfaceList;
|
||||
}
|
||||
|
||||
QStringList NetworkMonitorApplet::interfaceStats() const
|
||||
{
|
||||
QStringList stats;
|
||||
for (const QString &name : m_interfaceList) {
|
||||
if (m_interfaces.contains(name)) {
|
||||
const NetworkInterface &iface = m_interfaces[name];
|
||||
stats << QString("%1|%2|%3|%4|%5")
|
||||
.arg(iface.name)
|
||||
.arg(iface.rxBytes)
|
||||
.arg(iface.txBytes)
|
||||
.arg(iface.rxPackets)
|
||||
.arg(iface.txPackets);
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
bool NetworkMonitorApplet::ready() const
|
||||
{
|
||||
return m_ready;
|
||||
}
|
||||
|
||||
QString NetworkMonitorApplet::activeInterface() const
|
||||
{
|
||||
return m_activeInterface;
|
||||
}
|
||||
|
||||
// 返回活动接口的 IPv4 地址,供 QML 显示
|
||||
QString NetworkMonitorApplet::ipAddress() const
|
||||
{
|
||||
return m_ipAddress;
|
||||
}
|
||||
|
||||
// 返回活动接口的 IPv6 全球地址,供 QML 显示
|
||||
QString NetworkMonitorApplet::ipv6Address() const
|
||||
{
|
||||
return m_ipv6Address;
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::refresh()
|
||||
{
|
||||
readNetworkStats();
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::setActiveInterface(const QString &interface)
|
||||
{
|
||||
if (m_activeInterface != interface) {
|
||||
m_activeInterface = interface;
|
||||
m_firstUpdate = true;
|
||||
emit activeInterfaceChanged();
|
||||
// 接口切换后立即检测新接口的 IP 地址
|
||||
detectIpAddress();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::readNetworkStats()
|
||||
{
|
||||
QFile file("/proc/net/dev");
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qDebug() << "Failed to open /proc/net/dev";
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
QStringList lines = stream.readAll().split('\n', Qt::SkipEmptyParts);
|
||||
file.close();
|
||||
|
||||
QMap<QString, NetworkInterface> newInterfaces;
|
||||
|
||||
// 跳过前两行标题
|
||||
for (int i = 2; i < lines.size(); ++i) {
|
||||
QString line = lines[i].trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
// 格式: "interface: rx_bytes rx_packets rx_errors rx_dropped ... tx_bytes tx_packets tx_errors tx_dropped ..."
|
||||
QRegularExpression re("^([^:]+):\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");
|
||||
QRegularExpressionMatch match = re.match(line);
|
||||
|
||||
if (match.hasMatch()) {
|
||||
NetworkInterface iface;
|
||||
iface.name = match.captured(1);
|
||||
iface.rxBytes = match.captured(2).toLongLong();
|
||||
iface.rxPackets = match.captured(3).toLongLong();
|
||||
iface.rxErrors = match.captured(4).toLongLong();
|
||||
iface.rxDropped = match.captured(5).toLongLong();
|
||||
iface.txBytes = match.captured(6).toLongLong();
|
||||
iface.txPackets = match.captured(7).toLongLong();
|
||||
iface.txErrors = match.captured(8).toLongLong();
|
||||
iface.txDropped = match.captured(9).toLongLong();
|
||||
|
||||
// 过滤掉回环接口和虚拟接口
|
||||
if (iface.name != "lo" && !iface.name.startsWith("veth") &&
|
||||
!iface.name.startsWith("docker") && !iface.name.startsWith("br-")) {
|
||||
newInterfaces[iface.name] = iface;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检测接口变化
|
||||
if (newInterfaces.keys() != m_interfaces.keys()) {
|
||||
m_interfaceList = newInterfaces.keys();
|
||||
m_interfaces = newInterfaces;
|
||||
|
||||
// 自动选择活动接口
|
||||
if (m_activeInterface.isEmpty() && !m_interfaceList.isEmpty()) {
|
||||
// 优先选择物理网卡(wlp/wlan/enp/eth)中有流量的接口,
|
||||
// 避免选中 Meta/tun0 等虚拟代理接口作为默认显示
|
||||
for (const QString &name : m_interfaceList) {
|
||||
if (!isPhysicalInterface(name)) continue;
|
||||
const NetworkInterface &iface = m_interfaces[name];
|
||||
if (iface.rxBytes > 0 || iface.txBytes > 0) {
|
||||
m_activeInterface = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 其次选择任意有流量的接口(物理网卡都无流量时的兜底)
|
||||
if (m_activeInterface.isEmpty()) {
|
||||
for (const QString &name : m_interfaceList) {
|
||||
const NetworkInterface &iface = m_interfaces[name];
|
||||
if (iface.rxBytes > 0 || iface.txBytes > 0) {
|
||||
m_activeInterface = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 最后选第一个(无任何流量时)
|
||||
if (m_activeInterface.isEmpty()) {
|
||||
m_activeInterface = m_interfaceList.first();
|
||||
}
|
||||
emit activeInterfaceChanged();
|
||||
}
|
||||
|
||||
m_ready = !m_interfaceList.isEmpty();
|
||||
emit interfacesChanged();
|
||||
emit readyChanged();
|
||||
} else {
|
||||
m_interfaces = newInterfaces;
|
||||
}
|
||||
|
||||
calculateSpeed();
|
||||
emit statsChanged();
|
||||
// 每次刷新都检测 IP,以应对 DHCP 续约等 IP 变更场景
|
||||
detectIpAddress();
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::calculateSpeed()
|
||||
{
|
||||
qint64 currentRxBytes = getActiveRxBytes();
|
||||
qint64 currentTxBytes = getActiveTxBytes();
|
||||
|
||||
if (m_firstUpdate) {
|
||||
m_firstUpdate = false;
|
||||
m_lastRxBytes = currentRxBytes;
|
||||
m_lastTxBytes = currentTxBytes;
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算速度(字节/秒)
|
||||
m_downloadSpeed = currentRxBytes - m_lastRxBytes;
|
||||
m_uploadSpeed = currentTxBytes - m_lastTxBytes;
|
||||
|
||||
// 更新总量
|
||||
m_totalDownload = currentRxBytes;
|
||||
m_totalUpload = currentTxBytes;
|
||||
|
||||
m_lastRxBytes = currentRxBytes;
|
||||
m_lastTxBytes = currentTxBytes;
|
||||
|
||||
emit speedChanged();
|
||||
emit totalChanged();
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::detectInterfaces()
|
||||
{
|
||||
QDir sysClass("/sys/class/net");
|
||||
if (!sysClass.exists()) return;
|
||||
|
||||
QStringList entries = sysClass.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
m_interfaceList.clear();
|
||||
|
||||
for (const QString &entry : entries) {
|
||||
// 过滤回环接口
|
||||
if (entry != "lo") {
|
||||
m_interfaceList << entry;
|
||||
}
|
||||
}
|
||||
|
||||
m_ready = !m_interfaceList.isEmpty();
|
||||
emit interfacesChanged();
|
||||
emit readyChanged();
|
||||
}
|
||||
|
||||
qint64 NetworkMonitorApplet::getActiveRxBytes() const
|
||||
{
|
||||
if (m_activeInterface.isEmpty() || !m_interfaces.contains(m_activeInterface)) {
|
||||
return 0;
|
||||
}
|
||||
return m_interfaces[m_activeInterface].rxBytes;
|
||||
}
|
||||
|
||||
qint64 NetworkMonitorApplet::getActiveTxBytes() const
|
||||
{
|
||||
if (m_activeInterface.isEmpty() || !m_interfaces.contains(m_activeInterface)) {
|
||||
return 0;
|
||||
}
|
||||
return m_interfaces[m_activeInterface].txBytes;
|
||||
}
|
||||
|
||||
// 检测活动接口的 IPv4 与 IPv6 地址
|
||||
// IPv4:跳过 loopback,取第一个
|
||||
// IPv6:跳过 loopback 和 link-local (fe80::),取第一个全球地址
|
||||
// 任一地址变化时分别 emit 对应信号通知 QML 更新
|
||||
// 设计原因:DHCP 续约、网络切换、IPv6 SLAAC 等场景下地址可能变化,需持续检测
|
||||
void NetworkMonitorApplet::detectIpAddress()
|
||||
{
|
||||
QString newIp;
|
||||
QString newIpv6;
|
||||
if (!m_activeInterface.isEmpty()) {
|
||||
QNetworkInterface iface = QNetworkInterface::interfaceFromName(m_activeInterface);
|
||||
for (const QNetworkAddressEntry &entry : iface.addressEntries()) {
|
||||
// IPv4:跳过 loopback,取第一个
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol
|
||||
&& !entry.ip().isLoopback()
|
||||
&& newIp.isEmpty()) {
|
||||
newIp = entry.ip().toString();
|
||||
}
|
||||
// IPv6:跳过 loopback 和 link-local (fe80::),取第一个全球地址
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv6Protocol
|
||||
&& !entry.ip().isLoopback()
|
||||
&& !entry.ip().isLinkLocal()
|
||||
&& newIpv6.isEmpty()) {
|
||||
newIpv6 = entry.ip().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_ipAddress != newIp) {
|
||||
m_ipAddress = newIp;
|
||||
emit ipAddressChanged();
|
||||
}
|
||||
if (m_ipv6Address != newIpv6) {
|
||||
m_ipv6Address = newIpv6;
|
||||
emit ipv6AddressChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为物理网卡
|
||||
// 物理网卡前缀:无线 wlp/wlan/wifi,有线 enp/eth
|
||||
// 虚拟接口(Meta/tun/tap/docker/veth/br-)返回 false,
|
||||
// 用于自动选择时优先真实网卡而非代理 TUN 接口
|
||||
bool NetworkMonitorApplet::isPhysicalInterface(const QString &name) const
|
||||
{
|
||||
return name.startsWith("wlp") || name.startsWith("wlan")
|
||||
|| name.startsWith("enp") || name.startsWith("eth");
|
||||
}
|
||||
|
||||
D_APPLET_CLASS(NetworkMonitorApplet)
|
||||
|
||||
DS_END_NAMESPACE
|
||||
|
||||
#include "networkmonitorapplet.moc"
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2024 MyCompany
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <applet.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QVariantList>
|
||||
#include <QMap>
|
||||
|
||||
DS_BEGIN_NAMESPACE
|
||||
|
||||
struct NetworkInterface {
|
||||
QString name; // 接口名称,如 "eth0", "wlan0"
|
||||
qint64 rxBytes; // 接收字节数
|
||||
qint64 txBytes; // 发送字节数
|
||||
qint64 rxPackets; // 接收包数
|
||||
qint64 txPackets; // 发送包数
|
||||
qint64 rxErrors; // 接收错误数
|
||||
qint64 txErrors; // 发送错误数
|
||||
qint64 rxDropped; // 接收丢弃数
|
||||
qint64 txDropped; // 发送丢弃数
|
||||
};
|
||||
|
||||
class NetworkMonitorApplet : public DApplet
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(double downloadSpeed READ downloadSpeed NOTIFY speedChanged)
|
||||
Q_PROPERTY(double uploadSpeed READ uploadSpeed NOTIFY speedChanged)
|
||||
Q_PROPERTY(double totalDownload READ totalDownload NOTIFY totalChanged)
|
||||
Q_PROPERTY(double totalUpload READ totalUpload NOTIFY totalChanged)
|
||||
Q_PROPERTY(QStringList networkInterfaces READ networkInterfaces NOTIFY interfacesChanged)
|
||||
Q_PROPERTY(QStringList interfaceStats READ interfaceStats NOTIFY statsChanged)
|
||||
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
||||
Q_PROPERTY(QString activeInterface READ activeInterface NOTIFY activeInterfaceChanged)
|
||||
// 活动接口的 IPv4 地址,供 QML 在弹出面板和 tooltip 中显示
|
||||
Q_PROPERTY(QString ipAddress READ ipAddress NOTIFY ipAddressChanged)
|
||||
// 活动接口的 IPv6 全球地址(已过滤 link-local 和 loopback),供 QML 显示
|
||||
Q_PROPERTY(QString ipv6Address READ ipv6Address NOTIFY ipv6AddressChanged)
|
||||
|
||||
public:
|
||||
explicit NetworkMonitorApplet(QObject *parent = nullptr);
|
||||
~NetworkMonitorApplet();
|
||||
|
||||
virtual bool load() override;
|
||||
virtual bool init() override;
|
||||
|
||||
double downloadSpeed() const;
|
||||
double uploadSpeed() const;
|
||||
double totalDownload() const;
|
||||
double totalUpload() const;
|
||||
QStringList networkInterfaces() const;
|
||||
QStringList interfaceStats() const;
|
||||
bool ready() const;
|
||||
QString activeInterface() const;
|
||||
QString ipAddress() const;
|
||||
QString ipv6Address() const;
|
||||
|
||||
Q_INVOKABLE void refresh();
|
||||
Q_INVOKABLE void setActiveInterface(const QString &interface);
|
||||
|
||||
signals:
|
||||
void speedChanged();
|
||||
void totalChanged();
|
||||
void interfacesChanged();
|
||||
void statsChanged();
|
||||
void readyChanged();
|
||||
void activeInterfaceChanged();
|
||||
void ipAddressChanged();
|
||||
void ipv6AddressChanged();
|
||||
|
||||
private:
|
||||
void readNetworkStats();
|
||||
void calculateSpeed();
|
||||
void detectInterfaces();
|
||||
// 检测活动接口的 IPv4 与 IPv6 地址,变化时分别发射对应信号
|
||||
void detectIpAddress();
|
||||
// 判断是否为物理网卡(无线 wlp/wlan,有线 enp/eth),
|
||||
// 用于自动选择时优先真实网卡而非虚拟代理接口(如 Meta/tun0)
|
||||
bool isPhysicalInterface(const QString &name) const;
|
||||
qint64 getActiveRxBytes() const;
|
||||
qint64 getActiveTxBytes() const;
|
||||
|
||||
QTimer *m_refreshTimer;
|
||||
QMap<QString, NetworkInterface> m_interfaces;
|
||||
QStringList m_interfaceList;
|
||||
QString m_activeInterface;
|
||||
QString m_ipAddress; // 活动接口的 IPv4 地址
|
||||
QString m_ipv6Address; // 活动接口的 IPv6 全球地址(过滤 link-local)
|
||||
|
||||
// 速度计算
|
||||
qint64 m_lastRxBytes;
|
||||
qint64 m_lastTxBytes;
|
||||
qint64 m_downloadSpeed;
|
||||
qint64 m_uploadSpeed;
|
||||
|
||||
// 总量
|
||||
qint64 m_totalDownload;
|
||||
qint64 m_totalUpload;
|
||||
|
||||
bool m_ready;
|
||||
bool m_firstUpdate;
|
||||
};
|
||||
|
||||
DS_END_NAMESPACE
|
||||
Reference in New Issue
Block a user