1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

re-work nightmode setting to use gammastep

This commit is contained in:
bbedward
2025-08-13 09:53:46 -04:00
parent 3bbf8c5844
commit 67ac3d1c9d
12 changed files with 275 additions and 655 deletions

View File

@@ -1,5 +1,4 @@
import Quickshell
import qs.Common
pragma Singleton
Singleton {

View File

@@ -7,7 +7,6 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Common
Singleton {
id: root

View File

@@ -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()

View File

@@ -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();
}
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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
</details>

View File

@@ -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));
@@ -87,8 +94,51 @@ 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 {
@@ -270,4 +340,63 @@ 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"
}
}

View File

@@ -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,11 +509,44 @@ 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
interval: root.updateInterval

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"