Commit e8b376c9d58739ea47aec916b7cca8a031b7201e

Ryan C. Gordon 2015-06-03T13:11:28

Linux: Implemented sysfs-based version of SDL_GetPowerInfo(). Fixes Bugzilla #2938.

diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c
index 7b8dc15..8f919f2 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 e8f1f36..3986489 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 */