From e8b376c9d58739ea47aec916b7cca8a031b7201e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 3 Jun 2015 13:11:28 -0700 Subject: [PATCH] Linux: Implemented sysfs-based version of SDL_GetPowerInfo(). Fixes Bugzilla #2938. --- src/power/SDL_power.c | 2 + src/power/linux/SDL_syspower.c | 104 ++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c index 7b8dc15ed04db..8f919f2ebfa28 100644 --- a/src/power/SDL_power.c +++ b/src/power/SDL_power.c @@ -29,6 +29,7 @@ typedef SDL_bool (*SDL_GetPowerInfo_Impl) (SDL_PowerState * state, int *seconds, int *percent); +SDL_bool SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *, int *, int *); SDL_bool SDL_GetPowerInfo_Linux_proc_acpi(SDL_PowerState *, int *, int *); SDL_bool SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState *, int *, int *); SDL_bool SDL_GetPowerInfo_Windows(SDL_PowerState *, int *, int *); @@ -58,6 +59,7 @@ SDL_GetPowerInfo_Hardwired(SDL_PowerState * state, int *seconds, int *percent) static SDL_GetPowerInfo_Impl implementations[] = { #ifndef SDL_POWER_DISABLED #ifdef SDL_POWER_LINUX /* in order of preference. More than could work. */ + SDL_GetPowerInfo_Linux_sys_class_power_supply, SDL_GetPowerInfo_Linux_proc_acpi, SDL_GetPowerInfo_Linux_proc_apm, #endif diff --git a/src/power/linux/SDL_syspower.c b/src/power/linux/SDL_syspower.c index e8f1f364ea52e..3986489f743eb 100644 --- a/src/power/linux/SDL_syspower.c +++ b/src/power/linux/SDL_syspower.c @@ -36,8 +36,10 @@ static const char *proc_apm_path = "/proc/apm"; static const char *proc_acpi_battery_path = "/proc/acpi/battery"; static const char *proc_acpi_ac_adapter_path = "/proc/acpi/ac_adapter"; +static const char *sys_class_power_supply_path = "/sys/class/power_supply"; -static int open_acpi_file(const char *base, const char *node, const char *key) +static int +open_power_file(const char *base, const char *node, const char *key) { const size_t pathlen = strlen(base) + strlen(node) + strlen(key) + 3; char *path = (char *) alloca(pathlen); @@ -51,11 +53,11 @@ static int open_acpi_file(const char *base, const char *node, const char *key) static SDL_bool -load_acpi_file(const char *base, const char *node, const char *key, - char *buf, size_t buflen) +read_power_file(const char *base, const char *node, const char *key, + char *buf, size_t buflen) { ssize_t br = 0; - const int fd = open_acpi_file(base, node, key); + const int fd = open_power_file(base, node, key); if (fd == -1) { return SDL_FALSE; } @@ -133,9 +135,9 @@ check_proc_acpi_battery(const char * node, SDL_bool * have_battery, int secs = -1; int pct = -1; - if (!load_acpi_file(base, node, "state", state, sizeof (state))) { + if (!read_power_file(base, node, "state", state, sizeof (state))) { return; - } else if (!load_acpi_file(base, node, "info", info, sizeof (info))) { + } else if (!read_power_file(base, node, "info", info, sizeof (info))) { return; } @@ -214,7 +216,7 @@ check_proc_acpi_ac_adapter(const char * node, SDL_bool * have_ac) char *key = NULL; char *val = NULL; - if (!load_acpi_file(base, node, "state", state, sizeof (state))) { + if (!read_power_file(base, node, "state", state, sizeof (state))) { return; } @@ -423,6 +425,94 @@ SDL_GetPowerInfo_Linux_proc_apm(SDL_PowerState * state, return SDL_TRUE; } +/* !!! FIXME: implement d-bus queries to org.freedesktop.UPower. */ + +SDL_bool +SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *seconds, int *percent) +{ + const char *base = sys_class_power_supply_path; + struct dirent *dent; + DIR *dirp; + + dirp = opendir(base); + if (!dirp) { + return SDL_FALSE; + } + + *state = SDL_POWERSTATE_NO_BATTERY; /* assume we're just plugged in. */ + *seconds = -1; + *percent = -1; + + while ((dent = readdir(dirp)) != NULL) { + const char *name = dent->d_name; + SDL_bool choose = SDL_FALSE; + char str[64]; + SDL_PowerState st; + int secs; + int pct; + + if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) { + continue; /* skip these, of course. */ + } else if (!read_power_file(base, name, "type", str, sizeof (str))) { + continue; /* Don't know _what_ we're looking at. Give up on it. */ + } else if (SDL_strcmp(str, "Battery\n") != 0) { + continue; /* we don't care about UPS and such. */ + } + + /* some drivers don't offer this, so if it's not explicitly reported assume it's present. */ + if (read_power_file(base, name, "present", str, sizeof (str)) && (SDL_strcmp(str, "0\n") == 0)) { + st = SDL_POWERSTATE_NO_BATTERY; + } else if (!read_power_file(base, name, "status", str, sizeof (str))) { + st = SDL_POWERSTATE_UNKNOWN; /* uh oh */ + } else if (SDL_strcmp(str, "Charging\n") == 0) { + st = SDL_POWERSTATE_CHARGING; + } else if (SDL_strcmp(str, "Discharging\n") == 0) { + st = SDL_POWERSTATE_ON_BATTERY; + } else if ((SDL_strcmp(str, "Full\n") == 0) || (SDL_strcmp(str, "Not charging\n") == 0)) { + st = SDL_POWERSTATE_CHARGED; + } else { + st = SDL_POWERSTATE_UNKNOWN; /* uh oh */ + } + + if (!read_power_file(base, name, "capacity", str, sizeof (str))) { + pct = -1; + } else { + pct = SDL_atoi(str); + pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */ + } + + if (!read_power_file(base, name, "time_to_empty_now", str, sizeof (str))) { + secs = -1; + } else { + secs = SDL_atoi(str); + secs = (secs <= 0) ? -1 : secs; /* 0 == unknown */ + } + + /* + * We pick the battery that claims to have the most minutes left. + * (failing a report of minutes, we'll take the highest percent.) + */ + if ((secs < 0) && (*seconds < 0)) { + if ((pct < 0) && (*percent < 0)) { + choose = SDL_TRUE; /* at least we know there's a battery. */ + } else if (pct > *percent) { + choose = SDL_TRUE; + } + } else if (secs > *seconds) { + choose = SDL_TRUE; + } + + if (choose) { + *seconds = secs; + *percent = pct; + *state = st; + } + } + + closedir(dirp); + return SDL_TRUE; /* don't look any further. */ +} + #endif /* SDL_POWER_LINUX */ #endif /* SDL_POWER_DISABLED */