src/power/linux/SDL_syspower.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 30 Jun 2009 03:50:30 +0000
changeset 3203 790cbbda6429
parent 3186 51750b7a966f
child 3204 f77f50add24f
permissions -rw-r--r--
Power: First shot at Linux /proc/acpi/battery support.

Untested, not even tried to compile yet.
     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 SDL_bool
    38 SDL_GetPowerInfo_Linux_sys_power(SDL_PowerState * state,
    39                                  int *seconds, int *percent)
    40 {
    41     return SDL_FALSE;           /* !!! FIXME: write me. */
    42 }
    43 
    44 
    45 static const char *proc_acpi_path = "/proc/acpi/battery";
    46 
    47 static int open_acpi_file(const char *node, const char *key)
    48 {
    49     const size_t pathlen = strlen(proc_acpi_path)+strlen(node)+strlen(key)+3;
    50     char *path = (char *) alloca(pathlen);
    51     if (path == NULL) {
    52         return -1;  /* oh well. */
    53     }
    54 
    55     snprintf(path, pathlen, "%s/%s/%s", proc_acpi_path, node, key);
    56     return open(path, O_RDONLY);
    57 }
    58 
    59 
    60 static SDL_bool
    61 load_acpi_file(const char *node, const char *key, char *buf, size_t buflen)
    62 {
    63     ssize_t br = 0;
    64     const int fd = open_acpi_file(node, key);
    65     if (fd == -1) {
    66         return SDL_FALSE;
    67     }
    68     br = read(fd, buf, buflen-1);
    69     close(fd);
    70     if (br < 0) {
    71         return SDL_FALSE;
    72     }
    73     buf[br] = '\0';             // null-terminate the string.
    74     return SDL_TRUE;
    75 }
    76 
    77 static SDL_bool
    78 make_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 == ' ') && (*ptr != '\0')) {
   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
   125 check_acpi(const char * fname, SDL_bool * have_ac, SDL_bool * have_battery,
   126            SDL_bool * charging, int *seconds, int *percent)
   127 {
   128     int fd = -1;
   129     char info[1024];
   130     char state[1024];
   131     ssize_t br = 0;
   132     char *ptr = NULL;
   133     char *key = NULL;
   134     char *val = NULL;
   135     SDL_bool charge = SDL_FALSE;
   136     SDL_bool choose = SDL_FALSE;
   137     SDL_bool is_ac = SDL_FALSE;
   138     int maximum = -1;
   139     int remaining = -1;
   140     int secs = -1;
   141     int pct = -1;
   142 
   143     if (!load_acpi_file(fname, "state", state, sizeof (state))) {
   144         return;
   145     } else if (!load_acpi_file(fname, "info", info, sizeof (info))) {
   146         return;
   147     }
   148 
   149     ptr = &state[0];
   150     while (make_acpi_key_val(&ptr, &key, &val)) {
   151         if (strcmp(key, "present") == 0) {
   152             if (strcmp(val, "yes") == 0) {
   153                 *have_battery = SDL_TRUE;
   154             }
   155         } else if (strcmp(key, "charging state") == 0) {
   156             /* !!! FIXME: what exactly _does_ charging/discharging mean? */
   157             if (strcmp(val, "charging/discharging") == 0) {
   158                 *have_ac = is_ac = SDL_TRUE;
   159                 charge = SDL_TRUE;
   160             } else if (strcmp(val, "charging") == 0) {
   161                 *have_ac = is_ac = SDL_TRUE;
   162                 charge = SDL_TRUE;
   163             } else if (strcmp(val, "charged") == 0) {
   164                 /* !!! FIXME: maybe another battery is discharging,
   165                    !!! FIXME:   instead of AC connection. */
   166                 *have_ac = is_ac = SDL_TRUE;
   167                 charge = SDL_TRUE;
   168             }
   169         } else if (strcmp(key, "remaining capacity") == 0) {
   170             char *endptr = NULL;
   171             const int cvt = (int) strtol(val, &endptr, 10);
   172             if (*endptr == ' ') {
   173                 remaining = cvt;
   174             }
   175         }
   176     }
   177     
   178     ptr = &info[0];
   179     while (make_acpi_key_val(&ptr, &key, &val)) {
   180         if (strcmp(key, "design capacity") == 0) {
   181             char *endptr = NULL;
   182             const int cvt = (int) strtol(val, &endptr, 10);
   183             if (*endptr == ' ') {
   184                 maximum = cvt;
   185             }
   186         }
   187     }
   188 
   189     if ((maximum >= 0) && (remaining >= 0)) {
   190         pct = (int) ((((float) remaining) / ((float) maximum)) * 100.0f);
   191         if (pct < 0) {
   192             pct = 0;
   193         } else if (pct > 100) {
   194             pct = 100;
   195         }
   196     }
   197 
   198     /* !!! FIXME: calculate (secs). */
   199 
   200     /*
   201      * We pick the battery that claims to have the most minutes left.
   202      *  (failing a report of minutes, we'll take the highest percent.)
   203      */
   204     if ((secs < 0) && (*seconds < 0)) {
   205         if ((pct < 0) && (*percent < 0)) {
   206             choose = SDL_TRUE;  /* at least we know there's a battery. */
   207         }
   208         if (pct > *percent) {
   209             choose = SDL_TRUE;
   210         }
   211     } else if (secs > *seconds) {
   212         choose = SDL_TRUE;
   213     }
   214 
   215     if (choose) {
   216         *seconds = secs;
   217         *percent = pct;
   218         *charging = charge;
   219     }
   220 
   221 }
   222 
   223 SDL_bool
   224 SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
   225                                  int *seconds, int *percent)
   226 {
   227     struct dirent *dent = NULL;
   228     DIR *dirp = NULL;
   229     SDL_bool have_ac = SDL_FALSE;
   230     SDL_bool have_battery = SDL_FALSE;
   231     SDL_bool charging = SDL_FALSE;
   232 
   233     *seconds = -1;
   234     *percent = -1;
   235     *state = SDL_POWERSTATE_UNKNOWN;
   236 
   237     dirp = opendir(proc_acpi_path);
   238     if (dirp == NULL) {
   239         return SDL_FALSE;  /* can't use this interface. */
   240     }
   241 
   242     while ((dent = readdir(dirp)) != NULL) {
   243         const char *name = dent->d_name;
   244         check_acpi(name, &have_ac, &have_battery, &charging, seconds, percent);
   245     }
   246 
   247     if (!have_battery) {
   248         *state = SDL_POWERSTATE_NO_BATTERY;
   249     } else if (charging) {
   250         *state = SDL_POWERSTATE_CHARGING;
   251     } else if (have_ac) {
   252         *state = SDL_POWERSTATE_CHARGED;
   253     } else {
   254         *state = SDL_POWERSTATE_ON_BATTERY;
   255     }
   256 
   257     return SDL_TRUE;   /* definitive answer. */
   258 }
   259 
   260 
   261 static SDL_bool
   262 next_string(char **_ptr, char **_str)
   263 {
   264     char *ptr = *_ptr;
   265     char *str = *_str;
   266 
   267     while (*ptr == ' ') {       /* skip any spaces... */
   268         ptr++;
   269     }
   270 
   271     if (*ptr == '\0') {
   272         return SDL_FALSE;
   273     }
   274 
   275     str = ptr;
   276     while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
   277         ptr++;
   278 
   279     if (*ptr != '\0')
   280         *(ptr++) = '\0';
   281 
   282     *_str = str;
   283     *_ptr = ptr;
   284     return SDL_TRUE;
   285 }
   286 
   287 static SDL_bool
   288 int_string(char *str, int *val)
   289 {
   290     char *endptr = NULL;
   291     *val = (int) strtol(str + 2, &endptr, 16);
   292     return ((*str != '\0') && (*endptr == '\0'));
   293 }
   294 
   295 /* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
   296 SDL_bool
   297 SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
   298                                 int *seconds, int *percent)
   299 {
   300     SDL_bool need_details = SDL_FALSE;
   301     int ac_status = 0;
   302     int battery_status = 0;
   303     int battery_flag = 0;
   304     int battery_percent = 0;
   305     int battery_time = 0;
   306     const int fd = open("/proc/apm", O_RDONLY);
   307     char buf[128];
   308     char *ptr = &buf[0];
   309     char *str = NULL;
   310     ssize_t br;
   311 
   312     if (fd == -1) {
   313         return SDL_FALSE;       /* can't use this interface. */
   314     }
   315 
   316     br = read(fd, buf, sizeof (buf) - 1);
   317     close(fd);
   318 
   319     if (br < 0) {
   320         return SDL_FALSE;
   321     }
   322 
   323     buf[br] = '\0';             // null-terminate the string.
   324     if (!next_string(&ptr, &str)) {     /* driver version */
   325         return SDL_FALSE;
   326     }
   327     if (!next_string(&ptr, &str)) {     /* BIOS version */
   328         return SDL_FALSE;
   329     }
   330     if (!next_string(&ptr, &str)) {     /* APM flags */
   331         return SDL_FALSE;
   332     }
   333 
   334     if (!next_string(&ptr, &str)) {     /* AC line status */
   335         return SDL_FALSE;
   336     } else if (!int_string(str, &ac_status)) {
   337         return SDL_FALSE;
   338     }
   339 
   340     if (!next_string(&ptr, &str)) {     /* battery status */
   341         return SDL_FALSE;
   342     } else if (!int_string(str, &battery_status)) {
   343         return SDL_FALSE;
   344     }
   345     if (!next_string(&ptr, &str)) {     /* battery flag */
   346         return SDL_FALSE;
   347     } else if (!int_string(str, &battery_flag)) {
   348         return SDL_FALSE;
   349     }
   350     if (!next_string(&ptr, &str)) {     /* remaining battery life percent */
   351         return SDL_FALSE;
   352     }
   353     if (str[strlen(str) - 1] == '%') {
   354         str[strlen(str) - 1] = '\0';
   355     }
   356     if (!int_string(str, &battery_percent)) {
   357         return SDL_FALSE;
   358     }
   359 
   360     if (!next_string(&ptr, &str)) {     /* remaining battery life time */
   361         return SDL_FALSE;
   362     } else if (!int_string(str, &battery_time)) {
   363         return SDL_FALSE;
   364     }
   365 
   366     if (!next_string(&ptr, &str)) {     /* remaining battery life time units */
   367         return SDL_FALSE;
   368     } else if (strcmp(str, "min") == 0) {
   369         battery_time *= 60;
   370     }
   371 
   372     if (battery_flag == 0xFF) { /* unknown state */
   373         *state = SDL_POWERSTATE_UNKNOWN;
   374     } else if (battery_flag & (1 << 7)) {       /* no battery */
   375         *state = SDL_POWERSTATE_NO_BATTERY;
   376     } else if (battery_flag & (1 << 3)) {       /* charging */
   377         *state = SDL_POWERSTATE_CHARGING;
   378         need_details = SDL_TRUE;
   379     } else if (ac_status == 1) {
   380         *state = SDL_POWERSTATE_CHARGED;        /* on AC, not charging. */
   381         need_details = SDL_TRUE;
   382     } else {
   383         *state = SDL_POWERSTATE_ON_BATTERY;
   384         need_details = SDL_TRUE;
   385     }
   386 
   387     *percent = -1;
   388     *seconds = -1;
   389     if (need_details) {
   390         const int pct = battery_percent;
   391         const int secs = battery_time;
   392 
   393         if (pct >= 0) {         /* -1 == unknown */
   394             *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
   395         }
   396         if (secs >= 0) {        /* -1 == unknown */
   397             *seconds = secs;
   398         }
   399     }
   400 
   401     return SDL_TRUE;
   402 }
   403 
   404 #endif /* SDL_POWER_LINUX */
   405 #endif /* SDL_POWER_DISABLED */
   406 
   407 /* vi: set ts=4 sw=4 expandtab: */