diff --git a/amd-gpu-monitor/BarWidget.qml b/amd-gpu-monitor/BarWidget.qml new file mode 100644 index 000000000..260d7c47b --- /dev/null +++ b/amd-gpu-monitor/BarWidget.qml @@ -0,0 +1,585 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property var pluginApi: null + property ShellScreen screen + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + readonly property string screenName: screen ? screen.name : "" + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + readonly property var mon: pluginApi?.mainInstance + readonly property var cfg: pluginApi?.pluginSettings ?? pluginApi?.manifest?.metadata?.defaultSettings ?? ({}) + + readonly property bool compactMode: cfg.compactMode !== undefined ? cfg.compactMode : true + readonly property string iconColorKey: cfg.iconColor !== undefined ? cfg.iconColor : "primary" + readonly property string textColorKey: cfg.textColor !== undefined ? cfg.textColor : "onSurface" + readonly property bool useMonospaceFont: cfg.useMonospaceFont !== undefined ? cfg.useMonospaceFont : true + readonly property bool usePadding: !compactMode && !isVertical && useMonospaceFont && (cfg.usePadding !== undefined ? cfg.usePadding : false) + readonly property string fontFamily: useMonospaceFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault + readonly property color iconColor: Color.resolveColorKey(iconColorKey === "none" ? "primary" : iconColorKey) + readonly property color textColor: Color.resolveColorKey(textColorKey === "none" ? "onSurface" : textColorKey) + + function cfgBool(key, fallback) { + return cfg[key] !== undefined ? !!cfg[key] : fallback; + } + + readonly property bool dataReady: mon?.available ?? false + + readonly property int paddingPercent: usePadding ? String("100%").length : 0 + readonly property int paddingTemp: usePadding ? String("999°").length : 0 + readonly property int paddingPower: usePadding ? String("999W").length : 0 + readonly property int paddingMhz: usePadding ? String("9999").length : 0 + readonly property int paddingFan: usePadding ? String("999%").length : 0 + + readonly property real iconSize: Style.toOdd(capsuleHeight * 0.48) + readonly property real miniGaugeWidth: Math.max(3, Style.toOdd(iconSize * 0.25)) + readonly property real powerGaugeMax: mon ? Math.max(50, mon.powerGraphMax) : 300 + + readonly property real contentWidth: isVertical ? capsuleHeight : Math.round(mainGrid.implicitWidth + Style.marginM * 2) + readonly property real contentHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2) : capsuleHeight + + function tempTint(sensor) { + if (!mon) + return iconColor; + if (sensor === "junction") { + if (mon.gpuCritical) + return Color.mError; + if (mon.gpuWarning) + return Color.mWarning; + } + return iconColor; + } + + function clockGaugeMax(history) { + return mon ? mon.historyMax(history, 100) : 100; + } + + function gaugeFillColor(ratio, baseColor) { + if (ratio >= 0.9) + return Color.mError; + if (ratio >= 0.75) + return Color.mTertiary; + return baseColor; + } + + readonly property string displayTempSensor: { + if (cfgBool("showTempJunction", true)) + return "junction"; + if (cfgBool("showTempEdge", false)) + return "edge"; + if (cfgBool("showTempMemory", false)) + return "memory"; + return ""; + } + + readonly property real displayTemp: { + if (!mon || displayTempSensor === "") + return 0; + if (displayTempSensor === "edge") + return mon.tempEdge; + if (displayTempSensor === "memory") + return mon.tempMemory; + return mon.tempJunction; + } + + readonly property var tempGaugeEntries: { + if (!dataReady || !mon) + return []; + const entries = []; + if (cfgBool("showTempJunction", true)) + entries.push({ + "ratio": Math.min(1, mon.tempJunction / 100), + "fillColor": gaugeFillColor(Math.min(1, mon.tempJunction / 100), tempTint("junction")) + }); + if (cfgBool("showTempEdge", false)) + entries.push({ + "ratio": Math.min(1, mon.tempEdge / 100), + "fillColor": gaugeFillColor(Math.min(1, mon.tempEdge / 100), tempTint("edge")) + }); + if (cfgBool("showTempMemory", false)) + entries.push({ + "ratio": Math.min(1, mon.tempMemory / 100), + "fillColor": gaugeFillColor(Math.min(1, mon.tempMemory / 100), tempTint("memory")) + }); + return entries; + } + + readonly property string tempTextValue: mon && displayTempSensor !== "" ? `${Math.round(displayTemp)}°`.padStart(paddingTemp, " ") : "" + readonly property color tempIconTint: tempTint("junction") + readonly property color tempTextTint: tempTint("junction") + + readonly property var vramGaugeEntries: { + if (!dataReady || !mon) + return []; + const entries = []; + if (cfgBool("showVram", true)) + entries.push({ + "ratio": mon.vramPercent / 100, + "fillColor": gaugeFillColor(mon.vramPercent / 100, iconColor) + }); + if (cfgBool("showVramActivity", false)) + entries.push({ + "ratio": mon.vramActivity / 100, + "fillColor": gaugeFillColor(mon.vramActivity / 100, iconColor) + }); + return entries; + } + + readonly property string vramTextValue: { + if (!mon) + return ""; + if (cfgBool("showVram", true)) + return `${Math.round(mon.vramPercent)}%`.padStart(paddingPercent, " "); + if (cfgBool("showVramActivity", false)) + return `${Math.round(mon.vramActivity)}%`.padStart(paddingPercent, " "); + return ""; + } + + readonly property var clockGaugeEntries: { + if (!dataReady || !mon) + return []; + const entries = []; + if (cfgBool("showSclk", true)) + entries.push({ + "ratio": Math.min(1, mon.sclkMhz / clockGaugeMax(mon.sclkHistory)), + "fillColor": gaugeFillColor(Math.min(1, mon.sclkMhz / clockGaugeMax(mon.sclkHistory)), iconColor) + }); + if (cfgBool("showMclk", true)) + entries.push({ + "ratio": Math.min(1, mon.mclkMhz / clockGaugeMax(mon.mclkHistory)), + "fillColor": gaugeFillColor(Math.min(1, mon.mclkMhz / clockGaugeMax(mon.mclkHistory)), iconColor) + }); + if (cfgBool("showFclk", false)) + entries.push({ + "ratio": Math.min(1, mon.fclkMhz / clockGaugeMax(mon.fclkHistory)), + "fillColor": gaugeFillColor(Math.min(1, mon.fclkMhz / clockGaugeMax(mon.fclkHistory)), iconColor) + }); + if (cfgBool("showSocclk", false)) + entries.push({ + "ratio": Math.min(1, mon.socclkMhz / clockGaugeMax(mon.socclkHistory)), + "fillColor": gaugeFillColor(Math.min(1, mon.socclkMhz / clockGaugeMax(mon.socclkHistory)), iconColor) + }); + if (cfgBool("showDcefclk", false)) + entries.push({ + "ratio": Math.min(1, mon.dcefclkMhz / clockGaugeMax(mon.dcefclkHistory)), + "fillColor": gaugeFillColor(Math.min(1, mon.dcefclkMhz / clockGaugeMax(mon.dcefclkHistory)), iconColor) + }); + return entries; + } + + readonly property string clockTextValue: { + if (!mon) + return ""; + if (cfgBool("showSclk", true)) + return mon.sclkMhz > 0 ? `${Math.round(mon.sclkMhz)}`.padStart(paddingMhz, " ") : "—"; + if (cfgBool("showMclk", true)) + return mon.mclkMhz > 0 ? `${Math.round(mon.mclkMhz)}`.padStart(paddingMhz, " ") : "—"; + if (cfgBool("showFclk", false)) + return mon.fclkMhz > 0 ? `${Math.round(mon.fclkMhz)}`.padStart(paddingMhz, " ") : "—"; + if (cfgBool("showSocclk", false)) + return mon.socclkMhz > 0 ? `${Math.round(mon.socclkMhz)}`.padStart(paddingMhz, " ") : "—"; + if (cfgBool("showDcefclk", false)) + return mon.dcefclkMhz > 0 ? `${Math.round(mon.dcefclkMhz)}`.padStart(paddingMhz, " ") : "—"; + return ""; + } + + function clockMhzLabel(mhz) { + return mhz > 0 ? `${Math.round(mhz)} MHz` : "—"; + } + + function buildTooltipContent() { + if (!dataReady || !mon) + return []; + + const rows = []; + const tr = key => pluginApi?.tr(key) ?? key; + + if (mon.productName) + rows.push([mon.productName, ""]); + + if (cfgBool("showGpuUse", true)) + rows.push([tr("metrics.gpu_use"), `${Math.round(mon.gpuUse)}%`]); + + if (cfgBool("showTempJunction", true)) + rows.push([tr("metrics.temp_junction"), `${Math.round(mon.tempJunction)}°C`]); + if (cfgBool("showTempEdge", false)) + rows.push([tr("metrics.temp_edge"), `${Math.round(mon.tempEdge)}°C`]); + if (cfgBool("showTempMemory", false)) + rows.push([tr("metrics.temp_memory"), `${Math.round(mon.tempMemory)}°C`]); + + if (cfgBool("showVram", true)) { + if (mon.vramTotalGb > 0) + rows.push([tr("metrics.vram"), `${Math.round(mon.vramPercent)}% (${mon.vramUsedGb.toFixed(1)} / ${mon.vramTotalGb.toFixed(1)} GiB)`]); + else + rows.push([tr("metrics.vram"), `${Math.round(mon.vramPercent)}%`]); + } + + if (cfgBool("showVramActivity", false)) + rows.push([tr("metrics.vram_activity"), `${Math.round(mon.vramActivity)}%`]); + + if (cfgBool("showFanSpeed", false)) + rows.push([tr("metrics.fan_speed"), `${Math.round(mon.fanSpeed)}%`]); + + if (cfgBool("showPower", true)) + rows.push([tr("metrics.power"), `${mon.powerWatts.toFixed(0)} W`]); + + if (cfgBool("showSclk", true)) + rows.push([tr("metrics.sclk"), clockMhzLabel(mon.sclkMhz)]); + if (cfgBool("showMclk", true)) + rows.push([tr("metrics.mclk"), clockMhzLabel(mon.mclkMhz)]); + if (cfgBool("showFclk", false)) + rows.push([tr("metrics.fclk"), clockMhzLabel(mon.fclkMhz)]); + if (cfgBool("showSocclk", false)) + rows.push([tr("metrics.socclk"), clockMhzLabel(mon.socclkMhz)]); + if (cfgBool("showDcefclk", false)) + rows.push([tr("metrics.dcefclk"), clockMhzLabel(mon.dcefclkMhz)]); + + return rows; + } + + implicitWidth: contentWidth + implicitHeight: contentHeight + + Component.onCompleted: { + if (mon) + mon.registerPoller("bar:" + (screenName || "unknown")); + } + + Component.onDestruction: { + if (mon) + mon.unregisterPoller("bar:" + (screenName || "unknown")); + } + + Rectangle { + id: visualCapsule + anchors.centerIn: parent + width: root.contentWidth + height: root.contentHeight + radius: Style.radiusL + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + GridLayout { + id: mainGrid + anchors.centerIn: parent + flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: isVertical ? -1 : 1 + columns: isVertical ? 1 : -1 + rowSpacing: isVertical ? (compactMode ? Style.marginL : Style.marginXL) : 0 + columnSpacing: isVertical ? 0 : Style.marginM + + StatCell { + visible: cfgBool("showGpuUse", true) && dataReady + metricIcon: "activity" + percentText: `${Math.round(mon.gpuUse)}%`.padStart(paddingPercent, " ") + gaugeRatio: mon.gpuUse / 100 + iconTint: iconColor + textTint: textColor + gaugeTint: gaugeFillColor(mon.gpuUse / 100, iconColor) + } + + GroupedStatCell { + visible: tempGaugeEntries.length > 0 + metricIcon: "flame" + iconTint: tempIconTint + textValue: tempTextValue + textTint: tempTextTint + gaugeEntries: tempGaugeEntries + } + + GroupedStatCell { + visible: vramGaugeEntries.length > 0 + metricIcon: "database" + iconTint: iconColor + textValue: vramTextValue + textTint: textColor + gaugeEntries: vramGaugeEntries + } + + StatCell { + visible: cfgBool("showPower", true) && dataReady + metricIcon: "bolt" + percentText: `${Math.round(mon.powerWatts)}W`.padStart(paddingPower, " ") + gaugeRatio: Math.min(1, mon.powerWatts / powerGaugeMax) + iconTint: iconColor + textTint: textColor + gaugeTint: gaugeFillColor(Math.min(1, mon.powerWatts / powerGaugeMax), iconColor) + } + + StatCell { + visible: cfgBool("showFanSpeed", false) && dataReady + metricIcon: "car-fan" + percentText: `${Math.round(mon.fanSpeed)}%`.padStart(paddingFan, " ") + gaugeRatio: mon.fanSpeed / 100 + iconTint: iconColor + textTint: textColor + gaugeTint: gaugeFillColor(mon.fanSpeed / 100, iconColor) + } + + GroupedStatCell { + visible: clockGaugeEntries.length > 0 + metricIcon: "clock" + iconTint: iconColor + textValue: clockTextValue + textTint: textColor + gaugeEntries: clockGaugeEntries + } + + Item { + visible: !dataReady + Layout.preferredWidth: iconSize + Layout.preferredHeight: iconSize + Layout.alignment: Qt.AlignHCenter + + NIcon { + anchors.centerIn: parent + icon: "thermometer" + pointSize: iconSize + applyUiScale: false + color: Color.mOnSurfaceVariant + } + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: function (mouse) { + if (mouse.button === Qt.LeftButton) { + if (pluginApi) + pluginApi.openPanel(root.screen, root); + } else if (mouse.button === Qt.RightButton) { + TooltipService.hide(); + PanelService.showContextMenu(contextMenu, root, screen); + } + } + + onEntered: { + const rows = buildTooltipContent(); + if (rows.length > 0) + TooltipService.show(root, rows, BarService.getTooltipDirection(screenName)); + tooltipRefreshTimer.start(); + } + + onExited: { + tooltipRefreshTimer.stop(); + TooltipService.hide(); + } + } + + Timer { + id: tooltipRefreshTimer + interval: mon ? mon.pollIntervalMs : 1000 + repeat: true + onTriggered: { + const rows = buildTooltipContent(); + if (rows.length > 0) + TooltipService.updateText(rows); + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": pluginApi?.tr("actions.widget_settings") || "Settings", + "action": "widget-settings", + "icon": "settings" + } + ] + + onTriggered: action => { + contextMenu.close(); + PanelService.closeContextMenu(screen); + if (action === "widget-settings") + BarService.openPluginSettings(screen, pluginApi.manifest); + } + } + + component StatCell: Item { + id: cell + + property string metricIcon: "" + property string percentText: "" + property real gaugeRatio: 0 + property color iconTint: root.iconColor + property color textTint: root.textColor + property color gaugeTint: root.iconColor + property bool showGauge: true + property bool forceShowText: false + + implicitWidth: cellContent.implicitWidth + implicitHeight: cellContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + + GridLayout { + id: cellContent + anchors.centerIn: parent + flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: (isVertical && !compactMode) ? 2 : 1 + columns: (isVertical && !compactMode) ? 1 : 2 + rowSpacing: compactMode ? 3 : Style.marginXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: cell.metricIcon + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, (compactMode || isVertical) ? iconSize : capsuleHeight) + color: cell.iconTint + } + } + + NText { + visible: !compactMode || cell.forceShowText + text: cell.percentText + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: cell.textTint + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + Loader { + active: compactMode && cell.showGauge + visible: compactMode && cell.showGauge + sourceComponent: miniGaugeComponent + Layout.alignment: Qt.AlignCenter + Layout.row: 0 + Layout.column: 1 + + onLoaded: { + item.ratio = Qt.binding(() => cell.gaugeRatio); + item.fillColor = Qt.binding(() => cell.gaugeTint); + } + } + } + + Component { + id: miniGaugeComponent + + NLinearGauge { + ratio: 0 + orientation: Qt.Vertical + fillColor: Color.mPrimary + width: root.miniGaugeWidth + height: root.iconSize + } + } + } + + component GroupedStatCell: Item { + id: group + + property string metricIcon: "" + property color iconTint: root.iconColor + property string textValue: "" + property color textTint: root.textColor + property var gaugeEntries: [] + + readonly property bool verticalGaugeLayout: isVertical && (!compactMode || gaugeEntries.length > 0) + + implicitWidth: groupContent.implicitWidth + implicitHeight: groupContent.implicitHeight + Layout.preferredWidth: isVertical ? root.width : implicitWidth + Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight + Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter + + GridLayout { + id: groupContent + anchors.centerIn: parent + flow: verticalGaugeLayout ? GridLayout.TopToBottom : GridLayout.LeftToRight + rows: verticalGaugeLayout ? -1 : 1 + columns: verticalGaugeLayout ? 1 : -1 + rowSpacing: compactMode ? 3 : Style.marginXS + columnSpacing: compactMode ? 3 : Style.marginXS + + Item { + Layout.preferredWidth: iconSize + Layout.preferredHeight: (compactMode || isVertical) ? iconSize : capsuleHeight + Layout.alignment: Qt.AlignCenter + Layout.row: (isVertical && !compactMode) ? 1 : 0 + Layout.column: 0 + + NIcon { + icon: group.metricIcon + pointSize: iconSize + applyUiScale: false + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, (compactMode || isVertical) ? iconSize : capsuleHeight) + color: group.iconTint + } + } + + NText { + visible: !compactMode + text: group.textValue + family: fontFamily + pointSize: barFontSize + applyUiScale: false + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: group.textTint + Layout.row: isVertical ? 0 : 0 + Layout.column: isVertical ? 0 : 1 + } + + Row { + visible: compactMode && group.gaugeEntries.length > 0 + spacing: 3 + Layout.alignment: Qt.AlignCenter + Layout.row: verticalGaugeLayout ? 1 : 0 + Layout.column: verticalGaugeLayout ? 0 : 1 + + Repeater { + model: group.gaugeEntries + + delegate: NLinearGauge { + required property var modelData + width: isVertical ? iconSize : miniGaugeWidth + height: isVertical ? miniGaugeWidth : iconSize + orientation: isVertical ? Qt.Horizontal : Qt.Vertical + ratio: modelData.ratio + fillColor: modelData.fillColor + } + } + } + } + } +} diff --git a/amd-gpu-monitor/Main.qml b/amd-gpu-monitor/Main.qml new file mode 100644 index 000000000..1a2ed1bcf --- /dev/null +++ b/amd-gpu-monitor/Main.qml @@ -0,0 +1,275 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Item { + id: root + + property var pluginApi: null + + readonly property int pollIntervalMs: 1000 + readonly property int historyLength: 300 + + property bool available: false + property string lastError: "" + property string productName: "" + property string cardKey: "card0" + + property real tempEdge: 0 + property real tempJunction: 0 + property real tempMemory: 0 + property real fanSpeed: 0 + property real gpuUse: 0 + property real vramPercent: 0 + property real vramActivity: 0 + property real vramUsedGb: 0 + property real vramTotalGb: 0 + property real powerWatts: 0 + property real sclkMhz: 0 + property real mclkMhz: 0 + property real fclkMhz: 0 + property real socclkMhz: 0 + property real dcefclkMhz: 0 + + property var gpuUseHistory: new Array(historyLength).fill(0) + property var tempEdgeHistory: new Array(historyLength).fill(40) + property var tempJunctionHistory: new Array(historyLength).fill(40) + property var tempMemoryHistory: new Array(historyLength).fill(40) + property var fanSpeedHistory: new Array(historyLength).fill(0) + property var vramHistory: new Array(historyLength).fill(0) + property var powerHistory: new Array(historyLength).fill(0) + property var vramActivityHistory: new Array(historyLength).fill(0) + property var sclkHistory: new Array(historyLength).fill(0) + property var mclkHistory: new Array(historyLength).fill(0) + property var fclkHistory: new Array(historyLength).fill(0) + property var socclkHistory: new Array(historyLength).fill(0) + property var dcefclkHistory: new Array(historyLength).fill(0) + + readonly property var settings: pluginApi?.pluginSettings ?? pluginApi?.manifest?.metadata?.defaultSettings ?? ({}) + + readonly property int deviceIndex: intOrDefault(settings.deviceIndex, 0) + + readonly property int gpuWarningThreshold: Settings.data.systemMonitor.gpuWarningThreshold + readonly property int gpuCriticalThreshold: Settings.data.systemMonitor.gpuCriticalThreshold + readonly property bool gpuWarning: available && tempJunction >= gpuWarningThreshold + readonly property bool gpuCritical: available && tempJunction >= gpuCriticalThreshold + + readonly property real powerGraphMax: { + let max = 0; + for (let i = 0; i < powerHistory.length; i++) + max = Math.max(max, powerHistory[i]); + return Math.max(50, max * 1.2); + } + + function refreshNow() { + if (!rocmProcess.running) + rocmProcess.running = true; + } + + function registerPoller(consumerId) { + if (!_consumers[consumerId]) { + _consumers[consumerId] = true; + _consumers = Object.assign({}, _consumers); + } + } + + function unregisterPoller(consumerId) { + delete _consumers[consumerId]; + _consumers = Object.assign({}, _consumers); + } + + property var _consumers: ({}) + + readonly property bool shouldPoll: Object.keys(_consumers).length > 0 + + function intOrDefault(value, fallback) { + return (typeof value === "number") ? Math.floor(value) : fallback; + } + + function parseNumber(value) { + if (value === undefined || value === null) + return 0; + const n = parseFloat(String(value).replace(/[^0-9.-]/g, "")); + return isNaN(n) ? 0 : n; + } + + function parseMhz(value) { + if (!value) + return 0; + const m = String(value).match(/(\d+(?:\.\d+)?)\s*Mhz/i); + return m ? parseFloat(m[1]) : 0; + } + + function readMhz(card, key, current) { + if (!card || card[key] === undefined || card[key] === null) + return current; + const text = String(card[key]).trim(); + if (text === "" || text === "N/A") + return current; + const mhz = parseMhz(text); + if (mhz <= 0 && !/Mhz/i.test(text)) + return current; + return mhz; + } + + function bytesToGb(bytes) { + return bytes / (1024 * 1024 * 1024); + } + + function pushHistory(array, value, length) { + const h = array.slice(); + h.push(value); + while (h.length > length) + h.shift(); + return h; + } + + function historyMax(values, floorValue) { + let max = floorValue || 0; + for (let i = 0; i < values.length; i++) + max = Math.max(max, values[i]); + return Math.max(floorValue || 1, max * 1.1); + } + + function updateHistories() { + gpuUseHistory = pushHistory(gpuUseHistory, gpuUse, historyLength); + tempEdgeHistory = pushHistory(tempEdgeHistory, tempEdge, historyLength); + tempJunctionHistory = pushHistory(tempJunctionHistory, tempJunction, historyLength); + tempMemoryHistory = pushHistory(tempMemoryHistory, tempMemory, historyLength); + fanSpeedHistory = pushHistory(fanSpeedHistory, fanSpeed, historyLength); + vramHistory = pushHistory(vramHistory, vramPercent, historyLength); + powerHistory = pushHistory(powerHistory, powerWatts, historyLength); + vramActivityHistory = pushHistory(vramActivityHistory, vramActivity, historyLength); + sclkHistory = pushHistory(sclkHistory, sclkMhz, historyLength); + mclkHistory = pushHistory(mclkHistory, mclkMhz, historyLength); + fclkHistory = pushHistory(fclkHistory, fclkMhz, historyLength); + socclkHistory = pushHistory(socclkHistory, socclkMhz, historyLength); + dcefclkHistory = pushHistory(dcefclkHistory, dcefclkMhz, historyLength); + } + + function applyCardData(card) { + if (!card) + return; + + tempEdge = parseNumber(card["Temperature (Sensor edge) (C)"]); + tempJunction = parseNumber(card["Temperature (Sensor junction) (C)"]); + tempMemory = parseNumber(card["Temperature (Sensor memory) (C)"]); + fanSpeed = parseNumber(card["Fan speed (%)"]); + gpuUse = parseNumber(card["GPU use (%)"]); + vramPercent = parseNumber(card["GPU Memory Allocated (VRAM%)"]); + vramActivity = parseNumber(card["GPU Memory Read/Write Activity (%)"]); + powerWatts = parseNumber(card["Average Graphics Package Power (W)"]); + + const vramTotalB = parseNumber(card["VRAM Total Memory (B)"]); + const vramUsedB = parseNumber(card["VRAM Total Used Memory (B)"]); + if (vramTotalB > 0) { + vramTotalGb = bytesToGb(vramTotalB); + vramUsedGb = bytesToGb(vramUsedB); + if (!vramPercent) + vramPercent = (vramUsedB / vramTotalB) * 100; + } + + sclkMhz = readMhz(card, "sclk clock speed:", sclkMhz); + mclkMhz = readMhz(card, "mclk clock speed:", mclkMhz); + fclkMhz = readMhz(card, "fclk clock speed:", fclkMhz); + socclkMhz = readMhz(card, "socclk clock speed:", socclkMhz); + dcefclkMhz = readMhz(card, "dcefclk clock speed:", dcefclkMhz); + + const series = card["Card Series"]; + if (series) + productName = series; + + available = true; + lastError = ""; + updateHistories(); + } + + function parseRocmOutput(data) { + const text = String(data || "").trim(); + if (!text) { + lastError = "empty output"; + available = false; + return; + } + + try { + const parsed = JSON.parse(text); + const keys = Object.keys(parsed); + if (keys.length === 0) { + lastError = "no GPU data"; + available = false; + return; + } + cardKey = keys[0]; + applyCardData(parsed[cardKey]); + } catch (e) { + lastError = String(e); + available = false; + Logger.e("AmdGpuMonitor", "Failed to parse rocm-smi JSON:", e, text.substring(0, 200)); + } + } + + readonly property var rocmCommand: [ + "rocm-smi", + "--json", + "-d", + String(deviceIndex), + "--showuse", + "--showtemp", + "--showfan", + "--showmemuse", + "--showpower", + "--showclocks", + "--showmeminfo", + "vram", + "--showproductname" + ] + + Process { + id: rocmProcess + running: false + command: root.rocmCommand + + stdout: StdioCollector { + onStreamFinished: root.parseRocmOutput(text) + } + + onExited: function (exitCode) { + rocmProcess.running = false; + if (exitCode !== 0 && !root.available) + root.lastError = "rocm-smi exited with code " + exitCode; + } + } + + Timer { + id: pollTimer + interval: root.pollIntervalMs + repeat: true + running: root.shouldPoll && !rocmProcess.running + triggeredOnStart: true + onTriggered: rocmProcess.running = true + } + + Component.onCompleted: { + if (pluginApi) + Logger.i("AmdGpuMonitor", "Main instance loaded, device index:", deviceIndex); + } + + IpcHandler { + target: "plugin:amd-gpu-monitor" + + function togglePanel() { + if (!pluginApi) + return; + pluginApi.withCurrentScreen(screen => { + pluginApi.togglePanel(screen); + }); + } + + function refresh() { + if (!rocmProcess.running) + rocmProcess.running = true; + } + } +} diff --git a/amd-gpu-monitor/Panel.qml b/amd-gpu-monitor/Panel.qml new file mode 100644 index 000000000..1a969c9e1 --- /dev/null +++ b/amd-gpu-monitor/Panel.qml @@ -0,0 +1,610 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Item { + id: root + + property var pluginApi: null + + readonly property var geometryPlaceholder: panelContainer + readonly property bool allowAttach: true + + property real contentPreferredWidth: 440 * Style.uiScaleRatio + property real contentPreferredHeight: panelContainer.implicitHeight + + readonly property var mon: pluginApi?.mainInstance + readonly property var cfg: pluginApi?.pluginSettings ?? pluginApi?.manifest?.metadata?.defaultSettings ?? ({}) + + readonly property bool dataReady: mon?.available ?? false + readonly property real headerToMetricsGap: 8 + + function cfgBool(key, fallback) { + return cfg[key] !== undefined ? !!cfg[key] : fallback; + } + + readonly property string iconColorKey: cfg.iconColor !== undefined ? cfg.iconColor : "primary" + readonly property color graphColor: Color.resolveColorKey(iconColorKey === "none" ? "primary" : iconColorKey) + + // Цвета для разных линий графиков (берем из темы) + readonly property var lineColors: [ + Color.mPrimary, + Color.mSecondary, + Color.mTertiary, + Color.resolveColorKey("error"), + Color.resolveColorKey("warning"), + ] + + function getLineColor(index) { + return lineColors[index % lineColors.length]; + } + + // Функция для определения цвета на основе процента (75% warning, 90% critical) + function getThresholdColor(percentValue) { + if (percentValue >= 90) + return Color.resolveColorKey("error"); + if (percentValue >= 75) + return Color.mTertiary; + return graphColor; + } + + // Цвет для температурных датчиков + function tempGraphColor(sensorType) { + if (sensorType === "junction") + return Color.mPrimary; + if (sensorType === "edge") + return Color.mSecondary; + if (sensorType === "memory") + return Color.mTertiary; + return graphColor; + } + + // Цвет для температурных датчиков с учетом предупреждений + function getTempColor(sensorType) { + if (!mon) + return tempGraphColor(sensorType); + + if (sensorType === "junction") { + if (mon.gpuCritical) + return Color.resolveColorKey("error"); + if (mon.gpuWarning) + return Color.resolveColorKey("warning"); + } + + return tempGraphColor(sensorType); + } + + function clockMhzLabel(mhz) { + return mhz > 0 ? `${Math.round(mhz)} MHz` : "—"; + } + + readonly property string displayTempSensor: { + if (cfgBool("showTempJunction", true)) + return "junction"; + if (cfgBool("showTempEdge", false)) + return "edge"; + if (cfgBool("showTempMemory", false)) + return "memory"; + return ""; + } + + readonly property real displayTemp: { + if (!mon || displayTempSensor === "") + return 0; + if (displayTempSensor === "edge") + return mon.tempEdge; + if (displayTempSensor === "memory") + return mon.tempMemory; + return mon.tempJunction; + } + + readonly property string displayClockKey: { + if (cfgBool("showSclk", true)) + return "sclk"; + if (cfgBool("showMclk", true)) + return "mclk"; + if (cfgBool("showFclk", false)) + return "fclk"; + if (cfgBool("showSocclk", false)) + return "socclk"; + if (cfgBool("showDcefclk", false)) + return "dcefclk"; + return ""; + } + + function clockMhzForKey(key) { + if (!mon || !key) + return 0; + if (key === "mclk") + return mon.mclkMhz; + if (key === "fclk") + return mon.fclkMhz; + if (key === "socclk") + return mon.socclkMhz; + if (key === "dcefclk") + return mon.dcefclkMhz; + return mon.sclkMhz; + } + + readonly property string displayClockValue: mon && displayClockKey !== "" ? clockMhzLabel(clockMhzForKey(displayClockKey)) : "—" + + readonly property string displayVramValue: { + if (!mon) + return "—"; + if (cfgBool("showVram", true)) { + if (mon.vramTotalGb > 0) + return `${mon.vramUsedGb.toFixed(1)} / ${mon.vramTotalGb.toFixed(1)} GiB`; + return `${Math.round(mon.vramPercent)}%`; + } + if (cfgBool("showVramActivity", false)) + return `${Math.round(mon.vramActivity)}%`; + return "—"; + } + + Component.onCompleted: { + if (mon) + mon.registerPoller("panel:" + (pluginApi?.instanceId ?? "unknown")); + } + + Component.onDestruction: { + if (mon) + mon.unregisterPoller("panel:" + (pluginApi?.instanceId ?? "unknown")); + } + + NBox { + id: panelContainer + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginS + implicitHeight: mainColumn.implicitHeight + Style.marginM * 2 + color: Color.mSurface + border.width: 0 + radius: Style.radiusL + + ColumnLayout { + id: mainColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: headerToMetricsGap + + // Заголовок + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NIcon { + icon: "device-desktop-analytics" + pointSize: 24 * Style.uiScaleRatio + color: Color.mOnSurface + } + + NText { + text: pluginApi?.tr("panel.title") ?? "AMD GPU Monitor" + pointSize: Style.fontSizeM + color: Color.mOnSurface + } + + Item { Layout.fillWidth: true } + + NText { + text: mon ? mon.productName : "—" + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + } + + NBox { + id: errorBox + visible: !dataReady + Layout.fillWidth: true + implicitHeight: errorCol.implicitHeight + Style.marginM * 2 + color: Color.resolveColorKey("errorContainer") + border.color: Color.resolveColorKey("error") + border.width: 1 + radius: Style.radiusM + + ColumnLayout { + id: errorCol + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM + spacing: Style.marginS + + NText { + text: pluginApi?.tr("panel.unavailable") ?? "GPU data unavailable" + pointSize: Style.fontSizeS + color: Color.resolveColorKey("onErrorContainer") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + NText { + text: mon?.lastError || pluginApi?.tr("panel.check_rocm") || "" + pointSize: Style.fontSizeXS + color: Color.resolveColorKey("onErrorContainer") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + } + + // Основные метрики + ColumnLayout { + visible: dataReady + Layout.fillWidth: true + spacing: Style.marginM + + // GPU Use + MetricCard { + id: gpuUseCard + cardIcon: "activity" + cardTitle: pluginApi?.tr("metrics.gpu_use") ?? "GPU Use" + displayValue: mon ? `${Math.round(mon.gpuUse)}%` : "—" + displayColor: getThresholdColor(mon?.gpuUse ?? 0) + graphValues: mon ? mon.gpuUseHistory : [] + graphMin: 0 + graphMax: 100 + pollMs: mon ? mon.pollIntervalMs : 1000 + + readonly property bool _isVisible: cfgBool("showGpuUse", true) + visible: _isVisible + Layout.fillWidth: true + } + + // Temperatures (Grouped) + GroupedMetricCard { + id: tempCard + cardIcon: "flame" + cardTitle: pluginApi?.tr("metrics.temperature") ?? "Temperature" + displayValue: mon && displayTempSensor !== "" ? `${Math.round(displayTemp)}°C` : "—" + displayColor: getTempColor(displayTempSensor || "junction") + graphMin: 0 // Явно задаем минимум для температур + graphMax: 100 // Фиксированный максимум для температур + pollMs: mon ? mon.pollIntervalMs : 1000 + lines: [ + { + visible: cfgBool("showTempJunction", true), + values: mon ? mon.tempJunctionHistory : [], + color: tempGraphColor("junction"), + isPrimary: displayTempSensor === "junction", + label: pluginApi?.tr("temp.junction") ?? "Junction" + }, + { + visible: cfgBool("showTempEdge", false), + values: mon ? mon.tempEdgeHistory : [], + color: tempGraphColor("edge"), + isPrimary: displayTempSensor === "edge", + label: pluginApi?.tr("temp.edge") ?? "Edge" + }, + { + visible: cfgBool("showTempMemory", false), + values: mon ? mon.tempMemoryHistory : [], + color: tempGraphColor("memory"), + isPrimary: displayTempSensor === "memory", + label: pluginApi?.tr("temp.memory") ?? "Memory" + } + ] + + readonly property bool _isVisible: cfgBool("showTempJunction", true) || cfgBool("showTempEdge", false) || cfgBool("showTempMemory", false) + visible: _isVisible + Layout.fillWidth: true + } + + // VRAM (Grouped: VRAM Usage + VRAM Activity) + GroupedMetricCard { + id: vramCard + cardIcon: "database" + cardTitle: pluginApi?.tr("metrics.vram") ?? "VRAM" + displayValue: displayVramValue + displayColor: graphColor + graphMin: 0 + graphMax: 100 + pollMs: mon ? mon.pollIntervalMs : 1000 + lines: [ + { + visible: cfgBool("showVram", true), + values: mon ? mon.vramHistory : [], + color: graphColor, + isPrimary: cfgBool("showVram", true), + label: pluginApi?.tr("vram.usage") ?? "Usage" + }, + { + visible: cfgBool("showVramActivity", false), + values: mon ? mon.vramActivityHistory : [], + color: getLineColor(1), + isPrimary: !cfgBool("showVram", true) && cfgBool("showVramActivity", false), + label: pluginApi?.tr("vram.activity") ?? "Activity" + } + ] + + readonly property bool _isVisible: cfgBool("showVram", true) || cfgBool("showVramActivity", false) + visible: _isVisible + Layout.fillWidth: true + } + + // Fan Speed + MetricCard { + id: fanSpeedCard + cardIcon: "car-fan" + cardTitle: pluginApi?.tr("metrics.fan_speed") ?? "Fan Speed" + displayValue: mon ? `${Math.round(mon.fanSpeed)}%` : "—" + displayColor: graphColor + graphValues: mon ? mon.fanSpeedHistory : [] + graphMin: 0 + graphMax: 100 + pollMs: mon ? mon.pollIntervalMs : 1000 + + readonly property bool _isVisible: cfgBool("showFanSpeed", false) + visible: _isVisible + Layout.fillWidth: true + } + + // Power + MetricCard { + id: powerCard + cardIcon: "bolt" + cardTitle: pluginApi?.tr("metrics.power") ?? "Power" + displayValue: mon ? `${mon.powerWatts.toFixed(0)} W` : "—" + displayColor: graphColor + graphValues: mon ? mon.powerHistory : [] + graphMin: 0 + graphMax: mon ? Math.max(50, mon.powerGraphMax ?? 300) : 300 + pollMs: mon ? mon.pollIntervalMs : 1000 + + readonly property bool _isVisible: cfgBool("showPower", true) + visible: _isVisible + Layout.fillWidth: true + } + + // Clocks (Grouped: SCLK, MCLK, FCLK, SOCCLK, DCEFLK) + GroupedMetricCard { + id: clocksCard + cardIcon: "clock" + cardTitle: pluginApi?.tr("metrics.clock_speeds") ?? "Clock speeds" + displayValue: displayClockValue + displayColor: graphColor + graphMin: 0 + graphMax: mon ? Math.max( + 100, + cfgBool("showSclk", true) ? mon.historyMax(mon.sclkHistory, 100) : 0, + cfgBool("showMclk", true) ? mon.historyMax(mon.mclkHistory, 100) : 0, + cfgBool("showFclk", false) ? mon.historyMax(mon.fclkHistory, 100) : 0, + cfgBool("showSocclk", false) ? mon.historyMax(mon.socclkHistory, 100) : 0, + cfgBool("showDcefclk", false) ? mon.historyMax(mon.dcefclkHistory, 100) : 0 + ) : 100 + pollMs: mon ? mon.pollIntervalMs : 1000 + lines: [ + { + visible: cfgBool("showSclk", true), + values: mon ? mon.sclkHistory : [], + color: graphColor, + isPrimary: displayClockKey === "sclk", + label: pluginApi?.tr("clocks.sclk") ?? "SCLK" + }, + { + visible: cfgBool("showMclk", true), + values: mon ? mon.mclkHistory : [], + color: getLineColor(1), + isPrimary: displayClockKey === "mclk", + label: pluginApi?.tr("clocks.mclk") ?? "MCLK" + }, + { + visible: cfgBool("showFclk", false), + values: mon ? mon.fclkHistory : [], + color: getLineColor(2), + isPrimary: displayClockKey === "fclk", + label: pluginApi?.tr("clocks.fclk") ?? "FCLK" + }, + { + visible: cfgBool("showSocclk", false), + values: mon ? mon.socclkHistory : [], + color: getLineColor(3), + isPrimary: displayClockKey === "socclk", + label: pluginApi?.tr("clocks.socclk") ?? "SOCCLK" + }, + { + visible: cfgBool("showDcefclk", false), + values: mon ? mon.dcefclkHistory : [], + color: getLineColor(4), + isPrimary: displayClockKey === "dcefclk", + label: pluginApi?.tr("clocks.dcefclk") ?? "DCEFCLK" + } + ] + + readonly property bool _isVisible: cfgBool("showSclk", true) || cfgBool("showMclk", true) || cfgBool("showFclk", false) || cfgBool("showSocclk", false) || cfgBool("showDcefclk", false) + visible: _isVisible + Layout.fillWidth: true + } + + // VRAM Activity (Separate Card if needed) + MetricCard { + id: vramActivityCard + cardIcon: "chart-line" + cardTitle: pluginApi?.tr("metrics.vram_activity") ?? "VRAM Activity" + displayValue: mon ? `${Math.round(mon.vramActivity)}%` : "—" + displayColor: getLineColor(1) + graphValues: mon ? mon.vramActivityHistory : [] + graphMin: 0 + graphMax: 100 + pollMs: mon ? mon.pollIntervalMs : 1000 + + readonly property bool _isVisible: cfgBool("showVramActivity", false) && !cfgBool("showVram", true) + visible: _isVisible + Layout.fillWidth: true + } + } + } + } + + // Компонент для одиночных метрик + component MetricCard: NBox { + id: card + + property string cardIcon: "" + property string cardTitle: "" + property string displayValue: "" + property color displayColor: Color.mPrimary + property var graphValues: [] + property real graphMin: 0 + property real graphMax: 100 + property int pollMs: 1000 + + Layout.preferredHeight: (100 + 8) * Style.uiScaleRatio + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM + spacing: Style.marginXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIcon { + icon: card.cardIcon + pointSize: Style.fontSizeXS + color: card.displayColor + } + + NText { + text: card.cardTitle + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + Item { + Layout.fillWidth: true + } + + NText { + text: card.displayValue + pointSize: Style.fontSizeXS + color: card.displayColor + font.family: Settings.data.ui.fontFixed + } + } + + NGraph { + id: singleGraph + Layout.fillWidth: true + Layout.preferredHeight: 48 * Style.uiScaleRatio + values: card.graphValues + minValue: card.graphMin + maxValue: card.graphMax + color: card.displayColor + strokeWidth: Math.max(1, Style.uiScaleRatio) + fill: true + fillOpacity: 0.15 + updateInterval: card.pollMs + animateScale: true + } + } + } + + // Компонент для группированных метрик с несколькими линиями + component GroupedMetricCard: NBox { + id: groupedCard + + property string cardIcon: "" + property string cardTitle: "" + property string displayValue: "" + property color displayColor: Color.mPrimary + property real graphMin: 0 + property real graphMax: 100 + property int pollMs: 1000 + + // Массив объектов, описывающих линии графика: { visible, values, color, isPrimary } + property var lines: [] + + Layout.preferredHeight: (138) * Style.uiScaleRatio + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginS + anchors.bottomMargin: Style.radiusM + spacing: Style.marginXS + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS + + NIcon { + icon: groupedCard.cardIcon + pointSize: Style.fontSizeXS + color: groupedCard.displayColor + } + + NText { + text: groupedCard.cardTitle + pointSize: Style.fontSizeXS + color: Color.mOnSurfaceVariant + } + + Item { + Layout.fillWidth: true + } + + NText { + text: groupedCard.displayValue + pointSize: Style.fontSizeXS + color: groupedCard.displayColor + font.family: Settings.data.ui.fontFixed + } + } + + // Подписи линий + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + Repeater { + model: groupedCard.lines.length + + NText { + required property int index + + visible: groupedCard.lines[index].visible + text: groupedCard.lines[index].label ?? "" + pointSize: Style.fontSizeXS + color: groupedCard.lines[index].color + font.family: Settings.data.ui.fontFixed + } + } + } + + // Контейнер для наложенных графиков + Item { + id: graphContainer + Layout.fillWidth: true + Layout.preferredHeight: 48 * Style.uiScaleRatio + + Repeater { + model: groupedCard.lines.length + + NGraph { + required property int index + + anchors.fill: parent + visible: groupedCard.lines[index].visible + values: groupedCard.lines[index].values + minValue: groupedCard.graphMin + maxValue: groupedCard.graphMax + color: groupedCard.lines[index].color + strokeWidth: Math.max(1, Style.uiScaleRatio) + fill: groupedCard.lines[index].isPrimary // Заполняем только основную линию + fillOpacity: 0.15 + updateInterval: groupedCard.pollMs + animateScale: true + z: groupedCard.lines[index].isPrimary ? 1 : 0 // Основная линия сверху + } + } + } + } + } +} \ No newline at end of file diff --git a/amd-gpu-monitor/Settings.qml b/amd-gpu-monitor/Settings.qml new file mode 100644 index 000000000..581c071d5 --- /dev/null +++ b/amd-gpu-monitor/Settings.qml @@ -0,0 +1,270 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + property var pluginApi: null + readonly property var mainInstance: pluginApi?.mainInstance + readonly property var defaults: pluginApi?.manifest?.metadata?.defaultSettings ?? ({}) + + function settingBool(key, fallback) { + const s = pluginApi?.pluginSettings; + return s && s[key] !== undefined ? !!s[key] : fallback; + } + + property bool valueShowGpuUse: true + property bool valueShowTempJunction: true + property bool valueShowTempEdge: false + property bool valueShowTempMemory: false + property bool valueShowVram: true + property bool valueShowVramActivity: false + property bool valueShowFanSpeed: false + property bool valueShowPower: true + property bool valueShowSclk: true + property bool valueShowMclk: true + property bool valueShowFclk: false + property bool valueShowSocclk: false + property bool valueShowDcefclk: false + property bool valueCompactMode: true + property string valueIconColor: "primary" + property string valueTextColor: "onSurface" + property bool valueUseMonospaceFont: true + property bool valueUsePadding: false + + property int valueDeviceIndex: 0 + + spacing: Style.marginL + + Component.onCompleted: loadFromPluginSettings() + + function loadFromPluginSettings() { + const d = defaults; + valueShowGpuUse = settingBool("showGpuUse", d.showGpuUse ?? true); + valueShowTempJunction = settingBool("showTempJunction", d.showTempJunction ?? true); + valueShowTempEdge = settingBool("showTempEdge", d.showTempEdge ?? false); + valueShowTempMemory = settingBool("showTempMemory", d.showTempMemory ?? false); + valueShowVram = settingBool("showVram", d.showVram ?? true); + valueShowVramActivity = settingBool("showVramActivity", d.showVramActivity ?? false); + valueShowFanSpeed = settingBool("showFanSpeed", d.showFanSpeed ?? false); + valueShowPower = settingBool("showPower", d.showPower ?? true); + valueShowSclk = settingBool("showSclk", d.showSclk ?? true); + valueShowMclk = settingBool("showMclk", d.showMclk ?? true); + valueShowFclk = settingBool("showFclk", d.showFclk ?? false); + valueShowSocclk = settingBool("showSocclk", d.showSocclk ?? false); + valueShowDcefclk = settingBool("showDcefclk", d.showDcefclk ?? false); + + valueCompactMode = settingBool("compactMode", d.compactMode ?? true); + valueIconColor = pluginApi?.pluginSettings?.iconColor ?? d.iconColor ?? "primary"; + valueTextColor = pluginApi?.pluginSettings?.textColor ?? d.textColor ?? "onSurface"; + valueUseMonospaceFont = settingBool("useMonospaceFont", d.useMonospaceFont ?? true); + valueUsePadding = settingBool("usePadding", d.usePadding ?? false); + + valueDeviceIndex = pluginApi?.pluginSettings?.deviceIndex ?? d.deviceIndex ?? 0; + } + + NText { + text: pluginApi?.tr("settings.bar_section") + pointSize: Style.fontSizeL + font.weight: Font.Bold + color: Color.mOnSurface + } + + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("settings.compact_mode_label") + description: pluginApi?.tr("settings.compact_mode_description") + checked: root.valueCompactMode + onToggled: checked => root.valueCompactMode = checked + defaultValue: defaults.compactMode ?? true + } + + NColorChoice { + Layout.fillWidth: true + label: pluginApi?.tr("settings.icon_color_label") + currentKey: root.valueIconColor + onSelected: key => root.valueIconColor = key + defaultValue: defaults.iconColor ?? "primary" + } + + NColorChoice { + Layout.fillWidth: true + currentKey: root.valueTextColor + onSelected: key => root.valueTextColor = key + visible: !root.valueCompactMode + defaultValue: defaults.textColor ?? "onSurface" + } + + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("settings.monospace_font_label") + description: pluginApi?.tr("settings.monospace_font_description") + checked: root.valueUseMonospaceFont + onToggled: checked => root.valueUseMonospaceFont = checked + visible: !root.valueCompactMode + defaultValue: defaults.useMonospaceFont ?? true + } + + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("settings.use_padding_label") + description: pluginApi?.tr("settings.use_padding_description") + checked: root.valueUsePadding && root.valueUseMonospaceFont + onToggled: checked => root.valueUsePadding = checked + visible: !root.valueCompactMode + enabled: root.valueUseMonospaceFont + defaultValue: defaults.usePadding ?? false + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + } + + NText { + text: pluginApi?.tr("settings.metrics_section") + pointSize: Style.fontSizeL + font.weight: Font.Bold + color: Color.mOnSurface + } + + NText { + text: pluginApi?.tr("settings.metrics_description") + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.gpu_use") + checked: root.valueShowGpuUse + onToggled: checked => root.valueShowGpuUse = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.temp_junction") + checked: root.valueShowTempJunction + onToggled: checked => root.valueShowTempJunction = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.temp_edge") + checked: root.valueShowTempEdge + onToggled: checked => root.valueShowTempEdge = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.temp_memory") + checked: root.valueShowTempMemory + onToggled: checked => root.valueShowTempMemory = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.vram") + checked: root.valueShowVram + onToggled: checked => root.valueShowVram = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.vram_activity") + checked: root.valueShowVramActivity + onToggled: checked => root.valueShowVramActivity = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.fan_speed") + checked: root.valueShowFanSpeed + onToggled: checked => root.valueShowFanSpeed = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.power") + checked: root.valueShowPower + onToggled: checked => root.valueShowPower = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.sclk") + checked: root.valueShowSclk + onToggled: checked => root.valueShowSclk = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.mclk") + checked: root.valueShowMclk + onToggled: checked => root.valueShowMclk = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.fclk") + checked: root.valueShowFclk + onToggled: checked => root.valueShowFclk = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.socclk") + checked: root.valueShowSocclk + onToggled: checked => root.valueShowSocclk = checked + } + NToggle { + Layout.fillWidth: true + label: pluginApi?.tr("metrics.dcefclk") + checked: root.valueShowDcefclk + onToggled: checked => root.valueShowDcefclk = checked + } + NText { + text: pluginApi?.tr("settings.display_section") + pointSize: Style.fontSizeL + font.weight: Font.Bold + color: Color.mOnSurface + Layout.topMargin: Style.marginL + } + + NSpinBox { + Layout.fillWidth: true + label: pluginApi?.tr("settings.device_index") + description: pluginApi?.tr("settings.device_index_description") + from: 0 + to: 7 + value: root.valueDeviceIndex + onValueChanged: root.valueDeviceIndex = value + } + + function saveSettings() { + if (!pluginApi) + return; + + const s = pluginApi.pluginSettings; + s.compactMode = root.valueCompactMode; + s.iconColor = root.valueIconColor; + s.textColor = root.valueTextColor; + s.useMonospaceFont = root.valueUseMonospaceFont; + s.usePadding = root.valueUsePadding; + + s.showGpuUse = root.valueShowGpuUse; + s.showTempJunction = root.valueShowTempJunction; + s.showTempEdge = root.valueShowTempEdge; + s.showTempMemory = root.valueShowTempMemory; + s.showVram = root.valueShowVram; + s.showVramActivity = root.valueShowVramActivity; + s.showFanSpeed = root.valueShowFanSpeed; + s.showPower = root.valueShowPower; + s.showSclk = root.valueShowSclk; + s.showMclk = root.valueShowMclk; + s.showFclk = root.valueShowFclk; + s.showSocclk = root.valueShowSocclk; + s.showDcefclk = root.valueShowDcefclk; + s.deviceIndex = root.valueDeviceIndex; + + pluginApi.saveSettings(); + if (mainInstance) + mainInstance.refreshNow(); + Logger.i("AmdGpuMonitor", "Settings saved"); + } +} + diff --git a/amd-gpu-monitor/i18n/de.json b/amd-gpu-monitor/i18n/de.json new file mode 100644 index 000000000..05eeff2ff --- /dev/null +++ b/amd-gpu-monitor/i18n/de.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU Monitor", + "unavailable": "GPU-Daten nicht verfügbar", + "check_rocm": "Stellen Sie sicher, dass rocm-smi installiert ist und die dedizierte GPU verfügbar ist." + }, + "metrics": { + "gpu_use": "GPU-Auslastung", + "temperature": "Temperatur", + "temp_edge": "Randtemperatur", + "temp_junction": "Junction-Temperatur", + "temp_memory": "Speichertemperatur", + "vram": "VRAM", + "vram_activity": "VRAM-Aktivität", + "fan_speed": "Lüftergeschwindigkeit", + "power": "Leistung", + "sclk": "Grafiktakt (SCLK)", + "mclk": "Speichertakt (MCLK)", + "fclk": "Fabric-Takt (FCLK)", + "socclk": "SoC-Takt", + "dcefclk": "DCEF-Takt", + "clock_speeds": "Takte" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Speicher" + }, + "vram": { + "usage": "Nutzung", + "activity": "Aktivität" + }, + "settings": { + "bar_section": "Bar-Widget", + "compact_mode_label": "Kompaktmodus", + "compact_mode_description": "Vertikale Mini-Anzeigen statt Prozenttext.", + "icon_color_label": "Symbolfarbe", + "monospace_font_label": "Monospace-Schrift", + "monospace_font_description": "Festbreitenschrift für ausgerichtete Prozentwerte.", + "use_padding_label": "Werte auffüllen", + "use_padding_description": "Prozentwerte für stabile Balkenbreite auffüllen (nur horizontaler Balken).", + "metrics_section": "Sichtbare Metriken", + "metrics_description": "Wählen Sie, welche Werte im Bar-Widget und Panel angezeigt werden.", + "display_section": "Datenquelle", + "device_index": "GPU-Geräteindex", + "device_index_description": "rocm-smi -d Index (0 für erste dedizierte GPU)." + }, + "actions": { + "widget_settings": "Einstellungen" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/en.json b/amd-gpu-monitor/i18n/en.json new file mode 100644 index 000000000..231468a00 --- /dev/null +++ b/amd-gpu-monitor/i18n/en.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU Monitor", + "unavailable": "GPU data unavailable", + "check_rocm": "Ensure rocm-smi is installed and the discrete GPU is available." + }, + "metrics": { + "gpu_use": "GPU usage", + "temperature": "Temperature", + "temp_edge": "Edge temperature", + "temp_junction": "Junction temperature", + "temp_memory": "Memory temperature", + "vram": "VRAM", + "vram_activity": "VRAM activity", + "fan_speed": "Fan speed", + "power": "Power", + "sclk": "Graphics clock (SCLK)", + "mclk": "Memory clock (MCLK)", + "fclk": "Fabric clock (FCLK)", + "socclk": "SoC clock", + "dcefclk": "DCEF clock", + "clock_speeds": "Clock speeds" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Memory" + }, + "vram": { + "usage": "Usage", + "activity": "Activity" + }, + "settings": { + "bar_section": "Bar widget", + "compact_mode_label": "Compact mode", + "compact_mode_description": "Show vertical mini gauges instead of percentage text.", + "icon_color_label": "Icon color", + "monospace_font_label": "Monospace font", + "monospace_font_description": "Use a fixed-width font for aligned percentage values.", + "use_padding_label": "Pad values", + "use_padding_description": "Pad percentage values for stable bar width (horizontal bar only).", + "metrics_section": "Visible metrics", + "metrics_description": "Choose which values appear in the bar widget and panel.", + "display_section": "Data source", + "device_index": "GPU device index", + "device_index_description": "rocm-smi -d index (0 for first discrete GPU)." + }, + "actions": { + "widget_settings": "Settings" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/es.json b/amd-gpu-monitor/i18n/es.json new file mode 100644 index 000000000..0b1470b00 --- /dev/null +++ b/amd-gpu-monitor/i18n/es.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "Monitor GPU AMD", + "unavailable": "Datos de GPU no disponibles", + "check_rocm": "Asegúrese de que rocm-smi esté instalado y la GPU dedicada esté disponible." + }, + "metrics": { + "gpu_use": "Uso de GPU", + "temperature": "Temperatura", + "temp_edge": "Temperatura edge", + "temp_junction": "Temperatura junction", + "temp_memory": "Temperatura de memoria", + "vram": "VRAM", + "vram_activity": "Actividad VRAM", + "fan_speed": "Velocidad del ventilador", + "power": "Consumo", + "sclk": "Reloj gráfico (SCLK)", + "mclk": "Reloj de memoria (MCLK)", + "fclk": "Reloj fabric (FCLK)", + "socclk": "Reloj SoC", + "dcefclk": "Reloj DCEF", + "clock_speeds": "Relojes" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Memoria" + }, + "vram": { + "usage": "Uso", + "activity": "Actividad" + }, + "settings": { + "bar_section": "Widget de barra", + "compact_mode_label": "Modo compacto", + "compact_mode_description": "Mini indicadores verticales en lugar de texto con porcentajes.", + "icon_color_label": "Color de iconos", + "monospace_font_label": "Fuente monoespaciada", + "monospace_font_description": "Fuente de ancho fijo para valores alineados.", + "use_padding_label": "Rellenar valores", + "use_padding_description": "Rellenar porcentajes para un ancho de barra estable (solo barra horizontal).", + "metrics_section": "Métricas visibles", + "metrics_description": "Elija qué valores aparecen en el widget de barra y el panel.", + "display_section": "Fuente de datos", + "device_index": "Índice de GPU", + "device_index_description": "rocm-smi -d índice (0 para la primera GPU dedicada)." + }, + "actions": { + "widget_settings": "Ajustes" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/fr.json b/amd-gpu-monitor/i18n/fr.json new file mode 100644 index 000000000..c5309e3ab --- /dev/null +++ b/amd-gpu-monitor/i18n/fr.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "Moniteur GPU AMD", + "unavailable": "Données GPU indisponibles", + "check_rocm": "Assurez-vous que rocm-smi est installé et que le GPU dédié est disponible." + }, + "metrics": { + "gpu_use": "Utilisation GPU", + "temperature": "Température", + "temp_edge": "Température edge", + "temp_junction": "Température junction", + "temp_memory": "Température mémoire", + "vram": "VRAM", + "vram_activity": "Activité VRAM", + "fan_speed": "Vitesse du ventilateur", + "power": "Consommation", + "sclk": "Horloge graphique (SCLK)", + "mclk": "Horloge mémoire (MCLK)", + "fclk": "Horloge fabric (FCLK)", + "socclk": "Horloge SoC", + "dcefclk": "Horloge DCEF", + "clock_speeds": "Fréquences" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Mémoire" + }, + "vram": { + "usage": "Utilisation", + "activity": "Activité" + }, + "settings": { + "bar_section": "Widget de barre", + "compact_mode_label": "Mode compact", + "compact_mode_description": "Mini jauges verticales au lieu du texte en pourcentage.", + "icon_color_label": "Couleur des icônes", + "monospace_font_label": "Police monospace", + "monospace_font_description": "Police à chasse fixe pour des valeurs alignées.", + "use_padding_label": "Remplir les valeurs", + "use_padding_description": "Remplir les pourcentages pour une largeur de barre stable (barre horizontale uniquement).", + "metrics_section": "Métriques visibles", + "metrics_description": "Choisissez les valeurs affichées dans le widget de barre et le panneau.", + "display_section": "Source de données", + "device_index": "Index du GPU", + "device_index_description": "rocm-smi -d index (0 pour le premier GPU dédié)." + }, + "actions": { + "widget_settings": "Paramètres" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/it.json b/amd-gpu-monitor/i18n/it.json new file mode 100644 index 000000000..63c2f97f4 --- /dev/null +++ b/amd-gpu-monitor/i18n/it.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "Monitor GPU AMD", + "unavailable": "Dati GPU non disponibili", + "check_rocm": "Assicurati che rocm-smi sia installato e che la GPU dedicata sia disponibile." + }, + "metrics": { + "gpu_use": "Utilizzo GPU", + "temperature": "Temperatura", + "temp_edge": "Temperatura edge", + "temp_junction": "Temperatura junction", + "temp_memory": "Temperatura memoria", + "vram": "VRAM", + "vram_activity": "Attività VRAM", + "fan_speed": "Velocità ventola", + "power": "Consumo", + "sclk": "Clock grafico (SCLK)", + "mclk": "Clock memoria (MCLK)", + "fclk": "Clock fabric (FCLK)", + "socclk": "Clock SoC", + "dcefclk": "Clock DCEF", + "clock_speeds": "Frequenze" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Memoria" + }, + "vram": { + "usage": "Utilizzo", + "activity": "Attività" + }, + "settings": { + "bar_section": "Widget barra", + "compact_mode_label": "Modalità compatta", + "compact_mode_description": "Mini indicatori verticali invece del testo percentuale.", + "icon_color_label": "Colore icone", + "monospace_font_label": "Font monospace", + "monospace_font_description": "Font a larghezza fissa per valori allineati.", + "use_padding_label": "Riempi valori", + "use_padding_description": "Riempi le percentuali per una larghezza barra stabile (solo barra orizzontale).", + "metrics_section": "Metriche visibili", + "metrics_description": "Scegli quali valori mostrare nel widget barra e nel pannello.", + "display_section": "Sorgente dati", + "device_index": "Indice GPU", + "device_index_description": "rocm-smi -d indice (0 per la prima GPU dedicata)." + }, + "actions": { + "widget_settings": "Impostazioni" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/ja.json b/amd-gpu-monitor/i18n/ja.json new file mode 100644 index 000000000..42038e92d --- /dev/null +++ b/amd-gpu-monitor/i18n/ja.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU モニター", + "unavailable": "GPU データを利用できません", + "check_rocm": "rocm-smi がインストールされ、ディスクリート GPU が利用可能であることを確認してください。" + }, + "metrics": { + "gpu_use": "GPU 使用率", + "temperature": "温度", + "temp_edge": "エッジ温度", + "temp_junction": "ジャンクション温度", + "temp_memory": "メモリ温度", + "vram": "VRAM", + "vram_activity": "VRAM アクティビティ", + "fan_speed": "ファン速度", + "power": "消費電力", + "sclk": "グラフィックスクロック (SCLK)", + "mclk": "メモリクロック (MCLK)", + "fclk": "ファブリッククロック (FCLK)", + "socclk": "SoC クロック", + "dcefclk": "DCEF クロック", + "clock_speeds": "クロック" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "メモリ" + }, + "vram": { + "usage": "使用量", + "activity": "アクティビティ" + }, + "settings": { + "bar_section": "バーウィジェット", + "compact_mode_label": "コンパクトモード", + "compact_mode_description": "パーセント表示の代わりに縦型ミニゲージを表示します。", + "icon_color_label": "アイコンの色", + "monospace_font_label": "等幅フォント", + "monospace_font_description": "揃えた数値表示用の等幅フォントを使用します。", + "use_padding_label": "値をパディング", + "use_padding_description": "バー幅を安定させるためパーセント値をパディングします(水平バーのみ)。", + "metrics_section": "表示するメトリクス", + "metrics_description": "バーウィジェットとパネルに表示する値を選択します。", + "display_section": "データソース", + "device_index": "GPU デバイスインデックス", + "device_index_description": "rocm-smi -d インデックス(0 は最初のディスクリート GPU)。" + }, + "actions": { + "widget_settings": "設定" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/nl.json b/amd-gpu-monitor/i18n/nl.json new file mode 100644 index 000000000..f21fa9560 --- /dev/null +++ b/amd-gpu-monitor/i18n/nl.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU Monitor", + "unavailable": "GPU-gegevens niet beschikbaar", + "check_rocm": "Zorg dat rocm-smi is geïnstalleerd en de dedicated GPU beschikbaar is." + }, + "metrics": { + "gpu_use": "GPU-gebruik", + "temperature": "Temperatuur", + "temp_edge": "Edge-temperatuur", + "temp_junction": "Junction-temperatuur", + "temp_memory": "Geheugentemperatuur", + "vram": "VRAM", + "vram_activity": "VRAM-activiteit", + "fan_speed": "Ventilatorsnelheid", + "power": "Vermogen", + "sclk": "Grafische klok (SCLK)", + "mclk": "Geheugenklok (MCLK)", + "fclk": "Fabric-klok (FCLK)", + "socclk": "SoC-klok", + "dcefclk": "DCEF-klok", + "clock_speeds": "Kloksnelheden" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Geheugen" + }, + "vram": { + "usage": "Gebruik", + "activity": "Activiteit" + }, + "settings": { + "bar_section": "Balkwidget", + "compact_mode_label": "Compacte modus", + "compact_mode_description": "Verticale mini-meters in plaats van procenttekst.", + "icon_color_label": "Pictogramkleur", + "monospace_font_label": "Monospace-lettertype", + "monospace_font_description": "Vaste breedte voor uitgelijnde waarden.", + "use_padding_label": "Waarden opvullen", + "use_padding_description": "Procenten opvullen voor stabiele balkbreedte (alleen horizontale balk).", + "metrics_section": "Zichtbare metrieken", + "metrics_description": "Kies welke waarden in de balkwidget en het paneel verschijnen.", + "display_section": "Gegevensbron", + "device_index": "GPU-apparaatindex", + "device_index_description": "rocm-smi -d index (0 voor eerste dedicated GPU)." + }, + "actions": { + "widget_settings": "Instellingen" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/pt.json b/amd-gpu-monitor/i18n/pt.json new file mode 100644 index 000000000..7fd05437f --- /dev/null +++ b/amd-gpu-monitor/i18n/pt.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "Monitor GPU AMD", + "unavailable": "Dados da GPU indisponíveis", + "check_rocm": "Certifique-se de que o rocm-smi está instalado e a GPU dedicada está disponível." + }, + "metrics": { + "gpu_use": "Uso da GPU", + "temperature": "Temperatura", + "temp_edge": "Temperatura edge", + "temp_junction": "Temperatura junction", + "temp_memory": "Temperatura da memória", + "vram": "VRAM", + "vram_activity": "Atividade VRAM", + "fan_speed": "Velocidade do ventilador", + "power": "Consumo", + "sclk": "Clock gráfico (SCLK)", + "mclk": "Clock de memória (MCLK)", + "fclk": "Clock fabric (FCLK)", + "socclk": "Clock SoC", + "dcefclk": "Clock DCEF", + "clock_speeds": "Frequências" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Memória" + }, + "vram": { + "usage": "Uso", + "activity": "Atividade" + }, + "settings": { + "bar_section": "Widget da barra", + "compact_mode_label": "Modo compacto", + "compact_mode_description": "Mini medidores verticais em vez de texto percentual.", + "icon_color_label": "Cor dos ícones", + "monospace_font_label": "Fonte monoespaçada", + "monospace_font_description": "Fonte de largura fixa para valores alinhados.", + "use_padding_label": "Preencher valores", + "use_padding_description": "Preencher percentagens para largura estável da barra (apenas barra horizontal).", + "metrics_section": "Métricas visíveis", + "metrics_description": "Escolha quais valores aparecem no widget da barra e no painel.", + "display_section": "Fonte de dados", + "device_index": "Índice da GPU", + "device_index_description": "rocm-smi -d índice (0 para a primeira GPU dedicada)." + }, + "actions": { + "widget_settings": "Configurações" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/ru.json b/amd-gpu-monitor/i18n/ru.json new file mode 100644 index 000000000..800020d17 --- /dev/null +++ b/amd-gpu-monitor/i18n/ru.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU Monitor", + "unavailable": "Данные GPU недоступны", + "check_rocm": "Убедитесь, что установлен rocm-smi и дискретная видеокарта доступна." + }, + "metrics": { + "gpu_use": "Загрузка GPU", + "temperature": "Температура", + "temp_edge": "Температура (edge)", + "temp_junction": "Температура (junction)", + "temp_memory": "Температура памяти", + "vram": "VRAM", + "vram_activity": "Активность VRAM", + "fan_speed": "Скорость вентилятора", + "power": "Потребление", + "sclk": "Частота ядра (SCLK)", + "mclk": "Частота памяти (MCLK)", + "fclk": "Частота fabric (FCLK)", + "socclk": "Частота SoC", + "dcefclk": "Частота DCEF", + "clock_speeds": "Частоты" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Память" + }, + "vram": { + "usage": "Использование", + "activity": "Активность" + }, + "settings": { + "bar_section": "Виджет в баре", + "compact_mode_label": "Компактный режим", + "compact_mode_description": "Мини-столбики вместо текста с процентами.", + "icon_color_label": "Цвет иконок", + "monospace_font_label": "Моноширинный шрифт", + "monospace_font_description": "Фиксированная ширина цифр для ровного выравнивания.", + "use_padding_label": "Выравнивать значения", + "use_padding_description": "Дополнительные пробелы для стабильной ширины (только горизонтальный бар).", + "metrics_section": "Отображаемые метрики", + "metrics_description": "Выберите значения для виджета в баре и панели.", + "display_section": "Источник данных", + "device_index": "Индекс GPU", + "device_index_description": "Параметр rocm-smi -d (0 — первая дискретная видеокарта)." + }, + "actions": { + "widget_settings": "Настройки" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/tr.json b/amd-gpu-monitor/i18n/tr.json new file mode 100644 index 000000000..3569f4fce --- /dev/null +++ b/amd-gpu-monitor/i18n/tr.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU Monitörü", + "unavailable": "GPU verileri kullanılamıyor", + "check_rocm": "rocm-smi'nin kurulu olduğundan ve ayrık GPU'nun kullanılabilir olduğundan emin olun." + }, + "metrics": { + "gpu_use": "GPU kullanımı", + "temperature": "Sıcaklık", + "temp_edge": "Edge sıcaklığı", + "temp_junction": "Junction sıcaklığı", + "temp_memory": "Bellek sıcaklığı", + "vram": "VRAM", + "vram_activity": "VRAM etkinliği", + "fan_speed": "Fan hızı", + "power": "Güç tüketimi", + "sclk": "Grafik saati (SCLK)", + "mclk": "Bellek saati (MCLK)", + "fclk": "Fabric saati (FCLK)", + "socclk": "SoC saati", + "dcefclk": "DCEF saati", + "clock_speeds": "Saat hızları" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Bellek" + }, + "vram": { + "usage": "Kullanım", + "activity": "Etkinlik" + }, + "settings": { + "bar_section": "Çubuk widget'ı", + "compact_mode_label": "Kompakt mod", + "compact_mode_description": "Yüzde metni yerine dikey mini göstergeler.", + "icon_color_label": "Simge rengi", + "monospace_font_label": "Monospace yazı tipi", + "monospace_font_description": "Hizalı değerler için sabit genişlikli yazı tipi.", + "use_padding_label": "Değerleri doldur", + "use_padding_description": "Kararlı çubuk genişliği için yüzdeleri doldur (yalnızca yatay çubuk).", + "metrics_section": "Görünür metrikler", + "metrics_description": "Çubuk widget'ında ve panelde hangi değerlerin görüneceğini seçin.", + "display_section": "Veri kaynağı", + "device_index": "GPU cihaz indeksi", + "device_index_description": "rocm-smi -d indeksi (0 ilk ayrık GPU)." + }, + "actions": { + "widget_settings": "Ayarlar" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/uk-UA.json b/amd-gpu-monitor/i18n/uk-UA.json new file mode 100644 index 000000000..02e889ddb --- /dev/null +++ b/amd-gpu-monitor/i18n/uk-UA.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "Монітор AMD GPU", + "unavailable": "Дані GPU недоступні", + "check_rocm": "Переконайтеся, що rocm-smi встановлено і дискретна відеокарта доступна." + }, + "metrics": { + "gpu_use": "Завантаження GPU", + "temperature": "Температура", + "temp_edge": "Температура (edge)", + "temp_junction": "Температура (junction)", + "temp_memory": "Температура пам'яті", + "vram": "VRAM", + "vram_activity": "Активність VRAM", + "fan_speed": "Швидкість вентилятора", + "power": "Споживання", + "sclk": "Частота ядра (SCLK)", + "mclk": "Частота пам'яті (MCLK)", + "fclk": "Частота fabric (FCLK)", + "socclk": "Частота SoC", + "dcefclk": "Частота DCEF", + "clock_speeds": "Частоти" + }, + "temp": { + "junction": "Junction", + "edge": "Edge", + "memory": "Пам'ять" + }, + "vram": { + "usage": "Використання", + "activity": "Активність" + }, + "settings": { + "bar_section": "Віджет у барі", + "compact_mode_label": "Компактний режим", + "compact_mode_description": "Міні-стовпчики замість тексту з відсотками.", + "icon_color_label": "Колір іконок", + "monospace_font_label": "Моноширинний шрифт", + "monospace_font_description": "Фіксована ширина цифр для рівного вирівнювання.", + "use_padding_label": "Вирівнювати значення", + "use_padding_description": "Додаткові пробіли для стабільної ширини (лише горизонтальний бар).", + "metrics_section": "Відображувані метрики", + "metrics_description": "Оберіть значення для віджета в барі та панелі.", + "display_section": "Джерело даних", + "device_index": "Індекс GPU", + "device_index_description": "Параметр rocm-smi -d (0 — перша дискретна відеокарта)." + }, + "actions": { + "widget_settings": "Налаштування" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/i18n/zh-CN.json b/amd-gpu-monitor/i18n/zh-CN.json new file mode 100644 index 000000000..46d2ff134 --- /dev/null +++ b/amd-gpu-monitor/i18n/zh-CN.json @@ -0,0 +1,58 @@ +{ + "panel": { + "title": "AMD GPU 监控", + "unavailable": "GPU 数据不可用", + "check_rocm": "请确保已安装 rocm-smi 且独立 GPU 可用。" + }, + "metrics": { + "gpu_use": "GPU 使用率", + "temperature": "温度", + "temp_edge": "边缘温度", + "temp_junction": "结温", + "temp_memory": "显存温度", + "vram": "VRAM", + "vram_activity": "VRAM 活动", + "fan_speed": "风扇转速", + "power": "功耗", + "sclk": "核心频率 (SCLK)", + "mclk": "显存频率 (MCLK)", + "fclk": "Fabric 频率 (FCLK)", + "socclk": "SoC 频率", + "dcefclk": "DCEF 频率", + "clock_speeds": "频率" + }, + "temp": { + "junction": "结温", + "edge": "边缘", + "memory": "显存" + }, + "vram": { + "usage": "使用量", + "activity": "活动" + }, + "settings": { + "bar_section": "栏组件", + "compact_mode_label": "紧凑模式", + "compact_mode_description": "显示竖向迷你仪表而非百分比文字。", + "icon_color_label": "图标颜色", + "monospace_font_label": "等宽字体", + "monospace_font_description": "使用等宽字体对齐数值。", + "use_padding_label": "填充数值", + "use_padding_description": "填充百分比以稳定栏宽度(仅水平栏)。", + "metrics_section": "可见指标", + "metrics_description": "选择栏组件和面板中显示的值。", + "display_section": "数据源", + "device_index": "GPU 设备索引", + "device_index_description": "rocm-smi -d 索引(0 表示第一个独立 GPU)。" + }, + "actions": { + "widget_settings": "设置" + }, + "clocks": { + "sclk": "SCLK", + "mclk": "MCLK", + "fclk": "FCLK", + "socclk": "SOCCLK", + "dcefclk": "DCEFCLK" + } +} diff --git a/amd-gpu-monitor/manifest.json b/amd-gpu-monitor/manifest.json new file mode 100644 index 000000000..80057cd37 --- /dev/null +++ b/amd-gpu-monitor/manifest.json @@ -0,0 +1,42 @@ +{ + "id": "amd-gpu-monitor", + "name": "AMD GPU Monitor", + "version": "1.0.0", + "minNoctaliaVersion": "4.0.0", + "author": "Dvaxert", + "license": "MIT", + "description": "AMD discrete GPU monitoring via rocm-smi with graphs and configurable metrics.", + "tags": ["Bar", "Panel", "System", "Amd", "GPU", "Monitoring", "Graph", "Video"], + "entryPoints": { + "main": "Main.qml", + "barWidget": "BarWidget.qml", + "panel": "Panel.qml", + "settings": "Settings.qml" + }, + "dependencies": { + "plugins": [] + }, + "metadata": { + "defaultSettings": { + "deviceIndex": 0, + "compactMode": true, + "iconColor": "primary", + "textColor": "onSurface", + "useMonospaceFont": true, + "usePadding": false, + "showTempEdge": false, + "showTempJunction": true, + "showTempMemory": false, + "showGpuUse": true, + "showVram": true, + "showVramActivity": false, + "showFanSpeed": false, + "showPower": true, + "showSclk": true, + "showMclk": true, + "showFclk": false, + "showSocclk": false, + "showDcefclk": false + } + } +} diff --git a/amd-gpu-monitor/preview.png b/amd-gpu-monitor/preview.png new file mode 100644 index 000000000..2e25b4bd1 Binary files /dev/null and b/amd-gpu-monitor/preview.png differ diff --git a/amd-gpu-monitor/readme.md b/amd-gpu-monitor/readme.md new file mode 100644 index 000000000..e77fa3a27 --- /dev/null +++ b/amd-gpu-monitor/readme.md @@ -0,0 +1,95 @@ +# AMD GPU Monitor + +A real-time AMD GPU monitoring plugin for Noctalia Shell with comprehensive metrics and customizable graphs. + +## Features + +- **Panel Widget**: Full GPU monitoring interface with real-time graphs +- **Bar Widget**: Compact GPU status display in the bar +- **Real-time Graphs**: Visual graphs for all metrics with customizable colors +- **Multi-sensor Temperature**: Monitor Junction, Edge, and Memory temperatures +- **Multiple Clock Speeds**: Track SCLK, MCLK, FCLK, SOCCLK, and DCEFCLK +- **Priority Thresholds**: Automatic color changes at 75% (warning) and 90% (critical) for GPU usage +- **VRAM Monitoring**: Track VRAM usage and activity +- **Customizable Metrics**: Show/hide any metric via settings +- **Theme Integration**: Uses Noctalia Shell theme colors for graphs and icons +- **Settings**: Configure visible metrics, colors, and display preferences + +## Usage + +Add the bar widget to your bar, or open the panel for full GPU monitoring. The panel displays all available metrics with real-time graphs. + +### Panel + +The panel provides a comprehensive view of all GPU metrics with mini-graphs for each metric. Metrics include: + +- **GPU Usage**: Real-time GPU utilization with threshold-based coloring (green → yellow at 75% → red at 90%) +- **Temperature**: Multi-line graph supporting Junction, Edge, and Memory temperature sensors +- **VRAM**: Memory usage and optional activity monitoring +- **Fan Speed**: Cooling fan speed in percentage +- **Power**: Power consumption in watts +- **Clock Speeds**: Multi-line graph for SCLK, MCLK, FCLK, SOCCLK, and DCEFCLK + +### Bar Widget + +The bar widget shows a compact overview of GPU status directly in the bar. + +### Temperature Sensors + +Choose which temperature sensor to display as primary: +- **Junction**: GPU junction temperature (default) +- **Edge**: GPU edge temperature +- **Memory**: GPU memory temperature + +Multiple sensors can be shown simultaneously on the temperature graph with different colors. + +### Clock Speeds + +Track up to 5 different clock speeds simultaneously: +- **SCLK**: Graphics clock (default) +- **MCLK**: Memory clock +- **FCLK**: Fabric clock +- **SOCCLK**: SoC clock +- **DCEFCLK**: DCEF clock + +All visible clocks are displayed on a single multi-line graph with theme-based colors. + +## Configuration + +### Display Settings + +- **Compact Mode**: Toggle between compact and detailed display +- **Icon Color**: Customize the icon color using theme colors +- **Text Color**: Customize text color (when compact mode is off) +- **Monospace Font**: Use fixed-width font for aligned values +- **Pad Values**: Enable padding for stable bar width (monospace only) + +### Visible Metrics + +Choose which metrics to display: +- GPU Usage +- Junction Temperature +- Edge Temperature +- Memory Temperature +- VRAM Usage +- VRAM Activity +- Fan Speed +- Power +- SCLK, MCLK, FCLK, SOCCLK, DCEFCLK + +### Data Source + +- **GPU Device Index**: Select which GPU to monitor (rocm-smi device index, 0 for first discrete GPU) + +## Requirements + +- **rocm-smi**: ROCm System Management Interface must be installed and available +- **AMD GPU**: Requires a discrete AMD GPU + +## Troubleshooting + +If the panel shows "GPU data unavailable": +1. Ensure `rocm-smi` is installed: `sudo pacman -S rocm-smi-lib` (Arch) or equivalent for your distro +2. Verify the discrete GPU is detected: `rocm-smi` +3. Check that you have proper permissions to access GPU data +4. Verify the correct device index in settings if you have multiple GPUs \ No newline at end of file