1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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 
39 static const char *proc_apm_path = "/proc/apm";
40 static const char *proc_acpi_battery_path = "/proc/acpi/battery";
41 static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
42 static const char *sys_class_power_supply_path = "/sys/class/power_supply";
43 
44 static int
open_power_file(const char * base,const char * node,const char * key)45 open_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 
58 static SDL_bool
read_power_file(const char * base,const char * node,const char * key,char * buf,size_t buflen)59 read_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 
77 static SDL_bool
make_proc_acpi_key_val(char ** _ptr,char ** _key,char ** _val)78 make_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 
124 static void
check_proc_acpi_battery(const char * node,SDL_bool * have_battery,SDL_bool * charging,int * seconds,int * percent)125 check_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 
213 static void
check_proc_acpi_ac_adapter(const char * node,SDL_bool * have_ac)214 check_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 
237 SDL_bool
SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,int * seconds,int * percent)238 SDL_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 
288 static SDL_bool
next_string(char ** _ptr,char ** _str)289 next_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 
314 static SDL_bool
int_string(char * str,int * val)315 int_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 */
323 SDL_bool
SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,int * seconds,int * percent)324 SDL_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 
431 SDL_bool
SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState * state,int * seconds,int * percent)432 SDL_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 
542 static void
check_upower_device(DBusConnection * conn,const char * path,SDL_PowerState * state,int * seconds,int * percent)543 check_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 
613 SDL_bool
SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState * state,int * seconds,int * percent)614 SDL_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