206 lines
6.1 KiB
TypeScript

import { exec, execAsync, interval, Variable } from "astal";
import { Gtk } from "astal/gtk4";
const FETCH_INTERVAL = 2000;
const cpuUtil = Variable("0%");
const ramUtil = Variable("0%");
const ramUsed = Variable("0MiB");
const gpuUtil = Variable("0%");
let gpuName = "card1";
let enabled = false;
const refreshStats = (): Stats => {
gpuName = exec(`/bin/bash -c "ls /sys/class/drm/ | grep '^card[0-9]*$'"`);
const cpuNameInSensors = "CPUTIN";
const stats = {
kernel: exec("uname -sr"),
cpuTemp: exec(
`/bin/bash -c "sensors | grep -m1 ${cpuNameInSensors} | awk '{print $2}'"`,
),
cpuClk: exec(
`awk '/cpu MHz/ {sum+=$4; ++n} END {print sum/n " MHz"}' /proc/cpuinfo`,
),
gpuTemp: exec(
`/bin/bash -c "sensors | grep -E 'edge' | awk '{print $2}'"`,
),
gpuClk: exec(
`/bin/bash -c "cat /sys/class/drm/${gpuName}/device/pp_dpm_sclk | grep '\\*' | awk '{print $2 $3}'"`,
),
vram:
Math.round(
parseInt(
exec(
`cat /sys/class/drm/${gpuName}/device/mem_info_vram_used`,
),
) /
1024 /
1024,
) + "MiB",
availableVRAM:
Math.round(
parseInt(
exec(
`cat /sys/class/drm/${gpuName}/device/mem_info_vram_total`,
),
) /
1024 /
1024,
) + "MiB",
};
return stats;
};
const systemStats: Variable<Stats> = Variable(refreshStats());
const availableFeatures = {
cpu: true,
ram: true,
};
const featureTest = () => {
// Check if awk & sed are available
try {
exec("awk -V");
exec("sed --version");
enabled = true;
} catch (e) {
printerr(
"[ SysInfo ] AWK or SED missing! No system info will be available",
);
enabled = false;
return;
}
// Check if mpstat is available
try {
exec("mpstat -V");
} catch (e) {
availableFeatures.cpu = false;
printerr(
"[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!",
);
}
};
const info = () => {
return (
<box vertical>
<label
label={"System Information"}
cssClasses={["title-2"]}
></label>
<Gtk.Separator marginTop={5} marginBottom={10}></Gtk.Separator>
<label
vexpand
halign={Gtk.Align.START}
hexpand
label={ramUsed(used => {
return "RAM: " + used + ` (${ramUtil.get()}%)`;
})}
></label>
<label
label={systemStats(stats => {
return `CPU: ${stats.cpuTemp}, ${stats.cpuClk}
GPU: ${stats.gpuTemp}, ${stats.gpuClk} (${stats.vram} / ${stats.availableVRAM})
Kernel: ${stats.kernel}`;
})}
></label>
<Gtk.Separator marginTop={10}></Gtk.Separator>
<button
onClicked={() => exec(`/bin/sh -c "kitty --hold fish -c 'fastfetch'"`)}
child={
<label label={"View FastFetch"}></label>
}></button>
</box>
);
};
const SystemInformationPanel = () => {
const popover = new Gtk.Popover();
popover.set_child(info());
return popover;
};
const sysInfoFetcher = () => {
if (enabled) {
if (availableFeatures.cpu) {
execAsync(`/bin/fish -c cpu-utilization`).then(v => {
cpuUtil.set("" + Math.round(parseFloat(v)));
}).catch( e => {
console.error( e );
} );
}
if (availableFeatures.ram) {
execAsync(
`/bin/bash -c "free | awk '/Mem:/ {print $3 \\" \\" $2}'"`,
).then( v => {
const util = parseInt( v.split( ' ' )[ 0 ] );
const available = parseInt( v.split( ' ' )[ 1 ] );
ramUtil.set( "" + Math.round( available / util ) );
ramUsed.set( `${Math.round( util / 1024 / 1024 )}MiB used of ${Math.round( available / 1024 / 1024 )}MiB` );
} ).catch( e => {
console.error( e );
} );
ramUsed.set(
exec(
`/bin/bash -c \"free -h | awk '/^Mem:/ {print $3 \\" used of \\" $2}'\"`,
)
.replaceAll("Gi", "GiB")
.replaceAll("Mi", "MiB"),
);
}
gpuUtil.set(exec("cat /sys/class/drm/card1/device/gpu_busy_percent"));
}
};
const panel = SystemInformationPanel();
const SystemInfo = () => {
featureTest();
const openSysInfo = async () => {
panel.popup();
systemStats.set(refreshStats());
};
if (enabled) {
sysInfoFetcher();
interval(FETCH_INTERVAL, sysInfoFetcher);
return (
<button
onClicked={() => openSysInfo()}
child={
<box tooltipText={ramUsed(v => v)}>
<image
iconName={"power-profile-performance-symbolic"}
marginEnd={1}
></image>
<label
label={cpuUtil(util => util)}
marginEnd={5}
></label>
<image iconName={"histogram-symbolic"}></image>
<label label={ramUtil(util => util)}></label>
<image iconName={"show-gpu-effects-symbolic"}></image>
<label label={gpuUtil(util => util)}></label>
{panel}
</box>
}
cssClasses={["bar-button"]}
></button>
);
} else {
return <image iconName={"action-unavailable-symbolic"}></image>;
}
};
export default {
SystemInfo,
panel,
};