src/power/linux/SDL_syspower.c
author Ryan C. Gordon
Tue, 29 Dec 2015 02:29:56 -0500
changeset 9986 081fbd89a347
parent 9700 d8cc85e50561
child 9998 f67cf37e9cd4
permissions -rw-r--r--
NetBSD: improved joystick support (thanks, Thomas!).

This patch skips non-joystick HID devices and gives joysticks on NetBSD
a human readable name.

Fixes Bugzilla #3178.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 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
    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
    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
    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
   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
   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
   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
   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
   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
   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
   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: */