feat: 添加GPU温度/使用率/显存实时监控
- C++后端:GpuInfo扩展统计字段,readGpuStats()支持NVIDIA(nvidia-smi)和AMD(sysfs) - PCI到DRM card映射,新增gpuStats属性和refreshStats方法 - QML前端:弹窗卡片显示温度(带颜色)、GPU使用率进度条、显存使用 - 任务栏tooltip显示简洁摘要(厂商简称+温度+使用率) - 定时刷新:弹窗和tooltip打开时每2秒刷新,关闭时停止 - 优化卡片布局:压缩高度和间距,弹窗高度调整至420px
This commit is contained in:
+189
-31
@@ -55,11 +55,23 @@ AppletItem {
|
||||
// 悬停提示
|
||||
PanelToolTip {
|
||||
id: toolTip
|
||||
text: root.ready ? root.deviceInfo.replace(/\n/g, "\n") : qsTr("No GPU detected")
|
||||
text: root.ready ? buildToolTipText() : qsTr("No GPU detected")
|
||||
toolTipX: DockPanelPositioner.x
|
||||
toolTipY: DockPanelPositioner.y
|
||||
}
|
||||
|
||||
// 统计数据刷新定时器
|
||||
Timer {
|
||||
id: statsRefreshTimer
|
||||
interval: 2000
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (root.applet) {
|
||||
root.applet.refreshStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toolTipShowTimer
|
||||
interval: 50
|
||||
@@ -74,11 +86,18 @@ AppletItem {
|
||||
onHoveredChanged: {
|
||||
if (hovered && !driverPopup.popupVisible) {
|
||||
toolTipShowTimer.start()
|
||||
if (root.applet) {
|
||||
root.applet.refreshStats()
|
||||
}
|
||||
statsRefreshTimer.start()
|
||||
} else {
|
||||
if (toolTipShowTimer.running) {
|
||||
toolTipShowTimer.stop()
|
||||
}
|
||||
toolTip.close()
|
||||
if (!driverPopup.popupVisible) {
|
||||
statsRefreshTimer.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,13 +106,19 @@ AppletItem {
|
||||
PanelPopup {
|
||||
id: driverPopup
|
||||
width: 360
|
||||
height: 320
|
||||
height: 420
|
||||
popupX: DockPanelPositioner.x
|
||||
popupY: DockPanelPositioner.y
|
||||
|
||||
onPopupVisibleChanged: {
|
||||
if (popupVisible) {
|
||||
toolTip.close()
|
||||
statsRefreshTimer.start()
|
||||
if (root.applet) {
|
||||
root.applet.refreshStats()
|
||||
}
|
||||
} else {
|
||||
statsRefreshTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,12 +213,53 @@ AppletItem {
|
||||
delegate: Rectangle {
|
||||
id: gpuCard
|
||||
width: gpuColumn.width
|
||||
height: 72
|
||||
height: 98
|
||||
color: root.cardBackground
|
||||
radius: 10
|
||||
border.width: 1
|
||||
border.color: root.cardBorder
|
||||
|
||||
property string statsLine: root.applet && root.applet.gpuStats ? (root.applet.gpuStats[index] || "") : ""
|
||||
property string tempStr: {
|
||||
var parts = statsLine.split("|")
|
||||
var temp = parts[0]
|
||||
return temp === "-1" || temp === "" ? "—" : temp
|
||||
}
|
||||
property string gpuUsageStr: {
|
||||
var parts = statsLine.split("|")
|
||||
var usage = parts[1]
|
||||
return usage === "-1" || usage === "" ? "—" : usage
|
||||
}
|
||||
property string memUsageStr: {
|
||||
var parts = statsLine.split("|")
|
||||
var usage = parts[2]
|
||||
return usage === "-1" || usage === "" ? "—" : usage
|
||||
}
|
||||
property string memUsedStr: {
|
||||
var parts = statsLine.split("|")
|
||||
var used = parts[3]
|
||||
return used === "-1" || used === "" ? "—" : used
|
||||
}
|
||||
property string memTotalStr: {
|
||||
var parts = statsLine.split("|")
|
||||
var total = parts[4]
|
||||
return total === "-1" || total === "" ? "—" : total
|
||||
}
|
||||
property real gpuUsagePercent: {
|
||||
var parts = statsLine.split("|")
|
||||
var usage = parts[1]
|
||||
if (usage === "-1" || usage === "") return 0
|
||||
return parseFloat(usage)
|
||||
}
|
||||
property color tempColor: {
|
||||
var parts = statsLine.split("|")
|
||||
var temp = parseInt(parts[0])
|
||||
if (isNaN(temp) || temp === -1) return root.primaryText
|
||||
if (temp >= 80) return Qt.rgba(220/255, 38/255, 38/255, 1)
|
||||
if (temp >= 60) return Qt.rgba(245/255, 158/255, 11/255, 1)
|
||||
return Qt.rgba(22/255, 163/255, 74/255, 1)
|
||||
}
|
||||
|
||||
ToolTip.text: qsTr("GPU: ") + parseGpuName(modelData) + "\n"
|
||||
+ qsTr("Driver: ") + parseDriverName(modelData) + "\n"
|
||||
+ qsTr("Version: ") + (parseDriverVersion(modelData) || qsTr("Unknown"))
|
||||
@@ -204,47 +270,113 @@ AppletItem {
|
||||
id: cardHover
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 12
|
||||
anchors.margins: 10
|
||||
spacing: 6
|
||||
|
||||
// GPU 图标
|
||||
Rectangle {
|
||||
width: 44
|
||||
height: 44
|
||||
color: root.accentBlueLight
|
||||
radius: 8
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parseGpuVendorShort(modelData)
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: root.accentBlue
|
||||
// GPU 图标
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
color: root.accentBlueLight
|
||||
radius: 8
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parseGpuVendorShort(modelData)
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: root.accentBlue
|
||||
}
|
||||
}
|
||||
|
||||
// GPU 信息
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: parseGpuName(modelData)
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: root.primaryText
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parseDriverInfo(modelData)
|
||||
font.pixelSize: 11
|
||||
color: root.secondaryText
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU 信息
|
||||
// 统计数据区域
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: parseGpuName(modelData)
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: root.primaryText
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
spacing: 12
|
||||
|
||||
// 温度
|
||||
Text {
|
||||
text: tempStr !== "—" ? tempStr + "°C" : "—"
|
||||
font.pixelSize: 12
|
||||
color: gpuCard.tempColor
|
||||
font.bold: tempStr !== "—"
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// 显存信息
|
||||
Text {
|
||||
text: (memUsedStr !== "—" && memTotalStr !== "—") ? memUsedStr + "/" + memTotalStr + " MB" : "—"
|
||||
font.pixelSize: 11
|
||||
color: root.secondaryText
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parseDriverInfo(modelData)
|
||||
font.pixelSize: 11
|
||||
color: root.secondaryText
|
||||
// GPU 使用率进度条
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "GPU"
|
||||
font.pixelSize: 11
|
||||
color: root.tertiaryText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 4
|
||||
color: root.cardBorder
|
||||
radius: 2
|
||||
|
||||
Rectangle {
|
||||
width: Math.min(gpuCard.gpuUsagePercent, 100) * parent.width / 100
|
||||
height: parent.height
|
||||
color: root.accentBlue
|
||||
radius: 2
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: gpuUsageStr !== "—" ? gpuUsageStr + "%" : "—"
|
||||
font.pixelSize: 11
|
||||
color: root.secondaryText
|
||||
font.bold: gpuUsageStr !== "—"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,6 +498,32 @@ AppletItem {
|
||||
return "GPU"
|
||||
}
|
||||
|
||||
// 构建 tooltip 文本
|
||||
function buildToolTipText() {
|
||||
if (!root.applet || !root.applet.gpuStats) {
|
||||
return root.currentDriver || qsTr("No GPU detected")
|
||||
}
|
||||
|
||||
var lines = root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 })
|
||||
var result = []
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var vendor = parseGpuVendorShort(lines[i])
|
||||
var statsLine = root.applet.gpuStats[i] || ""
|
||||
var parts = statsLine.split("|")
|
||||
|
||||
var temp = parts[0]
|
||||
var gpuUsage = parts[1]
|
||||
|
||||
var tempStr = (temp === "-1" || temp === "") ? "" : temp + "°C"
|
||||
var gpuUsageStr = (gpuUsage === "-1" || gpuUsage === "") ? "" : gpuUsage + "%"
|
||||
|
||||
result.push(vendor + " " + tempStr + " " + gpuUsageStr)
|
||||
}
|
||||
|
||||
return result.join(" | ")
|
||||
}
|
||||
|
||||
// 点击处理
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
Reference in New Issue
Block a user