/* * asapm is the APM and ACPI monitor utility for X Windows * Copyright (c) 1998-2005 Albert 'Tigr' Dorofeev * 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 #include #include #include #include #include #if defined(__FreeBSD__) #include #include #include #endif #if defined(__NetBSD__)||defined(__OpenBSD__) #include #include #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; id_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; } } } } }