diff --git a/Bin/system-stats.sh b/Bin/system-stats.sh index 8d634ac..8cf7133 100755 --- a/Bin/system-stats.sh +++ b/Bin/system-stats.sh @@ -24,6 +24,11 @@ fi TEMP_SENSOR_PATH="" TEMP_SENSOR_TYPE="" +# Network speed monitoring variables +PREV_RX_BYTES=0 +PREV_TX_BYTES=0 +PREV_TIME=0 + # --- Data Collection Functions --- # @@ -194,6 +199,8 @@ get_cpu_temp() { fi } + + # --- Main Loop --- # This loop runs indefinitely, gathering and printing stats. while true; do @@ -205,14 +212,58 @@ while true; do disk_per=$(get_disk_usage) cpu_usage=$(get_cpu_usage) cpu_temp=$(get_cpu_temp) + + # Get network speeds + current_time=$(date +%s.%N) + total_rx=0 + total_tx=0 + + # Read total bytes from /proc/net/dev for all interfaces + while IFS=: read -r interface stats; do + # Skip only loopback interface, allow other interfaces + if [[ "$interface" =~ ^lo[[:space:]]*$ ]]; then + continue + fi + + # Extract rx and tx bytes (fields 1 and 9 in the stats part) + rx_bytes=$(echo "$stats" | awk '{print $1}') + tx_bytes=$(echo "$stats" | awk '{print $9}') + + # Add to totals if they are valid numbers + if [[ "$rx_bytes" =~ ^[0-9]+$ ]] && [[ "$tx_bytes" =~ ^[0-9]+$ ]]; then + total_rx=$((total_rx + rx_bytes)) + total_tx=$((total_tx + tx_bytes)) + fi + done < <(tail -n +3 /proc/net/dev) + + # Calculate speeds if we have previous data + rx_speed=0 + tx_speed=0 + + if [[ "$PREV_TIME" != "0" ]]; then + time_diff=$(awk -v current="$current_time" -v prev="$PREV_TIME" 'BEGIN { printf "%.3f", current - prev }') + rx_diff=$((total_rx - PREV_RX_BYTES)) + tx_diff=$((total_tx - PREV_TX_BYTES)) + + # Calculate speeds in bytes per second using awk + rx_speed=$(awk -v rx="$rx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", rx / time }') + tx_speed=$(awk -v tx="$tx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", tx / time }') + fi + + # Update previous values for next iteration + PREV_RX_BYTES=$total_rx + PREV_TX_BYTES=$total_tx + PREV_TIME=$current_time # Use printf to format the final JSON output string, adding the mem_mb key. - printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s"}\n' \ + printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s", "rx_speed": "%s", "tx_speed": "%s"}\n' \ "$cpu_usage" \ "$cpu_temp" \ "$mem_gb" \ "$mem_per" \ - "$disk_per" + "$disk_per" \ + "$rx_speed" \ + "$tx_speed" # Wait for the specified duration before the next update. sleep "$SLEEP_DURATION" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 4e8ed11..ba34ada 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -114,6 +114,7 @@ Singleton { property string position: "top" // Possible values: "top", "bottom" property bool showActiveWindowIcon: true property bool alwaysShowBatteryPercentage: false + property bool showNetworkStats: true property real backgroundOpacity: 1.0 property string showWorkspaceLabel: "none" property list monitors: [] diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index b8889d8..ef44f3c 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -141,10 +141,12 @@ NPanel { Layout.fillWidth: true color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant - text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..." + text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ').replace(/\s+/g, + ' ').trim() : "..." font.pointSize: Style.fontSizeS * scaling verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap + wrapMode: Text.NoWrap + elide: Text.ElideRight } Image { diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index ae9c9f0..91d3cf0 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -86,18 +86,18 @@ Row { NText { id: titleText - // Fix collapsed width to 120px to avoid layout shifts with neighbors - // Expand up to 400px on hover - width: mouseArea.containsMouse ? Math.min(Math.max(minWidth * scaling, fullTitleMetrics.contentWidth), - 400 * scaling) : minWidth * scaling + // Collapsed width when not hovered, expand on hover + width: mouseArea.containsMouse ? Math.min(fullTitleMetrics.contentWidth + (Style.marginS * scaling), + 400 * scaling) : (minWidth * scaling) horizontalAlignment: Text.AlignLeft text: getTitle() font.pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium - elide: Text.ElideRight + elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter color: Color.mSecondary + clip: true Behavior on width { NumberAnimation { diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 43cddf0..c9514f7 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -97,6 +97,50 @@ Row { color: Color.mPrimary } } + + // Network Download Speed Component + Row { + id: networkDownloadLayout + spacing: Style.marginXS * scaling + visible: Settings.data.bar.showNetworkStats + + NIcon { + text: "download" + anchors.verticalCenter: parent.verticalCenter + } + + NText { + text: SystemStatService.formatSpeed(SystemStatService.rxSpeed) + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + color: Color.mPrimary + } + } + + // Network Upload Speed Component + Row { + id: networkUploadLayout + spacing: Style.marginXS * scaling + visible: Settings.data.bar.showNetworkStats + + NIcon { + text: "upload" + anchors.verticalCenter: parent.verticalCenter + } + + NText { + text: SystemStatService.formatSpeed(SystemStatService.txSpeed) + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + color: Color.mPrimary + } + } } } } diff --git a/Modules/PowerPanel/PowerPanel.qml b/Modules/PowerPanel/PowerPanel.qml index b13a549..45de8fe 100644 --- a/Modules/PowerPanel/PowerPanel.qml +++ b/Modules/PowerPanel/PowerPanel.qml @@ -309,7 +309,8 @@ NPanel { return Color.mOnSurfaceVariant } opacity: Style.opacityHeavy - wrapMode: Text.WordWrap + wrapMode: Text.NoWrap + elide: Text.ElideRight } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 412b560..f000705 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -88,6 +88,15 @@ ColumnLayout { } } + NToggle { + label: "Show Network Statistics" + description: "Display network upload and download speeds in the system monitor." + checked: Settings.data.bar.showNetworkStats + onToggled: checked => { + Settings.data.bar.showNetworkStats = checked + } + } + NComboBox { label: "Show Workspaces Labels" description: "Display the workspace name or index in the workspace indicator" diff --git a/Services/SystemStatService.qml b/Services/SystemStatService.qml index 4c9d3b0..67f5c68 100644 --- a/Services/SystemStatService.qml +++ b/Services/SystemStatService.qml @@ -14,6 +14,21 @@ Singleton { property real memoryUsageGb: 0 property real memoryUsagePer: 0 property real diskUsage: 0 + property real rxSpeed: 0 + property real txSpeed: 0 + + // Helper function to format network speeds + function formatSpeed(bytesPerSecond) { + if (bytesPerSecond < 1024) { + return bytesPerSecond.toFixed(0) + " B/s" + } else if (bytesPerSecond < 1024 * 1024) { + return (bytesPerSecond / 1024).toFixed(1) + " KB/s" + } else if (bytesPerSecond < 1024 * 1024 * 1024) { + return (bytesPerSecond / (1024 * 1024)).toFixed(1) + " MB/s" + } else { + return (bytesPerSecond / (1024 * 1024 * 1024)).toFixed(1) + " GB/s" + } + } // Background process emitting one JSON line per sample Process { @@ -29,6 +44,8 @@ Singleton { root.memoryUsageGb = data.memgb root.memoryUsagePer = data.memper root.diskUsage = data.diskper + root.rxSpeed = parseFloat(data.rx_speed) || 0 + root.txSpeed = parseFloat(data.tx_speed) || 0 } catch (e) { // ignore malformed lines