From 299bf0bae8be6ab505f97d6aba4f82e4e6f96f62 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Sat, 12 Jul 2025 18:15:32 +0800 Subject: lot of minor improvement added utils and working networkusage --- utils/CircularProgress.qml | 91 ++++++++++++++++++++++++++++++++++++++++++++++ utils/Icons.qml | 34 +++++++++++++++++ utils/NetworkUsage.qml | 66 +++++++++++++++++++++++++++++++++ utils/Resource.qml | 59 ++++++++++++++++++++++++++++++ utils/networkUsage.js | 41 +++++++++++++++++++++ 5 files changed, 291 insertions(+) create mode 100644 utils/CircularProgress.qml create mode 100644 utils/Icons.qml create mode 100644 utils/NetworkUsage.qml create mode 100644 utils/Resource.qml create mode 100644 utils/networkUsage.js (limited to 'utils') diff --git a/utils/CircularProgress.qml b/utils/CircularProgress.qml new file mode 100644 index 0000000..240e520 --- /dev/null +++ b/utils/CircularProgress.qml @@ -0,0 +1,91 @@ +// From https://github.com/rafzby/circular-progressbar with modifications +// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo + +import QtQuick + +/** + * Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs + */ +Item { + id: root + + property int size: 30 + property int lineWidth: 2 + property real value: 0 + // property color primaryColor: Appearance.m3colors.m3onSecondaryContainer + // property color secondaryColor: Appearance.colors.colSecondaryContainer + property real gapAngle: Math.PI / 9 + property bool fill: false + property int fillOverflow: 2 + property int animationDuration: 1000 + property var easingType: Easing.OutCubic + + width: size + height: size + + signal animationFinished + + onValueChanged: { + canvas.degree = value * 360; + } + // onPrimaryColorChanged: { + // canvas.requestPaint(); + // } + // onSecondaryColorChanged: { + // canvas.requestPaint(); + // } + + Canvas { + id: canvas + + property real degree: 0 + + anchors.fill: parent + antialiasing: true + + onDegreeChanged: { + requestPaint(); + } + + onPaint: { + var ctx = getContext("2d"); + var x = root.width / 2; + var y = root.height / 2; + var radius = root.size / 2 - root.lineWidth; + var startAngle = (Math.PI / 180) * 270; + var fullAngle = (Math.PI / 180) * (270 + 360); + var progressAngle = (Math.PI / 180) * (270 + degree); + var epsilon = 0.01; // Small angle in radians + + ctx.reset(); + if (root.fill) { + // ctx.fillStyle = root.secondaryColor; + ctx.beginPath(); + ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle); + ctx.fill(); + } + ctx.lineCap = 'round'; + ctx.lineWidth = root.lineWidth; + + // Secondary + ctx.beginPath(); + ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle); + // ctx.strokeStyle = root.secondaryColor; + ctx.stroke(); + + // Primary (value indication) + var endAngle = progressAngle + (value > 0 ? 0 : epsilon); + ctx.beginPath(); + ctx.arc(x, y, radius, startAngle, endAngle); + // ctx.strokeStyle = root.primaryColor; + ctx.stroke(); + } + + Behavior on degree { + NumberAnimation { + duration: root.animationDuration + easing.type: root.easingType + } + } + } +} diff --git a/utils/Icons.qml b/utils/Icons.qml new file mode 100644 index 0000000..4b3915c --- /dev/null +++ b/utils/Icons.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Layouts + +import qs.config + +Text { + id: root + property real iconSize: General.iconSize + property real fill: 0 + property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping + renderType: Text.NativeRendering + font { + hintingPreference: Font.PreferFullHinting + family: Style.font.family.symbols ?? "Symbols Nerd Font" + pixelSize: iconSize + weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill + variableAxes: { + "FILL": truncatedFill, + // "wght": font.weight, + // "GRAD": 0, + "opsz": iconSize + } + } + verticalAlignment: Text.AlignVCenter + // color: Style.m3colors.m3onBackground + + // Behavior on fill { + // NumberAnimation { + // duration: Appearance?.animation.elementMoveFast.duration ?? 200 + // easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline + // easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] + // } + // } +} diff --git a/utils/NetworkUsage.qml b/utils/NetworkUsage.qml new file mode 100644 index 0000000..f71e3b7 --- /dev/null +++ b/utils/NetworkUsage.qml @@ -0,0 +1,66 @@ +pragma Singleton +// pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Io +import QtQuick +import "networkUsage.js" as Network +import qs.config + +Singleton { + property int up: 0 + property string upUnit: "" + property int down: 0 + property string downUnit: "" + + property string __iface: "" + property var __prev: ({ + rx: 0, + tx: 0 + }) + + Timer { + interval: General.resourceUpdateInterval * 1000 + running: true + repeat: true + onTriggered: update() + } + + function update() { + __iface = Network.defaultInterface(getRouteProcess.stdout.text); + const cur = Network.getRxTxBytes(getNetDevProcess.stdout.text, __iface); + const sec = General.resourceUpdateInterval; + const rawUp = (cur.tx - __prev.tx) / sec; + const rawDown = (cur.rx - __prev.rx) / sec; + + up = Network.formatSize(rawUp, false); + upUnit = Network.formatSize(rawUp, true); + down = Network.formatSize(rawDown, false); + downUnit = Network.formatSize(rawDown, true); + + __prev = cur; + getRouteProcess.running = true; + getNetDevProcess.running = true; + } + + Process { + id: getRouteProcess + running: true + command: ["ip", "route"] + stdout: StdioCollector {} + } + + Process { + id: getNetDevProcess + running: true + command: ["cat", "/proc/net/dev"] + stdout: StdioCollector {} + } + + Component.onCompleted: { + getRouteProcess.running = true; + getNetDevProcess.running = true; + __iface = Network.defaultInterface(getRouteProcess.stdout.text); + __prev = Network.getRxTxBytes(getNetDevProcess.stdout.text, __iface); + } +} diff --git a/utils/Resource.qml b/utils/Resource.qml new file mode 100644 index 0000000..2048e98 --- /dev/null +++ b/utils/Resource.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +import qs.config + +Item { + id: root + + required property string iconName + required property double percentage + property bool shown: true + clip: true + visible: width > 0 && height > 0 + implicitWidth: resourceRowLayout.x < 0 ? 0 : childrenRect.width + implicitHeight: childrenRect.height + + RowLayout { + id: resourceRowLayout + spacing: 4 + x: root.shown ? 0 : -resourceRowLayout.width + + CircularProgress { + Layout.alignment: Qt.AlignVCenter + lineWidth: 2 + value: root.percentage + size: 26 + // secondaryColor: Appearance.colors.colSecondaryContainer + // primaryColor: Appearance.m3colors.m3onSecondaryContainer + + MaterialSymbol { + anchors.centerIn: parent + fill: 1 + text: iconName + // iconSize: Appearance.font.pixelSize.normal + // color: Appearance.m3colors.m3onSecondaryContainer + } + } + + Text { + Layout.alignment: Qt.AlignVCenter + // color: Appearance.colors.colOnLayer1 + text: `${Math.round(percentage * 100)}` + } + + Behavior on x { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } +} diff --git a/utils/networkUsage.js b/utils/networkUsage.js new file mode 100644 index 0000000..a7131f2 --- /dev/null +++ b/utils/networkUsage.js @@ -0,0 +1,41 @@ +function defaultInterface(route) { + var lines = route.split(/\r?\n/) + for (var i = 0; i < lines.length; i++) { + var l = lines[i] + if (l.indexOf("default via") === 0) return l.split(/\s+/)[4] + } + throw "No default interface" +} +function getRxTxBytes(dev, iface) { + var lines = dev.split(/\r?\n/) + for (var i = 0; i < lines.length; i++) { + var l = lines[i].trim() + if (!l.startsWith(iface + ":")) continue + var p = l.split(/\s+/) + return { rx: parseInt(p[1]), tx: parseInt(p[9]) } + } + throw "Interface not found" +} +function formatSize(size, unit) { + const labels = ["B", "K", "M", "G", "T"] + let power = 0, kilo = 2 ** 10 + while (size > kilo && power < labels.length - 1) { + size /= kilo; power++ + } + if (unit) { + return labels[power] + } else { + return size.toFixed(0) + } +} + +// const iface = defaultInterface() +// let prev = getRxTxBytes(iface) + +// setInterval(() => { +// const cur = getRxTxBytes(iface) +// const dr = (cur.rx - prev.rx) / (interval / 1000) +// const dt = (cur.tx - prev.tx) / (interval / 1000) +// prev = cur +// console.log(formatSize(dr), formatSize(dt)) +// }, interval) -- cgit v1.2.3