/*
* 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