1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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#include "../SDL_syspower.h"
36
37#include "../../core/linux/SDL_dbus.h"
38
39static const char *proc_apm_path = "/proc/apm";
40static const char *proc_acpi_battery_path = "/proc/acpi/battery";
41static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
42static const char *sys_class_power_supply_path = "/sys/class/power_supply";
43
44static int
45open_power_file(const char *base, const char *node, const char *key)
46{
47 const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3;
48 char *path = (char *) alloca(pathlen);
49 if (path == NULL) {
50 return -1; /* oh well. */
51 }
52
53 snprintf(path, pathlen, "%s/%s/%s", base, node, key);
54 return open(path, O_RDONLY);
55}
56
57
58static SDL_bool
59read_power_file(const char *base, const char *node, const char *key,
60 char *buf, size_t buflen)
61{
62 ssize_t br = 0;
63 const int fd = open_power_file(base, node, key);
64 if (fd == -1) {
65 return SDL_FALSE;
66 }
67 br = read(fd, buf, buflen-1);
68 close(fd);
69 if (br < 0) {
70 return SDL_FALSE;
71 }
72 buf[br] = '\0'; /* null-terminate the string. */
73 return SDL_TRUE;
74}
75
76
77static SDL_bool
78make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
79{
80 char *ptr = *_ptr;
81
82 while (*ptr == ' ') {
83 ptr++; /* skip whitespace. */
84 }
85
86 if (*ptr == '\0') {
87 return SDL_FALSE; /* EOF. */
88 }
89
90 *_key = ptr;
91
92 while ((*ptr != ':') && (*ptr != '\0')) {
93 ptr++;
94 }
95
96 if (*ptr == '\0') {
97 return SDL_FALSE; /* (unexpected) EOF. */
98 }
99
100 *(ptr++) = '\0'; /* terminate the key. */
101
102 while (*ptr == ' ') {
103 ptr++; /* skip whitespace. */
104 }
105
106 if (*ptr == '\0') {
107 return SDL_FALSE; /* (unexpected) EOF. */
108 }
109
110 *_val = ptr;
111
112 while ((*ptr != '\n') && (*ptr != '\0')) {
113 ptr++;
114 }
115
116 if (*ptr != '\0') {
117 *(ptr++) = '\0'; /* terminate the value. */
118 }
119
120 *_ptr = ptr; /* store for next time. */
121 return SDL_TRUE;
122}
123
124static void
125check_proc_acpi_battery(const char * node, SDL_bool * have_battery,
126 SDL_bool * charging, int *seconds, int *percent)
127{
128 const char *base = proc_acpi_battery_path;
129 char info[1024];
130 char state[1024];
131 char *ptr = NULL;
132 char *key = NULL;
133 char *val = NULL;
134 SDL_bool charge = SDL_FALSE;
135 SDL_bool choose = SDL_FALSE;
136 int maximum = -1;
137 int remaining = -1;
138 int secs = -1;
139 int pct = -1;
140
141 if (!read_power_file(base, node, "state", state, sizeof (state))) {
142 return;
143 } else if (!read_power_file(base, node, "info", info, sizeof (info))) {
144 return;
145 }
146
147 ptr = &state[0];
148 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
149 if (strcmp(key, "present") == 0) {
150 if (strcmp(val, "yes") == 0) {
151 *have_battery = SDL_TRUE;
152 }
153 } else if (strcmp(key, "charging state") == 0) {
154 /* !!! FIXME: what exactly _does_ charging/discharging mean? */
155 if (strcmp(val, "charging/discharging") == 0) {
156 charge = SDL_TRUE;
157 } else if (strcmp(val, "charging") == 0) {
158 charge = SDL_TRUE;
159 }
160 } else if (strcmp(key, "remaining capacity") == 0) {
161 char *endptr = NULL;
162 const int cvt = (int) strtol(val, &endptr, 10);
163 if (*endptr == ' ') {
164 remaining = cvt;
165 }
166 }
167 }
168
169 ptr = &info[0];
170 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
171 if (strcmp(key, "design capacity") == 0) {
172 char *endptr = NULL;
173 const int cvt = (int) strtol(val, &endptr, 10);
174 if (*endptr == ' ') {
175 maximum = cvt;
176 }
177 }
178 }
179
180 if ((maximum >= 0) && (remaining >= 0)) {
181 pct = (int) ((((float) remaining) / ((float) maximum)) * 100.0f);
182 if (pct < 0) {
183 pct = 0;
184 } else if (pct > 100) {
185 pct = 100;
186 }
187 }
188
189 /* !!! FIXME: calculate (secs). */
190
191 /*
192 * We pick the battery that claims to have the most minutes left.
193 * (failing a report of minutes, we'll take the highest percent.)
194 */
195 if ((secs < 0) && (*seconds < 0)) {
196 if ((pct < 0) && (*percent < 0)) {
197 choose = SDL_TRUE; /* at least we know there's a battery. */
198 }
199 if (pct > *percent) {
200 choose = SDL_TRUE;
201 }
202 } else if (secs > *seconds) {
203 choose = SDL_TRUE;
204 }
205
206 if (choose) {
207 *seconds = secs;
208 *percent = pct;
209 *charging = charge;
210 }
211}
212
213static void
214check_proc_acpi_ac_adapter(const char * node, SDL_bool * have_ac)
215{
216 const char *base = proc_acpi_ac_adapter_path;
217 char state[256];
218 char *ptr = NULL;
219 char *key = NULL;
220 char *val = NULL;
221
222 if (!read_power_file(base, node, "state", state, sizeof (state))) {
223 return;
224 }
225
226 ptr = &state[0];
227 while (make_proc_acpi_key_val(&ptr, &key, &val)) {
228 if (strcmp(key, "state") == 0) {
229 if (strcmp(val, "on-line") == 0) {
230 *have_ac = SDL_TRUE;
231 }
232 }
233 }
234}
235
236
237SDL_bool
238SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
239 int *seconds, int *percent)
240{
241 struct dirent *dent = NULL;
242 DIR *dirp = NULL;
243 SDL_bool have_battery = SDL_FALSE;
244 SDL_bool have_ac = SDL_FALSE;
245 SDL_bool charging = SDL_FALSE;
246
247 *seconds = -1;
248 *percent = -1;
249 *state = SDL_POWERSTATE_UNKNOWN;
250
251 dirp = opendir(proc_acpi_battery_path);
252 if (dirp == NULL) {
253 return SDL_FALSE; /* can't use this interface. */
254 } else {
255 while ((dent = readdir(dirp)) != NULL) {
256 const char *node = dent->d_name;
257 check_proc_acpi_battery(node, &have_battery, &charging,
258 seconds, percent);
259 }
260 closedir(dirp);
261 }
262
263 dirp = opendir(proc_acpi_ac_adapter_path);
264 if (dirp == NULL) {
265 return SDL_FALSE; /* can't use this interface. */
266 } else {
267 while ((dent = readdir(dirp)) != NULL) {
268 const char *node = dent->d_name;
269 check_proc_acpi_ac_adapter(node, &have_ac);
270 }
271 closedir(dirp);
272 }
273
274 if (!have_battery) {
275 *state = SDL_POWERSTATE_NO_BATTERY;
276 } else if (charging) {
277 *state = SDL_POWERSTATE_CHARGING;
278 } else if (have_ac) {
279 *state = SDL_POWERSTATE_CHARGED;
280 } else {
281 *state = SDL_POWERSTATE_ON_BATTERY;
282 }
283
284 return SDL_TRUE; /* definitive answer. */
285}
286
287
288static SDL_bool
289next_string(char **_ptr, char **_str)
290{
291 char *ptr = *_ptr;
292 char *str;
293
294 while (*ptr == ' ') { /* skip any spaces... */
295 ptr++;
296 }
297
298 if (*ptr == '\0') {
299 return SDL_FALSE;
300 }
301
302 str = ptr;
303 while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
304 ptr++;
305
306 if (*ptr != '\0')
307 *(ptr++) = '\0';
308
309 *_str = str;
310 *_ptr = ptr;
311 return SDL_TRUE;
312}
313
314static SDL_bool
315int_string(char *str, int *val)
316{
317 char *endptr = NULL;
318 *val = (int) strtol(str, &endptr, 0);
319 return ((*str != '\0') && (*endptr == '\0'));
320}
321
322/* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
323SDL_bool
324SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
325 int *seconds, int *percent)
326{
327 SDL_bool need_details = SDL_FALSE;
328 int ac_status = 0;
329 int battery_status = 0;
330 int battery_flag = 0;
331 int battery_percent = 0;
332 int battery_time = 0;
333 const int fd = open(proc_apm_path, O_RDONLY);
334 char buf[128];
335 char *ptr = &buf[0];
336 char *str = NULL;
337 ssize_t br;
338
339 if (fd == -1) {
340 return SDL_FALSE; /* can't use this interface. */
341 }
342
343 br = read(fd, buf, sizeof (buf) - 1);
344 close(fd);
345
346 if (br < 0) {
347 return SDL_FALSE;
348 }
349
350 buf[br] = '\0'; /* null-terminate the string. */
351 if (!next_string(&ptr, &str)) { /* driver version */
352 return SDL_FALSE;
353 }
354 if (!next_string(&ptr, &str)) { /* BIOS version */
355 return SDL_FALSE;
356 }
357 if (!next_string(&ptr, &str)) { /* APM flags */
358 return SDL_FALSE;
359 }
360
361 if (!next_string(&ptr, &str)) { /* AC line status */
362 return SDL_FALSE;
363 } else if (!int_string(str, &ac_status)) {
364 return SDL_FALSE;
365 }
366
367 if (!next_string(&ptr, &str)) { /* battery status */
368 return SDL_FALSE;
369 } else if (!int_string(str, &battery_status)) {
370 return SDL_FALSE;
371 }
372 if (!next_string(&ptr, &str)) { /* battery flag */
373 return SDL_FALSE;
374 } else if (!int_string(str, &battery_flag)) {
375 return SDL_FALSE;
376 }
377 if (!next_string(&ptr, &str)) { /* remaining battery life percent */
378 return SDL_FALSE;
379 }
380 if (str[strlen(str) - 1] == '%') {
381 str[strlen(str) - 1] = '\0';
382 }
383 if (!int_string(str, &battery_percent)) {
384 return SDL_FALSE;
385 }
386
387 if (!next_string(&ptr, &str)) { /* remaining battery life time */
388 return SDL_FALSE;
389 } else if (!int_string(str, &battery_time)) {
390 return SDL_FALSE;
391 }
392
393 if (!next_string(&ptr, &str)) { /* remaining battery life time units */
394 return SDL_FALSE;
395 } else if (strcmp(str, "min") == 0) {
396 battery_time *= 60;
397 }
398
399 if (battery_flag == 0xFF) { /* unknown state */
400 *state = SDL_POWERSTATE_UNKNOWN;
401 } else if (battery_flag & (1 << 7)) { /* no battery */
402 *state = SDL_POWERSTATE_NO_BATTERY;
403 } else if (battery_flag & (1 << 3)) { /* charging */
404 *state = SDL_POWERSTATE_CHARGING;
405 need_details = SDL_TRUE;
406 } else if (ac_status == 1) {
407 *state = SDL_POWERSTATE_CHARGED; /* on AC, not charging. */
408 need_details = SDL_TRUE;
409 } else {
410 *state = SDL_POWERSTATE_ON_BATTERY;
411 need_details = SDL_TRUE;
412 }
413
414 *percent = -1;
415 *seconds = -1;
416 if (need_details) {
417 const int pct = battery_percent;
418 const int secs = battery_time;
419
420 if (pct >= 0) { /* -1 == unknown */
421 *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
422 }
423 if (secs >= 0) { /* -1 == unknown */
424 *seconds = secs;
425 }
426 }
427
428 return SDL_TRUE;
429}
430
431SDL_bool
432SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *seconds, int *percent)
433{
434 const char *base = sys_class_power_supply_path;
435 struct dirent *dent;
436 DIR *dirp;
437
438 dirp = opendir(base);
439 if (!dirp) {
440 return SDL_FALSE;
441 }
442
443 *state = SDL_POWERSTATE_NO_BATTERY; /* assume we're just plugged in. */
444 *seconds = -1;
445 *percent = -1;
446
447 while ((dent = readdir(dirp)) != NULL) {
448 const char *name = dent->d_name;
449 SDL_bool choose = SDL_FALSE;
450 char str[64];
451 SDL_PowerState st;
452 int secs;
453 int pct;
454 int energy;
455 int power;
456
457 if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
458 continue; /* skip these, of course. */
459 } else if (!read_power_file(base, name, "type", str, sizeof (str))) {
460 continue; /* Don't know _what_ we're looking at. Give up on it. */
461 } else if (SDL_strcmp(str, "Battery\n") != 0) {
462 continue; /* we don't care about UPS and such. */
463 }
464
465 /* if the scope is "device," it might be something like a PS4
466 controller reporting its own battery, and not something that powers
467 the system. Most system batteries don't list a scope at all; we
468 assume it's a system battery if not specified. */
469 if (read_power_file(base, name, "scope", str, sizeof (str))) {
470 if (SDL_strcmp(str, "device\n") == 0) {
471 continue; /* skip external devices with their own batteries. */
472 }
473 }
474
475 /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */
476 if (read_power_file(base, name, "present", str, sizeof (str)) && (SDL_strcmp(str, "0\n") == 0)) {
477 st = SDL_POWERSTATE_NO_BATTERY;
478 } else if (!read_power_file(base, name, "status", str, sizeof (str))) {
479 st = SDL_POWERSTATE_UNKNOWN; /* uh oh */
480 } else if (SDL_strcmp(str, "Charging\n") == 0) {
481 st = SDL_POWERSTATE_CHARGING;
482 } else if (SDL_strcmp(str, "Discharging\n") == 0) {
483 st = SDL_POWERSTATE_ON_BATTERY;
484 } else if ((SDL_strcmp(str, "Full\n") == 0) || (SDL_strcmp(str, "Not charging\n") == 0)) {
485 st = SDL_POWERSTATE_CHARGED;
486 } else {
487 st = SDL_POWERSTATE_UNKNOWN; /* uh oh */
488 }
489
490 if (!read_power_file(base, name, "capacity", str, sizeof (str))) {
491 pct = -1;
492 } else {
493 pct = SDL_atoi(str);
494 pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
495 }
496
497 if (read_power_file(base, name, "time_to_empty_now", str, sizeof (str))) {
498 secs = SDL_atoi(str);
499 secs = (secs <= 0) ? -1 : secs; /* 0 == unknown */
500 } else if (st == SDL_POWERSTATE_ON_BATTERY) {
501 /* energy is Watt*hours and power is Watts */
502 energy = (read_power_file(base, name, "energy_now", str, sizeof (str))) ? SDL_atoi(str) : -1;
503 power = (read_power_file(base, name, "power_now", str, sizeof (str))) ? SDL_atoi(str) : -1;
504 secs = (energy >= 0 && power > 0) ? (3600LL * energy) / power : -1;
505 } else {
506 secs = -1;
507 }
508
509 /*
510 * We pick the battery that claims to have the most minutes left.
511 * (failing a report of minutes, we'll take the highest percent.)
512 */
513 if ((secs < 0) && (*seconds < 0)) {
514 if ((pct < 0) && (*percent < 0)) {
515 choose = SDL_TRUE; /* at least we know there's a battery. */
516 } else if (pct > *percent) {
517 choose = SDL_TRUE;
518 }
519 } else if (secs > *seconds) {
520 choose = SDL_TRUE;
521 }
522
523 if (choose) {
524 *seconds = secs;
525 *percent = pct;
526 *state = st;
527 }
528 }
529
530 closedir(dirp);
531 return SDL_TRUE; /* don't look any further. */
532}
533
534
535/* d-bus queries to org.freedesktop.UPower. */
536#if SDL_USE_LIBDBUS
537#define UPOWER_DBUS_NODE "org.freedesktop.UPower"
538#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
539#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
540#define UPOWER_DEVICE_DBUS_INTERFACE "org.freedesktop.UPower.Device"
541
542static void
543check_upower_device(DBusConnection *conn, const char *path, SDL_PowerState *state, int *seconds, int *percent)
544{
545 SDL_bool choose = SDL_FALSE;
546 SDL_PowerState st;
547 int secs;
548 int pct;
549 Uint32 ui32 = 0;
550 Sint64 si64 = 0;
551 double d = 0.0;
552
553 if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) {
554 return; /* Don't know _what_ we're looking at. Give up on it. */
555 } else if (ui32 != 2) { /* 2==Battery*/
556 return; /* we don't care about UPS and such. */
557 } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) {
558 return;
559 } else if (!ui32) {
560 return; /* we don't care about random devices with batteries, like wireless controllers, etc */
561 } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) {
562 return;
563 } else if (!ui32) {
564 st = SDL_POWERSTATE_NO_BATTERY;
565 } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) {
566 st = SDL_POWERSTATE_UNKNOWN; /* uh oh */
567 } else if (ui32 == 1) { /* 1 == charging */
568 st = SDL_POWERSTATE_CHARGING;
569 } else if ((ui32 == 2) || (ui32 == 3)) { /* 2 == discharging, 3 == empty. */
570 st = SDL_POWERSTATE_ON_BATTERY;
571 } else if (ui32 == 4) { /* 4 == full */
572 st = SDL_POWERSTATE_CHARGED;
573 } else {
574 st = SDL_POWERSTATE_UNKNOWN; /* uh oh */
575 }
576
577 if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) {
578 pct = -1; /* some old/cheap batteries don't set this property. */
579 } else {
580 pct = (int) d;
581 pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
582 }
583
584 if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) {
585 secs = -1;
586 } else {
587 secs = (int) si64;
588 secs = (secs <= 0) ? -1 : secs; /* 0 == unknown */
589 }
590
591 /*
592 * We pick the battery that claims to have the most minutes left.
593 * (failing a report of minutes, we'll take the highest percent.)
594 */
595 if ((secs < 0) && (*seconds < 0)) {
596 if ((pct < 0) && (*percent < 0)) {
597 choose = SDL_TRUE; /* at least we know there's a battery. */
598 } else if (pct > *percent) {
599 choose = SDL_TRUE;
600 }
601 } else if (secs > *seconds) {
602 choose = SDL_TRUE;
603 }
604
605 if (choose) {
606 *seconds = secs;
607 *percent = pct;
608 *state = st;
609 }
610}
611#endif
612
613SDL_bool
614SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *seconds, int *percent)
615{
616 SDL_bool retval = SDL_FALSE;
617
618#if SDL_USE_LIBDBUS
619 SDL_DBusContext *dbus = SDL_DBus_GetContext();
620 char **paths = NULL;
621 int i, numpaths = 0;
622
623 if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices",
624 DBUS_TYPE_INVALID,
625 DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) {
626 return SDL_FALSE; /* try a different approach than UPower. */
627 }
628
629 retval = SDL_TRUE; /* Clearly we can use this interface. */
630 *state = SDL_POWERSTATE_NO_BATTERY; /* assume we're just plugged in. */
631 *seconds = -1;
632 *percent = -1;
633
634 for (i = 0; i < numpaths; i++) {
635 check_upower_device(dbus->system_conn, paths[i], state, seconds, percent);
636 }
637
638 if (dbus) {
639 dbus->free_string_array(paths);
640 }
641#endif /* SDL_USE_LIBDBUS */
642
643 return retval;
644}
645
646#endif /* SDL_POWER_LINUX */
647#endif /* SDL_POWER_DISABLED */
648
649/* vi: set ts=4 sw=4 expandtab: */
650