From 67ac3d1c9d3b1a5234013127919acf65f9745bc0 Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 13 Aug 2025 09:53:46 -0400 Subject: [PATCH] re-work nightmode setting to use gammastep --- Common/CacheUtils.qml | 1 - Common/Colors.qml | 1 - Common/SessionData.qml | 16 ++ Modules/ControlCenter/DisplayTab.qml | 42 +--- Modules/ProcessList/ProcessListView.qml | 68 ++++--- Modules/Settings/AppearanceTab.qml | 62 +++--- README.md | 5 +- Services/BrightnessService.qml | 129 ++++++++++++ Services/DgopService.qml | 48 ++++- sysmon_dynamic.sh | 254 ------------------------ sysmon_dynamic_lite.sh | 223 --------------------- sysmon_static.sh | 81 -------- 12 files changed, 275 insertions(+), 655 deletions(-) delete mode 100755 sysmon_dynamic.sh delete mode 100755 sysmon_dynamic_lite.sh delete mode 100755 sysmon_static.sh diff --git a/Common/CacheUtils.qml b/Common/CacheUtils.qml index ecaefccb..8f853851 100644 --- a/Common/CacheUtils.qml +++ b/Common/CacheUtils.qml @@ -1,5 +1,4 @@ import Quickshell -import qs.Common pragma Singleton Singleton { diff --git a/Common/Colors.qml b/Common/Colors.qml index a303fed8..f96f5dc0 100644 --- a/Common/Colors.qml +++ b/Common/Colors.qml @@ -7,7 +7,6 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Services -import qs.Common Singleton { id: root diff --git a/Common/SessionData.qml b/Common/SessionData.qml index 9d6db7c0..2d93e8c8 100644 --- a/Common/SessionData.qml +++ b/Common/SessionData.qml @@ -17,6 +17,8 @@ Singleton { property string wallpaperLastPath: "" property string profileLastPath: "" property bool doNotDisturb: false + property bool nightModeEnabled: false + property int nightModeTemperature: 4500 property var pinnedApps: [] property int selectedGpuIndex: 0 property bool nvidiaGpuTempEnabled: false @@ -45,6 +47,8 @@ Singleton { !== undefined ? settings.wallpaperLastPath : "" profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "" doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false + nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false + nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500 pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0 nvidiaGpuTempEnabled = settings.nvidiaGpuTempEnabled !== undefined ? settings.nvidiaGpuTempEnabled : false @@ -67,6 +71,8 @@ Singleton { "wallpaperLastPath": wallpaperLastPath, "profileLastPath": profileLastPath, "doNotDisturb": doNotDisturb, + "nightModeEnabled": nightModeEnabled, + "nightModeTemperature": nightModeTemperature, "pinnedApps": pinnedApps, "selectedGpuIndex": selectedGpuIndex, "nvidiaGpuTempEnabled": nvidiaGpuTempEnabled, @@ -89,6 +95,16 @@ Singleton { saveSettings() } + function setNightModeEnabled(enabled) { + nightModeEnabled = enabled + saveSettings() + } + + function setNightModeTemperature(temperature) { + nightModeTemperature = temperature + saveSettings() + } + function setWallpaperPath(path) { wallpaperPath = path saveSettings() diff --git a/Modules/ControlCenter/DisplayTab.qml b/Modules/ControlCenter/DisplayTab.qml index c9db5b71..68d391fb 100644 --- a/Modules/ControlCenter/DisplayTab.qml +++ b/Modules/ControlCenter/DisplayTab.qml @@ -39,28 +39,6 @@ Item { } - Process { - id: nightModeEnableProcess - - command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"] - running: false - onExited: (exitCode) => { - if (exitCode !== 0) - SettingsData.setNightModeEnabled(false); - - } - } - - Process { - id: nightModeDisableProcess - - command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"] - running: false - onExited: (exitCode) => { - if (exitCode !== 0) { - } - } - } Component { id: brightnessComponent @@ -155,25 +133,25 @@ Item { width: (parent.width - Theme.spacingM) / 2 height: 80 radius: Theme.cornerRadius - color: SettingsData.nightModeEnabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) - border.color: SettingsData.nightModeEnabled ? Theme.primary : "transparent" - border.width: SettingsData.nightModeEnabled ? 1 : 0 + color: BrightnessService.nightModeActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (nightModeToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) + border.color: BrightnessService.nightModeActive ? Theme.primary : "transparent" + border.width: BrightnessService.nightModeActive ? 1 : 0 Column { anchors.centerIn: parent spacing: Theme.spacingS DankIcon { - name: SettingsData.nightModeEnabled ? "nightlight" : "dark_mode" + name: BrightnessService.nightModeActive ? "nightlight" : "dark_mode" size: Theme.iconSizeLarge - color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText + color: BrightnessService.nightModeActive ? Theme.primary : Theme.surfaceText anchors.horizontalCenter: parent.horizontalCenter } StyledText { text: "Night Mode" font.pixelSize: Theme.fontSizeMedium - color: SettingsData.nightModeEnabled ? Theme.primary : Theme.surfaceText + color: BrightnessService.nightModeActive ? Theme.primary : Theme.surfaceText font.weight: Font.Medium anchors.horizontalCenter: parent.horizontalCenter } @@ -187,13 +165,7 @@ Item { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (SettingsData.nightModeEnabled) { - nightModeDisableProcess.running = true; - SettingsData.setNightModeEnabled(false); - } else { - nightModeEnableProcess.running = true; - SettingsData.setNightModeEnabled(true); - } + BrightnessService.toggleNightMode(); } } diff --git a/Modules/ProcessList/ProcessListView.qml b/Modules/ProcessList/ProcessListView.qml index de65279e..0e318ba7 100644 --- a/Modules/ProcessList/ProcessListView.qml +++ b/Modules/ProcessList/ProcessListView.qml @@ -26,10 +26,15 @@ Column { Rectangle { width: 60 height: 20 - color: processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, - Theme.surfaceText.g, - Theme.surfaceText.b, - 0.08) : "transparent" + color: { + if (DgopService.currentSort === "name") { + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + } + return processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, + Theme.surfaceText.g, + Theme.surfaceText.b, + 0.08) : "transparent" + } radius: Theme.cornerRadius anchors.left: parent.left anchors.leftMargin: 0 @@ -39,9 +44,9 @@ Column { text: "Process" font.pixelSize: Theme.fontSizeSmall font.family: SettingsData.monoFontFamily - font.weight: DgopService.sortBy === "name" ? Font.Bold : Font.Medium + font.weight: DgopService.currentSort === "name" ? Font.Bold : Font.Medium color: Theme.surfaceText - opacity: DgopService.sortBy === "name" ? 1 : 0.7 + opacity: DgopService.currentSort === "name" ? 1 : 0.7 anchors.centerIn: parent } @@ -66,10 +71,15 @@ Column { Rectangle { width: 80 height: 20 - color: cpuHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, - Theme.surfaceText.g, - Theme.surfaceText.b, - 0.08) : "transparent" + color: { + if (DgopService.currentSort === "cpu") { + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + } + return cpuHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, + Theme.surfaceText.g, + Theme.surfaceText.b, + 0.08) : "transparent" + } radius: Theme.cornerRadius anchors.right: parent.right anchors.rightMargin: 200 @@ -79,9 +89,9 @@ Column { text: "CPU" font.pixelSize: Theme.fontSizeSmall font.family: SettingsData.monoFontFamily - font.weight: DgopService.sortBy === "cpu" ? Font.Bold : Font.Medium + font.weight: DgopService.currentSort === "cpu" ? Font.Bold : Font.Medium color: Theme.surfaceText - opacity: DgopService.sortBy === "cpu" ? 1 : 0.7 + opacity: DgopService.currentSort === "cpu" ? 1 : 0.7 anchors.centerIn: parent } @@ -106,10 +116,15 @@ Column { Rectangle { width: 80 height: 20 - color: memoryHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, - Theme.surfaceText.g, - Theme.surfaceText.b, - 0.08) : "transparent" + color: { + if (DgopService.currentSort === "memory") { + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + } + return memoryHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, + Theme.surfaceText.g, + Theme.surfaceText.b, + 0.08) : "transparent" + } radius: Theme.cornerRadius anchors.right: parent.right anchors.rightMargin: 112 @@ -119,9 +134,9 @@ Column { text: "RAM" font.pixelSize: Theme.fontSizeSmall font.family: SettingsData.monoFontFamily - font.weight: DgopService.sortBy === "memory" ? Font.Bold : Font.Medium + font.weight: DgopService.currentSort === "memory" ? Font.Bold : Font.Medium color: Theme.surfaceText - opacity: DgopService.sortBy === "memory" ? 1 : 0.7 + opacity: DgopService.currentSort === "memory" ? 1 : 0.7 anchors.centerIn: parent } @@ -146,10 +161,15 @@ Column { Rectangle { width: 50 height: 20 - color: pidHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, - Theme.surfaceText.g, - Theme.surfaceText.b, - 0.08) : "transparent" + color: { + if (DgopService.currentSort === "pid") { + return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + } + return pidHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, + Theme.surfaceText.g, + Theme.surfaceText.b, + 0.08) : "transparent" + } radius: Theme.cornerRadius anchors.right: parent.right anchors.rightMargin: 53 @@ -159,9 +179,9 @@ Column { text: "PID" font.pixelSize: Theme.fontSizeSmall font.family: SettingsData.monoFontFamily - font.weight: DgopService.sortBy === "pid" ? Font.Bold : Font.Medium + font.weight: DgopService.currentSort === "pid" ? Font.Bold : Font.Medium color: Theme.surfaceText - opacity: DgopService.sortBy === "pid" ? 1 : 0.7 + opacity: DgopService.currentSort === "pid" ? 1 : 0.7 horizontalAlignment: Text.AlignHCenter anchors.centerIn: parent } diff --git a/Modules/Settings/AppearanceTab.qml b/Modules/Settings/AppearanceTab.qml index 1c6859d9..ead4e5bb 100644 --- a/Modules/Settings/AppearanceTab.qml +++ b/Modules/Settings/AppearanceTab.qml @@ -60,17 +60,46 @@ Item { } DankToggle { + id: nightModeToggle width: parent.width text: "Night Mode" description: "Apply warm color temperature to reduce eye strain" - checked: SettingsData.nightModeEnabled + checked: BrightnessService.nightModeActive onToggled: checked => { - SettingsData.setNightModeEnabled(checked) - if (checked) - nightModeEnableProcess.running = true - else - nightModeDisableProcess.running = true + if (checked !== BrightnessService.nightModeActive) { + if (checked) + BrightnessService.enableNightMode() + else + BrightnessService.disableNightMode() + } } + + Connections { + target: BrightnessService + function onNightModeActiveChanged() { + nightModeToggle.checked = BrightnessService.nightModeActive + } + } + } + + DankDropdown { + width: parent.width + text: "Night Mode Temperature" + description: BrightnessService.nightModeActive ? "Disable night mode to adjust" : "Set temperature for night mode" + enabled: !BrightnessService.nightModeActive + opacity: !BrightnessService.nightModeActive ? 1.0 : 0.6 + currentValue: SessionData.nightModeTemperature + "K" + options: { + var temps = []; + for (var i = 2500; i <= 6000; i += 500) { + temps.push(i + "K"); + } + return temps; + } + onValueChanged: value => { + var temp = parseInt(value.replace("K", "")); + SessionData.setNightModeTemperature(temp); + } } DankToggle { @@ -904,25 +933,4 @@ Item { } - Process { - id: nightModeEnableProcess - - command: ["bash", "-c", "if command -v wlsunset > /dev/null; then pkill wlsunset; wlsunset -t 3000 & elif command -v redshift > /dev/null; then pkill redshift; redshift -P -O 3000 & else echo 'No night mode tool available'; fi"] - running: false - onExited: exitCode => { - if (exitCode !== 0) - SettingsData.setNightModeEnabled(true) - } - } - - Process { - id: nightModeDisableProcess - - command: ["bash", "-c", "pkill wlsunset; pkill redshift; if command -v wlsunset > /dev/null; then wlsunset -t 6500 -T 6500 & sleep 1; pkill wlsunset; elif command -v redshift > /dev/null; then redshift -P -O 6500; redshift -x; fi"] - running: false - onExited: exitCode => { - if (exitCode !== 0) - SettingsData.setNightModeEnabled(false) - } - } } diff --git a/README.md b/README.md index 8ff72225..2e932323 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ make && sudo make install ```bash # Arch Linux pacman -S cava wl-clipboard cliphist brightnessctl -paru -S matugen dgop-git +paru -S matugen dgop # Fedora sudo dnf install cava wl-clipboard brightnessctl @@ -175,12 +175,13 @@ sudo dnf copr enable heus-sueh/packages && sudo dnf install matugen **What you get:** -- `dgop-git`: Ability to have system resource widgets, process list modal, and temperature monitoring. +- `dgop`: Ability to have system resource widgets, process list modal, and temperature monitoring. - `matugen`: Wallpaper-based dynamic theming - `brightnessctl`: Backlight and LED brightness control - `wl-clipboard`: Required for copying various elements to clipboard. - `cava`: Audio visualizer - `cliphist`: Clipboard history +- `gammastep`: Night mode support diff --git a/Services/BrightnessService.qml b/Services/BrightnessService.qml index eb4b0de5..6dcd4142 100644 --- a/Services/BrightnessService.qml +++ b/Services/BrightnessService.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import Quickshell.Io +import qs.Common pragma Singleton pragma ComponentBehavior @@ -21,6 +22,12 @@ Singleton { signal brightnessChanged() signal deviceSwitched() + signal nightModeActiveChanged() + + property bool nightModeActive: false + onNightModeActiveChanged: { + // Emit signal when property changes for UI reactivity + } function setBrightnessInternal(percentage, device) { const clampedValue = Math.max(1, Math.min(100, percentage)); @@ -86,9 +93,52 @@ Singleton { } return null; } + + function enableNightMode() { + if (nightModeActive) return; + + nightModeActive = true; + SessionData.setNightModeEnabled(true); + } + + function updateNightModeTemperature(temperature) { + SessionData.setNightModeTemperature(temperature); + + // If night mode is active, restart it with new temperature + if (nightModeActive) { + // Temporarily disable and re-enable to restart with new temp + nightModeActive = false; + Qt.callLater(() => { + if (SessionData.nightModeEnabled) { + nightModeActive = true; + } + }); + } + } + + function disableNightMode() { + nightModeActive = false; + SessionData.setNightModeEnabled(false); + + // Also kill any stray gammastep processes + Quickshell.execDetached(["pkill", "gammastep"]); + } + + function toggleNightMode() { + if (nightModeActive) { + disableNightMode(); + } else { + enableNightMode(); + } + } Component.onCompleted: { refreshDevices(); + + // Check if night mode was enabled on startup + if (SessionData.nightModeEnabled) { + enableNightMode(); + } } Process { @@ -189,6 +239,26 @@ Singleton { } + Process { + id: gammaStepProcess + + command: { + const temperature = SessionData.nightModeTemperature || 4500; + return ["gammastep", "-m", "wayland", "-O", String(temperature)]; + } + running: nightModeActive + + onExited: function(exitCode) { + // Only show error if we're still supposed to be active (not manually disabled) + if (exitCode !== 0 && nightModeActive) { + console.warn("BrightnessService: Failed to enable night mode (gammastep not found or error)"); + nightModeActive = false; + SessionData.setNightModeEnabled(false); + ToastService.showWarning("Night mode failed: gammastep not found or error occurred"); + } + } + } + // IPC Handler for external control IpcHandler { function set(percentage: string, device: string) : string { @@ -269,5 +339,64 @@ Singleton { target: "brightness" } + + // IPC Handler for night mode control + IpcHandler { + function toggle() : string { + root.toggleNightMode(); + return root.nightModeActive ? "Night mode enabled" : "Night mode disabled"; + } + + function enable() : string { + root.enableNightMode(); + return "Night mode enabled"; + } + + function disable() : string { + root.disableNightMode(); + return "Night mode disabled"; + } + + function status() : string { + return root.nightModeActive ? "Night mode is enabled" : "Night mode is disabled"; + } + + function temperature(value: string) : string { + if (!value) { + return "Current temperature: " + SessionData.nightModeTemperature + "K"; + } + + const temp = parseInt(value); + if (isNaN(temp)) { + return "Invalid temperature. Use a value between 2500 and 6000 (in steps of 500)"; + } + + // Validate temperature is in valid range and steps + if (temp < 2500 || temp > 6000) { + return "Temperature must be between 2500K and 6000K"; + } + + // Round to nearest 500 + const rounded = Math.round(temp / 500) * 500; + + SessionData.setNightModeTemperature(rounded); + + // If night mode is active, restart it with new temperature + if (root.nightModeActive) { + root.nightModeActive = false; + Qt.callLater(() => { + root.nightModeActive = true; + }); + } + + if (rounded !== temp) { + return "Night mode temperature set to " + rounded + "K (rounded from " + temp + "K)"; + } else { + return "Night mode temperature set to " + rounded + "K"; + } + } + + target: "night" + } } diff --git a/Services/DgopService.qml b/Services/DgopService.qml index 36c0db5f..bb4f6fb3 100644 --- a/Services/DgopService.qml +++ b/Services/DgopService.qml @@ -56,6 +56,8 @@ Singleton { property var diskDevices: [] property var processes: [] + property var allProcesses: [] + property string currentSort: "cpu" property var availableGpus: [] property string kernelVersion: "" @@ -264,10 +266,8 @@ Singleton { } if (enabledModules.indexOf("processes") !== -1 || enabledModules.indexOf("all") !== -1) { - if (processLimit > 0) { - cmd.push("--limit", processLimit.toString()) - } - cmd.push("--sort", processSort) + cmd.push("--limit", "100") // Get more data for client sorting + cmd.push("--sort", "cpu") // Always get CPU sorted data if (noCpu) { cmd.push("--no-cpu") } @@ -388,7 +388,8 @@ Singleton { proc.command.substring(0, 15) + "..." : (proc.command || "") }) } - processes = newProcesses + allProcesses = newProcesses + applySorting() // Store the single opaque cursor string for the entire process list if (data.cursor) { @@ -508,10 +509,43 @@ Singleton { } function setSortBy(newSortBy) { - if (newSortBy !== processSort) { - processSort = newSortBy + if (newSortBy !== currentSort) { + currentSort = newSortBy + applySorting() } } + + function applySorting() { + if (!allProcesses || allProcesses.length === 0) return + + const sorted = allProcesses.slice() + sorted.sort((a, b) => { + let valueA, valueB + + switch (currentSort) { + case "cpu": + valueA = a.cpu || 0 + valueB = b.cpu || 0 + return valueB - valueA + case "memory": + valueA = a.memoryKB || 0 + valueB = b.memoryKB || 0 + return valueB - valueA + case "name": + valueA = (a.command || "").toLowerCase() + valueB = (b.command || "").toLowerCase() + return valueA.localeCompare(valueB) + case "pid": + valueA = a.pid || 0 + valueB = b.pid || 0 + return valueA - valueB + default: + return 0 + } + }) + + processes = sorted.slice(0, processLimit) + } Timer { id: updateTimer diff --git a/sysmon_dynamic.sh b/sysmon_dynamic.sh deleted file mode 100755 index f9bb2019..00000000 --- a/sysmon_dynamic.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env bash -# Outputs dynamic system stats as JSON. -# Args: -# $1 sort_key (cpu|memory|name|pid) -# $2 max_procs -# $3 collect_gpu_temps (0/1) -# $4 collect_nvidia_only (0/1) -# $5 collect_non_nvidia (0/1) - -set -o pipefail - -sort_key=${1:-cpu} -max_procs=${2:-20} -collect_gpu_temps=${3:-0} -collect_nvidia_only=${4:-0} -collect_non_nvidia=${5:-1} - -json_escape() { sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'; } - -printf "{" - -# ---- Memory block (exact fields/keys) ---- -mem_line="$(awk ' - /^MemTotal:/ {t=$2} - /^MemFree:/ {f=$2} - /^MemAvailable:/ {a=$2} - /^Buffers:/ {b=$2} - /^Cached:/ {c=$2} - /^Shmem:/ {s=$2} - /^SwapTotal:/ {st=$2} - /^SwapFree:/ {sf=$2} - END{printf "%d %d %d %d %d %d %d %d", t,f,a,b,c,s,st,sf} -' /proc/meminfo)" -read -r MT MF MA BU CA SH ST SF <<< "$mem_line" - -printf '"memory":{"total":%d,"free":%d,"available":%d,"buffers":%d,"cached":%d,"shared":%d,"swaptotal":%d,"swapfree":%d},' \ - "$MT" "$MF" "$MA" "$BU" "$CA" "$SH" "$ST" "$SF" - -# ---- Helper: PSS in KiB ---- -get_pss_kb() { - local pid="$1" k v rest total=0 f - f="/proc/$pid/smaps_rollup" - if [ -r "$f" ]; then - while read -r k v rest; do - if [ "$k" = "Pss:" ]; then - printf '%s\n' "${v:-0}" - return - fi - done < "$f" - printf '0\n'; return - fi - f="/proc/$pid/smaps" - if [ -r "$f" ]; then - while read -r k v rest; do - if [ "$k" = "Pss:" ]; then - : "${v:=0}" - total=$(( total + v )) - fi - done < "$f" - printf '%s\n' "$total"; return - fi - printf '0\n' -} - -# ---- CPU (meta + temp) ---- -cpu_count=$(nproc) -cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') -cpu_freq=$(awk -F: '/cpu MHz/{gsub(/ /,"",$2);print $2;exit}' /proc/cpuinfo || echo 0) - -cpu_temp=0 -for hwmon_dir in /sys/class/hwmon/hwmon*/; do - [ -d "$hwmon_dir" ] || continue - name_file="${hwmon_dir}name" - [ -r "$name_file" ] || continue - if grep -qE 'coretemp|k10temp|k8temp|cpu_thermal|soc_thermal' "$name_file" 2>/dev/null; then - for temp_file in "${hwmon_dir}"temp*_input; do - [ -r "$temp_file" ] || continue - cpu_temp=$(awk '{printf "%.1f", $1/1000}' "$temp_file" 2>/dev/null || echo 0) - break 2 - done - fi -done - -printf '"cpu":{"count":%d,"model":"%s","frequency":%s,"temperature":%s,' \ - "$cpu_count" "$cpu_model" "$cpu_freq" "$cpu_temp" - -# cpu.total (first line of /proc/stat, fields 2..) -printf '"total":' -awk 'NR==1 { - printf "["; - for(i=2; i<=NF; i++) { if(i>2) printf ","; printf "%d", $i; } - printf "]"; - exit -}' /proc/stat - -# cpu.cores (each cpuN line, 8 columns) -printf ',"cores":[' -cpu_cores=$(nproc) -awk -v n="$cpu_cores" 'BEGIN{c=0} - /^cpu[0-9]+/ { - if(c>0) printf ","; - printf "[%d,%d,%d,%d,%d,%d,%d,%d]", $2,$3,$4,$5,$6,$7,$8,$9; - c++; - if(c==n) exit - }' /proc/stat -printf ']},' - -# ---- Network (rx/tx bytes per iface) ---- -printf '"network":[' -tmp_net=$(mktemp) -grep -E '(wlan|eth|enp|wlp|ens|eno)' /proc/net/dev > "$tmp_net" || true -nfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - iface=$(echo "$line" | awk '{print $1}' | sed 's/://') - rx_bytes=$(echo "$line" | awk '{print $2}') - tx_bytes=$(echo "$line" | awk '{print $10}') - [ $nfirst -eq 1 ] || printf "," - printf '{"name":"%s","rx":%d,"tx":%d}' "$iface" "$rx_bytes" "$tx_bytes" - nfirst=0 -done < "$tmp_net" -rm -f "$tmp_net" -printf '],' - -# ---- Disk (/proc/diskstats sectors; read/write) ---- -printf '"disk":[' -tmp_disk=$(mktemp) -grep -E ' (sd[a-z]+|nvme[0-9]+n[0-9]+|vd[a-z]+|dm-[0-9]+|mmcblk[0-9]+) ' /proc/diskstats > "$tmp_disk" || true -dfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - name=$(echo "$line" | awk '{print $3}') - read_sectors=$(echo "$line" | awk '{print $6}') - write_sectors=$(echo "$line" | awk '{print $10}') - [ $dfirst -eq 1 ] || printf "," - printf '{"name":"%s","read":%d,"write":%d}' "$name" "$read_sectors" "$write_sectors" - dfirst=0 -done < "$tmp_disk" -rm -f "$tmp_disk" -printf '],' - -# ---- Processes (shape & fields match your QML) ---- -case "$sort_key" in - cpu) SORT_OPT="--sort=-pcpu" ;; - memory) SORT_OPT="--sort=-pmem" ;; - name) SORT_OPT="--sort=+comm" ;; - pid) SORT_OPT="--sort=+pid" ;; - *) SORT_OPT="--sort=-pcpu" ;; -esac - -printf '"processes":[' -tmp_ps=$(mktemp) -ps -eo pid,ppid,pcpu,pmem,rss,comm,cmd --no-headers $SORT_OPT | head -n "$max_procs" > "$tmp_ps" || true -pfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - # split the first 6 columns, rest is full command tail - pid=$( awk '{print $1}' <<<"$line") - ppid=$( awk '{print $2}' <<<"$line") - cpu=$( awk '{print $3}' <<<"$line") - pmem=$( awk '{print $4}' <<<"$line") - rss_kib=$(awk '{print $5}' <<<"$line") - comm=$( awk '{print $6}' <<<"$line") - rest=$( printf '%s\n' "$line" | cut -d' ' -f7- ) - - # CPU ticks (utime+stime) - pticks=$(awk '{print $14+$15}' "/proc/$pid/stat" 2>/dev/null || echo 0) - - # PSS in KiB and % of MemTotal - if [ "${rss_kib:-0}" -eq 0 ]; then pss_kib=0; else pss_kib=$(get_pss_kb "$pid"); fi - case "$pss_kib" in (''|*[!0-9]*) pss_kib=0 ;; esac - pss_pct=$(LC_ALL=C awk -v p="$pss_kib" -v t="$MT" 'BEGIN{if(t>0) printf "%.2f", (100*p)/t; else printf "0.00"}') - - cmd=$(printf "%s %s" "$comm" "${rest:-}" | json_escape) - comm_esc=$(printf "%s" "$comm" | json_escape) - - [ $pfirst -eq 1 ] || printf "," - printf '{"pid":%s,"ppid":%s,"cpu":%s,"pticks":%s,"memoryPercent":%s,"memoryKB":%s,"pssKB":%s,"pssPercent":%s,"command":"%s","fullCommand":"%s"}' \ - "$pid" "$ppid" "$cpu" "$pticks" "$pss_pct" "$rss_kib" "$pss_kib" "$pss_pct" "$comm_esc" "$cmd" - pfirst=0 -done < "$tmp_ps" -rm -f "$tmp_ps" -printf '],' - -# ---- System (dynamic bits) ---- -load_avg=$(cut -d' ' -f1-3 /proc/loadavg) -proc_count=$(ls -Ud /proc/[0-9]* 2>/dev/null | wc -l) -thread_count=$(ls -Ud /proc/[0-9]*/task/[0-9]* 2>/dev/null | wc -l) -boot_time=$(who -b 2>/dev/null | awk '{print $3, $4}' | json_escape || echo 'Unknown') - -printf '"system":{"loadavg":"%s","processes":%d,"threads":%d,"boottime":"%s"},' \ - "$load_avg" "$proc_count" "$thread_count" "$boot_time" - -# ---- Mounts (same df -h shape/strings) ---- -printf '"diskmounts":[' -tmp_mounts=$(mktemp) -df -h --output=source,target,fstype,size,used,avail,pcent | tail -n +2 | grep -vE '^(tmpfs|devtmpfs)' > "$tmp_mounts" || true -mfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - device=$(echo "$line" | awk '{print $1}' | json_escape) - mount=$( echo "$line" | awk '{print $2}' | json_escape) - fstype=$(echo "$line" | awk '{print $3}') - size=$( echo "$line" | awk '{print $4}') - used=$( echo "$line" | awk '{print $5}') - avail=$( echo "$line" | awk '{print $6}') - percent=$(echo "$line" | awk '{print $7}') - [ $mfirst -eq 1 ] || printf "," - printf '{"device":"%s","mount":"%s","fstype":"%s","size":"%s","used":"%s","avail":"%s","percent":"%s"}' \ - "$device" "$mount" "$fstype" "$size" "$used" "$avail" "$percent" - mfirst=0 -done < "$tmp_mounts" -rm -f "$tmp_mounts" -printf '],' - -# ---- GPU temps (optional) ---- -printf '"gputemps":[' -if [ "$collect_gpu_temps" = "1" ]; then - gfirst=1 - for card in /sys/class/drm/card*; do - [ -e "$card/device/driver" ] || continue - drv=$(basename "$(readlink -f "$card/device/driver")"); drv=${drv##*/} - hw=""; temp="0" - - if [ "$collect_non_nvidia" = "1" ]; then - for h in "$card/device"/hwmon/hwmon*; do - [ -e "$h/temp1_input" ] || continue - hw=$(basename "$h") - temp=$(awk '{printf "%.1f",$1/1000}' "$h/temp1_input" 2>/dev/null || echo "0") - break - done - fi - - if [ "$drv" = "nvidia" ] && [ "$temp" = "0" ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then - t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$t" ] && { temp="$t"; hw="${hw:-nvidia}"; } - fi - - if [ "$temp" != "0" ]; then - [ $gfirst -eq 1 ] || printf "," - printf '{"driver":"%s","hwmon":"%s","temperature":%s}' "$drv" "${hw:-unknown}" "${temp:-0}" - gfirst=0 - fi - done - - # Fallback: nvidia-smi only - if [ ${gfirst:-1} -eq 1 ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then - temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$temp" ] && printf '{"driver":"nvidia","hwmon":"nvidia","temperature":%s}' "$temp" - fi -fi -printf ']' - -printf "}\n" diff --git a/sysmon_dynamic_lite.sh b/sysmon_dynamic_lite.sh deleted file mode 100755 index 13769ded..00000000 --- a/sysmon_dynamic_lite.sh +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env bash -# Lightweight version of sysmon_dynamic.sh that skips expensive PSS calculations -# Args: -# $1 sort_key (cpu|memory|name|pid) -# $2 max_procs -# $3 collect_gpu_temps (0/1) -# $4 collect_nvidia_only (0/1) -# $5 collect_non_nvidia (0/1) - -set -o pipefail - -sort_key=${1:-cpu} -max_procs=${2:-20} -collect_gpu_temps=${3:-0} -collect_nvidia_only=${4:-0} -collect_non_nvidia=${5:-1} - -json_escape() { sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'; } - -printf "{" - -# ---- Memory block (exact fields/keys) ---- -mem_line="$(awk ' - /^MemTotal:/ {t=$2} - /^MemFree:/ {f=$2} - /^MemAvailable:/ {a=$2} - /^Buffers:/ {b=$2} - /^Cached:/ {c=$2} - /^Shmem:/ {s=$2} - /^SwapTotal:/ {st=$2} - /^SwapFree:/ {sf=$2} - END{printf "%d %d %d %d %d %d %d %d", t,f,a,b,c,s,st,sf} -' /proc/meminfo)" -read -r MT MF MA BU CA SH ST SF <<< "$mem_line" - -printf '"memory":{"total":%d,"free":%d,"available":%d,"buffers":%d,"cached":%d,"shared":%d,"swaptotal":%d,"swapfree":%d},' \ - "$MT" "$MF" "$MA" "$BU" "$CA" "$SH" "$ST" "$SF" - -# ---- CPU (meta + temp) ---- -cpu_count=$(nproc) -cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') -cpu_freq=$(awk -F: '/cpu MHz/{gsub(/ /,"",$2);print $2;exit}' /proc/cpuinfo || echo 0) - -cpu_temp=0 -for hwmon_dir in /sys/class/hwmon/hwmon*/; do - [ -d "$hwmon_dir" ] || continue - name_file="${hwmon_dir}name" - [ -r "$name_file" ] || continue - if grep -qE 'coretemp|k10temp|k8temp|cpu_thermal|soc_thermal' "$name_file" 2>/dev/null; then - for temp_file in "${hwmon_dir}"temp*_input; do - [ -r "$temp_file" ] || continue - cpu_temp=$(awk '{printf "%.1f", $1/1000}' "$temp_file" 2>/dev/null || echo 0) - break 2 - done - fi -done - -printf '"cpu":{"count":%d,"model":"%s","frequency":%s,"temperature":%s,' \ - "$cpu_count" "$cpu_model" "$cpu_freq" "$cpu_temp" - -# cpu.total (first line of /proc/stat, fields 2..) -printf '"total":' -awk 'NR==1 { - printf "["; - for(i=2; i<=NF; i++) { if(i>2) printf ","; printf "%d", $i; } - printf "]"; - exit -}' /proc/stat - -# cpu.cores (each cpuN line, 8 columns) -printf ',"cores":[' -cpu_cores=$(nproc) -awk -v n="$cpu_cores" 'BEGIN{c=0} - /^cpu[0-9]+/ { - if(c>0) printf ","; - printf "[%d,%d,%d,%d,%d,%d,%d,%d]", $2,$3,$4,$5,$6,$7,$8,$9; - c++; - if(c==n) exit - }' /proc/stat -printf ']},' - -# ---- Network (rx/tx bytes per iface) ---- -printf '"network":[' -tmp_net=$(mktemp) -grep -E '(wlan|eth|enp|wlp|ens|eno)' /proc/net/dev > "$tmp_net" || true -nfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - iface=$(echo "$line" | awk '{print $1}' | sed 's/://') - rx_bytes=$(echo "$line" | awk '{print $2}') - tx_bytes=$(echo "$line" | awk '{print $10}') - [ $nfirst -eq 1 ] || printf "," - printf '{"name":"%s","rx":%d,"tx":%d}' "$iface" "$rx_bytes" "$tx_bytes" - nfirst=0 -done < "$tmp_net" -rm -f "$tmp_net" -printf '],' - -# ---- Disk (/proc/diskstats sectors; read/write) ---- -printf '"disk":[' -tmp_disk=$(mktemp) -grep -E ' (sd[a-z]+|nvme[0-9]+n[0-9]+|vd[a-z]+|dm-[0-9]+|mmcblk[0-9]+) ' /proc/diskstats > "$tmp_disk" || true -dfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - name=$(echo "$line" | awk '{print $3}') - read_sectors=$(echo "$line" | awk '{print $6}') - write_sectors=$(echo "$line" | awk '{print $10}') - [ $dfirst -eq 1 ] || printf "," - printf '{"name":"%s","read":%d,"write":%d}' "$name" "$read_sectors" "$write_sectors" - dfirst=0 -done < "$tmp_disk" -rm -f "$tmp_disk" -printf '],' - -# ---- Processes (SIMPLIFIED - no PSS, use RSS from ps directly) ---- -case "$sort_key" in - cpu) SORT_OPT="--sort=-pcpu" ;; - memory) SORT_OPT="--sort=-pmem" ;; - name) SORT_OPT="--sort=+comm" ;; - pid) SORT_OPT="--sort=+pid" ;; - *) SORT_OPT="--sort=-pcpu" ;; -esac - -printf '"processes":[' -tmp_ps=$(mktemp) -ps -eo pid,ppid,pcpu,pmem,rss,comm,cmd --no-headers $SORT_OPT | head -n "$max_procs" > "$tmp_ps" || true -pfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - pid=$( awk '{print $1}' <<<"$line") - ppid=$( awk '{print $2}' <<<"$line") - cpu=$( awk '{print $3}' <<<"$line") - pmem=$( awk '{print $4}' <<<"$line") - rss_kib=$(awk '{print $5}' <<<"$line") - comm=$( awk '{print $6}' <<<"$line") - rest=$( printf '%s\n' "$line" | cut -d' ' -f7- ) - - # CPU ticks (utime+stime) - pticks=$(awk '{print $14+$15}' "/proc/$pid/stat" 2>/dev/null || echo 0) - - # Use RSS-based memory percentage (skip expensive PSS calculation) - cmd=$(printf "%s %s" "$comm" "${rest:-}" | json_escape) - comm_esc=$(printf "%s" "$comm" | json_escape) - - [ $pfirst -eq 1 ] || printf "," - printf '{"pid":%s,"ppid":%s,"cpu":%s,"pticks":%s,"memoryPercent":%s,"memoryKB":%s,"pssKB":%s,"pssPercent":%s,"command":"%s","fullCommand":"%s"}' \ - "$pid" "$ppid" "$cpu" "$pticks" "$pmem" "$rss_kib" "$rss_kib" "$pmem" "$comm_esc" "$cmd" - pfirst=0 -done < "$tmp_ps" -rm -f "$tmp_ps" -printf '],' - -# ---- System (dynamic bits) ---- -load_avg=$(cut -d' ' -f1-3 /proc/loadavg) -proc_count=$(ls -Ud /proc/[0-9]* 2>/dev/null | wc -l) -thread_count=$(ls -Ud /proc/[0-9]*/task/[0-9]* 2>/dev/null | wc -l) -boot_time=$(who -b 2>/dev/null | awk '{print $3, $4}' | json_escape || echo 'Unknown') - -printf '"system":{"loadavg":"%s","processes":%d,"threads":%d,"boottime":"%s"},' \ - "$load_avg" "$proc_count" "$thread_count" "$boot_time" - -# ---- Mounts (same df -h shape/strings) ---- -printf '"diskmounts":[' -tmp_mounts=$(mktemp) -df -h --output=source,target,fstype,size,used,avail,pcent | tail -n +2 | grep -vE '^(tmpfs|devtmpfs)' > "$tmp_mounts" || true -mfirst=1 -while IFS= read -r line; do - [ -z "$line" ] && continue - device=$(echo "$line" | awk '{print $1}' | json_escape) - mount=$( echo "$line" | awk '{print $2}' | json_escape) - fstype=$(echo "$line" | awk '{print $3}') - size=$( echo "$line" | awk '{print $4}') - used=$( echo "$line" | awk '{print $5}') - avail=$( echo "$line" | awk '{print $6}') - percent=$(echo "$line" | awk '{print $7}') - [ $mfirst -eq 1 ] || printf "," - printf '{"device":"%s","mount":"%s","fstype":"%s","size":"%s","used":"%s","avail":"%s","percent":"%s"}' \ - "$device" "$mount" "$fstype" "$size" "$used" "$avail" "$percent" - mfirst=0 -done < "$tmp_mounts" -rm -f "$tmp_mounts" -printf '],' - -# ---- GPU temps (optional) ---- -printf '"gputemps":[' -if [ "$collect_gpu_temps" = "1" ]; then - gfirst=1 - for card in /sys/class/drm/card*; do - [ -e "$card/device/driver" ] || continue - drv=$(basename "$(readlink -f "$card/device/driver")"); drv=${drv##*/} - hw=""; temp="0" - - if [ "$collect_non_nvidia" = "1" ]; then - for h in "$card/device"/hwmon/hwmon*; do - [ -e "$h/temp1_input" ] || continue - hw=$(basename "$h") - temp=$(awk '{printf "%.1f",$1/1000}' "$h/temp1_input" 2>/dev/null || echo "0") - break - done - fi - - if [ "$drv" = "nvidia" ] && [ "$temp" = "0" ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then - t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$t" ] && { temp="$t"; hw="${hw:-nvidia}"; } - fi - - if [ "$temp" != "0" ]; then - [ $gfirst -eq 1 ] || printf "," - printf '{"driver":"%s","hwmon":"%s","temperature":%s}' "$drv" "${hw:-unknown}" "${temp:-0}" - gfirst=0 - fi - done - - # Fallback: nvidia-smi only - if [ ${gfirst:-1} -eq 1 ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then - temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$temp" ] && printf '{"driver":"nvidia","hwmon":"nvidia","temperature":%s}' "$temp" - fi -fi -printf ']' - -printf "}\n" \ No newline at end of file diff --git a/sysmon_static.sh b/sysmon_static.sh deleted file mode 100755 index 18b295a1..00000000 --- a/sysmon_static.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -# Outputs static system info + detected GPUs as JSON (no temps) - -set -o pipefail -exec 2>/dev/null - -json_escape() { sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'; } - -printf "{" - -cpu_count=$(nproc) -cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') -printf '"cpu":{"count":%d,"model":"%s"},' "$cpu_count" "$cpu_model" - -dmip="/sys/class/dmi/id" -[ -d "$dmip" ] || dmip="/sys/devices/virtual/dmi/id" -mb_vendor=$([ -r "$dmip/board_vendor" ] && cat "$dmip/board_vendor" | json_escape || echo "Unknown") -mb_name=$([ -r "$dmip/board_name" ] && cat "$dmip/board_name" | json_escape || echo "") -bios_ver=$([ -r "$dmip/bios_version" ] && cat "$dmip/bios_version" | json_escape || echo "Unknown") -bios_date=$([ -r "$dmip/bios_date" ] && cat "$dmip/bios_date" | json_escape || echo "") - -kern_ver=$(uname -r | json_escape) -distro=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2- | tr -d '"' | json_escape || echo 'Unknown') -host_name=$(hostname | json_escape) -arch_name=$(uname -m) - -printf '"system":{"kernel":"%s","distro":"%s","hostname":"%s","arch":"%s","motherboard":"%s %s","bios":"%s %s"},' \ - "$kern_ver" "$distro" "$host_name" "$arch_name" "$mb_vendor" "$mb_name" "$bios_ver" "$bios_date" - -printf '"gpus":[' -gfirst=1 -tmp_gpu=$(mktemp) - -infer_vendor() { - case "$1" in - nvidia|nouveau) echo NVIDIA ;; - amdgpu|radeon) echo AMD ;; - i915|xe) echo Intel ;; - *) case "$2" in - *NVIDIA*|*Nvidia*|*nvidia*) echo NVIDIA ;; - *AMD*|*ATI*|*amd*|*ati*) echo AMD ;; - *Intel*|*intel*) echo Intel ;; - *) echo Unknown ;; - esac ;; - esac -} - -prio_of() { - local drv="$1" bdf="$2" - case "$drv" in - nvidia) echo 3 ;; - amdgpu|radeon) - local dd="${bdf##*:}"; dd="${dd%%.*}" - [ "$dd" = "00" ] && echo 1 || echo 2 - ;; - i915|xe) echo 0 ;; - *) echo 0 ;; - esac -} - -LC_ALL=C lspci -nnD 2>/dev/null | grep -iE ' VGA| 3D| 2D| Display' | while IFS= read -r line; do - bdf="${line%% *}" - drv="" - [ -e "/sys/bus/pci/devices/$bdf/driver" ] && drv="$(basename "$(readlink -f "/sys/bus/pci/devices/$bdf/driver")")" - vendor="$(infer_vendor "$drv" "$line")" - raw_line="$(printf '%s' "$line" | json_escape)" - prio="$(prio_of "$drv" "$bdf")" - printf '%s|%s|%s|%s\n' "$prio" "$drv" "$vendor" "$raw_line" >> "$tmp_gpu" -done - -if [ -s "$tmp_gpu" ]; then - while IFS='|' read -r pr drv vendor raw_line; do - [ $gfirst -eq 1 ] || printf "," - printf '{"driver":"%s","vendor":"%s","rawLine":"%s"}' "$drv" "$vendor" "$raw_line" - gfirst=0 - done < <(sort -t'|' -k1,1nr -k2,2 "$tmp_gpu") -fi - -rm -f "$tmp_gpu" -printf ']' -printf "}\n"