// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import QtQuick.Window 2.15 import org.deepin.ds 1.0 import org.deepin.ds.dock 1.0 import org.deepin.dtk 1.0 AppletItem { id: root objectName: "graphics driver applet" property int dockOrder: 21 property int dockSize: Panel.rootObject.dockItemMaxSize || 48 implicitWidth: dockSize implicitHeight: dockSize readonly property var applet: Applet readonly property bool ready: applet ? applet.ready : false readonly property string gpuSummary: applet ? (applet.gpuSummary || qsTr("No GPU detected")) : qsTr("No GPU detected") readonly property string deviceInfo: applet ? (applet.deviceInfo || qsTr("Unknown")) : qsTr("Unknown") readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown") readonly property string gpuMode: applet ? (applet.gpuMode || "") : "" // 当前 PRIME 模式(nvidia/on-demand/intel) readonly property string currentPrimeMode: { if (root.gpuMode !== "PRIME") return "" if (root.applet && root.applet.primeOffloadEnabled) return "nvidia" return "on-demand" } // GPU 切换确认 property string pendingSwitchMode: "" property int switchCountdown: 15 // 切换模式描述 function switchModeDesc(mode) { if (mode === "nvidia") return qsTr("Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.") if (mode === "intel") return qsTr("Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.") if (mode === "on-demand") return qsTr("Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.") return "" } function switchModeLabel(mode) { if (mode === "nvidia") return "NVIDIA" if (mode === "intel") return getIgpuVendorName() if (mode === "on-demand") return qsTr("On-Demand") return mode } function requestSwitch(mode) { root.pendingSwitchMode = mode root.switchCountdown = 15 switchConfirmTimer.start() } function confirmSwitch() { if (root.pendingSwitchMode && root.applet) { root.applet.switchGpu(root.pendingSwitchMode) } cancelSwitch() } function cancelSwitch() { root.pendingSwitchMode = "" switchConfirmTimer.stop() } // 所有 GPU 中的最高温度,用于任务栏图标变色 readonly property int maxTemp: { if (!applet || !applet.gpuStats) return -1 var max = -1 for (var i = 0; i < applet.gpuStats.length; i++) { var parts = applet.gpuStats[i].split("|") var temp = parseInt(parts[0]) if (!isNaN(temp) && temp > max) max = temp } return max } // 图标颜色:温度 >=80 红色,>=60 橙色,否则正常 readonly property color iconColor: { if (maxTemp >= 80) return Qt.rgba(220/255, 38/255, 38/255, 1) if (maxTemp >= 60) return Qt.rgba(245/255, 158/255, 11/255, 1) return root.primaryText } property Palette basePalette: DockPalette.iconTextPalette readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.95) readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.8) readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65) readonly property color cardBackground: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.06) readonly property color cardBorder: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.1) readonly property color accentBlue: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 1) readonly property color accentBlueLight: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 0.12) // 图标区域(根据最高温度变色) Rectangle { anchors.centerIn: parent width: dockSize * 0.7 height: dockSize * 0.7 color: "transparent" radius: width * 0.45 border.width: 1 border.color: root.iconColor Text { anchors.centerIn: parent text: "GPU" font.pixelSize: root.dockSize * 0.22 font.bold: true color: root.iconColor } } // 悬停提示 PanelToolTip { id: toolTip text: root.ready ? buildToolTipText() : qsTr("No GPU detected") toolTipX: DockPanelPositioner.x toolTipY: DockPanelPositioner.y } // 统计数据刷新定时器 Timer { id: statsRefreshTimer interval: 1000 repeat: true onTriggered: { if (root.applet) { root.applet.refreshStats() } } } // 切换确认倒计时 Timer { id: switchConfirmTimer interval: 1000 repeat: true onTriggered: { root.switchCountdown-- if (root.switchCountdown <= 0) { root.cancelSwitch() } } } Timer { id: toolTipShowTimer interval: 50 onTriggered: { const point = root.mapToItem(null, root.width / 2, root.height / 2) toolTip.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, toolTip.width, toolTip.height) toolTip.open() } } HoverHandler { 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() } } } } // 弹出窗口 PanelPopup { id: driverPopup width: 480 height: 440 popupX: DockPanelPositioner.x popupY: DockPanelPositioner.y onPopupVisibleChanged: { if (popupVisible) { toolTip.close() statsRefreshTimer.start() if (root.applet) { root.applet.refreshStats() } } else { statsRefreshTimer.stop() } } Control { id: popupContainer anchors.fill: parent padding: 16 contentItem: ColumnLayout { spacing: 14 // 标题区域 RowLayout { Layout.fillWidth: true spacing: 10 Item { Layout.fillWidth: true } Rectangle { width: 28 height: 28 color: accentBlueLight radius: 8 Text { anchors.centerIn: parent text: "GPU" font.pixelSize: 12 font.bold: true color: accentBlue } } Text { text: qsTr("Graphics Driver Manager") font.pixelSize: 16 font.bold: true color: root.primaryText } 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() } } } } // 分隔线 Rectangle { Layout.fillWidth: true Layout.preferredHeight: 1 color: root.cardBorder } // 当前驱动信息 RowLayout { Layout.fillWidth: true spacing: 8 visible: root.ready Text { text: qsTr("Current Driver:") font.pixelSize: 12 color: root.secondaryText } Text { text: root.currentDriver font.pixelSize: 12 font.bold: true color: root.primaryText Layout.fillWidth: true elide: Text.ElideRight } // GPU 模式标签 Rectangle { visible: root.gpuMode.length > 0 width: modeText.implicitWidth + 16 height: 20 color: root.accentBlueLight radius: 10 Text { id: modeText anchors.centerIn: parent text: root.gpuMode font.pixelSize: 11 font.bold: true color: root.accentBlue } } // 帮助图标 Item { visible: root.gpuMode.length > 0 width: 16 height: 16 Rectangle { anchors.fill: parent radius: width / 2 color: helpHover.hovered ? root.accentBlue : root.tertiaryText } Text { anchors.centerIn: parent text: "?" font.pixelSize: 10 font.bold: true color: "white" } HoverHandler { id: helpHover } ToolTip { visible: helpHover.hovered delay: 200 text: { if (root.gpuMode === "PRIME") return qsTr("PRIME mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.") if (root.gpuMode === "Bumblebee") return qsTr("Bumblebee mode: a legacy dual-GPU solution, now deprecated.") if (root.gpuMode === "Solo") return qsTr("Solo mode: only one GPU detected.") if (root.gpuMode === "Hybrid") return qsTr("Hybrid mode: dual GPU detected, but no known switching solution found.") return "" } } } } // GPU 列表区域 Flickable { id: gpuListFlick Layout.fillWidth: true Layout.fillHeight: true clip: true contentWidth: width contentHeight: gpuColumn.height boundsBehavior: Flickable.StopAtBounds Column { id: gpuColumn width: gpuListFlick.width spacing: 10 visible: root.ready Repeater { model: root.ready ? root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 }) : [] delegate: Rectangle { id: gpuCard width: gpuColumn.width height: 78 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 bool isPrimaryGpu: root.applet && root.applet.gpuPrimary ? (root.applet.gpuPrimary[index] === "true") : false 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) } property string gpuClockStr: { var parts = statsLine.split("|") var v = parts[5] return v === "-1" || v === "" ? "-" : v } property string memClockStr: { var parts = statsLine.split("|") var v = parts[6] return v === "-1" || v === "" ? "-" : v } property string powerStr: { var parts = statsLine.split("|") var v = parts[7] return v === "-1.0" || v === "" ? "-" : v } property string fanStr: { var parts = statsLine.split("|") var v = parts[8] return v === "-1" || v === "" ? "-" : v } HoverHandler { 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 { anchors.fill: parent anchors.margins: 10 spacing: 6 RowLayout { Layout.fillWidth: true spacing: 12 // 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 RowLayout { spacing: 6 Layout.fillWidth: true Text { text: parseGpuName(modelData) font.pixelSize: 13 font.bold: true color: root.primaryText Layout.fillWidth: true elide: Text.ElideRight } // 主/副显标识 Rectangle { visible: gpuCard.isPrimaryGpu width: badgeText.implicitWidth + 12 height: 16 color: Qt.rgba(22/255, 163/255, 74/255, 0.15) radius: 8 Text { id: badgeText anchors.centerIn: parent text: qsTr("Primary") font.pixelSize: 10 font.bold: true color: Qt.rgba(22/255, 163/255, 74/255, 1) } } } RowLayout { Layout.fillWidth: true spacing: 4 Text { text: parseDriverName(modelData) font.pixelSize: 11 color: root.secondaryText } Item { Layout.fillWidth: true } Text { text: parseDriverVersion(modelData) || qsTr("Unknown") font.pixelSize: 11 color: root.tertiaryText } } } } // 统计数据区域 ColumnLayout { Layout.fillWidth: true spacing: 6 RowLayout { Layout.fillWidth: true spacing: 12 // 显存信息 Text { text: (memUsedStr !== "-" && memTotalStr !== "-") ? memUsedStr + "/" + memTotalStr + " MB" : "-" font.pixelSize: 11 color: root.secondaryText } Item { Layout.fillWidth: true } // GPU 使用率进度条 RowLayout { spacing: 8 Text { text: "GPU" font.pixelSize: 11 color: root.tertiaryText } Rectangle { Layout.preferredWidth: 180 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 !== "-" } } } } } } } } // 未检测到 GPU 提示 Rectangle { anchors.centerIn: parent width: gpuListFlick.width - 32 height: 80 color: root.cardBackground radius: 10 border.width: 1 border.color: root.cardBorder visible: !root.ready ColumnLayout { anchors.centerIn: parent spacing: 6 Text { text: "⚠" font.pixelSize: 24 } Text { text: qsTr("No GPU detected") font.pixelSize: 13 color: root.secondaryText } } } } // 合并温度趋势图 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.5) 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.1) 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 进程列表(仅有进程时显示) Rectangle { Layout.fillWidth: true Layout.preferredHeight: processList.implicitHeight + 24 color: root.cardBackground radius: 10 border.width: 1 border.color: root.cardBorder visible: root.applet && root.applet.gpuProcesses && root.applet.gpuProcesses.length > 0 ColumnLayout { id: processList anchors.fill: parent anchors.margins: 10 spacing: 4 Text { text: qsTr("GPU Processes") font.pixelSize: 11 font.bold: true color: root.secondaryText } Repeater { model: root.applet ? (root.applet.gpuProcesses || []) : [] RowLayout { Layout.fillWidth: true spacing: 8 Text { text: modelData.split("|")[1] || "?" font.pixelSize: 10 color: root.primaryText Layout.fillWidth: true elide: Text.ElideRight } Text { text: (modelData.split("|")[2] || "?") + " MB" font.pixelSize: 10 color: root.tertiaryText } } } } } // GPU 切换按钮(仅 PRIME 模式下显示) RowLayout { Layout.fillWidth: true spacing: 8 visible: root.gpuMode === "PRIME" // 核显按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 32 font.pixelSize: 11 text: getIgpuVendorName() background: Rectangle { color: root.currentPrimeMode === "intel" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground) radius: 6 border.width: 1 border.color: root.currentPrimeMode === "intel" ? root.accentBlue : root.cardBorder } contentItem: Text { text: parent.text color: root.currentPrimeMode === "intel" ? "white" : root.accentBlue font.pixelSize: 11 font.bold: root.currentPrimeMode === "intel" horizontalAlignment: Text.AlignHCenter } onClicked: root.requestSwitch("intel") } // 按需按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 32 font.pixelSize: 11 text: qsTr("On-Demand") background: Rectangle { color: root.currentPrimeMode === "on-demand" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground) radius: 6 border.width: 1 border.color: root.currentPrimeMode === "on-demand" ? root.accentBlue : root.cardBorder } contentItem: Text { text: parent.text color: root.currentPrimeMode === "on-demand" ? "white" : root.accentBlue font.pixelSize: 11 font.bold: root.currentPrimeMode === "on-demand" horizontalAlignment: Text.AlignHCenter } onClicked: root.requestSwitch("on-demand") } // NVIDIA 按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 32 font.pixelSize: 11 text: qsTr("NVIDIA") background: Rectangle { color: root.currentPrimeMode === "nvidia" ? root.accentBlue : (parent.hovered ? root.accentBlueLight : root.cardBackground) radius: 6 border.width: 1 border.color: root.currentPrimeMode === "nvidia" ? root.accentBlue : root.cardBorder } contentItem: Text { text: parent.text color: root.currentPrimeMode === "nvidia" ? "white" : root.accentBlue font.pixelSize: 11 font.bold: root.currentPrimeMode === "nvidia" horizontalAlignment: Text.AlignHCenter } onClicked: root.requestSwitch("nvidia") } } } } // GPU 切换确认覆盖层 Rectangle { id: switchOverlay anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.7) radius: 12 visible: root.pendingSwitchMode.length > 0 z: 100 ColumnLayout { anchors.centerIn: parent width: parent.width - 48 spacing: 16 // 模式标题 Text { Layout.alignment: Qt.AlignHCenter text: qsTr("Switch to") + " " + switchModeLabel(root.pendingSwitchMode) font.pixelSize: 16 font.bold: true color: "#e8e8e8" } // 模式描述 Text { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true text: switchModeDesc(root.pendingSwitchMode) font.pixelSize: 12 color: "#a0a0a0" horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap } // 倒计时圆环 Item { Layout.alignment: Qt.AlignHCenter width: 64 height: 64 // 背景圆环 Rectangle { anchors.fill: parent radius: 32 color: "transparent" border.width: 3 border.color: Qt.rgba(1, 1, 1, 0.1) } // 进度圆环 Rectangle { anchors.fill: parent radius: 32 color: "transparent" border.width: 3 border.color: root.accentBlue // 用 ConicGradient 效果模拟进度(简化用 opacity) opacity: root.switchCountdown / 15 Behavior on opacity { NumberAnimation { duration: 1000 } } } // 倒计时数字 Text { anchors.centerIn: parent text: root.switchCountdown font.pixelSize: 24 font.bold: true color: "#e8e8e8" } } // 提示文字 Text { Layout.alignment: Qt.AlignHCenter text: qsTr("Auto-cancel if not confirmed") font.pixelSize: 10 color: "#666666" } // 按钮行 RowLayout { Layout.fillWidth: true spacing: 12 // 取消按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 34 font.pixelSize: 12 background: Rectangle { color: parent.hovered ? Qt.rgba(1, 1, 1, 0.1) : Qt.rgba(1, 1, 1, 0.05) radius: 8 border.width: 1 border.color: Qt.rgba(1, 1, 1, 0.15) } contentItem: Text { text: qsTr("Cancel") color: "#cccccc" font.pixelSize: 12 horizontalAlignment: Text.AlignHCenter } onClicked: root.cancelSwitch() } // 确认按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 34 font.pixelSize: 12 background: Rectangle { color: parent.hovered ? Qt.darker(root.accentBlue, 1.2) : root.accentBlue radius: 8 } contentItem: Text { text: qsTr("Confirm") color: "white" font.pixelSize: 12 font.bold: true horizontalAlignment: Text.AlignHCenter } onClicked: root.confirmSwitch() } } } } // GPU 切换回退确认覆盖层(启动时检测到未确认的切换) Rectangle { id: restoreOverlay anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.75) radius: 12 visible: root.applet && root.applet.switchPending z: 100 ColumnLayout { anchors.centerIn: parent width: parent.width - 48 spacing: 16 // 警告图标 Text { Layout.alignment: Qt.AlignHCenter text: "⚠" font.pixelSize: 36 color: Qt.rgba(245/255, 158/255, 11/255, 1) } // 标题 Text { Layout.alignment: Qt.AlignHCenter text: qsTr("Display OK?") font.pixelSize: 18 font.bold: true color: "#e8e8e8" } // 说明 Text { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true text: qsTr("GPU mode was changed. If display is abnormal, it will auto-revert in %1s.").arg(root.applet ? root.applet.restoreCountdown : 0) font.pixelSize: 12 color: "#a0a0a0" horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap } // 按钮行 RowLayout { Layout.fillWidth: true spacing: 12 // 回退按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 34 font.pixelSize: 12 background: Rectangle { color: parent.hovered ? Qt.rgba(220/255, 38/255, 38/255, 0.8) : Qt.rgba(220/255, 38/255, 38/255, 0.3) radius: 8 border.width: 1 border.color: Qt.rgba(220/255, 38/255, 38/255, 0.6) } contentItem: Text { text: qsTr("Revert") color: "#ff6b6b" font.pixelSize: 12 font.bold: true horizontalAlignment: Text.AlignHCenter } onClicked: if (root.applet) root.applet.restoreSwitch() } // 确认正常按钮 Button { Layout.fillWidth: true Layout.preferredHeight: 34 font.pixelSize: 12 background: Rectangle { color: parent.hovered ? Qt.darker(root.accentBlue, 1.2) : root.accentBlue radius: 8 } contentItem: Text { text: qsTr("Display OK") color: "white" font.pixelSize: 12 font.bold: true horizontalAlignment: Text.AlignHCenter } onClicked: if (root.applet) root.applet.confirmSwitchSuccess() } } } } Component.onCompleted: { DockPanelPositioner.bounding = Qt.binding(function () { const point = root.mapToItem(null, root.width / 2, root.height / 2) return Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height) }) } } // 解析 GPU 名称的辅助函数 function parseGpuName(info) { var parts = info.split("(") if (parts.length > 0) { return parts[0].trim() } return info } // 解析驱动信息的辅助函数 function parseDriverInfo(info) { var startIndex = info.indexOf("(") var endIndex = info.lastIndexOf(")") if (startIndex !== -1 && endIndex !== -1) { return info.substring(startIndex + 1, endIndex) } return info } // 解析驱动名称 function parseDriverName(info) { var driver = parseDriverInfo(info) var parts = driver.split(" ") return parts[0] || qsTr("Unknown") } // 解析驱动版本号 function parseDriverVersion(info) { var driver = parseDriverInfo(info) var parts = driver.split(" ") if (parts.length > 1) { return parts[1] } return "" } // 识别 GPU 厂商简称 // 注意:用单词边界匹配,避免 "corporation" 中的 "ati" 等子串误匹配 function parseGpuVendorShort(info) { var name = parseGpuName(info).toLowerCase() if (/nvidia/.test(name)) return "NV" if (/\bamd\b/.test(name) || /\bati\b/.test(name) || /advanced micro/.test(name)) return "AMD" if (/intel/.test(name)) return "Intel" if (/qualcomm/.test(name)) return "QC" if (/microsoft/.test(name)) return "MS" return "GPU" } // 获取核显厂商简称 function getIgpuVendorName() { if (!root.ready) return qsTr("Integrated") var lines = root.deviceInfo.split("\n").filter(function(l) { return l.trim().length > 0 }) for (var i = 0; i < lines.length; i++) { if (!/nvidia/i.test(lines[i])) { return parseGpuVendorShort(lines[i]) } } return qsTr("Integrated") } // 构建 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 gesturePolicy: TapHandler.ReleaseWithinBounds onTapped: { if (driverPopup.popupVisible) { driverPopup.close() } else { Panel.requestClosePopup() const point = root.mapToItem(null, root.width / 2, root.height / 2) driverPopup.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height) driverPopup.open() } toolTip.close() } } // 设置窗口(无边框 + 毛玻璃风格) 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 { acceptedButtons: Qt.RightButton gesturePolicy: TapHandler.ReleaseWithinBounds onTapped: { settingsWindow.show() } } }