diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 46 | ||||
-rw-r--r-- | src/Makefile.am | 81 | ||||
-rw-r--r-- | src/brightness.c | 509 | ||||
-rw-r--r-- | src/brightness.h | 67 | ||||
-rw-r--r-- | src/dbus-shared.h | 28 | ||||
-rw-r--r-- | src/device-provider-upower.c | 509 | ||||
-rw-r--r-- | src/device-provider-upower.h | 6 | ||||
-rw-r--r-- | src/device-provider.c | 2 | ||||
-rw-r--r-- | src/device.c | 88 | ||||
-rw-r--r-- | src/device.h | 2 | ||||
-rw-r--r-- | src/ib-brightness-control.c | 156 | ||||
-rw-r--r-- | src/ib-brightness-control.h | 33 | ||||
-rw-r--r-- | src/main.c | 10 | ||||
-rw-r--r-- | src/notifier.c | 504 | ||||
-rw-r--r-- | src/notifier.h | 74 | ||||
-rw-r--r-- | src/org.freedesktop.UPower.xml | 43 | ||||
-rw-r--r-- | src/service.c | 341 |
17 files changed, 1841 insertions, 658 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f7efb80 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,46 @@ +set (SERVICE_LIB "indicatorpowerservice") +set (SERVICE_EXEC "indicator-power-service") + +add_definitions(-DG_LOG_DOMAIN="Indicator-Power") + +# handwritten sources +set(SERVICE_MANUAL_SOURCES + brightness.c + device-provider-upower.c + device-provider.c + device.c + notifier.c + service.c) + +# generated sources +include(GdbusCodegen) +set(SERVICE_GENERATED_SOURCES) +add_gdbus_codegen_with_namespace(SERVICE_GENERATED_SOURCES dbus-battery + com.canonical.indicator.power + Dbus + ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.power.Battery.xml) +# add the bin dir to our include path so the code can find the generated header files +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + +# add warnings/coverage info on handwritten files +# but not the autogenerated ones... +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-bad-function-cast") # g_clear_object() +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-used-but-marked-unused") # G_ADD_PRIVATE +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-disabled-macro-expansion") # G_DEFINE_TYPE +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-assign-enum") # GParamFlags +set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-switch-enum") +set_source_files_properties(${SERVICE_MANUAL_SOURCES} + PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} ${GCOV_FLAGS} -g -std=c99") + +# the service library for tests to link against (basically, everything except main()) +add_library(${SERVICE_LIB} STATIC ${SERVICE_MANUAL_SOURCES} ${SERVICE_GENERATED_SOURCES}) +include_directories(${CMAKE_SOURCE_DIR}) +link_directories(${SERVICE_DEPS_LIBRARY_DIRS}) + +# the executable: lib + main() +add_executable (${SERVICE_EXEC} main.c) +set_source_files_properties(${SERVICE_SOURCES} main.c PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -g -std=c99") +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) +install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) + diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index be746db..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,81 +0,0 @@ -BUILT_SOURCES = -EXTRA_DIST = -CLEANFILES = - -SHARED_CFLAGS = \ - -Wall -Wextra -Werror \ - $(SERVICE_DEPS_CFLAGS) \ - -DG_LOG_DOMAIN=\"Indicator-Power\" - -### -### - -upower_dbus_sources = \ - dbus-upower.c \ - dbus-upower.h - -$(upower_dbus_sources): org.freedesktop.UPower.xml - $(AM_V_GEN) gdbus-codegen \ - --c-namespace Dbus \ - --interface-prefix org.freedesktop \ - --generate-c-code dbus-upower \ - $^ - -BUILT_SOURCES += $(upower_dbus_sources) -CLEANFILES += $(upower_dbus_sources) -EXTRA_DIST += org.freedesktop.UPower.xml - -### -### -### - -noinst_LIBRARIES = libindicatorpower-upower.a libindicatorpower-service.a - -libindicatorpower_upower_a_SOURCES = \ - $(upower_dbus_sources) \ - device-provider-upower.c \ - device-provider-upower.h - -libindicatorpower_upower_a_CFLAGS = \ - $(SHARED_CFLAGS) \ - -Wno-unused-parameter \ - $(COVERAGE_CFLAGS) - -libindciatorpower_upower_a_LDFLAGS = $(COVERAGE_LDFLAGS) - -libindicatorpower_service_a_SOURCES = \ - ib-brightness-control.c \ - ib-brightness-control.h \ - device-provider.c \ - device-provider.h \ - device.c \ - device.h \ - service.c \ - service.h - -libindicatorpower_service_a_CFLAGS = \ - $(SHARED_CFLAGS) \ - -Wno-missing-field-initializers \ - $(COVERAGE_CFLAGS) - -libindicatorpower_service_a_LDFLAGS = $(COVERAGE_LDFLAGS) - -### -### -### - -pkglibexec_PROGRAMS = indicator-power-service - -indicator_power_service_SOURCES = main.c - -indicator_power_service_CFLAGS = \ - $(SHARED_CFLAGS) \ - $(COVERAGE_CFLAGS) - -indicator_power_service_LDADD = \ - libindicatorpower-upower.a \ - libindicatorpower-service.a \ - $(SERVICE_DEPS_LIBS) - -indicator_power_service_LDFLAGS = \ - $(COVERAGE_LDFLAGS) diff --git a/src/brightness.c b/src/brightness.c new file mode 100644 index 0000000..5e7c5e5 --- /dev/null +++ b/src/brightness.c @@ -0,0 +1,509 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include "brightness.h" + +#include <gio/gio.h> + +#define SCHEMA_NAME "com.ubuntu.touch.system" +#define KEY_AUTO "auto-brightness" +#define KEY_AUTO_SUPPORTED "auto-brightness-supported" +#define KEY_BRIGHTNESS "brightness" +#define KEY_NEED_DEFAULT "brightness-needs-hardware-default" + +enum +{ + PROP_0, + PROP_PERCENTAGE, + PROP_AUTO, + PROP_AUTO_SUPPORTED, + LAST_PROP +}; + +static GParamSpec* properties[LAST_PROP]; + +typedef struct +{ + GDBusConnection * system_bus; + GCancellable * cancellable; + + GSettings * settings; + + guint powerd_name_tag; + + double percentage; + + /* powerd brightness params */ + gint powerd_dim; + gint powerd_min; + gint powerd_max; + gint powerd_default_value; + gboolean powerd_ab_supported; + gboolean have_powerd_params; +} +IndicatorPowerBrightnessPrivate; + +typedef IndicatorPowerBrightnessPrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(IndicatorPowerBrightness, + indicator_power_brightness, + G_TYPE_OBJECT) + +#define get_priv(o) ((priv_t*)indicator_power_brightness_get_instance_private(o)) + +/*** +**** GObject virtual functions +***/ + +static void +my_get_property(GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerBrightness * self = INDICATOR_POWER_BRIGHTNESS(o); + priv_t * p = get_priv(self); + + switch (property_id) + { + case PROP_PERCENTAGE: + g_value_set_double(value, indicator_power_brightness_get_percentage(self)); + break; + + case PROP_AUTO: + g_value_set_boolean(value, p->settings ? g_settings_get_boolean(p->settings, KEY_AUTO) : FALSE); + break; + + case PROP_AUTO_SUPPORTED: + g_value_set_boolean(value, p->powerd_ab_supported); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(o, property_id, pspec); + } +} + +static void +my_set_property(GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerBrightness * self = INDICATOR_POWER_BRIGHTNESS(o); + priv_t * p = get_priv(self); + + switch (property_id) + { + case PROP_PERCENTAGE: + indicator_power_brightness_set_percentage(self, g_value_get_double(value)); + break; + + case PROP_AUTO: + if (p->settings != NULL) + g_settings_set_boolean (p->settings, KEY_AUTO, g_value_get_boolean(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(o, property_id, pspec); + } +} + +static void +my_dispose(GObject * o) +{ + IndicatorPowerBrightness * self = INDICATOR_POWER_BRIGHTNESS(o); + priv_t * p = get_priv(self); + + if (p->cancellable != NULL) + { + g_cancellable_cancel(p->cancellable); + g_clear_object(&p->cancellable); + } + + if (p->powerd_name_tag) + { + g_bus_unwatch_name(p->powerd_name_tag); + p->powerd_name_tag = 0; + } + + g_clear_object(&p->settings); + g_clear_object(&p->system_bus); + + G_OBJECT_CLASS(indicator_power_brightness_parent_class)->dispose(o); +} + +/*** +**** Percentage <-> Brightness Int conversion helpers +***/ + +static gdouble +brightness_to_percentage(IndicatorPowerBrightness * self, int brightness) +{ + const priv_t * p; + gdouble percentage; + + p = get_priv(self); + if (p->have_powerd_params) + { + const int lo = p->powerd_min; + const int hi = p->powerd_max; + percentage = (brightness-lo) / (double)(hi-lo); + } + else + { + percentage = 0; + } + + return percentage; +} + +static int +percentage_to_brightness(IndicatorPowerBrightness * self, double percentage) +{ + const priv_t * p; + int brightness; + + p = get_priv(self); + if (p->have_powerd_params) + { + const int lo = p->powerd_min; + const int hi = p->powerd_max; + brightness = (int)(lo + (percentage*(hi-lo))); + } + else + { + brightness = 0; + } + + return brightness; +} + +/** + * DBus Chatter: com.canonical.powerd + * + * This is used to get default value, and upper and lower bounds, + * of the brightness setting + */ + +static void set_brightness_global(IndicatorPowerBrightness*, int); + +static void +on_powerd_brightness_params_ready(GObject * source, + GAsyncResult * res, + gpointer gself) +{ + GError * error; + GVariant * v; + + error = NULL; + v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (v == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Unable to get system bus: %s", error->message); + + g_error_free(error); + } + else + { + IndicatorPowerBrightness * self = INDICATOR_POWER_BRIGHTNESS(gself); + priv_t * p = get_priv(self); + const gboolean old_ab_supported = p->powerd_ab_supported; + + p->have_powerd_params = TRUE; + g_variant_get(v, "((iiiib))", &p->powerd_dim, + &p->powerd_min, + &p->powerd_max, + &p->powerd_default_value, + &p->powerd_ab_supported); + g_debug("powerd brightness settings: dim=%d, min=%d, max=%d, default=%d, ab_supported=%d", + p->powerd_dim, + p->powerd_min, + p->powerd_max, + p->powerd_default_value, + (int)p->powerd_ab_supported); + + if (old_ab_supported != p->powerd_ab_supported) + g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_AUTO_SUPPORTED]); + + if (p->settings != NULL) + { + if (g_settings_get_boolean(p->settings, KEY_NEED_DEFAULT)) + { + /* user's first session, so init the schema's default + brightness from powerd's hardware-specific params */ + g_debug("%s is true, so initializing brightness to powerd default '%d'", KEY_NEED_DEFAULT, p->powerd_default_value); + set_brightness_global(self, p->powerd_default_value); + g_settings_set_boolean(p->settings, KEY_NEED_DEFAULT, FALSE); + } + else + { + /* not the first time, so restore the previous session's brightness */ + set_brightness_global(self, g_settings_get_int(p->settings, KEY_BRIGHTNESS)); + } + } + + /* cleanup */ + g_variant_unref(v); + } +} + +static void +call_powerd_get_brightness_params(IndicatorPowerBrightness * self) +{ + priv_t * p = get_priv(self); + + g_dbus_connection_call(p->system_bus, + "com.canonical.powerd", + "/com/canonical/powerd", + "com.canonical.powerd", + "getBrightnessParams", + NULL, + G_VARIANT_TYPE("((iiiib))"), + G_DBUS_CALL_FLAGS_NONE, + -1, /* default timeout */ + p->cancellable, + on_powerd_brightness_params_ready, + self); +} + +static void +on_powerd_appeared(GDBusConnection * connection, + const gchar * bus_name G_GNUC_UNUSED, + const gchar * name_owner G_GNUC_UNUSED, + gpointer gself) +{ + IndicatorPowerBrightness * self = INDICATOR_POWER_BRIGHTNESS(gself); + priv_t * p = get_priv(self); + + /* keep a handle to the system bus */ + g_clear_object(&p->system_bus); + p->system_bus = g_object_ref(connection); + + /* update our cache of powerd's brightness params */ + call_powerd_get_brightness_params(self); +} + +static void +on_powerd_vanished(GDBusConnection * connection G_GNUC_UNUSED, + const gchar * bus_name G_GNUC_UNUSED, + gpointer gself) +{ + priv_t * p = get_priv(INDICATOR_POWER_BRIGHTNESS(gself)); + + p->have_powerd_params = FALSE; +} + +/** + * DBus Chatter: com.canonical.Unity.Screen + * + * Used to set the backlight brightness via setUserBrightness + */ + +/* setUserBrightness doesn't return anything, + so this function is just to check for bus error messages */ +static void +on_set_uscreen_user_brightness_result(GObject * system_bus, + GAsyncResult * res, + gpointer gself G_GNUC_UNUSED) +{ + GError * error; + GVariant * v; + + error = NULL; + v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(system_bus), res, &error); + if (error != NULL) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Unable to call uscreen.setBrightness: %s", error->message); + + g_error_free(error); + } + + g_clear_pointer(&v, g_variant_unref); +} + +static void +set_uscreen_user_brightness(IndicatorPowerBrightness * self, + int value) +{ + priv_t * p = get_priv(self); + + g_dbus_connection_call(p->system_bus, + "com.canonical.Unity.Screen", + "/com/canonical/Unity/Screen", + "com.canonical.Unity.Screen", + "setUserBrightness", + g_variant_new("(i)", value), + NULL, /* no return args */ + G_DBUS_CALL_FLAGS_NONE, + -1, /* default timeout */ + p->cancellable, + on_set_uscreen_user_brightness_result, + self); +} + +/*** +**** +***/ + +static void +set_brightness_local(IndicatorPowerBrightness * self, int brightness) +{ + priv_t * p = get_priv(self); + p->percentage = brightness_to_percentage(self, brightness); + g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_PERCENTAGE]); +} + +static void +on_brightness_changed_in_schema(GSettings * settings, + gchar * key, + gpointer gself) +{ + set_brightness_local(INDICATOR_POWER_BRIGHTNESS(gself), + g_settings_get_int(settings, key)); +} + +static void +set_brightness_global(IndicatorPowerBrightness * self, int brightness) +{ + priv_t * p = get_priv(self); + + set_uscreen_user_brightness(self, brightness); + + if (p->settings != NULL) + g_settings_set_int(p->settings, KEY_BRIGHTNESS, brightness); + else + set_brightness_local(self, brightness); +} + +static void +on_auto_changed_in_schema(IndicatorPowerBrightness * self) +{ + g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_AUTO]); +} + + +/*** +**** Instantiation +***/ + +static void +indicator_power_brightness_init(IndicatorPowerBrightness * self) +{ + priv_t * p; + GSettingsSchema * schema; + + p = get_priv(self); + p->cancellable = g_cancellable_new(); + + schema = g_settings_schema_source_lookup(g_settings_schema_source_get_default(), + SCHEMA_NAME, + TRUE); + + /* "brightness" is only spec'ed for the phone profile, + so fail gracefully & silently if we don't have the + schema for it. */ + if (schema != NULL) + { + if (g_settings_schema_has_key(schema, KEY_BRIGHTNESS)) + { + p->settings = g_settings_new(SCHEMA_NAME); + g_signal_connect(p->settings, "changed::" KEY_BRIGHTNESS, + G_CALLBACK(on_brightness_changed_in_schema), self); + g_signal_connect_swapped(p->settings, "changed::" KEY_AUTO, + G_CALLBACK(on_auto_changed_in_schema), self); + } + g_settings_schema_unref(schema); + } + + p->powerd_name_tag = g_bus_watch_name(G_BUS_TYPE_SYSTEM, + "com.canonical.powerd", + G_BUS_NAME_WATCHER_FLAGS_NONE, + on_powerd_appeared, + on_powerd_vanished, + self, + NULL); +} + +static void +indicator_power_brightness_class_init(IndicatorPowerBrightnessClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = my_dispose; + object_class->get_property = my_get_property; + object_class->set_property = my_set_property; + + properties[PROP_0] = NULL; + + properties[PROP_PERCENTAGE] = g_param_spec_double( + "percentage", + "Percentage", + "Brightness percentage", + 0.0, /* minimum */ + 1.0, /* maximum */ + 0.8, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); + + properties[PROP_AUTO] = g_param_spec_boolean( + "auto-brightness", + "Auto-Brightness", + "Automatically adjust brightness level", + FALSE, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); + + properties[PROP_AUTO_SUPPORTED] = g_param_spec_boolean( + "auto-brightness-supported", + "Auto-Brightness Supported", + "True if the device can automatically adjust brightness", + FALSE, + G_PARAM_READABLE|G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, LAST_PROP, properties); +} + +/*** +**** Public API +***/ + +IndicatorPowerBrightness * +indicator_power_brightness_new(void) +{ + gpointer o = g_object_new(INDICATOR_TYPE_POWER_BRIGHTNESS, NULL); + + return INDICATOR_POWER_BRIGHTNESS(o); +} + +void +indicator_power_brightness_set_percentage(IndicatorPowerBrightness * self, + double percentage) +{ + g_return_if_fail(INDICATOR_IS_POWER_BRIGHTNESS(self)); + + set_brightness_global(self, percentage_to_brightness(self, percentage)); +} + +double +indicator_power_brightness_get_percentage(IndicatorPowerBrightness * self) +{ + g_return_val_if_fail(INDICATOR_IS_POWER_BRIGHTNESS(self), 0.0); + + return get_priv(self)->percentage; +} diff --git a/src/brightness.h b/src/brightness.h new file mode 100644 index 0000000..d2fcc61 --- /dev/null +++ b/src/brightness.h @@ -0,0 +1,67 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_POWER_BRIGHTNESS__H +#define INDICATOR_POWER_BRIGHTNESS__H + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +/* standard GObject macros */ +#define INDICATOR_POWER_BRIGHTNESS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_POWER_BRIGHTNESS, IndicatorPowerBrightness)) +#define INDICATOR_TYPE_POWER_BRIGHTNESS (indicator_power_brightness_get_type()) +#define INDICATOR_IS_POWER_BRIGHTNESS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_POWER_BRIGHTNESS)) + +typedef struct _IndicatorPowerBrightness IndicatorPowerBrightness; +typedef struct _IndicatorPowerBrightnessClass IndicatorPowerBrightnessClass; + +/* property keys */ +#define INDICATOR_POWER_BRIGHTNESS_PROP_PERCENTAGE "percentage" + +/** + * The Indicator Power Brightness. + */ +struct _IndicatorPowerBrightness +{ + /*< private >*/ + GObject parent; +}; + +struct _IndicatorPowerBrightnessClass +{ + GObjectClass parent_class; +}; + +/*** +**** +***/ + +GType indicator_power_brightness_get_type(void); + +IndicatorPowerBrightness * indicator_power_brightness_new(void); + +void indicator_power_brightness_set_percentage(IndicatorPowerBrightness * self, double percentage); + +double indicator_power_brightness_get_percentage(IndicatorPowerBrightness * self); + +G_END_DECLS + +#endif /* INDICATOR_POWER_BRIGHTNESS__H */ diff --git a/src/dbus-shared.h b/src/dbus-shared.h new file mode 100644 index 0000000..bf54034 --- /dev/null +++ b/src/dbus-shared.h @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * Ted Gould <ted@canonical.com> + */ + +#ifndef DBUS_SHARED_H +#define DBUS_SHARED_H + +#define BUS_NAME "com.canonical.indicator.power" +#define BUS_PATH "/com/canonical/indicator/power" + +#endif /* DBUS_SHARED_H */ + diff --git a/src/device-provider-upower.c b/src/device-provider-upower.c index 7c12beb..63f78ad 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -17,36 +17,45 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - -#include "dbus-upower.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" + +#define MGR_IFACE "org.freedesktop.UPower" +#define MGR_PATH "/org/freedesktop/UPower" + +#define DISPLAY_DEVICE_PATH "/org/freedesktop/UPower/devices/DisplayDevice" /*** **** private struct ***/ -struct _IndicatorPowerDeviceProviderUPowerPriv +typedef struct { GDBusConnection * bus; - - DbusUPower * upower_proxy; - GHashTable * devices; /* dbus object path --> IndicatorPowerDevice */ GCancellable * cancellable; + /* dbus object path --> IndicatorPowerDevice */ + GHashTable * devices; + /* 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; + GSList* subscriptions; + + guint name_tag; +} +IndicatorPowerDeviceProviderUPowerPrivate; + +typedef IndicatorPowerDeviceProviderUPowerPrivate priv_t; + +#define get_priv(o) ((priv_t*)indicator_power_device_provider_upower_get_instance_private(o)) + /*** **** GObject boilerplate @@ -59,8 +68,9 @@ G_DEFINE_TYPE_WITH_CODE ( IndicatorPowerDeviceProviderUPower, indicator_power_device_provider_upower, G_TYPE_OBJECT, + G_ADD_PRIVATE(IndicatorPowerDeviceProviderUPower) G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, - indicator_power_device_provider_interface_init)); + indicator_power_device_provider_interface_init)) /*** **** UPOWER DBUS @@ -79,11 +89,11 @@ emit_devices_changed (IndicatorPowerDeviceProviderUPower * self) } static void -on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) +on_get_all_response (GObject * o, GAsyncResult * res, gpointer gdata) { + struct device_get_all_data * data = gdata; GError * error; GVariant * response; - struct device_get_all_data * data = gdata; error = NULL; response = g_dbus_connection_call_finish (G_DBUS_CONNECTION(o), res, &error); @@ -102,9 +112,9 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) gdouble percentage = 0; gint64 time_to_empty = 0; gint64 time_to_full = 0; - time_t time; + gint64 time; IndicatorPowerDevice * device; - IndicatorPowerDeviceProviderUPowerPriv * p = data->self->priv; + priv_t * p = get_priv(data->self); GVariant * dict = g_variant_get_child_value (response, 0); g_variant_lookup (dict, "Type", "u", &kind); @@ -120,7 +130,7 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) INDICATOR_POWER_DEVICE_STATE, (gint)state, INDICATOR_POWER_DEVICE_OBJECT_PATH, data->path, INDICATOR_POWER_DEVICE_PERCENTAGE, percentage, - INDICATOR_POWER_DEVICE_TIME, (guint64)time, + INDICATOR_POWER_DEVICE_TIME, time, NULL); } else @@ -129,7 +139,7 @@ on_device_properties_ready (GObject * o, GAsyncResult * res, gpointer gdata) kind, percentage, state, - time); + (time_t)time); g_hash_table_insert (p->devices, g_strdup (data->path), @@ -151,55 +161,55 @@ static void update_device_from_object_path (IndicatorPowerDeviceProviderUPower * self, const char * path) { - priv_t * p = self->priv; + priv_t * p = get_priv(self); struct device_get_all_data * data; + /* Symbolic composite item. Nice idea! But its composite rules + differ from Design's so (for now) don't use it. + https://wiki.ubuntu.com/Power#Handling_multiple_batteries */ + if (!g_strcmp0(path, DISPLAY_DEVICE_PATH)) + return; + data = g_slice_new (struct device_get_all_data); data->path = g_strdup (path); data->self = self; - g_dbus_connection_call (p->bus, - BUS_NAME, - path, - "org.freedesktop.DBus.Properties", - "GetAll", - g_variant_new ("(s)", "org.freedesktop.UPower.Device"), - G_VARIANT_TYPE("(a{sv})"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, /* default timeout */ - p->cancellable, - on_device_properties_ready, - data); + g_dbus_connection_call(p->bus, + BUS_NAME, + path, + "org.freedesktop.DBus.Properties", + "GetAll", + g_variant_new ("(s)", "org.freedesktop.UPower.Device"), + G_VARIANT_TYPE("(a{sv})"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, /* default timeout */ + p->cancellable, + on_get_all_response, + data); } /* - * 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. + * UPower 0.99 added proper PropertyChanged signals, but before that + * it MGR_IFACE emitted a DeviceChanged signal which didn't tell which + * property changed, so all properties had to get refreshed w/GetAll(). * - * 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. + * Changes often come in bursts, so this timer tries to fold them together + * by waiting a small bit before making calling GetAll(). */ -/* rebuild all the proxies listed in our queued_paths hashset */ +/* rebuild all the devices listed in our queued_paths hashset */ static gboolean -on_queued_paths_timer (gpointer gself) +on_queued_paths_timer(gpointer gself) { - gpointer path; - GHashTableIter iter; IndicatorPowerDeviceProviderUPower * self; priv_t * p; + GHashTableIter iter; + gpointer path; self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); - p = self->priv; + p = get_priv(self); - /* create new proxies for all the queued paths */ + /* create new devices 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); @@ -215,7 +225,7 @@ static void refresh_device_soon (IndicatorPowerDeviceProviderUPower * self, const char * object_path) { - priv_t * p = self->priv; + priv_t * p = get_priv(self); g_hash_table_add (p->queued_paths, g_strdup (object_path)); @@ -228,155 +238,261 @@ refresh_device_soon (IndicatorPowerDeviceProviderUPower * self, ***/ static void -on_upower_device_enumerations_ready (GObject * proxy, - GAsyncResult * res, - gpointer gself) +on_enumerate_devices_response(GObject * bus, + GAsyncResult * res, + gpointer gself) { - GError * err; - char ** object_paths; - - err = NULL; - dbus_upower_call_enumerate_devices_finish (DBUS_UPOWER(proxy), - &object_paths, - res, - &err); + GError* error; + GVariant* v; - if (err != NULL) + error = NULL; + v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(bus), res, &error); + if (v == NULL) { - g_warning ("Unable to get UPower devices: %s", err->message); - g_error_free (err); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Unable to enumerate UPower devices: %s", error->message); + g_error_free (error); } - else + else if (g_variant_is_of_type(v, G_VARIANT_TYPE("(ao)"))) { - guint i; + GVariant * ao; + GVariantIter iter; + const gchar * path; - for (i=0; object_paths && object_paths[i]; i++) - refresh_device_soon (gself, object_paths[i]); + ao = g_variant_get_child_value(v, 0); + g_variant_iter_init(&iter, ao); + path = NULL; + while(g_variant_iter_loop(&iter, "o", &path)) + refresh_device_soon (gself, path); - g_strfreev (object_paths); + g_variant_unref(ao); } -} -static void -on_upower_device_changed (DbusUPower * unused G_GNUC_UNUSED, - const char * object_path, - gpointer gself) -{ - refresh_device_soon (gself, object_path); + g_clear_pointer(&v, g_variant_unref); } static void -on_upower_device_added (DbusUPower * unused G_GNUC_UNUSED, - const char * object_path, - gpointer gself) +on_device_properties_changed(GDBusConnection * connection G_GNUC_UNUSED, + const gchar * sender_name G_GNUC_UNUSED, + const gchar * object_path, + const gchar * interface_name G_GNUC_UNUSED, + const gchar * signal_name G_GNUC_UNUSED, + GVariant * parameters, + gpointer gself) { - refresh_device_soon (gself, object_path); -} + IndicatorPowerDeviceProviderUPower* self; + priv_t* p; + IndicatorPowerDevice* device; -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); + p = get_priv(self); - self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); - g_hash_table_remove (self->priv->devices, object_path); - g_hash_table_remove (self->priv->queued_paths, object_path); + device = g_hash_table_lookup(p->devices, object_path); + if (device == NULL) /* unlikely, but let's handle it */ + { + refresh_device_soon (self, object_path); + } + else if ((parameters != NULL) && g_variant_n_children(parameters)>=2) + { + gboolean changed = FALSE; + GVariant* dict; + GVariantIter iter; + gchar* key; + GVariant* value; + + dict = g_variant_get_child_value(parameters, 1); + g_variant_iter_init(&iter, dict); + while (g_variant_iter_next(&iter, "{sv}", &key, &value)) + { + if (!g_strcmp0(key, "TimeToFull") || !g_strcmp0(key, "TimeToEmpty")) + { + const gint64 i = g_variant_get_int64(value); + if (i != 0) + { + g_object_set(device, + INDICATOR_POWER_DEVICE_TIME, (guint64)i, + NULL); + changed = TRUE; + } + } + else if (!g_strcmp0(key, "Percentage")) + { + const gdouble d = g_variant_get_double(value); + g_object_set(device, + INDICATOR_POWER_DEVICE_PERCENTAGE, d, + NULL); + changed = TRUE; + } + else if (!g_strcmp0(key, "Type")) + { + const guint32 u = g_variant_get_uint32(value); + g_object_set(device, + INDICATOR_POWER_DEVICE_KIND, (gint)u, + NULL); + changed = TRUE; + } + else if (!g_strcmp0(key, "State")) + { + const guint32 u = g_variant_get_uint32(value); + g_object_set(device, + INDICATOR_POWER_DEVICE_STATE, (gint)u, + NULL); + changed = TRUE; + } + } + g_variant_unref(dict); - emit_devices_changed (self); + if (changed) + emit_devices_changed(self); + } } -static void -on_upower_resuming (DbusUPower * unused G_GNUC_UNUSED, - gpointer gself) +static const gchar* +get_path_from_nth_child(GVariant* parameters, gsize i) { - IndicatorPowerDeviceProviderUPower * self; - GHashTableIter iter; - gpointer object_path; + const gchar* path = NULL; - self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); + if ((parameters != NULL) && g_variant_n_children(parameters)>i) + { + GVariant* v = g_variant_get_child_value(parameters, i); + if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING) || /* UPower < 0.99 */ + g_variant_is_of_type(v, G_VARIANT_TYPE_OBJECT_PATH)) /* and >= 0.99 */ + { + path = g_variant_get_string(v, NULL); + } + g_variant_unref(v); + } - g_debug ("Resumed from hibernate/sleep; queueing all devices for a refresh"); - g_hash_table_iter_init (&iter, self->priv->devices); - while (g_hash_table_iter_next (&iter, &object_path, NULL)) - refresh_device_soon (self, object_path); + return path; } static void -on_upower_proxy_ready (GObject * source G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gself) +on_upower_signal(GDBusConnection * connection G_GNUC_UNUSED, + const gchar * sender_name G_GNUC_UNUSED, + const gchar * object_path G_GNUC_UNUSED, + const gchar * interface_name G_GNUC_UNUSED, + const gchar * signal_name, + GVariant * parameters, + gpointer gself) { - GError * err; - DbusUPower * proxy; + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(gself); + p = get_priv(self); - err = NULL; - proxy = dbus_upower_proxy_new_finish (res, &err); - if (err != NULL) + if (!g_strcmp0(signal_name, "DeviceAdded")) { - g_warning ("Unable to get UPower proxy: %s", err->message); - g_error_free (err); + refresh_device_soon (self, get_path_from_nth_child(parameters, 0)); } - else + else if (!g_strcmp0(signal_name, "DeviceRemoved")) { - IndicatorPowerDeviceProviderUPower * self; - priv_t * p; - - self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); - p = self->priv; - - p->upower_proxy = proxy; - g_signal_connect (proxy, "resuming", - G_CALLBACK (on_upower_resuming), self); - 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); + const char* device_path = get_path_from_nth_child(parameters, 0); + g_hash_table_remove(p->devices, device_path); + g_hash_table_remove(p->queued_paths, device_path); + emit_devices_changed(self); + } + else if (!g_strcmp0(signal_name, "DeviceChanged")) /* UPower < 0.99 */ + { + refresh_device_soon (self, get_path_from_nth_child(parameters, 0)); + } + else if (!g_strcmp0(signal_name, "Resuming")) /* UPower < 0.99 */ + { + GHashTableIter iter; + gpointer device_path = NULL; + g_debug("Resumed from hibernate/sleep; queueing all devices for a refresh"); + g_hash_table_iter_init (&iter, p->devices); + while (g_hash_table_iter_next (&iter, &device_path, NULL)) + refresh_device_soon (self, device_path); } } +/* start listening for UPower events on the bus */ static void -on_bus_ready (GObject * source_object G_GNUC_UNUSED, - GAsyncResult * res, - gpointer gself) +on_bus_name_appeared(GDBusConnection * bus, + const gchar * name G_GNUC_UNUSED, + const gchar * name_owner, + gpointer gself) { - GError * error; - GDBusConnection * tmp; + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + guint tag; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(gself); + p = get_priv(self); + p->bus = G_DBUS_CONNECTION(g_object_ref(bus)); + + /* listen for signals from the boss */ + tag = g_dbus_connection_signal_subscribe(p->bus, + name_owner, + MGR_IFACE, + NULL /*signal_name*/, + MGR_PATH, + NULL /*arg0*/, + G_DBUS_SIGNAL_FLAGS_NONE, + on_upower_signal, + self, + NULL); + p->subscriptions = g_slist_prepend(p->subscriptions, GUINT_TO_POINTER(tag)); + + /* listen for change events from the devices */ + tag = g_dbus_connection_signal_subscribe(p->bus, + name_owner, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + NULL /*object_path*/, + "org.freedesktop.UPower.Device", /*arg0*/ + G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, + on_device_properties_changed, + self, + NULL); + p->subscriptions = g_slist_prepend(p->subscriptions, GUINT_TO_POINTER(tag)); + + /* rebuild our devices list */ + g_dbus_connection_call(p->bus, + BUS_NAME, + MGR_PATH, + MGR_IFACE, + "EnumerateDevices", + NULL, + G_VARIANT_TYPE("(ao)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, /* default timeout */ + p->cancellable, + on_enumerate_devices_response, + self); +} - error = NULL; - tmp = g_bus_get_finish (res, &error); - if (error != NULL) +static void +on_bus_name_vanished(GDBusConnection * connection G_GNUC_UNUSED, + const gchar * name G_GNUC_UNUSED, + gpointer gself) +{ + IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + GSList * l; + + self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(gself); + p = get_priv(self); + + /* clear the devices */ + g_hash_table_remove_all(p->devices); + g_hash_table_remove_all(p->queued_paths); + if (p->queued_paths_timer != 0) { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Error acquiring bus: %s", error->message); - g_error_free (error); + g_source_remove(p->queued_paths_timer); + p->queued_paths_timer = 0; } - else - { - IndicatorPowerDeviceProviderUPower * self; - priv_t * p; - - self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER (gself); - p = self->priv; + emit_devices_changed (self); - p->bus = tmp; + /* clear the bus subscriptions */ + for (l=p->subscriptions; l!=NULL; l=l->next) + g_dbus_connection_signal_unsubscribe(p->bus, GPOINTER_TO_UINT(l->data)); + g_slist_free(p->subscriptions); + p->subscriptions = NULL; - dbus_upower_proxy_new (p->bus, - G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, - BUS_NAME, - BUS_PATH, - p->cancellable, - on_upower_proxy_ready, - self); - } + /* clear the bus */ + g_clear_object(&p->bus); } /*** @@ -384,14 +500,16 @@ on_bus_ready (GObject * source_object G_GNUC_UNUSED, ***/ static GList * -my_get_devices (IndicatorPowerDeviceProvider * provider) +my_get_devices(IndicatorPowerDeviceProvider * provider) { - GList * devices; IndicatorPowerDeviceProviderUPower * self; + priv_t * p; + GList * devices; self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(provider); + p = get_priv(self); - devices = g_hash_table_get_values (self->priv->devices); + devices = g_hash_table_get_values (p->devices); g_list_foreach (devices, (GFunc)g_object_ref, NULL); return devices; } @@ -407,7 +525,7 @@ my_dispose (GObject * o) priv_t * p; self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(o); - p = self->priv; + p = get_priv(self); if (p->cancellable != NULL) { @@ -423,18 +541,15 @@ my_dispose (GObject * o) p->queued_paths_timer = 0; } - if (p->upower_proxy != NULL) + if (p->name_tag != 0) { - g_signal_handlers_disconnect_by_data (p->upower_proxy, self); + g_bus_unwatch_name(p->name_tag); + on_bus_name_vanished(NULL, NULL, self); - g_clear_object (&p->upower_proxy); + p->name_tag = 0; } - g_hash_table_remove_all (p->devices); - - g_clear_object (&p->bus); - - G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose (o); + G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->dispose(o); } static void @@ -444,12 +559,12 @@ my_finalize (GObject * o) priv_t * p; self = INDICATOR_POWER_DEVICE_PROVIDER_UPOWER(o); - p = self->priv; + p = get_priv(self); 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); + G_OBJECT_CLASS (indicator_power_device_provider_upower_parent_class)->finalize (o); } /*** @@ -463,9 +578,6 @@ indicator_power_device_provider_upower_class_init (IndicatorPowerDeviceProviderU object_class->dispose = my_dispose; object_class->finalize = my_finalize; - - g_type_class_add_private (klass, - sizeof (IndicatorPowerDeviceProviderUPowerPriv)); } static void @@ -477,30 +589,27 @@ indicator_power_device_provider_interface_init (IndicatorPowerDeviceProviderInte 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); - - p->queued_paths = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - - g_bus_get (G_BUS_TYPE_SYSTEM, - p->cancellable, - on_bus_ready, - self); + priv_t * p = get_priv(self); + + p->cancellable = g_cancellable_new(); + + p->devices = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + p->queued_paths = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + NULL); + + p->name_tag = g_bus_watch_name(G_BUS_TYPE_SYSTEM, + BUS_NAME, + G_BUS_NAME_WATCHER_FLAGS_NONE, + on_bus_name_appeared, + on_bus_name_vanished, + self, + NULL); } /*** @@ -508,7 +617,7 @@ indicator_power_device_provider_upower_init (IndicatorPowerDeviceProviderUPower ***/ IndicatorPowerDeviceProvider * -indicator_power_device_provider_upower_new (void) +indicator_power_device_provider_upower_new(void) { gpointer o = g_object_new (INDICATOR_TYPE_POWER_DEVICE_PROVIDER_UPOWER, NULL); diff --git a/src/device-provider-upower.h b/src/device-provider-upower.h index 7bdd5d5..f385479 100644 --- a/src/device-provider-upower.h +++ b/src/device-provider-upower.h @@ -45,8 +45,6 @@ G_BEGIN_DECLS typedef struct _IndicatorPowerDeviceProviderUPower IndicatorPowerDeviceProviderUPower; -typedef struct _IndicatorPowerDeviceProviderUPowerPriv - IndicatorPowerDeviceProviderUPowerPriv; typedef struct _IndicatorPowerDeviceProviderUPowerClass IndicatorPowerDeviceProviderUPowerClass; @@ -56,8 +54,6 @@ typedef struct _IndicatorPowerDeviceProviderUPowerClass struct _IndicatorPowerDeviceProviderUPower { GObject parent_instance; - - IndicatorPowerDeviceProviderUPowerPriv * priv; }; struct _IndicatorPowerDeviceProviderUPowerClass @@ -65,6 +61,8 @@ struct _IndicatorPowerDeviceProviderUPowerClass GObjectClass parent_class; }; +GType indicator_power_device_provider_upower_get_type (void); + IndicatorPowerDeviceProvider * indicator_power_device_provider_upower_new (void); G_END_DECLS diff --git a/src/device-provider.c b/src/device-provider.c index 81a8eec..46fcfad 100644 --- a/src/device-provider.c +++ b/src/device-provider.c @@ -29,7 +29,7 @@ static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_INTERFACE (IndicatorPowerDeviceProvider, indicator_power_device_provider, - 0); + 0) static void indicator_power_device_provider_default_init (IndicatorPowerDeviceProviderInterface * klass) diff --git a/src/device.c b/src/device.c index ed3c399..eff76d1 100644 --- a/src/device.c +++ b/src/device.c @@ -44,8 +44,6 @@ struct _IndicatorPowerDevicePrivate GTimer * inestimable; }; -#define INDICATOR_POWER_DEVICE_GET_PRIVATE(o) (INDICATOR_POWER_DEVICE(o)->priv) - /* Properties */ /* Enum for the properties so that they can be quickly found and looked up. */ enum { @@ -69,7 +67,7 @@ static void set_property (GObject*, guint prop_id, const GValue*, GParamSpec* ); static void get_property (GObject*, guint prop_id, GValue*, GParamSpec* ); /* LCOV_EXCL_START */ -G_DEFINE_TYPE (IndicatorPowerDevice, indicator_power_device, G_TYPE_OBJECT); +G_DEFINE_TYPE (IndicatorPowerDevice, indicator_power_device, G_TYPE_OBJECT) /* LCOV_EXCL_STOP */ static void @@ -189,7 +187,7 @@ get_property (GObject * o, guint prop_id, GValue * value, GParamSpec * pspec) break; case PROP_TIME: - g_value_set_uint64 (value, priv->time); + g_value_set_uint64 (value, (guint64)priv->time); break; default: @@ -207,11 +205,11 @@ set_property (GObject * o, guint prop_id, const GValue * value, GParamSpec * psp switch (prop_id) { case PROP_KIND: - p->kind = g_value_get_int (value); + p->kind = (UpDeviceKind) g_value_get_int (value); break; case PROP_STATE: - p->state = g_value_get_int (value); + p->state = (UpDeviceState) g_value_get_int (value); break; case PROP_OBJECT_PATH: @@ -224,7 +222,7 @@ set_property (GObject * o, guint prop_id, const GValue * value, GParamSpec * psp break; case PROP_TIME: - p->time = g_value_get_uint64(value); + p->time = (time_t) g_value_get_uint64(value); break; default: @@ -355,13 +353,6 @@ device_kind_to_string (UpDeviceKind kind) indicator_power_device_get_icon_names: @device: #IndicatorPowerDevice from which to generate the icon names - This function's logic differs from GSD's power plugin in some ways: - - 1. For discharging batteries, we decide whether or not to use the 'caution' - icon based on whether or not we have <= 30 minutes remaining, rather than - looking at the battery's percentage left. - <https://bugs.launchpad.net/indicator-power/+bug/743823> - See also indicator_power_device_get_gicon(). Return value: (array zero-terminated=1) (transfer full): @@ -416,21 +407,15 @@ indicator_power_device_get_icon_names (const IndicatorPowerDevice * device) case UP_DEVICE_STATE_CHARGING: suffix_str = get_device_icon_suffix (percentage); index_str = get_device_icon_index (percentage); - g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging-symbolic", kind_str, suffix_str)); + g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging", kind_str, index_str)); g_ptr_array_add (names, g_strdup_printf ("gpm-%s-%s-charging", kind_str, index_str)); + g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging-symbolic", kind_str, suffix_str)); g_ptr_array_add (names, g_strdup_printf ("%s-%s-charging", kind_str, suffix_str)); break; case UP_DEVICE_STATE_PENDING_CHARGE: case UP_DEVICE_STATE_DISCHARGING: case UP_DEVICE_STATE_PENDING_DISCHARGE: - /* Don't show the caution/red icons unless we have <=30 min left. - <https://bugs.launchpad.net/indicator-power/+bug/743823> - Themes use the caution color when the percentage is 0% or 20%, - so if we have >30 min left, use 30% as the icon's percentage floor */ - if (indicator_power_device_get_time (device) > (30*60)) - percentage = MAX(percentage, 30); - suffix_str = get_device_icon_suffix (percentage); index_str = get_device_icon_index (percentage); g_ptr_array_add (names, g_strdup_printf ("%s-%s", kind_str, index_str)); @@ -595,10 +580,12 @@ get_expanded_time_remaining (const IndicatorPowerDevice * device) if (p->state == UP_DEVICE_STATE_CHARGING) { + /* TRANSLATORS: H:MM (hours, minutes) to charge the battery. Example: "1:30 to charge" */ str = g_strdup_printf (_("%0d:%02d to charge"), hours, minutes); } else // discharging { + /* TRANSLATORS: H:MM (hours, minutes) to discharge the battery. Example: "1:30 left"*/ str = g_strdup_printf (_("%0d:%02d left"), hours, minutes); } } @@ -624,29 +611,45 @@ get_accessible_time_remaining (const IndicatorPowerDevice * device) if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING))) { - int minutes = p->time / 60; - const int hours = minutes / 60; + guint minutes = (guint)p->time / 60u; + const guint hours = minutes / 60u; minutes %= 60; if (p->state == UP_DEVICE_STATE_CHARGING) { if (hours > 0) - str = g_strdup_printf (_("%d %s %d %s to charge"), - hours, g_dngettext (NULL, "hour", "hours", hours), - minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + { + /* TRANSLATORS: "X (hour,hours) Y (minute,minutes) to charge" the battery. + Example: "1 hour 10 minutes to charge" */ + str = g_strdup_printf (_("%d %s %d %s to charge"), + hours, g_dngettext (NULL, "hour", "hours", hours), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } else - str = g_strdup_printf (_("%d %s to charge"), - minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + { + /* TRANSLATORS: "Y (minute,minutes) to charge" the battery. + Example: "59 minutes to charge" */ + str = g_strdup_printf (_("%d %s to charge"), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } } else // discharging { if (hours > 0) - str = g_strdup_printf (_("%d %s %d %s left"), - hours, g_dngettext (NULL, "hour", "hours", hours), - minutes, g_dngettext (NULL, "minute", "minutes", minutes)); - else - str = g_strdup_printf (_("%d %s left"), - minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + { + /* TRANSLATORS: "X (hour,hours) Y (minute,minutes) left" until the battery's empty. + Example: "1 hour 10 minutes left" */ + str = g_strdup_printf (_("%d %s %d %s left"), + hours, g_dngettext (NULL, "hour", "hours", hours), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } + else + { + /* TRANSLATORS: "Y (minute,minutes) left" until the battery's empty. + Example: "59 minutes left" */ + str = g_strdup_printf (_("%d %s left"), + minutes, g_dngettext (NULL, "minute", "minutes", minutes)); + } } } else @@ -700,6 +703,7 @@ get_menuitem_text (const IndicatorPowerDevice * device, if (p->state == UP_DEVICE_STATE_FULLY_CHARGED) { + /* TRANSLATORS: example: "battery (charged)" */ str = g_strdup_printf (_("%s (charged)"), kind_str); } else @@ -715,9 +719,14 @@ get_menuitem_text (const IndicatorPowerDevice * device, } if (time_str && *time_str) - str = g_strdup_printf (_("%s (%s)"), kind_str, time_str); + { + /* TRANSLATORS: example: "battery (time remaining)" */ + str = g_strdup_printf (_("%s (%s)"), kind_str, time_str); + } else - str = g_strdup (kind_str); + { + str = g_strdup (kind_str); + } g_free (time_str); } @@ -783,14 +792,17 @@ indicator_power_device_get_readable_title (const IndicatorPowerDevice * device, if (want_time && want_percent) { + /* TRANSLATORS: after the icon, a time-remaining string + battery %. Example: "(0:59, 33%)" */ str = g_strdup_printf (_("(%s, %.0lf%%)"), time_str, p->percentage); } else if (want_time) { + /* TRANSLATORS: after the icon, a time-remaining string Example: "(0:59)" */ str = g_strdup_printf (_("(%s)"), time_str); } else if (want_percent) { + /* TRANSLATORS: after the icon, a battery %. Example: "(33%)" */ str = g_strdup_printf (_("(%.0lf%%)"), p->percentage); } else @@ -861,5 +873,5 @@ indicator_power_device_new_from_variant (GVariant * v) kind, percentage, state, - time); + (time_t)time); } diff --git a/src/device.h b/src/device.h index 3a10f89..d867707 100644 --- a/src/device.h +++ b/src/device.h @@ -24,7 +24,7 @@ License along with this library. If not, see #ifndef __INDICATOR_POWER_DEVICE_H__ #define __INDICATOR_POWER_DEVICE_H__ -#include <glib-object.h> +#include <gio/gio.h> /* GIcon */ G_BEGIN_DECLS diff --git a/src/ib-brightness-control.c b/src/ib-brightness-control.c deleted file mode 100644 index 4fb6bc5..0000000 --- a/src/ib-brightness-control.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Renato Araujo Oliveira Filho <renato@canonical.com> - */ - -#include <gudev/gudev.h> - -#include <errno.h> -#include <stdlib.h> -#include <fcntl.h> -#include <string.h> - -#include "ib-brightness-control.h" - -struct _IbBrightnessControl -{ - gchar *path; -}; - -IbBrightnessControl* -ib_brightness_control_new (void) -{ - IbBrightnessControl *control; - GUdevClient *client; - gchar *path = NULL; - GList *devices; - - // detect device - client = g_udev_client_new (NULL); - devices = g_udev_client_query_by_subsystem (client, "backlight"); - if (devices != NULL) { - GList *device; - const gchar *device_type; - - for (device = devices; device != NULL; device = device->next) { - device_type = g_udev_device_get_sysfs_attr (device->data, "type"); - if ((g_strcmp0 (device_type, "firmware") == 0) || - (g_strcmp0 (device_type, "platform") == 0) || - (g_strcmp0 (device_type, "raw") == 0)) { - path = g_strdup (g_udev_device_get_sysfs_path (device->data)); - g_debug ("found: %s", path); - break; - } - } - - g_list_free_full (devices, g_object_unref); - } - else { - g_warning ("Fail to query backlight devices."); - } - - control = g_new0 (IbBrightnessControl, 1); - control->path = path; - - g_object_unref (client); - return control; -} - -void -ib_brightness_control_set_value (IbBrightnessControl* self, gint value) -{ - gint fd; - gchar *filename; - gchar *svalue; - gint length; - gint err; - - if (self->path == NULL) - return; - - filename = g_build_filename (self->path, "brightness", NULL); - fd = open(filename, O_WRONLY); - if (fd < 0) { - g_warning ("Fail to set brightness."); - g_free (filename); - return; - } - - svalue = g_strdup_printf ("%i", value); - length = strlen (svalue); - - err = errno; - errno = 0; - if (write (fd, svalue, length) != length) { - g_warning ("Fail to write brightness information: %s", g_strerror(errno)); - } - errno = err; - - close (fd); - g_free (svalue); - g_free (filename); -} - -gint -ib_brightness_control_get_value_from_file (IbBrightnessControl *self, const gchar *file) -{ - GError *error; - gchar *svalue; - gint value; - gchar *filename; - - if (self->path == NULL) - return 0; - - svalue = NULL; - error = NULL; - filename = g_build_filename (self->path, file, NULL); - g_file_get_contents (filename, &svalue, NULL, &error); - if (error) { - g_warning ("Fail to get brightness value: %s", error->message); - value = -1; - g_error_free (error); - } else { - value = atoi (svalue); - g_free (svalue); - } - - g_free (filename); - - return value; - -} - -gint -ib_brightness_control_get_value (IbBrightnessControl* self) -{ - return ib_brightness_control_get_value_from_file (self, "brightness"); -} - -gint -ib_brightness_control_get_max_value (IbBrightnessControl* self) -{ - return ib_brightness_control_get_value_from_file (self, "max_brightness"); -} - -void -ib_brightness_control_free (IbBrightnessControl *self) -{ - g_free (self->path); - g_free (self); -} - diff --git a/src/ib-brightness-control.h b/src/ib-brightness-control.h deleted file mode 100644 index 87711e4..0000000 --- a/src/ib-brightness-control.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Renato Araujo Oliveira Filho <renato@canonical.com> - */ - -#ifndef __IB_BRIGHTNESS_CONTROL_H__ -#define __IB_BRIGHTNESS_CONTROL_H__ - -#include <gio/gio.h> - -typedef struct _IbBrightnessControl IbBrightnessControl; - -IbBrightnessControl* ib_brightness_control_new (void); -void ib_brightness_control_set_value (IbBrightnessControl* self, gint value); -gint ib_brightness_control_get_value (IbBrightnessControl* self); -gint ib_brightness_control_get_max_value (IbBrightnessControl* self); -void ib_brightness_control_free (IbBrightnessControl *self); - -#endif @@ -17,8 +17,6 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include <locale.h> #include <stdlib.h> /* exit() */ @@ -43,9 +41,9 @@ on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) int main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) { - GMainLoop * loop; - IndicatorPowerService * service; IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerService * service; + GMainLoop * loop; /* boilerplate i18n */ setlocale (LC_ALL, ""); @@ -61,8 +59,8 @@ main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) g_main_loop_run (loop); /* cleanup */ - g_clear_object (&device_provider); - g_clear_object (&service); g_main_loop_unref (loop); + g_clear_object (&service); + g_clear_object (&device_provider); return 0; } diff --git a/src/notifier.c b/src/notifier.c new file mode 100644 index 0000000..993e332 --- /dev/null +++ b/src/notifier.c @@ -0,0 +1,504 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include "dbus-battery.h" +#include "dbus-shared.h" +#include "notifier.h" + +#include <url-dispatcher.h> + +#include <libnotify/notify.h> + +#include <glib/gi18n.h> + +#include <stdint.h> /* UINT32_MAX */ + +typedef enum +{ + POWER_LEVEL_CRITICAL, + POWER_LEVEL_VERY_LOW, + POWER_LEVEL_LOW, + POWER_LEVEL_OK +} +PowerLevel; + +/** +*** GObject Properties +**/ + +enum +{ + PROP_0, + PROP_BATTERY, + LAST_PROP +}; + +#define PROP_BATTERY_NAME "battery" + +static GParamSpec * properties[LAST_PROP]; + +static int instance_count = 0; + +static gboolean actions_supported = FALSE; + +/** +*** +**/ + +typedef struct +{ + /* The battery we're currently watching. + This may be a physical battery or it may be an aggregated + battery from multiple batteries present on the device. + See indicator_power_service_choose_primary_device() and + bug #880881 */ + IndicatorPowerDevice * battery; + PowerLevel power_level; + gboolean discharging; + + NotifyNotification * notify_notification; + + GDBusConnection * bus; + DbusBattery * dbus_battery; /* com.canonical.indicator.power.Battery skeleton */ +} +IndicatorPowerNotifierPrivate; + +typedef IndicatorPowerNotifierPrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(IndicatorPowerNotifier, + indicator_power_notifier, + G_TYPE_OBJECT) + +#define get_priv(o) ((priv_t*)indicator_power_notifier_get_instance_private(o)) + +/*** +**** +***/ + +static const char * +power_level_to_dbus_string (const PowerLevel power_level) +{ + switch (power_level) + { + case POWER_LEVEL_LOW: return POWER_LEVEL_STR_LOW; + case POWER_LEVEL_VERY_LOW: return POWER_LEVEL_STR_VERY_LOW; + case POWER_LEVEL_CRITICAL: return POWER_LEVEL_STR_CRITICAL; + default: return POWER_LEVEL_STR_OK; + } +} + +static PowerLevel +get_battery_power_level (IndicatorPowerDevice * battery) +{ + static const double percent_critical = 2.0; + static const double percent_very_low = 5.0; + static const double percent_low = 10.0; + gdouble p; + PowerLevel ret; + + g_return_val_if_fail(battery != NULL, POWER_LEVEL_OK); + g_return_val_if_fail(indicator_power_device_get_kind(battery) == UP_DEVICE_KIND_BATTERY, POWER_LEVEL_OK); + + p = indicator_power_device_get_percentage(battery); + + if (p <= percent_critical) + ret = POWER_LEVEL_CRITICAL; + else if (p <= percent_very_low) + ret = POWER_LEVEL_VERY_LOW; + else if (p <= percent_low) + ret = POWER_LEVEL_LOW; + else + ret = POWER_LEVEL_OK; + + return ret; +} + +/*** +**** Notifications +***/ + +static void +on_notify_notification_finalized (gpointer gself, GObject * dead) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER(gself); + priv_t * const p = get_priv(self); + g_return_if_fail ((void*)(p->notify_notification) == (void*)dead); + p->notify_notification = NULL; + dbus_battery_set_is_warning (p->dbus_battery, FALSE); +} + +static void +notification_clear (IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv(self); + NotifyNotification * nn; + + if ((nn = p->notify_notification)) + { + GError * error = NULL; + + g_object_weak_unref(G_OBJECT(nn), on_notify_notification_finalized, self); + + if (!notify_notification_close(nn, &error)) + { + g_warning("Unable to close notification: %s", error->message); + g_error_free(error); + } + + p->notify_notification = NULL; + dbus_battery_set_is_warning (p->dbus_battery, FALSE); + } +} + +static void +on_battery_settings_clicked(NotifyNotification * nn G_GNUC_UNUSED, + char * action G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + url_dispatch_send("settings:///system/battery", NULL, NULL); +} + +static void +on_dismiss_clicked(NotifyNotification * nn G_GNUC_UNUSED, + char * action G_GNUC_UNUSED, + gpointer user_data G_GNUC_UNUSED) +{ + /* no-op; libnotify warns if we have a NULL action callback */ +} + +static void +notification_show(IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv(self); + gdouble pct; + const char * title; + char * body; + GStrv icon_names; + const char * icon_name; + NotifyNotification * nn; + GError * error; + const PowerLevel power_level = get_battery_power_level(p->battery); + + notification_clear(self); + + g_return_if_fail(power_level != POWER_LEVEL_OK); + + /* create the notification */ + title = power_level == POWER_LEVEL_LOW + ? _("Battery Low") + : _("Battery Critical"); + pct = indicator_power_device_get_percentage(p->battery); + body = g_strdup_printf(_("%.0f%% charge remaining"), pct); + icon_names = indicator_power_device_get_icon_names(p->battery); + if (icon_names && *icon_names) + icon_name = icon_names[0]; + else + icon_name = NULL; + nn = notify_notification_new(title, body, icon_name); + g_strfreev (icon_names); + g_free (body); + + if (actions_supported) + { + notify_notification_set_hint(nn, "x-canonical-snap-decisions", g_variant_new_string("true")); + notify_notification_set_hint(nn, "x-canonical-non-shaped-icon", g_variant_new_string("true")); + notify_notification_set_hint(nn, "x-canonical-private-affirmative-tint", g_variant_new_string("true")); + notify_notification_set_hint(nn, "x-canonical-snap-decisions-timeout", g_variant_new_int32(INT32_MAX)); + notify_notification_set_timeout(nn, NOTIFY_EXPIRES_NEVER); + notify_notification_add_action(nn, "dismiss", _("OK"), on_dismiss_clicked, NULL, NULL); + notify_notification_add_action(nn, "settings", _("Battery settings"), on_battery_settings_clicked, NULL, NULL); + } + + /* if we can show it, keep it */ + error = NULL; + if (notify_notification_show(nn, &error)) + { + p->notify_notification = nn; + g_signal_connect(nn, "closed", G_CALLBACK(g_object_unref), NULL); + g_object_weak_ref(G_OBJECT(nn), on_notify_notification_finalized, self); + dbus_battery_set_is_warning (p->dbus_battery, TRUE); + } + else + { + g_critical("Unable to show snap decision for '%s': %s", body, error->message); + g_error_free(error); + g_object_unref(nn); + } +} + +/*** +**** +***/ + +static void +on_battery_property_changed (IndicatorPowerNotifier * self) +{ + priv_t * p; + PowerLevel old_power_level; + PowerLevel new_power_level; + gboolean old_discharging; + gboolean new_discharging; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + p = get_priv (self); + g_return_if_fail(INDICATOR_IS_POWER_DEVICE(p->battery)); + + old_power_level = p->power_level; + new_power_level = get_battery_power_level (p->battery); + + old_discharging = p->discharging; + new_discharging = indicator_power_device_get_state(p->battery) == UP_DEVICE_STATE_DISCHARGING; + + /* pop up a 'low battery' notification if either: + a) it's already discharging, and its PowerLevel worsens, OR + b) it's already got a bad PowerLevel and its state becomes 'discharging */ + if ((new_discharging && (old_power_level > new_power_level)) || + ((new_power_level != POWER_LEVEL_OK) && new_discharging && !old_discharging)) + { + notification_show (self); + } + else if (!new_discharging || (new_power_level == POWER_LEVEL_OK)) + { + notification_clear (self); + } + + dbus_battery_set_power_level (p->dbus_battery, power_level_to_dbus_string (new_power_level)); + p->power_level = new_power_level; + p->discharging = new_discharging; +} + +/*** +**** GObject virtual functions +***/ + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER (o); + priv_t * const p = get_priv (self); + + switch (property_id) + { + case PROP_BATTERY: + g_value_set_object (value, p->battery); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER (o); + + switch (property_id) + { + case PROP_BATTERY: + indicator_power_notifier_set_battery (self, g_value_get_object(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_dispose (GObject * o) +{ + IndicatorPowerNotifier * const self = INDICATOR_POWER_NOTIFIER(o); + priv_t * const p = get_priv (self); + + indicator_power_notifier_set_bus (self, NULL); + notification_clear (self); + indicator_power_notifier_set_battery (self, NULL); + g_clear_object (&p->dbus_battery); + + G_OBJECT_CLASS (indicator_power_notifier_parent_class)->dispose (o); +} + +static void +my_finalize (GObject * o G_GNUC_UNUSED) +{ + /* FIXME: This is an awkward place to put this. + Ordinarily something like this would go in main(), but we need libnotify + to clean itself up before shutting down the bus in the unit tests as well. */ + if (!--instance_count) + notify_uninit(); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_notifier_init (IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv (self); + + /* bind the read-only properties so they'll get pushed to the bus */ + + p->dbus_battery = dbus_battery_skeleton_new (); + + p->power_level = POWER_LEVEL_OK; + + if (!instance_count++) + { + actions_supported = FALSE; + + if (!notify_init("indicator-power-service")) + { + g_critical("Unable to initialize libnotify! Notifications might not be shown."); + } + else + { + GList * caps; + GList * l; + + /* see if actions are supported */ + caps = notify_get_server_caps(); + for (l=caps; l!=NULL && !actions_supported; l=l->next) + if (!g_strcmp0(l->data, "actions")) + actions_supported = TRUE; + g_list_free_full(caps, g_free); + } + } +} + +static void +indicator_power_notifier_class_init (IndicatorPowerNotifierClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_dispose; + object_class->finalize = my_finalize; + object_class->get_property = my_get_property; + object_class->set_property = my_set_property; + + properties[PROP_BATTERY] = g_param_spec_object ( + PROP_BATTERY_NAME, + "Battery", + "The current battery", + G_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); +} + +/*** +**** Public API +***/ + +IndicatorPowerNotifier * +indicator_power_notifier_new (void) +{ + GObject * o = g_object_new (INDICATOR_TYPE_POWER_NOTIFIER, NULL); + + return INDICATOR_POWER_NOTIFIER (o); +} + +void +indicator_power_notifier_set_battery (IndicatorPowerNotifier * self, + IndicatorPowerDevice * battery) +{ + priv_t * p; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + g_return_if_fail((battery == NULL) || INDICATOR_IS_POWER_DEVICE(battery)); + g_return_if_fail((battery == NULL) || (indicator_power_device_get_kind(battery) == UP_DEVICE_KIND_BATTERY)); + + p = get_priv (self); + + if (p->battery == battery) + return; + + if (p->battery != NULL) + { + g_signal_handlers_disconnect_by_data (p->battery, self); + g_clear_object (&p->battery); + dbus_battery_set_power_level (p->dbus_battery, power_level_to_dbus_string (POWER_LEVEL_OK)); + notification_clear (self); + } + + if (battery != NULL) + { + p->battery = g_object_ref (battery); + g_signal_connect_swapped (p->battery, "notify::"INDICATOR_POWER_DEVICE_PERCENTAGE, + G_CALLBACK(on_battery_property_changed), self); + g_signal_connect_swapped (p->battery, "notify::"INDICATOR_POWER_DEVICE_STATE, + G_CALLBACK(on_battery_property_changed), self); + on_battery_property_changed (self); + } +} + +void +indicator_power_notifier_set_bus (IndicatorPowerNotifier * self, + GDBusConnection * bus) +{ + priv_t * p; + GDBusInterfaceSkeleton * skel; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + g_return_if_fail((bus == NULL) || G_IS_DBUS_CONNECTION(bus)); + + p = get_priv (self); + + if (p->bus == bus) + return; + + skel = G_DBUS_INTERFACE_SKELETON(p->dbus_battery); + + if (p->bus != NULL) + { + if (skel != NULL) + g_dbus_interface_skeleton_unexport (skel); + + g_clear_object (&p->bus); + } + + if (bus != NULL) + { + GError * error; + + p->bus = g_object_ref (bus); + + error = NULL; + if (!g_dbus_interface_skeleton_export(skel, + bus, + BUS_PATH"/Battery", + &error)) + { + g_warning ("Unable to export LowBattery properties: %s", error->message); + g_error_free (error); + } + } +} + +const char * +indicator_power_notifier_get_power_level (IndicatorPowerDevice * battery) +{ + return power_level_to_dbus_string (get_battery_power_level (battery)); +} diff --git a/src/notifier.h b/src/notifier.h new file mode 100644 index 0000000..18e25d7 --- /dev/null +++ b/src/notifier.h @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef __INDICATOR_POWER_NOTIFIER_H__ +#define __INDICATOR_POWER_NOTIFIER_H__ + +#include <gio/gio.h> + +#include "device.h" + +G_BEGIN_DECLS + +/* standard GObject macros */ +#define INDICATOR_POWER_NOTIFIER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_POWER_NOTIFIER, IndicatorPowerNotifier)) +#define INDICATOR_TYPE_POWER_NOTIFIER (indicator_power_notifier_get_type()) +#define INDICATOR_IS_POWER_NOTIFIER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_POWER_NOTIFIER)) +#define INDICATOR_POWER_NOTIFIER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_POWER_NOTIFIER, IndicatorPowerNotifierClass)) + +typedef struct _IndicatorPowerNotifier IndicatorPowerNotifier; +typedef struct _IndicatorPowerNotifierClass IndicatorPowerNotifierClass; + +/** + * The Indicator Power Notifier. + */ +struct _IndicatorPowerNotifier +{ + /*< private >*/ + GObject parent; +}; + +struct _IndicatorPowerNotifierClass +{ + GObjectClass parent_class; +}; + +/*** +**** +***/ + +GType indicator_power_notifier_get_type (void); + +IndicatorPowerNotifier * indicator_power_notifier_new (void); + +void indicator_power_notifier_set_bus (IndicatorPowerNotifier * self, + GDBusConnection * connection); + +void indicator_power_notifier_set_battery (IndicatorPowerNotifier * self, + IndicatorPowerDevice * battery); + +#define POWER_LEVEL_STR_OK "ok" +#define POWER_LEVEL_STR_LOW "low" +#define POWER_LEVEL_STR_VERY_LOW "very_low" +#define POWER_LEVEL_STR_CRITICAL "critical" +const char * indicator_power_notifier_get_power_level (IndicatorPowerDevice * battery); + +G_END_DECLS + +#endif /* __INDICATOR_POWER_NOTIFIER_H__ */ diff --git a/src/org.freedesktop.UPower.xml b/src/org.freedesktop.UPower.xml deleted file mode 100644 index 7b73583..0000000 --- a/src/org.freedesktop.UPower.xml +++ /dev/null @@ -1,43 +0,0 @@ -<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" - "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> -<node> - <interface name="org.freedesktop.UPower"> - <method name="HibernateAllowed"> - <arg name="allowed" type="b" direction="out"/> - </method> - <method name="Hibernate"> - </method> - <method name="SuspendAllowed"> - <arg name="allowed" type="b" direction="out"/> - </method> - <method name="Suspend"> - </method> - <method name="AboutToSleep"> - </method> - <method name="EnumerateDevices"> - <arg name="devices" type="ao" direction="out"/> - </method> - <signal name="Resuming"> - </signal> - <signal name="Sleeping"> - </signal> - <signal name="Changed"> - </signal> - <signal name="DeviceChanged"> - <arg type="s"/> - </signal> - <signal name="DeviceRemoved"> - <arg type="s"/> - </signal> - <signal name="DeviceAdded"> - <arg type="s"/> - </signal> - <property name="LidIsPresent" type="b" access="read"/> - <property name="LidIsClosed" type="b" access="read"/> - <property name="OnLowBattery" type="b" access="read"/> - <property name="OnBattery" type="b" access="read"/> - <property name="CanHibernate" type="b" access="read"/> - <property name="CanSuspend" type="b" access="read"/> - <property name="DaemonVersion" type="s" access="read"/> - </interface> -</node>
\ No newline at end of file diff --git a/src/service.c b/src/service.c index f22d33d..32cec38 100644 --- a/src/service.c +++ b/src/service.c @@ -18,15 +18,15 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include <glib/gi18n.h> #include <gio/gio.h> #include <url-dispatcher.h> +#include "brightness.h" +#include "dbus-shared.h" #include "device.h" #include "device-provider.h" -#include "ib-brightness-control.h" +#include "notifier.h" #include "service.h" #define BUS_NAME "com.canonical.indicator.power" @@ -103,7 +103,7 @@ struct _IndicatorPowerServicePrivate GSettings * settings; - IbBrightnessControl * brightness_control; + IndicatorPowerBrightness * brightness; guint own_id; guint actions_export_id; @@ -120,6 +120,7 @@ struct _IndicatorPowerServicePrivate GList * devices; /* IndicatorPowerDevice */ IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerNotifier * notifier; }; typedef IndicatorPowerServicePrivate priv_t; @@ -389,6 +390,8 @@ append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device, int pr item = g_menu_item_new (label, NULL); g_free (label); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.basic"); + if ((icon = indicator_power_device_get_gicon (device))) { GVariant * serialized_icon = g_icon_serialize (icon); @@ -454,44 +457,17 @@ create_phone_devices_section (IndicatorPowerService * self G_GNUC_UNUSED) **** ***/ -static void -get_brightness_range (IndicatorPowerService * self, gint * low, gint * high) -{ - const int max = ib_brightness_control_get_max_value (self->priv->brightness_control); - *low = max * 0.05; /* 5% minimum -- don't let the screen go completely dark */ - *high = max; -} - -static gdouble -brightness_to_percentage (IndicatorPowerService * self, int brightness) -{ - int lo, hi; - get_brightness_range (self, &lo, &hi); - return (brightness-lo) / (double)(hi-lo); -} - -static int -percentage_to_brightness (IndicatorPowerService * self, double percentage) -{ - int lo, hi; - get_brightness_range (self, &lo, &hi); - return (int)(lo + (percentage*(hi-lo))); -} - static GMenuItem * -create_brightness_menuitem (IndicatorPowerService * self) +create_brightness_menu_item(void) { - int lo, hi; GMenuItem * item; - get_brightness_range (self, &lo, &hi); - - item = g_menu_item_new (NULL, "indicator.brightness"); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.unity.slider"); - g_menu_item_set_attribute (item, "min-value", "d", brightness_to_percentage (self, lo)); - g_menu_item_set_attribute (item, "max-value", "d", brightness_to_percentage (self, hi)); - g_menu_item_set_attribute (item, "min-icon", "s", "torch-off" ); - g_menu_item_set_attribute (item, "max-icon", "s", "torch-on" ); + item = g_menu_item_new(NULL, "indicator.brightness"); + g_menu_item_set_attribute(item, "x-canonical-type", "s", "com.canonical.unity.slider"); + g_menu_item_set_attribute(item, "min-value", "d", 0.0); + g_menu_item_set_attribute(item, "max-value", "d", 1.0); + g_menu_item_set_attribute(item, "min-icon", "s", "torch-off" ); + g_menu_item_set_attribute(item, "max-icon", "s", "torch-on" ); return item; } @@ -499,9 +475,8 @@ create_brightness_menuitem (IndicatorPowerService * self) static GVariant * action_state_for_brightness (IndicatorPowerService * self) { - priv_t * p = self->priv; - const gint brightness = ib_brightness_control_get_value (p->brightness_control); - return g_variant_new_double (brightness_to_percentage (self, brightness)); + IndicatorPowerBrightness * b = self->priv->brightness; + return g_variant_new_double(indicator_power_brightness_get_percentage(b)); } static void @@ -517,10 +492,9 @@ on_brightness_change_requested (GSimpleAction * action G_GNUC_UNUSED, gpointer gself) { IndicatorPowerService * self = INDICATOR_POWER_SERVICE (gself); - const gdouble percentage = g_variant_get_double (parameter); - const int brightness = percentage_to_brightness (self, percentage); - ib_brightness_control_set_value (self->priv->brightness_control, brightness); - update_brightness_action_state (self); + + indicator_power_brightness_set_percentage(self->priv->brightness, + g_variant_get_double (parameter)); } static GMenuModel * @@ -544,21 +518,34 @@ create_desktop_settings_section (IndicatorPowerService * self G_GNUC_UNUSED) } static GMenuModel * -create_phone_settings_section (IndicatorPowerService * self G_GNUC_UNUSED) +create_phone_settings_section(IndicatorPowerService * self) { GMenu * section; GMenuItem * item; + gboolean ab_supported; - section = g_menu_new (); + section = g_menu_new(); - item = create_brightness_menuitem (self); - g_menu_append_item (section, item); - update_brightness_action_state (self); - g_object_unref (item); + item = create_brightness_menu_item(); + g_menu_append_item(section, item); + update_brightness_action_state(self); + g_object_unref(item); + + g_object_get(self->priv->brightness, + "auto-brightness-supported", &ab_supported, + NULL); + + if (ab_supported) + { + item = g_menu_item_new(_("Adjust brightness automatically"), "indicator.auto-brightness"); + g_menu_item_set_attribute(item, "x-canonical-type", "s", "com.canonical.indicator.switch"); + g_menu_append_item(section, item); + g_object_unref(item); + } - g_menu_append (section, _("Battery settingsā¦"), "indicator.activate-phone-settings"); + g_menu_append(section, _("Battery settingsā¦"), "indicator.activate-phone-settings"); - return G_MENU_MODEL (section); + return G_MENU_MODEL(section); } /*** @@ -588,14 +575,14 @@ rebuild_now (IndicatorPowerService * self, guint sections) struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP]; struct ProfileMenuInfo * greeter = &p->menus[PROFILE_DESKTOP_GREETER]; - if (p->conn == NULL) /* we haven't built the menus yet */ - return; - if (sections & SECTION_HEADER) { g_simple_action_set_state (p->header_action, create_header_state (self)); } + if (p->conn == NULL) /* we haven't built the menus yet */ + return; + if (sections & SECTION_DEVICES) { rebuild_section (desktop->submenu, 0, create_desktop_devices_section (self, PROFILE_DESKTOP)); @@ -605,7 +592,7 @@ rebuild_now (IndicatorPowerService * self, guint sections) if (sections & SECTION_SETTINGS) { rebuild_section (desktop->submenu, 1, create_desktop_settings_section (self)); - rebuild_section (phone->submenu, 1, create_desktop_settings_section (self)); + rebuild_section (phone->submenu, 1, create_phone_settings_section (self)); } } @@ -615,18 +602,6 @@ rebuild_header_now (IndicatorPowerService * self) rebuild_now (self, SECTION_HEADER); } -static inline void -rebuild_devices_section_now (IndicatorPowerService * self) -{ - rebuild_now (self, SECTION_DEVICES); -} - -static inline void -rebuild_settings_section_now (IndicatorPowerService * self) -{ - rebuild_now (self, SECTION_SETTINGS); -} - static void create_menu (IndicatorPowerService * self, int profile) { @@ -761,6 +736,28 @@ on_phone_settings_activated (GSimpleAction * a G_GNUC_UNUSED, **** ***/ +static gboolean +convert_auto_prop_to_state(GBinding * binding G_GNUC_UNUSED, + const GValue * from_value, + GValue * to_value, + gpointer user_data G_GNUC_UNUSED) +{ + const gboolean b = g_value_get_boolean(from_value); + g_value_set_variant(to_value, g_variant_new_boolean(b)); + return TRUE; +} + +static gboolean +convert_auto_state_to_prop(GBinding * binding G_GNUC_UNUSED, + const GValue * from_value, + GValue * to_value, + gpointer user_data G_GNUC_UNUSED) +{ + GVariant * v = g_value_get_variant(from_value); + g_value_set_boolean(to_value, g_variant_get_boolean(v)); + return TRUE; +} + static void init_gactions (IndicatorPowerService * self) { @@ -793,6 +790,16 @@ init_gactions (IndicatorPowerService * self) g_action_map_add_action (G_ACTION_MAP(p->actions), G_ACTION(a)); p->battery_level_action = a; + /* add the auto-brightness action */ + a = g_simple_action_new_stateful("auto-brightness", NULL, g_variant_new_boolean(FALSE)); + g_object_bind_property_full(p->brightness, "auto-brightness", + a, "state", + G_BINDING_SYNC_CREATE|G_BINDING_BIDIRECTIONAL, + convert_auto_prop_to_state, + convert_auto_state_to_prop, + NULL, NULL); + g_action_map_add_action(G_ACTION_MAP(p->actions), G_ACTION(a)); + /* add the brightness action */ a = g_simple_action_new_stateful ("brightness", NULL, action_state_for_brightness (self)); g_action_map_add_action (G_ACTION_MAP(p->actions), G_ACTION(a)); @@ -833,6 +840,9 @@ on_bus_acquired (GDBusConnection * connection, p->conn = g_object_ref (G_OBJECT (connection)); + /* export the battery properties */ + indicator_power_notifier_set_bus (p->notifier, connection); + /* export the actions */ if ((id = g_dbus_connection_export_action_group (connection, BUS_PATH, @@ -854,9 +864,6 @@ on_bus_acquired (GDBusConnection * connection, g_string_printf (path, "%s/%s", BUS_PATH, menu_names[i]); - if (menu->menu == NULL) - create_menu (self, i); - if ((id = g_dbus_connection_export_menu_model (connection, path->str, G_MENU_MODEL (menu->menu), @@ -932,16 +939,28 @@ on_devices_changed (IndicatorPowerService * self) g_clear_object (&p->primary_device); p->primary_device = indicator_power_service_choose_primary_device (p->devices); + /* update the notifier's battery */ + if ((p->primary_device != NULL) && (indicator_power_device_get_kind(p->primary_device) == UP_DEVICE_KIND_BATTERY)) + indicator_power_notifier_set_battery (p->notifier, p->primary_device); + else + indicator_power_notifier_set_battery (p->notifier, NULL); + /* update the battery-level action's state */ if (p->primary_device == NULL) battery_level = 0; else - battery_level = (int)(indicator_power_device_get_percentage (p->primary_device) + 0.5); + battery_level = (guint32)(indicator_power_device_get_percentage (p->primary_device) + 0.5); g_simple_action_set_state (p->battery_level_action, g_variant_new_uint32 (battery_level)); rebuild_now (self, SECTION_HEADER | SECTION_DEVICES); } +static void +on_auto_brightness_supported_changed(IndicatorPowerService * self) +{ + rebuild_now(self, SECTION_SETTINGS); +} + /*** **** GObject virtual functions @@ -1013,15 +1032,15 @@ my_dispose (GObject * o) g_clear_object (&p->settings); } + g_clear_object (&p->notifier); g_clear_object (&p->brightness_action); + g_clear_object (&p->brightness); g_clear_object (&p->battery_level_action); g_clear_object (&p->header_action); g_clear_object (&p->actions); g_clear_object (&p->conn); - g_clear_pointer (&p->brightness_control, ib_brightness_control_free); - indicator_power_service_set_device_provider (self, NULL); G_OBJECT_CLASS (indicator_power_service_parent_class)->dispose (o); @@ -1034,29 +1053,42 @@ my_dispose (GObject * o) static void indicator_power_service_init (IndicatorPowerService * self) { - priv_t * p = G_TYPE_INSTANCE_GET_PRIVATE (self, - INDICATOR_TYPE_POWER_SERVICE, - IndicatorPowerServicePrivate); + priv_t * p; + int i; + + p = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_TYPE_POWER_SERVICE, + IndicatorPowerServicePrivate); self->priv = p; p->cancellable = g_cancellable_new (); p->settings = g_settings_new ("com.canonical.indicator.power"); - p->brightness_control = ib_brightness_control_new (); + p->notifier = indicator_power_notifier_new (); + + p->brightness = indicator_power_brightness_new(); + g_signal_connect_swapped(p->brightness, "notify::percentage", + G_CALLBACK(update_brightness_action_state), self); init_gactions (self); g_signal_connect_swapped (p->settings, "changed", G_CALLBACK(rebuild_header_now), self); - p->own_id = g_bus_own_name (G_BUS_TYPE_SESSION, - BUS_NAME, - G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, - on_bus_acquired, - NULL, - on_name_lost, - self, - NULL); + for (i=0; i<N_PROFILES; ++i) + create_menu(self, i); + + g_signal_connect_swapped(p->brightness, "notify::auto-brightness-supported", + G_CALLBACK(on_auto_brightness_supported_changed), self); + + p->own_id = g_bus_own_name(G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, + on_bus_acquired, + NULL, + on_name_lost, + self, + NULL); } static void @@ -1117,9 +1149,8 @@ indicator_power_service_set_device_provider (IndicatorPowerService * self, if (p->device_provider != NULL) { - g_signal_handlers_disconnect_by_func (p->device_provider, - G_CALLBACK(on_devices_changed), - self); + g_signal_handlers_disconnect_by_data (p->device_provider, self); + g_clear_object (&p->device_provider); g_clear_object (&p->primary_device); @@ -1139,6 +1170,129 @@ indicator_power_service_set_device_provider (IndicatorPowerService * self, } } +/* If a device has multiple batteries and uses only one of them at a time, + they should be presented as separate items inside the battery menu, + but everywhere else they should be aggregated (bug 880881). + Their percentages should be averaged. If any are discharging, + the aggregated time remaining should be the maximum of the times + for all those that are discharging, plus the sum of the times + for all those that are idle. Otherwise, the aggregated time remaining + should be the the maximum of the times for all those that are charging. */ +static IndicatorPowerDevice * +create_totalled_battery_device (const GList * devices) +{ + const GList * l; + guint n_charged = 0; + guint n_charging = 0; + guint n_discharging = 0; + guint n_batteries = 0; + double sum_percent = 0; + time_t max_discharge_time = 0; + time_t max_charge_time = 0; + time_t sum_charged_time = 0; + IndicatorPowerDevice * device = NULL; + + for (l=devices; l!=NULL; l=l->next) + { + const IndicatorPowerDevice * walk = INDICATOR_POWER_DEVICE(l->data); + + if (indicator_power_device_get_kind(walk) == UP_DEVICE_KIND_BATTERY) + { + const double percent = indicator_power_device_get_percentage (walk); + const time_t t = indicator_power_device_get_time (walk); + const UpDeviceState state = indicator_power_device_get_state (walk); + + ++n_batteries; + + if (percent > 0.01) + sum_percent += percent; + + if (state == UP_DEVICE_STATE_CHARGING) + { + ++n_charging; + max_charge_time = MAX(max_charge_time, t); + } + else if (state == UP_DEVICE_STATE_DISCHARGING) + { + ++n_discharging; + max_discharge_time = MAX(max_discharge_time, t); + } + else if (state == UP_DEVICE_STATE_FULLY_CHARGED) + { + ++n_charged; + sum_charged_time += t; + } + } + } + + if (n_batteries > 1) + { + const double percent = sum_percent / n_batteries; + UpDeviceState state; + time_t time_left; + + if (n_discharging > 0) + { + state = UP_DEVICE_STATE_DISCHARGING; + time_left = max_discharge_time + sum_charged_time; + } + else if (n_charging > 0) + { + state = UP_DEVICE_STATE_CHARGING; + time_left = max_charge_time; + } + else if (n_charged > 0) + { + state = UP_DEVICE_STATE_FULLY_CHARGED; + time_left = 0; + } + else + { + state = UP_DEVICE_STATE_UNKNOWN; + time_left = 0; + } + + device = indicator_power_device_new (NULL, + UP_DEVICE_KIND_BATTERY, + percent, + state, + time_left); + } + + return device; +} + +/** + * If there are multiple UP_DEVICE_KIND_BATTERY devices in the list, + * they're merged into a new 'totalled' device representing the sum of them. + * + * Returns: (element-type IndicatorPowerDevice)(transfer full): a list of devices + */ +static GList* +merge_batteries_together (GList * devices) +{ + GList * ret; + IndicatorPowerDevice * merged_device; + + if ((merged_device = create_totalled_battery_device (devices))) + { + GList * l; + + ret = g_list_append (NULL, merged_device); + + for (l=devices; l!=NULL; l=l->next) + if (indicator_power_device_get_kind(INDICATOR_POWER_DEVICE(l->data)) != UP_DEVICE_KIND_BATTERY) + ret = g_list_append (ret, g_object_ref(l->data)); + } + else /* not enough batteries to merge */ + { + ret = g_list_copy (devices); + g_list_foreach (ret, (GFunc)g_object_ref, NULL); + } + + return ret; +} + IndicatorPowerDevice * indicator_power_service_choose_primary_device (GList * devices) { @@ -1146,13 +1300,10 @@ indicator_power_service_choose_primary_device (GList * devices) if (devices != NULL) { - GList * tmp; - - tmp = g_list_copy (devices); + GList * tmp = merge_batteries_together (devices); tmp = g_list_sort (tmp, device_compare_func); primary = g_object_ref (tmp->data); - - g_list_free (tmp); + g_list_free_full (tmp, (GDestroyNotify)g_object_unref); } return primary; |