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:
@@ -1,5 +1,4 @@
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
pragma Singleton
|
||||
|
||||
Singleton {
|
||||
|
||||
@@ -7,7 +7,6 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Services
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user