src/power/linux/SDL_syspower.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 30 Jun 2009 06:27:08 +0000
changeset 3207 65fdb0961d7c
parent 3206 f735edf338d3
child 3697 f7b03b6838cb
permissions -rw-r--r--
Moved hardcoding of "/proc/apm" elsewhere.
     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_apm_path = "/proc/apm";
    38 static const char *proc_acpi_battery_path = "/proc/acpi/battery";
    39 static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
    40 
    41 static int open_acpi_file(const char *base, const char *node, const char *key)
    42 {
    43     const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3;
    44     char *path = (char *) alloca(pathlen);
    45     if (path == NULL) {
    46         return -1;  /* oh well. */
    47     }
    48 
    49     snprintf(path, pathlen, "%s/%s/%s", base, node, key);
    50     return open(path, O_RDONLY);
    51 }
    52 
    53 
    54 static SDL_bool
    55 load_acpi_file(const char *base, const char *node, const char *key,
    56                char *buf, size_t buflen)
    57 {
    58     ssize_t br = 0;
    59     const int fd = open_acpi_file(base, node, key);
    60     if (fd == -1) {
    61         return SDL_FALSE;
    62     }
    63     br = read(fd, buf, buflen-1);
    64     close(fd);
    65     if (br < 0) {
    66         return SDL_FALSE;
    67     }
    68     buf[br] = '\0';             // null-terminate the string.
    69     return SDL_TRUE;
    70 }
    71 
    72 
    73 static SDL_bool
    74 make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
    75 {
    76     char *ptr = *_ptr;
    77 
    78     while (*ptr == ' ') {
    79         ptr++;  /* skip whitespace. */
    80     }
    81 
    82     if (*ptr == '\0') {
    83         return SDL_FALSE;  /* EOF. */
    84     }
    85 
    86     *_key = ptr;
    87 
    88     while ((*ptr != ':') && (*ptr != '\0')) {
    89         ptr++;
    90     }
    91 
    92     if (*ptr == '\0') {
    93         return SDL_FALSE;  /* (unexpected) EOF. */
    94     }
    95 
    96     *(ptr++) = '\0';  /* terminate the key. */
    97 
    98     while ((*ptr == ' ') && (*ptr != '\0')) {
    99         ptr++;  /* skip whitespace. */
   100     }
   101 
   102     if (*ptr == '\0') {
   103         return SDL_FALSE;  /* (unexpected) EOF. */
   104     }
   105 
   106     *_val = ptr;
   107 
   108     while ((*ptr != '\n') && (*ptr != '\0')) {
   109         ptr++;
   110     }
   111 
   112     if (*ptr != '\0') {
   113         *(ptr++) = '\0';  /* terminate the value. */
   114     }
   115 
   116     *_ptr = ptr;  /* store for next time. */
   117     return SDL_TRUE;
   118 }
   119 
   120 static void
   121 check_proc_acpi_battery(const char * node, SDL_bool * have_battery,
   122                         SDL_bool * charging, int *seconds, int *percent)
   123 {
   124     const char *base = proc_acpi_battery_path;
   125     char info[1024];
   126     char state[1024];
   127     char *ptr = NULL;
   128     char *key = NULL;
   129     char *val = NULL;
   130     SDL_bool charge = SDL_FALSE;
   131     SDL_bool choose = SDL_FALSE;
   132     SDL_bool is_ac = SDL_FALSE;
   133     int maximum = -1;
   134     int remaining = -1;
   135     int secs = -1;
   136     int pct = -1;
   137 
   138     if (!load_acpi_file(base, node, "state", state, sizeof (state))) {
   139         return;
   140     } else if (!load_acpi_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     SDL_bool charge = SDL_FALSE;
   219     SDL_bool choose = SDL_FALSE;
   220     SDL_bool is_ac = SDL_FALSE;
   221     int maximum = -1;
   222     int remaining = -1;
   223     int secs = -1;
   224     int pct = -1;
   225 
   226     if (!load_acpi_file(base, node, "state", state, sizeof (state))) {
   227         return;
   228     }
   229 
   230     ptr = &state[0];
   231     while (make_proc_acpi_key_val(&ptr, &key, &val)) {
   232         if (strcmp(key, "state") == 0) {
   233             if (strcmp(val, "on-line") == 0) {
   234                 *have_ac = SDL_TRUE;
   235             }
   236         }
   237     }
   238 }
   239 
   240 
   241 SDL_bool
   242 SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
   243                                  int *seconds, int *percent)
   244 {
   245     struct dirent *dent = NULL;
   246     DIR *dirp = NULL;
   247     SDL_bool have_battery = SDL_FALSE;
   248     SDL_bool have_ac = SDL_FALSE;
   249     SDL_bool charging = SDL_FALSE;
   250 
   251     *seconds = -1;
   252     *percent = -1;
   253     *state = SDL_POWERSTATE_UNKNOWN;
   254 
   255     dirp = opendir(proc_acpi_battery_path);
   256     if (dirp == NULL) {
   257         return SDL_FALSE;  /* can't use this interface. */
   258     } else {
   259         while ((dent = readdir(dirp)) != NULL) {
   260             const char *node = dent->d_name;
   261             check_proc_acpi_battery(node, &have_battery, &charging,
   262                                     seconds, percent);
   263         }
   264         closedir(dirp);
   265     }
   266 
   267     dirp = opendir(proc_acpi_ac_adapter_path);
   268     if (dirp == NULL) {
   269         return SDL_FALSE;  /* can't use this interface. */
   270     } else {
   271         while ((dent = readdir(dirp)) != NULL) {
   272             const char *node = dent->d_name;
   273             check_proc_acpi_ac_adapter(node, &have_ac);
   274         }
   275         closedir(dirp);
   276     }
   277 
   278     if (!have_battery) {
   279         *state = SDL_POWERSTATE_NO_BATTERY;
   280     } else if (charging) {
   281         *state = SDL_POWERSTATE_CHARGING;
   282     } else if (have_ac) {
   283         *state = SDL_POWERSTATE_CHARGED;
   284     } else {
   285         *state = SDL_POWERSTATE_ON_BATTERY;
   286     }
   287 
   288     return SDL_TRUE;   /* definitive answer. */
   289 }
   290 
   291 
   292 static SDL_bool
   293 next_string(char **_ptr, char **_str)
   294 {
   295     char *ptr = *_ptr;
   296     char *str = *_str;
   297 
   298     while (*ptr == ' ') {       /* skip any spaces... */
   299         ptr++;
   300     }
   301 
   302     if (*ptr == '\0') {
   303         return SDL_FALSE;
   304     }
   305 
   306     str = ptr;
   307     while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
   308         ptr++;
   309 
   310     if (*ptr != '\0')
   311         *(ptr++) = '\0';
   312 
   313     *_str = str;
   314     *_ptr = ptr;
   315     return SDL_TRUE;
   316 }
   317 
   318 static SDL_bool
   319 int_string(char *str, int *val)
   320 {
   321     char *endptr = NULL;
   322     *val = (int) strtol(str, &endptr, 0);
   323     return ((*str != '\0') && (*endptr == '\0'));
   324 }
   325 
   326 /* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
   327 SDL_bool
   328 SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
   329                                 int *seconds, int *percent)
   330 {
   331     SDL_bool need_details = SDL_FALSE;
   332     int ac_status = 0;
   333     int battery_status = 0;
   334     int battery_flag = 0;
   335     int battery_percent = 0;
   336     int battery_time = 0;
   337     const int fd = open(proc_apm_path, O_RDONLY);
   338     char buf[128];
   339     char *ptr = &buf[0];
   340     char *str = NULL;
   341     ssize_t br;
   342 
   343     if (fd == -1) {
   344         return SDL_FALSE;       /* can't use this interface. */
   345     }
   346 
   347     br = read(fd, buf, sizeof (buf) - 1);
   348     close(fd);
   349 
   350     if (br < 0) {
   351         return SDL_FALSE;
   352     }
   353 
   354     buf[br] = '\0';             // null-terminate the string.
   355     if (!next_string(&ptr, &str)) {     /* driver version */
   356         return SDL_FALSE;
   357     }
   358     if (!next_string(&ptr, &str)) {     /* BIOS version */
   359         return SDL_FALSE;
   360     }
   361     if (!next_string(&ptr, &str)) {     /* APM flags */
   362         return SDL_FALSE;
   363     }
   364 
   365     if (!next_string(&ptr, &str)) {     /* AC line status */
   366         return SDL_FALSE;
   367     } else if (!int_string(str, &ac_status)) {
   368         return SDL_FALSE;
   369     }
   370 
   371     if (!next_string(&ptr, &str)) {     /* battery status */
   372         return SDL_FALSE;
   373     } else if (!int_string(str, &battery_status)) {
   374         return SDL_FALSE;
   375     }
   376     if (!next_string(&ptr, &str)) {     /* battery flag */
   377         return SDL_FALSE;
   378     } else if (!int_string(str, &battery_flag)) {
   379         return SDL_FALSE;
   380     }
   381     if (!next_string(&ptr, &str)) {     /* remaining battery life percent */
   382         return SDL_FALSE;
   383     }
   384     if (str[strlen(str) - 1] == '%') {
   385         str[strlen(str) - 1] = '\0';
   386     }
   387     if (!int_string(str, &battery_percent)) {
   388         return SDL_FALSE;
   389     }
   390 
   391     if (!next_string(&ptr, &str)) {     /* remaining battery life time */
   392         return SDL_FALSE;
   393     } else if (!int_string(str, &battery_time)) {
   394         return SDL_FALSE;
   395     }
   396 
   397     if (!next_string(&ptr, &str)) {     /* remaining battery life time units */
   398         return SDL_FALSE;
   399     } else if (strcmp(str, "min") == 0) {
   400         battery_time *= 60;
   401     }
   402 
   403     if (battery_flag == 0xFF) { /* unknown state */
   404         *state = SDL_POWERSTATE_UNKNOWN;
   405     } else if (battery_flag & (1 << 7)) {       /* no battery */
   406         *state = SDL_POWERSTATE_NO_BATTERY;
   407     } else if (battery_flag & (1 << 3)) {       /* charging */
   408         *state = SDL_POWERSTATE_CHARGING;
   409         need_details = SDL_TRUE;
   410     } else if (ac_status == 1) {
   411         *state = SDL_POWERSTATE_CHARGED;        /* on AC, not charging. */
   412         need_details = SDL_TRUE;
   413     } else {
   414         *state = SDL_POWERSTATE_ON_BATTERY;
   415         need_details = SDL_TRUE;
   416     }
   417 
   418     *percent = -1;
   419     *seconds = -1;
   420     if (need_details) {
   421         const int pct = battery_percent;
   422         const int secs = battery_time;
   423 
   424         if (pct >= 0) {         /* -1 == unknown */
   425             *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
   426         }
   427         if (secs >= 0) {        /* -1 == unknown */
   428             *seconds = secs;
   429         }
   430     }
   431 
   432     return SDL_TRUE;
   433 }
   434 
   435 #endif /* SDL_POWER_LINUX */
   436 #endif /* SDL_POWER_DISABLED */
   437 
   438 /* vi: set ts=4 sw=4 expandtab: */