src/power/linux/SDL_syspower.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 30 Jun 2009 06:26:02 +0000
changeset 3206 f735edf338d3
parent 3205 f9a4aa1e1b34
child 3207 65fdb0961d7c
permissions -rw-r--r--
Linux /proc/apm: handle hex and decimal (and octal!) values.
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2009 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 #include "SDL_config.h"
    23 
    24 #ifndef SDL_POWER_DISABLED
    25 #ifdef SDL_POWER_LINUX
    26 
    27 #include <stdio.h>
    28 #include <unistd.h>
    29 
    30 #include <sys/types.h>
    31 #include <sys/stat.h>
    32 #include <dirent.h>
    33 #include <fcntl.h>
    34 
    35 #include "SDL_power.h"
    36 
    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 
    40 static int open_acpi_file(const char *base, const char *node, const char *key)
    41 {
    42     const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3;
    43     char *path = (char *) alloca(pathlen);
    44     if (path == NULL) {
    45         return -1;  /* oh well. */
    46     }
    47 
    48     snprintf(path, pathlen, "%s/%s/%s", base, node, key);
    49     return open(path, O_RDONLY);
    50 }
    51 
    52 
    53 static SDL_bool
    54 load_acpi_file(const char *base, const char *node, const char *key,
    55                char *buf, size_t buflen)
    56 {
    57     ssize_t br = 0;
    58     const int fd = open_acpi_file(base, node, key);
    59     if (fd == -1) {
    60         return SDL_FALSE;
    61     }
    62     br = read(fd, buf, buflen-1);
    63     close(fd);
    64     if (br < 0) {
    65         return SDL_FALSE;
    66     }
    67     buf[br] = '\0';             // null-terminate the string.
    68     return SDL_TRUE;
    69 }
    70 
    71 
    72 static SDL_bool
    73 make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
    74 {
    75     char *ptr = *_ptr;
    76 
    77     while (*ptr == ' ') {
    78         ptr++;  /* skip whitespace. */
    79     }
    80 
    81     if (*ptr == '\0') {
    82         return SDL_FALSE;  /* EOF. */
    83     }
    84 
    85     *_key = ptr;
    86 
    87     while ((*ptr != ':') && (*ptr != '\0')) {
    88         ptr++;
    89     }
    90 
    91     if (*ptr == '\0') {
    92         return SDL_FALSE;  /* (unexpected) EOF. */
    93     }
    94 
    95     *(ptr++) = '\0';  /* terminate the key. */
    96 
    97     while ((*ptr == ' ') && (*ptr != '\0')) {
    98         ptr++;  /* skip whitespace. */
    99     }
   100 
   101     if (*ptr == '\0') {
   102         return SDL_FALSE;  /* (unexpected) EOF. */
   103     }
   104 
   105     *_val = ptr;
   106 
   107     while ((*ptr != '\n') && (*ptr != '\0')) {
   108         ptr++;
   109     }
   110 
   111     if (*ptr != '\0') {
   112         *(ptr++) = '\0';  /* terminate the value. */
   113     }
   114 
   115     *_ptr = ptr;  /* store for next time. */
   116     return SDL_TRUE;
   117 }
   118 
   119 static void
   120 check_proc_acpi_battery(const char * node, SDL_bool * have_battery,
   121                         SDL_bool * charging, int *seconds, int *percent)
   122 {
   123     const char *base = proc_acpi_battery_path;
   124     char info[1024];
   125     char state[1024];
   126     char *ptr = NULL;
   127     char *key = NULL;
   128     char *val = NULL;
   129     SDL_bool charge = SDL_FALSE;
   130     SDL_bool choose = SDL_FALSE;
   131     SDL_bool is_ac = SDL_FALSE;
   132     int maximum = -1;
   133     int remaining = -1;
   134     int secs = -1;
   135     int pct = -1;
   136 
   137     if (!load_acpi_file(base, node, "state", state, sizeof (state))) {
   138         return;
   139     } else if (!load_acpi_file(base, node, "info", info, sizeof (info))) {
   140         return;
   141     }
   142 
   143     ptr = &state[0];
   144     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
   145         if (strcmp(key, "present") == 0) {
   146             if (strcmp(val, "yes") == 0) {
   147                 *have_battery = SDL_TRUE;
   148             }
   149         } else if (strcmp(key, "charging state") == 0) {
   150             /* !!! FIXME: what exactly _does_ charging/discharging mean? */
   151             if (strcmp(val, "charging/discharging") == 0) {
   152                 charge = SDL_TRUE;
   153             } else if (strcmp(val, "charging") == 0) {
   154                 charge = SDL_TRUE;
   155             }
   156         } else if (strcmp(key, "remaining capacity") == 0) {
   157             char *endptr = NULL;
   158             const int cvt = (int) strtol(val, &endptr, 10);
   159             if (*endptr == ' ') {
   160                 remaining = cvt;
   161             }
   162         }
   163     }
   164 
   165     ptr = &info[0];
   166     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
   167         if (strcmp(key, "design capacity") == 0) {
   168             char *endptr = NULL;
   169             const int cvt = (int) strtol(val, &endptr, 10);
   170             if (*endptr == ' ') {
   171                 maximum = cvt;
   172             }
   173         }
   174     }
   175 
   176     if ((maximum >= 0) && (remaining >= 0)) {
   177         pct = (int) ((((float) remaining) / ((float) maximum)) * 100.0f);
   178         if (pct < 0) {
   179             pct = 0;
   180         } else if (pct > 100) {
   181             pct = 100;
   182         }
   183     }
   184 
   185     /* !!! FIXME: calculate (secs). */
   186 
   187     /*
   188      * We pick the battery that claims to have the most minutes left.
   189      *  (failing a report of minutes, we'll take the highest percent.)
   190      */
   191     if ((secs < 0) && (*seconds < 0)) {
   192         if ((pct < 0) && (*percent < 0)) {
   193             choose = SDL_TRUE;  /* at least we know there's a battery. */
   194         }
   195         if (pct > *percent) {
   196             choose = SDL_TRUE;
   197         }
   198     } else if (secs > *seconds) {
   199         choose = SDL_TRUE;
   200     }
   201 
   202     if (choose) {
   203         *seconds = secs;
   204         *percent = pct;
   205         *charging = charge;
   206     }
   207 }
   208 
   209 static void
   210 check_proc_acpi_ac_adapter(const char * node, SDL_bool * have_ac)
   211 {
   212     const char *base = proc_acpi_ac_adapter_path;
   213     char state[256];
   214     char *ptr = NULL;
   215     char *key = NULL;
   216     char *val = NULL;
   217     SDL_bool charge = SDL_FALSE;
   218     SDL_bool choose = SDL_FALSE;
   219     SDL_bool is_ac = SDL_FALSE;
   220     int maximum = -1;
   221     int remaining = -1;
   222     int secs = -1;
   223     int pct = -1;
   224 
   225     if (!load_acpi_file(base, node, "state", state, sizeof (state))) {
   226         return;
   227     }
   228 
   229     ptr = &state[0];
   230     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
   231         if (strcmp(key, "state") == 0) {
   232             if (strcmp(val, "on-line") == 0) {
   233                 *have_ac = SDL_TRUE;
   234             }
   235         }
   236     }
   237 }
   238 
   239 
   240 SDL_bool
   241 SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
   242                                  int *seconds, int *percent)
   243 {
   244     struct dirent *dent = NULL;
   245     DIR *dirp = NULL;
   246     SDL_bool have_battery = SDL_FALSE;
   247     SDL_bool have_ac = SDL_FALSE;
   248     SDL_bool charging = SDL_FALSE;
   249 
   250     *seconds = -1;
   251     *percent = -1;
   252     *state = SDL_POWERSTATE_UNKNOWN;
   253 
   254     dirp = opendir(proc_acpi_battery_path);
   255     if (dirp == NULL) {
   256         return SDL_FALSE;  /* can't use this interface. */
   257     } else {
   258         while ((dent = readdir(dirp)) != NULL) {
   259             const char *node = dent->d_name;
   260             check_proc_acpi_battery(node, &have_battery, &charging,
   261                                     seconds, percent);
   262         }
   263         closedir(dirp);
   264     }
   265 
   266     dirp = opendir(proc_acpi_ac_adapter_path);
   267     if (dirp == NULL) {
   268         return SDL_FALSE;  /* can't use this interface. */
   269     } else {
   270         while ((dent = readdir(dirp)) != NULL) {
   271             const char *node = dent->d_name;
   272             check_proc_acpi_ac_adapter(node, &have_ac);
   273         }
   274         closedir(dirp);
   275     }
   276 
   277     if (!have_battery) {
   278         *state = SDL_POWERSTATE_NO_BATTERY;
   279     } else if (charging) {
   280         *state = SDL_POWERSTATE_CHARGING;
   281     } else if (have_ac) {
   282         *state = SDL_POWERSTATE_CHARGED;
   283     } else {
   284         *state = SDL_POWERSTATE_ON_BATTERY;
   285     }
   286 
   287     return SDL_TRUE;   /* definitive answer. */
   288 }
   289 
   290 
   291 static SDL_bool
   292 next_string(char **_ptr, char **_str)
   293 {
   294     char *ptr = *_ptr;
   295     char *str = *_str;
   296 
   297     while (*ptr == ' ') {       /* skip any spaces... */
   298         ptr++;
   299     }
   300 
   301     if (*ptr == '\0') {
   302         return SDL_FALSE;
   303     }
   304 
   305     str = ptr;
   306     while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
   307         ptr++;
   308 
   309     if (*ptr != '\0')
   310         *(ptr++) = '\0';
   311 
   312     *_str = str;
   313     *_ptr = ptr;
   314     return SDL_TRUE;
   315 }
   316 
   317 static SDL_bool
   318 int_string(char *str, int *val)
   319 {
   320     char *endptr = NULL;
   321     *val = (int) strtol(str, &endptr, 0);
   322     return ((*str != '\0') && (*endptr == '\0'));
   323 }
   324 
   325 /* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
   326 SDL_bool
   327 SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
   328                                 int *seconds, int *percent)
   329 {
   330     SDL_bool need_details = SDL_FALSE;
   331     int ac_status = 0;
   332     int battery_status = 0;
   333     int battery_flag = 0;
   334     int battery_percent = 0;
   335     int battery_time = 0;
   336     const int fd = open("/proc/apm", O_RDONLY);
   337     char buf[128];
   338     char *ptr = &buf[0];
   339     char *str = NULL;
   340     ssize_t br;
   341 
   342     if (fd == -1) {
   343         return SDL_FALSE;       /* can't use this interface. */
   344     }
   345 
   346     br = read(fd, buf, sizeof (buf) - 1);
   347     close(fd);
   348 
   349     if (br < 0) {
   350         return SDL_FALSE;
   351     }
   352 
   353     buf[br] = '\0';             // null-terminate the string.
   354     if (!next_string(&ptr, &str)) {     /* driver version */
   355         return SDL_FALSE;
   356     }
   357     if (!next_string(&ptr, &str)) {     /* BIOS version */
   358         return SDL_FALSE;
   359     }
   360     if (!next_string(&ptr, &str)) {     /* APM flags */
   361         return SDL_FALSE;
   362     }
   363 
   364     if (!next_string(&ptr, &str)) {     /* AC line status */
   365         return SDL_FALSE;
   366     } else if (!int_string(str, &ac_status)) {
   367         return SDL_FALSE;
   368     }
   369 
   370     if (!next_string(&ptr, &str)) {     /* battery status */
   371         return SDL_FALSE;
   372     } else if (!int_string(str, &battery_status)) {
   373         return SDL_FALSE;
   374     }
   375     if (!next_string(&ptr, &str)) {     /* battery flag */
   376         return SDL_FALSE;
   377     } else if (!int_string(str, &battery_flag)) {
   378         return SDL_FALSE;
   379     }
   380     if (!next_string(&ptr, &str)) {     /* remaining battery life percent */
   381         return SDL_FALSE;
   382     }
   383     if (str[strlen(str) - 1] == '%') {
   384         str[strlen(str) - 1] = '\0';
   385     }
   386     if (!int_string(str, &battery_percent)) {
   387         return SDL_FALSE;
   388     }
   389 
   390     if (!next_string(&ptr, &str)) {     /* remaining battery life time */
   391         return SDL_FALSE;
   392     } else if (!int_string(str, &battery_time)) {
   393         return SDL_FALSE;
   394     }
   395 
   396     if (!next_string(&ptr, &str)) {     /* remaining battery life time units */
   397         return SDL_FALSE;
   398     } else if (strcmp(str, "min") == 0) {
   399         battery_time *= 60;
   400     }
   401 
   402     if (battery_flag == 0xFF) { /* unknown state */
   403         *state = SDL_POWERSTATE_UNKNOWN;
   404     } else if (battery_flag & (1 << 7)) {       /* no battery */
   405         *state = SDL_POWERSTATE_NO_BATTERY;
   406     } else if (battery_flag & (1 << 3)) {       /* charging */
   407         *state = SDL_POWERSTATE_CHARGING;
   408         need_details = SDL_TRUE;
   409     } else if (ac_status == 1) {
   410         *state = SDL_POWERSTATE_CHARGED;        /* on AC, not charging. */
   411         need_details = SDL_TRUE;
   412     } else {
   413         *state = SDL_POWERSTATE_ON_BATTERY;
   414         need_details = SDL_TRUE;
   415     }
   416 
   417     *percent = -1;
   418     *seconds = -1;
   419     if (need_details) {
   420         const int pct = battery_percent;
   421         const int secs = battery_time;
   422 
   423         if (pct >= 0) {         /* -1 == unknown */
   424             *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
   425         }
   426         if (secs >= 0) {        /* -1 == unknown */
   427             *seconds = secs;
   428         }
   429     }
   430 
   431     return SDL_TRUE;
   432 }
   433 
   434 #endif /* SDL_POWER_LINUX */
   435 #endif /* SDL_POWER_DISABLED */
   436 
   437 /* vi: set ts=4 sw=4 expandtab: */