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 */