Merge pull request #1622 from xzeldon/feat/system-monitor-enhancements

feat: add CPU frequency and free disk space display options
This commit is contained in:
Lemmy
2026-01-30 16:03:09 -05:00
committed by GitHub
4 changed files with 177 additions and 7 deletions
+82 -3
View File
@@ -43,6 +43,7 @@ Item {
readonly property bool usePrimaryColor: widgetSettings.usePrimaryColor !== undefined ? widgetSettings.usePrimaryColor : widgetMetadata.usePrimaryColor
readonly property bool useMonospaceFont: widgetSettings.useMonospaceFont !== undefined ? widgetSettings.useMonospaceFont : widgetMetadata.useMonospaceFont
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuFreq: (widgetSettings.showCpuFreq !== undefined) ? widgetSettings.showCpuFreq : widgetMetadata.showCpuFreq
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : widgetMetadata.showGpuTemp
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
@@ -50,6 +51,7 @@ Item {
readonly property bool showSwapUsage: (widgetSettings.showSwapUsage !== undefined) ? widgetSettings.showSwapUsage : widgetMetadata.showSwapUsage
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property bool showDiskAsFree: (widgetSettings.showDiskAsFree !== undefined) ? widgetSettings.showDiskAsFree : widgetMetadata.showDiskAsFree
readonly property bool showLoadAverage: (widgetSettings.showLoadAverage !== undefined) ? widgetSettings.showLoadAverage : widgetMetadata.showLoadAverage
readonly property string diskPath: (widgetSettings.diskPath !== undefined) ? widgetSettings.diskPath : widgetMetadata.diskPath
readonly property string fontFamily: useMonospaceFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
@@ -75,7 +77,7 @@ Item {
let lines = [];
// CPU
lines.push(`${I18n.tr("system-monitor.cpu-usage")}: ${Math.round(SystemStatService.cpuUsage)}%`);
lines.push(`${I18n.tr("system-monitor.cpu-usage")}: ${Math.round(SystemStatService.cpuUsage)}% (${SystemStatService.cpuFreq})`);
if (SystemStatService.cpuTemp > 0) {
lines.push(`${I18n.tr("system-monitor.cpu-temp")}: ${Math.round(SystemStatService.cpuTemp)}°C`);
}
@@ -107,7 +109,9 @@ Item {
if (diskPercent !== undefined) {
const usedGb = SystemStatService.diskUsedGb[diskPath] || 0;
const sizeGb = SystemStatService.diskSizeGb[diskPath] || 0;
lines.push(`${I18n.tr("system-monitor.disk")}: ${usedGb.toFixed(1)} GB / ${sizeGb.toFixed(1)} GB (${diskPercent}%)`);
const availGb = SystemStatService.diskAvailGb[diskPath] || 0;
lines.push(`${I18n.tr("system-monitor.disk")}: ${usedGb.toFixed(1)}G / ${sizeGb.toFixed(1)}G Used (${diskPercent}%)`);
lines.push(`Available: ${availGb.toFixed(1)}G`);
}
return lines.join("\n");
@@ -279,6 +283,74 @@ Item {
}
}
// CPU Frequency Component
Item {
id: cpuFreqContainer
implicitWidth: cpuFreqContent.implicitWidth
implicitHeight: cpuFreqContent.implicitHeight
Layout.preferredWidth: isVertical ? root.width : implicitWidth
Layout.preferredHeight: compactMode ? implicitHeight : capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuFreq && (!isVertical || compactMode)
GridLayout {
id: cpuFreqContent
anchors.centerIn: parent
flow: (isVertical && !compactMode) ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: (isVertical && !compactMode) ? 2 : 1
columns: (isVertical && !compactMode) ? 1 : 2
rowSpacing: Style.marginXXS
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: "cpu-usage"
pointSize: iconSize
applyUiScale: false
x: Style.pixelAlignCenter(parent.width, width)
y: Style.pixelAlignCenter(parent.height, contentHeight)
color: Color.mOnSurface
}
}
// Text mode
NText {
visible: !compactMode
text: SystemStatService.cpuFreq.replace(" ", "")
family: fontFamily
pointSize: barFontSize
applyUiScale: false
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: textColor
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
// Compact mode
Loader {
active: compactMode
visible: compactMode
sourceComponent: miniGaugeComponent
Layout.alignment: Qt.AlignCenter
Layout.row: 0
Layout.column: 1
onLoaded: {
item.ratio = Qt.binding(() => SystemStatService.cpuFreqRatio);
item.statColor = Qt.binding(() => Color.mPrimary);
}
}
}
}
// CPU Temperature Component
Item {
id: cpuTempContainer
@@ -788,7 +860,14 @@ Item {
// Text mode
NText {
visible: !compactMode
text: SystemStatService.diskPercents[diskPath] ? `${SystemStatService.diskPercents[diskPath]}%` : "n/a"
text: {
if (showDiskAsFree && !isVertical) {
let avail = SystemStatService.diskAvailGb[diskPath] || 0;
return avail.toFixed(1) + "G";
} else {
return SystemStatService.diskPercents[diskPath] ? `${SystemStatService.diskPercents[diskPath]}%` : "n/a";
}
}
family: fontFamily
pointSize: barFontSize
applyUiScale: false
@@ -20,6 +20,7 @@ ColumnLayout {
property bool valueUsePrimaryColor: widgetData.usePrimaryColor !== undefined ? widgetData.usePrimaryColor : widgetMetadata.usePrimaryColor
property bool valueUseMonospaceFont: widgetData.useMonospaceFont !== undefined ? widgetData.useMonospaceFont : widgetMetadata.useMonospaceFont
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
property bool valueShowCpuFreq: widgetData.showCpuFreq !== undefined ? widgetData.showCpuFreq : widgetMetadata.showCpuFreq
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : widgetMetadata.showGpuTemp
property bool valueShowLoadAverage: widgetData.showLoadAverage !== undefined ? widgetData.showLoadAverage : widgetMetadata.showLoadAverage
@@ -28,6 +29,7 @@ ColumnLayout {
property bool valueShowSwapUsage: widgetData.showSwapUsage !== undefined ? widgetData.showSwapUsage : widgetMetadata.showSwapUsage
property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
property bool valueShowDiskAsFree: widgetData.showDiskAsFree !== undefined ? widgetData.showDiskAsFree : widgetMetadata.showDiskAsFree
property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath
function saveSettings() {
@@ -36,6 +38,7 @@ ColumnLayout {
settings.usePrimaryColor = valueUsePrimaryColor;
settings.useMonospaceFont = valueUseMonospaceFont;
settings.showCpuUsage = valueShowCpuUsage;
settings.showCpuFreq = valueShowCpuFreq;
settings.showCpuTemp = valueShowCpuTemp;
settings.showGpuTemp = valueShowGpuTemp;
settings.showLoadAverage = valueShowLoadAverage;
@@ -44,6 +47,7 @@ ColumnLayout {
settings.showSwapUsage = valueShowSwapUsage;
settings.showNetworkStats = valueShowNetworkStats;
settings.showDiskUsage = valueShowDiskUsage;
settings.showDiskAsFree = valueShowDiskAsFree;
settings.diskPath = valueDiskPath;
return settings;
@@ -96,6 +100,18 @@ ColumnLayout {
}
}
NToggle {
id: showCpuFreq
Layout.fillWidth: true
label: "Show CPU Frequency" // TODO: use I18n.tr
description: "Display the current CPU clock speed in GHz" // TODO: use I18n.tr
checked: valueShowCpuFreq
onToggled: checked => {
valueShowCpuFreq = checked;
settingsChanged(saveSettings());
}
}
NToggle {
id: showCpuTemp
Layout.fillWidth: true
@@ -194,6 +210,19 @@ ColumnLayout {
}
}
NToggle {
id: showDiskAsFree
Layout.fillWidth: true
label: "Show Free Space" // TODO: use I18n.tr
description: "Display available space (GB) instead of percentage" // TODO: use I18n.tr
checked: valueShowDiskAsFree
onToggled: checked => {
valueShowDiskAsFree = checked;
settingsChanged(saveSettings());
}
visible: valueShowDiskUsage
}
NComboBox {
id: diskPathComboBox
Layout.fillWidth: true
+64 -4
View File
@@ -20,6 +20,9 @@ Singleton {
// Public values
property real cpuUsage: 0
property real cpuTemp: 0
property string cpuFreq: "0.0GHz"
property real cpuFreqRatio: 0
property real cpuGlobalMaxFreq: 3.5
property real gpuTemp: 0
property bool gpuAvailable: false
property string gpuType: "" // "amd", "intel", "nvidia"
@@ -31,6 +34,7 @@ Singleton {
property var diskPercents: ({})
property var diskUsedGb: ({}) // Used space in GB per mount point
property var diskSizeGb: ({}) // Total size in GB per mount point
property var diskAvailGb: ({})
property real rxSpeed: 0
property real txSpeed: 0
property real zfsArcSizeKb: 0 // ZFS ARC cache size in KB
@@ -243,7 +247,10 @@ Singleton {
restart();
}
}
onTriggered: cpuStatFile.reload()
onTriggered: {
cpuStatFile.reload();
cpuFreqProcess.running = true;
}
}
// Timer for load average
@@ -379,13 +386,13 @@ Singleton {
}
// --------------------------------------------
// Process to fetch disk usage (percent, used, size)
// Process to fetch disk usage (percent, used, size, avail)
// Uses 'df' aka 'disk free'
// "-x efivarfs' skips efivarfs mountpoints, for which the `statfs` syscall may cause system-wide stuttering
// --block-size=1 gives us bytes for precise GB calculation
Process {
id: dfProcess
command: ["df", "--output=target,pcent,used,size", "--block-size=1", "-x", "efivarfs"]
command: ["df", "--output=target,pcent,used,size,avail", "--block-size=1", "-x", "efivarfs"]
running: false
stdout: StdioCollector {
onStreamFinished: {
@@ -393,23 +400,27 @@ Singleton {
const newPercents = {};
const newUsedGb = {};
const newSizeGb = {};
const newAvailGb = {};
const bytesPerGb = 1024 * 1024 * 1024;
// Start from line 1 (skip header)
for (var i = 1; i < lines.length; i++) {
const parts = lines[i].trim().split(/\s+/);
if (parts.length >= 4) {
if (parts.length >= 5) {
const target = parts[0];
const percent = parseInt(parts[1].replace(/[^0-9]/g, '')) || 0;
const usedBytes = parseFloat(parts[2]) || 0;
const sizeBytes = parseFloat(parts[3]) || 0;
const availBytes = parseFloat(parts[4]) || 0;
newPercents[target] = percent;
newUsedGb[target] = usedBytes / bytesPerGb;
newSizeGb[target] = sizeBytes / bytesPerGb;
newAvailGb[target] = availBytes / bytesPerGb;
}
}
root.diskPercents = newPercents;
root.diskUsedGb = newUsedGb;
root.diskSizeGb = newSizeGb;
root.diskAvailGb = newAvailGb;
}
}
}
@@ -426,6 +437,55 @@ Singleton {
}
}
// Process to get avg cpu frquency
Process {
id: cpuFreqProcess
command: ["cat", "/proc/cpuinfo"]
running: false
stdout: StdioCollector {
onStreamFinished: {
let txt = text;
let matches = txt.match(/cpu MHz\s+:\s+([0-9.]+)/g);
if (matches && matches.length > 0) {
let totalFreq = 0.0;
for (let i = 0; i < matches.length; i++) {
totalFreq += parseFloat(matches[i].split(":")[1]);
}
let avgFreq = (totalFreq / matches.length) / 1000.0;
root.cpuFreq = avgFreq.toFixed(1) + " GHz";
cpuMaxFreqProcess.running = true;
if (avgFreq > root.cpuGlobalMaxFreq)
root.cpuGlobalMaxFreq = avgFreq;
if (root.cpuGlobalMaxFreq > 0) {
root.cpuFreqRatio = Math.min(1.0, avgFreq / root.cpuGlobalMaxFreq);
}
}
}
}
}
// Process to get maximum CPU frequency limit
// Uses sysfs 'scaling_max_freq' to respect power profiles (e.g. power-profiles-daemon)
// 'sort -nr | head -n1' ensures we get the highest limit across all cores
// '2>/dev/null' ignores errors if cpufreq driver is missing or cores are offline
Process {
id: cpuMaxFreqProcess
command: ["sh", "-c", "cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq 2>/dev/null | sort -nr | head -n1"]
running: false
stdout: StdioCollector {
onStreamFinished: {
let maxKHz = parseInt(text.trim());
if (!isNaN(maxKHz) && maxKHz > 0) {
let newMaxFreq = maxKHz / 1000000.0;
if (Math.abs(root.cpuGlobalMaxFreq - newMaxFreq) > 0.01) {
Logger.i("SystemStat", `CPU Max Freq changed: ${root.cpuGlobalMaxFreq} -> ${newMaxFreq} GHz`);
root.cpuGlobalMaxFreq = newMaxFreq;
}
}
}
}
}
// --------------------------------------------
// --------------------------------------------
// CPU Temperature
+2
View File
@@ -199,6 +199,7 @@ Singleton {
"usePrimaryColor": false,
"useMonospaceFont": true,
"showCpuUsage": true,
"showCpuFreq": false,
"showCpuTemp": true,
"showGpuTemp": false,
"showLoadAverage": false,
@@ -207,6 +208,7 @@ Singleton {
"showSwapUsage": false,
"showNetworkStats": false,
"showDiskUsage": false,
"showDiskAsFree": false,
"diskPath": "/"
},
"Taskbar": {