/*
 * asapm is the APM and ACPI monitor utility for X Windows
 * Copyright (c) 1998-2005  Albert 'Tigr' Dorofeev <albert@tigr.net>
 * For the updates see http://www.tigr.net/afterstep/
 * 
 * This software is distributed under GPL. For details see LICENSE file.
 */


/*
 * These routines are for reading the APM daemon
 * output and parsing it.
 */

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>

#if defined(__FreeBSD__)
#include <sys/file.h>
#include <sys/ioctl.h>
#include <machine/apm_bios.h>
#endif

#if defined(__NetBSD__)||defined(__OpenBSD__)
#include <sys/ioctl.h>
#include <machine/apmvar.h>
#endif

/* file -> APM device */
extern char apm_device_file[];
extern char acpi_device_dir[];
extern char acpi_battery_name[];

extern int force_acpi;
extern int force_apm;

#include "state.h"
extern struct apm_state state;

/* Variables for the calculation of the time left */
static double discharge_rate;
static int discharge_rate_known;
static int last_recorded_percent;
static long int apm_checks_passed;

/* Selector of the type of power management */
static int apm_checked = 0;
static int apm_detected = 0;
#define DETECTED_APM	1
#define DETECTED_ACPI	2

/*
 * An error handler for the problems while reading APM.
 */
void error_handle( int place, const char * message )
{
        int error_num;
        error_num = errno;
        /* if that was an interrupt - quit quietly */
        if (error_num == EINTR) {
                printf("asapm: Interrupted.\n");
		++state.error;
		return;
	}
        printf("asapm: %s: ", message);
        switch ( place )
        {
        case 1: /* Opening the file APM device */
                switch (error_num)
                {
                case ENOENT :
                        printf("The file does not exist.\n");
			if (state.fail)
				++state.error;
                        break;
                case EACCES :
                        printf("You do not have permissions to read APM or ACPI info.\n"); 
			if (state.fail)
				++state.error;
                        break;  
                default:
                        /* let the user guess what it is */
                        printf("cannot open device. Error %d:  %s\n",
                                errno,
                                strerror(errno));
			if (state.fail)
				++state.error;
                }               
                break;          
        default: /* all the rest */
                printf("Error %d:  %s\n",
                        errno, strerror(errno));
		if (state.fail)
			++state.error;
        }               
}

/*
 * Routine to read the APM information different
 * for Linux, FreeBSD, and NetBSD.
 */
#if defined(__FreeBSD__)
int ReadAPMDevice( )		/* FreeBSD version */
{
	int fd;
	struct apm_info info;
	memset(&info, 0, sizeof(info));
	if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
		error_handle(1, "");
		return -1;
	}
	if (ioctl(fd, APMIO_GETINFO, &info) == -1) {
		error_handle(4, "");
		close(fd);
		return -1;
	}
	close(fd);

	sprintf(state.driver_version, "?");
	sprintf(state.apm_bios_info_version, "%d.%d", 
		info.ai_major, info.ai_minor);
	if (state.ac_line_status != info.ai_acline) {
		state.ac_line_status = info.ai_acline;
		++state.update;
		if ( state.ac_line_status == AC_ONLINE ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != info.ai_batt_stat) {
		state.battery_status = info.ai_batt_stat;
		++state.update;
	}
	if (state.percent != info.ai_batt_life) {
		if ( state.percent < info.ai_batt_life )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = info.ai_batt_life;
		++state.update;
	}
	state.time_left = info.ai_batt_time / 60;
	return 0;
}
#elif defined(__NetBSD__)||defined(__OpenBSD__)
int ReadAPMDevice( )		/* NetBSD version */
{
	int fd;
	struct apm_power_info info;
	memset(&info, 0, sizeof(info));
	if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
		error_handle(1, "");
		return -1;
	}
	if (ioctl(fd, APM_IOC_GETPOWER, &info) == -1) {
		error_handle(4, "");
		close(fd);
		return -1;
	}
	close(fd);

#ifdef DEBUG
	sprintf(state.driver_version, "?");
	sprintf(state.apm_bios_info_version, "?.?");
#endif
	if (state.ac_line_status != info.ac_state) {
		state.ac_line_status = info.ac_state;
		++state.update;
		if ( state.ac_line_status == APM_AC_ON ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != info.battery_state) {
		state.battery_status = info.battery_state;
		++state.update;
	}
	if (state.percent != info.battery_life) {
		if ( state.percent < info.battery_life )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = info.battery_life;
		++state.update;
	}
	state.time_left = info.minutes_left;
	return 0;
}
#else
#ifdef TEST
int countdown = 100;
int timedown = 620;
#endif
int ReadAPMDevice( )		/* Linux version */
{
	int fd;
	int tmp;
	char buf[256];
	unsigned int ac_line_status;
	unsigned int battery_status;
	unsigned int battery_flag;
	int percent;
	int time_units;
	char units[10];

        /* First check if there is an APM */
        if ((fd = open(apm_device_file, O_RDONLY)) == -1) {
                error_handle(1, "open_apm_device_file");
                return -1;
        }

	tmp = read(fd, buf, sizeof buf);
	close(fd);
	switch( tmp ) 
	{
	case 0:
		/* end of file */
		break;
	case -1:
		error_handle(2, "read_apm_device_file");
		return -1;
	default:
		switch(sscanf(buf, "%s %s %x %x %x %x %d%% %d %s\n", 
			state.driver_version,
			state.apm_bios_info_version,
			&state.apm_bios_info_flags,
			&ac_line_status,
			&battery_status,
			&battery_flag,
			&percent,
			&time_units,
			units
			)) 
		{
		case 0:
		case -1:
			printf("asapm: invalid input character while "
				"reading %s\n", apm_device_file);
		}
	}
#ifdef TEST
	percent = countdown;
	if (countdown) countdown -= 1;
	time_units = timedown;
	if (timedown) timedown -= 20;
	strcpy(units, "min");
#endif

	if (state.ac_line_status != ac_line_status) {
		state.ac_line_status = ac_line_status;
		++state.update;
		if ( state.ac_line_status == AC_ONLINE ) 
			state.flags |= CHANGE_AC_ON;
		else
			state.flags |= CHANGE_AC_OFF;
	}
	if (state.battery_status != battery_status) {
		state.battery_status = battery_status;
		++state.update;
	}
	if (state.battery_flag != battery_flag) {
		state.battery_flag = battery_flag;
		++state.update;
	}
	if (state.percent != percent) {
		if ( state.percent < percent )
			state.flags |= CHANGE_POWER_UP;
		else
			state.flags |= CHANGE_POWER_DOWN;
		state.percent = percent;
		++state.update;
	}
	if ( strncmp( units, "min", 3 ) )
		time_units /= 60;
	/* we can display maximum 99:59 */
	if ( time_units > 5999 )
		time_units = 5999;
	if (state.time_left != time_units) {
		state.time_left = time_units;
		++state.update;
	}
	return 0;
}
#endif

static int acpi_available = 0;
static char acpi_name[256] = "";

int acpi_name_filter(const struct dirent * entry) {
	if ( entry->d_name[0] == '.' )
		return 0;
	if ( entry->d_name[1] == '.' )
		return 0;
	return 1;
}

int ListACPIDevices(const int print_result) {
	int result;
	struct dirent **names;
	int i;
	static char acpi_file[256];
	int fd;
	int tmp;
	static char buf[1024];
	static char present[256];

	sprintf( acpi_file, "%s/battery/", acpi_device_dir);
#ifdef DEBUG
	printf("asapm will check ACPI dir:\t%s\n", acpi_file);
#endif
	result = scandir(acpi_file, &names,
			&acpi_name_filter, &alphasort);

	if ( result == -1 )
		return -1;
	if ( result == 0 )
		return -1;
	/* Now we can go and check which batteries are connected */
	for ( i=0; i<result; ++i) {
		sprintf( acpi_file, "%s/battery/%s/state",
			acpi_device_dir, names[i]->d_name);
#ifdef DEBUG
		printf("asapm will check: #%d [%d/%d]\t%s\n",
			i, i+1, result, acpi_file);
#endif

		if ((fd = open(acpi_file, O_RDONLY)) == -1) {
			error_handle(1, "open_acpi_bat_stat_file");
		} else {
			tmp = read(fd, buf, sizeof buf);
			close(fd);
			switch( sscanf( strstr(buf, "present:"), 
					"present: %s\n", present)) 
			{
			case 0:
			case -1:
				printf("asapm: invalid input character while "
				"reading present state from %s\n", acpi_file);
				return -1;
				break;
			default:
#ifdef DEBUG
				printf("\tBattery %s, present state: \t[%s]\n", 
					names[i]->d_name, present);
#endif
				if ( ! strcmp(present, "yes") ) {
					printf("\tBattery %s is available\n",
							names[i]->d_name);
					if ( ! acpi_available ) {
						sprintf(acpi_name,
							"%s/battery/%s",
							acpi_device_dir,
							names[i]->d_name);
						acpi_available = 1;
					}
				}
			}
		}
		free(names[i]);
	}
	free(names);

	if ( strlen(acpi_battery_name) ) {
#ifdef DEBUG
		printf("asapm would use by default: %s\n", acpi_name);
#endif
		sprintf(acpi_name, "%s/battery/%s", acpi_device_dir,
			acpi_battery_name);
#ifdef DEBUG
		printf("but command-line specifies: %s\n", acpi_name);
#endif
	} else {
#ifdef DEBUG
		printf("asapm will use default: %s\n", acpi_name);
#endif
	}
	return 0;
}

/*
 * This function will read ACPI entry in /proc/acpi.
 *
 * The problem of handling the ACPI batteries is that
 * there may be multiple entries for multiple batteries.
 * It seems there is no standard way of naming those
 * and we simply need to check all batteries that are
 * represented by subdirectories of /proc/acpi/battery.
 *
 * I will select the first that comes in as the default
 * battery to check. If it is "present: yes", we give
 * out the info about it. If it is not, we move on to
 * the next one. The user will have to specify the
 * subdirectory name if he wants to force the reading
 * of a particular battery.
 */
int ReadACPIDevice() {
	int fd;
	int tmp;
	static char buf[1024];
	static char acpi_dir[256];
	static char stat_file[256];
	static char info_file[256];

	char present[64];
	char capacity_state[64];
	char charging_state[64];

	unsigned int last_full_capacity = 0;
	unsigned int remaining_capacity = 0;
	unsigned int present_rate = 0;

	unsigned int percent;
	unsigned int time_left;

	if ( ! acpi_available )
		return -1;

	/*
	 * Now, for the hard part. The ACPI keeps the battery
	 * information in various files scattered through the
	 * battery directories, one directory per battery
	 * installed. 
	 * So, a hard design decision was taken :)
	 * Instead of trying to present all of the information
	 * in one instance of this applet, we will by default
	 * show the first battery present and make this configurable 
	 * to show whichever battery number user desires.
	 * This way, we need to work with one battery only
	 * and on the screen it will look better too to have
	 * one asapm per battery.
	 */

	sprintf( stat_file, "%s/state", acpi_name);
	sprintf( info_file, "%s/info", acpi_name);
#ifdef DEBUG
	printf("asapm will check:\t%s\n\t\t\t%s\n",
			stat_file, info_file);
#endif

        if ((fd = open(stat_file, O_RDONLY)) == -1) {
                error_handle(1, "open_acpi_bat_stat_file");
                return -1;
        }
	tmp = read(fd, buf, sizeof buf);
	close(fd);
	switch( sscanf( strstr(buf, "present:"), 
				"present: %s\n", present)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading present state from %s\n", stat_file);
		return -1;
	}
#ifdef DEBUG
	printf("Battery %s, present state: \t[%s]\n", 
			acpi_name, present);
#endif
	if ( ! strcmp(present, "yes") ) {
		/* The battery is absent? This is not critical. */
		/*printf("asapm: ACPI: battery #%d presence state is \"%s\". I will continue to poll it.\n", acpi_battery_number, present);
		return 0;*/
	}
	if ( ! strstr(buf, "capacity state:") ) {
		/* The battery is really not there or we cannot
		 * read the state properly. In either case
		 * we cannot do anything without knowing
		 * the state of the battery. Bailing out...
		 */
		printf("asapm: ACPI: battery %s is either absent or does not report its state.\n", acpi_name);
		return 0;
	}
	switch( sscanf( strstr(buf, "capacity state:"), 
				"capacity state: %s\n", capacity_state)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading capacity state from %s\n", stat_file);
		return -1;
	}
#ifdef DEBUG
	printf("Battery %s, capacity state: \t[%s]\n", 
			acpi_name, capacity_state);
#endif
	switch( sscanf( strstr(buf, "charging state:"), 
				"charging state: %s\n", charging_state)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading charging state from %s\n", stat_file);
		return -1;
	}
#ifdef DEBUG
	printf("Battery %s, charging state: \t[%s]\n", 
			acpi_name, charging_state);
#endif
	switch( sscanf( strstr(buf, "present rate:"), 
				"present rate: %u\n", 
				&present_rate)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading present rate from %s\n", stat_file);
		present_rate = 0;
	}
#ifdef DEBUG
	printf("Battery %s, present rate: \t[%u]\n", 
			acpi_name, present_rate);
#endif
	if ( ! strcmp(charging_state, "charging") ) {
		/* The battery is charging => AC is online */
		if ( present_rate ) {
			if ( state.battery_status != BATTERY_CHARGING ) {
				state.flags |= CHANGE_POWER_UP;
				state.battery_status = BATTERY_CHARGING;
				++state.update;
			}
		} else {
			/* even though we are charging, the
			 * rate is either zero or unknown */
			if ( state.battery_status != BATTERY_HIGH ) {
				/* state.flags |= CHANGE_POWER_UP; */
				state.battery_status = BATTERY_HIGH;
				++state.update;
			}
		}
		if ( state.ac_line_status != AC_ONLINE ) {
			state.flags |= CHANGE_AC_ON;
			state.ac_line_status = AC_ONLINE;
			++state.update;
		}
	} else if ( ! strcmp(charging_state, "discharging") ) {
		/* The battery is discharging, AC is offline */
		unsigned int new_battery_status;
		if ( ! strcmp(capacity_state, "unknown") ) {
			new_battery_status = BATTERY_UNKNOWN;
		} else if ( ! strcmp(capacity_state, "ok") ) {
			new_battery_status = BATTERY_HIGH;
		} else {
			new_battery_status = BATTERY_LOW;
		}
		if ( state.battery_status != new_battery_status ) {
			state.flags |= CHANGE_POWER_DOWN;
			state.battery_status = new_battery_status;
			++state.update;
		}
		if ( state.ac_line_status != AC_BATTERY ) {
			state.flags |= CHANGE_AC_OFF;
			state.ac_line_status = AC_BATTERY;
			++state.update;
		}
	} else if ( ! strcmp(charging_state, "charged") ) {
		/* Well, if you say so, we have but to obey */
		if ( state.battery_status != BATTERY_HIGH ) {
			state.battery_status = BATTERY_HIGH;
			++state.update;
		}
		if ( state.ac_line_status != AC_ONLINE ) {
			state.flags |= CHANGE_AC_ON;
			state.ac_line_status = AC_ONLINE;
			++state.update;
		}
	} else {
		if ( state.battery_status != BATTERY_UNKNOWN ) {
			state.battery_status = BATTERY_UNKNOWN;
			++state.update;
		}
		if ( state.ac_line_status != AC_UNKNOWN ) {
			state.ac_line_status = AC_UNKNOWN;
			++state.update;
		}
	}
	switch( sscanf( strstr(buf, "remaining capacity:"), 
				"remaining capacity: %u\n", 
				&remaining_capacity)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading remaining capacity from %s\n", stat_file);
		return -1;
	}
#ifdef DEBUG
	printf("Battery %s, remaining capacity: \t[%u]\n", 
			acpi_name, remaining_capacity);
#endif

	/*
	 * Off we go to read the BAT1/info file
	 */
        if ((fd = open(info_file, O_RDONLY)) == -1) {
                error_handle(1, "open_acpi_bat_info_file");
                return -1;
        }
	tmp = read(fd, buf, sizeof buf);
	close(fd);
	switch( sscanf( strstr(buf, "last full capacity:"), 
				"last full capacity: %u\n", 
				&last_full_capacity)) 
	{
	case 0:
	case -1:
		printf("asapm: invalid input character while "
			"reading last full capacity from %s\n", info_file);
		return -1;
	}
#ifdef DEBUG
	printf("Battery %s, last full capacity: \t[%u]\n", 
			acpi_name, last_full_capacity);
#endif

	/*
	 * Calculate the percentage and time to full charge
	 * or discharge based on the reported rate.
	 */
	percent = 
		last_full_capacity ?
		(float)remaining_capacity / (float)last_full_capacity * 100
		: 0;
	if ( state.percent != percent ) {
		state.percent = percent;
		++state.update;
	}

	if ( state.battery_status == BATTERY_CHARGING ) {
		time_left = 
			present_rate ?
		((float)(last_full_capacity - remaining_capacity) / 
		 	(float)present_rate) * 60
			: 0;
	} else {
		time_left = 
			present_rate ?
			((float)remaining_capacity / (float)present_rate) * 60
			: 0;
	}
	if ( state.time_left != time_left ) {
		state.time_left = time_left;
		++state.update;
	}
	return 0;
}

/*
 * This checks the current status of the APM.
 */
void CheckAPMEvents()
{
	unsigned int tmp;
	if ( ! apm_checked ) {
#ifdef DEBUG
	printf("Checking APM events from device file [%s]\n", apm_device_file);
	printf("Checking ACPI events from files in [%s]\n", acpi_device_dir);
#endif
		/* That's our first run, see what devices are there. */
		/*
		 * Now we need to check both APM and ACPI
		 * and see if we can use one or the other.
		 * Normally, ACPI takes precedence as it
		 * is newer and may replace APM totally
		 * in the future.
		 */
		/*if ( ! ReadACPIDevice() )*/
		if ( ! force_apm )
			if ( ! ListACPIDevices(0) )
				apm_detected |= DETECTED_ACPI;
		if ( ! force_acpi )
			if ( ! ReadAPMDevice() )
				apm_detected |= DETECTED_APM;
		apm_checked = 1;
	}
	if ( apm_detected & DETECTED_ACPI )
		tmp = ReadACPIDevice();
	else
		if ( apm_detected & DETECTED_APM )
			tmp = ReadAPMDevice();
		else {
			/* Nothing detected... Nothing to do. */
			printf("asapm: Neither ACPI nor APM are available!\n");
			exit(0);
		}

#ifdef DEBUG
	printf("apmd %s ", state.driver_version);
	printf("APM BIOS %s ", state.apm_bios_info_version);
	printf("APM flags %#x\n", state.apm_bios_info_flags);
	printf("AC line %#x ", state.ac_line_status);
	printf("Battery status %#x ", state.battery_status);
	printf("Battery flag %#x\n", state.battery_flag);
	printf("Battery charge %d ", state.percent);
	printf("Battery life %d ", state.time_left);
	printf("(estimate %d)\n\n", state.time_estimate);
#endif

	if ( (! state.system_levels) && 
			(state.battery_status != BATTERY_CHARGING) ) {
		if ( state.percent < 0 )
			state.battery_status = BATTERY_UNKNOWN;
		else if ( state.percent <= 20 )
			state.battery_status = BATTERY_CRITICAL;
		else if ( state.percent <= 40 )
			state.battery_status = BATTERY_LOW;
		else if ( state.percent <= 100 )
			state.battery_status = BATTERY_HIGH;
		else
			state.battery_status = BATTERY_UNKNOWN;
	}
	if ( (state.ac_line_status < 0) || 
			(state.ac_line_status > AC_UNKNOWN) )
		state.ac_line_status = AC_UNKNOWN;
	/* The APM may put this to -1 when the battery is gone */
	if ((state.percent > 100) || (state.percent < 0))
		state.percent = -1;
	/* we can display maximum 99:59 */
	if ( state.time_left > 5999 )
		state.time_left = 5999;

	if (( state.time_left <= 0 ) || ( ! state.system_time )) {
		if ( state.ac_line_status != AC_BATTERY ) {
			state.time_estimate = 0;
			apm_checks_passed = 0;
			last_recorded_percent = state.percent;
		} else {
			++apm_checks_passed;
			if ( last_recorded_percent > state.percent ) {
				discharge_rate = 
				( (double)(last_recorded_percent - 
						state.percent) /
					( (double)(apm_checks_passed * 
					(state.apm_interval/1000000))
						/60 ) );
				if ( discharge_rate > 0 )
					discharge_rate_known = 1;
				apm_checks_passed = 0;
			}
			last_recorded_percent = state.percent;
			if ( discharge_rate_known && (discharge_rate > 0)) {
				tmp = state.percent / discharge_rate;
				/* we can display maximum 99:59 */
				if ( tmp > 5999 )
					tmp = 5999;
				if ( tmp != state.time_estimate ) {
					state.time_estimate = tmp;
					++state.update;
				}
			}
		}
	}
}



syntax highlighted by Code2HTML, v. 0.9.1