power: Add Linux org.freedesktop.UPower D-Bus implementation.

Fixes Bugzilla #3485.

(I think.)
diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c
index c139412..5f47836 100644
--- a/src/core/linux/SDL_dbus.c
+++ b/src/core/linux/SDL_dbus.c
@@ -70,6 +70,7 @@
     SDL_DBUS_SYM(error_free);
     SDL_DBUS_SYM(get_local_machine_id);
     SDL_DBUS_SYM(free);
+    SDL_DBUS_SYM(free_string_array);
     SDL_DBUS_SYM(shutdown);
 
     #undef SDL_DBUS_SYM
diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h
index b5fbaac..1069a5c 100644
--- a/src/core/linux/SDL_dbus.h
+++ b/src/core/linux/SDL_dbus.h
@@ -68,6 +68,7 @@
     void (*error_free)(DBusError *);
     char *(*get_local_machine_id)(void);
     void (*free)(void *);
+    void (*free_string_array)(char **);
     void (*shutdown)(void);
 
 } SDL_DBusContext;
diff --git a/src/power/SDL_power.c b/src/power/SDL_power.c
index b5da6ec..9e262c7 100644
--- a/src/power/SDL_power.c
+++ b/src/power/SDL_power.c
@@ -48,6 +48,7 @@
 static SDL_GetPowerInfo_Impl implementations[] = {
 #ifndef SDL_POWER_DISABLED
 #ifdef SDL_POWER_LINUX          /* in order of preference. More than could work. */
+    SDL_GetPowerInfo_Linux_org_freedesktop_upower,
     SDL_GetPowerInfo_Linux_sys_class_power_supply,
     SDL_GetPowerInfo_Linux_proc_acpi,
     SDL_GetPowerInfo_Linux_proc_apm,
diff --git a/src/power/SDL_syspower.h b/src/power/SDL_syspower.h
index 534eea7..22c35cf 100644
--- a/src/power/SDL_syspower.h
+++ b/src/power/SDL_syspower.h
@@ -28,6 +28,7 @@
 #include "SDL_power.h"
 
 /* Not all of these are available in a given build. Use #ifdefs, etc. */
+SDL_bool SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *, int *, int *);
 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 *);
diff --git a/src/power/linux/SDL_syspower.c b/src/power/linux/SDL_syspower.c
index d072d73..a53fca4 100644
--- a/src/power/linux/SDL_syspower.c
+++ b/src/power/linux/SDL_syspower.c
@@ -34,6 +34,8 @@
 #include "SDL_power.h"
 #include "../SDL_syspower.h"
 
+#include "../../core/linux/SDL_dbus.h"
+
 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";
@@ -426,8 +428,6 @@
     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)
 {
@@ -514,6 +514,118 @@
     return SDL_TRUE;  /* don't look any further. */
 }
 
+
+/* d-bus queries to org.freedesktop.UPower. */
+#if SDL_USE_LIBDBUS
+#define UPOWER_DBUS_NODE "org.freedesktop.UPower"
+#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
+#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
+#define UPOWER_DEVICE_DBUS_INTERFACE "org.freedesktop.UPower.Device"
+
+static void
+check_upower_device(DBusConnection *conn, const char *path, SDL_PowerState *state, int *seconds, int *percent)
+{
+    SDL_bool choose = SDL_FALSE;
+    SDL_PowerState st;
+    int secs;
+    int pct;
+    Uint32 ui32 = 0;
+    Sint64 si64 = 0;
+    double d = 0.0;
+
+    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Type", DBUS_TYPE_UINT32, &ui32)) {
+        return; /* Don't know _what_ we're looking at. Give up on it. */
+    } else if (ui32 != 2) {  /* 2==Battery*/
+        return;  /* we don't care about UPS and such. */
+    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "PowerSupply", DBUS_TYPE_BOOLEAN, &ui32)) {
+        return;
+    } else if (!ui32) {
+        return;  /* we don't care about random devices with batteries, like wireless controllers, etc */
+    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "IsPresent", DBUS_TYPE_BOOLEAN, &ui32)) {
+        return;
+    } else if (!ui32) {
+        st = SDL_POWERSTATE_NO_BATTERY;
+    } else if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "State", DBUS_TYPE_UINT32, &ui32)) {
+        st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
+    } else if (ui32 == 1) {  /* 1 == charging */
+        st = SDL_POWERSTATE_CHARGING;
+    } else if ((ui32 == 2) || (ui32 == 3)) {  /* 2 == discharging, 3 == empty. */
+        st = SDL_POWERSTATE_ON_BATTERY;
+    } else if (ui32 == 4) {   /* 4 == full */
+        st = SDL_POWERSTATE_CHARGED;
+    } else {
+        st = SDL_POWERSTATE_UNKNOWN;  /* uh oh */
+    }
+
+    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "Percentage", DBUS_TYPE_DOUBLE, &d)) {
+        pct = -1;  /* some old/cheap batteries don't set this property. */
+    } else {
+        pct = (int) d;
+        pct = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
+    }
+
+    if (!SDL_DBus_QueryPropertyOnConnection(conn, UPOWER_DBUS_NODE, path, UPOWER_DEVICE_DBUS_INTERFACE, "TimeToEmpty", DBUS_TYPE_INT64, &si64)) {
+        secs = -1;
+    } else {
+        secs = (int) si64;
+        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;
+    }
+}
+#endif
+
+SDL_bool
+SDL_GetPowerInfo_Linux_org_freedesktop_upower(SDL_PowerState *state, int *seconds, int *percent)
+{
+    SDL_bool retval = SDL_FALSE;
+
+    #if SDL_USE_LIBDBUS
+    SDL_DBusContext *dbus = SDL_DBus_GetContext();
+    char **paths = NULL;
+    int i, numpaths = 0;
+
+    if (!SDL_DBus_CallMethodOnConnection(dbus->system_conn, UPOWER_DBUS_NODE, UPOWER_DBUS_PATH, UPOWER_DBUS_INTERFACE, "EnumerateDevices",
+            DBUS_TYPE_INVALID,
+            DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &numpaths, DBUS_TYPE_INVALID)) {
+        return SDL_FALSE;  /* try a different approach than UPower. */
+    }
+
+    retval = SDL_TRUE;  /* Clearly we can use this interface. */
+    *state = SDL_POWERSTATE_NO_BATTERY;  /* assume we're just plugged in. */
+    *seconds = -1;
+    *percent = -1;
+
+    for (i = 0; i < numpaths; i++) {
+        check_upower_device(dbus->system_conn, paths[i], state, seconds, percent);
+    }
+
+    if (dbus) {
+        dbus->free_string_array(paths);
+    }
+    #endif  /* SDL_USE_LIBDBUS */
+
+    return retval;
+}
+
 #endif /* SDL_POWER_LINUX */
 #endif /* SDL_POWER_DISABLED */