dockapps/wmbattery/acpi.c
2014-09-21 10:11:55 +01:00

446 lines
12 KiB
C

/*
* A not-yet-general-purpose ACPI library, by Joey Hess <joey@kitenet.net>
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <fnmatch.h>
#ifdef ACPI_APM
#include "apm.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "acpi.h"
#define SYSFS_PATH "/sys/class/power_supply"
#define ACPI_MAXITEM 8
int acpi_batt_count = 0;
/* Filenames of the battery info files for each system battery. */
char acpi_batt_info[ACPI_MAXITEM][128];
/* Filenames of the battery status files for each system battery. */
char acpi_batt_status[ACPI_MAXITEM][128];
/* Stores battery capacity, or 0 if the battery is absent. */
int acpi_batt_capacity[ACPI_MAXITEM];
int acpi_ac_count = 0;
char acpi_ac_adapter_info[ACPI_MAXITEM][128];
char acpi_ac_adapter_status[ACPI_MAXITEM][128];
char *acpi_labels[] = {
"uevent",
"status",
"Battery",
"Mains",
"POWER_SUPPLY_CAPACITY=",
"POWER_SUPPLY_??????_FULL_DESIGN=", /* CHARGE or ENERGY */
"POWER_SUPPLY_PRESENT=",
"POWER_SUPPLY_??????_NOW=",
"POWER_SUPPLY_CURRENT_NOW=",
"POWER_SUPPLY_STATUS=",
#if ACPI_THERMAL
"thermal_zone",
#endif
"POWER_SUPPLY_ONLINE=",
"POWER_SUPPLY_??????_FULL=",
NULL
};
#if ACPI_THERMAL
int acpi_thermal_count = 0;
char acpi_thermal_info[ACPI_MAXITEM][128];
char acpi_thermal_status[ACPI_MAXITEM][128];
#endif
/* Read in an entire ACPI proc file (well, the first 1024 bytes anyway), and
* return a statically allocated array containing it. */
inline char *get_acpi_file (const char *file) {
int fd;
int end;
static char buf[1024];
fd = open(file, O_RDONLY);
if (fd == -1) return NULL;
end = read(fd, buf, sizeof(buf));
buf[end-1] = '\0';
close(fd);
return buf;
}
int strmcmp(const char *s1, const char *s2)
{
for (; (*s1 == *s2) || (*s2 == '?'); s1++, s2++) {
if (*s1 == '\0')
return 0;
}
if (*s2 == '\0')
return 0;
else
return 1;
}
/* Given a buffer holding an acpi file, searches for the given key in it,
* and returns the numeric value. 0 is returned on failure. */
inline int scan_acpi_num (const char *buf, const char *key) {
char *ptr;
int ret = 0;
do {
ptr = strchr(buf, '\n');
if (!strmcmp(buf, key)) {
if ((ptr = strchr(buf, '='))) {
sscanf(ptr + 1, "%d", &ret);
return ret;
} else {
return 0;
}
}
if (ptr)
ptr++;
buf = ptr;
} while (buf != NULL);
return 0;
}
/* Given a buffer holding an acpi file, searches for the given key in it,
* and returns its value in a statically allocated string. */
inline char *scan_acpi_value (const char *buf, const char *key) {
char *ptr;
static char ret[256];
do {
ptr = strchr(buf, '\n');
if (!strmcmp(buf, key)) {
if ((ptr = strchr(buf, '='))) {
if (sscanf(ptr + 1, "%255s", ret) == 1) {
return ret;
} else {
return NULL;
}
} else {
return NULL;
}
}
if (ptr)
ptr++;
buf = ptr;
} while (buf != NULL);
return NULL;
}
/* Read an ACPI proc file, pull out the requested piece of information, and
* return it (statically allocated string). Returns NULL on error, This is
* the slow, dumb way, fine for initialization or if only one value is needed
* from a file, slow if called many times. */
char *get_acpi_value (const char *file, const char *key) {
char *buf = get_acpi_file(file);
if (! buf) return NULL;
return scan_acpi_value(buf, key);
}
/* Returns the last full charge capacity of a battery.
*/
int get_acpi_batt_capacity(int battery) {
char *s;
s = get_acpi_value(acpi_batt_info[battery], acpi_labels[label_last_full_capacity]);
if (s == NULL) {
return 0;
} else {
return atoi(s);
}
}
/* Comparison function for qsort. */
int _acpi_compare_strings (const void *a, const void *b) {
const char **pa = (const char **)a;
const char **pb = (const char **)b;
return strcasecmp((const char *)*pa, (const char *)*pb);
}
/* Find something (batteries, ac adpaters, etc), and set up a string array
* to hold the paths to info and status files of the things found.
* Returns the number of items found. */
int find_items (char *itemname, char infoarray[ACPI_MAXITEM][128],
char statusarray[ACPI_MAXITEM][128]) {
DIR *dir;
struct dirent *ent;
int num_devices=0;
int i;
char **devices = malloc(ACPI_MAXITEM * sizeof(char *));
char pathname[128];
sprintf(pathname, SYSFS_PATH);
dir = opendir(pathname);
if (dir == NULL)
return 0;
while ((ent = readdir(dir))) {
char filename[128];
char buf[1024];
if (!strcmp(".", ent->d_name) ||
!strcmp("..", ent->d_name))
continue;
snprintf(filename, sizeof(filename), SYSFS_PATH "/%s/type", ent->d_name);
int fd = open(filename, O_RDONLY);
if (fd != -1) {
int end = read(fd, buf, sizeof(buf));
buf[end-1] = '\0';
close(fd);
if (strstr(buf, itemname) != buf)
continue;
}
devices[num_devices]=strdup(ent->d_name);
num_devices++;
if (num_devices >= ACPI_MAXITEM)
break;
}
closedir(dir);
/* Sort, since readdir can return in any order. /sys/ does
* sometimes list BAT1 before BAT0. */
qsort(devices, num_devices, sizeof(char *), _acpi_compare_strings);
for (i = 0; i < num_devices; i++) {
snprintf(infoarray[i], sizeof(infoarray[i]), SYSFS_PATH "/%s/%s", devices[i],
acpi_labels[label_info]);
snprintf(statusarray[i], sizeof(statusarray[i]), SYSFS_PATH "/%s/%s", devices[i],
acpi_labels[label_status]);
free(devices[i]);
}
return num_devices;
}
/* Find batteries, return the number, and set acpi_batt_count to it as well. */
int find_batteries(void) {
int i;
acpi_batt_count = find_items(acpi_labels[label_battery], acpi_batt_info, acpi_batt_status);
for (i = 0; i < acpi_batt_count; i++)
acpi_batt_capacity[i] = get_acpi_batt_capacity(i);
return acpi_batt_count;
}
/* Find AC power adapters, return the number found, and set acpi_ac_count to it
* as well. */
int find_ac_adapters(void) {
acpi_ac_count = find_items(acpi_labels[label_ac_adapter], acpi_ac_adapter_info, acpi_ac_adapter_status);
return acpi_ac_count;
}
#if ACPI_THERMAL
/* Find thermal information sources, return the number found, and set
* thermal_count to it as well. */
int find_thermal(void) {
acpi_thermal_count = find_items(acpi_labels[label_thermal], acpi_thermal_info, acpi_thermal_status);
return acpi_thermal_count;
}
#endif
/* Returns true if the system is on ac power. Call find_ac_adapters first. */
int on_ac_power (void) {
int i;
for (i = 0; i < acpi_ac_count; i++) {
char *online=get_acpi_value(acpi_ac_adapter_info[i], acpi_labels[label_ac_state]);
if (online && atoi(online))
return 1;
else
return 0;
}
return 0;
}
/* See if we have ACPI support and check version. Also find batteries and
* ac power adapters. */
int acpi_supported (void) {
char *version;
DIR *dir;
int num;
if (!(dir = opendir(SYSFS_PATH))) {
return 0;
}
closedir(dir);
/* If kernel is 2.6.21 or newer, version is in
/sys/module/acpi/parameters/acpica_version */
version = get_acpi_file("/sys/module/acpi/parameters/acpica_version");
if (version == NULL) {
return 0;
}
num = atoi(version);
if (num < ACPI_VERSION) {
fprintf(stderr, "ACPI subsystem %s too is old, consider upgrading to %i.\n",
version, ACPI_VERSION);
return 0;
}
find_batteries();
find_ac_adapters();
#if ACPI_THERMAL
find_thermal();
#endif
return 1;
}
#ifdef ACPI_APM
/* Read ACPI info on a given power adapter and battery, and fill the passed
* apm_info struct. */
int acpi_read (int battery, apm_info *info) {
char *buf, *state;
if (acpi_batt_count == 0) {
info->battery_percentage = 0;
info->battery_time = 0;
info->battery_status = BATTERY_STATUS_ABSENT;
acpi_batt_capacity[battery] = 0;
/* Where else would the power come from, eh? ;-) */
info->ac_line_status = 1;
return 0;
}
/* Internally it's zero indexed. */
battery--;
buf = get_acpi_file(acpi_batt_info[battery]);
if (buf == NULL) {
fprintf(stderr, "unable to read %s\n", acpi_batt_info[battery]);
perror("read");
exit(1);
}
info->ac_line_status = 0;
info->battery_flags = 0;
info->using_minutes = 1;
/* Work out if the battery is present, and what percentage of full
* it is and how much time is left. */
if (strcmp(scan_acpi_value(buf, acpi_labels[label_present]), "1") == 0) {
int pcap = scan_acpi_num(buf, acpi_labels[label_remaining_capacity]);
int rate = scan_acpi_num(buf, acpi_labels[label_present_rate]);
if (rate) {
/* time remaining = (current_capacity / discharge rate) */
info->battery_time = (float) pcap / (float) rate * 60;
}
else {
char *rate_s = scan_acpi_value(buf, acpi_labels[label_present_rate]);
if (! rate_s) {
/* Time remaining unknown. */
info->battery_time = 0;
}
else {
/* a zero or unknown in the file; time
* unknown so use a negative one to
* indicate this */
info->battery_time = -1;
}
}
state = scan_acpi_value(buf, acpi_labels[label_charging_state]);
if (state) {
if (state[0] == 'D') { /* discharging */
info->battery_status = BATTERY_STATUS_CHARGING;
/* Expensive ac power check used here
* because AC power might be on even if a
* battery is discharging in some cases. */
info->ac_line_status = on_ac_power();
}
else if (state[0] == 'C' && state[1] == 'h') { /* charging */
info->battery_status = BATTERY_STATUS_CHARGING;
info->ac_line_status = 1;
info->battery_flags = info->battery_flags | BATTERY_FLAGS_CHARGING;
if (rate)
info->battery_time = -1 * (float) (acpi_batt_capacity[battery] - pcap) / (float) rate * 60;
else
info->battery_time = 0;
if (abs(info->battery_time) < 0.5)
info->battery_time = 0;
}
else if (state[0] == 'F') { /* full */
/* charged, on ac power */
info->battery_status = BATTERY_STATUS_HIGH;
info->ac_line_status = 1;
}
else if (state[0] == 'C') { /* not charging, so must be critical */
info->battery_status = BATTERY_STATUS_CRITICAL;
/* Expensive ac power check used here
* because AC power might be on even if a
* battery is critical in some cases. */
info->ac_line_status = on_ac_power();
}
else if (state[0] == 'U') { /* unknown */
info->ac_line_status = on_ac_power();
int current = scan_acpi_num(buf, acpi_labels[label_present_rate]);
if (info->ac_line_status) {
if (current == 0)
info->battery_status = BATTERY_STATUS_HIGH;
else
info->battery_status = BATTERY_STATUS_CHARGING;
}
else {
info->battery_status = BATTERY_STATUS_CHARGING;
}
}
else {
fprintf(stderr, "unknown battery state: %s\n", state);
}
}
else {
/* Battery state unknown. */
info->battery_status = BATTERY_STATUS_ABSENT;
}
if (acpi_batt_capacity[battery] == 0) {
/* The battery was absent, and now is present.
* Well, it might be a different battery. So
* re-probe the battery. */
/* NOTE that this invalidates buf. No accesses of
* buf below this point! */
acpi_batt_capacity[battery] = get_acpi_batt_capacity(battery);
}
else if (pcap > acpi_batt_capacity[battery]) {
/* Battery is somehow charged to greater than max
* capacity. Rescan for a new max capacity. */
find_batteries();
}
if (pcap && acpi_batt_capacity[battery]) {
info->battery_percentage = 100 * pcap / acpi_batt_capacity[battery];
if (info->battery_percentage > 100)
info->battery_percentage = 100;
}
else {
info->battery_percentage = -1;
}
}
else {
info->battery_percentage = 0;
info->battery_time = 0;
info->battery_status = BATTERY_STATUS_ABSENT;
acpi_batt_capacity[battery] = 0;
if (acpi_batt_count == 0) {
/* Where else would the power come from, eh? ;-) */
info->ac_line_status = 1;
}
else {
/* Expensive ac power check. */
info->ac_line_status = on_ac_power();
}
}
return 0;
}
#endif