import QtQuick import org.kde.plasma.plasmoid import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.plasma5support as P5Support import org.kde.kirigami as Kirigami PlasmoidItem { id: root readonly property int updateMs: 1000 readonly property color cpuAccent: "#55c5c0" readonly property color gpuAccent: "#9876d9" readonly property color ramAccent: "#d9a84e" property real cpuUsage: NaN property real cpuTemp: NaN property real cpuW: NaN property real gpuUsage: NaN property real gpuTemp: NaN property real gpuW: NaN property real gpuWAvg: NaN property real gpuMhz: NaN property real ramUsed: NaN property real ramTotal: NaN property real ramAvailable: NaN property real swapUsed: NaN property real swapTotal: NaN property var cpuHistory: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] property var gpuHistory: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] property var ramHistory: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] signal metricsUpdated() // Come gli altri dashboard KDE: sfondo semitrasparente nativo Plasma. Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground preferredRepresentation: fullRepresentation P5Support.DataSource { engine: "executable" interval: root.updateMs connectedSources: [root.script] onNewData: function(name, data) { if (data["exit code"] === 0) root.parse(data["stdout"] || "") } } readonly property string script: Qt.resolvedUrl("../scripts/power.sh").toString().replace("file://", "") function number(values, key, divisor) { const value = parseFloat(values[key]) return isNaN(value) ? NaN : value / (divisor || 1) } function appendSample(samples, value) { const copy = samples.slice(0) copy.push(isNaN(value) ? 0 : value) if (copy.length > 24) copy.shift() return copy } function parse(output) { const values = {} output.split("\n").forEach(function(line) { const separator = line.indexOf("=") if (separator > 0) values[line.slice(0, separator)] = line.slice(separator + 1) }) cpuUsage = number(values, "cpu_usage_pct") cpuTemp = number(values, "cpu_temp_mdegC", 1000) cpuW = number(values, "cpu_power_cw", 100) gpuUsage = number(values, "gpu_busy_pct") gpuTemp = number(values, "gpu_temp_mdegC", 1000) gpuW = number(values, "gpu_power_uw", 1000000) gpuWAvg = number(values, "gpu_power_avg_uw", 1000000) gpuMhz = number(values, "gpu_freq_hz", 1000000) ramTotal = number(values, "ram_total_kb") ramUsed = number(values, "ram_used_kb") ramAvailable = number(values, "ram_available_kb") swapTotal = number(values, "swap_total_kb") swapUsed = number(values, "swap_used_kb") cpuHistory = appendSample(cpuHistory, cpuUsage) gpuHistory = appendSample(gpuHistory, gpuUsage) ramHistory = appendSample(ramHistory, ramPercent()) metricsUpdated() } function clampPercent(value) { return isNaN(value) ? 0 : Math.max(0, Math.min(100, value)) } function ramPercent() { return ramTotal > 0 ? ramUsed / ramTotal * 100 : NaN } function formatNumber(value, decimals) { return isNaN(value) ? "—" : value.toFixed(decimals === undefined ? 0 : decimals) } function watts(value) { return isNaN(value) ? "n/d" : value.toFixed(1) + " W" } function gibibytes(kilobytes) { return isNaN(kilobytes) ? "—" : (kilobytes / 1048576).toFixed(1) + " GiB" } fullRepresentation: Rectangle { id: dashboard readonly property bool narrow: width < 520 implicitWidth: 570 implicitHeight: narrow ? 650 : 368 radius: 14 color: "transparent" border.color: "transparent" border.width: 0 Column { anchors.fill: parent anchors.margins: 16 spacing: 10 // Due righe esplicite: nessuna collisione tra titolo e stato LIVE. Column { width: parent.width height: 39 spacing: 3 Row { spacing: 9 Rectangle { width: 4 height: 18 radius: 2 color: root.cpuAccent } PlasmaComponents.Label { text: "POWER MONITOR" color: "#e8edf4" font.bold: true font.pointSize: dashboard.narrow ? 11 : 13 font.letterSpacing: 0.8 } } Item { width: parent.width height: 14 Row { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 6 Rectangle { width: 6 height: 6 radius: 3 color: "#62c98b" } PlasmaComponents.Label { text: "LIVE / 1 s" color: "#8293a8" font.family: "monospace" font.pointSize: 8 } } } } Grid { id: metricsGrid width: parent.width columns: dashboard.narrow ? 1 : 3 spacing: 8 MetricCard { width: dashboard.narrow ? metricsGrid.width : (metricsGrid.width - 16) / 3 height: dashboard.narrow ? 105 : 126 title: "CPU" value: root.formatNumber(root.cpuUsage) + "%" detail: root.watts(root.cpuW) + " / " + (isNaN(root.cpuTemp) ? "n/d" : root.formatNumber(root.cpuTemp) + " °C") accent: root.cpuAccent progress: root.clampPercent(root.cpuUsage) metricIcon: "cpu" } MetricCard { width: dashboard.narrow ? metricsGrid.width : (metricsGrid.width - 16) / 3 height: dashboard.narrow ? 105 : 126 title: "iGPU 780M" value: root.formatNumber(root.gpuUsage) + "%" detail: root.watts(root.gpuWAvg) + " / " + (isNaN(root.gpuTemp) ? "n/d" : root.formatNumber(root.gpuTemp) + " °C") accent: root.gpuAccent progress: root.clampPercent(root.gpuUsage) metricIcon: "video-display" } MetricCard { width: dashboard.narrow ? metricsGrid.width : (metricsGrid.width - 16) / 3 height: dashboard.narrow ? 105 : 126 title: "RAM" value: root.formatNumber(root.ramPercent()) + "%" detail: root.gibibytes(root.ramUsed) + " / " + root.gibibytes(root.ramTotal) accent: root.ramAccent progress: root.clampPercent(root.ramPercent()) metricIcon: "media-flash-memory" } } Flow { width: parent.width height: childrenRect.height spacing: 7 StatChip { label: "CPU"; metric: root.watts(root.cpuW); accent: root.cpuAccent } StatChip { label: "PPT APU"; metric: root.watts(root.gpuW); accent: root.gpuAccent } StatChip { label: "FREE"; metric: root.gibibytes(root.ramAvailable); accent: root.ramAccent } StatChip { label: "SWAP"; metric: root.gibibytes(root.swapUsed) + " / " + root.gibibytes(root.swapTotal); accent: "#7187a4" } } Rectangle { width: parent.width height: dashboard.narrow ? 118 : 104 radius: 8 color: Qt.rgba(0, 0, 0, 0.25) border.color: Qt.rgba(0.40, 0.48, 0.58, 0.40) border.width: 1 Column { anchors.fill: parent anchors.margins: 10 spacing: 5 Item { width: parent.width height: 18 PlasmaComponents.Label { anchors.left: parent.left text: dashboard.narrow ? "HISTORY" : "UTILIZATION HISTORY" color: "#8293a8" font.family: "monospace" font.pointSize: 8 font.letterSpacing: 0.5 } Row { anchors.right: parent.right spacing: dashboard.narrow ? 5 : 9 Legend { label: "CPU"; accent: root.cpuAccent } Legend { label: "GPU"; accent: root.gpuAccent } Legend { label: "RAM"; accent: root.ramAccent } } } Canvas { id: trend width: parent.width height: parent.height - 23 onWidthChanged: requestPaint() onHeightChanged: requestPaint() Connections { target: root function onMetricsUpdated() { trend.requestPaint() } } onPaint: { const context = getContext("2d") context.clearRect(0, 0, width, height) context.lineWidth = 1 context.strokeStyle = "#202b39" for (let row = 1; row < 4; row++) { const y = row * height / 4 context.beginPath() context.moveTo(0, y) context.lineTo(width, y) context.stroke() } function drawSeries(samples, color) { if (!samples || samples.length < 2) return context.lineWidth = 1.5 context.strokeStyle = color context.beginPath() for (let index = 0; index < samples.length; index++) { const x = index * width / (samples.length - 1) const y = height - root.clampPercent(samples[index]) / 100 * height if (index === 0) context.moveTo(x, y) else context.lineTo(x, y) } context.stroke() } drawSeries(root.ramHistory, root.ramAccent) drawSeries(root.gpuHistory, root.gpuAccent) drawSeries(root.cpuHistory, root.cpuAccent) } } } } } } component MetricCard: Rectangle { id: card property string title property string value property string detail property color accent property real progress property string metricIcon implicitWidth: 170 implicitHeight: 126 radius: 8 color: Qt.rgba(0, 0, 0, 0.25) border.color: Qt.rgba(card.accent.r, card.accent.g, card.accent.b, 0.55) border.width: 1 Rectangle { anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: 9; bottomMargin: 9 } width: 2 radius: 1 color: card.accent opacity: 0.9 } Column { anchors.fill: parent anchors.margins: 11 spacing: 7 Item { width: parent.width height: 27 Row { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 6 Rectangle { width: 24 height: 24 radius: 5 color: Qt.rgba(card.accent.r, card.accent.g, card.accent.b, 0.11) Kirigami.Icon { anchors.centerIn: parent width: 14 height: 14 source: card.metricIcon } } PlasmaComponents.Label { text: card.title color: "#b9c4d2" font.bold: true font.pointSize: 9 } } PlasmaComponents.Label { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: card.value color: card.accent font.family: "monospace" font.bold: true font.pointSize: 18 } } Rectangle { width: parent.width height: 4 radius: 2 color: "#293649" Rectangle { width: parent.width * Math.max(0, Math.min(100, card.progress)) / 100 height: parent.height radius: 2 color: card.accent } } PlasmaComponents.Label { width: parent.width text: card.detail color: "#899aaf" font.family: "monospace" font.pointSize: 8 elide: Text.ElideRight } } } component StatChip: Rectangle { id: chip property string label property string metric property color accent implicitWidth: chipRow.implicitWidth + 16 implicitHeight: 25 radius: 5 color: Qt.rgba(0, 0, 0, 0.25) border.color: Qt.rgba(0.40, 0.48, 0.58, 0.40) border.width: 1 Row { id: chipRow anchors.centerIn: parent spacing: 5 Rectangle { width: 4; height: 4; radius: 2; color: chip.accent } PlasmaComponents.Label { text: chip.label; color: "#72849a"; font.family: "monospace"; font.pointSize: 7 } PlasmaComponents.Label { text: chip.metric; color: "#c1cad6"; font.family: "monospace"; font.bold: true; font.pointSize: 8 } } } component Legend: Row { id: legend property string label property color accent spacing: 4 Rectangle { width: 7; height: 2; color: legend.accent; anchors.verticalCenter: parent.verticalCenter } PlasmaComponents.Label { text: legend.label; color: "#73859b"; font.family: "monospace"; font.pointSize: 7 } } compactRepresentation: PlasmaComponents.Label { text: root.formatNumber(root.cpuUsage) + "% / " + root.formatNumber(root.ramPercent()) + "%" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.family: "monospace" } }