#!/usr/bin/perl use warnings FATAL => 'all'; use strict; use Data::Dumper; # turn on autoflush $| = 1; sub booleq { my ($s) = @_; return sub { return $_[0] eq $s }; } sub boolne { my ($s) = @_; return sub { return $_[0] ne $s }; } sub numwh { my ($s) = @_; my ($n) = $s =~ m/^(.*) Wh$/; return $n; } sub htime { my ($s) = @_; if ($s =~ m/^([0-9.]+) hours$/) { return int($1 * 60); } elsif ($s =~ m/^([0-9.]+) minutes$/) { return int($1); } else { die "unknown time: $s"; } } sub stime { my ($n) = @_; my $h = $n / 60; my $m = $n % 60; return sprintf("%02d:%02d", $h, $m); } sub perc { my ($n, $d) = @_; return sprintf("%.1f%%", $n * 100 / $d); } sub charge_time { my ($data) = @_; return "--:--" if $data->{full} || $data->{tcharge} == 0; return stime($data->{tcharge}); } sub drain_time { my ($data) = @_; return "--:--" if $data->{tdrain} == 0; return stime($data->{tdrain}); } sub main { if (@ARGV) { my $t = $ARGV[0]; # require a positive number of seconds (e.g. 2, 4.5, 0.2, ...) unless ($t =~ m/^([1-9][0-9]*(\.[0-9]+)?|0\.[0-9]*[1-9][0-9]*)$/) { print "usage: $0 [POLL-SECS]\n"; exit(1); } do { run(); select(undef, undef, undef, $t); } while (999); } else { run(); } } sub run { my $data = init(); #print Dumper($data) . "\n"; display1($data, 5); } sub mac_meta { return ( "ExternalConnected" => [["connected", booleq("Yes")]], "AvgTimeToEmpty" => [["tdrain", \&int]], "AvgTimeToFull" => [["tcharge", \&int]], "MaxCapacity" => [["max", \&int]], "CurrentCapacity" => [["cur", \&int]], "FullyCharged" => [["full", booleq("Yes")]], "IsCharging" => [["charging", booleq("Yes")]], ); } sub linux_meta { return ( "energy" => [["cur", \&numwh]], "energy-full" => [["max", \&numwh]], "time to empty" => [["tdrain", \&htime]], "time to full" => [["tcharge", \&htime]], "state" => [ ["connected", boolne("discharging")], ["full", booleq("fully-charged")], ], ) } my $is_linux = $^O eq "linux"; my %meta = $is_linux ? linux_meta() : mac_meta(); sub mac_cmd { return "ioreg -rc AppleSmartBattery"; } sub mac_re { return qr/^ *"(\w+)" = (.+)$/; } sub linux_cmd { return "upower -i `upower -e | grep battery`"; } sub linux_re { return qr/^ *([^:]+): +(.+)$/; } sub init { my $data = {}; $data->{tcharge} = 0; my $cmd = $is_linux ? linux_cmd() : mac_cmd(); open(my $fh, "-|", $cmd); my $re = $is_linux ? linux_re() : mac_re(); while (my $line = <$fh>) { my ($key, $val) = $line =~ m/$re/; next unless $key && $val; my $r = $meta{$key}; next unless $r; for my $p (@$r) { $data->{$p->[0]} = $p->[1]->($val); } } return $data; } sub icon { my ($data) = @_; return $data->{full} ? "☀" : "⚡" if $data->{connected}; return $data->{tdrain} < 30 ? "⚠" : "⌸"; } sub display1 { my ($data, $n) = @_; my $c = $data->{connected}; my $p = perc($data->{cur}, $data->{max}); my $icon = icon($data); my $f = $c ? \&charge_time : \&drain_time; my $t = $f->($data); printf("$icon %s (%s)\n", $t, $p); } main();