Remove trailing whitespace.
[dockapps.git] / wmbattery / acpi.c
blob39b662e6b11db65e5c6a6a9ef4285a40394be393
1 /*
2 * A not-yet-general-purpose ACPI library, by Joey Hess <joey@kitenet.net>
3 */
5 #include <unistd.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <sys/types.h>
9 #include <dirent.h>
10 #include <string.h>
11 #include <fnmatch.h>
12 #ifdef ACPI_APM
13 #include "apm.h"
14 #endif
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
19 #include "acpi.h"
21 #define SYSFS_PATH "/sys/class/power_supply"
22 #define ACPI_MAXITEM 8
24 int acpi_batt_count = 0;
25 /* Filenames of the battery info files for each system battery. */
26 char acpi_batt_info[ACPI_MAXITEM][128];
27 /* Filenames of the battery status files for each system battery. */
28 char acpi_batt_status[ACPI_MAXITEM][128];
29 /* Stores battery capacity, or 0 if the battery is absent. */
30 int acpi_batt_capacity[ACPI_MAXITEM];
32 int acpi_ac_count = 0;
33 char acpi_ac_adapter_info[ACPI_MAXITEM][128];
34 char acpi_ac_adapter_status[ACPI_MAXITEM][128];
36 char *acpi_labels[] = {
37 "uevent",
38 "status",
39 "Battery",
40 "Mains",
41 "POWER_SUPPLY_CAPACITY=",
42 "POWER_SUPPLY_??????_FULL_DESIGN=", /* CHARGE or ENERGY */
43 "POWER_SUPPLY_PRESENT=",
44 "POWER_SUPPLY_??????_NOW=",
45 "POWER_SUPPLY_CURRENT_NOW=",
46 "POWER_SUPPLY_STATUS=",
47 #if ACPI_THERMAL
48 "thermal_zone",
49 #endif
50 "POWER_SUPPLY_ONLINE=",
51 "POWER_SUPPLY_??????_FULL=",
52 NULL
55 #if ACPI_THERMAL
56 int acpi_thermal_count = 0;
57 char acpi_thermal_info[ACPI_MAXITEM][128];
58 char acpi_thermal_status[ACPI_MAXITEM][128];
59 #endif
61 /* Read in an entire ACPI proc file (well, the first 1024 bytes anyway), and
62 * return a statically allocated array containing it. */
63 inline char *get_acpi_file (const char *file) {
64 int fd;
65 int end;
66 static char buf[1024];
67 fd = open(file, O_RDONLY);
68 if (fd == -1) return NULL;
69 end = read(fd, buf, sizeof(buf));
70 buf[end-1] = '\0';
71 close(fd);
72 return buf;
75 int strmcmp(const char *s1, const char *s2)
77 for (; (*s1 == *s2) || (*s2 == '?'); s1++, s2++) {
78 if (*s1 == '\0')
79 return 0;
81 if (*s2 == '\0')
82 return 0;
83 else
84 return 1;
87 /* Given a buffer holding an acpi file, searches for the given key in it,
88 * and returns the numeric value. 0 is returned on failure. */
89 inline int scan_acpi_num (const char *buf, const char *key) {
90 char *ptr;
91 int ret = 0;
93 do {
94 ptr = strchr(buf, '\n');
95 if (!strmcmp(buf, key)) {
96 if ((ptr = strchr(buf, '='))) {
97 sscanf(ptr + 1, "%d", &ret);
98 return ret;
99 } else {
100 return 0;
103 if (ptr)
104 ptr++;
105 buf = ptr;
106 } while (buf != NULL);
107 return 0;
110 /* Given a buffer holding an acpi file, searches for the given key in it,
111 * and returns its value in a statically allocated string. */
112 inline char *scan_acpi_value (const char *buf, const char *key) {
113 char *ptr;
114 static char ret[256];
116 do {
117 ptr = strchr(buf, '\n');
118 if (!strmcmp(buf, key)) {
119 if ((ptr = strchr(buf, '='))) {
120 if (sscanf(ptr + 1, "%255s", ret) == 1) {
121 return ret;
122 } else {
123 return NULL;
125 } else {
126 return NULL;
129 if (ptr)
130 ptr++;
131 buf = ptr;
132 } while (buf != NULL);
133 return NULL;
136 /* Read an ACPI proc file, pull out the requested piece of information, and
137 * return it (statically allocated string). Returns NULL on error, This is
138 * the slow, dumb way, fine for initialization or if only one value is needed
139 * from a file, slow if called many times. */
140 char *get_acpi_value (const char *file, const char *key) {
141 char *buf = get_acpi_file(file);
142 if (! buf) return NULL;
143 return scan_acpi_value(buf, key);
146 /* Returns the last full charge capacity of a battery.
148 int get_acpi_batt_capacity(int battery) {
149 char *s;
151 s = get_acpi_value(acpi_batt_info[battery], acpi_labels[label_last_full_capacity]);
152 if (s == NULL) {
153 return 0;
154 } else {
155 return atoi(s);
159 /* Comparison function for qsort. */
160 int _acpi_compare_strings (const void *a, const void *b) {
161 const char **pa = (const char **)a;
162 const char **pb = (const char **)b;
163 return strcasecmp((const char *)*pa, (const char *)*pb);
166 /* Find something (batteries, ac adpaters, etc), and set up a string array
167 * to hold the paths to info and status files of the things found.
168 * Returns the number of items found. */
169 int find_items (char *itemname, char infoarray[ACPI_MAXITEM][128],
170 char statusarray[ACPI_MAXITEM][128]) {
171 DIR *dir;
172 struct dirent *ent;
173 int num_devices=0;
174 int i;
175 char **devices = malloc(ACPI_MAXITEM * sizeof(char *));
177 char pathname[128];
179 sprintf(pathname, SYSFS_PATH);
181 dir = opendir(pathname);
182 if (dir == NULL)
183 return 0;
184 while ((ent = readdir(dir))) {
185 char filename[128];
186 char buf[1024];
188 if (!strcmp(".", ent->d_name) ||
189 !strcmp("..", ent->d_name))
190 continue;
192 snprintf(filename, sizeof(filename), SYSFS_PATH "/%s/type", ent->d_name);
193 int fd = open(filename, O_RDONLY);
194 if (fd != -1) {
195 int end = read(fd, buf, sizeof(buf));
196 buf[end-1] = '\0';
197 close(fd);
198 if (strstr(buf, itemname) != buf)
199 continue;
202 devices[num_devices]=strdup(ent->d_name);
203 num_devices++;
204 if (num_devices >= ACPI_MAXITEM)
205 break;
207 closedir(dir);
209 /* Sort, since readdir can return in any order. /sys/ does
210 * sometimes list BAT1 before BAT0. */
211 qsort(devices, num_devices, sizeof(char *), _acpi_compare_strings);
213 for (i = 0; i < num_devices; i++) {
214 snprintf(infoarray[i], sizeof(infoarray[i]), SYSFS_PATH "/%s/%s", devices[i],
215 acpi_labels[label_info]);
216 snprintf(statusarray[i], sizeof(statusarray[i]), SYSFS_PATH "/%s/%s", devices[i],
217 acpi_labels[label_status]);
218 free(devices[i]);
221 return num_devices;
224 /* Find batteries, return the number, and set acpi_batt_count to it as well. */
225 int find_batteries(void) {
226 int i;
227 acpi_batt_count = find_items(acpi_labels[label_battery], acpi_batt_info, acpi_batt_status);
228 for (i = 0; i < acpi_batt_count; i++)
229 acpi_batt_capacity[i] = get_acpi_batt_capacity(i);
230 return acpi_batt_count;
233 /* Find AC power adapters, return the number found, and set acpi_ac_count to it
234 * as well. */
235 int find_ac_adapters(void) {
236 acpi_ac_count = find_items(acpi_labels[label_ac_adapter], acpi_ac_adapter_info, acpi_ac_adapter_status);
237 return acpi_ac_count;
240 #if ACPI_THERMAL
241 /* Find thermal information sources, return the number found, and set
242 * thermal_count to it as well. */
243 int find_thermal(void) {
244 acpi_thermal_count = find_items(acpi_labels[label_thermal], acpi_thermal_info, acpi_thermal_status);
245 return acpi_thermal_count;
247 #endif
249 /* Returns true if the system is on ac power. Call find_ac_adapters first. */
250 int on_ac_power (void) {
251 int i;
252 for (i = 0; i < acpi_ac_count; i++) {
253 char *online=get_acpi_value(acpi_ac_adapter_info[i], acpi_labels[label_ac_state]);
254 if (online && atoi(online))
255 return 1;
256 else
257 return 0;
259 return 0;
262 /* See if we have ACPI support and check version. Also find batteries and
263 * ac power adapters. */
264 int acpi_supported (void) {
265 char *version;
266 DIR *dir;
267 int num;
269 if (!(dir = opendir(SYSFS_PATH))) {
270 return 0;
272 closedir(dir);
274 /* If kernel is 2.6.21 or newer, version is in
275 /sys/module/acpi/parameters/acpica_version */
277 version = get_acpi_file("/sys/module/acpi/parameters/acpica_version");
278 if (version == NULL) {
279 return 0;
281 num = atoi(version);
282 if (num < ACPI_VERSION) {
283 fprintf(stderr, "ACPI subsystem %s too is old, consider upgrading to %i.\n",
284 version, ACPI_VERSION);
285 return 0;
288 find_batteries();
289 find_ac_adapters();
290 #if ACPI_THERMAL
291 find_thermal();
292 #endif
294 return 1;
297 #ifdef ACPI_APM
298 /* Read ACPI info on a given power adapter and battery, and fill the passed
299 * apm_info struct. */
300 int acpi_read (int battery, apm_info *info) {
301 char *buf, *state;
303 if (acpi_batt_count == 0) {
304 info->battery_percentage = 0;
305 info->battery_time = 0;
306 info->battery_status = BATTERY_STATUS_ABSENT;
307 acpi_batt_capacity[battery] = 0;
308 /* Where else would the power come from, eh? ;-) */
309 info->ac_line_status = 1;
310 return 0;
313 /* Internally it's zero indexed. */
314 battery--;
316 buf = get_acpi_file(acpi_batt_info[battery]);
317 if (buf == NULL) {
318 fprintf(stderr, "unable to read %s\n", acpi_batt_info[battery]);
319 perror("read");
320 exit(1);
323 info->ac_line_status = 0;
324 info->battery_flags = 0;
325 info->using_minutes = 1;
327 /* Work out if the battery is present, and what percentage of full
328 * it is and how much time is left. */
329 if (strcmp(scan_acpi_value(buf, acpi_labels[label_present]), "1") == 0) {
330 int pcap = scan_acpi_num(buf, acpi_labels[label_remaining_capacity]);
331 int rate = scan_acpi_num(buf, acpi_labels[label_present_rate]);
332 if (rate) {
333 /* time remaining = (current_capacity / discharge rate) */
334 info->battery_time = (float) pcap / (float) rate * 60;
336 else {
337 char *rate_s = scan_acpi_value(buf, acpi_labels[label_present_rate]);
338 if (! rate_s) {
339 /* Time remaining unknown. */
340 info->battery_time = 0;
342 else {
343 /* a zero or unknown in the file; time
344 * unknown so use a negative one to
345 * indicate this */
346 info->battery_time = -1;
350 state = scan_acpi_value(buf, acpi_labels[label_charging_state]);
351 if (state) {
352 if (state[0] == 'D') { /* discharging */
353 info->battery_status = BATTERY_STATUS_CHARGING;
354 /* Expensive ac power check used here
355 * because AC power might be on even if a
356 * battery is discharging in some cases. */
357 info->ac_line_status = on_ac_power();
359 else if (state[0] == 'C' && state[1] == 'h') { /* charging */
360 info->battery_status = BATTERY_STATUS_CHARGING;
361 info->ac_line_status = 1;
362 info->battery_flags = info->battery_flags | BATTERY_FLAGS_CHARGING;
363 if (rate)
364 info->battery_time = -1 * (float) (acpi_batt_capacity[battery] - pcap) / (float) rate * 60;
365 else
366 info->battery_time = 0;
367 if (abs(info->battery_time) < 0.5)
368 info->battery_time = 0;
370 else if (state[0] == 'F') { /* full */
371 /* charged, on ac power */
372 info->battery_status = BATTERY_STATUS_HIGH;
373 info->ac_line_status = 1;
375 else if (state[0] == 'C') { /* not charging, so must be critical */
376 info->battery_status = BATTERY_STATUS_CRITICAL;
377 /* Expensive ac power check used here
378 * because AC power might be on even if a
379 * battery is critical in some cases. */
380 info->ac_line_status = on_ac_power();
382 else if (state[0] == 'U') { /* unknown */
383 info->ac_line_status = on_ac_power();
384 int current = scan_acpi_num(buf, acpi_labels[label_present_rate]);
385 if (info->ac_line_status) {
386 if (current == 0)
387 info->battery_status = BATTERY_STATUS_HIGH;
388 else
389 info->battery_status = BATTERY_STATUS_CHARGING;
391 else {
392 info->battery_status = BATTERY_STATUS_CHARGING;
395 else {
396 fprintf(stderr, "unknown battery state: %s\n", state);
399 else {
400 /* Battery state unknown. */
401 info->battery_status = BATTERY_STATUS_ABSENT;
404 if (acpi_batt_capacity[battery] == 0) {
405 /* The battery was absent, and now is present.
406 * Well, it might be a different battery. So
407 * re-probe the battery. */
408 /* NOTE that this invalidates buf. No accesses of
409 * buf below this point! */
410 acpi_batt_capacity[battery] = get_acpi_batt_capacity(battery);
412 else if (pcap > acpi_batt_capacity[battery]) {
413 /* Battery is somehow charged to greater than max
414 * capacity. Rescan for a new max capacity. */
415 find_batteries();
418 if (pcap && acpi_batt_capacity[battery]) {
419 info->battery_percentage = 100 * pcap / acpi_batt_capacity[battery];
420 if (info->battery_percentage > 100)
421 info->battery_percentage = 100;
423 else {
424 info->battery_percentage = -1;
428 else {
429 info->battery_percentage = 0;
430 info->battery_time = 0;
431 info->battery_status = BATTERY_STATUS_ABSENT;
432 acpi_batt_capacity[battery] = 0;
433 if (acpi_batt_count == 0) {
434 /* Where else would the power come from, eh? ;-) */
435 info->ac_line_status = 1;
437 else {
438 /* Expensive ac power check. */
439 info->ac_line_status = on_ac_power();
443 return 0;
445 #endif