Files
kde-power-monitor-plasmoid/contents/ui/main.qml
T

450 lines
16 KiB
QML

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"
}
}