forked from Shinonome/dots-hyprland
hefty: bar: resources popup: show mem & cpu
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property list<real> valueWeights: [1]
|
||||||
|
property list<real> values: [0.5]
|
||||||
|
property list<color> valueHighlights: ["white"]
|
||||||
|
property list<color> valueTroughs: []
|
||||||
|
|
||||||
|
readonly property list<real> normalizedValueWeights: {
|
||||||
|
const totalWeight = valueWeights.reduce((sum, weight) => sum + weight, 0)
|
||||||
|
return valueWeights.map(weight => weight / totalWeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<real> visualEnds: {
|
||||||
|
let cumsum = 0;
|
||||||
|
let positions = [];
|
||||||
|
for (let i = 0; i < normalizedValueWeights.length; i++) {
|
||||||
|
cumsum += normalizedValueWeights[i];
|
||||||
|
positions.push(cumsum);
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<real> visualPositions: {
|
||||||
|
let positions = [];
|
||||||
|
let lastEnd = 0;
|
||||||
|
for(let i = 0; i < visualEnds.length; i++) {
|
||||||
|
const thisEnd = visualEnds[i];
|
||||||
|
const width = thisEnd - lastEnd;
|
||||||
|
const thisPos = lastEnd + width * values[i];
|
||||||
|
positions.push(thisPos);
|
||||||
|
lastEnd = visualEnds[i];
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<var> visualSegments: {
|
||||||
|
let segs = [];
|
||||||
|
let lastEnd = 0;
|
||||||
|
for(let i = 0; i < visualEnds.length; i++) {
|
||||||
|
const thisEnd = visualEnds[i];
|
||||||
|
const thisPos = visualPositions[i];
|
||||||
|
segs.push([lastEnd, thisPos]);
|
||||||
|
segs.push([thisPos, thisEnd]);
|
||||||
|
lastEnd = visualEnds[i];
|
||||||
|
}
|
||||||
|
return segs;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<color> segmentColors: {
|
||||||
|
var cols = [];
|
||||||
|
for(let i = 0; i < valueHighlights.length; i++) {
|
||||||
|
cols.push(valueHighlights[i]);
|
||||||
|
cols.push(valueTroughs[i]);
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
import QtQuick
|
||||||
|
import qs.modules.common
|
||||||
|
|
||||||
|
AbstractCombinedProgressBar {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real valueBarWidth: 120
|
||||||
|
property real valueBarHeight: 4
|
||||||
|
property real valueBarGap: 4
|
||||||
|
property real valueBarInnerRadius: Appearance.rounding.unsharpen
|
||||||
|
valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary]
|
||||||
|
valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer]
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
implicitWidth: root.valueBarWidth
|
||||||
|
implicitHeight: root.valueBarHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// "negligible" = too small that it'd look weird when shown
|
||||||
|
function isNegligibleSegment(seg: var): bool {
|
||||||
|
const wdth = seg[1] - seg[0];
|
||||||
|
const visualWidth = availableWidth * wdth;
|
||||||
|
return (visualWidth <= valueBarGap + valueBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
Repeater {
|
||||||
|
model: root.visualSegments
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
visible: !root.isNegligibleSegment(modelData)
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
property bool atStart: index == 0
|
||||||
|
property bool atEnd: index == root.visualSegments.length - 1
|
||||||
|
property real displaySegStart: { // swallow previous segments if they're "negligible"
|
||||||
|
var i = index;
|
||||||
|
while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i-1])))
|
||||||
|
i--;
|
||||||
|
return root.visualSegments[i][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
x: {
|
||||||
|
var result = root.availableWidth * displaySegStart;
|
||||||
|
if (!atStart) result += root.valueBarGap / 2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
width: {
|
||||||
|
var result = root.availableWidth * (modelData[1] - displaySegStart)
|
||||||
|
if (atStart || atEnd) result -= root.valueBarGap / 2;
|
||||||
|
else result -= root.valueBarGap;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
color: root.segmentColors[index % root.segmentColors.length]
|
||||||
|
|
||||||
|
property real startRadius: atStart ? height / 2 : root.valueBarInnerRadius
|
||||||
|
property real endRadius: atEnd ? height / 2 : root.valueBarInnerRadius
|
||||||
|
|
||||||
|
topLeftRadius: startRadius
|
||||||
|
bottomLeftRadius: startRadius
|
||||||
|
topRightRadius: endRadius
|
||||||
|
bottomRightRadius: endRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,6 +162,102 @@ HBarWidgetWithPopout {
|
|||||||
W.FlyFadeEnterChoreographable {
|
W.FlyFadeEnterChoreographable {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
implicitHeight: memUsed.implicitHeight
|
||||||
|
|
||||||
|
BigSmallTextPair {
|
||||||
|
id: memUsed
|
||||||
|
materialSymbol: "memory"
|
||||||
|
bigText: S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryUsed, false)
|
||||||
|
smallText: {
|
||||||
|
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryTotal, false);
|
||||||
|
return S.Translation.tr("%1").arg(`/ ${total}`)
|
||||||
|
}
|
||||||
|
W.StyledText {
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
text: S.Translation.tr("Memory")
|
||||||
|
color: C.Appearance.colors.colOutline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigSmallTextPair {
|
||||||
|
id: swapUsed
|
||||||
|
TextMetrics {
|
||||||
|
id: plusTextMetric
|
||||||
|
font: swapUsed.bigFont
|
||||||
|
text: "+"
|
||||||
|
}
|
||||||
|
property real halfWidthOfAPlus: plusTextMetric.width / 2
|
||||||
|
x: Math.min(memProg.availableWidth * memProg.visualEnds[0] - halfWidthOfAPlus, parent.width - width)
|
||||||
|
bigText: "+ " + S.ResourceUsage.kbToGbString(S.ResourceUsage.swapUsed, false)
|
||||||
|
smallText: {
|
||||||
|
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.swapTotal, false);
|
||||||
|
return `/ ${total} GB`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
W.StyledCombinedProgressBar {
|
||||||
|
id: memProg
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal]
|
||||||
|
values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
W.FlyFadeEnterChoreographable {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
BigSmallTextPair {
|
||||||
|
spacing: 0
|
||||||
|
materialSymbol: "developer_board"
|
||||||
|
bigText: Math.round(S.ResourceUsage.cpuUsage * 100)
|
||||||
|
smallText: "%"
|
||||||
|
W.StyledText {
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
text: " " + S.Translation.tr("CPU")
|
||||||
|
color: C.Appearance.colors.colOutline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
W.StyledCombinedProgressBar {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
property bool useSingleAggregate: S.ResourceUsage.cpuCoreUsages.length > 8
|
||||||
|
valueWeights: useSingleAggregate ? [1] : S.ResourceUsage.cpuCoreFreqCaps
|
||||||
|
values: useSingleAggregate ? [S.ResourceUsage.cpuUsage] : S.ResourceUsage.cpuCoreUsages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
W.FlyFadeEnterChoreographable {
|
||||||
|
Layout.topMargin: 8
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: 10
|
spacing: 10
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -258,6 +354,36 @@ HBarWidgetWithPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component BigSmallTextPair: RowLayout {
|
||||||
|
id: txtPair
|
||||||
|
property string materialSymbol: ""
|
||||||
|
property string bigText: ""
|
||||||
|
property string smallText: ""
|
||||||
|
property alias bigFont: bigTxt.font
|
||||||
|
property alias smallFont: smallTxt.font
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
W.MaterialSymbol {
|
||||||
|
Layout.rightMargin: 6 - spacing
|
||||||
|
visible: text.length > 0
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
text: txtPair.materialSymbol
|
||||||
|
fill: 1
|
||||||
|
iconSize: 24
|
||||||
|
}
|
||||||
|
W.StyledText {
|
||||||
|
id: bigTxt
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
font.pixelSize: C.Appearance.font.pixelSize.title
|
||||||
|
text: txtPair.bigText
|
||||||
|
}
|
||||||
|
W.StyledText {
|
||||||
|
id: smallTxt
|
||||||
|
Layout.alignment: Qt.AlignBaseline
|
||||||
|
text: txtPair.smallText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
component StatWithIcon: Item {
|
component StatWithIcon: Item {
|
||||||
id: statItem
|
id: statItem
|
||||||
required property string icon
|
required property string icon
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ Singleton {
|
|||||||
property real swapUsed: swapTotal - swapFree
|
property real swapUsed: swapTotal - swapFree
|
||||||
property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
||||||
property real cpuUsage: 0
|
property real cpuUsage: 0
|
||||||
|
property list<real> cpuCoreUsages: []
|
||||||
|
property list<real> cpuCoreFreqCaps: []
|
||||||
property var previousCpuStats
|
property var previousCpuStats
|
||||||
|
|
||||||
property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal)
|
property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal)
|
||||||
@@ -31,10 +33,13 @@ Singleton {
|
|||||||
property list<real> memoryUsageHistory: []
|
property list<real> memoryUsageHistory: []
|
||||||
property list<real> swapUsageHistory: []
|
property list<real> swapUsageHistory: []
|
||||||
|
|
||||||
function kbToGbString(kb) {
|
function kbToGbString(kb, attachUnit = true) {
|
||||||
return (kb / (1024 * 1024)).toFixed(1) + " GB";
|
return (kb / (1024 * 1024)).toFixed(1) + (attachUnit ? " GB" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// onCpuCoreUsagesChanged: print(cpuCoreUsages)
|
||||||
|
// onCpuCoreFreqCapsChanged: print(cpuCoreFreqCaps)
|
||||||
|
|
||||||
function updateMemoryUsageHistory() {
|
function updateMemoryUsageHistory() {
|
||||||
memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage]
|
memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage]
|
||||||
if (memoryUsageHistory.length > historyLength) {
|
if (memoryUsageHistory.length > historyLength) {
|
||||||
@@ -77,20 +82,36 @@ Singleton {
|
|||||||
|
|
||||||
// Parse CPU usage
|
// Parse CPU usage
|
||||||
const textStat = fileStat.text()
|
const textStat = fileStat.text()
|
||||||
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
|
const lines = textStat.split("\n")
|
||||||
if (cpuLine) {
|
const currentStats = {}
|
||||||
const stats = cpuLine.slice(1).map(Number)
|
const coreUsages = []
|
||||||
const total = stats.reduce((a, b) => a + b, 0)
|
|
||||||
const idle = stats[3]
|
|
||||||
|
|
||||||
if (previousCpuStats) {
|
for (const line of lines) {
|
||||||
const totalDiff = total - previousCpuStats.total
|
const match = line.match(/^(cpu\d*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
|
||||||
const idleDiff = idle - previousCpuStats.idle
|
if (match) {
|
||||||
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0
|
const name = match[1]
|
||||||
|
const stats = match.slice(2).map(Number)
|
||||||
|
const total = stats.reduce((a, b) => a + b, 0)
|
||||||
|
const idle = stats[3]
|
||||||
|
|
||||||
|
let usage = 0
|
||||||
|
if (previousCpuStats && previousCpuStats[name]) {
|
||||||
|
const totalDiff = total - previousCpuStats[name].total
|
||||||
|
const idleDiff = idle - previousCpuStats[name].idle
|
||||||
|
usage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStats[name] = { total, idle }
|
||||||
|
|
||||||
|
if (name === "cpu") {
|
||||||
|
cpuUsage = usage
|
||||||
|
} else {
|
||||||
|
coreUsages.push(usage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousCpuStats = { total, idle }
|
|
||||||
}
|
}
|
||||||
|
previousCpuStats = currentStats
|
||||||
|
cpuCoreUsages = coreUsages
|
||||||
|
|
||||||
root.updateHistories()
|
root.updateHistories()
|
||||||
interval = Config.options?.resources?.updateInterval ?? 3000
|
interval = Config.options?.resources?.updateInterval ?? 3000
|
||||||
@@ -106,12 +127,19 @@ Singleton {
|
|||||||
LANG: "C",
|
LANG: "C",
|
||||||
LC_ALL: "C"
|
LC_ALL: "C"
|
||||||
})
|
})
|
||||||
command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"]
|
command: ["bash", "-c", "cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq 2>/dev/null || lscpu | grep 'CPU max MHz' | awk '{print $4 * 1000}'"]
|
||||||
running: true
|
running: true
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
id: outputCollector
|
id: outputCollector
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + " GHz"
|
const lines = outputCollector.text.trim().split("\n")
|
||||||
|
const caps = lines.map(line => parseFloat(line)).filter(val => !isNaN(val))
|
||||||
|
|
||||||
|
if (caps.length > 0) {
|
||||||
|
root.cpuCoreFreqCaps = caps
|
||||||
|
const maxFreq = Math.max(...caps)
|
||||||
|
root.maxAvailableCpuString = (maxFreq / 1000000).toFixed(1) + " GHz"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user