From da79f94b45b12154b89d3a095844f97115769558 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 16 Jun 2013 23:08:21 -0500 Subject: add device-provider-upower, a UPower implementation of device-provider --- src/device-provider-upower.c | 364 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 src/device-provider-upower.c (limited to 'src/device-provider-upower.c') diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c new file mode 100644 index 0000000..850717e --- /dev/null +++ b/src/device-provider-upower.c @@ -0,0 +1,364 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . + */ + +#include "config.h" + +#include "dbus-upower.h" +#include "dbus-upower-device.h" +#include "device.h" +#include "device-provider.h" +#include "device-provider-upower.h" + +#define BUS_NAME "org.freedesktop.UPower" +#define BUS_PATH "/org/freedesktop/UPower" + +/*** +**** private struct +***/ + +struct _IndicatorPowerDeviceProviderUPowerPriv +{ + DbusUPower * upower_proxy; + GHashTable * devices; + GCancellable * cancellable; + guint timer; +}; + +typedef IndicatorPowerDeviceProviderUPowerPriv priv_t; + +/*** +**** GObject boilerplate +***/ + +static void indicator_power_device_provider_interface_init ( + IndicatorPowerDeviceProviderInterface * iface); + +G_DEFINE_TYPE_WITH_CODE ( + IndicatorPowerDeviceProviderUPower, + indicator_power_device_provider_upower, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, + indicator_power_device_provider_interface_init)); + +/*** +**** TIMER +***/ + +/* + * Rebuilds are needed whenever upower devices are added, changed, or removed. + * + * Events often come in batches. For example, unplugging a power cable + * triggers a device-removed signal, and also a device-changed as the + * battery's state changes to 'discharging'. + * + * We use a small timer here to fold upower events into a single + * IndicatorPowerDeviceProvider devices-changed signal. + */ + +static gboolean +on_timer (gpointer gself) +{ + IndicatorPowerDeviceProvider * provider; + IndicatorPowerDeviceProviderUPower * provider_upower; + + provider = INDICATOR_POWER_DEVICE_PROVIDER (gself); + indicator_power_device_provider_emit_devices_changed (provider); + + provider_upower = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + provider_upower->priv->timer = 0; + return G_SOURCE_REMOVE; +} + +static void +emit_devices_changed_soon (IndicatorPowerDeviceProviderUPower * self) +{ + if (self->priv->timer == 0) + self->priv->timer = g_timeout_add_seconds (1, on_timer, self); +} + +/*** +**** UPOWER DBUS +***/ + +static void +on_upower_device_proxy_ready (GObject * o, GAsyncResult * res, gpointer gself) +{ + GError * err; + DbusDevice * tmp; + + err = NULL; + tmp = dbus_device_proxy_new_for_bus_finish (res, &err); + if (err != NULL) + { + g_warning ("Unable to get UPower Device Proxy: %s", err->message); + g_error_free (err); + } + else + { + IndicatorPowerDevice * device; + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + p = self->priv; + + const guint kind = dbus_device_get_type_ (tmp); + const gdouble percentage = dbus_device_get_percentage (tmp); + const guint state = dbus_device_get_state (tmp); + const gint64 time_to_empty = dbus_device_get_time_to_empty (tmp); + const gint64 time_to_full = dbus_device_get_time_to_full (tmp); + const time_t time = time_to_empty ? time_to_empty : time_to_full; + const char * path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (tmp)); + + device = indicator_power_device_new (path, + kind, + percentage, + state, + time); + + g_hash_table_insert (p->devices, + g_strdup (path), + g_object_ref (device)); + + emit_devices_changed_soon (self); + + g_object_unref (device); + g_object_unref (tmp); + } +} + +static void +update_device_from_object_path (IndicatorPowerDeviceProviderUPower * self, + const char * path) +{ + dbus_device_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + BUS_NAME, + path, + self->priv->cancellable, + on_upower_device_proxy_ready, + self); +} + +static void +on_upower_device_enumerations_ready (GObject * proxy, + GAsyncResult * res, + gpointer gself) +{ + GError * err; + char ** object_paths; + + err = NULL; + dbus_upower_call_enumerate_devices_finish (DBUS_UPOWER(proxy), + &object_paths, + res, + &err); + + if (err != NULL) + { + g_warning ("Unable to get UPower devices: %s", err->message); + g_error_free (err); + } + else + { + guint i; + + for (i=0; object_paths && object_paths[i]; i++) + update_device_from_object_path (gself, object_paths[i]); + + g_strfreev (object_paths); + } +} + +static void +on_upower_device_added (DbusUPower * unused G_GNUC_UNUSED, + const char * object_path, + gpointer gself) +{ + update_device_from_object_path (gself, object_path); +} + +static void +on_upower_device_changed (DbusUPower * unused G_GNUC_UNUSED, + const char * object_path, + gpointer gself) +{ + update_device_from_object_path (gself, object_path); +} + +static void +on_upower_device_removed (DbusUPower * unused G_GNUC_UNUSED, + const char * object_path, + gpointer gself) +{ + IndicatorPowerDeviceProviderUPower * self; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + g_hash_table_remove (self->priv->devices, object_path); + emit_devices_changed_soon (self); +} + +static void +on_upower_proxy_ready (GObject * source G_GNUC_UNUSED, + GAsyncResult * res, + gpointer gself) +{ + GError * err; + DbusUPower * proxy; + + err = NULL; + proxy = dbus_upower_proxy_new_for_bus_finish (res, &err); + if (err != NULL) + { + g_warning ("Unable to get UPower proxy: %s", err->message); + g_error_free (err); + } + else + { + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + p = self->priv; + + p->upower_proxy = proxy; + g_signal_connect (proxy, "device-changed", + G_CALLBACK (on_upower_device_changed), self); + g_signal_connect (proxy, "device-added", + G_CALLBACK (on_upower_device_added), self); + g_signal_connect (proxy, "device-removed", + G_CALLBACK (on_upower_device_removed), self); + + dbus_upower_call_enumerate_devices (p->upower_proxy, + p->cancellable, + on_upower_device_enumerations_ready, + self); + } +} + +/*** +**** IndicatorPowerDeviceProvider virtual functions +***/ + +static GList * +my_get_devices (IndicatorPowerDeviceProvider * provider) +{ + GList * devices; + IndicatorPowerDeviceProviderUPower * self; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(provider); + + devices = g_hash_table_get_values (self->priv->devices); + g_list_foreach (devices, (GFunc)g_object_ref, NULL); + return devices; +} + +/*** +**** GObject virtual functions +***/ + +static void +my_dispose (GObject * o) +{ + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(o); + p = self->priv; + + if (p->cancellable != NULL) + { + g_cancellable_cancel (p->cancellable); + + g_clear_object (&p->cancellable); + } + + if (p->timer != 0) + { + g_source_remove (p->timer); + + p->timer = 0; + } + + g_clear_object (&p->upower_proxy); + + g_clear_pointer (&p->devices, g_hash_table_destroy); + + G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose (o); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_device_provider_upower_class_init (IndicatorPowerDeviceProviderUPowerClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_dispose; + + g_type_class_add_private (klass, + sizeof (IndicatorPowerDeviceProviderUPowerPriv)); +} + +static void +indicator_power_device_provider_interface_init (IndicatorPowerDeviceProviderInterface * iface) +{ + iface->get_devices = my_get_devices; +} + +static void +indicator_power_device_provider_upower_init (IndicatorPowerDeviceProviderUPower * self) +{ + IndicatorPowerDeviceProviderUPowerPriv * p; + + p = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_UPOWER, + IndicatorPowerDeviceProviderUPowerPriv); + + self->priv = p; + + p->cancellable = g_cancellable_new (); + + p->devices = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + dbus_upower_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, + BUS_NAME, + BUS_PATH, + p->cancellable, + on_upower_proxy_ready, + self); +} + +/*** +**** Public API +***/ + +IndicatorPowerDeviceProvider * +indicator_power_device_provider_upower_new (void) +{ + gpointer o = g_object_new (INDICATOR_TYPE_POWER_DEVICE_PROVIDER_UPOWER, NULL); + + return INDICATOR_POWER_DEVICE_PROVIDER (o); +} -- cgit v1.2.3 From 72d74f44b31cefa9d5f4b66d34f9546cb4b40d5a Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 17 Jun 2013 11:53:41 -0500 Subject: in device-provider-upower's dispose(), disconnect from our proxy's signal before we unref the proxy --- src/device-provider-upower.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/device-provider-upower.c') diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 850717e..7cc8858 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -35,7 +35,7 @@ struct _IndicatorPowerDeviceProviderUPowerPriv { DbusUPower * upower_proxy; - GHashTable * devices; + GHashTable * devices; /* dbus object path --> IndicatorPowerDevice */ GCancellable * cancellable; guint timer; }; @@ -67,7 +67,7 @@ G_DEFINE_TYPE_WITH_CODE ( * triggers a device-removed signal, and also a device-changed as the * battery's state changes to 'discharging'. * - * We use a small timer here to fold upower events into a single + * We use a small timer here to fold multiple upower events into a single * IndicatorPowerDeviceProvider devices-changed signal. */ @@ -111,6 +111,8 @@ on_upower_device_proxy_ready (GObject * o, GAsyncResult * res, gpointer gself) } else { + /* use this proxy's properties to update our own IndicatorPowerDevice */ + IndicatorPowerDevice * device; IndicatorPowerDeviceProviderUPower * self; priv_t * p; @@ -295,8 +297,13 @@ my_dispose (GObject * o) p->timer = 0; } - - g_clear_object (&p->upower_proxy); + + if (p->upower_proxy != NULL) + { + g_signal_handlers_disconnect_by_data (p->upower_proxy, self); + + g_clear_object (&p->upower_proxy); + } g_clear_pointer (&p->devices, g_hash_table_destroy); -- cgit v1.2.3 From dec4af40c78f85b36b995f69da66f30d07270852 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 19 Jun 2013 09:43:11 -0500 Subject: lower the interval on the upower timer to make our updates more responsive --- src/device-provider-upower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/device-provider-upower.c') diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 7cc8858..6dd5029 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -89,7 +89,7 @@ static void emit_devices_changed_soon (IndicatorPowerDeviceProviderUPower * self) { if (self->priv->timer == 0) - self->priv->timer = g_timeout_add_seconds (1, on_timer, self); + self->priv->timer = g_timeout_add (333, on_timer, self); } /*** -- cgit v1.2.3 From 0ec4365a419db7d7b19e4bd415645ada50a5e5fc Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 19 Jun 2013 09:52:36 -0500 Subject: in provider-upower.c, clear its private hashtable in dispose() and destroy it in finalize() --- src/device-provider-upower.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src/device-provider-upower.c') diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 6dd5029..0fc5d91 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -305,7 +305,21 @@ my_dispose (GObject * o) g_clear_object (&p->upower_proxy); } - g_clear_pointer (&p->devices, g_hash_table_destroy); + g_hash_table_remove_all (p->devices); + + G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose (o); +} + +static void +my_finalize (GObject * o) +{ + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(o); + p = self->priv; + + g_hash_table_destroy (p->devices); G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose (o); } @@ -320,6 +334,7 @@ indicator_power_device_provider_upower_class_init (IndicatorPowerDeviceProviderU GObjectClass * object_class = G_OBJECT_CLASS (klass); object_class->dispose = my_dispose; + object_class->finalize = my_finalize; g_type_class_add_private (klass, sizeof (IndicatorPowerDeviceProviderUPowerPriv)); -- cgit v1.2.3 From 258f3ad689ae4ba49e2813a5051b4968d568f999 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 19 Jun 2013 14:47:23 -0500 Subject: adjust device-provider-upower's timer to reduce how many times we pull the device's properties over the bus. --- src/device-provider-upower.c | 134 +++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 48 deletions(-) (limited to 'src/device-provider-upower.c') diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 0fc5d91..05faeab 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -37,7 +37,12 @@ struct _IndicatorPowerDeviceProviderUPowerPriv DbusUPower * upower_proxy; GHashTable * devices; /* dbus object path --> IndicatorPowerDevice */ GCancellable * cancellable; - guint timer; + + /* a hashset of paths whose devices need to be refreshed */ + GHashTable * queued_paths; + + /* when this timer fires, the queued_paths will be refreshed */ + guint queued_paths_timer; }; typedef IndicatorPowerDeviceProviderUPowerPriv priv_t; @@ -57,45 +62,15 @@ G_DEFINE_TYPE_WITH_CODE ( indicator_power_device_provider_interface_init)); /*** -**** TIMER +**** UPOWER DBUS ***/ -/* - * Rebuilds are needed whenever upower devices are added, changed, or removed. - * - * Events often come in batches. For example, unplugging a power cable - * triggers a device-removed signal, and also a device-changed as the - * battery's state changes to 'discharging'. - * - * We use a small timer here to fold multiple upower events into a single - * IndicatorPowerDeviceProvider devices-changed signal. - */ - -static gboolean -on_timer (gpointer gself) -{ - IndicatorPowerDeviceProvider * provider; - IndicatorPowerDeviceProviderUPower * provider_upower; - - provider = INDICATOR_POWER_DEVICE_PROVIDER (gself); - indicator_power_device_provider_emit_devices_changed (provider); - - provider_upower = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); - provider_upower->priv->timer = 0; - return G_SOURCE_REMOVE; -} - static void -emit_devices_changed_soon (IndicatorPowerDeviceProviderUPower * self) +emit_devices_changed (IndicatorPowerDeviceProviderUPower * self) { - if (self->priv->timer == 0) - self->priv->timer = g_timeout_add (333, on_timer, self); + indicator_power_device_provider_emit_devices_changed (INDICATOR_POWER_DEVICE_PROVIDER (self)); } -/*** -**** UPOWER DBUS -***/ - static void on_upower_device_proxy_ready (GObject * o, GAsyncResult * res, gpointer gself) { @@ -138,7 +113,7 @@ on_upower_device_proxy_ready (GObject * o, GAsyncResult * res, gpointer gself) g_strdup (path), g_object_ref (device)); - emit_devices_changed_soon (self); + emit_devices_changed (self); g_object_unref (device); g_object_unref (tmp); @@ -158,6 +133,61 @@ update_device_from_object_path (IndicatorPowerDeviceProviderUPower * self, self); } +/* + * UPower doesn't seem to be sending PropertyChanged signals. + * + * Instead, it's got a DIY mechanism for notification: a DeviceChanged signal + * that doesn't tell us which property changed, so to refresh we need to + * rebuild all the properties with a GetAll() call. + * + * To make things worse, these DeviceChanged signals come fast and furious + * in common situations like disconnecting a power cable. + * + * This code tries to reduce bus traffic by adding a timer to wait a small bit + * before rebuilding our proxy's properties. This helps to fold multiple + * DeviceChanged events into a single rebuild. + */ + +/* rebuild all the proxies listed in our queued_paths hashset */ +static gboolean +on_queued_paths_timer (gpointer gself) +{ + gpointer path; + GHashTableIter iter; + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + p = self->priv; + + /* create new proxies for all the queued paths */ + g_hash_table_iter_init (&iter, p->queued_paths); + while (g_hash_table_iter_next (&iter, &path, NULL)) + update_device_from_object_path (self, path); + + /* cleanup */ + g_hash_table_remove_all (p->queued_paths); + p->queued_paths_timer = 0; + return G_SOURCE_REMOVE; +} + +/* add the path to our queued_paths hashset and ensure the timer's running */ +static void +refresh_device_soon (IndicatorPowerDeviceProviderUPower * self, + const char * object_path) +{ + priv_t * p = self->priv; + + g_hash_table_add (p->queued_paths, g_strdup (object_path)); + + if (p->queued_paths_timer == 0) + p->queued_paths_timer = g_timeout_add (500, on_queued_paths_timer, self); +} + +/*** +**** +***/ + static void on_upower_device_enumerations_ready (GObject * proxy, GAsyncResult * res, @@ -182,26 +212,26 @@ on_upower_device_enumerations_ready (GObject * proxy, guint i; for (i=0; object_paths && object_paths[i]; i++) - update_device_from_object_path (gself, object_paths[i]); + refresh_device_soon (gself, object_paths[i]); g_strfreev (object_paths); } } static void -on_upower_device_added (DbusUPower * unused G_GNUC_UNUSED, - const char * object_path, - gpointer gself) +on_upower_device_changed (DbusUPower * unused G_GNUC_UNUSED, + const char * object_path, + gpointer gself) { - update_device_from_object_path (gself, object_path); + refresh_device_soon (gself, object_path); } static void -on_upower_device_changed (DbusUPower * unused G_GNUC_UNUSED, - const char * object_path, - gpointer gself) +on_upower_device_added (DbusUPower * unused G_GNUC_UNUSED, + const char * object_path, + gpointer gself) { - update_device_from_object_path (gself, object_path); + refresh_device_soon (gself, object_path); } static void @@ -213,7 +243,9 @@ on_upower_device_removed (DbusUPower * unused G_GNUC_UNUSED, self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); g_hash_table_remove (self->priv->devices, object_path); - emit_devices_changed_soon (self); + g_hash_table_remove (self->priv->queued_paths, object_path); + + emit_devices_changed (self); } static void @@ -291,11 +323,11 @@ my_dispose (GObject * o) g_clear_object (&p->cancellable); } - if (p->timer != 0) + if (p->queued_paths_timer != 0) { - g_source_remove (p->timer); + g_source_remove (p->queued_paths_timer); - p->timer = 0; + p->queued_paths_timer = 0; } if (p->upower_proxy != NULL) @@ -320,6 +352,7 @@ my_finalize (GObject * o) p = self->priv; g_hash_table_destroy (p->devices); + g_hash_table_destroy (p->queued_paths); G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose (o); } @@ -364,6 +397,11 @@ indicator_power_device_provider_upower_init (IndicatorPowerDeviceProviderUPower g_free, g_object_unref); + p->queued_paths = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + dbus_upower_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, BUS_NAME, -- cgit v1.2.3