wmclockmon: import version 0.8.0
[dockapps.git] / wmbattery / acpi.c
blob219e7d8fa1131c2f88bf166338b5d9654e183dcd
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)
65 int fd;
66 int end;
67 static char buf[1024];
68 fd = open(file, O_RDONLY);
69 if (fd == -1)
70 return NULL;
71 end = read(fd, buf, sizeof(buf) - 1);
72 buf[end] = '\0';
73 close(fd);
74 return buf;
77 int strmcmp(const char *s1, const char *s2)
79 for (; (*s1 == *s2) || (*s2 == '?'); s1++, s2++) {
80 if (*s1 == '\0')
81 return 0;
83 if (*s2 == '\0')
84 return 0;
85 else
86 return 1;
89 /* Given a buffer holding an acpi file, searches for the given key in it,
90 * and returns the numeric value. 0 is returned on failure. */
91 inline int scan_acpi_num(const char *buf, const char *key)
93 char *ptr;
94 int ret = 0;
96 do {
97 ptr = strchr(buf, '\n');
98 if (!strmcmp(buf, key)) {
99 ptr = strchr(buf, '=');
100 if (ptr) {
101 sscanf(ptr + 1, "%d", &ret);
102 return ret;
103 } else {
104 return 0;
107 if (ptr)
108 ptr++;
109 buf = ptr;
110 } while (buf != NULL);
111 return 0;
114 /* Given a buffer holding an acpi file, searches for the given key in it,
115 * and returns its value in a statically allocated string. */
116 inline char *scan_acpi_value(const char *buf, const char *key)
118 char *ptr;
119 static char ret[256];
121 do {
122 ptr = strchr(buf, '\n');
123 if (!strmcmp(buf, key)) {
124 ptr = strchr(buf, '=');
125 if (ptr) {
126 if (sscanf(ptr + 1, "%255s", ret) == 1)
127 return ret;
128 else
129 return NULL;
130 } else {
131 return NULL;
134 if (ptr)
135 ptr++;
136 buf = ptr;
137 } while (buf != NULL);
138 return NULL;
141 /* Read an ACPI proc file, pull out the requested piece of information, and
142 * return it (statically allocated string). Returns NULL on error, This is
143 * the slow, dumb way, fine for initialization or if only one value is needed
144 * from a file, slow if called many times. */
145 char *get_acpi_value(const char *file, const char *key)
147 char *buf = get_acpi_file(file);
148 if (!buf)
149 return NULL;
150 return scan_acpi_value(buf, key);
153 /* Returns the last full charge capacity of a battery.
155 int get_acpi_batt_capacity(int battery)
157 char *s;
159 s = get_acpi_value(acpi_batt_info[battery], acpi_labels[label_last_full_capacity]);
160 if (s == NULL)
161 return 0;
162 else
163 return atoi(s);
166 /* Comparison function for qsort. */
167 int _acpi_compare_strings(const void *a, const void *b)
169 const char **pa = (const char **)a;
170 const char **pb = (const char **)b;
171 return strcasecmp((const char *)*pa, (const char *)*pb);
174 /* Find something (batteries, ac adpaters, etc), and set up a string array
175 * to hold the paths to info and status files of the things found.
176 * Returns the number of items found. */
177 int find_items(char *itemname, char infoarray[ACPI_MAXITEM][128],
178 char statusarray[ACPI_MAXITEM][128])
180 DIR *dir;
181 struct dirent *ent;
182 int num_devices = 0;
183 int i;
184 char **devices = malloc(ACPI_MAXITEM * sizeof(char *));
186 dir = opendir(SYSFS_PATH);
187 if (dir == NULL) {
188 free(devices);
189 return 0;
191 while ((ent = readdir(dir))) {
192 char filename[sizeof(SYSFS_PATH) + 1 + sizeof(ent->d_name) + 6];
193 char buf[1024];
195 if (!strcmp(".", ent->d_name) ||
196 !strcmp("..", ent->d_name))
197 continue;
199 snprintf(filename, sizeof(filename), SYSFS_PATH "/%s/type", ent->d_name);
200 int fd = open(filename, O_RDONLY);
201 if (fd != -1) {
202 int end = read(fd, buf, sizeof(buf));
203 buf[end-1] = '\0';
204 close(fd);
205 if (strstr(buf, itemname) != buf)
206 continue;
209 devices[num_devices] = strdup(ent->d_name);
210 num_devices++;
211 if (num_devices >= ACPI_MAXITEM)
212 break;
214 closedir(dir);
216 /* Sort, since readdir can return in any order. /sys/ does
217 * sometimes list BAT1 before BAT0. */
218 qsort(devices, num_devices, sizeof(char *), _acpi_compare_strings);
220 for (i = 0; i < num_devices; i++) {
221 snprintf(infoarray[i], sizeof(infoarray[i]), SYSFS_PATH "/%s/%s", devices[i],
222 acpi_labels[label_info]);
223 snprintf(statusarray[i], sizeof(statusarray[i]), SYSFS_PATH "/%s/%s", devices[i],
224 acpi_labels[label_status]);
225 free(devices[i]);
228 free(devices);
229 return num_devices;
232 /* Find batteries, return the number, and set acpi_batt_count to it as well. */
233 int find_batteries(void)
235 int i;
236 acpi_batt_count = find_items(acpi_labels[label_battery], acpi_batt_info, acpi_batt_status);
237 for (i = 0; i < acpi_batt_count; i++)
238 acpi_batt_capacity[i] = get_acpi_batt_capacity(i);
239 return acpi_batt_count;
242 /* Find AC power adapters, return the number found, and set acpi_ac_count to it
243 * as well. */
244 int find_ac_adapters(void)
246 acpi_ac_count = find_items(acpi_labels[label_ac_adapter], acpi_ac_adapter_info, acpi_ac_adapter_status);
247 return acpi_ac_count;
250 #if ACPI_THERMAL
251 /* Find thermal information sources, return the number found, and set
252 * thermal_count to it as well. */
253 int find_thermal(void)
255 acpi_thermal_count = find_items(acpi_labels[label_thermal], acpi_thermal_info, acpi_thermal_status);
256 return acpi_thermal_count;
258 #endif
260 /* Returns true if the system is on ac power. Call find_ac_adapters first. */
261 int on_ac_power(void)
263 int i;
264 for (i = 0; i < acpi_ac_count; i++) {
265 char *online = get_acpi_value(acpi_ac_adapter_info[i], acpi_labels[label_ac_state]);
266 if (online && atoi(online))
267 return 1;
268 else
269 return 0;
271 return 0;
274 /* See if we have ACPI support and check version. Also find batteries and
275 * ac power adapters. */
276 int acpi_supported(void)
278 char *version;
279 DIR *dir;
280 int num;
282 dir = opendir(SYSFS_PATH);
283 if (!dir)
284 return 0;
285 closedir(dir);
287 /* If kernel is 2.6.21 or newer, version is in
288 /sys/module/acpi/parameters/acpica_version */
290 version = get_acpi_file("/sys/module/acpi/parameters/acpica_version");
291 if (version == NULL)
292 return 0;
293 num = atoi(version);
294 if (num < ACPI_VERSION) {
295 fprintf(stderr, "ACPI subsystem %s too is old, consider upgrading to %i.\n",
296 version, ACPI_VERSION);
297 return 0;
300 find_batteries();
301 find_ac_adapters();
302 #if ACPI_THERMAL
303 find_thermal();
304 #endif
306 return 1;
309 #ifdef ACPI_APM
310 /* Read ACPI info on a given power adapter and battery, and fill the passed
311 * apm_info struct. */
312 int acpi_read(int battery, apm_info *info)
314 char *buf, *state;
316 if (acpi_batt_count == 0) {
317 info->battery_percentage = 0;
318 info->battery_time = 0;
319 info->battery_status = BATTERY_STATUS_ABSENT;
320 acpi_batt_capacity[battery] = 0;
321 /* Where else would the power come from, eh? ;-) */
322 info->ac_line_status = 1;
323 return 0;
326 /* Internally it's zero indexed. */
327 battery--;
329 buf = get_acpi_file(acpi_batt_info[battery]);
330 if (buf == NULL) {
331 fprintf(stderr, "unable to read %s\n", acpi_batt_info[battery]);
332 perror("read");
333 exit(1);
336 info->ac_line_status = 0;
337 info->battery_flags = 0;
338 info->using_minutes = 1;
340 /* Work out if the battery is present, and what percentage of full
341 * it is and how much time is left. */
342 if (strcmp(scan_acpi_value(buf, acpi_labels[label_present]), "1") == 0) {
343 int pcap = scan_acpi_num(buf, acpi_labels[label_remaining_capacity]);
344 int rate = scan_acpi_num(buf, acpi_labels[label_present_rate]);
345 if (rate) {
346 /* time remaining = (current_capacity / discharge rate) */
347 info->battery_time = (float) pcap / (float) rate * 60;
348 } else {
349 char *rate_s = scan_acpi_value(buf, acpi_labels[label_present_rate]);
350 if (!rate_s) {
351 /* Time remaining unknown. */
352 info->battery_time = 0;
353 } else {
354 /* a zero or unknown in the file; time
355 * unknown so use a negative one to
356 * indicate this */
357 info->battery_time = -1;
361 state = scan_acpi_value(buf, acpi_labels[label_charging_state]);
362 if (state) {
363 if (state[0] == 'D') { /* discharging */
364 info->battery_status = BATTERY_STATUS_CHARGING;
365 /* Expensive ac power check used here
366 * because AC power might be on even if a
367 * battery is discharging in some cases. */
368 info->ac_line_status = on_ac_power();
369 } else if (state[0] == 'C' && state[1] == 'h') { /* charging */
370 info->battery_status = BATTERY_STATUS_CHARGING;
371 info->ac_line_status = 1;
372 info->battery_flags = info->battery_flags | BATTERY_FLAGS_CHARGING;
373 if (rate)
374 info->battery_time = -1 * (float) (acpi_batt_capacity[battery] - pcap) /
375 (float) rate * 60;
376 else
377 info->battery_time = 0;
378 if (abs(info->battery_time) < 0.5)
379 info->battery_time = 0;
380 } else if (state[0] == 'F') { /* full */
381 /* charged, on ac power */
382 info->battery_status = BATTERY_STATUS_HIGH;
383 info->ac_line_status = 1;
384 } else if (state[0] == 'C') { /* not charging, so must be critical */
385 info->battery_status = BATTERY_STATUS_CRITICAL;
386 /* Expensive ac power check used here
387 * because AC power might be on even if a
388 * battery is critical in some cases. */
389 info->ac_line_status = on_ac_power();
390 } else if (state[0] == 'U') { /* unknown */
391 info->ac_line_status = on_ac_power();
392 int current = scan_acpi_num(buf, acpi_labels[label_present_rate]);
393 if (info->ac_line_status) {
394 if (current == 0)
395 info->battery_status = BATTERY_STATUS_HIGH;
396 else
397 info->battery_status = BATTERY_STATUS_CHARGING;
398 } else {
399 info->battery_status = BATTERY_STATUS_CHARGING;
401 } else {
402 fprintf(stderr, "unknown battery state: %s\n", state);
404 } else {
405 /* Battery state unknown. */
406 info->battery_status = BATTERY_STATUS_ABSENT;
409 if (acpi_batt_capacity[battery] == 0) {
410 /* The battery was absent, and now is present.
411 * Well, it might be a different battery. So
412 * re-probe the battery. */
413 /* NOTE that this invalidates buf. No accesses of
414 * buf below this point! */
415 acpi_batt_capacity[battery] = get_acpi_batt_capacity(battery);
416 } else if (pcap > acpi_batt_capacity[battery]) {
417 /* Battery is somehow charged to greater than max
418 * capacity. Rescan for a new max capacity. */
419 find_batteries();
422 if (pcap && acpi_batt_capacity[battery]) {
423 info->battery_percentage = 100 * pcap / acpi_batt_capacity[battery];
424 if (info->battery_percentage > 100)
425 info->battery_percentage = 100;
426 } else {
427 info->battery_percentage = -1;
430 } else {
431 info->battery_percentage = 0;
432 info->battery_time = 0;
433 info->battery_status = BATTERY_STATUS_ABSENT;
434 acpi_batt_capacity[battery] = 0;
435 if (acpi_batt_count == 0) {
436 /* Where else would the power come from, eh? ;-) */
437 info->ac_line_status = 1;
438 } else {
439 /* Expensive ac power check. */
440 info->ac_line_status = on_ac_power();
444 return 0;
446 #endif