/*
 * portable.cpp
 *
 * Copyright (c) 1999 Paul Campbell <paul@taniwha.com>
 *
 * Requires the Qt widget libraries, available at no cost at
 * http://www.troll.no/
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

//
//	this file contains the machine specific laptop power management stuff
//	to add support for your own OS this is should be the only place you need
//	to change - to add your own stuff insert above the line marked 
//	'INSERT HERE' :
//
//		#ifdef MY_OS"
//			.. copy of linux code or whatever you want to use as a base
//		# else
//
//	then tag an extra '#endif' at the end
//
//	If you have any problems, questions, whatever please get in touch
//
//		Paul Campbell
//		paul@taniwha.com
//		
//

#include <qlabel.h>
#include <klocale.h>
#include <stdio.h>
#include "portable.h"

#ifdef __linux__
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>

//
//	here's the Linux specific laptop control panel stuff
//

typedef struct apm_info {
   int        apm_flags;
   int        ac_line_status;
   int        battery_percentage;
   int        battery_time;
} apm_info;
 
static int
apm_read(apm_info *ap)
{
   	FILE	*f;
   	char	tmp2[10];
	int	tmp, s;
	char 	version[256];

	f = fopen("/proc/apm", "r");
	if (f == NULL)
		return(1);
	s = fscanf(f, "%s %d.%d %x %x %x %x %d%% %d %s\n",
		version,
		&tmp,
		&tmp,
		&ap->apm_flags,
	   	&ap->ac_line_status,
		&tmp,
		&tmp,
	   	&ap->battery_percentage,
	   	&ap->battery_time,
	   	tmp2);
	if (s < 9)
		return(1);	
	if (version[0] == 'B') {
		fclose(f);
		return(2);
	}
   	if (ap->battery_percentage > 100) 
		ap->battery_percentage = -1;
	if (strcmp(tmp2, "sec") == 0)
		ap->battery_time /= 60;
   	fclose(f);
	return(0);
}

static int apm_no_time;
static apm_info apmx = {0,0,0,0};
static int
has_apm()
{
        static int init = 0;
        static int val;
        if (init)
                return(val);
 
        init = 1;
        val = 1;
        apm_no_time=0;
        if (apm_read(&apmx) || (apmx.apm_flags&0x20)) {
                val = 0;
                apm_no_time = 1;
                                      
        } else {
                apm_no_time = apmx.battery_time < 0;
        }
        return(val);
}
 
static int
apm_has_time()
{
        return(!apm_no_time);
}

//
//	returns 1 if we support power management
//
int laptop_portable::has_power_management()
{
	return (::has_apm());
}
//
//	returns 1 if the BIOS returns the time left in the battery rather than a % of full
//
int laptop_portable::has_battery_time()
{
	return (::apm_has_time());
}
//
//	returns 1 if we can perform a change-to-suspend-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_suspend()
{
	struct stat s;
        if (stat("/usr/bin/apm", &s) || !(getuid() == 0 || s.st_mode&S_ISUID)) 
		return(0);
	return(1);
}
//
//	returns 1 if we can perform a change-to-standby-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_standby()
{
	struct stat s;
        if (stat("/usr/bin/apm", &s) || !(getuid() == 0 || s.st_mode&S_ISUID)) 
		return(0);
	return(1);
}
//
//	returns 1 if we can perform a change-to-hibernate-mode for a user
//      (has_power_management() has already returned 1)  [hibernate is the save-to-disk mode
//	not supported by linux APM]
//
static int hiber_type = 0;		// remember how we hibernate
int laptop_portable::has_hibernation()
{
	struct stat s;

	hiber_type = 0;
        if (stat("/usr/local/bin/tpctl", &s) == 0 && (getuid() == 0 || s.st_mode&S_ISUID))  {
		hiber_type = 1;
		return(1);
	}
	return(0);
}

//
//	explain to the user what they need to do if has_power_management() returned 0
//	to get any software they lack
//

QLabel *laptop_portable::no_power_management_explanation(QWidget *parent)
{
	QLabel* explain = new QLabel(i18n("Your computer doesn't have the Linux APM (Advanced\nPower Management) software installed, or doesn't have\nthe APM kernel drivers installed - check out the Linux Laptop-HOWTO\ndocument for information how to install APM\nit is available at http://www.linuxdoc.org/HOWTO/Laptop-HOWTO.html"), parent);
      	explain->setMinimumSize(explain->sizeHint());       
	return(explain);
}

//
//	explain to the user what they need to do to get suspend/resume to work from user mode
//
QLabel *laptop_portable::how_to_do_suspend_resume(QWidget *parent)
{
 	QLabel* note = new QLabel(i18n("\nIf you make /usr/bin/apm setuid then you will also\nbe able to choose 'suspend' and 'standby' in the\nabove dialog - check out the help button below to\nfind out how to do this"), parent);
        note->setMinimumSize(note->sizeHint()); 
	return(note);
}

static char tmp0[256], tmp1[256];
static int present=0;
static 
void get_pcmcia_info()
{
      FILE *f = fopen("/var/lib/pcmcia/stab", "r");
      if (!f) f = fopen("/var/run/stab", "r");
      if (f) {
	char c, *cp;

	present = 1;
	cp = tmp0;
	for (;;) {
		c = getc(f);
		if (c == EOF || c == '\n')
			break;
		if (c == ':') {
			while ((c = getc(f)) == ' ')
				;
			for (;;) {
				*cp++ = c;
				c = getc(f);
				if (c == EOF || c == '\n')
					break;
			}
			break;
		}
	}
	*cp = 0;

	cp = tmp1;
	for (;;) {
		c = getc(f);
		if (c == EOF)
			break;
		if (c == ':') {
			while ((c = getc(f)) == ' ')
				;
			for (;;) {
				*cp++ = c;
				c = getc(f);
				if (c == EOF || c == '\n')
					break;
			}
			break;
		}
	}
	*cp = 0;

	fclose(f);
      } else {
	present = 0;
      }
}

//
//	pcmcia support - this will be replaced by better - pcmcia support being worked on by
//	others
//
QLabel *laptop_portable::pcmcia_info(int x, QWidget *parent)
{
	if (x == 0) 
		get_pcmcia_info();
	if (!present) {
		if (x == 1)
      			return(new QLabel(i18n("No PCMCIA controller detected"), parent));
      		return(new QLabel(i18n(""), parent));
	} else {
		switch (x) {
        	case 0: return(new QLabel(i18n("Card 0:"), parent));
        	case 1: return(new QLabel(tmp0, parent));
		case 2: return(new QLabel(i18n("Card 1:"), parent));
        	default:return(new QLabel(tmp1, parent));  
		}
	}
}

//
//	puts us into standby mode
//
void laptop_portable::invoke_standby()
{
	 ::system("/usr/bin/apm --standby");    
}

//
//	puts us into suspend mode
//
void laptop_portable::invoke_suspend()
{
	 ::system("/usr/bin/apm --suspend");    
}

//
//	puts us into hibernate mode
//
void laptop_portable::invoke_hibernation()
{
	switch (hiber_type) {
	case 1:
	 	::system("/usr/local/bin/tpctl --hibernate");    
		break;
	// add other machine specific hibernates here
	}
}


//
//	return current battery state
//
struct power_result laptop_portable::poll_battery_state()
{
	struct power_result p;

	apm_info x = {0,0,0,0};
        if (apm_read(&x) || (x.apm_flags&0x20)) {
                p.powered = 0;
                p.percentage=0;
                p.time = 0;
        } else {
		p.powered = x.ac_line_status&1;
                p.percentage = x.battery_percentage;
                p.time = x.battery_time;
        }                                 
	return(p);
}


//
//	returns true if any mouse or kdb activity has been detected
//	

int laptop_portable::poll_activity()
{
	static int mouse_count = 0, key_count = 0;

	int m=0, k = 0;
	int v;
	char	name[256];
	char *cp, *cp2;
	int *vp;
	static FILE *procint = 0;

	if (procint == 0) {
		procint = fopen("/proc/interrupts", "r");
		if (procint != 0)
			return(0);
		poll_activity();	// initialise statics
		return(1);
	}

	::rewind(procint);
	for (;;) {
		if (::fgets(name, sizeof(name), procint) == 0)
			break;
		vp = 0;
		if (strstr(name, "Mouse") || strstr(name, "mouse")) {
			vp = &m;
		} else
		if (strstr(name, "Keyboard") || strstr(name, "keyboard"))
			vp = &k;
		if (vp == 0)
			continue;
		v = 0;
		for (cp = name;*cp;cp++) {
			if (*cp != ':')
				continue;
			cp++;
			for (;;) {
				for(;;cp++) {
					if (*cp != ' ' && *cp != '\t')
						break;
				}
				if (*cp < '0' || *cp > '9')
					break;
				cp2 = cp;
				while (*cp >= '0' && *cp <= '9')
					cp++;
				
				*cp++ = 0;
				v += atoi(cp2);	
			}
			break;
		}
		if (v > *vp)
			*vp = v;
	}
	v = k != key_count || m != mouse_count;
	key_count = k;
	mouse_count = m;
	return(v);
}



#elif __FreeBSD__
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <machine/apm_bios.h>
#include <sys/stat.h>
#define APMDEV "/dev/apm"

// FreeBSD support by yours truely.  Yay.
// Actually this code was "adapted" from apm(8) from
// FreeBSD's collection of tools.  The orignal apm program
// was pieced together by Tatsumi Hosokawa <hosokawa@jp.FreeBSD.org> in 1994

//
//	returns 1 if we support power management
//

#include <iostream.h>

int
laptop_portable::has_power_management()
{
	int ret, fd = ::open(APMDEV, O_RDWR);

	if (fd == -1) {
	  return 0;
	}

	struct apm_info info;
	ret=ioctl(fd, APMIO_GETINFO, &info);
	::close(fd);

	if (ret == -1) {
	  return 0;
	}

	return info.ai_status;
}
//
//	returns 1 if the BIOS returns the time left in the battery rather than a % of full
//
int laptop_portable::has_battery_time()
{
	int ret, fd = ::open(APMDEV, O_RDWR);

	if (fd == -1)
	  return 0;

	struct apm_info info;
	ret=ioctl(fd, APMIO_GETINFO, &info);
	::close(fd);

	if (ret == -1)
	  return 0;

	return (info.ai_batt_time != 0xffff);
}

//
//	returns 1 if we can perform a change-to-suspend-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_suspend()
{
	int ret, fd = ::open(APMDEV, O_RDWR);

	if (fd == -1)
	  return 0;

	struct apm_info info;
	ret=ioctl(fd, APMIO_GETINFO, &info);
	::close(fd);

	if (ret == -1)
	  return 0;

	return (info.ai_capabilities & 0x02);
}
//
//	returns 1 if we can perform a change-to-standby-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_standby()
{
	int ret, fd = ::open(APMDEV, O_RDWR);

	if (fd == -1)
	  return 0;

	struct apm_info info;
	ret=ioctl(fd, APMIO_GETINFO, &info);
	::close(fd);

	if (ret == -1)
	  return 0;

	return (info.ai_capabilities & 0x01);
}
//
//	returns 1 if we can perform a change-to-hibernate-mode for a user
//      (has_power_management() has already returned 1)  [hibernate is the save-to-disk mode
//	not supported by linux - different laptops have their own - the first here is for 
//	a ThinkPad]
//
static int hiber_type = 0;		// remember how we hibernate
int laptop_portable::has_hibernation()
{
	struct stat s;

	hiber_type = 0;
        if (stat("/usr/local/bin/tpctl", &s) == 0 && (getuid() == 0 || s.st_mode&S_ISUID))  {
		hiber_type = 1;	// ThinkPad
		return(1);
	}
	return(0);
}

//
//	explain to the user what they need to do if has_power_management() returned 0
//	to get any software they lack
//
QLabel *laptop_portable::no_power_management_explanation(QWidget *parent)
{
  int fd;
  QLabel *explain;

  fd = ::open(APMDEV, O_RDWR);
  if (fd == -1) {
    switch (errno) {
    case ENOENT:
      explain = new QLabel("There is no /dev/apm file on this system.  Pleae review the FreeBSD handbook on how to create a device node for the apm device driver (man 4 apm)", parent);
      break;
    case EACCES:
      explain = new QLabel("Your system has the proper device node for apm support, however you can't access it.  If you're root right now, you've got a problem, otherwise contact your local sysadmin and beg for read/write access to /dev/apm.", parent);
      break;
    case ENXIO:
      explain = new QLabel("Your kernel lacks support for Advanced Power Managment.", parent);
      break;
      break;
    default:
      explain = new QLabel("There was some generic error while opening /dev/apm.  Contact your local supermarket, there's a blue light special on FreeBSD, really.", parent);
      break;
    }
  } else {
    close(fd);
    explain = new QLabel("APM has most likely been disabled.  Oops", parent);
  }
  
  explain->setMinimumSize(explain->sizeHint());       
  return(explain);
}

//
//	explain to the user what they need to do to get suspend/resume to work from user mode
//
QLabel *laptop_portable::how_to_do_suspend_resume(QWidget *parent)
{
 	QLabel* note = new QLabel(i18n(" "), parent);
        note->setMinimumSize(note->sizeHint()); 
	return(note);
}


//
//	pcmcia support - this will be replaced by better - pcmcia support being worked on by
//	others
//
QLabel *laptop_portable::pcmcia_info(int x, QWidget *parent)
{
      	if (x == 0)
		return(new QLabel(i18n("No PCMCIA controller detected"), parent));
      	return(new QLabel(i18n(""), parent));
}
//
//	puts us into standby mode
//
void laptop_portable::invoke_standby()
{
  	int fd = ::open(APMDEV, O_RDWR);

	if (fd == -1)
	  return;

	ioctl(fd, APMIO_STANDBY, NULL);
	::close(fd);

	return;
}

//
//	puts us into suspend mode
//
void laptop_portable::invoke_suspend()
{
  	int fd = ::open(APMDEV, O_RDWR);

	if (fd == -1)
	  return;

	ioctl(fd, APMIO_SUSPEND, NULL);
	::close(fd);

	return;
}
//
//	puts us into hibernate mode
//
void laptop_portable::invoke_hibernation()
{
	switch (hiber_type) {
	case 1:
	 	::system("/usr/local/bin/tpctl --hibernate");    
		break;
	// add other macheine specific hibernates here
	}
}


//
//	return current battery state
//
struct power_result laptop_portable::poll_battery_state()
{
	struct power_result p;
       	int ret;

       	int fd = ::open(APMDEV, O_RDWR);

       	if (fd == -1)
         	goto bad;

       	struct apm_info info;
       	ret=ioctl(fd, APMIO_GETINFO, &info);
       	::close(fd);
	
       	if (ret == -1)
         	goto bad;

       	p.powered = info.ai_acline;
       	p.percentage = (info.ai_batt_life==255 ? 100 : info.ai_batt_life);
       	p.time = (info.ai_batt_time != 0xffff ? info.ai_batt_time/60 : -1);
       	return(p);

bad:
       	p.powered = 1; 
	p.percentage = 100;
	p.time = 0;
	return(p);
}

//
//
//	returns true if any mouse or kdb activity has been detected
//	

int laptop_portable::poll_activity()
{
	return(1);
}
#else

// INSERT HERE

//
//	returns 1 if we support power management
//
int
laptop_portable::has_power_management()
{
	return(0);
}
//
//	returns 1 if the BIOS returns the time left in the battery rather than a % of full
//
int laptop_portable::has_battery_time()
{
	return (0);
}

//
//	returns 1 if we can perform a change-to-suspend-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_suspend()
{
	return(0);
}
//
//	returns 1 if we can perform a change-to-standby-mode operation for the user
//	(has_power_management() has already returned 1)
//
int laptop_portable::has_standby()
{
	return(0);
}
//
//	returns 1 if we can perform a change-to-hibernate-mode for a user
//      (has_power_management() has already returned 1)  [hibernate is the save-to-disk mode
//	not supported by linux]
//
int laptop_portable::has_hibernation()
{
	return(0);
}

//
//	explain to the user what they need to do if has_power_management() returned 0
//	to get any software they lack
//
QLabel *laptop_portable::no_power_management_explanation(QWidget *parent)
{
	QLabel* explain = new QLabel(i18n("Your computer or operating system is not supported by the current version of the\nKDE laptop control panels. If you want help porting these panels to work with it\nplease contact paul@taniwha.com."), parent);
      	explain->setMinimumSize(explain->sizeHint());       
	return(explain);
}

//
//	explain to the user what they need to do to get suspend/resume to work from user mode
//
QLabel *laptop_portable::how_to_do_suspend_resume(QWidget *parent)
{
 	QLabel* note = new QLabel(i18n(" "), parent);
        note->setMinimumSize(note->sizeHint()); 
	return(note);
}


//
//	pcmcia support - this will be replaced by better - pcmcia support being worked on by
//	others
//
QLabel *laptop_portable::pcmcia_info(int x, QWidget *parent)
{
      	if (x == 0)
		return(new QLabel(i18n("No PCMCIA controller detected"), parent));
      	return(new QLabel(i18n(""), parent));
}
//
//	puts us into standby mode
//
void laptop_portable::invoke_standby()
{
}

//
//	puts us into suspend mode
//
void laptop_portable::invoke_suspend()
{
}
//
//	puts us into hibernate mode
//
void laptop_portable::invoke_hibernation()
{
}


//
//	return current battery state
//
struct power_result laptop_portable::poll_battery_state()
{
	struct power_result p;
	p.powered = 0;
	p.percentage = 0;
	p.time = 0;
	return(p);
}

//
//	returns true if any mouse or kdb activity has been detected
//	

int laptop_portable::poll_activity()
{
	return(1);
}
#endif
