Files
JNetApplet/src/networkmonitorapplet.h
T

320 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2026 Jokul
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include <applet.h>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <QVariantList>
#include <QMap>
#include <QHash>
#include <QPointF>
#include <QDateTime>
#include <QColor>
#include <QSettings>
#include <QJsonObject>
#include <QMetaType>
#include <QVariantMap>
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; // 发送丢弃数
};
// 单个速度采样点:用于趋势图的环形缓冲
// timestamp: 自 epoch 的秒级时间戳,作为 X 轴定位
// downloadSpeed / uploadSpeed: 当时的瞬时速度(字节/秒),作为两条折线的 Y 值
struct SpeedSample {
qint64 timestamp;
double downloadSpeed;
double uploadSpeed;
};
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(bool ready READ ready NOTIFY readyChanged)
// 当前活动接口,用户在弹窗或设置窗口选择后持久化到 ~/.config/jnetapplet/settings.ini,
// 下次启动自动恢复;若保存的接口已不存在则回退到自动选择
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)
// 活动接口的收发包/错误/丢包累计统计(自开机起累计值),供弹窗接口信息区展示
// 全部用 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)
// 活动接口的链路协商速率(Mbps):有线读 /sys/class/net/<iface>/speed,
// 无线读失败时降级解析 `iw dev link` 的 bitrate;0 表示不可用
Q_PROPERTY(int linkSpeed READ linkSpeed NOTIFY linkSpeedChanged)
// 刷新间隔(毫秒),默认 1000,用户在设置窗口选择后持久化并即时生效
Q_PROPERTY(int refreshInterval READ refreshInterval WRITE setRefreshInterval NOTIFY refreshIntervalChanged)
// 当前活动接口的 TCP ESTABLISHED 连接数,供弹窗展示
Q_PROPERTY(int tcpConnections READ tcpConnections NOTIFY tcpConnectionsChanged)
// 活动接口的 WiFi 信号强度(dBm,负值如 -65),非无线接口或不可用时为 0
Q_PROPERTY(int wifiSignal READ wifiSignal NOTIFY wifiSignalChanged)
// 插件版本号,从 dde-shell 元数据读取,版本唯一源为 CMakeLists.txt project(VERSION)
Q_PROPERTY(QString version READ version CONSTANT)
// 任务栏网速数值字体颜色,空串表示跟随系统主题(由 QML 回退到 primaryText),
// 用户在设置窗口选择后持久化到 ~/.config/jnetapplet/settings.ini
Q_PROPERTY(QString textColor READ textColor WRITE setTextColor NOTIFY textColorChanged)
// 当前活动接口的下行速度历史采样点(QPointF 列表:x=时间戳秒, y=速度 bytes/sec)
// 用于趋势图绘制下载折线,每次 calculateSpeed 追加一个点并发射 speedHistoryChanged
Q_PROPERTY(QVariantList speedHistoryDownload READ speedHistoryDownload NOTIFY speedHistoryChanged)
// 当前活动接口的上行速度历史采样点,结构同 speedHistoryDownload
Q_PROPERTY(QVariantList speedHistoryUpload READ speedHistoryUpload NOTIFY speedHistoryChanged)
// 流量统计日志(JSON 对象),供 QML 流量统计窗口读取展示
// 结构:{"byDay": {日期: {接口: {rx, tx}}}, "byMonth": {月份: {接口: {rx, tx}}}}
// 设计原因:会话总量重启清零,用户无法了解长期趋势;按日/月聚合让用户看
// 每日使用量和月度汇总,按接口分让多网卡各自独立统计。
// 保存频率:累加每秒执行(内存操作),写盘与通知 QML 降频(每 30 秒一次),
// 避免每秒磁盘 IO 与 QML 重绘
Q_PROPERTY(QJsonObject trafficLog READ trafficLog NOTIFY trafficLogChanged)
// TCP 连接详情列表,供 QML 连接清单窗口展示
// 每项为 QVariantMap(键:localAddress/localPort/remoteAddress/remotePort/state/processName)
// 仅包含 ESTABLISHED 状态连接,含进程名反查
// 设计原因:用 ss 命令反查进程名,通过 netlink 获取不受 /proc 权限限制,
// 但仍需启动子进程开销较大,故降频至每 5 秒更新一次
Q_PROPERTY(QVariantList tcpConnectionList READ tcpConnectionList NOTIFY tcpConnectionListChanged)
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;
bool ready() const;
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;
// 返回活动接口的链路协商速率(Mbps),0 表示不可用
int linkSpeed() const;
// 返回刷新间隔(毫秒)
int refreshInterval() const;
// 设置刷新间隔(毫秒),校验后持久化并即时生效
void setRefreshInterval(int ms);
// 返回当前活动接口的 TCP ESTABLISHED 连接数
int tcpConnections() const;
// 返回活动接口的 WiFi 信号强度(dBm),0 表示不可用
int wifiSignal() const;
// 返回当前活动接口的下行速度历史(QPointF 列表),供 QML 趋势图绘制
QVariantList speedHistoryDownload() const;
// 返回当前活动接口的上行速度历史(QPointF 列表),供 QML 趋势图绘制
QVariantList speedHistoryUpload() const;
// 返回插件版本号,从 dde-shell 插件元数据(metadata.json)读取
QString version() const;
// 返回任务栏网速字体颜色,空串表示跟随系统主题
QString textColor() const;
// 设置任务栏网速字体颜色,空串表示跟随系统主题
void setTextColor(const QString &color);
// 返回流量统计日志(JSON 对象),结构见 trafficLog 属性注释,供 QML 流量统计窗口读取
QJsonObject trafficLog() const;
// 返回 TCP 连接详情列表(QVariantList of QVariantMap),供 QML 连接清单窗口展示
QVariantList tcpConnectionList() const;
// 定时器回调,由 m_refreshTimer 每秒调用。非 Q_INVOKABLE(QML 不应直接调用,
// 否则会打破 1 秒定时间隔导致速度计算失真)
void refresh();
Q_INVOKABLE void setActiveInterface(const QString &interface);
signals:
void speedChanged();
void totalChanged();
void interfacesChanged();
void readyChanged();
void activeInterfaceChanged();
void ipAddressChanged();
void ipv6AddressChanged();
// 活动接口包统计(收发包/错误/丢包)变化时通知 QML 更新
void packetStatsChanged();
// 活动接口链路速率变化时通知 QML 更新
void linkSpeedChanged();
// 刷新间隔变化时通知 QML 更新
void refreshIntervalChanged();
// TCP ESTABLISHED 连接数变化时通知 QML 更新
void tcpConnectionsChanged();
// WiFi 信号强度变化时通知 QML 更新
void wifiSignalChanged();
void speedHistoryChanged();
// 任务栏网速字体颜色变化时通知 QML 更新
void textColorChanged();
// 流量统计日志变化时通知 QML 更新(降频,每 30 秒发射一次)
void trafficLogChanged();
// TCP 连接详情列表变化时通知 QML 更新(降频,每 5 秒发射一次)
void tcpConnectionListChanged();
private:
void readNetworkStats();
void calculateSpeed();
void detectInterfaces();
// 检测活动接口的 IPv4 与 IPv6 地址,变化时分别发射对应信号
void detectIpAddress();
// 低频统计检测:链路速率与 WiFi 信号(每 5 秒),变化时发射对应信号
void detectLowFreqStats();
// 读取指定接口的链路协商速率(Mbps),无线读 speed 失败时降级解析 iw bitrate
int detectLinkSpeed(const QString &iface);
// 解析 `iw dev <iface> link` 输出中的 bitrate(Mbps),失败返回 0
int parseIwBitrate(const QString &output) const;
// 统计 /proc/net/tcp 与 tcp6 中 ESTABLISHED(状态 01)的连接数
int countTcpConnections() const;
// 构建 TCP 连接详情列表(调用 ss 命令获取 ESTABLISHED 连接,含进程名反查)
// 结果存入 m_tcpConnectionList 并发射 tcpConnectionListChanged;仅 ESTABLISHED
void buildTcpConnectionList();
// 读取指定接口的 WiFi 信号强度(dBm),非无线接口或不可用时返回 0
int detectWifiSignal(const QString &iface);
// 判断是否为物理网卡(无线 wlp/wlan,有线 enp/eth),
// 用于自动选择时优先真实网卡而非虚拟代理接口(如 Meta/tun0)
bool isPhysicalInterface(const QString &name) const;
qint64 getActiveRxBytes() const;
qint64 getActiveTxBytes() const;
// ---- 流量日志(日/月持久化)----
// 启动时从 traffic_log.json 加载日志;文件不存在或解析失败时降级为空结构,不崩溃
void loadTrafficLog();
// 将日志写入 traffic_log.json(先裁剪超期记录再写盘),供析构与降频保存调用
void saveTrafficLog();
// 将本次流量增量累加到当日/当月/当前活动接口的日志记录中(每秒调用,内存操作)
void appendToTrafficLog(qint64 rxDelta, qint64 txDelta);
// 裁剪超期记录:按日最多 90 天、按月最多 24 个月,超出删除最旧记录
void pruneTrafficLog();
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_lastTimestampMs;
// 当前瞬时速度(字节/秒),用 double 存储以保留按真实间隔除算的小数精度
double m_downloadSpeed;
double m_uploadSpeed;
// 每个接口的上次收发字节数,用于为所有接口(非仅活动接口)计算速度并采集历史
// 设计原因:原仅跟踪活动接口,切换到非活动接口时趋势图为空需等待数分钟才有数据
QHash<QString, qint64> m_lastRxBytesByIface;
QHash<QString, qint64> m_lastTxBytesByIface;
// 总量
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;
// 活动接口链路协商速率(Mbps),0 表示不可用
int m_linkSpeed;
// 刷新间隔(毫秒),默认 1000,可配置并持久化
int m_refreshInterval;
// 当前活动接口的 TCP ESTABLISHED 连接数
int m_tcpConnections;
// 活动接口的 WiFi 信号强度(dBm),0 表示不可用
int m_wifiSignal;
// 低频统计检测计数器:链路速率/WiFi 信号每 5 次 refresh 检测一次
// 初始化 4 使首次 refresh 立即检测
int m_lowFreqCounter;
bool m_ready;
bool m_firstUpdate;
// 任务栏网速字体颜色,空串表示跟随系统主题
// 设计原因:空串作为"跟随系统"的语义值,QML 层用 textColor ? textColor : primaryText
// 做回退,避免后端硬编码系统色
QString m_textColor;
// 速度历史环形缓冲:每个接口独立维护一份,key 为接口名
// 设计原因:接口切换时趋势图不出现跳变,切回时仍能看到该接口历史
QHash<QString, QVector<SpeedSample>> m_speedHistory;
// 每个接口最多保留的采样点数:30 分钟 × 60 秒 = 1800
// 设计原因:流量波动图支持 1/5/30 分钟时间窗口切换,需在展示层选择显示范围,
// 故后端需存够 30 分钟的历史数据;时间窗口选择是纯 QML 展示层行为,后端只需
// 提供足够长的历史缓冲,无需新增 Q_PROPERTY
static constexpr int MAX_HISTORY_SAMPLES = 1800;
// 历史 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;
// ---- 流量日志成员 ----
// 完整流量日志 JSON:{"byDay": {日期: {接口: {rx, tx}}}, "byMonth": {月份: {接口: {rx, tx}}}}
// 设计原因:会话总量重启清零,用户无法了解长期趋势;按日/月聚合让用户看
// 每日使用量和月度汇总,按接口分让多网卡各自独立统计
QJsonObject m_trafficLog;
// 日志文件路径(~/.config/jnetapplet/traffic_log.json)
QString m_trafficLogPath;
// 当前日期 "yyyy-MM-dd" 与月份 "yyyy-MM",用于跨日/跨月检测
QString m_currentDate;
QString m_currentMonth;
// 降频保存计数器:每 30 秒(按刷新间隔折算)写盘一次并通知 QML,
// 避免每秒磁盘 IO 与 QML 重绘;析构函数再兜底保存一次
int m_trafficSaveCounter;
// 按日记录最多保留 90 天、按月最多保留 24 个月,超出自动裁剪最旧记录
static constexpr int MAX_DAY_ENTRIES = 90;
static constexpr int MAX_MONTH_ENTRIES = 24;
// ---- TCP 连接清单成员 ----
// TCP 连接详情列表(QVariantList of QVariantMap),供 QML 连接清单窗口读取
// 设计原因:QVariantMap 便于 QML 以 JS 对象直接访问各字段,
// 不暴露中间结构体(需注册元类型,QML 访问繁琐)
QVariantList m_tcpConnectionList;
// TCP 连接清单降频计数器:ss 命令需启动子进程开销大,每 5 次 refresh 更新一次
// 初始化 4 使首次 refresh 立即更新
int m_tcpListCounter = 4;
};
DS_END_NAMESPACE