[AGS] Bar: BT, Audio, SysInfo, Brightness
This commit is contained in:
@@ -13,11 +13,14 @@ const STATE = AstalNetwork.DeviceState;
|
||||
const QuickView = () => {
|
||||
const quickActions = QuickActions.QuickActions();
|
||||
|
||||
|
||||
return <button onClicked={() => quickActions.popup()} child={
|
||||
<box>
|
||||
<BatteryWidget></BatteryWidget>
|
||||
<Audio></Audio>
|
||||
<BluetoothWidget></BluetoothWidget>
|
||||
<NetworkWidget></NetworkWidget>
|
||||
<BrightnessWidget></BrightnessWidget>
|
||||
<image iconName={"system-shutdown-symbolic"}></image>
|
||||
{ quickActions }
|
||||
</box>
|
||||
}></button>
|
||||
@@ -32,20 +35,18 @@ const NetworkWidget = () => {
|
||||
if ( state === AstalNetwork.State.CONNECTING ) {
|
||||
return 'chronometer-reset-symbolic';
|
||||
} else if ( state === AstalNetwork.State.CONNECTED_LOCAL || state === AstalNetwork.State.CONNECTED_SITE || state === AstalNetwork.State.CONNECTED_GLOBAL ) {
|
||||
print( 'Wired connected' );
|
||||
return 'network-wired-activated-symbolic';
|
||||
} else {
|
||||
print( 'Unknown state' );
|
||||
return 'paint-unknown-symbolic';
|
||||
}
|
||||
} )} cssClasses={[ 'network-widget' ]} visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED )}></image>
|
||||
} )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED )}></image>
|
||||
<image iconName={bind( network.wifi, 'state' ).as( state => {
|
||||
if ( state === STATE.ACTIVATED ) {
|
||||
return network.wifi.iconName
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} )} cssClasses={[ 'network-widget' ]} visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED )}></image>
|
||||
} )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED )}></image>
|
||||
</box>
|
||||
|
||||
|
||||
@@ -53,15 +54,34 @@ const NetworkWidget = () => {
|
||||
|
||||
const BluetoothWidget = () => {
|
||||
const bluetooth = AstalBluetooth.get_default();
|
||||
const enabled = bind( bluetooth, "isPowered" );
|
||||
const enabled = bind( bluetooth.adapter, "powered" );
|
||||
const connected = bind( bluetooth, "isConnected" );
|
||||
|
||||
// For each connected BT device, render status
|
||||
return <box>
|
||||
<box visible={enabled.as( e => e )}>
|
||||
<image iconName={"bluetooth-active-symbolic"} visible={connected.as( c => c )}></image>
|
||||
<image iconName={"bluetooth-disconnected-symbolic"} visible={connected.as( c => !c )}></image>
|
||||
</box>
|
||||
<image iconName={"bluetooth-disabled-symbolic"} visible={enabled.as( e => !e )}></image>
|
||||
<box>
|
||||
{bind( bluetooth, 'devices' ).as( devices => {
|
||||
return devices.map( device => {
|
||||
return <box visible={bind( device, 'connected' ).as( c => c )}>
|
||||
<image iconName={bind( device, 'icon' ).as( icon => icon )}></image>
|
||||
<label label={bind( device, 'batteryPercentage' ).as( n => { return n + '%' } ) }></label>
|
||||
</box>
|
||||
} );
|
||||
} )}
|
||||
</box>
|
||||
</box>
|
||||
}
|
||||
|
||||
|
||||
const BatteryWidget = () => {
|
||||
const battery = AstalBattery.get_default();
|
||||
if ( battery.get_is_present() ) {
|
||||
return <image iconName={battery.iconName}></image>
|
||||
return <image iconName={bind( battery, 'iconName' ).as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||
} else {
|
||||
return <box></box>
|
||||
}
|
||||
@@ -70,31 +90,27 @@ const BatteryWidget = () => {
|
||||
|
||||
|
||||
const BrightnessWidget = () => {
|
||||
// TODO: Finish (detect if there is a controllable screen)
|
||||
const brightness = Brightness.get_default();
|
||||
const screen_brightness = bind( brightness, "screen" );
|
||||
|
||||
return <label label={"🌣" + screen_brightness}></label>
|
||||
return <label label={"🌣" + screen_brightness} visible={bind( brightness, 'screenAvailable' )} cssClasses={[ 'quick-view-symbol' ]}></label>
|
||||
}
|
||||
|
||||
|
||||
const Audio = () => {
|
||||
const wireplumber = AstalWp.get_default();
|
||||
if ( wireplumber ) {
|
||||
const volume_speakers = bind( wireplumber.defaultSpeaker, 'volume' );
|
||||
|
||||
return <box orientation={Gtk.Orientation.HORIZONTAL}>
|
||||
<image iconName={wireplumber.defaultSpeaker.volumeIcon}></image>
|
||||
<image iconName={wireplumber.defaultMicrophone.volumeIcon}></image>
|
||||
<label label={volume_speakers.as( v => { return "" + v } ) }></label>
|
||||
<label label={wireplumber.defaultSpeaker.get_name()}></label>
|
||||
<image iconName={bind(wireplumber.defaultSpeaker, 'volumeIcon').as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||
<image iconName={bind(wireplumber.defaultMicrophone, 'volumeIcon').as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||
</box>
|
||||
} else {
|
||||
print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing' );
|
||||
return <image iconName={"action-unavailable-symbolic"}></image>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// cssClasses={[ 'quick-view-symbol' ]}
|
||||
|
||||
export default {
|
||||
QuickView
|
||||
|
@@ -1,7 +1,46 @@
|
||||
import { exec, GLib } from "astal"
|
||||
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 is available
|
||||
// Check if awk & sed are available
|
||||
try {
|
||||
exec( 'awk -V' );
|
||||
exec( 'sed --version' );
|
||||
@@ -11,44 +50,86 @@ const featureTest = () => {
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mpstat is available
|
||||
try {
|
||||
exec( 'mpstat' );
|
||||
exec( 'mpstat -V' );
|
||||
} catch ( e ) {
|
||||
availableFeatures.cpu = false;
|
||||
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
|
||||
}
|
||||
|
||||
// Battery... acpi might be present, but potentially no bat
|
||||
}
|
||||
|
||||
let enabled = false;
|
||||
|
||||
const availableFeatures = {
|
||||
cpu: true,
|
||||
ram: true,
|
||||
bat: true,
|
||||
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 ) {
|
||||
const cpuUtil = exec( 'mpstat | awk "/all/ {print(100 - $NF)"}' );
|
||||
cpuUtil.set( '' + Math.round( parseFloat( exec( `/bin/fish -c cpu-utilization` ) ) ) );
|
||||
}
|
||||
if ( availableFeatures.ram ) {
|
||||
const ramUtil = exec( `free | awk '/Mem/ { printf("%.2f\\n", ($3/$2)*100) }'` );
|
||||
}
|
||||
if ( availableFeatures.bat ) {
|
||||
const acpi = exec( `acpi -i | grep 'Battery'` );
|
||||
// TODO: Parse acpi output
|
||||
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 = () => {
|
||||
return <box></box>
|
||||
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
|
||||
SystemInfo,
|
||||
panel
|
||||
}
|
||||
|
10
config/astal/components/bar/ui/modules/stats.d.ts
vendored
Normal file
10
config/astal/components/bar/ui/modules/stats.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
interface Stats {
|
||||
kernel: string;
|
||||
netSpeed: string;
|
||||
cpuTemp: string;
|
||||
cpuClk: string;
|
||||
gpuTemp: string;
|
||||
gpuClk: string;
|
||||
vram: string;
|
||||
availableVRAM: string;
|
||||
}
|
Reference in New Issue
Block a user