import QtQuick import QtQuick.Layouts import Quickshell import "../config" import "../services" // Down/up throughput on the active interface, with a wifi/ethernet icon. // Clicking the pill toggles a drawer listing the top network-consuming // processes, refreshed live (SysStats only scans sockets while the drawer is // open). CpuGraph.qml is the reference for the drawer pattern. Item { id: root implicitWidth: pill.implicitWidth implicitHeight: pill.implicitHeight Pill { id: pill anchors.fill: parent spacing: Theme.spacing Text { text: SysStats.iface === "" ? Icons.netOff : SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet font.family: Theme.monoFont font.pixelSize: Theme.fontSize + 1 color: SysStats.iface === "" ? Theme.overlay : Theme.sky Layout.alignment: Qt.AlignVCenter } // reserve the width of the widest rate string so values never shift TextMetrics { id: rateMetrics font: rxText.font text: Icons.down + " 99.9M" } Text { id: rxText text: Icons.down + " " + SysStats.fmtRate(SysStats.rxRate) font.family: Theme.monoFont font.pixelSize: Theme.fontSize - 1 color: Theme.green horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: rateMetrics.advanceWidth } Text { text: Icons.up + " " + SysStats.fmtRate(SysStats.txRate) font.family: Theme.monoFont font.pixelSize: Theme.fontSize - 1 color: Theme.peach horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: rateMetrics.advanceWidth } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: drawer.visible = !drawer.visible } // ---- top network-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 (same anchoring rationale as CpuGraph's drawer): anchor // to the item so the rect is in the pill's own coordinates and tracks // it 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 socket scan while the drawer is actually showing. onVisibleChanged: SysStats.netProcPollEnabled = visible Rectangle { id: panel readonly property int rowH: 22 implicitWidth: 340 // Fixed height (header + netProcCount 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. implicitHeight: col.implicitHeight + Theme.padding * 2 radius: Theme.radius color: Theme.barColor border.color: Theme.surface1 border.width: 1 // Largest combined rate in the current frame, used to scale the // per-row background bar (network has no fixed ceiling like CPU%). readonly property real maxRate: { let m = 1; const ps = SysStats.topNetProcs; for (let i = 0; i < ps.length; i++) m = Math.max(m, ps[i].rx + ps[i].tx); return m; } ColumnLayout { id: col anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.padding spacing: 3 // header: icon + title + total throughput RowLayout { Layout.fillWidth: true spacing: Theme.spacing Text { text: SysStats.iface === "" ? Icons.netOff : SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet font.family: Theme.monoFont font.pixelSize: Theme.fontSize + 1 color: SysStats.iface === "" ? Theme.overlay : Theme.sky } Text { text: "Network by process" font.family: Theme.font font.pixelSize: Theme.fontSize color: Theme.text Layout.fillWidth: true } Text { text: Icons.down + SysStats.fmtRate(SysStats.rxRate) + " " + Icons.up + SysStats.fmtRate(SysStats.txRate) font.family: Theme.monoFont font.pixelSize: Theme.fontSize - 3 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.netProcCount * panel.rowH Text { anchors.centerIn: parent visible: SysStats.topNetProcs.length === 0 text: "sampling…" font.family: Theme.font font.pixelSize: Theme.fontSize - 2 color: Theme.subtext } Column { anchors.fill: parent Repeater { model: SysStats.topNetProcs delegate: Item { id: rowItem required property var modelData width: parent.width height: panel.rowH // throughput bar behind the text, scaled to the // busiest process in the frame. Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left height: parent.height - 4 radius: 3 width: parent.width * Math.min(1, (rowItem.modelData.rx + rowItem.modelData.tx) / panel.maxRate) color: Qt.rgba(Theme.sky.r, Theme.sky.g, Theme.sky.b, 0.16) } 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: Icons.down + " " + SysStats.fmtRate(rowItem.modelData.rx) font.family: Theme.monoFont font.pixelSize: Theme.fontSize - 2 color: Theme.green horizontalAlignment: Text.AlignRight Layout.preferredWidth: 62 } Text { text: Icons.up + " " + SysStats.fmtRate(rowItem.modelData.tx) font.family: Theme.monoFont font.pixelSize: Theme.fontSize - 2 color: Theme.peach horizontalAlignment: Text.AlignRight Layout.preferredWidth: 62 } } } } } } } } } }