--- PerlPanel.orig/lib/PerlPanel/Applet/BatteryMonitor.pm 2005-04-02 19:15:38.000000000 +0200
+++ PerlPanel/lib/PerlPanel/Applet/BatteryMonitor.pm 2011-05-07 21:28:27.000000000 +0200
@@ -16,21 +16,18 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Copyright: (C) 2003-2005 Eric Andreychek
-#
-# ACPI support originally written by Waider (www.waider.ie), modified to work
-# with PerlPanel/BatteryMonitor by Eric Andreychek.
-#
+# Rewritten by David Lazar .
package PerlPanel::Applet::BatteryMonitor;
use strict;
-$PerlPanel::Applet::BatteryMonitor::VERSION = 0.05;
+our $VERSION = 1.0;
sub new {
- my $self = {};
- $self->{package} = shift;
- bless($self, $self->{package});
- return $self;
+ my $class = shift;
+ my $self = {};
+ $self->{package} = $class;
+ return bless($self, $class);
}
sub configure {
@@ -41,67 +38,52 @@
$self->{widget}->add($self->{label});
$self->{config} = PerlPanel::get_config('BatteryMonitor');
PerlPanel::tips->set_tip($self->{widget}, 'Battery Monitor');
- $self->widget->show_all;
- $self->update;
- PerlPanel::add_timeout($self->{config}->{interval}, sub { $self->update });
+ $self->{monitor} = Monitor::create();
+ $self->{widget}->show_all();
+ $self->update();
+ PerlPanel::add_timeout($self->{config}->{interval}, sub { $self->update() });
return 1;
}
+our %STATUSES = (
+ 0 => {
+ 'symbol' => '*',
+ 'message' => 'The system is running on battery power.',
+ },
+ 1 => {
+ 'symbol' => '|',
+ 'message' => 'The system is running on AC power.',
+ },
+ 2 => {
+ 'symbol' => '-',
+ 'message' => 'The system is running on backup power.',
+ },
+);
+our $UNKNOWN_STATUS = {
+ 'symbol' => '!',
+ 'message' => 'Unknown status',
+};
+
+
sub update {
my $self = shift;
- my ($ac_status, $charge) = (-1);
- if (-r "/proc/pmu/info") {
- my $bat_count;
- ($bat_count, $ac_status) = parse_pmu_info();
- for my $cur_bat_num (0 .. ($bat_count-1)) {
- my $bat_status = parse_pmu_bat($cur_bat_num);
- my $bat_charge = $bat_status->{charge} / $bat_status->{max_charge} * 100;
- $bat_charge = int ($bat_charge + 0.5);
- $charge = ($charge || 0) + $bat_charge/$bat_count;
- }
-
- $charge .= "%";
- } else {
- eval {
- my $apm = Sys::Apm_ACPI->new;
- if (defined($apm)) {
- $ac_status = $apm->ac_status;
- $charge = $apm->charge;
- }
- else {
- return undef;
- }
- };
- }
-
- my $status_symbol;
- if ( $ac_status == 1 ) {
- $status_symbol = " | ";
- PerlPanel::tips->set_tip($self->{widget},
- _('The system is running on AC power'));
- }
- elsif ( $ac_status == 0 ) {
- $status_symbol = " * ";
- PerlPanel::tips->set_tip($self->{widget},
- _('The system is running on battery power'));
- }
- elsif ( $ac_status == 2 ) {
- $status_symbol = " - ";
- PerlPanel::tips->set_tip($self->{widget},
- _('The system is running on backup power'));
- }
- else {
- $status_symbol = " ? ";
- PerlPanel::tips->set_tip($self->{widget},
- _('Unknown status'));
- }
- $self->{label}->set_text($charge . $status_symbol );
+ $self->{monitor}->refresh();
+ my $ac_status = $self->{monitor}->get_ac_status();
+ my $charge = $self->{monitor}->get_charge_percent();
+
+ my $status = $STATUSES{$ac_status};
+ $status = $UNKNOWN_STATUS unless (defined $status);
+
+ PerlPanel::tips->set_tip($self->{widget}, _($status->{message}));
+ $self->{label}->set_text(sprintf(' %d%% %s ', int($charge + 0.5), $status->{symbol}));
+
return 1;
}
sub widget {
- return $_[0]->{widget};
+ my $self = shift;
+ return $self->{widget};
}
sub expand {
@@ -117,245 +99,371 @@
}
sub get_default_config {
- return { interval => 1000 }
+ return { 'interval' => 1000 };
}
-1;
-sub parse_pmu_info {
- my ($bat_num, $on_ac);
-
- open (INFO, "<", "/proc/pmu/info") || die ("Couldn't open /proc/pmu/info: $!");
- while () {
- $bat_num = $1 if (/^Battery count\s*:\s*(\d+)\s*$/i);
- $on_ac = $1 if (/^AC Power\s*:\s*(\d+)\s*$/i);
+# Monitor is an abstract base class of the various battery monitor
+# classes.
+#
+# External users should call Monitor::create() to get a Monitor instance
+# (the exact subclass is determined at runtime). On the monitor
+# instance, the refresh() method should be called to read the battery
+# data from the kernel. After refresh() has been called,
+# get_ac_status() and get_charge_percent() can be used to query the
+# power supply state.
+package Monitor;
+
+our $AC_UNKNOWN = -1;
+our $AC_OFFLINE = 0;
+our $AC_ONLINE = 1;
+our $AC_BACKUP = 2;
+
+# Returns a Monitor object. The exact type of the returned object is
+# determined at run-time, based on the detected kernel functionality.
+sub create {
+ return SysfsAcpiMonitor->new() if SysfsAcpiMonitor::is_supported();
+ return ProcfsAcpiMonitor->new() if ProcfsAcpiMonitor::is_supported();
+ return PmuMonitor->new() if PmuMonitor::is_supported();
+ return ApmMonitor->new() if ApmMonitor::is_supported();
+ return Monitor->new();
+}
+
+sub new {
+ my $class = shift;
+ my $self = {};
+ $self->{ac_status} = $AC_UNKNOWN;
+ $self->{batteries} = [];
+ return bless($self, $class);
+}
+
+# Returns the status of the AC adapter. Possible values are:
+# $AC_ONLINE, $AC_OFFLINE, $AC_BACKUP or $AC_UNKNOWN
+sub get_ac_status {
+ my $self = shift;
+ return $self->{ac_status};
+}
+
+# Returns the remaining battery charge, as a percentage of the maximum
+# charge.
+sub get_charge_percent {
+ my $self = shift;
+
+ # It appears that different batteries report their current charge
+ # either in terms of electrical charge (mAh) or in terms of energy
+ # (mWh). This code doesn't really care about the units (since it only
+ # computes percentages), but it does assume that all batteries in the
+ # system report their status in the same measurement unit.
+
+ my ($total_cur_charge, $total_max_charge) = (0, 0);
+ for my $battery (@{$self->{batteries}}) {
+ $total_cur_charge += $battery->[0];
+ $total_max_charge += $battery->[1];
}
- close INFO;
+ return 0 if ($total_max_charge == 0);
- $on_ac = "?" if not defined($on_ac);
+ return $total_cur_charge * 100 / $total_max_charge;
+}
+
+# This method should be overridden by the subclasses, to read the
+# battery data from the kernel, and to populate the internal fields:
+#
+# self->{ac_status}:
+# one of $AC_ONLINE, $AC_OFFLINE, $AC_BACKUP or $AC_UNKNOWN
+#
+# self->{batteries}:
+# a reference to a list of pairs (current_charge, max_charge)
+sub refresh {
+}
- return ($bat_num || 0, $on_ac);
+# Clears the internal state of the monitor.
+sub _clear {
+ my $self = shift;
+ $self->{ac_status} = $AC_UNKNOWN;
+ $self->{batteries} = [];
}
-sub parse_pmu_bat {
- my $bat_num = shift;
- my $file = "/proc/pmu/battery_$bat_num";
- my %status;
+# Parses a file looking for regexps, and uses them to extract values
+# matched by groups in the regexps.
+#
+# The routine takes as arguments the name of the file to parse, and a
+# reference to an array of matchers. Each matcher is a tuple that
+# contains a reference to a variable (where the output value will be
+# stored) and a regexp. Each regexp should contain one group, which
+# will be extracted and stored into the referenced value.
+#
+# Returns true on success.
+sub _extract_values {
+ my $file = shift;
+ my $matchers = shift;
- open (FILE, "<", $file) || die ("Couldn't open $file: $!");
+ open(FILE, "<", $file) or return 0;
while () {
- if (/^(\S+)\s*:\s*(.+)$/) {
- $status{lc($1)} = $2;
+ for my $matcher (@$matchers) {
+ ${$matcher->[0]} = $1 if (/$matcher->[1]/);
}
}
- close FILE;
+ close(FILE) or return 0;
- return \%status;
+ return 1;
}
-package Sys::Apm_ACPI;
+# Returns the full content of a file. Removes the final newline, if it
+# is present.
+sub _read_string {
+ my $file = shift;
-# Sys::Apm - Perl extension for APM
+ open(FILE, "<", $file) or return undef;
+ my $content = scalar ;
+ chomp($content);
+ close(FILE) or return undef;
-# Copyright 2003 by Raoul Zwart
+ return $content;
+}
-# This library is free software; you can redistribute it and/or modify
-# it under the same terms as Perl itself.
-use strict;
-use warnings;
+# SysfsAcpiMonitor uses the sysfs ACPI interface for getting information
+# about the power supply from the kernel.
+package SysfsAcpiMonitor;
+use base 'Monitor';
-our $VERSION = 0.20;
+our $ACPI_DIR = "/sys/class/power_supply";
-sub new {
- my $class = shift;
- my $self = {};
- bless ( $self, $class );
- $self->fetch;
- return $self;
+sub is_supported {
+ return 0 unless (-d $ACPI_DIR);
+ return 1;
}
-sub fetch {
+sub refresh {
my $self = shift;
+ $self->_clear();
- my $a;
- if ( open( APM, "/proc/apm" )) {
- $a = ;
- close( APM );
- } elsif ( -d "/proc/acpi/battery" ) {
- $a = acpi_to_apm();
+ $self->{ac_status} = _get_ac_status();
+ for my $battery (_list_batteries()) {
+ push(@{$self->{batteries}}, _get_battery_charge($battery));
}
- chomp($a);
+}
+
+sub _get_ac_status {
+ my $ac_status = $Monitor::AC_UNKNOWN;
+
+ for my $ac_adapter (_list_ac_adapters()) {
+ next unless (-f "$ac_adapter/online");
+
+ my $state = _read_int("$ac_adapter/online");
- unless ($a) {
- warn "Error: APM or ACPI support not detected";
- return 0;
+ return $Monitor::AC_ONLINE if ($state eq "1");
+ $ac_status = $Monitor::AC_OFFLINE if ($state eq "0");
}
- $self->parse($a);
+
+ return $ac_status;
}
-sub parse {
- my $self = shift;
- my $str = shift;
- $self->{data}=[split / /, $str];
+sub _list_ac_adapters {
+ return grep(_is_ac_adapter($_), <$ACPI_DIR/*>);
}
-sub driver_version {
- my $self = shift;
- $self->{data}[0];
+sub _is_ac_adapter {
+ my $dir = shift;
+ return 0 unless (-f "$dir/type");
+ return 0 unless (Monitor::_read_string("$dir/type") eq "Mains");
+ return 1;
}
-sub bios_version {
- my $self = shift;
- $self->{data}[1];
+sub _list_batteries {
+ return grep(_is_battery($_), <$ACPI_DIR/*>);
}
-sub ac_status {
- my $self = shift;
- hex($self->{data}[3]);
+sub _is_battery {
+ my $dir = shift;
+ return 0 unless (-f "$dir/type");
+ return 0 unless (Monitor::_read_string("$dir/type") eq "Battery");
+ return 0 unless (-f "$dir/present");
+ return 0 unless (_read_int("$dir/present"));
+ return 1;
}
-sub battery_status {
- my $self = shift;
- hex($self->{data}[4]);
+sub _get_battery_charge {
+ my $dir = shift;
+
+ if (-f "$dir/charge_now" && -f "$dir/charge_full") {
+ return [ _read_int("$dir/charge_now"),
+ _read_int("$dir/charge_full") ];
+ }
+
+ if (-f "$dir/energy_now" && -f "$dir/energy_full") {
+ return [ _read_int("$dir/energy_now"),
+ _read_int("$dir/energy_full") ];
+ }
+
+ return [0, 0];
}
-sub charge {
- my $self = shift;
- substr($self->{data}[6],-1,1) eq "%" ? $self->{data}[6] :
- $self->{data}[6] . '%';
+sub _read_int {
+ my $file = shift;
+ return int($1) if (Monitor::_read_string($file) =~ /^(\d+)$/);
+ return 0;
}
-sub remaining {
+
+# ProcfsAcpiMonitor uses the procfs ACPI interface for getting
+# information about the power supply from the kernel.
+package ProcfsAcpiMonitor;
+use base 'Monitor';
+
+our $ACPI_AC_DIR = "/proc/acpi/ac_adapter";
+our $ACPI_BATTERY_DIR = "/proc/acpi/battery";
+
+sub is_supported {
+ return 0 unless (-d "$ACPI_AC_DIR");
+ return 1;
+}
+
+sub refresh {
my $self = shift;
- $self->{data}[7];
+ $self->_clear();
+
+ $self->{ac_status} = _get_ac_status();
+ for my $battery (_list_batteries()) {
+ push(@{$self->{batteries}}, _get_battery_charge($battery));
+ }
+}
+
+sub _get_ac_status {
+ my $ac_status = $Monitor::AC_UNKNOWN;
+
+ for my $ac_adapter (_list_ac_adapters()) {
+ next unless (-f "$ac_adapter/state");
+
+ my $state = "unknown";
+ Monitor::_extract_values("$ac_adapter/state", [
+ [ \$state, qr/^state\s*:\s*(\S+)\s*$/i ],
+ ]) or return $Monitor::AC_UNKNOWN;
+
+ return $Monitor::AC_ONLINE if (lc($state) eq "on-line");
+ $ac_status = $Monitor::AC_OFFLINE if (lc($state) eq "off-line");
+ }
+
+ return $ac_status;
+}
+
+sub _list_ac_adapters {
+ return grep(_is_ac_adapter($_), <$ACPI_AC_DIR/*>);
+}
+
+sub _is_ac_adapter {
+ my $dir = shift;
+ return 0 unless (-f "$dir/state");
+ return 1;
+}
+
+sub _list_batteries {
+ return grep(_is_battery($_), <$ACPI_BATTERY_DIR/*>);
+}
+
+sub _is_battery {
+ my $dir = shift;
+ return 0 unless (-f "$dir/info");
+ return 0 unless (-f "$dir/state");
+ return 1;
+}
+
+sub _get_battery_charge {
+ my $dir = shift;
+ my ($present, $cur_charge, $max_charge) = (0, 0, 0);
+
+ Monitor::_extract_values("$dir/state", [
+ [ \$present, qr/^present\s*:\s*(\S+)\s*$/i ],
+ [ \$cur_charge, qr/^remaining capacity\s*:\s*(\d+)\D*$/i ],
+ ]) or return [0, 0];
+
+ return [0, 0] unless (lc($present) eq "yes");
+
+ Monitor::_extract_values("$dir/info", [
+ [ \$max_charge, qr/^last full capacity\s*:\s*(\d+)\D*$/i ],
+ ]) or return [0, 0];
+
+ return [ int($cur_charge), int($max_charge) ];
}
-sub units {
+
+# PmuMonitor uses the /proc/pmu interface for getting information about
+# the power supply from the kernel.
+package PmuMonitor;
+use base 'Monitor';
+
+our $PMU_INFO_FILE = "/proc/pmu/info";
+our $PMU_BATTERY_FILE = "/proc/pmu/battery_%d";
+
+sub is_supported {
+ return 0 unless (-f $PMU_INFO_FILE);
+ return 1;
+}
+
+sub refresh {
my $self = shift;
- $self->{data}[8];
+ $self->_clear();
+
+ my $num_batteries = 0;
+ Monitor::_extract_values($PMU_INFO_FILE, [
+ [ \$self->{ac_status}, qr/^AC Power\s*:\s*(\d+)\s*$/i ],
+ [ \$num_batteries, qr/^Battery count\s*:\s*(\d+)\s*$/i ],
+ ]) or return;
+
+ for my $battery_index (0 .. $num_batteries-1) {
+ push(@{$self->{batteries}}, _get_battery_charge($battery_index));
+ }
}
-# This sub written by Waider
-# Waider 26/09/2000
-# http://www.waider.ie/hacks/workshop/perl/Monitor/APM.pm
-sub acpi_to_apm {
- # Convert the info in /proc/acpi/battery/* into an APM string. Loses info, but screw that!
- my $apm;
- my ( $drvver, $biosver, $flags, $acstat, $btstat, $btflag, $btpercent, $bttime, $bttime_unit )
- = ( "1.4", "1.1", 0, 0, 0, 0, -1, -1, "?" );
-
- # here's the output for a battery:
- # present: yes
- # design capacity: 54719 mWh
- # last full capacity: 53913 mWh
- # battery technology: rechargeable
- # design voltage: 14399 mV
- # design capacity warning: 5391 mWh
- # design capacity low: 3235 mWh
- # capacity granularity 1: 2 mWh
- # capacity granularity 2: 2 mWh
- # model number: Primary
- # serial number: 1FA50011
- # battery type: LIon
- # OEM info: COMPAQ
- # present: yes
- # capacity state: ok
- # charging state: unknown
- # present rate: 0 mW
- # remaining capacity: 52530 mWh
- # present voltage: 16875 mV
- #
- # when not on mains, charging state => discharging and present rate => rate of discharge
-
- # get the ac adapter state for acstat
- if ( opendir( DIR, "/proc/acpi/ac_adapter" )) {
- for my $dir ( grep !/^\.\.?$/, readdir( DIR )) {
- if ( open( ACPI, "/proc/acpi/ac_adapter/$dir/state" )) {
- my $state = ;
- $acstat |= 0x1 if $state =~ /on-line/;
- close( ACPI );
- } else {
- warn "Error: Failed to get AC $dir state: $!\n";
- return 0;
- }
- }
- }
-
- opendir( DIR, "/proc/acpi/battery" );
- my @batteries = grep !/^\.\.?$/, readdir( DIR );
- closedir( DIR );
-
- my ( $max, $lev, $low, $crit, $rate ) = ( 0, 0, 0, 0, 0 );
-
- for my $battery ( @batteries ) {
- open( BATT, "/proc/acpi/battery/$battery/info" );
- my @bits = ;
- next unless $bits[0] =~ /yes/;
-
- close( BATT );
-
- open( BATT, "/proc/acpi/battery/$battery/state" );
- push @bits, ;
- close( BATT );
-
- for my $bits ( @bits, @bits ) { # stupidity!
- chomp( $bits );
- my ( $field, $value ) = split( /:\s*/, $bits, 2 );
- $value =~ s/\s+$//;
- if ( $field eq "last full capacity" ) { #"design capacity" ) {
- ( $max ) = $value =~ /(\d+)/;
- } elsif ( $field eq "remaining capacity" ) {
- ( $lev ) = $value =~ /(\d+)/;
- } elsif ( $field eq "design capacity warning" ) {
- ( $low ) = $value =~ /(\d+)/;
- } elsif ( $field eq "design capacity low" ) {
- ( $crit ) = $value =~ /(\d+)/;
- } elsif ( $field eq "charging state" ) {
- if ( $value eq "unknown" ) {
- $btstat = 0xff;
- $btflag = 0xff;
- } elsif ( $value eq "discharging" ) {
- if ( $lev ) {
- if ( $lev > $low ) {
- $btflag = 0x00;
- $btstat |= 0x1;
- } elsif ( $lev > $crit ) {
- $btflag = 0x01;
- $btstat |= 0x2;
- } else {
- $btflag = 0x02;
- $btstat |= 0x4;
- }
- }
- } elsif ( $value eq "charging" ) {
- $btstat = 0x03;
- $btflag |= 0x8;
- $acstat |= 0x1; # xxx check power_resource
- } elsif ( $value eq "charged" ) {
- $btstat = 0x00;
- $btflag |= 0x1;
- $acstat |= 0x1;
- }
- } elsif ( $field eq "present rate" ) {
- ( $rate ) = $value =~ /(\d+)/;
- }
- }
- last; # XXX
- }
-
- $btpercent = sprintf( "%02d", $lev / $max * 100 ) if ( $max );
-
- if ( $rate ) {
- $bttime_unit = "min";
- $bttime = ( $lev - $crit ) / $rate * 60;
- }
+sub _get_battery_charge {
+ my $battery_index = shift;
+
+ my ($cur_charge, $max_charge) = (0, 0);
+ Monitor::_extract_values(sprintf($PMU_BATTERY_FILE, $battery_index), [
+ [ \$cur_charge, qr/^charge\s*:\s*(\d+)\s*$/i ],
+ [ \$max_charge, qr/^max_charge\s*:\s*(\d+)\s*$/i ],
+ ]) or return [0, 0];
+
+ return [ int($cur_charge), int($max_charge) ];
+}
+
+
+# ApmMonitor uses the /proc/apm interface for getting information about
+# the power supply from the kernel.
+package ApmMonitor;
+use base 'Monitor';
+
+our $APM_FILE = "/proc/apm";
+our $APM_RE = qr/^\S+ \d+\.\d+ 0x[0-9A-Fa-f]{2} 0x([0-9A-Fa-f]{2}) 0x([0-9A-Fa-f]{2}) 0x[0-9A-Fa-f]{2} (\d+)% -?\d+ \S+$/;
- $apm = sprintf( "%s %s 0x%02x 0x%02x 0x%02x 0x%02x %s %d %s", $drvver, $biosver, $flags, $acstat, $btstat, $btflag, $btpercent, $bttime, $bttime_unit );
+sub is_supported {
+ return 0 unless (-f $APM_FILE);
+ return 1;
+}
+
+sub refresh {
+ my $self = shift;
+ $self->_clear();
- return $apm;
+ if (Monitor::_read_string($APM_FILE) =~ /$APM_RE/) {
+ # AC line status can be one of the following:
+ # 0x00: Off-line
+ # 0x01: On-line
+ # 0x02: On backup power (BIOS >= 1.1 only)
+ # 0xff: Unknown
+ $self->{ac_status} = hex($1);
+
+ # Battery status can be one of the following values:
+ # 0x00: High
+ # 0x01: Low
+ # 0x02: Critical
+ # 0x03: Charging
+ # 0x04: Selected battery not present (BIOS >= 1.2 only)
+ if (hex($2) < 4) {
+ push(@{$self->{batteries}}, [ int($3), 100 ]);
+ }
+ }
}
1;
__END__
-