summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-07-12 18:15:32 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-07-12 18:15:32 +0800
commit299bf0bae8be6ab505f97d6aba4f82e4e6f96f62 (patch)
treee1a72a68b5c2fae7fab097a80462233878a3231c
parentce5f49a9324bceccab821c1d2ebf3f72bdfc09ea (diff)
lot of minor improvement added utils and working networkusage
-rw-r--r--bar/ActiveWindow.qml30
-rw-r--r--bar/Bar.qml24
-rw-r--r--bar/Cpu.qml27
-rw-r--r--bar/Gpu.qml27
-rw-r--r--bar/Memory.qml27
-rw-r--r--bar/Mpris.qml4
-rw-r--r--bar/Network.qml34
-rw-r--r--bar/Temp.qml27
-rw-r--r--config/Bar.qml5
-rw-r--r--config/General.qml6
-rw-r--r--config/Icons.qml34
-rw-r--r--services/Mpris.qml (renamed from services/SMpris.qml)2
-rw-r--r--services/Network.qml86
-rw-r--r--services/Resource.qml105
-rw-r--r--shell.qml5
-rw-r--r--utils/CircularProgress.qml91
-rw-r--r--utils/Icons.qml34
-rw-r--r--utils/NetworkUsage.qml66
-rw-r--r--utils/Resource.qml59
-rw-r--r--utils/networkUsage.js41
20 files changed, 716 insertions, 18 deletions
diff --git a/bar/ActiveWindow.qml b/bar/ActiveWindow.qml
index efdbfd9..4cb1a08 100644
--- a/bar/ActiveWindow.qml
+++ b/bar/ActiveWindow.qml
@@ -1,23 +1,35 @@
import QtQuick
+import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.utils
+import qs.config
Item {
id: root
property var toplevel: ToplevelManager.activeToplevel
property var icon: Quickshell.iconPath(AppSearch.guessIcon(toplevel?.appId), "image-missing")
+ anchors.verticalCenter: parent.verticalCenter
- IconImage {
- source: root.icon
- implicitWidth: 32
- implicitHeight: 32
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.leftMargin: 8
+ Rectangle {
+ anchors.fill: parent
+ color: "blue"
}
- Text {
- text: root.toplevel?.title
+ RowLayout {
+ anchors.fill: parent
+
+ anchors.verticalCenter: parent.verticalCenter
+ IconImage {
+ source: root.icon
+ implicitWidth: Bar.appIconSize
+ implicitHeight: Bar.appIconSize
+ Layout.fillHeight: true
+ Layout.fillWidth: false
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ }
+ Text {
+ text: root.toplevel?.title
+ }
}
}
diff --git a/bar/Bar.qml b/bar/Bar.qml
index f8d3a2d..24fc0ce 100644
--- a/bar/Bar.qml
+++ b/bar/Bar.qml
@@ -65,7 +65,7 @@ Scope {
Layout.preferredWidth: 1
}
}
- RowLayout {
+ Item {
id: centerRow
Layout.fillWidth: true
Layout.fillHeight: true
@@ -76,11 +76,25 @@ Scope {
id: rightRow
Layout.fillWidth: true
Layout.preferredWidth: 1
- Rectangle {
+ Cpu {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ Memory {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ Gpu {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ Temp {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ Network {
+ Layout.fillHeight: true
Layout.fillWidth: true
- Text {
- text: "a"
- }
}
}
}
diff --git a/bar/Cpu.qml b/bar/Cpu.qml
new file mode 100644
index 0000000..5d65415
--- /dev/null
+++ b/bar/Cpu.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+
+import qs.services as Services
+import qs.utils
+import qs.config as Config
+
+Item {
+ id: root
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ anchors.fill: parent
+ spacing: Config.Bar.resourceIconTextSpacing
+
+ Icons {
+ text: Config.Icons.resource.cpu
+ }
+ Text {
+ text: Math.round(Services.Resource.cpuUsage * 100)
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+}
diff --git a/bar/Gpu.qml b/bar/Gpu.qml
new file mode 100644
index 0000000..e47d8f8
--- /dev/null
+++ b/bar/Gpu.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+
+import qs.services as Services
+import qs.utils
+import qs.config as Config
+
+Item {
+ id: root
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ anchors.fill: parent
+ spacing: Config.Bar.resourceIconTextSpacing
+
+ Icons {
+ text: Config.Icons.resource.gpu
+ }
+ Text {
+ text: Services.Resource.gpuUsage
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+}
diff --git a/bar/Memory.qml b/bar/Memory.qml
new file mode 100644
index 0000000..130c9ea
--- /dev/null
+++ b/bar/Memory.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+
+import qs.services as Services
+import qs.utils
+import qs.config as Config
+
+Item {
+ id: root
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ anchors.fill: parent
+ spacing: Config.Bar.resourceIconTextSpacing
+
+ Icons {
+ text: Config.Icons.resource.memory
+ }
+ Text {
+ text: Math.round(Services.Resource.memoryUsedPercentage * 100) + "%"
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+}
diff --git a/bar/Mpris.qml b/bar/Mpris.qml
index 87bef93..8d56335 100644
--- a/bar/Mpris.qml
+++ b/bar/Mpris.qml
@@ -2,7 +2,7 @@ import QtQuick.Layouts
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
-import qs.services
+import qs.services as Services
import qs.config
// import Quickshell.Utils
@@ -10,7 +10,7 @@ import qs.config
Item {
id: root
height: Bar.height
- readonly property MprisPlayer activePlayer: SMpris.activePlayer
+ readonly property MprisPlayer activePlayer: Services.Mpris.activePlayer
readonly property string cleanedTitle: cleanMusicTitle(activePlayer?.trackTitle) || qsTr("No media")
// length broken (https://github.com/quickshell-mirror/quickshell/issues/109)
readonly property string position: friendlyTimeForSeconds(activePlayer?.position) || "0:00"
diff --git a/bar/Network.qml b/bar/Network.qml
new file mode 100644
index 0000000..df70ecd
--- /dev/null
+++ b/bar/Network.qml
@@ -0,0 +1,34 @@
+import QtQuick
+import QtQuick.Layouts
+
+import qs.services as Services
+import qs.utils
+import qs.config as Config
+
+Item {
+ id: root
+
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ anchors.fill: parent
+ spacing: Config.Bar.resourceIconTextSpacing
+
+ Icons {
+ text: Services.Network.state
+ }
+ Text {
+ text: Math.round(Services.Resource.cpuUsage * 100)
+ }
+ Text {
+ text: NetworkUsage.up + " " + NetworkUsage.upUnit
+ }
+ Text {
+ text: NetworkUsage.down + " " + NetworkUsage.downUnit
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+}
diff --git a/bar/Temp.qml b/bar/Temp.qml
new file mode 100644
index 0000000..c2c5f1d
--- /dev/null
+++ b/bar/Temp.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+
+import qs.services as Services
+import qs.utils
+import qs.config as Config
+
+Item {
+ id: root
+ RowLayout {
+ Item {
+ Layout.fillWidth: true
+ }
+ anchors.fill: parent
+ spacing: Config.Bar.resourceIconTextSpacing
+
+ Icons {
+ text: Config.Icons.resource.temp
+ }
+ Text {
+ text: Math.round(Services.Resource.cpuTemp * 100)
+ }
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+}
diff --git a/config/Bar.qml b/config/Bar.qml
index 5a5be3a..8e14851 100644
--- a/config/Bar.qml
+++ b/config/Bar.qml
@@ -11,5 +11,10 @@ Singleton {
readonly property int leftMargin: Style.spacing.larger
readonly property int rightMargin: Style.spacing.larger
+ readonly property int workspaceIconSize: 32
readonly property int workspaces: 10
+
+ readonly property int appIconSize: 32
+
+ readonly property int resourceIconTextSpacing: 8
}
diff --git a/config/General.qml b/config/General.qml
index 80df622..2b71635 100644
--- a/config/General.qml
+++ b/config/General.qml
@@ -6,4 +6,10 @@ Singleton {
id: root
readonly property bool sloopySearch: false
+
+ readonly property int resourceUpdateInterval: 1
+
+ readonly property real iconSize: Style.font.size.small ?? 16
+
+ readonly property string cpuThermalPath: "/sys/class/thermal/thermal_zone0/temp"
}
diff --git a/config/Icons.qml b/config/Icons.qml
index ec52e80..13deee9 100644
--- a/config/Icons.qml
+++ b/config/Icons.qml
@@ -29,4 +29,38 @@ Singleton {
mute: "",
unmute: ""
})
+
+ readonly property var wifi: ({
+ min: "󰤯",
+ low: "󰤟",
+ mid: "󰤢",
+ high: "󰤥",
+ max: "󰤨"
+ })
+
+ readonly property var network: ({
+ disconnected: "󰤭",
+ download: "",
+ upload: "",
+ wifi: wifi
+ })
+
+ readonly property var resource: ({
+ cpu: "",
+ memory: "󰍛",
+ disk: "",
+ gpu: "󱓞",
+ temp: "",
+ network: network,
+ clock: "",
+ calendar: ""
+ })
+
+ readonly property var power: ({
+ shutdown: "",
+ dpms: "󰶐",
+ lock: "󰍁",
+ suspend: "󰤄",
+ restart: ""
+ })
}
diff --git a/services/SMpris.qml b/services/Mpris.qml
index 012a354..9618459 100644
--- a/services/SMpris.qml
+++ b/services/Mpris.qml
@@ -1,5 +1,5 @@
pragma Singleton
-pragma ComponentBehavior: Bound
+// pragma ComponentBehavior: Bound
// From https://git.outfoxxed.me/outfoxxed/nixnew
// It does not have a license, but the author is okay with redistribution.
diff --git a/services/Network.qml b/services/Network.qml
new file mode 100644
index 0000000..52f15fa
--- /dev/null
+++ b/services/Network.qml
@@ -0,0 +1,86 @@
+pragma Singleton
+// pragma ComponentBehavior: Bound
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+import qs.config
+
+Singleton {
+ id: root
+
+ property bool wifi: true
+ property bool ethernet: false
+ property string networkName: ""
+ property int networkStrength
+ // property string materialSymbol: ethernet ? "lan" : (Network.networkName.length > 0 && Network.networkName != "lo") ? (Network.networkStrength > 80 ? "signal_wifi_4_bar" : Network.networkStrength > 60 ? "network_wifi_3_bar" : Network.networkStrength > 40 ? "network_wifi_2_bar" : Network.networkStrength > 20 ? "network_wifi_1_bar" : "signal_wifi_0_bar") : "signal_wifi_off"
+ property string state: ethernet ? Icons.resource.network.wifi : (networkName.length >= 0 && networkName !== "lo") ? (networkStrength >= 90 ? Icons.resource.network.wifi.max : networkStrength >= 80 ? Icons.resource.network.wifi.high : networkStrength >= 60 ? Icons.resource.network.wifi.mid : networkStrength >= 40 ? Icons.resource.network.wifi.low : networkStrength >= 20 ? Icons.resource.network.wifi.min : Icons.resource.network.disconnected) : Icons.resource.network.disconnected
+
+ function update() {
+ updateConnectionType.startCheck();
+ updateNetworkName.running = true;
+ updateNetworkStrength.running = true;
+ }
+
+ Timer {
+ interval: General.resourceUpdateInterval
+ running: true
+ repeat: true
+ onTriggered: {
+ root.update();
+ interval = General.resourceUpdateInterval * 1000;
+ }
+ }
+
+ Process {
+ id: updateConnectionType
+ property string buffer
+ command: ["nmcli", "-t", "-f", "NAME,TYPE,DEVICE", "c", "show", "--active"]
+ running: true
+ function startCheck() {
+ buffer = "";
+ updateConnectionType.running = true;
+ }
+ stdout: SplitParser {
+ onRead: data => {
+ updateConnectionType.buffer += data + "\n";
+ }
+ }
+ onExited: (exitCode, exitStatus) => {
+ const lines = updateConnectionType.buffer.trim().split('\n');
+ let hasEthernet = false;
+ let hasWifi = false;
+ lines.forEach(line => {
+ if (line.includes("ethernet"))
+ hasEthernet = true;
+ else if (line.includes("wireless"))
+ hasWifi = true;
+ });
+ root.ethernet = hasEthernet;
+ root.wifi = hasWifi;
+ }
+ }
+
+ Process {
+ id: updateNetworkName
+ command: ["sh", "-c", "nmcli -t -f NAME c show --active | head -1"]
+ running: true
+ stdout: SplitParser {
+ onRead: data => {
+ root.networkName = data;
+ }
+ }
+ }
+
+ Process {
+ id: updateNetworkStrength
+ running: true
+ command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\*/{if (NR!=1) {print $2}}'"]
+ stdout: SplitParser {
+ onRead: data => {
+ root.networkStrength = parseInt(data);
+ }
+ }
+ }
+}
diff --git a/services/Resource.qml b/services/Resource.qml
new file mode 100644
index 0000000..b45538b
--- /dev/null
+++ b/services/Resource.qml
@@ -0,0 +1,105 @@
+pragma Singleton
+// pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import qs.config
+
+Singleton {
+ id: root
+
+ property double memoryTotal: 1
+ property double memoryFree: 1
+ property double memoryUsed: memoryTotal - memoryFree
+ property double memoryUsedPercentage: memoryUsed / memoryTotal
+ property double swapTotal: 1
+ property double swapFree: 1
+ property double swapUsed: swapTotal - swapFree
+ property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
+
+ property double cpuUsage: 0
+ property double cpuTemp: 0
+ property var previousCpuStats
+
+ property int gpuUsage: 0
+ property int gpuTemp: 0
+
+ Timer {
+ interval: General.resourceUpdateInterval
+ running: true
+ repeat: true
+ onTriggered: {
+ fileMeminfo.reload();
+ fileStat.reload();
+ fileCpuThermal.reload();
+
+ const textMeminfo = fileMeminfo.text();
+ memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)[1] ?? 1);
+ memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)[1] ?? 0);
+ memoryUsed = memoryTotal - memoryFree;
+ memoryUsedPercentage = memoryUsed / memoryTotal;
+ swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)[1] ?? 1);
+ swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)[1] ?? 0);
+
+ const textStat = fileStat.text();
+ const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
+ if (cpuLine) {
+ const stats = cpuLine.slice(1).map(Number);
+ const total = stats.reduce((a, b) => a + b, 0);
+ const idle = stats[3];
+
+ if (root.previousCpuStats) {
+ const totalDiff = total - root.previousCpuStats.total;
+ const idleDiff = idle - root.previousCpuStats.idle;
+ cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
+ }
+
+ root.previousCpuStats = {
+ total,
+ idle
+ };
+ }
+ const cpuTempText = fileCpuThermal.text();
+ cpuTemp = cpuTempText ? parseInt(cpuTempText.trim()) / 100000 : 0;
+
+ gpuUsageProcess.running = true;
+ gpuTempProcess.running = true;
+ interval = General.resourceUpdateInterval * 1000;
+ }
+ }
+
+ FileView {
+ id: fileMeminfo
+ path: "/proc/meminfo"
+ }
+ FileView {
+ id: fileStat
+ path: "/proc/stat"
+ }
+ FileView {
+ id: fileCpuThermal
+ path: General.cpuThermalPath
+ }
+ // nvidia-smi may be dead if firmware update so ABI break etc.
+ Process {
+ id: gpuUsageProcess
+ running: true
+ command: ["nvidia-smi", "--query-gpu=utilization.gpu", "--format=csv,noheader,nounits"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ root.gpuUsage = parseInt(this.text.trim());
+ }
+ }
+ }
+ Process {
+ id: gpuTempProcess
+ running: true
+ command: ["nvidia-smi", "--query-gpu=temperature.gpu", "--format=csv,noheader,nounits"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ root.gpuTemp = parseInt(this.text.trim());
+ }
+ }
+ }
+}
diff --git a/shell.qml b/shell.qml
index a2059bc..0997775 100644
--- a/shell.qml
+++ b/shell.qml
@@ -1,6 +1,9 @@
import Quickshell
import "bar"
+import QtQuick
+import QtQuick.Layouts
+
Scope {
- Bar {}
+ Bar {}
}
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)