138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import app from "ags/gtk3/app"
|
|
import { Astal, Gtk } from "ags/gtk3"
|
|
import { createPoll } from "ags/time"
|
|
import { shell } from "../lib/utils"
|
|
|
|
export const SYSMONITOR_WINDOW = "sys-monitor"
|
|
|
|
let previousIdle = 0
|
|
let previousTotal = 0
|
|
|
|
const cpuUsage = createPoll(
|
|
0,
|
|
2000,
|
|
shell("grep '^cpu ' /proc/stat"),
|
|
(stdout) => {
|
|
const values = stdout.trim().split(/\s+/).slice(1).map(Number)
|
|
const idle = (values[3] || 0) + (values[4] || 0)
|
|
const total = values.reduce((sum, value) => sum + value, 0)
|
|
const idleDelta = idle - previousIdle
|
|
const totalDelta = total - previousTotal
|
|
previousIdle = idle
|
|
previousTotal = total
|
|
if (totalDelta <= 0) return 0
|
|
return Math.round((1 - idleDelta / totalDelta) * 100)
|
|
},
|
|
)
|
|
|
|
type Usage = { percent: number; used: string; total: string }
|
|
|
|
const memory = createPoll<Usage>(
|
|
{ percent: 0, used: "0", total: "0" },
|
|
2000,
|
|
shell("free -m | awk '/^Mem:/ {print $3\" \"$2}'"),
|
|
(stdout) => {
|
|
const [used, total] = stdout.trim().split(/\s+/).map(Number)
|
|
if (!total) return { percent: 0, used: "0", total: "0" }
|
|
return {
|
|
percent: Math.round((used / total) * 100),
|
|
used: `${(used / 1024).toFixed(1)}G`,
|
|
total: `${(total / 1024).toFixed(1)}G`,
|
|
}
|
|
},
|
|
)
|
|
|
|
const disk = createPoll<Usage>(
|
|
{ percent: 0, used: "0", total: "0" },
|
|
30000,
|
|
shell("df -h --output=pcent,used,size / | tail -1"),
|
|
(stdout) => {
|
|
const [percent, used, total] = stdout.trim().split(/\s+/)
|
|
return {
|
|
percent: Number(percent.replace("%", "")) || 0,
|
|
used: used || "0",
|
|
total: total || "0",
|
|
}
|
|
},
|
|
)
|
|
|
|
const temperature = createPoll(
|
|
0,
|
|
2000,
|
|
shell("cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0"),
|
|
(stdout) => Math.round(Number(stdout.trim()) / 1000) || 0,
|
|
)
|
|
|
|
function Metric({
|
|
icon,
|
|
name,
|
|
percent,
|
|
detail,
|
|
}: {
|
|
icon: string
|
|
name: string
|
|
percent: any
|
|
detail: any
|
|
}) {
|
|
return (
|
|
<box class="sys-metric" vertical spacing={4}>
|
|
<box spacing={8}>
|
|
<label class="sys-metric-icon" label={icon} />
|
|
<label class="sys-metric-name" label={name} xalign={0} halign={Gtk.Align.START} hexpand />
|
|
<label class="sys-metric-detail" label={detail} halign={Gtk.Align.END} />
|
|
</box>
|
|
<levelbar
|
|
class="sys-metric-bar"
|
|
value={percent.as((value: number) => value / 100)}
|
|
/>
|
|
</box>
|
|
)
|
|
}
|
|
|
|
export default function SysMonitor() {
|
|
const { TOP, RIGHT } = Astal.WindowAnchor
|
|
|
|
return (
|
|
<window
|
|
name={SYSMONITOR_WINDOW}
|
|
namespace={SYSMONITOR_WINDOW}
|
|
class="SysMonitor"
|
|
anchor={TOP | RIGHT}
|
|
margin={10}
|
|
layer={Astal.Layer.OVERLAY}
|
|
exclusivity={Astal.Exclusivity.NORMAL}
|
|
visible={false}
|
|
application={app}
|
|
>
|
|
<box class="panel sys-monitor" vertical spacing={12}>
|
|
<label class="panel-title" label="System Monitor" xalign={0} halign={Gtk.Align.START} />
|
|
|
|
<Metric
|
|
icon={"\udb80\udf5b"}
|
|
name="CPU"
|
|
percent={cpuUsage}
|
|
detail={cpuUsage.as((value) => `${value}%`)}
|
|
/>
|
|
<Metric
|
|
icon={"\udb81\ude1a"}
|
|
name="Memory"
|
|
percent={memory.as((value) => value.percent)}
|
|
detail={memory.as((value) => `${value.used} / ${value.total}`)}
|
|
/>
|
|
<Metric
|
|
icon={"\udb80\udeca"}
|
|
name="Disk"
|
|
percent={disk.as((value) => value.percent)}
|
|
detail={disk.as((value) => `${value.used} / ${value.total}`)}
|
|
/>
|
|
<Metric
|
|
icon={"\uf2c9"}
|
|
name="Temperature"
|
|
percent={temperature}
|
|
detail={temperature.as((value) => `${value}°C`)}
|
|
/>
|
|
</box>
|
|
</window>
|
|
)
|
|
}
|