import QtQuick import Quickshell.Io import "." as M M.BarSection { id: root spacing: Math.max(1, M.Theme.moduleSpacing - 2) tooltip: "" property var _mounts: [] property int _rootPct: 0 Process { id: proc running: true command: ["sh", "-c", "df -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay -B1 --output=target,size,used 2>/dev/null | awk 'NR>1 && $2+0>0 {print $1\"|\"$2\"|\"$3}'"] stdout: StdioCollector { onStreamFinished: { const lines = text.trim().split("\n").filter(l => l); const mounts = []; for (const line of lines) { const parts = line.split("|"); if (parts.length < 3) continue; const total = parseInt(parts[1]); const used = parseInt(parts[2]); if (total <= 0) continue; mounts.push({ "target": parts[0], "pct": Math.round(used / total * 100), "usedBytes": used, "totalBytes": total }); } root._mounts = mounts; const rm = mounts.find(m => m.target === "/"); if (rm) root._rootPct = rm.pct; } } } Timer { interval: M.Modules.disk.interval || 30000 running: true repeat: true onTriggered: proc.running = true } function _fmt(bytes) { if (bytes >= 1e12) return (bytes / 1e12).toFixed(1) + "T"; if (bytes >= 1e9) return Math.round(bytes / 1e9) + "G"; if (bytes >= 1e6) return Math.round(bytes / 1e6) + "M"; return bytes + "B"; } function _barColor(pct) { const t = Math.max(0, Math.min(100, pct)) / 100; const a = t < 0.5 ? M.Theme.base0B : M.Theme.base0A; const b = t < 0.5 ? M.Theme.base0A : M.Theme.base08; const u = t < 0.5 ? t * 2 : (t - 0.5) * 2; return Qt.rgba(a.r + (b.r - a.r) * u, a.g + (b.g - a.g) * u, a.b + (b.b - a.b) * u, 1); } property bool _pinned: false readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered readonly property bool _showPanel: _anyHover || _pinned on_AnyHoverChanged: { if (_anyHover) _unpinTimer.stop(); else if (_pinned) _unpinTimer.start(); } Timer { id: _unpinTimer interval: 500 onTriggered: root._pinned = false } M.BarIcon { icon: "\uF0C9" anchors.verticalCenter: parent.verticalCenter TapHandler { cursorShape: Qt.PointingHandCursor onTapped: root._pinned = !root._pinned } } M.BarLabel { label: root._rootPct + "%" minText: "100%" anchors.verticalCenter: parent.verticalCenter TapHandler { cursorShape: Qt.PointingHandCursor onTapped: root._pinned = !root._pinned } } M.HoverPanel { id: hoverPanel showPanel: root._showPanel screen: QsWindow.window?.screen ?? null anchorItem: root accentColor: root.accentColor panelNamespace: "nova-disk" contentWidth: 260 Item { width: parent.width height: 28 Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "Disk" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 1 font.family: M.Theme.fontFamily } } Repeater { model: root._mounts delegate: Item { required property var modelData width: hoverPanel.contentWidth height: 22 Text { id: mountLabel anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: modelData.target color: M.Theme.base05 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily elide: Text.ElideRight width: 72 } Item { id: mountBar anchors.left: mountLabel.right anchors.leftMargin: 6 anchors.right: sizeLabel.left anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter height: 4 Rectangle { anchors.fill: parent color: M.Theme.base02 radius: 2 } Rectangle { width: parent.width * (modelData.pct / 100) height: parent.height color: root._barColor(modelData.pct) radius: 2 Behavior on width { NumberAnimation { duration: 200 } } } } Text { id: sizeLabel anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: root._fmt(modelData.usedBytes) + "/" + root._fmt(modelData.totalBytes) color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily width: 72 horizontalAlignment: Text.AlignRight } } } Item { width: 1 height: 4 } } }