src/power/linux/SDL_syspower.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 29 Oct 2018 20:18:50 -0400
changeset 12370 86e097182d16
parent 11949 2db064e4a284
child 12503 806492103856
permissions -rw-r--r--
winmain: Patched to compile on C89 compilers.
icculus@3170
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@11811
     3
  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
icculus@3170
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
icculus@3170
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
icculus@3170
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
icculus@3170
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
icculus@3170
    22
icculus@3170
    23
#ifndef SDL_POWER_DISABLED
slouken@6044
    24
#if SDL_POWER_LINUX
icculus@3170
    25
icculus@3170
    26
#include <stdio.h>
icculus@3170
    27
#include <unistd.h>
icculus@3170
    28
bob@3174
    29
#include <sys/types.h>
bob@3174
    30
#include <sys/stat.h>
icculus@3203
    31
#include <dirent.h>
bob@3174
    32
#include <fcntl.h>
bob@3174
    33
icculus@3170
    34
#include "SDL_power.h"
slouken@10609
    35
#include "../SDL_syspower.h"
icculus@3170
    36
icculus@11043
    37
#include "../../core/linux/SDL_dbus.h"
icculus@11043
    38
icculus@3207
    39
static const char *proc_apm_path = "/proc/apm";
icculus@3205
    40
static const char *proc_acpi_battery_path = "/proc/acpi/battery";
icculus@3205
    41
static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter";
icculus@9700
    42
static const char *sys_class_power_supply_path = "/sys/class/power_supply";
icculus@3203
    43
icculus@9700
    44
static int
icculus@9700
    45
open_power_file(const char *base, const char *node, const char *key)
icculus@3203
    46
{
icculus@3205
    47
    const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3;
icculus@3203
    48
    char *path = (char *) alloca(pathlen);
icculus@3203
    49
    if (path == NULL) {
icculus@3203
    50
        return -1;  /* oh well. */
icculus@3203
    51
    }
icculus@3203
    52
icculus@3205
    53
    snprintf(path, pathlen, "%s/%s/%s", base, node, key);
icculus@3203
    54
    return open(path, O_RDONLY);
icculus@3203
    55
}
icculus@3203
    56
icculus@3203
    57
icculus@3203
    58
static SDL_bool
icculus@9700
    59
read_power_file(const char *base, const char *node, const char *key,
icculus@9700
    60
                char *buf, size_t buflen)
icculus@3203
    61
{
icculus@3203
    62
    ssize_t br = 0;
icculus@9700
    63
    const int fd = open_power_file(base, node, key);
icculus@3170
    64
    if (fd == -1) {
icculus@3203
    65
        return SDL_FALSE;
icculus@3170
    66
    }
icculus@3203
    67
    br = read(fd, buf, buflen-1);
icculus@3203
    68
    close(fd);
icculus@3203
    69
    if (br < 0) {
icculus@3203
    70
        return SDL_FALSE;
icculus@3203
    71
    }
slouken@7191
    72
    buf[br] = '\0';             /* null-terminate the string. */
icculus@3170
    73
    return SDL_TRUE;
icculus@3203
    74
}
icculus@3203
    75
icculus@3205
    76
icculus@3203
    77
static SDL_bool
icculus@3205
    78
make_proc_acpi_key_val(char **_ptr, char **_key, char **_val)
icculus@3203
    79
{
icculus@3203
    80
    char *ptr = *_ptr;
icculus@3203
    81
icculus@3203
    82
    while (*ptr == ' ') {
icculus@3203
    83
        ptr++;  /* skip whitespace. */
icculus@3203
    84
    }
icculus@3203
    85
icculus@3203
    86
    if (*ptr == '\0') {
icculus@3203
    87
        return SDL_FALSE;  /* EOF. */
icculus@3203
    88
    }
icculus@3203
    89
icculus@3203
    90
    *_key = ptr;
icculus@3203
    91
icculus@3203
    92
    while ((*ptr != ':') && (*ptr != '\0')) {
icculus@3203
    93
        ptr++;
icculus@3203
    94
    }
icculus@3203
    95
icculus@3203
    96
    if (*ptr == '\0') {
icculus@3203
    97
        return SDL_FALSE;  /* (unexpected) EOF. */
icculus@3203
    98
    }
icculus@3203
    99
icculus@3203
   100
    *(ptr++) = '\0';  /* terminate the key. */
icculus@3203
   101
icculus@3203
   102
    while ((*ptr == ' ') && (*ptr != '\0')) {
icculus@3203
   103
        ptr++;  /* skip whitespace. */
icculus@3203
   104
    }
icculus@3203
   105
icculus@3203
   106
    if (*ptr == '\0') {
icculus@3203
   107
        return SDL_FALSE;  /* (unexpected) EOF. */
icculus@3203
   108
    }
icculus@3203
   109
icculus@3203
   110
    *_val = ptr;
icculus@3203
   111
icculus@3203
   112
    while ((*ptr != '\n') && (*ptr != '\0')) {
icculus@3203
   113
        ptr++;
icculus@3203
   114
    }
icculus@3203
   115
icculus@3203
   116
    if (*ptr != '\0') {
icculus@3203
   117
        *(ptr++) = '\0';  /* terminate the value. */
icculus@3203
   118
    }
icculus@3203
   119
icculus@3203
   120
    *_ptr = ptr;  /* store for next time. */
icculus@3203
   121
    return SDL_TRUE;
icculus@3203
   122
}
icculus@3203
   123
icculus@3203
   124
static void
icculus@3205
   125
check_proc_acpi_battery(const char * node, SDL_bool * have_battery,
icculus@3205
   126
                        SDL_bool * charging, int *seconds, int *percent)
icculus@3203
   127
{
icculus@3205
   128
    const char *base = proc_acpi_battery_path;
icculus@3203
   129
    char info[1024];
icculus@3203
   130
    char state[1024];
icculus@3203
   131
    char *ptr = NULL;
icculus@3203
   132
    char *key = NULL;
icculus@3203
   133
    char *val = NULL;
icculus@3203
   134
    SDL_bool charge = SDL_FALSE;
icculus@3203
   135
    SDL_bool choose = SDL_FALSE;
icculus@3203
   136
    int maximum = -1;
icculus@3203
   137
    int remaining = -1;
icculus@3203
   138
    int secs = -1;
icculus@3203
   139
    int pct = -1;
icculus@3203
   140
icculus@9700
   141
    if (!read_power_file(base, node, "state", state, sizeof (state))) {
icculus@3203
   142
        return;
icculus@9700
   143
    } else if (!read_power_file(base, node, "info", info, sizeof (info))) {
icculus@3203
   144
        return;
icculus@3203
   145
    }
icculus@3203
   146
icculus@3203
   147
    ptr = &state[0];
icculus@3205
   148
    while (make_proc_acpi_key_val(&ptr, &key, &val)) {
icculus@3203
   149
        if (strcmp(key, "present") == 0) {
icculus@3203
   150
            if (strcmp(val, "yes") == 0) {
icculus@3203
   151
                *have_battery = SDL_TRUE;
icculus@3203
   152
            }
icculus@3203
   153
        } else if (strcmp(key, "charging state") == 0) {
icculus@3203
   154
            /* !!! FIXME: what exactly _does_ charging/discharging mean? */
icculus@3203
   155
            if (strcmp(val, "charging/discharging") == 0) {
icculus@3203
   156
                charge = SDL_TRUE;
icculus@3203
   157
            } else if (strcmp(val, "charging") == 0) {
icculus@3203
   158
                charge = SDL_TRUE;
icculus@3203
   159
            }
icculus@3203
   160
        } else if (strcmp(key, "remaining capacity") == 0) {
icculus@3203
   161
            char *endptr = NULL;
icculus@3203
   162
            const int cvt = (int) strtol(val, &endptr, 10);
icculus@3203
   163
            if (*endptr == ' ') {
icculus@3203
   164
                remaining = cvt;
icculus@3203
   165
            }
icculus@3203
   166
        }
icculus@3203
   167
    }
icculus@3205
   168
icculus@3203
   169
    ptr = &info[0];
icculus@3205
   170
    while (make_proc_acpi_key_val(&ptr, &key, &val)) {
icculus@3203
   171
        if (strcmp(key, "design capacity") == 0) {
icculus@3203
   172
            char *endptr = NULL;
icculus@3203
   173
            const int cvt = (int) strtol(val, &endptr, 10);
icculus@3203
   174
            if (*endptr == ' ') {
icculus@3203
   175
                maximum = cvt;
icculus@3203
   176
            }
icculus@3203
   177
        }
icculus@3203
   178
    }
icculus@3203
   179
icculus@3203
   180
    if ((maximum >= 0) && (remaining >= 0)) {
icculus@3203
   181
        pct = (int) ((((float) remaining) / ((float) maximum)) * 100.0f);
icculus@3203
   182
        if (pct < 0) {
icculus@3203
   183
            pct = 0;
icculus@3203
   184
        } else if (pct > 100) {
icculus@3203
   185
            pct = 100;
icculus@3203
   186
        }
icculus@3203
   187
    }
icculus@3203
   188
icculus@3203
   189
    /* !!! FIXME: calculate (secs). */
icculus@3203
   190
icculus@3203
   191
    /*
icculus@3203
   192
     * We pick the battery that claims to have the most minutes left.
icculus@3203
   193
     *  (failing a report of minutes, we'll take the highest percent.)
icculus@3203
   194
     */
icculus@3203
   195
    if ((secs < 0) && (*seconds < 0)) {
icculus@3203
   196
        if ((pct < 0) && (*percent < 0)) {
icculus@3203
   197
            choose = SDL_TRUE;  /* at least we know there's a battery. */
icculus@3203
   198
        }
icculus@3203
   199
        if (pct > *percent) {
icculus@3203
   200
            choose = SDL_TRUE;
icculus@3203
   201
        }
icculus@3203
   202
    } else if (secs > *seconds) {
icculus@3203
   203
        choose = SDL_TRUE;
icculus@3203
   204
    }
icculus@3203
   205
icculus@3203
   206
    if (choose) {
icculus@3203
   207
        *seconds = secs;
icculus@3203
   208
        *percent = pct;
icculus@3203
   209
        *charging = charge;
icculus@3203
   210
    }
icculus@3205
   211
}
icculus@3203
   212
icculus@3205
   213
static void
icculus@3205
   214
check_proc_acpi_ac_adapter(const char * node, SDL_bool * have_ac)
icculus@3205
   215
{
icculus@3205
   216
    const char *base = proc_acpi_ac_adapter_path;
icculus@3205
   217
    char state[256];
icculus@3205
   218
    char *ptr = NULL;
icculus@3205
   219
    char *key = NULL;
icculus@3205
   220
    char *val = NULL;
icculus@3205
   221
icculus@9700
   222
    if (!read_power_file(base, node, "state", state, sizeof (state))) {
icculus@3205
   223
        return;
icculus@3205
   224
    }
icculus@3205
   225
icculus@3205
   226
    ptr = &state[0];
icculus@3205
   227
    while (make_proc_acpi_key_val(&ptr, &key, &val)) {
icculus@3205
   228
        if (strcmp(key, "state") == 0) {
icculus@3205
   229
            if (strcmp(val, "on-line") == 0) {
icculus@3205
   230
                *have_ac = SDL_TRUE;
icculus@3205
   231
            }
icculus@3205
   232
        }
icculus@3205
   233
    }
icculus@3170
   234
}
icculus@3170
   235
icculus@3205
   236
icculus@3170
   237
SDL_bool
slouken@3186
   238
SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState * state,
slouken@3186
   239
                                 int *seconds, int *percent)
icculus@3170
   240
{
icculus@3203
   241
    struct dirent *dent = NULL;
icculus@3203
   242
    DIR *dirp = NULL;
icculus@3205
   243
    SDL_bool have_battery = SDL_FALSE;
icculus@3203
   244
    SDL_bool have_ac = SDL_FALSE;
icculus@3203
   245
    SDL_bool charging = SDL_FALSE;
icculus@3203
   246
icculus@3203
   247
    *seconds = -1;
icculus@3203
   248
    *percent = -1;
icculus@3203
   249
    *state = SDL_POWERSTATE_UNKNOWN;
icculus@3203
   250
icculus@3205
   251
    dirp = opendir(proc_acpi_battery_path);
icculus@3203
   252
    if (dirp == NULL) {
icculus@3203
   253
        return SDL_FALSE;  /* can't use this interface. */
icculus@3205
   254
    } else {
icculus@3205
   255
        while ((dent = readdir(dirp)) != NULL) {
icculus@3205
   256
            const char *node = dent->d_name;
icculus@3205
   257
            check_proc_acpi_battery(node, &have_battery, &charging,
icculus@3205
   258
                                    seconds, percent);
icculus@3205
   259
        }
icculus@3205
   260
        closedir(dirp);
icculus@3170
   261
    }
icculus@3203
   262
icculus@3205
   263
    dirp = opendir(proc_acpi_ac_adapter_path);
icculus@3205
   264
    if (dirp == NULL) {
icculus@3205
   265
        return SDL_FALSE;  /* can't use this interface. */
icculus@3205
   266
    } else {
icculus@3205
   267
        while ((dent = readdir(dirp)) != NULL) {
icculus@3205
   268
            const char *node = dent->d_name;
icculus@3205
   269
            check_proc_acpi_ac_adapter(node, &have_ac);
icculus@3205
   270
        }
icculus@3205
   271
        closedir(dirp);
icculus@3203
   272
    }
icculus@3203
   273
icculus@3203
   274
    if (!have_battery) {
icculus@3203
   275
        *state = SDL_POWERSTATE_NO_BATTERY;
icculus@3203
   276
    } else if (charging) {
icculus@3203
   277
        *state = SDL_POWERSTATE_CHARGING;
icculus@3203
   278
    } else if (have_ac) {
icculus@3203
   279
        *state = SDL_POWERSTATE_CHARGED;
icculus@3203
   280
    } else {
icculus@3203
   281
        *state = SDL_POWERSTATE_ON_BATTERY;
icculus@3203
   282
    }
icculus@3203
   283
icculus@3203
   284
    return SDL_TRUE;   /* definitive answer. */
icculus@3170
   285
}
icculus@3170
   286
icculus@3203
   287
icculus@3170
   288
static SDL_bool
icculus@3170
   289
next_string(char **_ptr, char **_str)
icculus@3170
   290
{
icculus@3170
   291
    char *ptr = *_ptr;
icculus@3170
   292
    char *str = *_str;
icculus@3170
   293
slouken@3186
   294
    while (*ptr == ' ') {       /* skip any spaces... */
icculus@3170
   295
        ptr++;
icculus@3170
   296
    }
icculus@3170
   297
icculus@3170
   298
    if (*ptr == '\0') {
icculus@3170
   299
        return SDL_FALSE;
icculus@3170
   300
    }
icculus@3170
   301
icculus@3170
   302
    str = ptr;
icculus@3203
   303
    while ((*ptr != ' ') && (*ptr != '\n') && (*ptr != '\0'))
icculus@3170
   304
        ptr++;
icculus@3170
   305
icculus@3170
   306
    if (*ptr != '\0')
icculus@3170
   307
        *(ptr++) = '\0';
icculus@3170
   308
icculus@3170
   309
    *_str = str;
icculus@3170
   310
    *_ptr = ptr;
icculus@3170
   311
    return SDL_TRUE;
icculus@3170
   312
}
icculus@3170
   313
icculus@3170
   314
static SDL_bool
icculus@3170
   315
int_string(char *str, int *val)
icculus@3170
   316
{
icculus@3170
   317
    char *endptr = NULL;
icculus@3206
   318
    *val = (int) strtol(str, &endptr, 0);
icculus@3170
   319
    return ((*str != '\0') && (*endptr == '\0'));
icculus@3170
   320
}
icculus@3170
   321
icculus@3170
   322
/* http://lxr.linux.no/linux+v2.6.29/drivers/char/apm-emulation.c */
icculus@3170
   323
SDL_bool
slouken@3186
   324
SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state,
slouken@3186
   325
                                int *seconds, int *percent)
icculus@3170
   326
{
icculus@3170
   327
    SDL_bool need_details = SDL_FALSE;
icculus@3170
   328
    int ac_status = 0;
icculus@3170
   329
    int battery_status = 0;
icculus@3170
   330
    int battery_flag = 0;
icculus@3170
   331
    int battery_percent = 0;
icculus@3170
   332
    int battery_time = 0;
icculus@3207
   333
    const int fd = open(proc_apm_path, O_RDONLY);
icculus@3170
   334
    char buf[128];
icculus@3170
   335
    char *ptr = &buf[0];
icculus@3170
   336
    char *str = NULL;
icculus@3170
   337
    ssize_t br;
icculus@3170
   338
icculus@3170
   339
    if (fd == -1) {
slouken@3186
   340
        return SDL_FALSE;       /* can't use this interface. */
icculus@3170
   341
    }
icculus@3170
   342
icculus@3203
   343
    br = read(fd, buf, sizeof (buf) - 1);
icculus@3170
   344
    close(fd);
icculus@3170
   345
icculus@3170
   346
    if (br < 0) {
icculus@3170
   347
        return SDL_FALSE;
icculus@3170
   348
    }
icculus@3170
   349
slouken@7191
   350
    buf[br] = '\0';             /* null-terminate the string. */
slouken@3186
   351
    if (!next_string(&ptr, &str)) {     /* driver version */
icculus@3170
   352
        return SDL_FALSE;
icculus@3170
   353
    }
slouken@3186
   354
    if (!next_string(&ptr, &str)) {     /* BIOS version */
icculus@3170
   355
        return SDL_FALSE;
icculus@3170
   356
    }
slouken@3186
   357
    if (!next_string(&ptr, &str)) {     /* APM flags */
icculus@3170
   358
        return SDL_FALSE;
icculus@3170
   359
    }
icculus@3170
   360
slouken@3186
   361
    if (!next_string(&ptr, &str)) {     /* AC line status */
icculus@3170
   362
        return SDL_FALSE;
icculus@3170
   363
    } else if (!int_string(str, &ac_status)) {
icculus@3170
   364
        return SDL_FALSE;
icculus@3170
   365
    }
icculus@3170
   366
slouken@3186
   367
    if (!next_string(&ptr, &str)) {     /* battery status */
icculus@3170
   368
        return SDL_FALSE;
icculus@3170
   369
    } else if (!int_string(str, &battery_status)) {
icculus@3170
   370
        return SDL_FALSE;
icculus@3170
   371
    }
slouken@3186
   372
    if (!next_string(&ptr, &str)) {     /* battery flag */
icculus@3170
   373
        return SDL_FALSE;
icculus@3170
   374
    } else if (!int_string(str, &battery_flag)) {
icculus@3170
   375
        return SDL_FALSE;
icculus@3170
   376
    }
slouken@3186
   377
    if (!next_string(&ptr, &str)) {     /* remaining battery life percent */
icculus@3170
   378
        return SDL_FALSE;
icculus@3170
   379
    }
icculus@3170
   380
    if (str[strlen(str) - 1] == '%') {
icculus@3170
   381
        str[strlen(str) - 1] = '\0';
icculus@3170
   382
    }
icculus@3170
   383
    if (!int_string(str, &battery_percent)) {
icculus@3170
   384
        return SDL_FALSE;
icculus@3170
   385
    }
icculus@3170
   386
slouken@3186
   387
    if (!next_string(&ptr, &str)) {     /* remaining battery life time */
icculus@3170
   388
        return SDL_FALSE;
icculus@3170
   389
    } else if (!int_string(str, &battery_time)) {
icculus@3170
   390
        return SDL_FALSE;
icculus@3170
   391
    }
icculus@3170
   392
slouken@3186
   393
    if (!next_string(&ptr, &str)) {     /* remaining battery life time units */
icculus@3170
   394
        return SDL_FALSE;
icculus@3170
   395
    } else if (strcmp(str, "min") == 0) {
icculus@3170
   396
        battery_time *= 60;
icculus@3170
   397
    }
icculus@3170
   398
slouken@3186
   399
    if (battery_flag == 0xFF) { /* unknown state */
icculus@3170
   400
        *state = SDL_POWERSTATE_UNKNOWN;
slouken@3186
   401
    } else if (battery_flag & (1 << 7)) {       /* no battery */
icculus@3170
   402
        *state = SDL_POWERSTATE_NO_BATTERY;
slouken@3186
   403
    } else if (battery_flag & (1 << 3)) {       /* charging */
icculus@3170
   404
        *state = SDL_POWERSTATE_CHARGING;
icculus@3170
   405
        need_details = SDL_TRUE;
icculus@3170
   406
    } else if (ac_status == 1) {
slouken@3186
   407
        *state = SDL_POWERSTATE_CHARGED;        /* on AC, not charging. */
icculus@3170
   408
        need_details = SDL_TRUE;
icculus@3170
   409
    } else {
icculus@3170
   410
        *state = SDL_POWERSTATE_ON_BATTERY;
icculus@3170
   411
        need_details = SDL_TRUE;
icculus@3170
   412
    }
icculus@3170
   413
icculus@3170
   414
    *percent = -1;
icculus@3170
   415
    *seconds = -1;
icculus@3170
   416
    if (need_details) {
icculus@3170
   417
        const int pct = battery_percent;
icculus@3170
   418
        const int secs = battery_time;
icculus@3170
   419
slouken@3186
   420
        if (pct >= 0) {         /* -1 == unknown */
slouken@3186
   421
            *percent = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
icculus@3170
   422
        }
slouken@3186
   423
        if (secs >= 0) {        /* -1 == unknown */
icculus@3170
   424
            *seconds = secs;
icculus@3170
   425
        }
icculus@3170
   426
    }
icculus@3170
   427
icculus@3170
   428
    return SDL_TRUE;
icculus@3170
   429
}
icculus@3170
   430
icculus@9700
   431
SDL_bool
icculus@9700
   432
SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *seconds, int *percent)
icculus@9700
   433
{
icculus@9700
   434
    const char *base = sys_class_power_supply_path;
icculus@9700
   435
    struct dirent *dent;
icculus@9700
   436
    DIR *dirp;
icculus@9700
   437
icculus@9700
   438
    dirp = opendir(base);
icculus@9700
   439
    if (!dirp) {
icculus@9700
   440
        return SDL_FALSE;
icculus@9700
   441
    }
icculus@9700
   442
icculus@9700
   443
    *state = SDL_POWERSTATE_NO_BATTERY;  /* assume we're just plugged in. */
icculus@9700
   444
    *seconds = -1;
icculus@9700
   445
    *percent = -1;
icculus@9700
   446
icculus@9700
   447
    while ((dent = readdir(dirp)) != NULL) {
icculus@9700
   448
        const char *name = dent->d_name;
icculus@9700
   449
        SDL_bool choose = SDL_FALSE;
icculus@9700
   450
        char str[64];
icculus@9700
   451
        SDL_PowerState st;
icculus@9700
   452
        int secs;
icculus@9700
   453
        int pct;
icculus@9700
   454
icculus@9700
   455
        if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
icculus@9700
   456
            continue;  /* skip these, of course. */
icculus@9700
   457
        } else if (!read_power_file(base, name, "type", str, sizeof (str))) {
icculus@9700
   458
            continue;  /* Don't know _what_ we're looking at. Give up on it. */
icculus@9700
   459
        } else if (SDL_strcmp(str, "Battery\n") != 0) {
icculus@9700
   460
            continue;  /* we don't care about UPS and such. */
icculus@9700
   461
        }
icculus@9700
   462
icculus@11125
   463
        /* if the scope is "device," it might be something like a PS4
icculus@11125
   464
           controller reporting its own battery, and not something that powers
icculus@11125
   465
           the system. Most system batteries don't list a scope at all; we
icculus@11125
   466
           assume it's a system battery if not specified. */
icculus@11125
   467
        if (read_power_file(base, name, "scope", str, sizeof (str))) {
icculus@11126
   468
            if (SDL_strcmp(str, "device\n") == 0) {
icculus@11125
   469
                continue;  /* skip external devices with their own batteries. */
icculus@11125
   470
            }
icculus@11125
   471
        }
icculus@11125
   472
icculus@9700
   473
        /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */
icculus@9700
   474
        if (read_power_file(base, name, "present", str, sizeof (str)) && (SDL_strcmp(str, "0\n") == 0)) {
icculus@9700
   475
            st = SDL_POWERSTATE_NO_BATTERY;
icculus@9700
   476
        } else if (!read_power_file(base, name, "status", str, sizeof (str))) {
icculus@9700
   477
            st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
icculus@9700
   478
        } else if (SDL_strcmp(str, "Charging\n") == 0) {
icculus@9700
   479
            st = SDL_POWERSTATE_CHARGING;
icculus@9700
   480
        } else if (SDL_strcmp(str, "Discharging\n") == 0) {
icculus@9700
   481
            st = SDL_POWERSTATE_ON_BATTERY;
icculus@9700
   482
        } else if ((SDL_strcmp(str, "Full\n") == 0) || (SDL_strcmp(str, "Not charging\n") == 0)) {
icculus@9700
   483
            st = SDL_POWERSTATE_CHARGED;
icculus@9700
   484
        } else {
icculus@9700
   485
            st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
icculus@9700
   486
        }
icculus@9700
   487
icculus@9700
   488
        if (!read_power_file(base, name, "capacity", str, sizeof (str))) {
icculus@9700
   489
            pct = -1;
icculus@9700
   490
        } else {
icculus@9700
   491
            pct = SDL_atoi(str);
icculus@9700
   492
            pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
icculus@9700
   493
        }
icculus@9700
   494
icculus@9700
   495
        if (!read_power_file(base, name, "time_to_empty_now", str, sizeof (str))) {
icculus@9700
   496
            secs = -1;
icculus@9700
   497
        } else {
icculus@9700
   498
            secs = SDL_atoi(str);
icculus@9700
   499
            secs = (secs <= 0) ? -1 : secs;  /* 0 == unknown */
icculus@9700
   500
        }
icculus@9700
   501
icculus@9700
   502
        /*
icculus@9700
   503
         * We pick the battery that claims to have the most minutes left.
icculus@9700
   504
         *  (failing a report of minutes, we'll take the highest percent.)
icculus@9700
   505
         */
icculus@9700
   506
        if ((secs < 0) && (*seconds < 0)) {
icculus@9700
   507
            if ((pct < 0) && (*percent < 0)) {
icculus@9700
   508
                choose = SDL_TRUE;  /* at least we know there's a battery. */
icculus@9700
   509
            } else if (pct > *percent) {
icculus@9700
   510
                choose = SDL_TRUE;
icculus@9700
   511
            }
icculus@9700
   512
        } else if (secs > *seconds) {
icculus@9700
   513
            choose = SDL_TRUE;
icculus@9700
   514
        }
icculus@9700
   515
icculus@9700
   516
        if (choose) {
icculus@9700
   517
            *seconds = secs;
icculus@9700
   518
            *percent = pct;
icculus@9700
   519
            *state = st;
icculus@9700
   520
        }
icculus@9700
   521
    }
icculus@9700
   522
icculus@9700
   523
    closedir(dirp);
icculus@9700
   524
    return SDL_TRUE;  /* don't look any further. */
icculus@9700
   525
}
icculus@9700
   526
icculus@11043
   527
icculus@11043
   528
/* d-bus queries to org.freedesktop.UPower. */
icculus@11043
   529
#if SDL_USE_LIBDBUS
icculus@11043
   530
#define UPOWER_DBUS_NODE "org.freedesktop.UPower"
icculus@11043
   531
#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
icculus@11043
   532
#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
icculus@11043
   533
#define UPOWER_DEVICE_DBUS_INTERFACE "org.freedesktop.UPower.Device"
icculus@11043
   534
icculus@11043
   535
static void
icculus@11043
   536
check_upower_device(DBusConnection *conn, const char *path, SDL_PowerState *state, int *seconds, int *percent)
icculus@11043
   537
{
icculus@11043
   538
    SDL_bool choose = SDL_FALSE;
icculus@11043
   539
    SDL_PowerState st;
icculus@11043
   540
    int secs;
icculus@11043
   541
    int pct;
icculus@11043
   542
    Uint32 ui32 = 0;
icculus@11043
   543
    Sint64 si64 = 0;
icculus@11043
   544
    double d = 0.0;
icculus@11043
   545
icculus@11043
   546
    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) {
icculus@11043
   547
        return; /* Don't know _what_ we're looking at. Give up on it. */
icculus@11043
   548
    } else if (ui32 != 2) {  /* 2==Battery*/
icculus@11043
   549
        return;  /* we don't care about UPS and such. */
icculus@11043
   550
    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) {
icculus@11043
   551
        return;
icculus@11043
   552
    } else if (!ui32) {
icculus@11043
   553
        return;  /* we don't care about random devices with batteries, like wireless controllers, etc */
icculus@11043
   554
    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) {
icculus@11043
   555
        return;
icculus@11043
   556
    } else if (!ui32) {
icculus@11043
   557
        st = SDL_POWERSTATE_NO_BATTERY;
icculus@11043
   558
    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) {
icculus@11043
   559
        st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
icculus@11043
   560
    } else if (ui32 == 1) {  /* 1 == charging */
icculus@11043
   561
        st = SDL_POWERSTATE_CHARGING;
icculus@11043
   562
    } else if ((ui32 == 2) || (ui32 == 3)) {  /* 2 == discharging, 3 == empty. */
icculus@11043
   563
        st = SDL_POWERSTATE_ON_BATTERY;
icculus@11043
   564
    } else if (ui32 == 4) {   /* 4 == full */
icculus@11043
   565
        st = SDL_POWERSTATE_CHARGED;
icculus@11043
   566
    } else {
icculus@11043
   567
        st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
icculus@11043
   568
    }
icculus@11043
   569
icculus@11043
   570
    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) {
icculus@11043
   571
        pct = -1;  /* some old/cheap batteries don't set this property. */
icculus@11043
   572
    } else {
icculus@11043
   573
        pct = (int) d;
icculus@11043
   574
        pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
icculus@11043
   575
    }
icculus@11043
   576
icculus@11043
   577
    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) {
icculus@11043
   578
        secs = -1;
icculus@11043
   579
    } else {
icculus@11043
   580
        secs = (int) si64;
icculus@11043
   581
        secs = (secs <= 0) ? -1 : secs;  /* 0 == unknown */
icculus@11043
   582
    }
icculus@11043
   583
icculus@11043
   584
    /*
icculus@11043
   585
     * We pick the battery that claims to have the most minutes left.
icculus@11043
   586
     *  (failing a report of minutes, we'll take the highest percent.)
icculus@11043
   587
     */
icculus@11043
   588
    if ((secs < 0) && (*seconds < 0)) {
icculus@11043
   589
        if ((pct < 0) && (*percent < 0)) {
icculus@11043
   590
            choose = SDL_TRUE;  /* at least we know there's a battery. */
icculus@11043
   591
        } else if (pct > *percent) {
icculus@11043
   592
            choose = SDL_TRUE;
icculus@11043
   593
        }
icculus@11043
   594
    } else if (secs > *seconds) {
icculus@11043
   595
        choose = SDL_TRUE;
icculus@11043
   596
    }
icculus@11043
   597
icculus@11043
   598
    if (choose) {
icculus@11043
   599
        *seconds = secs;
icculus@11043
   600
        *percent = pct;
icculus@11043
   601
        *state = st;
icculus@11043
   602
    }
icculus@11043
   603
}
icculus@11043
   604
#endif
icculus@11043
   605
icculus@11043
   606
SDL_bool
icculus@11043
   607
SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *seconds, int *percent)
icculus@11043
   608
{
icculus@11043
   609
    SDL_bool retval = SDL_FALSE;
icculus@11043
   610
slouken@11949
   611
#if SDL_USE_LIBDBUS
icculus@11043
   612
    SDL_DBusContext *dbus = SDL_DBus_GetContext();
icculus@11043
   613
    char **paths = NULL;
icculus@11043
   614
    int i, numpaths = 0;
icculus@11043
   615
slouken@11949
   616
    if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices",
icculus@11043
   617
            DBUS_TYPE_INVALID,
icculus@11043
   618
            DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) {
icculus@11043
   619
        return SDL_FALSE;  /* try a different approach than UPower. */
icculus@11043
   620
    }
icculus@11043
   621
icculus@11043
   622
    retval = SDL_TRUE;  /* Clearly we can use this interface. */
icculus@11043
   623
    *state = SDL_POWERSTATE_NO_BATTERY;  /* assume we're just plugged in. */
icculus@11043
   624
    *seconds = -1;
icculus@11043
   625
    *percent = -1;
icculus@11043
   626
icculus@11043
   627
    for (i = 0; i < numpaths; i++) {
icculus@11043
   628
        check_upower_device(dbus->system_conn, paths[i], state, seconds, percent);
icculus@11043
   629
    }
icculus@11043
   630
icculus@11043
   631
    if (dbus) {
icculus@11043
   632
        dbus->free_string_array(paths);
icculus@11043
   633
    }
slouken@11949
   634
#endif  /* SDL_USE_LIBDBUS */
icculus@11043
   635
icculus@11043
   636
    return retval;
icculus@11043
   637
}
icculus@11043
   638
icculus@3170
   639
#endif /* SDL_POWER_LINUX */
icculus@3170
   640
#endif /* SDL_POWER_DISABLED */
icculus@3170
   641
icculus@3170
   642
/* vi: set ts=4 sw=4 expandtab: */