diff options
Diffstat (limited to 'src/linux/up-device-supply.c')
-rw-r--r-- | src/linux/up-device-supply.c | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/src/linux/up-device-supply.c b/src/linux/up-device-supply.c new file mode 100644 index 0000000..c7ca2e1 --- /dev/null +++ b/src/linux/up-device-supply.c @@ -0,0 +1,902 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 David Zeuthen <davidz@redhat.com> + * Copyright (C) 2008 Richard Hughes <richard@hughsie.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> +#include <math.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <glib/gprintf.h> +#include <glib/gi18n-lib.h> +#include <glib-object.h> +#include <gudev/gudev.h> + +#include "sysfs-utils.h" +#include "egg-debug.h" + +#include "up-enum.h" +#include "up-device-supply.h" + +#define DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT 30 /* seconds */ +#define DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT 2 /* seconds */ +#define DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES 30 +#define DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD 90.0f /* % */ + +struct DkpDeviceSupplyPrivate +{ + guint poll_timer_id; + gboolean has_coldplug_values; + gdouble energy_old; + GTimeVal energy_old_timespec; + guint unknown_retries; + gboolean enable_poll; +}; + +G_DEFINE_TYPE (DkpDeviceSupply, dkp_device_supply, DKP_TYPE_DEVICE) +#define DKP_DEVICE_SUPPLY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_SUPPLY, DkpDeviceSupplyPrivate)) + +static gboolean dkp_device_supply_refresh (DkpDevice *device); + +/** + * dkp_device_supply_refresh_line_power: + * + * Return %TRUE on success, %FALSE if we failed to refresh or no data + **/ +static gboolean +dkp_device_supply_refresh_line_power (DkpDeviceSupply *supply) +{ + DkpDevice *device = DKP_DEVICE (supply); + GUdevDevice *native; + const gchar *native_path; + + /* force true */ + g_object_set (device, "power-supply", TRUE, NULL); + + /* get new AC value */ + native = G_UDEV_DEVICE (dkp_device_get_native (device)); + native_path = g_udev_device_get_sysfs_path (native); + g_object_set (device, "online", sysfs_get_int (native_path, "online"), NULL); + + return TRUE; +} + +/** + * dkp_device_supply_reset_values: + **/ +static void +dkp_device_supply_reset_values (DkpDeviceSupply *supply) +{ + DkpDevice *device = DKP_DEVICE (supply); + + supply->priv->has_coldplug_values = FALSE; + supply->priv->energy_old = 0; + supply->priv->energy_old_timespec.tv_sec = 0; + + /* reset to default */ + g_object_set (device, + "vendor", NULL, + "model", NULL, + "serial", NULL, + "update-time", 0, + "power-supply", FALSE, + "online", FALSE, + "energy", 0.0, + "is-present", FALSE, + "is-rechargeable", FALSE, + "has-history", FALSE, + "has-statistics", FALSE, + "state", NULL, + "capacity", 0.0, + "energy-empty", 0.0, + "energy-full", 0.0, + "energy-full-design", 0.0, + "energy-rate", 0.0, + "voltage", 0.0, + "time-to-empty", 0, + "time-to-full", 0, + "percentage", 0, + "technology", NULL, + NULL); +} + +/** + * dkp_device_supply_get_on_battery: + **/ +static gboolean +dkp_device_supply_get_on_battery (DkpDevice *device, gboolean *on_battery) +{ + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + DkpDeviceType type; + DkpDeviceState state; + gboolean is_present; + + g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE); + g_return_val_if_fail (on_battery != NULL, FALSE); + + g_object_get (device, + "type", &type, + "state", &state, + "is-present", &is_present, + NULL); + + if (type != DKP_DEVICE_TYPE_BATTERY) + return FALSE; + if (state == DKP_DEVICE_STATE_UNKNOWN) + return FALSE; + if (!is_present) + return FALSE; + + *on_battery = (state == DKP_DEVICE_STATE_DISCHARGING); + return TRUE; +} + +/** + * dkp_device_supply_get_low_battery: + **/ +static gboolean +dkp_device_supply_get_low_battery (DkpDevice *device, gboolean *low_battery) +{ + gboolean ret; + gboolean on_battery; + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + gdouble percentage; + + g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE); + g_return_val_if_fail (low_battery != NULL, FALSE); + + /* reuse the common checks */ + ret = dkp_device_supply_get_on_battery (device, &on_battery); + if (!ret) + return FALSE; + + /* shortcut */ + if (!on_battery) { + *low_battery = FALSE; + return TRUE; + } + + g_object_get (device, "percentage", &percentage, NULL); + *low_battery = (percentage < 10.0f); + return TRUE; +} + +/** + * dkp_device_supply_get_online: + **/ +static gboolean +dkp_device_supply_get_online (DkpDevice *device, gboolean *online) +{ + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + DkpDeviceType type; + gboolean online_tmp; + + g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE); + g_return_val_if_fail (online != NULL, FALSE); + + g_object_get (device, + "type", &type, + "online", &online_tmp, + NULL); + + if (type != DKP_DEVICE_TYPE_LINE_POWER) + return FALSE; + + *online = online_tmp; + + return TRUE; +} + +/** + * dkp_device_supply_calculate_rate: + **/ +static void +dkp_device_supply_calculate_rate (DkpDeviceSupply *supply) +{ + guint time_s; + gdouble energy; + gdouble energy_rate; + GTimeVal now; + DkpDevice *device = DKP_DEVICE (supply); + + g_object_get (device, "energy", &energy, NULL); + + if (energy < 0.1f) + return; + + if (supply->priv->energy_old < 0.1f) + return; + + if (supply->priv->energy_old == energy) + return; + + /* get the time difference */ + g_get_current_time (&now); + time_s = now.tv_sec - supply->priv->energy_old_timespec.tv_sec; + + if (time_s == 0) + return; + + /* get the difference in charge */ + energy = supply->priv->energy_old - energy; + if (energy < 0.1f) + return; + + /* probably okay */ + energy_rate = energy * 3600 / time_s; + g_object_set (device, "energy-rate", energy_rate, NULL); +} + +/** + * dkp_device_supply_convert_device_technology: + **/ +static DkpDeviceTechnology +dkp_device_supply_convert_device_technology (const gchar *type) +{ + if (type == NULL) + return DKP_DEVICE_TECHNOLOGY_UNKNOWN; + /* every case combination of Li-Ion is commonly used.. */ + if (g_ascii_strcasecmp (type, "li-ion") == 0 || + g_ascii_strcasecmp (type, "lion") == 0) + return DKP_DEVICE_TECHNOLOGY_LITHIUM_ION; + if (g_ascii_strcasecmp (type, "pb") == 0 || + g_ascii_strcasecmp (type, "pbac") == 0) + return DKP_DEVICE_TECHNOLOGY_LEAD_ACID; + if (g_ascii_strcasecmp (type, "lip") == 0 || + g_ascii_strcasecmp (type, "lipo") == 0 || + g_ascii_strcasecmp (type, "li-poly") == 0) + return DKP_DEVICE_TECHNOLOGY_LITHIUM_POLYMER; + if (g_ascii_strcasecmp (type, "nimh") == 0) + return DKP_DEVICE_TECHNOLOGY_NICKEL_METAL_HYDRIDE; + if (g_ascii_strcasecmp (type, "lifo") == 0) + return DKP_DEVICE_TECHNOLOGY_LITHIUM_IRON_PHOSPHATE; + return DKP_DEVICE_TECHNOLOGY_UNKNOWN; +} + +/** + * dkp_device_supply_get_string: + **/ +static gchar * +dkp_device_supply_get_string (const gchar *native_path, const gchar *key) +{ + gchar *value; + + /* get value, and strip to remove spaces */ + value = g_strstrip (sysfs_get_string (native_path, key)); + + /* no value */ + if (value == NULL) + goto out; + + /* empty value */ + if (value[0] == '\0') { + g_free (value); + value = NULL; + goto out; + } +out: + return value; +} + +/** + * dkp_device_supply_get_design_voltage: + **/ +static gdouble +dkp_device_supply_get_design_voltage (const gchar *native_path) +{ + gdouble voltage; + + /* design maximum */ + voltage = sysfs_get_double (native_path, "voltage_max_design") / 1000000.0; + if (voltage > 1.00f) { + egg_debug ("using max design voltage"); + goto out; + } + + /* design minimum */ + voltage = sysfs_get_double (native_path, "voltage_min_design") / 1000000.0; + if (voltage > 1.00f) { + egg_debug ("using min design voltage"); + goto out; + } + + /* current voltage */ + voltage = sysfs_get_double (native_path, "voltage_present") / 1000000.0; + if (voltage > 1.00f) { + egg_debug ("using present voltage"); + goto out; + } + + /* current voltage, alternate form */ + voltage = sysfs_get_double (native_path, "voltage_now") / 1000000.0; + if (voltage > 1.00f) { + egg_debug ("using present voltage (alternate)"); + goto out; + } + + /* completely guess, to avoid getting zero values */ + egg_warning ("no voltage values, using 10V as approximation"); + voltage = 10.0f; +out: + return voltage; +} + +/** + * dkp_device_supply_make_safe_string: + **/ +static void +dkp_device_supply_make_safe_string (gchar *text) +{ + guint i; + guint idx = 0; + + /* no point checking */ + if (text == NULL) + return; + + /* shunt up only safe chars */ + for (i=0; text[i] != '\0'; i++) { + if (g_ascii_isprint (text[i])) { + /* only copy if the address is going to change */ + if (idx != i) + text[idx] = text[i]; + idx++; + } else { + egg_debug ("invalid char '%c'", text[i]); + } + } + + /* ensure null terminated */ + text[idx] = '\0'; +} + +/** + * dkp_device_supply_refresh_battery: + * + * Return %TRUE on success, %FALSE if we failed to refresh or no data + **/ +static gboolean +dkp_device_supply_refresh_battery (DkpDeviceSupply *supply) +{ + gchar *status = NULL; + gchar *technology_native = NULL; + gboolean ret = TRUE; + gdouble voltage_design; + DkpDeviceState old_state; + DkpDeviceState state; + DkpDevice *device = DKP_DEVICE (supply); + const gchar *native_path; + GUdevDevice *native; + gboolean is_present; + gdouble energy; + gdouble energy_full; + gdouble energy_full_design; + gdouble energy_rate; + gdouble capacity; + gdouble percentage = 0.0f; + gdouble voltage; + guint64 time_to_empty; + guint64 time_to_full; + gchar *manufacturer = NULL; + gchar *model_name = NULL; + gchar *serial_number = NULL; + gboolean recall_notice; + const gchar *recall_vendor = NULL; + const gchar *recall_url = NULL; + DkpDaemon *daemon; + gboolean on_battery; + guint battery_count; + + native = G_UDEV_DEVICE (dkp_device_get_native (device)); + native_path = g_udev_device_get_sysfs_path (native); + + /* have we just been removed? */ + is_present = sysfs_get_bool (native_path, "present"); + g_object_set (device, "is-present", is_present, NULL); + if (!is_present) { + dkp_device_supply_reset_values (supply); + goto out; + } + + /* get the currect charge */ + energy = sysfs_get_double (native_path, "energy_now") / 1000000.0; + if (energy == 0) + energy = sysfs_get_double (native_path, "energy_avg") / 1000000.0; + + /* used to convert A to W later */ + voltage_design = dkp_device_supply_get_design_voltage (native_path); + + /* initial values */ + if (!supply->priv->has_coldplug_values) { + + /* when we add via sysfs power_supply class then we know this is true */ + g_object_set (device, "power-supply", TRUE, NULL); + + /* the ACPI spec is bad at defining battery type constants */ + technology_native = dkp_device_supply_get_string (native_path, "technology"); + g_object_set (device, "technology", dkp_device_supply_convert_device_technology (technology_native), NULL); + + /* get values which may be blank */ + manufacturer = dkp_device_supply_get_string (native_path, "manufacturer"); + model_name = dkp_device_supply_get_string (native_path, "model_name"); + serial_number = dkp_device_supply_get_string (native_path, "serial_number"); + + /* some vendors fill this with binary garbage */ + dkp_device_supply_make_safe_string (manufacturer); + dkp_device_supply_make_safe_string (model_name); + dkp_device_supply_make_safe_string (serial_number); + + /* are we possibly recalled by the vendor? */ + recall_notice = g_udev_device_has_property (native, "DKP_RECALL_NOTICE"); + if (recall_notice) { + recall_vendor = g_udev_device_get_property (native, "DKP_RECALL_VENDOR"); + recall_url = g_udev_device_get_property (native, "DKP_RECALL_URL"); + } + + g_object_set (device, + "vendor", manufacturer, + "model", model_name, + "serial", serial_number, + "is-rechargeable", TRUE, /* assume true for laptops */ + "has-history", TRUE, + "has-statistics", TRUE, + "recall-notice", recall_notice, + "recall-vendor", recall_vendor, + "recall-url", recall_url, + NULL); + + /* these don't change at runtime */ + energy_full = sysfs_get_double (native_path, "energy_full") / 1000000.0; + energy_full_design = sysfs_get_double (native_path, "energy_full_design") / 1000000.0; + + /* convert charge to energy */ + if (energy == 0) { + energy_full = sysfs_get_double (native_path, "charge_full") / 1000000.0; + energy_full_design = sysfs_get_double (native_path, "charge_full_design") / 1000000.0; + energy_full *= voltage_design; + energy_full_design *= voltage_design; + } + + /* the last full should not be bigger than the design */ + if (energy_full > energy_full_design) + egg_warning ("energy_full (%f) is greater than energy_full_design (%f)", + energy_full, energy_full_design); + + /* some systems don't have this */ + if (energy_full < 0.01) { + egg_warning ("correcting energy_full (%f) using energy_full_design (%f)", + energy_full, energy_full_design); + energy_full = energy_full_design; + } + + /* calculate how broken our battery is */ + if (energy_full > 0) { + capacity = (energy_full / energy_full_design) * 100.0f; + if (capacity < 0) + capacity = 0.0; + if (capacity > 100.0) + capacity = 100.0; + } + g_object_set (device, "capacity", capacity, NULL); + + /* we only coldplug once, as these values will never change */ + supply->priv->has_coldplug_values = TRUE; + } else { + /* get the old full */ + g_object_get (device, + "energy-full", &energy_full, + "energy-full-design", &energy_full_design, + NULL); + } + + status = g_strstrip (sysfs_get_string (native_path, "status")); + if (g_ascii_strcasecmp (status, "charging") == 0) + state = DKP_DEVICE_STATE_CHARGING; + else if (g_ascii_strcasecmp (status, "discharging") == 0) + state = DKP_DEVICE_STATE_DISCHARGING; + else if (g_ascii_strcasecmp (status, "full") == 0) + state = DKP_DEVICE_STATE_FULLY_CHARGED; + else if (g_ascii_strcasecmp (status, "empty") == 0) + state = DKP_DEVICE_STATE_EMPTY; + else if (g_ascii_strcasecmp (status, "unknown") == 0) + state = DKP_DEVICE_STATE_UNKNOWN; + else { + egg_warning ("unknown status string: %s", status); + state = DKP_DEVICE_STATE_UNKNOWN; + } + + /* only disable the polling if the kernel tells us we're fully charged, + not if we've guessed the state to be fully charged */ + supply->priv->enable_poll = (state != DKP_DEVICE_STATE_FULLY_CHARGED); + + /* reset unknown counter */ + if (state != DKP_DEVICE_STATE_UNKNOWN) { + egg_debug ("resetting unknown timeout after %i retries", supply->priv->unknown_retries); + supply->priv->unknown_retries = 0; + } + + /* get rate; it seems odd as it's either in uVh or uWh */ + energy_rate = fabs (sysfs_get_double (native_path, "current_now") / 1000000.0); + + /* convert charge to energy */ + if (energy == 0) { + energy = sysfs_get_double (native_path, "charge_now") / 1000000.0; + if (energy == 0) + energy = sysfs_get_double (native_path, "charge_avg") / 1000000.0; + energy *= voltage_design; + energy_rate *= voltage_design; + } + + /* some batteries don't update last_full attribute */ + if (energy > energy_full) { + egg_warning ("energy %f bigger than full %f", energy, energy_full); + energy_full = energy; + } + + /* present voltage */ + voltage = sysfs_get_double (native_path, "voltage_now") / 1000000.0; + if (voltage == 0) + voltage = sysfs_get_double (native_path, "voltage_avg") / 1000000.0; + + /* ACPI gives out the special 'Ones' value for rate when it's unable + * to calculate the true rate. We should set the rate zero, and wait + * for the BIOS to stabilise. */ + if (energy_rate == 0xffff) + energy_rate = 0; + + /* sanity check to less than 100W */ + if (energy_rate > 100*1000) + energy_rate = 0; + + /* the hardware reporting failed -- try to calculate this */ + if (energy_rate < 0) + dkp_device_supply_calculate_rate (supply); + + /* get a precise percentage */ + if (energy_full > 0.0f) { + percentage = 100.0 * energy / energy_full; + if (percentage < 0.0f) + percentage = 0.0f; + if (percentage > 100.0f) + percentage = 100.0f; + } + + /* some batteries stop charging much before 100% */ + if (state == DKP_DEVICE_STATE_UNKNOWN && + percentage > DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD) { + egg_debug ("fixing up unknown %f", percentage); + state = DKP_DEVICE_STATE_FULLY_CHARGED; + } + + /* the battery isn't charging or discharging, it's just + * sitting there half full doing nothing: try to guess a state */ + if (state == DKP_DEVICE_STATE_UNKNOWN) { + + /* get global battery status */ + daemon = dkp_device_get_daemon (device); + g_object_get (daemon, + "on-battery", &on_battery, + NULL); + + /* only guess when we have more than one battery devices */ + battery_count = dkp_daemon_get_number_devices_of_type (daemon, DKP_DEVICE_TYPE_BATTERY); + + /* try to find a suitable icon depending on AC state */ + if (battery_count > 1) { + if (on_battery && percentage < 1.0f) { + /* battery is low */ + state = DKP_DEVICE_STATE_EMPTY; + } else if (on_battery) { + /* battery is waiting */ + state = DKP_DEVICE_STATE_PENDING_DISCHARGE; + } else { + /* battery is waiting */ + state = DKP_DEVICE_STATE_PENDING_CHARGE; + } + } else { + if (on_battery) { + /* battery is assumed discharging */ + state = DKP_DEVICE_STATE_DISCHARGING; + } else { + /* battery is waiting */ + state = DKP_DEVICE_STATE_FULLY_CHARGED; + } + } + + /* print what we did */ + egg_debug ("guessing battery state '%s' using global on-battery:%i", + dkp_device_state_to_text (state), on_battery); + + g_object_unref (daemon); + } + + /* if empty, and BIOS does not know what to do */ + if (state == DKP_DEVICE_STATE_UNKNOWN && energy < 0.01) { + egg_warning ("Setting %s state empty as unknown and very low", native_path); + state = DKP_DEVICE_STATE_EMPTY; + } + + /* some batteries give out massive rate values when nearly empty */ + if (energy < 0.1f) + energy_rate = 0.0f; + + /* calculate a quick and dirty time remaining value */ + time_to_empty = 0; + time_to_full = 0; + if (energy_rate > 0) { + if (state == DKP_DEVICE_STATE_DISCHARGING) + time_to_empty = 3600 * (energy / energy_rate); + else if (state == DKP_DEVICE_STATE_CHARGING) + time_to_full = 3600 * ((energy_full - energy) / energy_rate); + /* TODO: need to factor in battery charge metrics */ + } + + /* check the remaining time is under a set limit, to deal with broken + primary batteries rate */ + if (time_to_empty > (20 * 60 * 60)) + time_to_empty = 0; + if (time_to_full > (20 * 60 * 60)) + time_to_full = 0; + + /* set the old status */ + supply->priv->energy_old = energy; + g_get_current_time (&supply->priv->energy_old_timespec); + + /* we changed state */ + g_object_get (device, "state", &old_state, NULL); + if (old_state != state) + supply->priv->energy_old = 0; + + g_object_set (device, + "energy", energy, + "energy-full", energy_full, + "energy-full-design", energy_full_design, + "energy-rate", energy_rate, + "percentage", percentage, + "state", state, + "voltage", voltage, + "time-to-empty", time_to_empty, + "time-to-full", time_to_full, + NULL); + +out: + g_free (technology_native); + g_free (manufacturer); + g_free (model_name); + g_free (serial_number); + g_free (status); + return ret; +} + +/** + * dkp_device_supply_poll_battery: + **/ +static gboolean +dkp_device_supply_poll_battery (DkpDeviceSupply *supply) +{ + DkpDevice *device = DKP_DEVICE (supply); + + egg_debug ("No updates on supply %s for %i seconds; forcing update", dkp_device_get_object_path (device), DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT); + supply->priv->poll_timer_id = 0; + dkp_device_supply_refresh (device); + + /* never repeat */ + return FALSE; +} + +/** + * dkp_device_supply_coldplug: + * + * Return %TRUE on success, %FALSE if we failed to get data and should be removed + **/ +static gboolean +dkp_device_supply_coldplug (DkpDevice *device) +{ + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + gboolean ret = FALSE; + GUdevDevice *native; + const gchar *native_path; + gchar *device_type = NULL; + DkpDeviceType type = DKP_DEVICE_TYPE_UNKNOWN; + + dkp_device_supply_reset_values (supply); + + /* detect what kind of device we are */ + native = G_UDEV_DEVICE (dkp_device_get_native (device)); + native_path = g_udev_device_get_sysfs_path (native); + if (native_path == NULL) { + egg_warning ("could not get native path for %p", device); + goto out; + } + + /* try to detect using the device type */ + device_type = dkp_device_supply_get_string (native_path, "type"); + if (device_type != NULL) { + if (g_ascii_strcasecmp (device_type, "mains") == 0) { + type = DKP_DEVICE_TYPE_LINE_POWER; + } else if (g_ascii_strcasecmp (device_type, "battery") == 0) { + type = DKP_DEVICE_TYPE_BATTERY; + } else { + egg_warning ("did not recognise type %s, please report", device_type); + } + } + + /* if reading the device type did not work, use the previous method */ + if (type == DKP_DEVICE_TYPE_UNKNOWN) { + if (sysfs_file_exists (native_path, "online")) { + type = DKP_DEVICE_TYPE_LINE_POWER; + } else { + /* this is a good guess as UPS and CSR are not in the kernel */ + type = DKP_DEVICE_TYPE_BATTERY; + } + } + + /* set the value */ + g_object_set (device, "type", type, NULL); + + /* coldplug values */ + ret = dkp_device_supply_refresh (device); +out: + g_free (device_type); + return ret; +} + +/** + * dkp_device_supply_setup_poll: + **/ +static gboolean +dkp_device_supply_setup_poll (DkpDevice *device) +{ + DkpDeviceState state; + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + + g_object_get (device, "state", &state, NULL); + + /* don't setup the poll only if we're sure */ + if (!supply->priv->enable_poll) + goto out; + + /* if it's unknown, poll faster than we would normally */ + if (state == DKP_DEVICE_STATE_UNKNOWN && + supply->priv->unknown_retries < DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES) { + supply->priv->poll_timer_id = + g_timeout_add_seconds (DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT, + (GSourceFunc) dkp_device_supply_poll_battery, supply); + /* increase count, we don't want to poll at 0.5Hz forever */ + supply->priv->unknown_retries++; + goto out; + } + + /* any other state just fall back */ + supply->priv->poll_timer_id = + g_timeout_add_seconds (DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT, + (GSourceFunc) dkp_device_supply_poll_battery, supply); +out: + return (supply->priv->poll_timer_id != 0); +} + +/** + * dkp_device_supply_refresh: + * + * Return %TRUE on success, %FALSE if we failed to refresh or no data + **/ +static gboolean +dkp_device_supply_refresh (DkpDevice *device) +{ + gboolean ret; + GTimeVal timeval; + DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device); + DkpDeviceType type; + + if (supply->priv->poll_timer_id > 0) { + g_source_remove (supply->priv->poll_timer_id); + supply->priv->poll_timer_id = 0; + } + + g_object_get (device, "type", &type, NULL); + switch (type) { + case DKP_DEVICE_TYPE_LINE_POWER: + ret = dkp_device_supply_refresh_line_power (supply); + break; + case DKP_DEVICE_TYPE_BATTERY: + ret = dkp_device_supply_refresh_battery (supply); + + /* Seems that we don't get change uevents from the + * kernel on some BIOS types */ + dkp_device_supply_setup_poll (device); + break; + default: + g_assert_not_reached (); + break; + } + + /* reset time if we got new data */ + if (ret) { + g_get_current_time (&timeval); + g_object_set (device, "update-time", (guint64) timeval.tv_sec, NULL); + } + + return ret; +} + +/** + * dkp_device_supply_init: + **/ +static void +dkp_device_supply_init (DkpDeviceSupply *supply) +{ + supply->priv = DKP_DEVICE_SUPPLY_GET_PRIVATE (supply); + supply->priv->unknown_retries = 0; + supply->priv->poll_timer_id = 0; + supply->priv->enable_poll = TRUE; +} + +/** + * dkp_device_supply_finalize: + **/ +static void +dkp_device_supply_finalize (GObject *object) +{ + DkpDeviceSupply *supply; + + g_return_if_fail (object != NULL); + g_return_if_fail (DKP_IS_SUPPLY (object)); + + supply = DKP_DEVICE_SUPPLY (object); + g_return_if_fail (supply->priv != NULL); + + if (supply->priv->poll_timer_id > 0) + g_source_remove (supply->priv->poll_timer_id); + + G_OBJECT_CLASS (dkp_device_supply_parent_class)->finalize (object); +} + +/** + * dkp_device_supply_class_init: + **/ +static void +dkp_device_supply_class_init (DkpDeviceSupplyClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass); + + object_class->finalize = dkp_device_supply_finalize; + device_class->get_on_battery = dkp_device_supply_get_on_battery; + device_class->get_low_battery = dkp_device_supply_get_low_battery; + device_class->get_online = dkp_device_supply_get_online; + device_class->coldplug = dkp_device_supply_coldplug; + device_class->refresh = dkp_device_supply_refresh; + + g_type_class_add_private (klass, sizeof (DkpDeviceSupplyPrivate)); +} + +/** + * dkp_device_supply_new: + **/ +DkpDeviceSupply * +dkp_device_supply_new (void) +{ + return g_object_new (DKP_TYPE_SUPPLY, NULL); +} + |