#!/usr/bin/env perl # # Dependencies: iproute2 iw volctl # ################################################################################ use strict; use warnings; use feature 'signatures'; use Time::HiRes qw; # Turn off buffering $| = 1; ################################################################################ # Formatting use constant { COLOR_WARN => "#ffff44", COLOR_ERR => "#ff6600", }; sub color_thresholds($x, $warn, $err) { if ($x >= $err) { return { type => 'background', val => COLOR_ERR }; } elsif ($x >= $warn) { return { type => 'color', val => COLOR_WARN }; } else { return 0; } } sub pango_warning($txt, $enabled) { return $enabled ? "$txt" : $txt; } sub pango_onoff($txt, $on) { return $on ? $txt : "$txt"; } sub txt_onoff($txt, $on) { return $on ? $txt : "${txt}X"; } sub render_iface_linux($is_wireless, $name, $status, $addr) { my $extra = $addr ? " $addr" : ''; if ($is_wireless and $status eq 'UP') { my ($ssid) = `iw dev $name link` =~ /SSID: ([^\n]*).*/; my ($dbm) = `iw dev $name link` =~ /signal: ([0-9-]+ dBm)/; $extra .= ' (' . $ssid . ', ' . $dbm . ')'; } return pango_onoff($is_wireless ? '๐Ÿ›œ' : '๐ŸŒ', $status eq 'UP') . $extra; } sub render_iface_openbsd($ifaces, $ifname) { my $iface = $ifaces->{$ifname}; my $extra = $iface->{'ip'} ? " $iface->{'ip'}" : ''; my $is_wireless = exists $iface->{'wifi'}; my $is_up = $iface->{'up'}; if ($is_wireless and $is_up) { $extra .= ' (' . $iface->{'wifi_ssid'} . ', ' . $iface->{'wifi_signal'} . ')'; } return txt_onoff($is_wireless ? '๐Ÿ“ถ' : '๐ŸŒ', $is_up) . $extra; } ################################################################################ # Profiling sub show_time_delta($section, $time0, $computed) { my (undef, $time) = gettimeofday(); my $diff = ($time - $time0) / 1000; my $extra = $computed ? ' (COMPUTED)' : ''; printf STDERR "Spent $diff milliseconds @ $section$extra\n"; } ################################################################################ # Modules # open(my $loadf, '<', '/proc/loadavg'); # my %mod_cpu_linux = ( # name => 'CPU', # period => 5, # compute => sub { # read($loadf, my $loads, 14); # seek($loadf, 0, 0); # return { text => sprintf ("CPU %s/%s/%s", split ' ', $loads) }; # } # ); my %mod_cpu_openbsd = ( name => 'CPU', period => 5, compute => sub { my $loads = `sysctl -n vm.loadavg`; chomp $loads; my @loads = split ' ', $loads; return { text => sprintf ("CPU %s/%s/%s", @loads), color => color_thresholds($loads[1], 1, 3), }; } ); # my %mod_mem_linux = ( # name => 'MEMORY', # period => 5, # compute => sub { # my $mused = `free | awk '/^Mem:/ {printf("%d", 100 * (\$3/\$2))}'`; # return { text => pango_warning(sprintf("MEM %s%%", $mused), $mused > 90) }; # } # ); my %mod_mem_openbsd = ( name => 'MEMORY', period => 5, compute => sub { my $uvmexp = `vmstat -s`; my ($mtotal) = $uvmexp =~ /([0-9]+) pages managed/; my ($mused) = $uvmexp =~ /([0-9]+) pages active/; $mused = 100 * $mused / $mtotal; return { text => sprintf("MEM %d%%", $mused), color => color_thresholds($mused, 70, 90), }; } ); # my %mod_net_linux = ( # name => 'NETWORK', # period => 4, # compute => sub { # my @wired = (); # my @wireless = (); # for (split("\n", `ip --br a`)) { # next if substr($_, 0, 2) eq 'lo'; # prevent regex # my ($if, $status, $addr) = $_ =~ /^(\S+)\s+(\S+)(?:\s+(\S+))?/; # push @wired, [0, $if, $status, $addr] if (substr($if, 0, 2) eq 'en'); # push @wireless, [1, $if, $status, $addr] if (substr($if, 0, 2) eq 'wl'); # } # return { text => join ' | ', map { render_iface_linux(@$_) } @wired, @wireless }; # } # ); my %mod_net_openbsd = ( name => 'NETWORK', period => 4, compute => sub { my @wired = (); my @wireless = (); my %ifaces; my $ifname; for (`ifconfig`) { if (/^([a-z]+[0-9]+):/) { $ifname = $1; $ifaces{$ifname} = {}; $ifaces{$ifname}{'up'} = /UP/; } elsif (/media: (.*)/) { $ifaces{$ifname}{'media'} = $1; } elsif (/ieee80211:.* join "([^"]*)".* ([0-9]+%)/) { $ifaces{$ifname}{'wifi'} = 1; $ifaces{$ifname}{'wifi_ssid'} = $1; $ifaces{$ifname}{'wifi_signal'} = $2; } elsif (/inet ([0-9.]+)/) { $ifaces{$ifname}{'ip'} = $1; } } foreach my $ifname (keys %ifaces) { next if $ifname =~ /^lo[0-9]+/; my $iface = $ifaces{$ifname}; next unless $iface->{'ip'} || $iface->{'media'}; if ($iface->{'wifi'}) { push @wireless, $ifname; } else { push @wired, $ifname; } } # use Data::Dumper; # print Dumper(\%ifaces); # print Dumper(\@wired); # print Dumper(\@wireless); return { text => join ' ยท ', map { render_iface_openbsd(\%ifaces, $_) } sort(@wired), @wireless }; } ); # my $BATDIR = glob '/sys/class/power_supply/BAT?'; # open(my $bcapf, '<', "$BATDIR/capacity"); # open(my $bstatf, '<', "$BATDIR/status"); # my %mod_bat_linux = ( # name => 'BATTERY', # period => 3, # compute => sub { # my $batlevel = <$bcapf>; # chomp $batlevel; # seek($bcapf, 0, 0); # my $batcharging = <$bstatf> eq "Charging\n" ? 'โšก' : ''; # seek($bstatf, 0, 0); # return { text => pango_warning(sprintf("๐Ÿ”‹%s%s", $batlevel, $batcharging), $batlevel < 10) }; # } # ); sub obsd_get_sysctl($mib) { my @parts = split(' ', `sysctl -n $mib`); return $parts[0]; } my %mod_bat_openbsd = ( name => 'BATTERY', period => 3, compute => sub { my $batcharging = obsd_get_sysctl('hw.sensors.acpibat0.raw0') eq "0" ? 'โšก' : ''; my $batmax = obsd_get_sysctl('hw.sensors.acpibat0.watthour0'); my $batcur = obsd_get_sysctl('hw.sensors.acpibat0.watthour3'); my $batlevel = 100 * $batcur / $batmax; return { text => sprintf("๐Ÿ”‹%d%%%s", $batlevel, $batcharging), color => color_thresholds(100 - $batlevel, 70, 90) }; } ); # my %mod_audio_linux = ( # name => 'AUDIO', # period => 2, # compute => sub { # return { text => sprintf "%s%s", # pango_onoff('๐Ÿ”Š', do {`volctl vol-on`; $? == 0}), # pango_onoff('๐ŸŽค', do {`volctl mic-on`; $? == 0}) # }; # } # ); my %mod_audio_openbsd = ( name => 'AUDIO', period => 2, compute => sub { my $audioinfo = `sndioctl`; my ($muted) = $audioinfo =~ /output\.mute=([01])/; my ($micmuted) = $audioinfo =~ /input\.mute=([01])/; return { text => sprintf "%s %s", txt_onoff('๐Ÿ”Š', $muted == 0), txt_onoff('๐ŸŽค', $micmuted == 0) }; } ); my %mod_date = ( name => 'DATE', period => 2, compute => sub { my $date = `date +'%Y-%m-%d %H:%M'`; chomp $date; return { text => $date }; } ); ################################################################################ # Main my @mods = (\%mod_cpu_openbsd, \%mod_mem_openbsd, \%mod_net_openbsd, \%mod_bat_openbsd, \%mod_audio_openbsd, \%mod_date); my %cache; my $profile = 0; foreach my $arg (@ARGV) { if ($arg eq '--profile') { $profile = 1; } } printf("{ \"version\": 1 }\n[\n"); sub render_section($result) { my $color = $result->{color}; my $color = $color ? ",\"$color->{type}\":\"$color->{val}\"" : ''; return sprintf "{\"full_text\":\"%s\",\"separator\":false$color}", $result->{text}; } my $counter = 0; while (1) { my @sections; my (undef, $time0) = gettimeofday() if $profile; foreach my $mod (@mods) { my (undef, $time) = gettimeofday() if $profile; my $result; if ($counter % $mod->{period} == 0) { $result = &{$mod->{compute}}; $cache{$mod->{name}} = $result; } else { $result = $cache{$mod->{name}}; } push(@sections, render_section($result)); show_time_delta($mod->{name}, $time, $counter % $mod->{period} == 0) if $profile; } show_time_delta('TOTAL', $time0, 0) if $profile; printf("[%s],\n", join(',{"full_text":"ยท","separator":false},', @sections)); $counter += 1; sleep 1; } # vim: set sw=4