f7c6801793
- 新增设置窗口(右键打开):无边框毛玻璃风格,温度告警开关,QSettings持久化 - 合并温度趋势图:多GPU多色折线,图例,Y轴刻度,填充区域,最新温度标注 - 显卡排序:核显在前独显在后(std::stable_sort by boot_vga) - 卡片优化:删除温度行和频率/功耗行,驱动名+版本两端对齐,高度压缩至78 - 弹窗优化:宽度480,右上角刷新图标替代底部按钮 - tooltip修复:删除自定义Rectangle tooltip改用标准ToolTip - 翻译更新:设置/常规设置/温度告警/温度趋势等
663 lines
22 KiB
C++
663 lines
22 KiB
C++
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
|
||
//
|
||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
||
#include "graphicsdriverapplet.h"
|
||
#include <pluginfactory.h>
|
||
|
||
#include <QProcess>
|
||
#include <QDir>
|
||
#include <QDebug>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
#include <QRegularExpression>
|
||
#include <QFileInfo>
|
||
#include <QSettings>
|
||
#include <algorithm>
|
||
#include <QDateTime>
|
||
#include <QDBusInterface>
|
||
#include <QDBusMessage>
|
||
#include <QVariantList>
|
||
|
||
DCORE_USE_NAMESPACE
|
||
|
||
DS_BEGIN_NAMESPACE
|
||
|
||
GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent)
|
||
: DApplet(parent)
|
||
, m_ready(false)
|
||
, m_lastAlertTime(0)
|
||
, m_tempAlertEnabled(true)
|
||
{
|
||
QSettings settings("deepin", "graphics-driver-applet");
|
||
m_tempAlertEnabled = settings.value("tempAlertEnabled", true).toBool();
|
||
}
|
||
|
||
GraphicsDriverApplet::~GraphicsDriverApplet()
|
||
{
|
||
}
|
||
|
||
bool GraphicsDriverApplet::load()
|
||
{
|
||
return DApplet::load();
|
||
}
|
||
|
||
bool GraphicsDriverApplet::init()
|
||
{
|
||
detectGpus();
|
||
return DApplet::init();
|
||
}
|
||
|
||
QString GraphicsDriverApplet::deviceInfo() const
|
||
{
|
||
return m_deviceInfo;
|
||
}
|
||
|
||
QString GraphicsDriverApplet::currentDriver() const
|
||
{
|
||
return m_currentDriver;
|
||
}
|
||
|
||
QString GraphicsDriverApplet::gpuSummary() const
|
||
{
|
||
return m_gpuSummary;
|
||
}
|
||
|
||
bool GraphicsDriverApplet::ready() const
|
||
{
|
||
return m_ready;
|
||
}
|
||
|
||
QStringList GraphicsDriverApplet::gpuStats() const
|
||
{
|
||
return m_gpuStats;
|
||
}
|
||
|
||
QString GraphicsDriverApplet::gpuMode() const
|
||
{
|
||
return m_gpuMode;
|
||
}
|
||
|
||
QStringList GraphicsDriverApplet::gpuPrimary() const
|
||
{
|
||
return m_gpuPrimary;
|
||
}
|
||
|
||
QVariantList GraphicsDriverApplet::tempHistory() const
|
||
{
|
||
QVariantList result;
|
||
for (const QList<int> &history : m_tempHistory) {
|
||
QVariantList gpuHistory;
|
||
for (int temp : history) {
|
||
gpuHistory << temp;
|
||
}
|
||
result << QVariant(gpuHistory);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
QStringList GraphicsDriverApplet::gpuProcesses() const
|
||
{
|
||
return m_gpuProcesses;
|
||
}
|
||
|
||
bool GraphicsDriverApplet::tempAlertEnabled() const
|
||
{
|
||
return m_tempAlertEnabled;
|
||
}
|
||
|
||
void GraphicsDriverApplet::setTempAlertEnabled(bool enabled)
|
||
{
|
||
if (m_tempAlertEnabled == enabled) return;
|
||
m_tempAlertEnabled = enabled;
|
||
QSettings settings("deepin", "graphics-driver-applet");
|
||
settings.setValue("tempAlertEnabled", enabled);
|
||
emit tempAlertEnabledChanged();
|
||
}
|
||
|
||
void GraphicsDriverApplet::refreshDeviceInfo()
|
||
{
|
||
detectGpus();
|
||
}
|
||
|
||
void GraphicsDriverApplet::refreshStats()
|
||
{
|
||
readGpuStats();
|
||
}
|
||
|
||
void GraphicsDriverApplet::detectGpus()
|
||
{
|
||
m_gpus.clear();
|
||
m_tempHistory.clear();
|
||
|
||
// 通过 lspci 检测显卡
|
||
QStringList lines = runCommand("lspci", {"-mm"});
|
||
for (const QString &line : lines) {
|
||
if (line.contains("VGA compatible controller", Qt::CaseInsensitive) ||
|
||
line.contains("3D controller", Qt::CaseInsensitive) ||
|
||
line.contains("Display controller", Qt::CaseInsensitive)) {
|
||
|
||
GpuInfo gpu;
|
||
// 初始化统计字段
|
||
gpu.drmCard = -1;
|
||
gpu.isPrimary = false;
|
||
gpu.temperature = -1;
|
||
gpu.gpuUsage = -1;
|
||
gpu.memUsage = -1;
|
||
gpu.memTotal = -1;
|
||
gpu.memUsed = -1;
|
||
gpu.gpuClock = -1;
|
||
gpu.memClock = -1;
|
||
gpu.powerDraw = -1;
|
||
gpu.fanSpeed = -1;
|
||
|
||
// lspci -mm 格式: "Class" "Vendor" "Device" ...
|
||
// 提取 PCI ID(行首的 01:00.0 等)
|
||
int spaceIdx = line.indexOf(' ');
|
||
if (spaceIdx > 0) {
|
||
gpu.pciId = line.left(spaceIdx);
|
||
}
|
||
|
||
gpu.name = parseGpuName(line);
|
||
|
||
// 检测驱动:通过 symlink 目标获取真实驱动名
|
||
QString sysPath = "/sys/bus/pci/devices/0000:" + gpu.pciId + "/driver";
|
||
QFileInfo driverLink(sysPath);
|
||
if (driverLink.exists() && driverLink.isSymLink()) {
|
||
QString target = driverLink.symLinkTarget();
|
||
// symLinkTarget 返回如 ".../drivers/nvidia",提取最后一段
|
||
gpu.driver = target.section('/', -1);
|
||
|
||
// 读取驱动版本
|
||
gpu.driverVersion = readDriverVersion(gpu.driver);
|
||
} else {
|
||
gpu.driver = "unknown";
|
||
}
|
||
|
||
// 查找 DRM 卡号
|
||
gpu.drmCard = findDrmCard(gpu.pciId);
|
||
|
||
// 读取 boot_vga 判断主副显
|
||
QFile bootVgaFile("/sys/bus/pci/devices/0000:" + gpu.pciId + "/boot_vga");
|
||
if (bootVgaFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.isPrimary = QTextStream(&bootVgaFile).readAll().trimmed() == "1";
|
||
}
|
||
|
||
m_gpus.append(gpu);
|
||
}
|
||
}
|
||
|
||
// 排序:核显(isPrimary)排在前面
|
||
std::stable_sort(m_gpus.begin(), m_gpus.end(), [](const GpuInfo &a, const GpuInfo &b) {
|
||
return a.isPrimary > b.isPrimary;
|
||
});
|
||
|
||
// 生成摘要信息
|
||
QStringList parts;
|
||
QStringList driverParts;
|
||
for (const GpuInfo &gpu : m_gpus) {
|
||
QString info = gpu.name;
|
||
if (!gpu.driver.isEmpty() && gpu.driver != "unknown") {
|
||
info += " (" + gpu.driver;
|
||
if (!gpu.driverVersion.isEmpty()) {
|
||
info += " " + gpu.driverVersion;
|
||
}
|
||
info += ")";
|
||
}
|
||
parts << info;
|
||
if (!gpu.driver.isEmpty() && !driverParts.contains(gpu.driver)) {
|
||
driverParts << gpu.driver;
|
||
}
|
||
}
|
||
|
||
m_deviceInfo = parts.join("\n");
|
||
m_currentDriver = driverParts.join(", ");
|
||
m_gpuSummary = parts.join(" | ");
|
||
m_ready = !m_gpus.isEmpty();
|
||
|
||
// 生成主副显标识
|
||
m_gpuPrimary.clear();
|
||
for (const GpuInfo &gpu : m_gpus) {
|
||
m_gpuPrimary << (gpu.isPrimary ? "true" : "false");
|
||
}
|
||
|
||
qDebug() << "Detected GPUs:" << m_deviceInfo;
|
||
qDebug() << "Current drivers:" << m_currentDriver;
|
||
|
||
emit deviceInfoChanged();
|
||
emit currentDriverChanged();
|
||
emit gpuSummaryChanged();
|
||
emit gpuStatsChanged();
|
||
emit gpuPrimaryChanged();
|
||
emit readyChanged();
|
||
|
||
// 读取实时统计信息
|
||
readGpuStats();
|
||
|
||
// 检测 GPU 切换模式
|
||
detectGpuMode();
|
||
}
|
||
|
||
QStringList GraphicsDriverApplet::runCommand(const QString &cmd, const QStringList &args)
|
||
{
|
||
QProcess process;
|
||
process.start(cmd, args);
|
||
process.waitForFinished(5000);
|
||
QString output = QString::fromUtf8(process.readAllStandardOutput());
|
||
return output.split('\n', Qt::SkipEmptyParts);
|
||
}
|
||
|
||
QString GraphicsDriverApplet::parseGpuName(const QString &line)
|
||
{
|
||
// lspci -mm 格式示例:
|
||
// 01:00.0 "VGA compatible controller" "NVIDIA Corporation" "GA106M [GeForce RTX 3060 Mobile / Max-Q]" "ASUSTeK Computer Inc." "Device 10a2"
|
||
// 提取引号中的内容
|
||
QStringList parts;
|
||
int pos = 0;
|
||
while (pos < line.length()) {
|
||
int start = line.indexOf('"', pos);
|
||
if (start < 0) break;
|
||
int end = line.indexOf('"', start + 1);
|
||
if (end < 0) break;
|
||
parts << line.mid(start + 1, end - start - 1);
|
||
pos = end + 1;
|
||
}
|
||
|
||
if (parts.size() >= 3) {
|
||
// parts[0] = class, parts[1] = vendor, parts[2] = device
|
||
QString vendor = parts[1];
|
||
QString device = parts[2];
|
||
// 去掉方括号内的别名,保留主要名称
|
||
int bracket = device.indexOf('[');
|
||
if (bracket > 0) {
|
||
// 保留完整名称,方括号内是子系统信息
|
||
}
|
||
return vendor + " " + device;
|
||
}
|
||
|
||
return line;
|
||
}
|
||
|
||
QString GraphicsDriverApplet::readDriverVersion(const QString &driverName)
|
||
{
|
||
// 尝试从 /sys/module/<driver>/version 读取版本
|
||
QString path = "/sys/module/" + driverName + "/version";
|
||
QFile file(path);
|
||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QString version = QTextStream(&file).readAll().trimmed();
|
||
if (!version.isEmpty()) {
|
||
return version;
|
||
}
|
||
}
|
||
|
||
// nvidia 特殊处理:从 /proc/driver/nvidia/version 读取
|
||
if (driverName == "nvidia") {
|
||
QFile nvFile("/proc/driver/nvidia/version");
|
||
if (nvFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QString content = QTextStream(&nvFile).readAll();
|
||
// 格式: NVRM version: NVIDIA UNIX x86_64 Kernel Module 535.183.01 ...
|
||
QRegularExpression re("Kernel Module\\s+(\\d+\\.\\d+\\.\\d+)");
|
||
auto match = re.match(content);
|
||
if (match.hasMatch()) {
|
||
return match.captured(1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 开源内核驱动(如 amdgpu, radeon, nouveau, i915)无独立版本号,版本跟随内核
|
||
static const QStringList kernelDrivers = {
|
||
"amdgpu", "radeon", "nouveau", "i915", "i965", "mgag200", "ast", "vmwgfx"
|
||
};
|
||
if (kernelDrivers.contains(driverName)) {
|
||
QFile osrelease("/proc/sys/kernel/osrelease");
|
||
if (osrelease.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
return "Linux " + QTextStream(&osrelease).readAll().trimmed();
|
||
}
|
||
}
|
||
|
||
return QString();
|
||
}
|
||
|
||
void GraphicsDriverApplet::detectGpuMode()
|
||
{
|
||
// 单 GPU
|
||
if (m_gpus.size() <= 1) {
|
||
m_gpuMode = "Solo";
|
||
emit gpuModeChanged();
|
||
return;
|
||
}
|
||
|
||
// 多 GPU:检测 PRIME / Bumblebee
|
||
// PRIME: nvidia-drm-outputclass.conf 存在
|
||
if (QFile::exists("/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf") ||
|
||
QFile::exists("/etc/X11/xorg.conf.d/70-nvidia.conf")) {
|
||
m_gpuMode = "PRIME";
|
||
emit gpuModeChanged();
|
||
return;
|
||
}
|
||
|
||
// Bumblebee: 检查 bumblebeed 服务
|
||
QStringList lsmod = runCommand("lsmod", {});
|
||
bool bumblebeeLoaded = false;
|
||
for (const QString &line : lsmod) {
|
||
if (line.contains("bumblebee", Qt::CaseInsensitive)) {
|
||
bumblebeeLoaded = true;
|
||
break;
|
||
}
|
||
}
|
||
if (bumblebeeLoaded || QFile::exists("/usr/sbin/bumblebeed")) {
|
||
m_gpuMode = "Bumblebee";
|
||
emit gpuModeChanged();
|
||
return;
|
||
}
|
||
|
||
// 多 GPU 但无已知切换方案
|
||
m_gpuMode = "Hybrid";
|
||
emit gpuModeChanged();
|
||
}
|
||
|
||
int GraphicsDriverApplet::findDrmCard(const QString &pciId)
|
||
{
|
||
// 扫描 /sys/bus/pci/devices/0000:XX:XX.X/drm/ 目录,找到 cardN 条目
|
||
QString drmPath = "/sys/bus/pci/devices/0000:" + pciId + "/drm";
|
||
QDir dir(drmPath);
|
||
if (!dir.exists()) return -1;
|
||
|
||
QStringList entries = dir.entryList(QStringList() << "card*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||
for (const QString &entry : entries) {
|
||
// 匹配 "card0" "card1" 等,排除 "card0-DP-1" 等连接器
|
||
QRegularExpression re("^card(\\d+)$");
|
||
auto match = re.match(entry);
|
||
if (match.hasMatch()) {
|
||
return match.captured(1).toInt();
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
void GraphicsDriverApplet::readGpuStats()
|
||
{
|
||
// 先处理 NVIDIA GPU(单次 nvidia-smi 调用获取所有 NVIDIA GPU 数据)
|
||
// nvidia-smi 输出格式: pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,
|
||
// memory.total,memory.used,clocks.gr,clocks.mem,power.draw,fan.speed
|
||
QMap<QString, QStringList> nvidiaData; // pciId -> stats
|
||
QStringList nvLines = runCommand("nvidia-smi", {
|
||
"--query-gpu=pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used,clocks.gr,clocks.mem,power.draw,fan.speed",
|
||
"--format=csv,noheader,nounits"
|
||
});
|
||
for (const QString &line : nvLines) {
|
||
QStringList parts = line.split(",");
|
||
if (parts.size() >= 6) {
|
||
// nvidia-smi pci.bus_id 格式: 00000000:01:00.0,提取后半部分匹配
|
||
QString busId = parts[0].trimmed();
|
||
QString shortId = busId.section(':', -2); // "01:00.0"
|
||
nvidiaData[shortId] = parts;
|
||
}
|
||
}
|
||
|
||
// 逐个 GPU 读取统计
|
||
for (GpuInfo &gpu : m_gpus) {
|
||
// 每次刷新前重置实时统计字段,确保数据源不可用时不会残留旧值
|
||
gpu.temperature = -1;
|
||
gpu.gpuUsage = -1;
|
||
gpu.memUsage = -1;
|
||
gpu.memTotal = -1;
|
||
gpu.memUsed = -1;
|
||
gpu.gpuClock = -1;
|
||
gpu.memClock = -1;
|
||
gpu.powerDraw = -1;
|
||
gpu.fanSpeed = -1;
|
||
|
||
if (gpu.driver == "nvidia" && nvidiaData.contains(gpu.pciId)) {
|
||
// NVIDIA: 使用 nvidia-smi 数据
|
||
QStringList &parts = nvidiaData[gpu.pciId];
|
||
gpu.temperature = parts[1].trimmed().toInt();
|
||
gpu.gpuUsage = parts[2].trimmed().toInt();
|
||
gpu.memUsage = parts[3].trimmed().toInt();
|
||
gpu.memTotal = parts[4].trimmed().toInt();
|
||
gpu.memUsed = parts[5].trimmed().toInt();
|
||
// 频率 (parts[6]=GPU, parts[7]=显存)
|
||
if (parts.size() > 6) gpu.gpuClock = parts[6].trimmed().toInt();
|
||
if (parts.size() > 7) gpu.memClock = parts[7].trimmed().toInt();
|
||
// 功耗 (parts[8]),[N/A] 时保持 -1
|
||
if (parts.size() > 8) {
|
||
QString pwr = parts[8].trimmed();
|
||
if (pwr != "[N/A]" && !pwr.isEmpty()) gpu.powerDraw = pwr.toDouble();
|
||
}
|
||
// 风扇转速 (parts[9]),[N/A] 时保持 -1
|
||
if (parts.size() > 9) {
|
||
QString fan = parts[9].trimmed();
|
||
if (fan != "[N/A]" && !fan.isEmpty()) gpu.fanSpeed = fan.toInt();
|
||
}
|
||
} else if (gpu.drmCard >= 0) {
|
||
// AMD / Intel / 其他: 使用 sysfs
|
||
QString cardPath = QString("/sys/class/drm/card%1/device").arg(gpu.drmCard);
|
||
|
||
// GPU 使用率
|
||
QFile busyFile(cardPath + "/gpu_busy_percent");
|
||
if (busyFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.gpuUsage = QTextStream(&busyFile).readAll().trimmed().toInt();
|
||
}
|
||
|
||
// 显存 (字节,转换为 MB)
|
||
QFile vramTotal(cardPath + "/mem_info_vram_total");
|
||
QFile vramUsed(cardPath + "/mem_info_vram_used");
|
||
if (vramTotal.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.memTotal = QTextStream(&vramTotal).readAll().trimmed().toLongLong() / 1024 / 1024;
|
||
}
|
||
if (vramUsed.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.memUsed = QTextStream(&vramUsed).readAll().trimmed().toLongLong() / 1024 / 1024;
|
||
}
|
||
if (gpu.memTotal > 0 && gpu.memUsed >= 0) {
|
||
gpu.memUsage = gpu.memUsed * 100 / gpu.memTotal;
|
||
}
|
||
|
||
// 温度: 查找 hwmon 子目录
|
||
QDir hwmonDir(cardPath + "/hwmon");
|
||
if (hwmonDir.exists()) {
|
||
QStringList hwmons = hwmonDir.entryList(QStringList() << "hwmon*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||
for (const QString &hwmon : hwmons) {
|
||
QFile nameFile(hwmonDir.absoluteFilePath(hwmon + "/name"));
|
||
if (nameFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QString name = QTextStream(&nameFile).readAll().trimmed();
|
||
if (name == gpu.driver || name == "amdgpu" || name == "radeon") {
|
||
QString hwmonPath = hwmonDir.absoluteFilePath(hwmon);
|
||
// 温度
|
||
QFile tempFile(hwmonPath + "/temp1_input");
|
||
if (tempFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
int tempMilli = QTextStream(&tempFile).readAll().trimmed().toInt();
|
||
gpu.temperature = tempMilli / 1000;
|
||
}
|
||
// 功耗 (µW -> W)
|
||
QFile powerFile(hwmonPath + "/power1_average");
|
||
if (powerFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
qint64 microWatts = QTextStream(&powerFile).readAll().trimmed().toLongLong();
|
||
gpu.powerDraw = microWatts / 1000000.0;
|
||
}
|
||
// 风扇转速 (RPM)
|
||
QFile fanFile(hwmonPath + "/fan1_input");
|
||
if (fanFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.fanSpeed = QTextStream(&fanFile).readAll().trimmed().toInt();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// GPU 频率: Intel i915 使用 gt/gt0/rps_act_freq_mhz,AMD 使用 hwmon freq1_input
|
||
QString gtPath = QString("/sys/class/drm/card%1/gt/gt0/rps_act_freq_mhz").arg(gpu.drmCard);
|
||
QFile gtFreqFile(gtPath);
|
||
if (gtFreqFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
gpu.gpuClock = QTextStream(>FreqFile).readAll().trimmed().toInt();
|
||
}
|
||
}
|
||
|
||
// --- 以下为 Intel 核显等无 hwmon/vram sysfs 接口时的回退逻辑 ---
|
||
|
||
// 温度回退:扫描 thermal_zone,匹配 Intel GPU 的 ACPI 标识 (B0D4)
|
||
// B0D4 是 Intel GPU 在 ACPI 规范中的标准热区标识
|
||
if (gpu.temperature < 0) {
|
||
QDir thermalDir("/sys/class/thermal");
|
||
QStringList zones = thermalDir.entryList(QStringList() << "thermal_zone*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||
for (const QString &zone : zones) {
|
||
QFile typeFile(thermalDir.absoluteFilePath(zone + "/type"));
|
||
if (typeFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QString type = QTextStream(&typeFile).readAll().trimmed();
|
||
if (type == "B0D4") {
|
||
QFile tempFile(thermalDir.absoluteFilePath(zone + "/temp"));
|
||
if (tempFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
int tempMilli = QTextStream(&tempFile).readAll().trimmed().toInt();
|
||
gpu.temperature = tempMilli / 1000;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 显存回退:Intel 核显使用共享系统内存,读取 /proc/meminfo 作为参考
|
||
// 注意:这是系统内存使用量,非核显独占,仅作参考
|
||
if (gpu.memTotal < 0) {
|
||
QFile meminfo("/proc/meminfo");
|
||
if (meminfo.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
QString content = QTextStream(&meminfo).readAll();
|
||
QRegularExpression totalRe("MemTotal:\\s*(\\d+)\\s*kB");
|
||
QRegularExpression availRe("MemAvailable:\\s*(\\d+)\\s*kB");
|
||
auto totalMatch = totalRe.match(content);
|
||
auto availMatch = availRe.match(content);
|
||
if (totalMatch.hasMatch()) {
|
||
gpu.memTotal = totalMatch.captured(1).toLongLong() / 1024; // kB -> MB
|
||
}
|
||
if (availMatch.hasMatch() && gpu.memTotal > 0) {
|
||
qint64 availMB = availMatch.captured(1).toLongLong() / 1024;
|
||
gpu.memUsed = gpu.memTotal - availMB;
|
||
if (gpu.memUsed >= 0) {
|
||
gpu.memUsage = gpu.memUsed * 100 / gpu.memTotal;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 生成统计数据 (格式: temp|gpuUsage|memUsage|memUsed|memTotal|gpuClock|memClock|powerDraw|fanSpeed)
|
||
m_gpuStats.clear();
|
||
for (const GpuInfo &gpu : m_gpus) {
|
||
m_gpuStats << QString("%1|%2|%3|%4|%5|%6|%7|%8|%9")
|
||
.arg(gpu.temperature)
|
||
.arg(gpu.gpuUsage)
|
||
.arg(gpu.memUsage)
|
||
.arg(gpu.memUsed)
|
||
.arg(gpu.memTotal)
|
||
.arg(gpu.gpuClock)
|
||
.arg(gpu.memClock)
|
||
.arg(gpu.powerDraw, 0, 'f', 1)
|
||
.arg(gpu.fanSpeed);
|
||
}
|
||
|
||
emit gpuStatsChanged();
|
||
|
||
// 更新温度历史(保留最近 60 个采样点)
|
||
if (m_tempHistory.size() != m_gpus.size()) {
|
||
m_tempHistory.resize(m_gpus.size());
|
||
}
|
||
for (int i = 0; i < m_gpus.size(); ++i) {
|
||
m_tempHistory[i].append(m_gpus[i].temperature);
|
||
if (m_tempHistory[i].size() > 60) {
|
||
m_tempHistory[i].removeFirst();
|
||
}
|
||
}
|
||
emit tempHistoryChanged();
|
||
|
||
// 温度告警检测
|
||
checkTempAlert();
|
||
|
||
// 读取 NVIDIA 进程列表
|
||
readGpuProcesses();
|
||
}
|
||
|
||
// 温度告警:检测是否有 GPU 温度超过 80°C,30 秒内不重复告警
|
||
void GraphicsDriverApplet::checkTempAlert()
|
||
{
|
||
if (!m_tempAlertEnabled) return;
|
||
|
||
qint64 now = QDateTime::currentSecsSinceEpoch();
|
||
if (now - m_lastAlertTime < 30) return;
|
||
|
||
for (const GpuInfo &gpu : m_gpus) {
|
||
if (gpu.temperature >= 80) {
|
||
sendNotification(
|
||
tr("GPU Temperature Alert"),
|
||
tr("%1 temperature reached %2°C, please check cooling")
|
||
.arg(gpu.name)
|
||
.arg(gpu.temperature)
|
||
);
|
||
m_lastAlertTime = now;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通过 D-Bus 发送桌面通知
|
||
void GraphicsDriverApplet::sendNotification(const QString &summary, const QString &body)
|
||
{
|
||
QDBusInterface notify("org.freedesktop.Notifications",
|
||
"/org/freedesktop/Notifications",
|
||
"org.freedesktop.Notifications",
|
||
QDBusConnection::sessionBus());
|
||
if (notify.isValid()) {
|
||
notify.call("Notify", "dde-shell-graphics-driver", 0u,
|
||
"dialog-warning", summary, body,
|
||
QStringList(), QVariantMap(), 5000);
|
||
}
|
||
}
|
||
|
||
// 读取 NVIDIA GPU 上运行的进程列表
|
||
void GraphicsDriverApplet::readGpuProcesses()
|
||
{
|
||
m_gpuProcesses.clear();
|
||
|
||
// 仅当存在 NVIDIA GPU 时查询
|
||
bool hasNvidia = false;
|
||
for (const GpuInfo &gpu : m_gpus) {
|
||
if (gpu.driver == "nvidia") { hasNvidia = true; break; }
|
||
}
|
||
if (!hasNvidia) {
|
||
emit gpuProcessesChanged();
|
||
return;
|
||
}
|
||
|
||
// nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv,noheader,nounits
|
||
QStringList lines = runCommand("nvidia-smi", {
|
||
"--query-compute-apps=pid,process_name,used_memory",
|
||
"--format=csv,noheader,nounits"
|
||
});
|
||
for (const QString &line : lines) {
|
||
QStringList parts = line.split(",");
|
||
if (parts.size() >= 3) {
|
||
m_gpuProcesses << QString("%1|%2|%3")
|
||
.arg(parts[0].trimmed())
|
||
.arg(parts[1].trimmed())
|
||
.arg(parts[2].trimmed());
|
||
}
|
||
}
|
||
|
||
emit gpuProcessesChanged();
|
||
}
|
||
|
||
// GPU 切换:调用 prime-select 切换 PRIME 模式(需要 polkit 提权)
|
||
bool GraphicsDriverApplet::switchGpu(const QString &mode)
|
||
{
|
||
// mode: "nvidia" / "intel" / "on-demand"
|
||
QProcess process;
|
||
process.start("pkexec", {"prime-select", "set", mode});
|
||
process.waitForFinished(30000);
|
||
return process.exitCode() == 0;
|
||
}
|
||
|
||
D_APPLET_CLASS(GraphicsDriverApplet)
|
||
|
||
DS_END_NAMESPACE
|
||
|
||
#include "graphicsdriverapplet.moc"
|