Compare commits
2 commits
0f71e6ca45
...
2f0e6bbb31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f0e6bbb31 | ||
|
|
334c48e9b4 |
6 changed files with 294 additions and 0 deletions
|
|
@ -12,6 +12,7 @@ Singleton {
|
||||||
property Mpris mpris: Mpris {}
|
property Mpris mpris: Mpris {}
|
||||||
property Clock clock: Clock {}
|
property Clock clock: Clock {}
|
||||||
property Pipewire pipewire: Pipewire {}
|
property Pipewire pipewire: Pipewire {}
|
||||||
|
property Storage storage: Storage {}
|
||||||
property Caffeine caffeine: Caffeine {}
|
property Caffeine caffeine: Caffeine {}
|
||||||
property Workspace workspace: Workspace {}
|
property Workspace workspace: Workspace {}
|
||||||
property Tray tray: Tray {}
|
property Tray tray: Tray {}
|
||||||
|
|
@ -56,6 +57,16 @@ Singleton {
|
||||||
property int verticalPadding: 6
|
property int verticalPadding: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component Storage: QtObject {
|
||||||
|
id: clock
|
||||||
|
|
||||||
|
property int iconSize: 14
|
||||||
|
property int fontSize: 14
|
||||||
|
property int height: 30
|
||||||
|
property int horizontalPadding: 8
|
||||||
|
property int verticalPadding: 6
|
||||||
|
}
|
||||||
|
|
||||||
component Caffeine: QtObject {
|
component Caffeine: QtObject {
|
||||||
id: clock
|
id: clock
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import Quickshell
|
||||||
Singleton {
|
Singleton {
|
||||||
property string brickWall: "\u{E586}"
|
property string brickWall: "\u{E586}"
|
||||||
property string coffee: "\u{E09a}"
|
property string coffee: "\u{E09a}"
|
||||||
|
property string hardDrive: "\u{E0f1}"
|
||||||
property string triangle: "\u{E192}"
|
property string triangle: "\u{E192}"
|
||||||
property string triangleDashed: "\u{E642}"
|
property string triangleDashed: "\u{E642}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,10 @@ Scope {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Storage {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
Clock {
|
Clock {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
modules/bar/components/Storage.qml
Normal file
49
modules/bar/components/Storage.qml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import "../../../config/"
|
||||||
|
import "../../../constants/"
|
||||||
|
import "../../../services/"
|
||||||
|
import "../../../styled/"
|
||||||
|
import "../../../utils/"
|
||||||
|
|
||||||
|
StyledLabel {
|
||||||
|
implicitWidth: childrenRect.width
|
||||||
|
implicitHeight: Dimensions.storage.height
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: SystemInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
StyledText {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
font.family: Theme.lucide.font.family
|
||||||
|
font.pixelSize: Dimensions.storage.iconSize
|
||||||
|
font.bold: true
|
||||||
|
text: Icons.hardDrive
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
topPadding: Dimensions.storage.verticalPadding
|
||||||
|
bottomPadding: Dimensions.storage.verticalPadding
|
||||||
|
leftPadding: Dimensions.storage.horizontalPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: text
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
topPadding: Dimensions.storage.verticalPadding
|
||||||
|
bottomPadding: Dimensions.storage.verticalPadding
|
||||||
|
rightPadding: Dimensions.storage.horizontalPadding
|
||||||
|
|
||||||
|
font.pixelSize: Dimensions.storage.fontSize
|
||||||
|
|
||||||
|
text: ` ${(SystemInfo.storagePerc * 100).toFixed()}%`
|
||||||
|
|
||||||
|
SystemClock {
|
||||||
|
id: clock
|
||||||
|
precision: SystemClock.Seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
220
services/SystemInfo.qml
Normal file
220
services/SystemInfo.qml
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real cpuPerc
|
||||||
|
property real cpuTemp
|
||||||
|
property string gpuType: "NONE"
|
||||||
|
property real gpuPerc
|
||||||
|
property real gpuTemp
|
||||||
|
property real memUsed
|
||||||
|
property real memTotal
|
||||||
|
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
|
||||||
|
property real storageUsed
|
||||||
|
property real storageTotal
|
||||||
|
property real storagePerc: storageTotal > 0 ? storageUsed / storageTotal : 0
|
||||||
|
|
||||||
|
property real lastCpuIdle
|
||||||
|
property real lastCpuTotal
|
||||||
|
|
||||||
|
property int refCount
|
||||||
|
|
||||||
|
function formatKib(kib: real): 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: root.refCount > 0
|
||||||
|
interval: 3000
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: {
|
||||||
|
stat.reload();
|
||||||
|
meminfo.reload();
|
||||||
|
storage.running = true;
|
||||||
|
gpuUsage.running = true;
|
||||||
|
sensors.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: stat
|
||||||
|
|
||||||
|
path: "/proc/stat"
|
||||||
|
onLoaded: {
|
||||||
|
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
|
||||||
|
if (data) {
|
||||||
|
const stats = data.slice(1).map(n => parseInt(n, 10));
|
||||||
|
const total = stats.reduce((a, b) => a + b, 0);
|
||||||
|
const idle = stats[3] + (stats[4] ?? 0);
|
||||||
|
|
||||||
|
const totalDiff = total - root.lastCpuTotal;
|
||||||
|
const idleDiff = idle - root.lastCpuIdle;
|
||||||
|
root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
|
||||||
|
|
||||||
|
root.lastCpuTotal = total;
|
||||||
|
root.lastCpuIdle = idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: meminfo
|
||||||
|
|
||||||
|
path: "/proc/meminfo"
|
||||||
|
onLoaded: {
|
||||||
|
const data = text();
|
||||||
|
root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
|
||||||
|
root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: storage
|
||||||
|
|
||||||
|
command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const deviceMap = new Map();
|
||||||
|
|
||||||
|
for (const line of text.trim().split("\n")) {
|
||||||
|
if (line.trim() === "")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const parts = line.trim().split(/\s+/);
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const device = parts[0];
|
||||||
|
const used = parseInt(parts[1], 10) || 0;
|
||||||
|
const avail = parseInt(parts[2], 10) || 0;
|
||||||
|
|
||||||
|
// Only keep the entry with the largest total space for each device
|
||||||
|
if (!deviceMap.has(device) || (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) {
|
||||||
|
deviceMap.set(device, {
|
||||||
|
used: used,
|
||||||
|
avail: avail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalUsed = 0;
|
||||||
|
let totalAvail = 0;
|
||||||
|
|
||||||
|
for (const [device, stats] of deviceMap) {
|
||||||
|
totalUsed += stats.used;
|
||||||
|
totalAvail += stats.avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.storageUsed = totalUsed;
|
||||||
|
root.storageTotal = totalUsed + totalAvail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: gpuTypeCheck
|
||||||
|
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", "if ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; elif command -v nvidia-smi >/dev/null; then echo NVIDIA; else echo NONE; fi"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.gpuType = text.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: gpuUsage
|
||||||
|
|
||||||
|
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits"] : ["echo"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
if (root.gpuType === "GENERIC") {
|
||||||
|
const percs = text.trim().split("\n");
|
||||||
|
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
||||||
|
root.gpuPerc = sum / percs.length / 100;
|
||||||
|
} else if (root.gpuType === "NVIDIA") {
|
||||||
|
const [usage, temp] = text.trim().split(",");
|
||||||
|
root.gpuPerc = parseInt(usage, 10) / 100;
|
||||||
|
root.gpuTemp = parseInt(temp, 10);
|
||||||
|
} else {
|
||||||
|
root.gpuPerc = 0;
|
||||||
|
root.gpuTemp = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: sensors
|
||||||
|
|
||||||
|
command: ["sensors"]
|
||||||
|
environment: ({
|
||||||
|
LANG: "C",
|
||||||
|
LC_ALL: "C"
|
||||||
|
})
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
|
||||||
|
if (!cpuTemp)
|
||||||
|
// If AMD Tdie pattern failed, try fallback on Tctl
|
||||||
|
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
|
||||||
|
|
||||||
|
if (cpuTemp)
|
||||||
|
root.cpuTemp = parseFloat(cpuTemp[1]);
|
||||||
|
|
||||||
|
if (root.gpuType !== "GENERIC")
|
||||||
|
return;
|
||||||
|
|
||||||
|
let eligible = false;
|
||||||
|
let sum = 0;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const line of text.trim().split("\n")) {
|
||||||
|
if (line === "Adapter: PCI adapter")
|
||||||
|
eligible = true;
|
||||||
|
else if (line === "")
|
||||||
|
eligible = false;
|
||||||
|
else if (eligible) {
|
||||||
|
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
||||||
|
if (!match)
|
||||||
|
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
|
||||||
|
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
sum += parseFloat(match[2]);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.gpuTemp = count > 0 ? sum / count : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
utils/Ref.qml
Normal file
9
utils/Ref.qml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
required property Singleton service
|
||||||
|
|
||||||
|
Component.onCompleted: service.refCount++
|
||||||
|
Component.onDestruction: service.refCount--
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue