1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #ifndef SDL_POWER_DISABLED
24 #if SDL_POWER_LINUX
25 
26 #include <stdio.h>
27 #include <unistd.h>
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <dirent.h>
32 #include <fcntl.h>
33 
34 #include "SDL_power.h"
35 
36 static const char *proc_apm_path = "/proc/apm";
37 static const char *proc_acpi_battery_path = "/proc/acpi/battery";
38 static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
39 static const char *sys_class_power_supply_path = "/sys/class/power_supply";
40 
41 static int
open_power_file(const char * base,const char * node,const char * key)42 open_power_file(const char *base, const char *node, const char *key)
43 {
44     const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3;
45     char *path = (char *) alloca(pathlen);
46     if (path == NULL) {
47         return -1;  /* oh well. */
48     }
49 
50     snprintf(path, pathlen, "%s/%s/%s", base, node, key);
51     return open(path, O_RDONLY);
52 }
53 
54 
55 static SDL_bool
read_power_file(const char * base,const char * node,const char * key,char * buf,size_t buflen)56 read_power_file(const char *base, const char *node, const char *key,
57                 char *buf, size_t buflen)
58 {
59     ssize_t br = 0;
60     const int fd = open_power_file(base, node, key);
61     if (fd == -1) {
62         return SDL_FALSE;
63     }
64     br = read(fd, buf, buflen-1);
65     close(fd);
66     if (br < 0) {
67         return SDL_FALSE;
68     }
69     buf[br] = '\0';             /* null-terminate the string. */
70     return SDL_TRUE;
71 }
72 
73 
74 static SDL_bool
make_proc_acpi_key_val(char ** _ptr,char ** _key,char ** _val)75 make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
76 {
77     char *ptr = *_ptr;
78 
79     while (*ptr == ' ') {
80         ptr++;  /* skip whitespace. */
81     }
82 
83     if (*ptr == '\0') {
84         return SDL_FALSE;  /* EOF. */
85     }
86 
87     *_key = ptr;
88 
89     while ((*ptr != ':') && (*ptr != '\0')) {
90         ptr++;
91     }
92 
93     if (*ptr == '\0') {
94         return SDL_FALSE;  /* (unexpected) EOF. */
95     }
96 
97     *(ptr++) = '\0';  /* terminate the key. */
98 
99     while ((*ptr == ' ') && (*ptr != '\0')) {
100         ptr++;  /* skip whitespace. */
101     }
102 
103     if (*ptr == '\0') {
104         return SDL_FALSE;  /* (unexpected) EOF. */
105     }
106 
107     *_val = ptr;
108 
109     while ((*ptr != '\n') && (*ptr != '\0')) {
110         ptr++;
111     }
112 
113     if (*ptr != '\0') {
114         *(ptr++) = '\0';  /* terminate the value. */
115     }
116 
117     *_ptr = ptr;  /* store for next time. */
118     return SDL_TRUE;
119 }
120 
121 static void
check_proc_acpi_battery(const char * node,SDL_bool * have_battery,SDL_bool * charging,int * seconds,int * percent)122 check_proc_acpi_battery(const char * node, SDL_bool * have_battery,
123                         SDL_bool * charging, int *seconds, int *percent)
124 {
125     const char *base = proc_acpi_battery_path;
126     char info[1024];
127     char state[1024];
128     char *ptr = NULL;
129     char *key = NULL;
130     char *val = NULL;
131     SDL_bool charge = SDL_FALSE;
132     SDL_bool choose = SDL_FALSE;
133     int maximum = -1;
134     int remaining = -1;
135     int secs = -1;
136     int pct = -1;
137 
138     if (!read_power_file(base, node, "state", state, sizeof (state))) {
139         return;
140     } else if (!read_power_file(base, node, "info", info, sizeof (info))) {
141         return;
142     }
143 
144     ptr = &state[0];
145     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
146         if (strcmp(key, "present") == 0) {
147             if (strcmp(val, "yes") == 0) {
148                 *have_battery = SDL_TRUE;
149             }
150         } else if (strcmp(key, "charging state") == 0) {
151             /* !!! FIXME: what exactly _does_ charging/discharging mean? */
152             if (strcmp(val, "charging/discharging") == 0) {
153                 charge = SDL_TRUE;
154             } else if (strcmp(val, "charging") == 0) {
155                 charge = SDL_TRUE;
156             }
157         } else if (strcmp(key, "remaining capacity") == 0) {
158             char *endptr = NULL;
159             const int cvt = (int) strtol(val, &endptr, 10);
160             if (*endptr == ' ') {
161                 remaining = cvt;
162             }
163         }
164     }
165 
166     ptr = &info[0];
167     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
168         if (strcmp(key, "design capacity") == 0) {
169             char *endptr = NULL;
170             const int cvt = (int) strtol(val, &endptr, 10);
171             if (*endptr == ' ') {
172                 maximum = cvt;
173             }
174         }
175     }
176 
177     if ((maximum >= 0) && (remaining >= 0)) {
178         pct = (int) ((((float) remaining) / ((float) maximum)) * 100.0f);
179         if (pct < 0) {
180             pct = 0;
181         } else if (pct > 100) {
182             pct = 100;
183         }
184     }
185 
186     /* !!! FIXME: calculate (secs). */
187 
188     /*
189      * We pick the battery that claims to have the most minutes left.
190      *  (failing a report of minutes, we'll take the highest percent.)
191      */
192     if ((secs < 0) && (*seconds < 0)) {
193         if ((pct < 0) && (*percent < 0)) {
194             choose = SDL_TRUE;  /* at least we know there's a battery. */
195         }
196         if (pct > *percent) {
197             choose = SDL_TRUE;
198         }
199     } else if (secs > *seconds) {
200         choose = SDL_TRUE;
201     }
202 
203     if (choose) {
204         *seconds = secs;
205         *percent = pct;
206         *charging = charge;
207     }
208 }
209 
210 static void
check_proc_acpi_ac_adapter(const char * node,SDL_bool * have_ac)211 check_proc_acpi_ac_adapter(const char * node, SDL_bool * have_ac)
212 {
213     const char *base = proc_acpi_ac_adapter_path;
214     char state[256];
215     char *ptr = NULL;
216     char *key = NULL;
217     char *val = NULL;
218 
219     if (!read_power_file(base, node, "state", state, sizeof (state))) {
220         return;
221     }
222 
223     ptr = &state[0];
224     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
225         if (strcmp(key, "state") == 0) {
226             if (strcmp(val, "on-line") == 0) {
227                 *have_ac = SDL_TRUE;
228             }
229         }
230     }
231 }
232 
233 
234 SDL_bool
SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,int * seconds,int * percent)235 SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
236                                  int *seconds, int *percent)
237 {
238     struct dirent *dent = NULL;
239     DIR *dirp = NULL;
240     SDL_bool have_battery = SDL_FALSE;
241     SDL_bool have_ac = SDL_FALSE;
242     SDL_bool charging = SDL_FALSE;
243 
244     *seconds = -1;
245     *percent = -1;
246     *state = SDL_POWERSTATE_UNKNOWN;
247 
248     dirp = opendir(proc_acpi_battery_path);
249     if (dirp == NULL) {
250         return SDL_FALSE;  /* can't use this interface. */
251     } else {
252         while ((dent = readdir(dirp)) != NULL) {
253             const char *node = dent->d_name;
254             check_proc_acpi_battery(node, &have_battery, &charging,
255                                     seconds, percent);
256         }
257         closedir(dirp);
258     }
259 
260     dirp = opendir(proc_acpi_ac_adapter_path);
261     if (dirp == NULL) {
262         return SDL_FALSE;  /* can't use this interface. */
263     } else {
264         while ((dent = readdir(dirp)) != NULL) {
265             const char *node = dent->d_name;
266             check_proc_acpi_ac_adapter(node, &have_ac);
267         }
268         closedir(dirp);
269     }
270 
271     if (!have_battery) {
272         *state = SDL_POWERSTATE_NO_BATTERY;
273     } else if (charging) {
274         *state = SDL_POWERSTATE_CHARGING;
275     } else if (have_ac) {
276         *state = SDL_POWERSTATE_CHARGED;
277     } else {
278         *state = SDL_POWERSTATE_ON_BATTERY;
279     }
280 
281     return SDL_TRUE;   /* definitive answer. */
282 }
283 
284 
285 static SDL_bool
next_string(char ** _ptr,char ** _str)286 next_string(char **_ptr, char **_str)
287 {
288     char *ptr = *_ptr;
289     char *str = *_str;
290 
291     while (*ptr == ' ') {       /* skip any spaces... */
292         ptr++;
293     }
294 
295     if (*ptr == '\0') {
296         return SDL_FALSE;
297     }
298 
299     str = ptr;
300     while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
301         ptr++;
302 
303     if (*ptr != '\0')
304         *(ptr++) = '\0';
305 
306     *_str = str;
307     *_ptr = ptr;
308     return SDL_TRUE;
309 }
310 
311 static SDL_bool
int_string(char * str,int * val)312 int_string(char *str, int *val)
313 {
314     char *endptr = NULL;
315     *val = (int) strtol(str, &endptr, 0);
316     return ((*str != '\0') && (*endptr == '\0'));
317 }
318 
319 /* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
320 SDL_bool
SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,int * seconds,int * percent)321 SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
322                                 int *seconds, int *percent)
323 {
324     SDL_bool need_details = SDL_FALSE;
325     int ac_status = 0;
326     int battery_status = 0;
327     int battery_flag = 0;
328     int battery_percent = 0;
329     int battery_time = 0;
330     const int fd = open(proc_apm_path, O_RDONLY);
331     char buf[128];
332     char *ptr = &buf[0];
333     char *str = NULL;
334     ssize_t br;
335 
336     if (fd == -1) {
337         return SDL_FALSE;       /* can't use this interface. */
338     }
339 
340     br = read(fd, buf, sizeof (buf) - 1);
341     close(fd);
342 
343     if (br < 0) {
344         return SDL_FALSE;
345     }
346 
347     buf[br] = '\0';             /* null-terminate the string. */
348     if (!next_string(&ptr, &str)) {     /* driver version */
349         return SDL_FALSE;
350     }
351     if (!next_string(&ptr, &str)) {     /* BIOS version */
352         return SDL_FALSE;
353     }
354     if (!next_string(&ptr, &str)) {     /* APM flags */
355         return SDL_FALSE;
356     }
357 
358     if (!next_string(&ptr, &str)) {     /* AC line status */
359         return SDL_FALSE;
360     } else if (!int_string(str, &ac_status)) {
361         return SDL_FALSE;
362     }
363 
364     if (!next_string(&ptr, &str)) {     /* battery status */
365         return SDL_FALSE;
366     } else if (!int_string(str, &battery_status)) {
367         return SDL_FALSE;
368     }
369     if (!next_string(&ptr, &str)) {     /* battery flag */
370         return SDL_FALSE;
371     } else if (!int_string(str, &battery_flag)) {
372         return SDL_FALSE;
373     }
374     if (!next_string(&ptr, &str)) {     /* remaining battery life percent */
375         return SDL_FALSE;
376     }
377     if (str[strlen(str) - 1] == '%') {
378         str[strlen(str) - 1] = '\0';
379     }
380     if (!int_string(str, &battery_percent)) {
381         return SDL_FALSE;
382     }
383 
384     if (!next_string(&ptr, &str)) {     /* remaining battery life time */
385         return SDL_FALSE;
386     } else if (!int_string(str, &battery_time)) {
387         return SDL_FALSE;
388     }
389 
390     if (!next_string(&ptr, &str)) {     /* remaining battery life time units */
391         return SDL_FALSE;
392     } else if (strcmp(str, "min") == 0) {
393         battery_time *= 60;
394     }
395 
396     if (battery_flag == 0xFF) { /* unknown state */
397         *state = SDL_POWERSTATE_UNKNOWN;
398     } else if (battery_flag & (1 << 7)) {       /* no battery */
399         *state = SDL_POWERSTATE_NO_BATTERY;
400     } else if (battery_flag & (1 << 3)) {       /* charging */
401         *state = SDL_POWERSTATE_CHARGING;
402         need_details = SDL_TRUE;
403     } else if (ac_status == 1) {
404         *state = SDL_POWERSTATE_CHARGED;        /* on AC, not charging. */
405         need_details = SDL_TRUE;
406     } else {
407         *state = SDL_POWERSTATE_ON_BATTERY;
408         need_details = SDL_TRUE;
409     }
410 
411     *percent = -1;
412     *seconds = -1;
413     if (need_details) {
414         const int pct = battery_percent;
415         const int secs = battery_time;
416 
417         if (pct >= 0) {         /* -1 == unknown */
418             *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
419         }
420         if (secs >= 0) {        /* -1 == unknown */
421             *seconds = secs;
422         }
423     }
424 
425     return SDL_TRUE;
426 }
427 
428 /* !!! FIXME: implement d-bus queries to org.freedesktop.UPower. */
429 
430 SDL_bool
SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState * state,int * seconds,int * percent)431 SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *seconds, int *percent)
432 {
433     const char *base = sys_class_power_supply_path;
434     struct dirent *dent;
435     DIR *dirp;
436 
437     dirp = opendir(base);
438     if (!dirp) {
439         return SDL_FALSE;
440     }
441 
442     *state = SDL_POWERSTATE_NO_BATTERY;  /* assume we're just plugged in. */
443     *seconds = -1;
444     *percent = -1;
445 
446     while ((dent = readdir(dirp)) != NULL) {
447         const char *name = dent->d_name;
448         SDL_bool choose = SDL_FALSE;
449         char str[64];
450         SDL_PowerState st;
451         int secs;
452         int pct;
453 
454         if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
455             continue;  /* skip these, of course. */
456         } else if (!read_power_file(base, name, "type", str, sizeof (str))) {
457             continue;  /* Don't know _what_ we're looking at. Give up on it. */
458         } else if (SDL_strcmp(str, "Battery\n") != 0) {
459             continue;  /* we don't care about UPS and such. */
460         }
461 
462         /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */
463         if (read_power_file(base, name, "present", str, sizeof (str)) && (SDL_strcmp(str, "0\n") == 0)) {
464             st = SDL_POWERSTATE_NO_BATTERY;
465         } else if (!read_power_file(base, name, "status", str, sizeof (str))) {
466             st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
467         } else if (SDL_strcmp(str, "Charging\n") == 0) {
468             st = SDL_POWERSTATE_CHARGING;
469         } else if (SDL_strcmp(str, "Discharging\n") == 0) {
470             st = SDL_POWERSTATE_ON_BATTERY;
471         } else if ((SDL_strcmp(str, "Full\n") == 0) || (SDL_strcmp(str, "Not charging\n") == 0)) {
472             st = SDL_POWERSTATE_CHARGED;
473         } else {
474             st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
475         }
476 
477         if (!read_power_file(base, name, "capacity", str, sizeof (str))) {
478             pct = -1;
479         } else {
480             pct = SDL_atoi(str);
481             pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
482         }
483 
484         if (!read_power_file(base, name, "time_to_empty_now", str, sizeof (str))) {
485             secs = -1;
486         } else {
487             secs = SDL_atoi(str);
488             secs = (secs <= 0) ? -1 : secs;  /* 0 == unknown */
489         }
490 
491         /*
492          * We pick the battery that claims to have the most minutes left.
493          *  (failing a report of minutes, we'll take the highest percent.)
494          */
495         if ((secs < 0) && (*seconds < 0)) {
496             if ((pct < 0) && (*percent < 0)) {
497                 choose = SDL_TRUE;  /* at least we know there's a battery. */
498             } else if (pct > *percent) {
499                 choose = SDL_TRUE;
500             }
501         } else if (secs > *seconds) {
502             choose = SDL_TRUE;
503         }
504 
505         if (choose) {
506             *seconds = secs;
507             *percent = pct;
508             *state = st;
509         }
510     }
511 
512     closedir(dirp);
513     return SDL_TRUE;  /* don't look any further. */
514 }
515 
516 #endif /* SDL_POWER_LINUX */
517 #endif /* SDL_POWER_DISABLED */
518 
519 /* vi: set ts=4 sw=4 expandtab: */
520