--- 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 <address@hidden>
-#
-# ACPI support originally written by Waider (www.waider.ie), modified to work
-# with PerlPanel/BatteryMonitor by Eric Andreychek.
-# 
+# Rewritten by David Lazar <address@hidden>.
 
 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 (<INFO>) {
-		$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 (<FILE>) {
-		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 <FILE>;
+	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 = <APM>;
-        	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 = <ACPI>;
-                $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 = <BATT>;
-        next unless $bits[0] =~ /yes/;
-
-        close( BATT );
-
-        open( BATT, "/proc/acpi/battery/$battery/state" );
-        push @bits, <BATT>;
-        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__
-
