136 lines
4.3 KiB
TypeScript

import { exec, GLib, interval, Variable } from "astal"
import { Gtk } from "astal/gtk4";
import AstalBattery from "gi://AstalBattery?version=0.1";
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' ),
netSpeed: exec( `/bin/bash -c "interface=$(ip route get 8.8.8.8 | awk '{print $5; exit}') && cat \"/sys/class/net/$interface/speed\""` ),
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 valign={Gtk.Align.START}>
<label label={ramUsed( used => {
return used + `(${ ramUtil.get() }%)`;
} )}></label>
<label label={systemStats( stats => {
return `CPU: ${stats.cpuTemp}, ${stats.cpuClk}
GPU: ${stats.gpuTemp}, ${stats.gpuClk} (${stats.vram} / ${stats.availableVRAM})
Network: ${stats.netSpeed}
Kernel: ${stats.kernel}` } ) }></label>
</box>;
}
const SystemInformationPanel = () => {
const popover = new Gtk.Popover();
popover.set_child( info() );
return popover;
}
const sysInfoFetcher = () => {
if ( enabled ) {
if ( availableFeatures.cpu ) {
cpuUtil.set( '' + Math.round( parseFloat( exec( `/bin/fish -c cpu-utilization` ) ) ) );
}
if ( availableFeatures.ram ) {
ramUtil.set( '' + Math.round( parseFloat( exec( `/bin/bash -c "free | awk '/Mem/ { printf(\\"%.2f\\\\n\\", ($3/$2)*100) }'"` ) ) ) );
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>
}></button>
} else {
return <image iconName={"action-unavailable-symbolic"}></image>
}
}
export default {
SystemInfo,
panel
}