# -*-Perl-*-

# instances allowed: one 

# (single instance modules would be silly to use more than one of
# anyway, so we use package local storage.  This is faster and places
# less artificial load on the machine than doing everything through
# the object hash)

package MGMmodule::battery;
use IO::Seekable;
use Socket;
use vars qw(@percent @battstatus @gstate $ac $lstate $graph $widget 
	    $xpath $openp $openf $openapm $openacpi $batteries $ac $active $lastbat
	    @acpi_battery_dirs $acpi_adapter_dir);

sub module_init{
    my$this=shift;
    my$xclass=$this->{"xclass"};
    my$toplevel=$this->{"toplevel"};

    $active=0;
    $openp=0;
    $openf=0;
    $openapm=0;
    $openacpi=0;

    $this->read_proc;
    if($active==0){
	$toplevel->optionAdd("$xclass.active",        'false',21);      
    }
    $toplevel->optionAdd("$xclass.order",201,21);      
    $this;
}

sub module_instance{
    my$this=shift;
    my$toplevel=$this->{"toplevel"};
    return undef if(defined($xpath));
    $xpath=$this->{"xpath"};

    $lastbat=$batteries;

    # modify defaults
    $toplevel->optionAdd("$xpath.order",201,21); # status group
    $toplevel->optionAdd("$xpath.scalerefresh",4000,21);
    $toplevel->optionAdd("$xpath.scalereturn" ,1,21);     # don't need hyst

    if($batteries<=1){
	$toplevel->optionAdd("$xpath*label", "battery",21);
	$toplevel->optionAdd("$xpath*lowlabel", "battery low",21);
	$toplevel->optionAdd("$xpath*nonelabel", "no battery",21);
	$toplevel->optionAdd("$xpath*chargelabel", "charging",21);
	$toplevel->optionAdd("$xpath*fulllabel", "ac power",21);
    }else{
	for(my$i=0;$i<$batteries;$i++){
	    $toplevel->optionAdd("$xpath*$i.label", "battery$i",21);
	    $toplevel->optionAdd("$xpath*$i.lowlabel", "battery$i low",21);
	    $toplevel->optionAdd("$xpath*$i.nonelabel", "no battery$i",21);
	    $toplevel->optionAdd("$xpath*$i.chargelabel", "charging $i",21);
	    $toplevel->optionAdd("$xpath*$i.fulllabel", "ac power",21);
	}
    }

    $toplevel->optionAdd("$xpath.scalewidadj", 80*$batteries,21);  # narrower
    $toplevel->optionAdd("$xpath.scalelenadj", 100,21);

    my$fg=&main::moption($this,"litforeground");
    $toplevel->optionAdd("$xpath*midforeground", $fg,21);
    $toplevel->optionAdd("$xpath*litbackground", '#60c060',21);
    $toplevel->optionAdd("$xpath*midbackground", '#d0d060',21);
    $toplevel->optionAdd("$xpath*lowbackground", '#ff4040',21);
    $toplevel->optionAdd("$xpath*lowforeground", '#ffffff',21);
    $toplevel->optionAdd("$xpath*acbackground", '#808080',21);
    $toplevel->optionAdd("$xpath*acforeground", '#000000',21);
    $toplevel->optionAdd("$xpath*chargebackground", '#74ade7',21);
    $toplevel->optionAdd("$xpath*chargeforeground", '#000000',21);

    my($minx,$miny)=&MGM::Graph::calcxysize($this,100,'%',$batteries);
    $toplevel->optionAdd("$xpath.minx",        $minx,21);      
    $toplevel->optionAdd("$xpath.miny",        $miny,21);      
    $this;
}

sub module_run{
    my$this=shift;
    
    $graph=MGM::Graph->new($this,num=>$batteries,fixed=>1,rangesetting=>100,
			   rangecurrent=>100,minscale=>0,
			   prompt=>'%');

    # color change hack
    @gstate=map{0}(1...$batteries);
    $lstate=0;
    $this->module_update;
    $widget=$graph->{"widget"};        # must return the widget
}

my @pmulist=("Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "Unknown",
	     "2400/3400/3500",
	     "G3/Wallstreet",
	     "G3/Lombard",
	     "G4/NewWorld/iBook",
	     "newer than Titanium");

sub read_proc{
    my$test;
    my$this=shift;

    if($openacpi){return $this->read_proc_acpi;}
    if($openapm){return $this->read_proc_apm;}
    if($openp || $openf){return $this->read_proc_mac;}

    $test=$this->read_proc_acpi;
    if(defined($test)){return $test;}
    $test=$this->read_proc_apm;
    if(defined($test)){return $test;}
    $this->read_proc_mac;
}

# Mac style power management
sub read_proc_mac{
    my $percent;
    my $line;
    my $data;

    # recent 2.4 has gone to a /proc entry for pmu values.  Use that
    # if we can.
    if(!$openf && !$openp){
	if(open(PROC,"/proc/pmu/info")){
	    $openf=1;
	}
    }

    if($openf){
	my %hash;
	
	sysseek PROC, 0, SEEK_SET;
	sysread PROC,$data,4096;

	map{
	    my$pos=rindex $_, ":";
	    if($pos>0){
		my$key=substr $_,0,$pos;
		$key=~s/^\s*(\S+)\s(\S*)\s*/$1$2/;
		$hash{$key}=substr $_, $pos+1;
	    }
	} split "\n", $data;
	$ac=int($hash{'ACPower'});
	$batteries=int($hash{'Batterycount'});
	
	for(my$i=0;$i<$batteries;$i++){
	    if(open(FPROC,"/proc/pmu/battery_$i")){
		sysread FPROC,$data,4096;
		map{
		    my$pos=rindex $_, ":";
		    if($pos>0){
			my$key=substr $_,0,$pos;
			$key=~s/^\s*(\S+)\s*/$1/;
			$hash{$key}=substr $_, $pos+1;
		    }
		} split "\n", $data;
		close FPROC;
		if($hash{'max_charge'}>0){
		    $percent[$i]=$hash{'charge'}*100./$hash{'max_charge'}
		}else{
		    $percent[$i]=0;
		}

		$battstatus[$i]=0;
		$battstatus[$i]=1 if($hash{'current'}>0);
		$battstatus[$i]=-1 if($hash{'flags'}==0);
		$active=1;
	    }
	}
	return;
    }
  

    # get a connection to pmud if we don't have one
    if(!$openp){
	my $iaddr = inet_aton('localhost');
	my $port = 879;
	my $paddr = sockaddr_in($port,$iaddr);
	
	my $proto = getprotobyname('tcp');
	
	if(socket(B_FD, PF_INET, SOCK_STREAM, $proto)){
	    if(connect(B_FD, $paddr)){
		# pmud gives back the raw power status; the status is different
		# for different powerbooks (grumble, grumble)
		sysread B_FD,$_,1024;
		if(m/^\S+\s+\S+\s+(\S+)/){
		  SWITCH:{
		      if($1>=10 && $1<=12){
			  $active=1;
			  $openp=$1;
			  my$foo="\n";
			  syswrite B_FD,$foo,1;
			  last SWITCH;
		      }
		      print "PowerMac PMU version $1 (".
			  $pmulist[$1].
			      ") not supported yet.\n";
		      close B_FD;
		  }
		} 
	    }
	}
    }
    
    if($openp){
	if($openp>=10 && $openp<=12){
	    sysread B_FD,$line,1024;
	    $line=~m/^S\s+\{([^\}]*)\}\s+\{([^\}]*)\}/;
	    my$l=$1;
	    my$r=$2;
	    
	    my($lac,$lcharge,$lbatt,$lcurrent,$lvoltage)=
		split ' ',$l;
	    my($rac,$rcharge,$rbatt,$rcurrent,$rvoltage)=
		split ' ',$r;
	    
	    $ac=0;
	    $ac=1 if($lac>=100);
	    
	    $batteries=0;
	    if(defined($lcurrent)){
		$percent[$batteries]=($lcharge / $lbatt)*100.;
		$battstatus[$batteries]=0;
		$battstatus[$batteries]=1 if($lcurrent>0);
		
		$batteries++;
	    }
	    if(defined($rcurrent)){
		$percent[$batteries]=($rcharge / $rbatt)*100.;
		$battstatus[$batteries]=0;
		$battstatus[$batteries]=1 if($rcurrent>0);
		
		$batteries++;
	    }

	    my$foo="\n";
	    syswrite B_FD,$foo,1;
	}
    }
}

# PC APM style power management.
# /proc/apm only reports one line for all batteries
sub read_proc_apm{
    my$this=shift;
    my$percent;


    # Arguments, with symbols from linux/apm_bios.h
    #0) Linux driver version (this will change if format changes)
    #1) APM BIOS Version.  Usually 1.0 or 1.1.
    #2) APM flags from APM Installation Check (0x00):
    #      bit 0: APM_16_BIT_SUPPORT
    #      bit 1: APM_32_BIT_SUPPORT
    #      bit 2: APM_IDLE_SLOWS_CLOCK
    #      bit 3: APM_BIOS_DISABLED
    #      bit 4: APM_BIOS_DISENGAGED
    #3) AC line status
    #      0x00: Off-line
    #      0x01: On-line
    #      0x02: On backup power (APM BIOS 1.1 only)
    #      0xff: Unknown
    #4) Battery status
    #      0x00: High
    #      0x01: Low
    #      0x02: Critical
    #      0x03: Charging
    #      0xff: Unknown
    #5) Battery flag
    #      bit 0: High
    #      bit 1: Low
    #      bit 2: Critical
    #      bit 3: Charging
    #      bit 7: No system battery
    #      0xff: Unknown
    #6) Remaining battery life (percentage of charge):
    #      0-100: valid
    #      -1: Unknown
    #7) Remaining battery life (time units):
    #      Number of remaining minutes or seconds
    #      -1: Unknown
    #   8) min = minutes; sec = seconds */


    $batteries=0;
    if(open(PROC,"/proc/apm")){
	$active=1;
	$openapm=1;
	sysread PROC,$_,1024;
	if(m/^\S+\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s+([^ \%]+)\%/){
	    if($1 eq '0xff'){
		$batteries=0;
	    }else{
		$ac=1;
		$ac=0 if($1 eq '0x00');
		$batteries=1;
		$battstatus[0]=0;
		$battstatus[0]=1 if($2 eq '0x03');
		$battstatus[0]=-1 if($2 eq '0xff');
		$percent[0]=$3;
	    }
	}
	close PROC;
    }
    $percent;
}

sub list_subdirs{
    my@dirs;
    my$path=shift;
    if(opendir(DIR,$path)){
	@dirs = map ("$path/$_", grep { /^[^\.]/ && -d "$path/$_" } readdir(DIR));
	close DIR;
	return @dirs;
    }
    return;
}

# PC ACPI style power management.
sub read_proc_acpi{
    my$this=shift;
    my$percent;

    $batteries=0;

    if($openacpi==0){
	# initialize
	@acpi_battery_dirs=(list_subdirs("/proc/acpi/battery"));
	my @adapdir=(list_subdirs("/proc/acpi/ac_adapter"));
	$acpi_adapter_dir=$adapdir[0];

    }
	    
    foreach my$dir (@acpi_battery_dirs) {

	if(open(PROC,"$dir/info")){
	    if(open(PROC2,"$dir/state")){
		
		$active=1;
		$openacpi=1;
		my$total=0;
		
		$battstatus[$batteries]=-1;
		$percent[$batteries]=0;
		while(<PROC>){
		    if(m/^last full capacity:\s+(\d+)/){
			$total=$1;
		    }
		    if(m/^present:\s+yes/){
			$battstatus[$batteries] = 0 if($battstatus[$batteries]==-1);
		    }
		}
		
		while(<PROC2>){
		    if(m/^remaining capacity:\s+(\d+)/){
			$percent[$batteries]=$1*100/$total;
		    }
		    if(m/^charging state:\s+charging/){
			$battstatus[$batteries]=1;
		    }
		    if(m/^charging state:\s+discharging/){
			$battstatus[$batteries]=0;
		    }
		    if(m/^present:\s+yes/){
			$battstatus[$batteries] = 0 if($battstatus[$batteries]==-1);
		    }
		}
		close PROC2;
	    }
	    close PROC;
	    $batteries++;
	}
	
    }
    
    if($batteries && defined($acpi_adapter_dir)){
	$ac=0;
	if(open(PROC,"$acpi_adapter_dir/state")){
	    while(<PROC>){
		if(m/^state:\s+on-line/){
		    $ac=1;
		}
	    }
	    close PROC;
	}
    }

    $batteries;
}

sub module_update{ 
    my$this=shift;
    my$toplevel=$this->{"toplevel"};
    my$percent;
    my$maxpercent=-1;
    my$i=0;

    read_proc($this);
    return &reconfig if($lastbat!=$batteries);

    for($i=0;$i<$batteries;$i++){
	my$val=$percent[$i];
	# apm, for some reason, seems to occasionally return -1/1.
	# guard against that
	if($val>=0){
	    $maxpercent=$val if($maxpercent<$val);
	}

	if($battstatus[$i]<0){
	    if($gstate[$i]!=6){
		$graph->
		    barconfigure($i,'aforeXr'=>'acforeground',
				 'abackXr'=>'acbackground',
				 'labelXr'=>'nonelabel');
		$gstate[$i]=6;
	    }
	    
	}elsif($ac){
	    if($battstatus[$i]==1){
		if($gstate[$i]!=4){
		    $graph->
			barconfigure($i,'aforeXr'=>'chargeforeground',
				     'abackXr'=>'chargebackground',
				     'labelXr'=>'chargelabel');
		    $gstate[$i]=4;
		}
	    }else{
		if($gstate[$i]!=5){
		    $graph->
			barconfigure($i,'aforeXr'=>'acforeground',
				     'abackXr'=>'acbackground',
				     'labelXr'=>'fulllabel');
		    $gstate[$i]=5;
		}
	    }
	}else{
	    if($val<30){
		if($val<8){
		    if($gstate[$i]!=2){
			$graph->
			    barconfigure($i,'aforeXr'=>'lowforeground',
					 'abackXr'=>'lowbackground',
					 'labelXr'=>'lowlabel');
			$gstate[$i]=2;
		    }
		}else{
		    if($gstate[$i]!=1){
			$graph->
			    barconfigure($i,'aforeXr'=>'midforeground',
					 'abackXr'=>'midbackground',
					 'labelXr'=>'label');
			$gstate[$i]=1;
		    }
		}
	    }else{
		if($gstate[$i]!=0){
		    $graph->
			barconfigure($i,'aforeXr'=>'litforeground',
				     'abackXr'=>'litbackground',
				     'labelXr'=>'label');
		    $gstate[$i]=0;
		}
	    }
	    
	    $i++;
	}
    }
    
    
    $percent=$maxpercent;
    if($percent>=0){
	
	$graph->set(@percent);
	
	if($percent<30){
	    # below 30%, it would be nice to unfix the scale so 
	    # the user can see		
	    if($percent<8){
		if($lstate!=2){
		    $graph->configure(fixed=>0,rangesetting=>8);
		    $lstate=2;
		}
	    }else{
		if($lstate!=1){
		    $graph->configure(fixed=>0,rangesetting=>32);
		    $lstate=1;
		}
	    }
	}else{
	    if($lstate!=0){
		$graph->configure(fixed=>1,rangesetting=>100);
		$lstate=0;
	    }
	}
    }
}

sub destroy{
    close B_FD if($openp);
    close PROC if($openf);
    $openp=0;
    $openf=0;
    undef $xpath;
}

sub reconfig{
    &main::reinstance() if($widget->optionGet("reconfig","") eq 'true');
}

sub module_place_p{
    $batteries;
}


bless {};

