--- 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__ -