src/power/linux/SDL_syspower.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 30 Jun 2009 05:44:00 +0000
changeset 3205 f9a4aa1e1b34
parent 3204 f77f50add24f
child 3206 f735edf338d3
permissions -rw-r--r--
Linux /proc/acpi: check for ac_adapter states, other cleanup.

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