summaryrefslogtreecommitdiffstats
path: root/bin/hornet/status
diff options
context:
space:
mode:
Diffstat (limited to 'bin/hornet/status')
-rwxr-xr-xbin/hornet/status251
1 files changed, 251 insertions, 0 deletions
diff --git a/bin/hornet/status b/bin/hornet/status
new file mode 100755
index 0000000..d068a17
--- /dev/null
+++ b/bin/hornet/status
@@ -0,0 +1,251 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use 5.010;
+
+use IO::Select;
+use IO::Socket::UNIX;
+use JSON::PP;
+
+my $i3status = get_i3status_handle();
+my $ipc = get_ipc_handle();
+$|++;
+say "[]";
+
+my $select = IO::Select->new;
+$select->add($i3status);
+$select->add($ipc);
+
+my $current_data;
+
+while (my @ready = $select->can_read) {
+ for my $fh (@ready) {
+ if ($fh == $i3status) {
+ handle_i3status(scalar <$fh>);
+ }
+ elsif ($fh == $ipc) {
+ handle_ipc(receive_ipc_message($fh));
+ }
+ else {
+ die "???";
+ }
+ }
+}
+
+sub get_i3status_handle {
+ open my $i3status, '-|', 'i3status';
+
+ print scalar(<$i3status>);
+ print scalar(<$i3status>);
+
+ return $i3status;
+}
+
+sub handle_i3status {
+ $_ = shift;
+ s/^,//;
+
+ my $data = decode_json($_);
+
+ my %line = map { $_->{name} => $_ } @$data;
+ my $line = [
+ grep {
+ $_->{name} eq 'wireless' || $_->{name} eq 'ethernet'
+ ? $_->{instance} ne $_->{full_text}
+ : $_->{name} ne 'load'
+ } @$data
+ ];
+
+ my %discharge_rate;
+ my %battery_index;
+
+ my $index = 0;
+ for my $item (@$line) {
+ if ($item->{name} eq 'wireless' || $item->{name} eq 'ethernet') {
+ my $iface = $item->{instance};
+ my ($up, $down) = map { net_rate($iface, $_) } 'rx', 'tx';
+ my $rate = sprintf("%6s/%6s", map { human_bytes($_) } $up, $down);
+ $item->{full_text} =~ s{^($iface:)}{$1 $rate};
+ $item->{color} =
+ $up + $down >= 512000 ? '#FF0000'
+ : $up + $down >= 51200 ? '#FFFF00'
+ : $up + $down >= 5120 ? '#00FF00'
+ : '#FFFFFF';
+ }
+ if ($item->{name} eq 'cpu_usage') {
+ my ($val) = $line{load}{full_text} * 100 / ncpu();
+ $item->{color} =
+ $val >= 50 ? '#FF0000'
+ : $val >= 25 ? '#FFFF00'
+ : $val >= 5 ? '#00FF00'
+ : '#FFFFFF';
+ }
+ if ($item->{name} eq 'battery') {
+ my ($state, $val) = $item->{full_text} =~ /(\w+) (\d+(?:\.\d+))%/;
+ my ($rate) = $item->{full_text} =~ /\(.* (.*)W\)/;
+ $rate *= -1 if defined($rate) && $state ne 'BAT';
+ $rate //= 0;
+
+ $discharge_rate{$item->{instance}} = $rate;
+ $battery_index{$item->{instance}} = $index;
+
+ $item->{full_text} =~ s/\((.*) (.*)W\)/\($1\)/;
+ $item->{full_text} =~ s/ \(\)//;
+ $item->{color} =
+ !defined($val) ? '#FFFFFF'
+ : $val >= 80 ? '#00FF00'
+ : $state ne 'BAT' ? '#FFFFFF'
+ : $val >= 40 ? '#FFFFFF'
+ : $val >= 15 ? '#FFFF00'
+ : $val >= 5 ? '#CD0000'
+ : '#FF0000';
+ }
+ $index++;
+ }
+
+ my @batteries = sort { $battery_index{$b} <=> $battery_index{$a} }
+ keys %battery_index;
+ for my $battery (@batteries) {
+ next unless $discharge_rate{$battery};
+ splice @$line, $battery_index{$battery} + 1, 0, {
+ name => 'battery_discharge',
+ full_text => sprintf("%0.2fW", $discharge_rate{$battery}),
+ color => (
+ $discharge_rate{$battery} < 6 ? '#FFFFFF'
+ : $discharge_rate{$battery} < 10 ? '#00FF00'
+ : $discharge_rate{$battery} < 14 ? '#FFFF00'
+ : '#FF0000'
+ ),
+ };
+ }
+
+ my $weather_file = "$ENV{HOME}/.cache/weather";
+ if (-r $weather_file && -f $weather_file) {
+ my $weather = slurp($weather_file);
+ chomp($weather);
+ my ($temp) = $weather =~ /\d+%.*?(-?\d+)C.*?-?\d+C/;
+ unshift @$line, {
+ name => 'weather',
+ full_text => $weather,
+ color => (
+ $temp >= 30 ? '#FF0000'
+ : $temp >= 25 ? '#FFFF00'
+ : $temp >= 20 ? '#00FF00'
+ : $temp >= 10 ? '#FFFFFF'
+ : $temp >= 0 ? '#CDCDFF'
+ : '#0000FF'
+ ),
+ };
+ }
+
+ $current_data = $line;
+
+ show_status();
+}
+
+sub get_ipc_handle {
+ chomp(my $path = qx(i3 --get-socketpath));
+
+ my $sock = IO::Socket::UNIX->new(Peer => $path);
+ $sock->write(format_ipc_message(2, q{["window", "workspace"]}));
+ my ($type, $payload) = receive_ipc_message($sock);
+ die "Couldn't subscribe: ($type) $payload"
+ unless $type == 2 && decode_json($payload)->{success};
+
+ return $sock;
+}
+
+sub handle_ipc {
+ my ($type, $payload) = @_;
+ show_status();
+}
+
+sub format_ipc_message {
+ my ($type, $payload) = @_;
+ my $len = do { use bytes; length($payload) };
+ return "i3-ipc" . pack("LL", $len, $type) . $payload;
+}
+
+sub receive_ipc_message {
+ my ($sock) = @_;
+
+ $sock->read(my $magic, 6);
+ die "unknown response: $magic" unless $magic eq 'i3-ipc';
+ $sock->read(my $len, 4);
+ $len = unpack("L", $len);
+ $sock->read(my $type, 4);
+ $type = unpack("L", $type);
+ $sock->read(my $payload, $len);
+
+ return ($type, $payload);
+}
+
+sub show_status {
+ my @line = @$current_data;
+ unshift @line, { name => 'title', full_text => get_title() };
+ say "," . encode_json(\@line);
+}
+
+sub get_title {
+ my $node = find(decode_json(`i3-msg -t get_tree`));
+ if ($node->{window}) {
+ return $node->{name};
+ }
+ else {
+ return '-none-';
+ }
+}
+
+sub find {
+ my ($t) = @_;
+ if ($t->{focused}) {
+ return $t;
+ }
+
+ for my $subtree (@{ $t->{nodes} }, @{ $t->{floating_nodes} }) {
+ my $found = find($subtree);
+ return $found if $found;
+ }
+
+ return;
+}
+
+{
+ my %last_bytes;
+ sub net_rate {
+ my ($iface, $which) = @_;
+ my $stat_file = "/sys/class/net/$iface/statistics/${which}_bytes";
+ chomp(my $bytes = slurp($stat_file));
+ my $prev = $last_bytes{$iface}{$which};
+ $last_bytes{$iface}{$which} = $bytes;
+ return 0 unless defined $prev;
+ return $bytes - $prev;
+ }
+}
+
+sub ncpu {
+ scalar grep { !/^#/ } `lscpu -p=cpu`
+}
+
+sub human_bytes {
+ my ($bytes) = @_;
+ my @prefixes = ('', qw(k M G T));
+ my $prefix_index = 0;
+ while ($bytes > 512) {
+ $bytes /= 1024;
+ $prefix_index++;
+ }
+ my $prec = ($prefix_index == 0 ? 0 : 1);
+ return sprintf("%0.${prec}f%sB", $bytes, $prefixes[$prefix_index]);
+}
+
+sub slurp {
+ open my $fh, '<:encoding(UTF-8)', $_[0] or die "Couldn't open $_[0]: $!";
+ if (wantarray) {
+ <$fh>
+ }
+ else {
+ local $/;
+ <$fh>
+ }
+}