import QtQuick import QtQuick.Layouts import Quickshell import "../config" import "../services" // CPU usage as a live filled-area graph plus the current percentage. // Clicking the pill toggles a drawer listing the top CPU-consuming processes, // refreshed live (SysStats only scans /proc while the drawer is open). Item { id: root implicitWidth: pill.implicitWidth implicitHeight: pill.implicitHeight Pill { id: pill anchors.fill: parent spacing: Theme.spacing Text { text: Icons.cpu font.family: Theme.monoFont font.pixelSize: Theme.fontSize + 1 color: Theme.loadColor(SysStats.cpu) Layout.alignment: Qt.AlignVCenter } // One vertical bar per core, filled from the bottom by that core's load. Row { id: cores Layout.alignment: Qt.AlignVCenter spacing: 1 readonly property int barH: Theme.barHeight - Theme.gap * 2 - 12 Repeater { model: SysStats.coreCount delegate: Rectangle { readonly property real load: SysStats.coreLoads[index] || 0 width: 3 height: cores.barH radius: 1 color: Qt.rgba(1, 1, 1, 0.08) // unfilled track Rectangle { anchors.bottom: parent.bottom width: parent.width radius: 1 height: Math.max(1, parent.height * (parent.load / 100)) color: Theme.loadColor(parent.load) } } } } Text { text: SysStats.cpu.toFixed(0) + "%" font.family: Theme.font font.pixelSize: Theme.fontSize color: Theme.text Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: 42 // fits "100%" at fontSize 15 horizontalAlignment: Text.AlignRight } // Max current core frequency, shown in GHz. Text { text: (SysStats.cpuFreq / 1000).toFixed(1) + "GHz" font.family: Theme.font font.pixelSize: Theme.fontSize color: Theme.subtext Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: 56 // fits "0.0GHz" at fontSize 15 horizontalAlignment: Text.AlignRight } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: drawer.visible = !drawer.visible } // ---- top-processes drawer --------------------------------------------- PopupWindow { id: drawer visible: false color: "transparent" implicitWidth: panel.implicitWidth implicitHeight: panel.implicitHeight // Hang below the pill, top-right corner aligned to the pill's // bottom-right; Quickshell slides it to stay on-screen if it would // overflow the right edge. Anchoring to the item (not the window) means // the rect is in the pill's own coordinates, so it tracks the pill // wherever the RowLayout places it. anchor { item: root rect.x: 0 rect.y: root.height + Theme.gap rect.width: root.width rect.height: 1 edges: Edges.Bottom | Edges.Right gravity: Edges.Bottom | Edges.Left } // Only pay for the /proc scan while the drawer is actually showing. onVisibleChanged: SysStats.procPollEnabled = visible Rectangle { id: panel readonly property int rowH: 22 implicitWidth: 320 // Fixed height (header + procCount rows): the panel must NOT resize // when rows replace "sampling…", or the popup's layer surface shows a // stale/blank buffer under Sway on first open (same class of bug as // Bar.qml's geomKey remap). A constant size sidesteps it entirely. implicitHeight: col.implicitHeight + Theme.padding * 2 radius: Theme.radius color: Theme.barColor border.color: Theme.surface1 border.width: 1 ColumnLayout { id: col anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.padding spacing: 3 // header: title + overall CPU RowLayout { Layout.fillWidth: true spacing: Theme.spacing Text { text: Icons.cpu font.family: Theme.monoFont font.pixelSize: Theme.fontSize + 1 color: Theme.loadColor(SysStats.cpu) } Text { text: "Top processes" font.family: Theme.font font.pixelSize: Theme.fontSize color: Theme.text Layout.fillWidth: true } Text { text: SysStats.cpu.toFixed(0) + "% total" font.family: Theme.font font.pixelSize: Theme.fontSize - 2 color: Theme.subtext } } Rectangle { // divider Layout.fillWidth: true Layout.topMargin: 2 Layout.bottomMargin: 2 implicitHeight: 1 color: Theme.surface1 } // Fixed-height body so the panel stays one constant size whether // it shows the placeholder or the rows (see panel.implicitHeight). Item { Layout.fillWidth: true Layout.topMargin: 2 implicitHeight: SysStats.procCount * panel.rowH Text { anchors.centerIn: parent visible: SysStats.topProcs.length === 0 text: "sampling…" font.family: Theme.font font.pixelSize: Theme.fontSize - 2 color: Theme.subtext } Column { anchors.fill: parent Repeater { model: SysStats.topProcs delegate: Item { id: rowItem required property var modelData width: parent.width height: panel.rowH // load bar behind the text; full width == one busy core. Rectangle { readonly property color loadColor: Theme.loadColor(rowItem.modelData.cpu) anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left height: parent.height - 4 radius: 3 width: parent.width * Math.min(1, rowItem.modelData.cpu / 100) color: Qt.rgba(loadColor.r, loadColor.g, loadColor.b, 0.18) } RowLayout { anchors.fill: parent anchors.leftMargin: 6 anchors.rightMargin: 6 spacing: Theme.spacing Text { text: rowItem.modelData.name font.family: Theme.font font.pixelSize: Theme.fontSize - 1 color: Theme.text elide: Text.ElideRight Layout.fillWidth: true } Text { text: rowItem.modelData.mem.toFixed(0) + "% mem" font.family: Theme.font font.pixelSize: Theme.fontSize - 3 color: Theme.subtext horizontalAlignment: Text.AlignRight Layout.preferredWidth: 54 } Text { text: rowItem.modelData.cpu.toFixed(0) + "%" font.family: Theme.font font.pixelSize: Theme.fontSize - 1 color: Theme.loadColor(rowItem.modelData.cpu) horizontalAlignment: Text.AlignRight Layout.preferredWidth: 40 } } } } } } } } } }