From 643f1cb73d6ccb04c72bc39c84c144277698a5e2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 28 May 2017 07:14:11 -0400 Subject: [PATCH] power: Add Linux org.freedesktop.UPower D-Bus implementation. Fixes Bugzilla #3485. (I think.) --- src/core/linux/SDL_dbus.c | 1 + src/core/linux/SDL_dbus.h | 1 + src/power/SDL_power.c | 1 + src/power/SDL_syspower.h | 1 + src/power/linux/SDL_syspower.c | 116 ++++++++++++++++++++++++++++++++- 5 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index c1394126645c0..5f47836a72ab6 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -70,6 +70,7 @@ LoadDBUSSyms(void) 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 b5fbaac144835..1069a5c0beb26 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -68,6 +68,7 @@ typedef struct SDL_DBusContext { 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 b5da6ec9f3763..9e262c78fcf69 100644 --- a/src/power/SDL_power.c +++ b/src/power/SDL_power.c @@ -48,6 +48,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_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 534eea7f6fff0..22c35cfb710f9 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 d072d73f91da1..a53fca4a294b7 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 @@ 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) { @@ -514,6 +514,118 @@ SDL_GetPowerInfo_Linux_sys_class_power_supply(SDL_PowerState *state, int *second 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 */