feat: 设置窗口、合并温度趋势图、UI优化
- 新增设置窗口(右键打开):无边框毛玻璃风格,温度告警开关,QSettings持久化 - 合并温度趋势图:多GPU多色折线,图例,Y轴刻度,填充区域,最新温度标注 - 显卡排序:核显在前独显在后(std::stable_sort by boot_vga) - 卡片优化:删除温度行和频率/功耗行,驱动名+版本两端对齐,高度压缩至78 - 弹窗优化:宽度480,右上角刷新图标替代底部按钮 - tooltip修复:删除自定义Rectangle tooltip改用标准ToolTip - 翻译更新:设置/常规设置/温度告警/温度趋势等
This commit is contained in:
@@ -12,6 +12,8 @@
|
|||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <algorithm>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
@@ -25,7 +27,10 @@ GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent)
|
|||||||
: DApplet(parent)
|
: DApplet(parent)
|
||||||
, m_ready(false)
|
, m_ready(false)
|
||||||
, m_lastAlertTime(0)
|
, m_lastAlertTime(0)
|
||||||
|
, m_tempAlertEnabled(true)
|
||||||
{
|
{
|
||||||
|
QSettings settings("deepin", "graphics-driver-applet");
|
||||||
|
m_tempAlertEnabled = settings.value("tempAlertEnabled", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsDriverApplet::~GraphicsDriverApplet()
|
GraphicsDriverApplet::~GraphicsDriverApplet()
|
||||||
@@ -96,6 +101,20 @@ QStringList GraphicsDriverApplet::gpuProcesses() const
|
|||||||
return m_gpuProcesses;
|
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()
|
void GraphicsDriverApplet::refreshDeviceInfo()
|
||||||
{
|
{
|
||||||
detectGpus();
|
detectGpus();
|
||||||
@@ -168,6 +187,11 @@ void GraphicsDriverApplet::detectGpus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 排序:核显(isPrimary)排在前面
|
||||||
|
std::stable_sort(m_gpus.begin(), m_gpus.end(), [](const GpuInfo &a, const GpuInfo &b) {
|
||||||
|
return a.isPrimary > b.isPrimary;
|
||||||
|
});
|
||||||
|
|
||||||
// 生成摘要信息
|
// 生成摘要信息
|
||||||
QStringList parts;
|
QStringList parts;
|
||||||
QStringList driverParts;
|
QStringList driverParts;
|
||||||
@@ -555,6 +579,8 @@ void GraphicsDriverApplet::readGpuStats()
|
|||||||
// 温度告警:检测是否有 GPU 温度超过 80°C,30 秒内不重复告警
|
// 温度告警:检测是否有 GPU 温度超过 80°C,30 秒内不重复告警
|
||||||
void GraphicsDriverApplet::checkTempAlert()
|
void GraphicsDriverApplet::checkTempAlert()
|
||||||
{
|
{
|
||||||
|
if (!m_tempAlertEnabled) return;
|
||||||
|
|
||||||
qint64 now = QDateTime::currentSecsSinceEpoch();
|
qint64 now = QDateTime::currentSecsSinceEpoch();
|
||||||
if (now - m_lastAlertTime < 30) return;
|
if (now - m_lastAlertTime < 30) return;
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class GraphicsDriverApplet : public DApplet
|
|||||||
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
||||||
Q_PROPERTY(QVariantList tempHistory READ tempHistory NOTIFY tempHistoryChanged)
|
Q_PROPERTY(QVariantList tempHistory READ tempHistory NOTIFY tempHistoryChanged)
|
||||||
Q_PROPERTY(QStringList gpuProcesses READ gpuProcesses NOTIFY gpuProcessesChanged)
|
Q_PROPERTY(QStringList gpuProcesses READ gpuProcesses NOTIFY gpuProcessesChanged)
|
||||||
|
Q_PROPERTY(bool tempAlertEnabled READ tempAlertEnabled WRITE setTempAlertEnabled NOTIFY tempAlertEnabledChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GraphicsDriverApplet(QObject *parent = nullptr);
|
explicit GraphicsDriverApplet(QObject *parent = nullptr);
|
||||||
@@ -63,6 +64,8 @@ public:
|
|||||||
bool ready() const;
|
bool ready() const;
|
||||||
QVariantList tempHistory() const;
|
QVariantList tempHistory() const;
|
||||||
QStringList gpuProcesses() const;
|
QStringList gpuProcesses() const;
|
||||||
|
bool tempAlertEnabled() const;
|
||||||
|
void setTempAlertEnabled(bool enabled);
|
||||||
|
|
||||||
Q_INVOKABLE void refreshDeviceInfo();
|
Q_INVOKABLE void refreshDeviceInfo();
|
||||||
Q_INVOKABLE void refreshStats();
|
Q_INVOKABLE void refreshStats();
|
||||||
@@ -78,6 +81,7 @@ signals:
|
|||||||
void readyChanged();
|
void readyChanged();
|
||||||
void tempHistoryChanged();
|
void tempHistoryChanged();
|
||||||
void gpuProcessesChanged();
|
void gpuProcessesChanged();
|
||||||
|
void tempAlertEnabledChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void detectGpus();
|
void detectGpus();
|
||||||
@@ -105,6 +109,7 @@ private:
|
|||||||
QStringList m_gpuProcesses;
|
QStringList m_gpuProcesses;
|
||||||
// 温度告警冷却时间戳
|
// 温度告警冷却时间戳
|
||||||
qint64 m_lastAlertTime;
|
qint64 m_lastAlertTime;
|
||||||
|
bool m_tempAlertEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
DS_END_NAMESPACE
|
DS_END_NAMESPACE
|
||||||
|
|||||||
+413
-216
@@ -5,7 +5,7 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import Qt.labs.platform 1.1 as LP
|
import QtQuick.Window 2.15
|
||||||
import org.deepin.ds 1.0
|
import org.deepin.ds 1.0
|
||||||
import org.deepin.ds.dock 1.0
|
import org.deepin.ds.dock 1.0
|
||||||
import org.deepin.dtk 1.0
|
import org.deepin.dtk 1.0
|
||||||
@@ -125,8 +125,8 @@ AppletItem {
|
|||||||
// 弹出窗口
|
// 弹出窗口
|
||||||
PanelPopup {
|
PanelPopup {
|
||||||
id: driverPopup
|
id: driverPopup
|
||||||
width: 360
|
width: 480
|
||||||
height: 420
|
height: 440
|
||||||
popupX: DockPanelPositioner.x
|
popupX: DockPanelPositioner.x
|
||||||
popupY: DockPanelPositioner.y
|
popupY: DockPanelPositioner.y
|
||||||
|
|
||||||
@@ -180,6 +180,46 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
// 刷新图标
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 6
|
||||||
|
color: refreshIconArea.containsMouse ? root.accentBlueLight : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "⟳"
|
||||||
|
font.pixelSize: 18
|
||||||
|
color: refreshIconArea.containsMouse ? root.accentBlue : root.secondaryText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: refreshIconArea
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (root.applet) {
|
||||||
|
root.applet.refreshDeviceInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旋转动画
|
||||||
|
RotationAnimator on rotation {
|
||||||
|
id: refreshSpin
|
||||||
|
running: false
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
duration: 500
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: refreshIconArea
|
||||||
|
function onClicked() { refreshSpin.start() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分隔线
|
// 分隔线
|
||||||
@@ -292,7 +332,7 @@ AppletItem {
|
|||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: gpuCard
|
id: gpuCard
|
||||||
width: gpuColumn.width
|
width: gpuColumn.width
|
||||||
height: 120
|
height: 78
|
||||||
color: root.cardBackground
|
color: root.cardBackground
|
||||||
radius: 10
|
radius: 10
|
||||||
border.width: 1
|
border.width: 1
|
||||||
@@ -360,49 +400,16 @@ AppletItem {
|
|||||||
return v === "-1" || v === "" ? "-" : v
|
return v === "-1" || v === "" ? "-" : v
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义深色主题悬浮提示(替代系统默认白色 tooltip)
|
|
||||||
// 第一张卡片 tooltip 显示在下方,避免被弹框顶部裁剪
|
|
||||||
Rectangle {
|
|
||||||
visible: cardHover.hovered
|
|
||||||
anchors.top: index === 0 ? parent.bottom : undefined
|
|
||||||
anchors.topMargin: index === 0 ? 6 : 0
|
|
||||||
anchors.bottom: index === 0 ? undefined : parent.top
|
|
||||||
anchors.bottomMargin: index === 0 ? 0 : 6
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: tooltipContent.implicitWidth + 24
|
|
||||||
height: tooltipContent.implicitHeight + 16
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.85)
|
|
||||||
radius: 8
|
|
||||||
z: 100
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: tooltipContent
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 3
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("GPU: ") + parseGpuName(modelData)
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 11
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: qsTr("Driver: ") + parseDriverName(modelData)
|
|
||||||
color: Qt.rgba(1, 1, 1, 0.65)
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: qsTr("Version: ") + (parseDriverVersion(modelData) || qsTr("Unknown"))
|
|
||||||
color: Qt.rgba(1, 1, 1, 0.65)
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: cardHover
|
id: cardHover
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolTip.text: qsTr("GPU: ") + parseGpuName(modelData) + "\n"
|
||||||
|
+ qsTr("Driver: ") + parseDriverName(modelData) + "\n"
|
||||||
|
+ qsTr("Version: ") + (parseDriverVersion(modelData) || qsTr("Unknown"))
|
||||||
|
ToolTip.visible: cardHover.hovered
|
||||||
|
ToolTip.delay: 300
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
@@ -465,12 +472,23 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: parseDriverInfo(modelData)
|
text: parseDriverName(modelData)
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
color: root.secondaryText
|
color: root.secondaryText
|
||||||
Layout.fillWidth: true
|
}
|
||||||
elide: Text.ElideRight
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: parseDriverVersion(modelData) || qsTr("Unknown")
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: root.tertiaryText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,27 +502,17 @@ AppletItem {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
// 温度
|
// 显存信息
|
||||||
Text {
|
Text {
|
||||||
text: tempStr !== "—" ? tempStr + "°C" : "—"
|
text: (memUsedStr !== "-" && memTotalStr !== "-") ? memUsedStr + "/" + memTotalStr + " MB" : "-"
|
||||||
font.pixelSize: 12
|
font.pixelSize: 11
|
||||||
color: gpuCard.tempColor
|
color: root.secondaryText
|
||||||
font.bold: tempStr !== "—"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
// 显存信息
|
|
||||||
Text {
|
|
||||||
text: (memUsedStr !== "—" && memTotalStr !== "—") ? memUsedStr + "/" + memTotalStr + " MB" : "—"
|
|
||||||
font.pixelSize: 11
|
|
||||||
color: root.secondaryText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GPU 使用率进度条
|
// GPU 使用率进度条
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
@@ -514,7 +522,7 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.preferredWidth: 140
|
||||||
height: 4
|
height: 4
|
||||||
color: root.cardBorder
|
color: root.cardBorder
|
||||||
radius: 2
|
radius: 2
|
||||||
@@ -534,92 +542,8 @@ AppletItem {
|
|||||||
font.bold: gpuUsageStr !== "-"
|
font.bold: gpuUsageStr !== "-"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 频率/功耗/风扇信息行
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
// GPU 频率
|
|
||||||
Text {
|
|
||||||
text: gpuClockStr !== "-" ? gpuClockStr + " MHz" : "-"
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: root.tertiaryText
|
|
||||||
visible: gpuClockStr !== "-"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 功耗
|
|
||||||
Text {
|
|
||||||
text: powerStr !== "-" ? powerStr + " W" : ""
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: root.tertiaryText
|
|
||||||
visible: powerStr !== "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 风扇转速
|
|
||||||
Text {
|
|
||||||
text: fanStr !== "-" ? qsTr("Fan") + " " + fanStr + "%" : ""
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: root.tertiaryText
|
|
||||||
visible: fanStr !== "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
// 显存频率
|
|
||||||
Text {
|
|
||||||
text: memClockStr !== "-" ? memClockStr + " MHz" : ""
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: root.tertiaryText
|
|
||||||
visible: memClockStr !== "-"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 温度趋势迷你图
|
|
||||||
Canvas {
|
|
||||||
id: tempCanvas
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 24
|
|
||||||
visible: {
|
|
||||||
if (!root.applet || !root.applet.tempHistory) return false
|
|
||||||
var h = root.applet.tempHistory[index]
|
|
||||||
return h && h.length > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
var ctx = getContext("2d")
|
|
||||||
ctx.reset()
|
|
||||||
var w = width
|
|
||||||
var h = height
|
|
||||||
if (!root.applet || !root.applet.tempHistory) return
|
|
||||||
var history = root.applet.tempHistory[index]
|
|
||||||
if (!history || history.length < 2) return
|
|
||||||
|
|
||||||
// 有效温度范围 20-100°C
|
|
||||||
var minT = 20, maxT = 100
|
|
||||||
var len = history.length
|
|
||||||
ctx.strokeStyle = gpuCard.tempColor
|
|
||||||
ctx.lineWidth = 1.5
|
|
||||||
ctx.beginPath()
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var x = (i / (len - 1)) * w
|
|
||||||
var t = history[i]
|
|
||||||
if (t < 0) t = minT
|
|
||||||
var y = h - ((t - minT) / (maxT - minT)) * h
|
|
||||||
if (y < 0) y = 0
|
|
||||||
if (y > h) y = h
|
|
||||||
if (i === 0) ctx.moveTo(x, y)
|
|
||||||
else ctx.lineTo(x, y)
|
|
||||||
}
|
|
||||||
ctx.stroke()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 温度历史变化时重绘
|
|
||||||
Connections {
|
|
||||||
target: root.applet
|
|
||||||
function onTempHistoryChanged() { tempCanvas.requestPaint() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,6 +579,179 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 合并温度趋势图
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 90
|
||||||
|
color: root.cardBackground
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: root.cardBorder
|
||||||
|
visible: {
|
||||||
|
if (!root.applet || !root.applet.tempHistory) return false
|
||||||
|
var th = root.applet.tempHistory
|
||||||
|
for (var i = 0; i < th.length; i++) {
|
||||||
|
if (th[i] && th[i].length > 1) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
// 图例
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.ready ? root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 }) : []
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 10
|
||||||
|
height: 10
|
||||||
|
radius: 2
|
||||||
|
color: {
|
||||||
|
var colors = [
|
||||||
|
Qt.rgba(22/255, 163/255, 74/255, 1),
|
||||||
|
Qt.rgba(20/255, 80/255, 160/255, 1),
|
||||||
|
Qt.rgba(245/255, 158/255, 11/255, 1)
|
||||||
|
]
|
||||||
|
return colors[index] || colors[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: parseGpuVendorShort(modelData)
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: root.secondaryText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Temperature Trend")
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: root.tertiaryText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 趋势图 Canvas
|
||||||
|
Canvas {
|
||||||
|
id: mergedTempCanvas
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d")
|
||||||
|
ctx.reset()
|
||||||
|
var w = width
|
||||||
|
var h = height
|
||||||
|
if (!root.applet || !root.applet.tempHistory) return
|
||||||
|
|
||||||
|
var th = root.applet.tempHistory
|
||||||
|
var minT = 20, maxT = 100
|
||||||
|
var pad = 4
|
||||||
|
var colors = [
|
||||||
|
Qt.rgba(22/255, 163/255, 74/255, 1),
|
||||||
|
Qt.rgba(20/255, 80/255, 160/255, 1),
|
||||||
|
Qt.rgba(245/255, 158/255, 11/255, 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
// 1. 绘制网格线和刻度
|
||||||
|
ctx.font = "9px sans-serif"
|
||||||
|
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.25)
|
||||||
|
var temps = [40, 60, 80]
|
||||||
|
for (var ti = 0; ti < temps.length; ti++) {
|
||||||
|
var t = temps[ti]
|
||||||
|
var y = h - ((t - minT) / (maxT - minT)) * (h - pad * 2) - pad
|
||||||
|
ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.06)
|
||||||
|
ctx.lineWidth = 1
|
||||||
|
ctx.setLineDash(t === 80 ? [3, 3] : [])
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(24, y)
|
||||||
|
ctx.lineTo(w, y)
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.setLineDash([])
|
||||||
|
ctx.fillText(t + "°C", 2, y + 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 绘制每个 GPU 的温度线
|
||||||
|
for (var g = 0; g < th.length; g++) {
|
||||||
|
var history = th[g]
|
||||||
|
if (!history || history.length < 2) continue
|
||||||
|
var len = history.length
|
||||||
|
var color = colors[g] || colors[0]
|
||||||
|
|
||||||
|
// 填充区域
|
||||||
|
ctx.beginPath()
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
var x = 24 + (i / (len - 1)) * (w - 24)
|
||||||
|
var temp = history[i]
|
||||||
|
if (temp < 0) temp = minT
|
||||||
|
var y2 = h - ((temp - minT) / (maxT - minT)) * (h - pad * 2) - pad
|
||||||
|
if (i === 0) ctx.moveTo(x, y2)
|
||||||
|
else ctx.lineTo(x, y2)
|
||||||
|
}
|
||||||
|
ctx.lineTo(24 + (w - 24), h)
|
||||||
|
ctx.lineTo(24, h)
|
||||||
|
ctx.closePath()
|
||||||
|
var grad = ctx.createLinearGradient(0, 0, 0, h)
|
||||||
|
grad.addColorStop(0, Qt.rgba(color.r, color.g, color.b, 0.15))
|
||||||
|
grad.addColorStop(1, Qt.rgba(color.r, color.g, color.b, 0.01))
|
||||||
|
ctx.fillStyle = grad
|
||||||
|
ctx.fill()
|
||||||
|
|
||||||
|
// 折线
|
||||||
|
ctx.strokeStyle = color
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.beginPath()
|
||||||
|
for (var j = 0; j < len; j++) {
|
||||||
|
var x2 = 24 + (j / (len - 1)) * (w - 24)
|
||||||
|
var t2 = history[j]
|
||||||
|
if (t2 < 0) t2 = minT
|
||||||
|
var y3 = h - ((t2 - minT) / (maxT - minT)) * (h - pad * 2) - pad
|
||||||
|
if (j === 0) ctx.moveTo(x2, y3)
|
||||||
|
else ctx.lineTo(x2, y3)
|
||||||
|
}
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 最新数据点
|
||||||
|
var lastIdx = len - 1
|
||||||
|
var lastX = 24 + (lastIdx / (len - 1)) * (w - 24)
|
||||||
|
var lastT = history[lastIdx]
|
||||||
|
if (lastT < 0) lastT = minT
|
||||||
|
var lastY = h - ((lastT - minT) / (maxT - minT)) * (h - pad * 2) - pad
|
||||||
|
ctx.fillStyle = color
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2)
|
||||||
|
ctx.fill()
|
||||||
|
|
||||||
|
// 最新温度标注
|
||||||
|
ctx.font = "bold 10px sans-serif"
|
||||||
|
ctx.fillStyle = color
|
||||||
|
var label = lastT + "°C"
|
||||||
|
var labelX = lastX + 6
|
||||||
|
if (labelX + 30 > w) labelX = lastX - 30
|
||||||
|
ctx.fillText(label, labelX, lastY + 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.applet
|
||||||
|
function onTempHistoryChanged() { mergedTempCanvas.requestPaint() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NVIDIA 进程列表(仅有进程时显示)
|
// NVIDIA 进程列表(仅有进程时显示)
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -772,34 +869,6 @@ AppletItem {
|
|||||||
onClicked: if (root.applet) root.applet.switchGpu("on-demand")
|
onClicked: if (root.applet) root.applet.switchGpu("on-demand")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新按钮
|
|
||||||
Button {
|
|
||||||
id: refreshBtn
|
|
||||||
text: qsTr("Refresh")
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 36
|
|
||||||
font.pixelSize: 13
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: refreshBtn.hovered ? Qt.darker(root.accentBlue, 1.2) : root.accentBlue
|
|
||||||
radius: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Text {
|
|
||||||
text: refreshBtn.text
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: refreshBtn.font.pixelSize
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (root.applet) {
|
|
||||||
root.applet.refreshDeviceInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -903,53 +972,181 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右键菜单(使用 Qt.labs.platform 原生菜单,通过 dock 的 MenuHelper 弹出)
|
// 设置窗口(无边框 + 毛玻璃风格)
|
||||||
|
Window {
|
||||||
|
id: settingsWindow
|
||||||
|
title: qsTr("Settings")
|
||||||
|
width: 650
|
||||||
|
height: 350
|
||||||
|
flags: Qt.FramelessWindowHint | Qt.Window
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// 居中屏幕
|
||||||
|
x: (Screen.width - width) / 2
|
||||||
|
y: (Screen.height - height) / 2
|
||||||
|
|
||||||
|
// 毛玻璃风格背景
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: 16
|
||||||
|
color: Qt.rgba(0.12, 0.12, 0.14, 0.88)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.1)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 0
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// 自定义标题栏
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
color: "transparent"
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: 0
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.04)
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 20
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("General Settings")
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.bold: true
|
||||||
|
color: "#e8e8e8"
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
// 关闭按钮
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: closeBtnArea.containsMouse ? Qt.rgba(255/255, 95/255, 86/255, 0.8) : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "✕"
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: closeBtnArea.containsMouse ? "white" : "#999999"
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeBtnArea
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: settingsWindow.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口拖拽(使用系统移动,避免幻影)
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: 44
|
||||||
|
onPressed: settingsWindow.startSystemMove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.08)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.leftMargin: 24
|
||||||
|
Layout.rightMargin: 24
|
||||||
|
Layout.topMargin: 20
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
// 温度告警通知开关
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
text: qsTr("Temperature Alert")
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#e0e0e0"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: qsTr("Show notification when GPU temperature exceeds threshold")
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#777777"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
checked: root.applet ? root.applet.tempAlertEnabled : false
|
||||||
|
onToggled: {
|
||||||
|
if (root.applet) {
|
||||||
|
root.applet.tempAlertEnabled = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.06)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 温度告警阈值
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Alert Temperature Threshold")
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#e0e0e0"
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "80°C"
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
color: Qt.rgba(245/255, 158/255, 11/255, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右键点击显示设置窗口
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
|
|
||||||
onTapped: {
|
onTapped: {
|
||||||
contextMenuLoader.active = true
|
settingsWindow.show()
|
||||||
if (contextMenuLoader.item) {
|
|
||||||
MenuHelper.openMenu(contextMenuLoader.item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: contextMenuLoader
|
|
||||||
active: false
|
|
||||||
sourceComponent: LP.Menu {
|
|
||||||
LP.MenuItem {
|
|
||||||
text: qsTr("Refresh")
|
|
||||||
onTriggered: {
|
|
||||||
if (root.applet) root.applet.refreshDeviceInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LP.MenuItem {
|
|
||||||
text: qsTr("Switch to NVIDIA")
|
|
||||||
visible: root.gpuMode === "PRIME"
|
|
||||||
onTriggered: {
|
|
||||||
if (root.applet) root.applet.switchGpu("nvidia")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LP.MenuItem {
|
|
||||||
text: qsTr("Switch to Intel")
|
|
||||||
visible: root.gpuMode === "PRIME"
|
|
||||||
onTriggered: {
|
|
||||||
if (root.applet) root.applet.switchGpu("intel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LP.MenuItem {
|
|
||||||
text: qsTr("Switch to On-Demand")
|
|
||||||
visible: root.gpuMode === "PRIME"
|
|
||||||
onTriggered: {
|
|
||||||
if (root.applet) root.applet.switchGpu("on-demand")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,5 +128,30 @@
|
|||||||
<source>Fan</source>
|
<source>Fan</source>
|
||||||
<translation>风扇</translation>
|
<translation>风扇</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Settings</source>
|
||||||
|
<translation>设置</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>General Settings</source>
|
||||||
|
<translation>常规设置</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Show notification when GPU temperature exceeds threshold</source>
|
||||||
|
<translation>GPU 温度超过阈值时显示通知</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Alert Temperature Threshold</source>
|
||||||
|
<translation>告警温度阈值</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Temperature Trend</source>
|
||||||
|
<translation>温度趋势</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
@@ -128,5 +128,30 @@
|
|||||||
<source>Fan</source>
|
<source>Fan</source>
|
||||||
<translation>風扇</translation>
|
<translation>風扇</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Settings</source>
|
||||||
|
<translation>設定</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>General Settings</source>
|
||||||
|
<translation>一般設定</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Show notification when GPU temperature exceeds threshold</source>
|
||||||
|
<translation>GPU 溫度超過閾值時顯示通知</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Alert Temperature Threshold</source>
|
||||||
|
<translation>告警溫度閾值</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../package/driverview.qml"/>
|
||||||
|
<source>Temperature Trend</source>
|
||||||
|
<translation>溫度趨勢</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
Reference in New Issue
Block a user