Files
noctalia-shell/Modules/Bar/Widgets/SystemMonitor.qml
T

506 lines
21 KiB
QML

import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Panels.Settings
import qs.Services.UI
import qs.Services.System
import qs.Widgets
Rectangle {
id: root
property ShellScreen screen
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool density: Settings.data.bar.density
readonly property bool usePrimaryColor: widgetSettings.usePrimaryColor !== undefined ? widgetSettings.usePrimaryColor : widgetMetadata.usePrimaryColor
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property real iconSize: textSize * 1.4
readonly property real textSize: {
var base = isVertical ? width * 0.82 : height
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33)
}
// Match Workspace widget pill sizing: base ratio depends on bar density
readonly property real pillBaseRatio: (density === "compact") ? 0.85 : 0.65
readonly property real pillHeight: Style.capsuleHeight * pillBaseRatio
// Highlight colors
readonly property color warningColor: Color.mTertiary
readonly property color criticalColor: Color.mError
readonly property int percentTextWidth: Math.ceil(percentMetrics.boundingRect.width + 3)
readonly property int tempTextWidth: Math.ceil(tempMetrics.boundingRect.width + 3)
readonly property int memTextWidth: Math.ceil(memMetrics.boundingRect.width + 3)
readonly property color textColor: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
// Threshold settings from widget configuration
readonly property int cpuWarningThreshold: (widgetSettings.cpuWarningThreshold !== undefined) ? widgetSettings.cpuWarningThreshold : widgetMetadata.cpuWarningThreshold
readonly property int cpuCriticalThreshold: (widgetSettings.cpuCriticalThreshold !== undefined) ? widgetSettings.cpuCriticalThreshold : widgetMetadata.cpuCriticalThreshold
readonly property int tempWarningThreshold: (widgetSettings.tempWarningThreshold !== undefined) ? widgetSettings.tempWarningThreshold : widgetMetadata.tempWarningThreshold
readonly property int tempCriticalThreshold: (widgetSettings.tempCriticalThreshold !== undefined) ? widgetSettings.tempCriticalThreshold : widgetMetadata.tempCriticalThreshold
readonly property int memWarningThreshold: (widgetSettings.memWarningThreshold !== undefined) ? widgetSettings.memWarningThreshold : widgetMetadata.memWarningThreshold
readonly property int memCriticalThreshold: (widgetSettings.memCriticalThreshold !== undefined) ? widgetSettings.memCriticalThreshold : widgetMetadata.memCriticalThreshold
readonly property int diskWarningThreshold: (widgetSettings.diskWarningThreshold !== undefined) ? widgetSettings.diskWarningThreshold : widgetMetadata.diskWarningThreshold
readonly property int diskCriticalThreshold: (widgetSettings.diskCriticalThreshold !== undefined) ? widgetSettings.diskCriticalThreshold : widgetMetadata.diskCriticalThreshold
// Warning threshold calculation properties
readonly property bool cpuWarning: showCpuUsage && SystemStatService.cpuUsage > cpuWarningThreshold
readonly property bool cpuCritical: showCpuUsage && SystemStatService.cpuUsage > cpuCriticalThreshold
readonly property bool tempWarning: showCpuTemp && SystemStatService.cpuTemp > tempWarningThreshold
readonly property bool tempCritical: showCpuTemp && SystemStatService.cpuTemp > tempCriticalThreshold
readonly property bool memWarning: showMemoryUsage && SystemStatService.memPercent > memWarningThreshold
readonly property bool memCritical: showMemoryUsage && SystemStatService.memPercent > memCriticalThreshold
readonly property bool diskWarning: showDiskUsage && SystemStatService.diskPercents["/"] > diskWarningThreshold
readonly property bool diskCritical: showDiskUsage && SystemStatService.diskPercents["/"] > diskCriticalThreshold
TextMetrics {
id: percentMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99%" // Use the longest possible string for measurement
}
TextMetrics {
id: tempMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99°" // Use the longest possible string for measurement
}
TextMetrics {
id: memMetrics
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightMedium
font.pointSize: textSize * Settings.data.ui.fontFixedScale
text: "99.9K" // Longest value part of network speed
}
anchors.centerIn: parent
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(mainGrid.implicitWidth + Style.marginM * 2)
implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
radius: Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Status indicator component definition
Component {
id: statusIndicatorComponent
Rectangle {
id: statusIndicator
property bool warning: false
property bool critical: false
property int indicatorWidth: Style.capsuleHeight
width: isVertical ? Math.max(0, indicatorWidth - Style.marginS * 2) : Math.max(0, indicatorWidth + Style.marginXS * 2)
height: isVertical ? Math.max(0, Style.capsuleHeight + Style.marginXS * 2) : pillHeight
radius: Math.min(width, height) / 2
// Hide the rectangular indicator when the bar is vertical; keep it available for horizontal layout
visible: !root.isVertical
color: critical ? Color.mError : Color.mTertiary
scale: (warning || critical) ? 1.0 : 0.0
opacity: (warning || critical) ? 1.0 : 0.0
// Smooth appearance/disappearance animation
Behavior on scale {
NumberAnimation { duration: Style.animationNormal; easing.type: Easing.OutCubic }
}
Behavior on opacity {
NumberAnimation { duration: Style.animationNormal; easing.type: Easing.OutCubic }
}
Behavior on color {
ColorAnimation { duration: Style.animationNormal; easing.type: Easing.OutCubic }
}
}
}
GridLayout {
id: mainGrid
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? -1 : 1
columns: isVertical ? 1 : -1
rowSpacing: isVertical ? (Style.marginM) : 0
columnSpacing: isVertical ? 0 : (Style.marginM)
// CPU Usage Component
Item {
id: cpuUsageContainer
Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuUsage
// Status indicator covering the entire component
Loader {
sourceComponent: statusIndicatorComponent
anchors.centerIn: parent
onLoaded: {
item.warning = Qt.binding(() => cpuWarning)
item.critical = Qt.binding(() => cpuCritical)
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width)
}
}
GridLayout {
id: cpuUsageContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
Item {
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
implicitWidth: iconSize
implicitHeight: iconSize
NIcon {
icon: "cpu-usage"
pointSize: iconSize
applyUiScale: false
anchors.centerIn: parent
// Invert color to bar background when indicator active
color: isVertical ? (cpuCritical ? criticalColor : (cpuWarning ? warningColor : Color.mOnSurface)) : ((cpuWarning || cpuCritical) ? Color.mSurfaceVariant : Color.mOnSurface)
}
}
NText {
text: {
let usage = Math.round(SystemStatService.cpuUsage)
if (usage < 100) {
return `${usage}%`
} else {
return usage
}
}
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
// Use highlight colors in vertical bar; otherwise invert text color to bar background when indicator active
color: isVertical ? (cpuCritical ? criticalColor : (cpuWarning ? warningColor : textColor)) : ((cpuWarning || cpuCritical) ? Color.mSurfaceVariant : textColor)
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// CPU Temperature Component
Item {
id: cpuTempContainer
Layout.preferredWidth: isVertical ? root.width : (iconSize + tempTextWidth) + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuTemp
// Status indicator covering the entire component
Loader {
sourceComponent: statusIndicatorComponent
anchors.centerIn: parent
onLoaded: {
item.warning = Qt.binding(() => tempWarning)
item.critical = Qt.binding(() => tempCritical)
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width)
}
}
GridLayout {
id: cpuTempContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
Item {
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
implicitWidth: iconSize
implicitHeight: iconSize
NIcon {
icon: "cpu-temperature"
pointSize: iconSize
applyUiScale: false
anchors.centerIn: parent
// Invert color when temp indicator active
color: isVertical ? (tempCritical ? criticalColor : (tempWarning ? warningColor : Color.mOnSurface)) : ((tempWarning || tempCritical) ? Color.mSurfaceVariant : Color.mOnSurface)
}
}
NText {
text: `${Math.round(SystemStatService.cpuTemp)}°`
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : tempTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
// Use highlight colors in vertical bar; otherwise invert text color to bar background when temp indicator active
color: isVertical ? (tempCritical ? criticalColor : (tempWarning ? warningColor : textColor)) : ((tempWarning || tempCritical) ? Color.mSurfaceVariant : textColor)
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Memory Usage Component
Item {
id: memoryContainer
Layout.preferredWidth: isVertical ? root.width : iconSize + (showMemoryAsPercent ? percentTextWidth : memTextWidth) + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showMemoryUsage
// Status indicator covering the entire component
Loader {
sourceComponent: statusIndicatorComponent
anchors.centerIn: parent
onLoaded: {
item.warning = Qt.binding(() => memWarning)
item.critical = Qt.binding(() => memCritical)
item.indicatorWidth = Qt.binding(() => memoryContainer.width)
}
}
GridLayout {
id: memoryContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
Item {
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
implicitWidth: iconSize
implicitHeight: iconSize
NIcon {
icon: "memory"
pointSize: iconSize
applyUiScale: false
anchors.centerIn: parent
// Invert color when memory indicator active
color: isVertical ? (memCritical ? criticalColor : (memWarning ? warningColor : Color.mOnSurface)) : ((memWarning || memCritical) ? Color.mSurfaceVariant : Color.mOnSurface)
}
}
NText {
text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : `${SystemStatService.memGb.toFixed(1)}G`
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : (showMemoryAsPercent ? percentTextWidth : memTextWidth)
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
// Use highlight colors in vertical bar; otherwise invert text color to bar background when memory indicator active
color: isVertical ? (memCritical ? criticalColor : (memWarning ? warningColor : textColor)) : ((memWarning || memCritical) ? Color.mSurfaceVariant : textColor)
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Network Download Speed Component
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
GridLayout {
id: downloadContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
NIcon {
icon: "download-speed"
pointSize: iconSize
applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: textColor
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Network Upload Speed Component
Item {
Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
GridLayout {
id: uploadContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
NIcon {
icon: "upload-speed"
pointSize: iconSize
applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
color: textColor
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
// Disk Usage Component (primary drive)
Item {
id: diskContainer
Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS)
Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showDiskUsage
// Status indicator covering the entire component
Loader {
sourceComponent: statusIndicatorComponent
anchors.centerIn: parent
onLoaded: {
item.warning = Qt.binding(() => diskWarning)
item.critical = Qt.binding(() => diskCritical)
item.indicatorWidth = Qt.binding(() => diskContainer.width)
}
}
GridLayout {
id: diskContent
anchors.centerIn: parent
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS
columnSpacing: Style.marginXXS
NIcon {
icon: "storage"
pointSize: iconSize
applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
// Invert color when disk indicator active (vertical uses highlight colors)
color: isVertical ? (diskCritical ? criticalColor : (diskWarning ? warningColor : Color.mOnSurface)) : ((diskWarning || diskCritical) ? Color.mSurfaceVariant : Color.mOnSurface)
}
NText {
text: `${SystemStatService.diskPercents["/"]}%`
family: Settings.data.ui.fontFixed
pointSize: textSize
applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
horizontalAlignment: isVertical ? Text.AlignHCenter : Text.AlignRight
verticalAlignment: Text.AlignVCenter
// Use highlight colors in vertical bar; otherwise invert text color to bar background when disk indicator active
color: isVertical ? (diskCritical ? criticalColor : (diskWarning ? warningColor : textColor)) : ((diskWarning || diskCritical) ? Color.mSurfaceVariant : textColor)
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
scale: isVertical ? Math.min(1.0, root.width / implicitWidth) : 1.0
}
}
}
}
}