summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-05-28 23:56:44 +0800
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-05-28 23:56:44 +0800
commitea573554d47dac0bca9034aee5f3fc591ac063f3 (patch)
tree46333e8c0abfcb9a5388cf3275c998c718dd6fc2
parentinternal: move weather into service (diff)
downloadcaelestia-shell-ea573554d47dac0bca9034aee5f3fc591ac063f3.tar.gz
caelestia-shell-ea573554d47dac0bca9034aee5f3fc591ac063f3.tar.bz2
caelestia-shell-ea573554d47dac0bca9034aee5f3fc591ac063f3.zip
feat: dashboard performance panel
-rw-r--r--config/DashboardConfig.qml1
-rw-r--r--modules/dashboard/Content.qml15
-rw-r--r--modules/dashboard/Performance.qml230
-rw-r--r--services/SystemUsage.qml84
4 files changed, 323 insertions, 7 deletions
diff --git a/config/DashboardConfig.qml b/config/DashboardConfig.qml
index 3e942e8..3c32e07 100644
--- a/config/DashboardConfig.qml
+++ b/config/DashboardConfig.qml
@@ -21,5 +21,6 @@ Singleton {
readonly property int weatherWidth: 250
readonly property int mediaCoverArtSize: 150
readonly property int mediaVisualiserSize: 80
+ readonly property int resourceSize: 200
}
}
diff --git a/modules/dashboard/Content.qml b/modules/dashboard/Content.qml
index 4026989..5b88f9b 100644
--- a/modules/dashboard/Content.qml
+++ b/modules/dashboard/Content.qml
@@ -54,18 +54,21 @@ Item {
radius: Appearance.rounding.normal
color: "transparent"
- Dash {
- clip: true
- }
+ Dash {}
}
ClippingWrapperRectangle {
radius: Appearance.rounding.normal
color: "transparent"
- Media {
- clip: true
- }
+ Media {}
+ }
+
+ ClippingWrapperRectangle {
+ radius: Appearance.rounding.normal
+ color: "transparent"
+
+ Performance {}
}
}
}
diff --git a/modules/dashboard/Performance.qml b/modules/dashboard/Performance.qml
new file mode 100644
index 0000000..9334874
--- /dev/null
+++ b/modules/dashboard/Performance.qml
@@ -0,0 +1,230 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import QtQuick
+
+Row {
+ id: root
+
+ spacing: Appearance.spacing.large * 3
+ padding: Appearance.padding.large
+ leftPadding: padding * 2
+ rightPadding: padding * 3
+
+ Resource {
+ value1: Math.min(1, SystemUsage.gpuTemp / 90)
+ value2: SystemUsage.gpuPerc
+
+ label1: `${Math.ceil(SystemUsage.gpuTemp)}°C`
+ label2: `${Math.round(SystemUsage.gpuPerc * 100)}%`
+
+ sublabel1: "GPU temp"
+ sublabel2: "Usage"
+ }
+
+ Resource {
+ primary: true
+
+ value1: Math.min(1, SystemUsage.cpuTemp / 90)
+ value2: SystemUsage.cpuPerc
+
+ label1: `${Math.ceil(SystemUsage.cpuTemp)}°C`
+ label2: `${Math.round(SystemUsage.cpuPerc * 100)}%`
+
+ sublabel1: "CPU temp"
+ sublabel2: "Usage"
+ }
+
+ Resource {
+ value1: SystemUsage.memPerc
+ value2: SystemUsage.storagePerc
+
+ label1: {
+ const fmt = SystemUsage.formatKib(SystemUsage.memUsed);
+ return `${+fmt.value.toFixed(1)}${fmt.unit}`;
+ }
+ label2: {
+ const fmt = SystemUsage.formatKib(SystemUsage.storageUsed);
+ return `${Math.floor(fmt.value)}${fmt.unit}`;
+ }
+
+ sublabel1: "Memory"
+ sublabel2: "Storage"
+ }
+
+ component Resource: Item {
+ id: res
+
+ required property real value1
+ required property real value2
+ required property string sublabel1
+ required property string sublabel2
+ required property string label1
+ required property string label2
+
+ property bool primary
+ readonly property real primaryMult: primary ? 1.2 : 1
+
+ readonly property real thickness: DashboardConfig.sizes.resourceProgessThickness * primaryMult
+
+ property color fg1: Colours.palette.m3primary
+ property color fg2: Colours.palette.m3secondary
+ property color bg1: Colours.palette.m3primaryContainer
+ property color bg2: Colours.palette.m3secondaryContainer
+
+ anchors.verticalCenter: parent.verticalCenter
+
+ implicitWidth: DashboardConfig.sizes.resourceSize * primaryMult
+ implicitHeight: DashboardConfig.sizes.resourceSize * primaryMult
+
+ onValue1Changed: canvas.requestPaint()
+ onValue2Changed: canvas.requestPaint()
+ onFg1Changed: canvas.requestPaint()
+ onFg2Changed: canvas.requestPaint()
+ onBg1Changed: canvas.requestPaint()
+ onBg2Changed: canvas.requestPaint()
+
+ Column {
+ anchors.centerIn: parent
+
+ StyledText {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ text: res.label1
+ font.pointSize: Appearance.font.size.extraLarge * res.primaryMult
+ }
+
+ StyledText {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ text: res.sublabel1
+ color: Colours.palette.m3onSurfaceVariant
+ font.pointSize: Appearance.font.size.smaller * res.primaryMult
+ }
+ }
+
+ Column {
+ anchors.horizontalCenter: parent.right
+ anchors.top: parent.verticalCenter
+ anchors.horizontalCenterOffset: -res.thickness / 2
+ anchors.topMargin: res.thickness / 2 + Appearance.spacing.small
+
+ StyledText {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ text: res.label2
+ font.pointSize: Appearance.font.size.smaller * res.primaryMult
+ }
+
+ StyledText {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ text: res.sublabel2
+ color: Colours.palette.m3onSurfaceVariant
+ font.pointSize: Appearance.font.size.small * res.primaryMult
+ }
+ }
+
+ Canvas {
+ id: canvas
+
+ readonly property real centerX: width / 2
+ readonly property real centerY: height / 2
+
+ readonly property real arc1Start: degToRad(45)
+ readonly property real arc1End: degToRad(220)
+ readonly property real arc2Start: degToRad(230)
+ readonly property real arc2End: degToRad(360)
+
+ function degToRad(deg: int): real {
+ return deg * Math.PI / 180;
+ }
+
+ anchors.fill: parent
+
+ onPaint: {
+ const ctx = getContext("2d");
+ ctx.reset();
+
+ ctx.lineWidth = res.thickness;
+ ctx.lineCap = "round";
+
+ const radius = (Math.min(width, height) - ctx.lineWidth) / 2;
+ const cx = centerX;
+ const cy = centerY;
+ const a1s = arc1Start;
+ const a1e = arc1End;
+ const a2s = arc2Start;
+ const a2e = arc2End;
+
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, a1s, a1e, false);
+ ctx.strokeStyle = res.bg1;
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, a1s, (a1e - a1s) * res.value1 + a1s, false);
+ ctx.strokeStyle = res.fg1;
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, a2s, a2e, false);
+ ctx.strokeStyle = res.bg2;
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, a2s, (a2e - a2s) * res.value2 + a2s, false);
+ ctx.strokeStyle = res.fg2;
+ ctx.stroke();
+ }
+ }
+
+ Behavior on value1 {
+ NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+
+ Behavior on value2 {
+ NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+
+ Behavior on fg1 {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+
+ Behavior on fg2 {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+
+ Behavior on bg1 {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+
+ Behavior on bg2 {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+ }
+}
diff --git a/services/SystemUsage.qml b/services/SystemUsage.qml
index c5503b9..e545210 100644
--- a/services/SystemUsage.qml
+++ b/services/SystemUsage.qml
@@ -8,6 +8,9 @@ Singleton {
id: root
property real cpuPerc
+ property real cpuTemp
+ property real gpuPerc
+ property real gpuTemp
property int memUsed
property int memTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
@@ -18,14 +21,41 @@ Singleton {
property int lastCpuIdle
property int lastCpuTotal
+ function formatKib(kib: int): var {
+ const mib = 1024;
+ const gib = 1024 ** 2;
+ const tib = 1024 ** 3;
+
+ if (kib >= tib)
+ return {
+ value: kib / tib,
+ unit: "TiB"
+ };
+ if (kib >= gib)
+ return {
+ value: kib / gib,
+ unit: "GiB"
+ };
+ if (kib >= mib)
+ return {
+ value: kib / mib,
+ unit: "MiB"
+ };
+ return {
+ value: kib,
+ unit: "KiB"
+ };
+ }
+
Timer {
running: true
- interval: 1000
+ interval: 3000
repeat: true
onTriggered: {
stat.reload();
meminfo.reload();
storage.running = true;
+ cpuTemp.running = true;
}
}
@@ -81,4 +111,56 @@ Singleton {
}
}
}
+
+ Process {
+ id: cpuTemp
+
+ running: true
+ command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"]
+ stdout: SplitParser {
+ onRead: data => {
+ const temps = data.trim().split(" ");
+ const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0);
+ root.cpuTemp = sum / temps.length / 1000;
+ }
+ }
+ }
+
+ Process {
+ id: gpuUsage
+
+ running: true
+ command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"]
+ stdout: SplitParser {
+ splitMarker: ""
+ onRead: data => root.gpuPerc = data.trim().split("\n").reduce((acc, d) => acc + parseInt(d, 10), 0)
+ }
+ }
+
+ Process {
+ id: gpuTemp
+
+ running: true
+ command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"]
+ stdout: SplitParser {
+ readonly property var tempTest: new RegExp("^temp[0-9]+:")
+
+ onRead: data => {
+ let eligible = false;
+ let sum = 0;
+ let count = 0;
+ for (const line of JSON.parse(data)) {
+ if (line === "Adapter: PCI Adapter")
+ eligible = true;
+ else if (line === "")
+ eligible = false;
+ else if (eligible && (line.startsWith("GPU core:") || tempTest.test(line))) {
+ sum += parseFloat(line).split(" ")[1];
+ count++;
+ }
+ }
+ root.gpuTemp = count > 0 ? sum / count : 0;
+ }
+ }
+ }
}