Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30a186b6dc | ||
|
|
6ef9a23a24 | ||
|
|
a5cfffc2f1 | ||
|
|
ee885e0dbb | ||
|
|
d18314bdec | ||
|
|
4aca583d2d |
@@ -74,9 +74,12 @@ Window {
|
||||
color: "transparent"
|
||||
|
||||
// 拖动层:声明在关闭按钮之前(位于其下方),避免遮挡按钮点击与 hover
|
||||
// 用 startSystemMove() 系统级窗口拖动(drag.target 对 Window 无效,
|
||||
// 与 SettingsWindow/TrafficChartWindow 一致)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: root
|
||||
cursorShape: Qt.OpenHandCursor
|
||||
onPressed: root.startSystemMove()
|
||||
}
|
||||
|
||||
Text {
|
||||
|
||||
@@ -43,6 +43,14 @@ Control {
|
||||
readonly property string ipv6Address: applet ? (applet.ipv6Address || "") : ""
|
||||
readonly property var networkInterfaces: applet ? (applet.networkInterfaces || []) : []
|
||||
|
||||
// 活动接口的收发包/错误/丢包累计统计(自开机起),由 C++ 后端持续更新
|
||||
readonly property real rxPackets: applet ? (applet.rxPackets || 0) : 0
|
||||
readonly property real txPackets: applet ? (applet.txPackets || 0) : 0
|
||||
readonly property real rxErrors: applet ? (applet.rxErrors || 0) : 0
|
||||
readonly property real txErrors: applet ? (applet.txErrors || 0) : 0
|
||||
readonly property real rxDropped: applet ? (applet.rxDropped || 0) : 0
|
||||
readonly property real txDropped: applet ? (applet.txDropped || 0) : 0
|
||||
|
||||
// 用户自定义字体色:非空时覆盖 primaryText,空串回退 DTK 调色板派生
|
||||
// 仅作用于主要文字(接口名、速度数值、累计数值、chip 未选中文字)
|
||||
// secondaryText/tertiaryText/强调色不跟随,保持系统主题层次不崩
|
||||
@@ -458,6 +466,71 @@ Control {
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
|
||||
// 包统计:收发包 + 错误/丢包(累计值,自开机起),紧接累计流量下方
|
||||
// 布局与流量总计行一致:标签 + ↓蓝粗 + 数值 + ↑绿粗 + 数值,拆分为独立 Text
|
||||
// 设计原因:包统计与流量统计同属"累计数据",放一起信息归属清晰且视觉对称;
|
||||
// 包总数始终显示;错误/丢包仅在存在时显示,避免正常网卡信息过载
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
visible: popup.ready
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// 包统计标签(与"流量总计"标签对齐)
|
||||
Text {
|
||||
text: qsTr("Packets")
|
||||
font.pixelSize: Math.round(12 * popup.fontScale)
|
||||
color: popup.tertiaryText
|
||||
}
|
||||
|
||||
// 接收包数:蓝色下箭头(与流量总计行下载箭头一致)
|
||||
Text {
|
||||
text: "↓"
|
||||
font.pixelSize: Math.round(12 * popup.fontScale)
|
||||
font.weight: Font.Bold
|
||||
color: popup.accentBlue
|
||||
}
|
||||
|
||||
Text {
|
||||
text: popup.rxPackets.toFixed(0)
|
||||
font.pixelSize: Math.round(13 * popup.fontScale)
|
||||
color: popup.primaryText
|
||||
}
|
||||
|
||||
// 发送包数:绿色上箭头(与流量总计行上传箭头一致)
|
||||
Text {
|
||||
text: "↑"
|
||||
font.pixelSize: Math.round(12 * popup.fontScale)
|
||||
font.weight: Font.Bold
|
||||
color: popup.accentGreen
|
||||
}
|
||||
|
||||
Text {
|
||||
text: popup.txPackets.toFixed(0)
|
||||
font.pixelSize: Math.round(13 * popup.fontScale)
|
||||
color: popup.primaryText
|
||||
}
|
||||
|
||||
// 错误数:仅在存在错误或丢包时显示,格式 "错误 {rxE}/{txE}"
|
||||
Text {
|
||||
text: qsTr("Errors") + " " + popup.rxErrors.toFixed(0) + "/" + popup.txErrors.toFixed(0)
|
||||
font.pixelSize: Math.round(12 * popup.fontScale)
|
||||
color: popup.tertiaryText
|
||||
visible: popup.rxErrors > 0 || popup.txErrors > 0 || popup.rxDropped > 0 || popup.txDropped > 0
|
||||
}
|
||||
|
||||
// 丢包数:仅在存在错误或丢包时显示,格式 "丢包 {rxD}/{txD}"
|
||||
Text {
|
||||
text: qsTr("Dropped") + " " + popup.rxDropped.toFixed(0) + "/" + popup.txDropped.toFixed(0)
|
||||
font.pixelSize: Math.round(12 * popup.fontScale)
|
||||
color: popup.tertiaryText
|
||||
visible: popup.rxErrors > 0 || popup.txErrors > 0 || popup.rxDropped > 0 || popup.txDropped > 0
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
|
||||
// 分隔线3
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -320,7 +320,7 @@ AppletItem {
|
||||
PanelPopup {
|
||||
id: networkPopup
|
||||
width: 330
|
||||
height: 250
|
||||
height: 280
|
||||
popupX: DockPanelPositioner.x
|
||||
popupY: DockPanelPositioner.y
|
||||
|
||||
|
||||
+101
-23
@@ -30,9 +30,17 @@ NetworkMonitorApplet::NetworkMonitorApplet(QObject *parent)
|
||||
, m_uploadSpeed(0)
|
||||
, m_totalDownload(0)
|
||||
, m_totalUpload(0)
|
||||
, m_rxPackets(0)
|
||||
, m_txPackets(0)
|
||||
, m_rxErrors(0)
|
||||
, m_txErrors(0)
|
||||
, m_rxDropped(0)
|
||||
, m_txDropped(0)
|
||||
, m_ipDetectCounter(4)
|
||||
, m_ready(false)
|
||||
, m_firstUpdate(true)
|
||||
, m_textColor()
|
||||
, m_historyDirty(true)
|
||||
{
|
||||
// 从独立配置文件读取持久化的字体颜色
|
||||
// 设计原因:使用独立配置文件避免污染 dde-shell 的共享配置,
|
||||
@@ -139,6 +147,15 @@ QString NetworkMonitorApplet::ipv6Address() const
|
||||
return m_ipv6Address;
|
||||
}
|
||||
|
||||
// ---- 活动接口包统计 getter(累计值,自开机起)----
|
||||
// 供弹窗接口信息区展示收发包/错误/丢包数
|
||||
quint64 NetworkMonitorApplet::rxPackets() const { return m_rxPackets; }
|
||||
quint64 NetworkMonitorApplet::txPackets() const { return m_txPackets; }
|
||||
quint64 NetworkMonitorApplet::rxErrors() const { return m_rxErrors; }
|
||||
quint64 NetworkMonitorApplet::txErrors() const { return m_txErrors; }
|
||||
quint64 NetworkMonitorApplet::rxDropped() const { return m_rxDropped; }
|
||||
quint64 NetworkMonitorApplet::txDropped() const { return m_txDropped; }
|
||||
|
||||
// 返回插件版本号,从 dde-shell 插件元数据(metadata.json)读取
|
||||
// 版本唯一源为 CMakeLists.txt 的 project(VERSION),经 configure_file 写入 metadata.json,
|
||||
// dde-shell 加载时存入 DPluginMetaData,此处通过继承自 DApplet 的 pluginMetaData() 读取
|
||||
@@ -191,34 +208,42 @@ void NetworkMonitorApplet::setTextColor(const QString &color)
|
||||
}
|
||||
|
||||
// 返回当前活动接口的下行速度历史,转换为 QVariantList of QPointF 供 QML 使用
|
||||
// 设计原因:QPointF 仅携带 x/y 两个值,上传与下载分别对应两个列表
|
||||
// 设计原因:QPointF 仅携带 x/y 两个值,上传与下载分别对应两个列表;
|
||||
// 结果缓存于成员变量,仅当 m_historyDirty 时重建,避免 QML 每次读取都重新构造 300 个点
|
||||
QVariantList NetworkMonitorApplet::speedHistoryDownload() const
|
||||
{
|
||||
QVariantList list;
|
||||
if (m_activeInterface.isEmpty() || !m_speedHistory.contains(m_activeInterface)) {
|
||||
return list;
|
||||
if (m_historyDirty) {
|
||||
rebuildHistoryCache();
|
||||
}
|
||||
const QVector<SpeedSample> &samples = m_speedHistory[m_activeInterface];
|
||||
list.reserve(samples.size());
|
||||
for (const SpeedSample &s : samples) {
|
||||
list.append(QPointF(s.timestamp, s.downloadSpeed));
|
||||
}
|
||||
return list;
|
||||
return m_cachedHistoryDl;
|
||||
}
|
||||
|
||||
// 返回当前活动接口的上行速度历史,结构同 speedHistoryDownload
|
||||
QVariantList NetworkMonitorApplet::speedHistoryUpload() const
|
||||
{
|
||||
QVariantList list;
|
||||
if (m_activeInterface.isEmpty() || !m_speedHistory.contains(m_activeInterface)) {
|
||||
return list;
|
||||
if (m_historyDirty) {
|
||||
rebuildHistoryCache();
|
||||
}
|
||||
const QVector<SpeedSample> &samples = m_speedHistory[m_activeInterface];
|
||||
list.reserve(samples.size());
|
||||
for (const SpeedSample &s : samples) {
|
||||
list.append(QPointF(s.timestamp, s.uploadSpeed));
|
||||
return m_cachedHistoryUpload;
|
||||
}
|
||||
|
||||
// 重建上下行历史缓存(一次遍历同时填充两个列表)
|
||||
// 仅当 dirty 时由两个 getter 调用,避免重复构建;
|
||||
// 单 dirty 标记 + 一次重建双缓存,保证先后读取两个列表时数据一致
|
||||
void NetworkMonitorApplet::rebuildHistoryCache() const
|
||||
{
|
||||
m_cachedHistoryDl.clear();
|
||||
m_cachedHistoryUpload.clear();
|
||||
if (!m_activeInterface.isEmpty() && m_speedHistory.contains(m_activeInterface)) {
|
||||
const QVector<SpeedSample> &samples = m_speedHistory[m_activeInterface];
|
||||
m_cachedHistoryDl.reserve(samples.size());
|
||||
m_cachedHistoryUpload.reserve(samples.size());
|
||||
for (const SpeedSample &s : samples) {
|
||||
m_cachedHistoryDl.append(QPointF(s.timestamp, s.downloadSpeed));
|
||||
m_cachedHistoryUpload.append(QPointF(s.timestamp, s.uploadSpeed));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
m_historyDirty = false;
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::refresh()
|
||||
@@ -240,6 +265,19 @@ void NetworkMonitorApplet::setActiveInterface(const QString &interface)
|
||||
m_activeInterface = interface;
|
||||
m_firstUpdate = true;
|
||||
|
||||
// 接口切换后历史数据变化,标记脏缓存让趋势图立即重建
|
||||
m_historyDirty = true;
|
||||
|
||||
// 接口切换后包统计指向新接口,旧值不再有效,重置并通知 QML
|
||||
// 设计原因:包统计为各接口独立的累计值,切换后应显示新接口的数据
|
||||
m_rxPackets = 0;
|
||||
m_txPackets = 0;
|
||||
m_rxErrors = 0;
|
||||
m_txErrors = 0;
|
||||
m_rxDropped = 0;
|
||||
m_txDropped = 0;
|
||||
emit packetStatsChanged();
|
||||
|
||||
// 持久化到配置文件,下次启动自动加载用户选择的网卡
|
||||
// 设计原因:弹窗 chip 和设置窗口都调用此方法,统一持久化保证两处选择一致
|
||||
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)
|
||||
@@ -276,7 +314,8 @@ void NetworkMonitorApplet::readNetworkStats()
|
||||
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+)");
|
||||
// static const 仅首次调用时编译正则一次,后续复用,避免每秒对每行重复编译
|
||||
static const 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()) {
|
||||
@@ -357,8 +396,13 @@ void NetworkMonitorApplet::readNetworkStats()
|
||||
}
|
||||
|
||||
calculateSpeed();
|
||||
// 每次刷新都检测 IP,以应对 DHCP 续约等 IP 变更场景
|
||||
detectIpAddress();
|
||||
// IP 地址变化频率极低(DHCP 续约/网络切换等),降频至每 5 秒检测一次,
|
||||
// 减少每秒 QNetworkInterface::interfaceFromName 的系统查询开销
|
||||
// 计数器每次 refresh 递增,达到 5 时归零并执行检测
|
||||
if (++m_ipDetectCounter >= 5) {
|
||||
m_ipDetectCounter = 0;
|
||||
detectIpAddress();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::calculateSpeed()
|
||||
@@ -369,6 +413,13 @@ void NetworkMonitorApplet::calculateSpeed()
|
||||
const double elapsedSec = (nowMs - m_lastTimestampMs) / 1000.0;
|
||||
m_lastTimestampMs = nowMs;
|
||||
|
||||
// 记录旧值用于 dirty 比较:网络空闲时速度/总量可能不变,
|
||||
// 仅在真正变化时才发射信号,避免 QML 每秒无效重绘
|
||||
const double oldDownload = m_downloadSpeed;
|
||||
const double oldUpload = m_uploadSpeed;
|
||||
const qint64 oldTotalDownload = m_totalDownload;
|
||||
const qint64 oldTotalUpload = m_totalUpload;
|
||||
|
||||
// ---- 活动接口速度计算(用于任务栏显示和总量累加)----
|
||||
if (m_firstUpdate) {
|
||||
// 首次更新或接口切换后:仅记录基线,不计算速度
|
||||
@@ -393,6 +444,26 @@ void NetworkMonitorApplet::calculateSpeed()
|
||||
m_lastTxBytes = currentTxBytes;
|
||||
}
|
||||
|
||||
// ---- 活动接口包统计(累计值,自开机起)----
|
||||
// 直接从当前活动接口读取收发包/错误/丢包计数,任一值变化时发射 packetStatsChanged
|
||||
if (!m_activeInterface.isEmpty() && m_interfaces.contains(m_activeInterface)) {
|
||||
const NetworkInterface &iface = m_interfaces[m_activeInterface];
|
||||
if (m_rxPackets != static_cast<quint64>(iface.rxPackets)
|
||||
|| m_txPackets != static_cast<quint64>(iface.txPackets)
|
||||
|| m_rxErrors != static_cast<quint64>(iface.rxErrors)
|
||||
|| m_txErrors != static_cast<quint64>(iface.txErrors)
|
||||
|| m_rxDropped != static_cast<quint64>(iface.rxDropped)
|
||||
|| m_txDropped != static_cast<quint64>(iface.txDropped)) {
|
||||
m_rxPackets = static_cast<quint64>(iface.rxPackets);
|
||||
m_txPackets = static_cast<quint64>(iface.txPackets);
|
||||
m_rxErrors = static_cast<quint64>(iface.rxErrors);
|
||||
m_txErrors = static_cast<quint64>(iface.txErrors);
|
||||
m_rxDropped = static_cast<quint64>(iface.rxDropped);
|
||||
m_txDropped = static_cast<quint64>(iface.txDropped);
|
||||
emit packetStatsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 为所有接口采集速度历史(非仅活动接口)----
|
||||
// 设计原因:原仅采集活动接口,切换到非活动接口时趋势图为空需等待数分钟。
|
||||
// 改为遍历所有接口计算各自速度并追加历史,切换接口时趋势图立即有数据。
|
||||
@@ -426,11 +497,18 @@ void NetworkMonitorApplet::calculateSpeed()
|
||||
m_lastRxBytesByIface[name] = iface.rxBytes;
|
||||
m_lastTxBytesByIface[name] = iface.txBytes;
|
||||
}
|
||||
// 历史采样点已追加,标记脏缓存,QML 下次读取时重建
|
||||
m_historyDirty = true;
|
||||
emit speedHistoryChanged();
|
||||
}
|
||||
|
||||
emit speedChanged();
|
||||
emit totalChanged();
|
||||
// 仅在速度/总量实际变化时发射信号,避免空闲时每秒触发 QML 无效重绘
|
||||
if (m_downloadSpeed != oldDownload || m_uploadSpeed != oldUpload) {
|
||||
emit speedChanged();
|
||||
}
|
||||
if (m_totalDownload != oldTotalDownload || m_totalUpload != oldTotalUpload) {
|
||||
emit totalChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMonitorApplet::detectInterfaces()
|
||||
|
||||
@@ -57,6 +57,14 @@ class NetworkMonitorApplet : public DApplet
|
||||
Q_PROPERTY(QString ipAddress READ ipAddress NOTIFY ipAddressChanged)
|
||||
// 活动接口的 IPv6 全球地址(已过滤 link-local 和 loopback),供 QML 显示
|
||||
Q_PROPERTY(QString ipv6Address READ ipv6Address NOTIFY ipv6AddressChanged)
|
||||
// 活动接口的收发包/错误/丢包累计统计(自开机起累计值),供弹窗接口信息区展示
|
||||
// 全部用 quint64 类型,QML 侧可直接读取;任一值变化时统一发射 packetStatsChanged
|
||||
Q_PROPERTY(quint64 rxPackets READ rxPackets NOTIFY packetStatsChanged)
|
||||
Q_PROPERTY(quint64 txPackets READ txPackets NOTIFY packetStatsChanged)
|
||||
Q_PROPERTY(quint64 rxErrors READ rxErrors NOTIFY packetStatsChanged)
|
||||
Q_PROPERTY(quint64 txErrors READ txErrors NOTIFY packetStatsChanged)
|
||||
Q_PROPERTY(quint64 rxDropped READ rxDropped NOTIFY packetStatsChanged)
|
||||
Q_PROPERTY(quint64 txDropped READ txDropped NOTIFY packetStatsChanged)
|
||||
// 插件版本号,从 dde-shell 元数据读取,版本唯一源为 CMakeLists.txt project(VERSION)
|
||||
Q_PROPERTY(QString version READ version CONSTANT)
|
||||
// 任务栏网速数值字体颜色,空串表示跟随系统主题(由 QML 回退到 primaryText),
|
||||
@@ -84,6 +92,13 @@ public:
|
||||
QString activeInterface() const;
|
||||
QString ipAddress() const;
|
||||
QString ipv6Address() const;
|
||||
// 返回活动接口的收发包/错误/丢包累计值(自开机起),供弹窗接口信息区展示
|
||||
quint64 rxPackets() const;
|
||||
quint64 txPackets() const;
|
||||
quint64 rxErrors() const;
|
||||
quint64 txErrors() const;
|
||||
quint64 rxDropped() const;
|
||||
quint64 txDropped() const;
|
||||
// 返回当前活动接口的下行速度历史(QPointF 列表),供 QML 趋势图绘制
|
||||
QVariantList speedHistoryDownload() const;
|
||||
// 返回当前活动接口的上行速度历史(QPointF 列表),供 QML 趋势图绘制
|
||||
@@ -108,6 +123,8 @@ signals:
|
||||
void activeInterfaceChanged();
|
||||
void ipAddressChanged();
|
||||
void ipv6AddressChanged();
|
||||
// 活动接口包统计(收发包/错误/丢包)变化时通知 QML 更新
|
||||
void packetStatsChanged();
|
||||
void speedHistoryChanged();
|
||||
// 任务栏网速字体颜色变化时通知 QML 更新
|
||||
void textColorChanged();
|
||||
@@ -147,6 +164,19 @@ private:
|
||||
// 总量
|
||||
qint64 m_totalDownload;
|
||||
qint64 m_totalUpload;
|
||||
|
||||
// 活动接口包统计缓存(自开机起累计值),供弹窗接口信息区展示
|
||||
// 设计原因:/proc/net/dev 已解析包计数但此前直接丢弃,现缓存并暴露给 QML
|
||||
quint64 m_rxPackets;
|
||||
quint64 m_txPackets;
|
||||
quint64 m_rxErrors;
|
||||
quint64 m_txErrors;
|
||||
quint64 m_rxDropped;
|
||||
quint64 m_txDropped;
|
||||
|
||||
// IP 地址检测降频计数器:IP 变化频率极低,每 5 次 refresh 才检测一次
|
||||
// 初始化 4 使首次 refresh 立即检测,避免启动后 IP 显示延迟
|
||||
int m_ipDetectCounter;
|
||||
|
||||
bool m_ready;
|
||||
bool m_firstUpdate;
|
||||
@@ -160,6 +190,16 @@ private:
|
||||
QHash<QString, QVector<SpeedSample>> m_speedHistory;
|
||||
// 每个接口最多保留的采样点数:5 分钟 × 60 秒 = 300
|
||||
static constexpr int MAX_HISTORY_SAMPLES = 300;
|
||||
|
||||
// 历史 QVariantList 缓存:避免 QML 每次读取都重新构造 300 个 QPointF
|
||||
// 设计原因:Canvas 每秒读取一次 + hover 时也读取,逐个 append 开销可观;
|
||||
// 用 dirty 标记懒重建,仅当追加采样点或切换接口后重建一次
|
||||
// 注意:getter 为 const,故缓存与 dirty 标记声明为 mutable
|
||||
mutable QVariantList m_cachedHistoryDl;
|
||||
mutable QVariantList m_cachedHistoryUpload;
|
||||
mutable bool m_historyDirty;
|
||||
// 重建历史缓存:仅当 m_historyDirty 时由两个 getter 调用
|
||||
void rebuildHistoryCache() const;
|
||||
};
|
||||
|
||||
DS_END_NAMESPACE
|
||||
@@ -79,7 +79,22 @@
|
||||
<message>
|
||||
<location filename="../package/components/NetworkPopup.qml" line="427"/>
|
||||
<source>Total</source>
|
||||
<translation>总计</translation>
|
||||
<translation>流量总计</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/components/NetworkPopup.qml" line="474"/>
|
||||
<source>Packets</source>
|
||||
<translation>包统计</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/components/NetworkPopup.qml" line="483"/>
|
||||
<source>Errors</source>
|
||||
<translation>错误</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/components/NetworkPopup.qml" line="494"/>
|
||||
<source>Dropped</source>
|
||||
<translation>丢包</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
Reference in New Issue
Block a user