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