mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user