#!/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' ), }; } chomp(my $brightness = `backlight get`); splice @$line, -1, 0, { name => 'brightness', full_text => "\N{WHITE SUN WITH RAYS} $brightness%", color => '#FFFFFF' }; chomp(my $volume = `volume get`); if ($volume eq 'mute') { splice @$line, -1, 0, { name => 'volume', full_text => "\N{SPEAKER}", color => '#FFFFFF' }; } else { splice @$line, -1, 0, { name => 'volume', full_text => "\N{SPEAKER WITH THREE SOUND WAVES} $volume%", color => '#FFFFFF' }; } $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> } }