diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 50 | ||||
-rw-r--r-- | src/Makefile.am | 81 | ||||
-rw-r--r-- | src/dbus-shared.h | 28 | ||||
-rw-r--r-- | src/device-provider-upower.c | 10 | ||||
-rw-r--r-- | src/device-provider-upower.h | 2 | ||||
-rw-r--r-- | src/device-provider.c | 2 | ||||
-rw-r--r-- | src/device.c | 492 | ||||
-rw-r--r-- | src/device.h | 33 | ||||
-rw-r--r-- | src/ib-brightness-control.c | 6 | ||||
-rw-r--r-- | src/ib-brightness-uscreen-control.c | 202 | ||||
-rw-r--r-- | src/ib-brightness-uscreen-control.h | 43 | ||||
-rw-r--r-- | src/main.c | 10 | ||||
-rw-r--r-- | src/notifier.c | 445 | ||||
-rw-r--r-- | src/notifier.h | 74 | ||||
-rw-r--r-- | src/service.c | 351 |
15 files changed, 1438 insertions, 391 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7a4a297 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,50 @@ +set (SERVICE_LIB "indicatorpowerservice") +set (SERVICE_EXEC "indicator-power-service") + +add_definitions(-DG_LOG_DOMAIN="Indicator-Power") + +# handwritten sources +set(SERVICE_MANUAL_SOURCES + device-provider-upower.c + ib-brightness-control.c + ib-brightness-uscreen-control.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-upower + org.freedesktop + Dbus + ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.UPower.xml) +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-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/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..400a060 100644 --- a/src/device-provider-upower.c +++ b/src/device-provider-upower.c @@ -17,8 +17,6 @@ * 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" @@ -60,7 +58,7 @@ G_DEFINE_TYPE_WITH_CODE ( indicator_power_device_provider_upower, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, - indicator_power_device_provider_interface_init)); + indicator_power_device_provider_interface_init)) /*** **** UPOWER DBUS @@ -102,7 +100,7 @@ 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; GVariant * dict = g_variant_get_child_value (response, 0); @@ -120,7 +118,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 +127,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), diff --git a/src/device-provider-upower.h b/src/device-provider-upower.h index 7bdd5d5..7bfecd9 100644 --- a/src/device-provider-upower.h +++ b/src/device-provider-upower.h @@ -65,6 +65,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 7f1b14f..eff76d1 100644 --- a/src/device.c +++ b/src/device.c @@ -37,9 +37,12 @@ struct _IndicatorPowerDevicePrivate gchar * object_path; gdouble percentage; time_t time; -}; -#define INDICATOR_POWER_DEVICE_GET_PRIVATE(o) (INDICATOR_POWER_DEVICE(o)->priv) + /* Timestamp of when when we first noticed that upower couldn't estimate + the time-remaining field for this device, or 0 if not applicable. + This is used when generating the time-remaining string. */ + GTimer * inestimable; +}; /* Properties */ /* Enum for the properties so that they can be quickly found and looked up. */ @@ -64,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 @@ -136,6 +139,11 @@ indicator_power_device_init (IndicatorPowerDevice *self) static void indicator_power_device_dispose (GObject *object) { + IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(object); + IndicatorPowerDevicePrivate * p = self->priv; + + g_clear_pointer (&p->inestimable, g_timer_destroy); + G_OBJECT_CLASS (indicator_power_device_parent_class)->dispose (object); } @@ -179,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: @@ -192,35 +200,54 @@ static void set_property (GObject * o, guint prop_id, const GValue * value, GParamSpec * pspec) { IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(o); - IndicatorPowerDevicePrivate * priv = self->priv; + IndicatorPowerDevicePrivate * p = self->priv; switch (prop_id) { case PROP_KIND: - priv->kind = g_value_get_int (value); + p->kind = (UpDeviceKind) g_value_get_int (value); break; case PROP_STATE: - priv->state = g_value_get_int (value); + p->state = (UpDeviceState) g_value_get_int (value); break; case PROP_OBJECT_PATH: - g_free (priv->object_path); - priv->object_path = g_value_dup_string (value); + g_free (p->object_path); + p->object_path = g_value_dup_string (value); break; case PROP_PERCENTAGE: - priv->percentage = g_value_get_double (value); + p->percentage = g_value_get_double (value); break; case PROP_TIME: - priv->time = g_value_get_uint64(value); + p->time = (time_t) g_value_get_uint64(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(o, prop_id, pspec); break; } + + /** + * Check to see if the time-remaining value is estimable. + * When it first becomes inestimable, kick off a timer because + * we need to track that to generate the appropriate title text. + */ + + const gboolean is_inestimable = (p->time == 0) + && (p->state != UP_DEVICE_STATE_FULLY_CHARGED) + && (p->percentage > 0); + + if (!is_inestimable) + { + g_clear_pointer (&p->inestimable, g_timer_destroy); + } + else if (p->inestimable == NULL) + { + p->inestimable = g_timer_new (); + } } /*** @@ -326,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): @@ -387,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)); @@ -442,57 +456,6 @@ indicator_power_device_get_gicon (const IndicatorPowerDevice * device) **** ***/ -/* Format time remaining for reading ("H:MM") and speech ("H hours, MM minutes") */ -static void -get_timestring (guint64 time_secs, - gchar **readable_timestring, - gchar **accessible_timestring) -{ - gint hours; - gint minutes; - - /* Add 0.5 to do rounding */ - minutes = (int) ( ( time_secs / 60.0 ) + 0.5 ); - - if (minutes == 0) - { - *readable_timestring = g_strdup (_("Unknown time")); - *accessible_timestring = g_strdup (_("Unknown time")); - - return; - } - - if (minutes < 60) - { - *readable_timestring = g_strdup_printf ("0:%.2i", minutes); - *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%i minute", - "%i minutes", - minutes), minutes); - return; - } - - hours = minutes / 60; - minutes = minutes % 60; - - *readable_timestring = g_strdup_printf ("%i:%.2i", hours, minutes); - - if (minutes == 0) - { - *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%i hour", - "%i hours", - hours), hours); - } - else - { - /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes" - * Swap order with "%2$s %2$i %1$s %1$i if needed */ - *accessible_timestring = g_strdup_printf (_("%i %s %i %s"), - hours, g_dngettext (GETTEXT_PACKAGE, "hour", "hours", hours), - minutes, g_dngettext (GETTEXT_PACKAGE, "minute", "minutes", minutes)); - } -} - static const gchar * device_kind_to_localised_string (UpDeviceKind kind) { @@ -555,149 +518,314 @@ device_kind_to_localised_string (UpDeviceKind kind) return text; } +/** + * The '''brief time-remaining string''' for a component should be: + * * the time remaining for it to empty or fully charge, + * if estimable, in H:MM format; otherwise + * * “estimating…” if the time remaining has been inestimable for + * less than 30 seconds; otherwise + * * “unknown” if the time remaining has been inestimable for + * between 30 seconds and one minute; otherwise + * * the empty string. + */ static char * -join_strings (const char * name, const char * time, const char * percent) +get_brief_time_remaining (const IndicatorPowerDevice * device) { - char * str; - const gboolean have_name = name && *name; - const gboolean have_time = time && *time; - const gboolean have_percent = percent && *percent; - - if (have_name && have_time && have_percent) - str = g_strdup_printf (_("%s (%s, %s)"), name, time, percent); - else if (have_name && have_time) - str = g_strdup_printf (_("%s (%s)"), name, time); - else if (have_name && have_percent) - str = g_strdup_printf (_("%s (%s)"), name, percent); - else if (have_name) - str = g_strdup (name); - else if (have_time && have_percent) - str = g_strdup_printf (_("(%s, %s)"), time, percent); - else if (have_time) - str = g_strdup_printf (_("(%s)"), time); - else if (have_percent) - str = g_strdup_printf (_("(%s)"), percent); - else - str = g_strdup (""); + gchar * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + + if (p->time > 0) + { + int minutes = p->time / 60; + const int hours = minutes / 60; + minutes %= 60; + + str = g_strdup_printf("%0d:%02d", hours, minutes); + } + else if (p->inestimable != NULL) + { + const double elapsed = g_timer_elapsed (p->inestimable, NULL); + + if (elapsed < 30) + { + str = g_strdup_printf (_("estimating…")); + } + else if (elapsed < 60) + { + str = g_strdup_printf (_("unknown")); + } + } return str; } -static void -indicator_power_device_get_text (const IndicatorPowerDevice * device, - gboolean show_time_in_header, - gboolean show_percentage_in_header, - gchar ** header, - gchar ** label, - gchar ** a11y) +/** + * The '''expanded time-remaining string''' for a component should + * be the same as the brief time-remaining string, except that if + * the time is estimable: + * * if the component is charging, it should be “H:MM to charge” + * * if the component is discharging, it should be “H:MM left”. + */ +static char* +get_expanded_time_remaining (const IndicatorPowerDevice * device) { - if (!INDICATOR_IS_POWER_DEVICE(device)) + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + + 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; + minutes %= 60; + + 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); + } + } + else { - if (a11y != NULL) *a11y = NULL; - if (label != NULL) *label = NULL; - if (header != NULL) *header = NULL; - g_warning ("%s: %p is not an IndicatorPowerDevice", G_STRFUNC, device); - return; + str = get_brief_time_remaining (device); } - const time_t time = indicator_power_device_get_time (device); - const UpDeviceState state = indicator_power_device_get_state (device); - const UpDeviceKind kind = indicator_power_device_get_kind (device); - const gchar * device_name = device_kind_to_localised_string (kind); - const gdouble percentage = indicator_power_device_get_percentage (device); - char pctstr[32] = { '\0' }; - g_snprintf (pctstr, sizeof(pctstr), "%.0lf%%", percentage); + return str; +} - GString * terse_time = g_string_new (NULL); - GString * verbose_time = g_string_new (NULL); - GString * accessible_time = g_string_new (NULL); +/** + * The '''accessible time-remaining string''' for a component + * should be the same as the expanded time-remaining string, + * except the H:MM time should be rendered as “''H'' hours ''M'' minutes”, + * or just as “''M'' minutes” if the time is less than one hour. + */ +static char * +get_accessible_time_remaining (const IndicatorPowerDevice * device) +{ + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; - if (time > 0) + if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING))) { - char * readable_timestr = NULL; - char * accessible_timestr = NULL; - get_timestring (time, &readable_timestr, &accessible_timestr); + guint minutes = (guint)p->time / 60u; + const guint hours = minutes / 60u; + minutes %= 60; - if (state == UP_DEVICE_STATE_CHARGING) - { - g_string_assign (terse_time, readable_timestr); - g_string_printf (verbose_time, _("%s to charge"), readable_timestr); - g_string_printf (accessible_time, _("%s to charge"), accessible_timestr); - } - else if ((state == UP_DEVICE_STATE_DISCHARGING) && (time <= (60*60*24))) + if (p->state == UP_DEVICE_STATE_CHARGING) { - g_string_assign (terse_time, readable_timestr); - g_string_printf (verbose_time, _("%s left"), readable_timestr); - g_string_printf (accessible_time, _("%s left"), accessible_timestr); + if (hours > 0) + { + /* 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 + { + /* 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 + else // discharging { - /* if there's more than 24 hours remaining, we don't show it */ + if (hours > 0) + { + /* 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)); + } } - - g_free (readable_timestr); - g_free (accessible_timestr); } - else if (state == UP_DEVICE_STATE_FULLY_CHARGED) + else { - g_string_assign (verbose_time, _("charged")); - g_string_assign (accessible_time, _("charged")); + str = get_brief_time_remaining (device); } - else if (percentage > 0) + + return str; +} + +/** + * The time is relevant for a device if either (a) the component is charging, + * or (b) the component is discharging and the estimated time is less than + * 24 hours. (A time greater than 24 hours is probably a mistaken calculation.) + */ +static gboolean +time_is_relevant (const IndicatorPowerDevice * device) +{ + const IndicatorPowerDevicePrivate * p = device->priv; + + if (p->state == UP_DEVICE_STATE_CHARGING) + return TRUE; + + if ((p->state == UP_DEVICE_STATE_DISCHARGING) && (p->time<(24*60*60))) + return TRUE; + + return FALSE; +} + +/** + * The menu item for each chargeable component should consist of ... + * Text representing the name of the component (“Battery”, “Mouse”, + * “UPS”, “Alejandra’s iPod”, etc) and the charge status in brackets: + * + * * “X (charged)” if it is fully charged and not discharging; + * * “X (expanded time-remaining string)” if it is charging, + * or discharging with less than 24 hours left; + * * “X” if it is discharging with 24 hours or more left. + * + * The accessible label for the menu item should be the same as the + * visible label, except with the accessible time-remaining string + * instead of the expanded time-remaining string. + */ +static char * +get_menuitem_text (const IndicatorPowerDevice * device, + gboolean accessible) +{ + char * str = NULL; + const IndicatorPowerDevicePrivate * p = device->priv; + const char * kind_str = device_kind_to_localised_string (p->kind); + + if (p->state == UP_DEVICE_STATE_FULLY_CHARGED) { - g_string_assign (terse_time, _("estimating…")); - g_string_assign (verbose_time, _("estimating…")); - g_string_assign (accessible_time, _("estimating…")); + /* TRANSLATORS: example: "battery (charged)" */ + str = g_strdup_printf (_("%s (charged)"), kind_str); } else { - *pctstr = '\0'; + char * time_str = NULL; + + if (time_is_relevant (device)) + { + if (accessible) + time_str = get_accessible_time_remaining (device); + else + time_str = get_expanded_time_remaining (device); + } - if (kind != UP_DEVICE_KIND_LINE_POWER) + if (time_str && *time_str) { - g_string_assign (verbose_time, _("not present")); - g_string_assign (accessible_time, _("not present")); + /* TRANSLATORS: example: "battery (time remaining)" */ + str = g_strdup_printf (_("%s (%s)"), kind_str, time_str); } + else + { + str = g_strdup (kind_str); + } + + g_free (time_str); } - if (header != NULL) - *header = join_strings (NULL, - show_time_in_header ? terse_time->str : "", - show_percentage_in_header ? pctstr : ""); + return str; +} + +char * +indicator_power_device_get_readable_text (const IndicatorPowerDevice * device) +{ + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); - if (label != NULL) - *label = join_strings (device_name, - verbose_time->str, - NULL); + return get_menuitem_text (device, FALSE); +} - if (a11y != NULL) - *a11y = join_strings (device_name, - accessible_time->str, - pctstr); +char * +indicator_power_device_get_accessible_text (const IndicatorPowerDevice * device) +{ + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); - g_string_free (terse_time, TRUE); - g_string_free (verbose_time, TRUE); - g_string_free (accessible_time, TRUE); + return get_menuitem_text (device, TRUE); } -gchar * -indicator_power_device_get_label (const IndicatorPowerDevice * device) +/** + * If the time is relevant and/or “Show Percentage in Menu Bar” is checked, + * the icon should be followed by brackets. + * + * If the time is relevant, the brackets should contain the time-remaining + * string for that component. + * + * If “Show Percentage in Menu Bar” is checked (as it should not be by default), + * the brackets should contain the percentage charge for that device. + * + * If both conditions are true, the time and percentage should be separated by a space. + */ +char* +indicator_power_device_get_readable_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent) { - gchar * label = NULL; - indicator_power_device_get_text (device, FALSE, FALSE, - NULL, &label, NULL); - return label; + char * str = NULL; + char * time_str = NULL; + const IndicatorPowerDevicePrivate * p; + + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); + + p = device->priv; + + // if we can't provide time-remaining, turn off the time flag + if (want_time && !time_is_relevant (device)) + want_time = FALSE; + + // if we can't provide percent, turn off the percent flag + if (p->percentage < 0.01) + want_percent = FALSE; + + // try to build the time-remaining string + if (want_time) + { + time_str = get_brief_time_remaining (device); + want_time = time_str && *time_str; + } + + 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 + { + str = NULL; + } + + g_free (time_str); + return str; } -void -indicator_power_device_get_header (const IndicatorPowerDevice * device, - gboolean show_time, - gboolean show_percentage, - gchar ** header, - gchar ** a11y) +/** + * Regardless, the accessible name for the whole menu title should be the same + * as the accessible name for that thing’s component inside the menu itself. + */ +char * +indicator_power_device_get_accessible_title (const IndicatorPowerDevice * device, + gboolean want_time G_GNUC_UNUSED, + gboolean want_percent G_GNUC_UNUSED) { - indicator_power_device_get_text (device, show_time, show_percentage, - header, NULL, a11y); + g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL); + + return indicator_power_device_get_accessible_text (device); } /*** @@ -745,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 1f395a1..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 @@ -116,22 +116,27 @@ IndicatorPowerDevice* indicator_power_device_new (const gchar * object_path, IndicatorPowerDevice* indicator_power_device_new_from_variant (GVariant * variant); -UpDeviceKind indicator_power_device_get_kind (const IndicatorPowerDevice * device); -UpDeviceState indicator_power_device_get_state (const IndicatorPowerDevice * device); -const gchar * indicator_power_device_get_object_path (const IndicatorPowerDevice * device); -gdouble indicator_power_device_get_percentage (const IndicatorPowerDevice * device); -time_t indicator_power_device_get_time (const IndicatorPowerDevice * device); +UpDeviceKind indicator_power_device_get_kind (const IndicatorPowerDevice * device); +UpDeviceState indicator_power_device_get_state (const IndicatorPowerDevice * device); +const gchar * indicator_power_device_get_object_path (const IndicatorPowerDevice * device); +gdouble indicator_power_device_get_percentage (const IndicatorPowerDevice * device); +time_t indicator_power_device_get_time (const IndicatorPowerDevice * device); -GStrv indicator_power_device_get_icon_names (const IndicatorPowerDevice * device); -GIcon * indicator_power_device_get_gicon (const IndicatorPowerDevice * device); +GStrv indicator_power_device_get_icon_names (const IndicatorPowerDevice * device); +GIcon * indicator_power_device_get_gicon (const IndicatorPowerDevice * device); -gchar * indicator_power_device_get_label (const IndicatorPowerDevice * device); -void indicator_power_device_get_header (const IndicatorPowerDevice * device, - gboolean show_time, - gboolean show_percentage, - gchar ** header, - gchar ** a11y); +char * indicator_power_device_get_readable_text (const IndicatorPowerDevice * device); + +char * indicator_power_device_get_accessible_text (const IndicatorPowerDevice * device); + +char * indicator_power_device_get_readable_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent); + +char * indicator_power_device_get_accessible_title (const IndicatorPowerDevice * device, + gboolean want_time, + gboolean want_percent); G_END_DECLS diff --git a/src/ib-brightness-control.c b/src/ib-brightness-control.c index 4fb6bc5..67da10c 100644 --- a/src/ib-brightness-control.c +++ b/src/ib-brightness-control.c @@ -76,7 +76,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) gint fd; gchar *filename; gchar *svalue; - gint length; + size_t length; gint err; if (self->path == NULL) @@ -95,7 +95,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) err = errno; errno = 0; - if (write (fd, svalue, length) != length) { + if (write (fd, svalue, length) != (ssize_t)length) { g_warning ("Fail to write brightness information: %s", g_strerror(errno)); } errno = err; @@ -105,7 +105,7 @@ ib_brightness_control_set_value (IbBrightnessControl* self, gint value) g_free (filename); } -gint +static gint ib_brightness_control_get_value_from_file (IbBrightnessControl *self, const gchar *file) { GError *error; diff --git a/src/ib-brightness-uscreen-control.c b/src/ib-brightness-uscreen-control.c new file mode 100644 index 0000000..ad2c155 --- /dev/null +++ b/src/ib-brightness-uscreen-control.c @@ -0,0 +1,202 @@ +/* + * 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: + * Yuan-Chen Cheng <yc.cheng@canonical.com> + */ + +#include "ib-brightness-uscreen-control.h" + +static gboolean getBrightnessParams(GDBusProxy* powerd_proxy, int *dim, int *min, + int *max, int *dflt, gboolean *ab_supported); + +GDBusProxy* +uscreen_get_proxy(brightness_params_t *params) +{ + GError *error = NULL; + gboolean ret; + + g_return_val_if_fail (params != NULL, NULL); + + /* For now we still need to obtain the brigthness params from powerd */ + GDBusProxy* powerd_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "com.canonical.powerd", + "/com/canonical/powerd", + "com.canonical.powerd", + NULL, + &error); + + if (error != NULL) + { + g_debug ("could not connect to powerd: %s", error->message); + g_error_free (error); + return NULL; + } + + ret = getBrightnessParams(powerd_proxy, &(params->dim), &(params->min), + &(params->max), &(params->dflt), &(params->ab_supported)); + + if (! ret) + { + g_debug ("can't get brightness parameters from powerd"); + g_object_unref (powerd_proxy); + return NULL; + } + + g_clear_object (&powerd_proxy); + + GDBusProxy* uscreen_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "com.canonical.Unity.Screen", + "/com/canonical/Unity/Screen", + "com.canonical.Unity.Screen", + NULL, + &error); + + if (error != NULL) + { + g_debug ("could not connect to unity screen: %s", error->message); + g_error_free (error); + return NULL; + } + + return uscreen_proxy; +} + + +static gboolean +getBrightnessParams(GDBusProxy* powerd_proxy, int *dim, int *min, int *max, int *dflt, gboolean *ab_supported) +{ + GVariant *ret = NULL; + GError *error = NULL; + + ret = g_dbus_proxy_call_sync(powerd_proxy, + "getBrightnessParams", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 400, NULL, &error); // timeout: 400 ms + if (!ret) + { + if (error != NULL) + { + if (!g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) + { + g_warning("getBrightnessParams from powerd failed: %s", error->message); + } + g_error_free(error); + } + return FALSE; + } + + g_variant_get(ret, "((iiiib))", dim, min, max, dflt, ab_supported); + g_variant_unref(ret); + return TRUE; +} + +static gboolean setUserBrightness(GDBusProxy* uscreen_proxy, GCancellable *gcancel, int brightness) +{ + GVariant *ret = NULL; + GError *error = NULL; + + ret = g_dbus_proxy_call_sync(uscreen_proxy, + "setUserBrightness", + g_variant_new("(i)", brightness), + G_DBUS_CALL_FLAGS_NONE, + -1, gcancel, &error); + if (!ret) { + g_warning("setUserBrightness via unity.screen failed: %s", error->message); + g_error_free(error); + return FALSE; + } else { + g_variant_unref(ret); + return TRUE; + } +} + +struct _IbBrightnessUScreenControl +{ + GDBusProxy *uscreen_proxy; + GCancellable *gcancel; + + int dim; + int min; + int max; + int dflt; // defalut value + gboolean ab_supported; + + int current; +}; + +IbBrightnessUscreenControl* +ib_brightness_uscreen_control_new (GDBusProxy* uscreen_proxy, brightness_params_t params) +{ + IbBrightnessUscreenControl *control; + + control = g_new0 (IbBrightnessUscreenControl, 1); + control->uscreen_proxy = uscreen_proxy; + control->gcancel = g_cancellable_new(); + + control->dim = params.dim; + control->min = params.min; + control->max = params.max; + control->dflt = params.dflt; + control->ab_supported = params.ab_supported; + + // XXX: set the brightness value is the only way to sync the brightness value with + // unity.screen, and we should set the user prefered / last set brightness value upon startup. + // Before we have code to store last set brightness value or other mechanism, we set + // it to default brightness that powerd proposed. + ib_brightness_uscreen_control_set_value(control, control->dflt); + + return control; +} + +void +ib_brightness_uscreen_control_set_value (IbBrightnessUscreenControl* self, gint value) +{ + gboolean ret; + + value = CLAMP(value, self->min, self->max); + ret = setUserBrightness(self->uscreen_proxy, self->gcancel, value); + if (ret) + { + self->current = value; + } +} + +gint +ib_brightness_uscreen_control_get_value (IbBrightnessUscreenControl* self) +{ + return self->current; +} + +gint +ib_brightness_uscreen_control_get_max_value (IbBrightnessUscreenControl* self) +{ + return self->max; +} + +void +ib_brightness_uscreen_control_free (IbBrightnessUscreenControl *self) +{ + g_cancellable_cancel (self->gcancel); + g_object_unref (self->gcancel); + g_object_unref (self->uscreen_proxy); + g_free (self); +} + diff --git a/src/ib-brightness-uscreen-control.h b/src/ib-brightness-uscreen-control.h new file mode 100644 index 0000000..3d026a9 --- /dev/null +++ b/src/ib-brightness-uscreen-control.h @@ -0,0 +1,43 @@ +/* + * 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: + * Y.C Cheng <yc.cheng@canonical.com> + */ + +#ifndef __IB_BRIGHTNESS_USCREEN_CONTROL_H__ +#define __IB_BRIGHTNESS_USCREEN_CONTROL_H__ + +#include <gio/gio.h> + +typedef struct { + int dim; + int min; + int max; + int dflt; + gboolean ab_supported; +} brightness_params_t; + +GDBusProxy* uscreen_get_proxy(brightness_params_t *); + +typedef struct _IbBrightnessUScreenControl IbBrightnessUscreenControl; + +IbBrightnessUscreenControl* ib_brightness_uscreen_control_new (GDBusProxy* uscreen_proxy, brightness_params_t params); +void ib_brightness_uscreen_control_set_value (IbBrightnessUscreenControl* self, gint value); +gint ib_brightness_uscreen_control_get_value (IbBrightnessUscreenControl* self); +gint ib_brightness_uscreen_control_get_max_value (IbBrightnessUscreenControl* self); +void ib_brightness_uscreen_control_free (IbBrightnessUscreenControl *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..81cd6f1 --- /dev/null +++ b/src/notifier.c @@ -0,0 +1,445 @@ +/* + * 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 <libnotify/notify.h> + +#include <glib/gi18n.h> + +#define HINT_INTERACTIVE "x-canonical-switch-to-application" + +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; + +/** +*** +**/ + +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; + } +} + +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 +notification_show(IndicatorPowerNotifier * self) +{ + priv_t * const p = get_priv(self); + gdouble pct; + char * body; + NotifyNotification * nn; + GError * error; + + notification_clear(self); + + /* create the notification */ + pct = indicator_power_device_get_percentage(p->battery); + body = g_strdup_printf(_("%.0f%% charge remaining"), pct); + nn = notify_notification_new(_("Battery Low"), body, NULL); + g_free (body); + /*notify_notification_set_hint(nn, HINT_INTERACTIVE, g_variant_new_boolean(TRUE));*/ + + /* 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++) + { + if (!notify_init("indicator-power-service")) + { + g_critical("Unable to initialize libnotify! Notifications might not be shown."); + } + } +} + +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/service.c b/src/service.c index 982a24e..0cd448b 100644 --- a/src/service.c +++ b/src/service.c @@ -18,15 +18,16 @@ * 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 "dbus-shared.h" #include "device.h" #include "device-provider.h" +#include "notifier.h" #include "ib-brightness-control.h" +#include "ib-brightness-uscreen-control.h" #include "service.h" #define BUS_NAME "com.canonical.indicator.power" @@ -104,6 +105,7 @@ struct _IndicatorPowerServicePrivate GSettings * settings; IbBrightnessControl * brightness_control; + IbBrightnessUscreenControl * brightness_uscreen_control; guint own_id; guint actions_export_id; @@ -120,6 +122,7 @@ struct _IndicatorPowerServicePrivate GList * devices; /* IndicatorPowerDevice */ IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerNotifier * notifier; }; typedef IndicatorPowerServicePrivate priv_t; @@ -312,21 +315,7 @@ static GVariant * create_header_state (IndicatorPowerService * self) { GVariantBuilder b; - gchar * label = NULL; - gchar * a11y = NULL; - GIcon * icon = NULL; - priv_t * p = self->priv; - - if (p->primary_device != NULL) - { - indicator_power_device_get_header (p->primary_device, - g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S), - g_settings_get_boolean (p->settings, SETTINGS_SHOW_PERCENTAGE_S), - &label, - &a11y); - - icon = indicator_power_device_get_gicon (p->primary_device); - } + const priv_t * const p = self->priv; g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}")); @@ -335,24 +324,48 @@ create_header_state (IndicatorPowerService * self) g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (should_be_visible (self))); - if (label != NULL) - g_variant_builder_add (&b, "{sv}", "label", g_variant_new_take_string (label)); - - if (icon != NULL) + if (p->primary_device != NULL) { - GVariant * v; + char * title; + GIcon * icon; + const gboolean want_time = g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S); + const gboolean want_percent = g_settings_get_boolean (p->settings, SETTINGS_SHOW_PERCENTAGE_S); + + title = indicator_power_device_get_readable_title (p->primary_device, + want_time, + want_percent); + if (title) + { + if (*title) + g_variant_builder_add (&b, "{sv}", "label", g_variant_new_take_string (title)); + else + g_free (title); + } - if ((v = g_icon_serialize (icon))) + title = indicator_power_device_get_accessible_title (p->primary_device, + want_time, + want_percent); + if (title) { - g_variant_builder_add (&b, "{sv}", "icon", v); - g_variant_unref (v); + if (*title) + g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_take_string (title)); + else + g_free (title); } - g_object_unref (icon); - } + if ((icon = indicator_power_device_get_gicon (p->primary_device))) + { + GVariant * serialized_icon = g_icon_serialize (icon); - if (a11y != NULL) - g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_take_string (a11y)); + if (serialized_icon != NULL) + { + g_variant_builder_add (&b, "{sv}", "icon", serialized_icon); + g_variant_unref (serialized_icon); + } + + g_object_unref (icon); + } + } return g_variant_builder_end (&b); } @@ -365,7 +378,7 @@ create_header_state (IndicatorPowerService * self) ***/ static void -append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) +append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device, int profile) { const UpDeviceKind kind = indicator_power_device_get_kind (device); @@ -375,24 +388,33 @@ append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) GMenuItem * item; GIcon * icon; - label = indicator_power_device_get_label (device); - item = g_menu_item_new (label, "indicator.activate-statistics"); + label = indicator_power_device_get_readable_text (device); + item = g_menu_item_new (label, NULL); g_free (label); - g_menu_item_set_action_and_target(item, "indicator.activate-statistics", "s", - indicator_power_device_get_object_path (device)); + + g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.basic"); if ((icon = indicator_power_device_get_gicon (device))) { - GVariant * v; - if ((v = g_icon_serialize (icon))) + GVariant * serialized_icon = g_icon_serialize (icon); + + if (serialized_icon != NULL) { - g_menu_item_set_attribute_value (item, G_MENU_ATTRIBUTE_ICON, v); - g_variant_unref (v); + g_menu_item_set_attribute_value (item, + G_MENU_ATTRIBUTE_ICON, + serialized_icon); + g_variant_unref (serialized_icon); } g_object_unref (icon); } + if (profile == PROFILE_DESKTOP) + { + g_menu_item_set_action_and_target(item, "indicator.activate-statistics", "s", + indicator_power_device_get_object_path (device)); + } + g_menu_append_item (menu, item); g_object_unref (item); } @@ -400,13 +422,13 @@ append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device) static GMenuModel * -create_desktop_devices_section (IndicatorPowerService * self) +create_desktop_devices_section (IndicatorPowerService * self, int profile) { GList * l; GMenu * menu = g_menu_new (); for (l=self->priv->devices; l!=NULL; l=l->next) - append_device_to_menu (menu, l->data); + append_device_to_menu (menu, l->data, profile); return G_MENU_MODEL (menu); } @@ -440,8 +462,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 */ + priv_t * p = self->priv; + int max = 0; + if (p->brightness_control) + { + max = ib_brightness_control_get_max_value (self->priv->brightness_control); + } + else if (p->brightness_uscreen_control) + { + max = ib_brightness_uscreen_control_get_max_value (self->priv->brightness_uscreen_control); + } + *low = (gint)(max * 0.05); /* 5% minimum -- don't let the screen go completely dark */ *high = max; } @@ -461,29 +492,19 @@ percentage_to_brightness (IndicatorPowerService * self, double percentage) return (int)(lo + (percentage*(hi-lo))); } -static GMenuItem * -create_brightness_menuitem (IndicatorPowerService * self) -{ - 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" ); - - return item; -} - static GVariant * action_state_for_brightness (IndicatorPowerService * self) { priv_t * p = self->priv; - const gint brightness = ib_brightness_control_get_value (p->brightness_control); + gint brightness = 0; + if (p->brightness_control) + { + brightness = ib_brightness_control_get_value (p->brightness_control); + } + else if (p->brightness_uscreen_control) + { + brightness = ib_brightness_uscreen_control_get_value (p->brightness_uscreen_control); + } return g_variant_new_double (brightness_to_percentage (self, brightness)); } @@ -502,7 +523,16 @@ on_brightness_change_requested (GSimpleAction * action G_GNUC_UNUSED, 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); + + if (self->priv->brightness_control) + { + ib_brightness_control_set_value (self->priv->brightness_control, brightness); + } + else if (self->priv->brightness_uscreen_control) + { + ib_brightness_uscreen_control_set_value (self->priv->brightness_uscreen_control, brightness); + } + update_brightness_action_state (self); } @@ -527,18 +557,12 @@ 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; section = g_menu_new (); - - item = create_brightness_menuitem (self); - g_menu_append_item (section, item); update_brightness_action_state (self); - g_object_unref (item); - g_menu_append (section, _("Battery settings…"), "indicator.activate-phone-settings"); return G_MENU_MODEL (section); @@ -571,18 +595,18 @@ 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)); - rebuild_section (greeter->submenu, 0, create_desktop_devices_section (self)); + rebuild_section (desktop->submenu, 0, create_desktop_devices_section (self, PROFILE_DESKTOP)); + rebuild_section (greeter->submenu, 0, create_desktop_devices_section (self, PROFILE_DESKTOP_GREETER)); } if (sections & SECTION_SETTINGS) @@ -598,18 +622,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) { @@ -633,12 +645,12 @@ create_menu (IndicatorPowerService * self, int profile) break; case PROFILE_DESKTOP: - sections[n++] = create_desktop_devices_section (self); + sections[n++] = create_desktop_devices_section (self, PROFILE_DESKTOP); sections[n++] = create_desktop_settings_section (self); break; case PROFILE_DESKTOP_GREETER: - sections[n++] = create_desktop_devices_section (self); + sections[n++] = create_desktop_devices_section (self, PROFILE_DESKTOP_GREETER); break; } @@ -812,6 +824,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, @@ -911,11 +926,17 @@ 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); @@ -992,6 +1013,7 @@ my_dispose (GObject * o) g_clear_object (&p->settings); } + g_clear_object (&p->notifier); g_clear_object (&p->brightness_action); g_clear_object (&p->battery_level_action); g_clear_object (&p->header_action); @@ -999,7 +1021,9 @@ my_dispose (GObject * o) g_clear_object (&p->conn); + // g_clear_pointer has NULL check inside. g_clear_pointer (&p->brightness_control, ib_brightness_control_free); + g_clear_pointer (&p->brightness_uscreen_control, ib_brightness_uscreen_control_free); indicator_power_service_set_device_provider (self, NULL); @@ -1013,6 +1037,8 @@ my_dispose (GObject * o) static void indicator_power_service_init (IndicatorPowerService * self) { + GDBusProxy *uscreen_proxy; + brightness_params_t brightness_params; priv_t * p = G_TYPE_INSTANCE_GET_PRIVATE (self, INDICATOR_TYPE_POWER_SERVICE, IndicatorPowerServicePrivate); @@ -1022,7 +1048,17 @@ indicator_power_service_init (IndicatorPowerService * self) p->settings = g_settings_new ("com.canonical.indicator.power"); - p->brightness_control = ib_brightness_control_new (); + p->notifier = indicator_power_notifier_new (); + + uscreen_proxy = uscreen_get_proxy(&brightness_params); + if (uscreen_proxy != NULL) + { + p->brightness_uscreen_control = ib_brightness_uscreen_control_new(uscreen_proxy, brightness_params); + } + else + { + p->brightness_control = ib_brightness_control_new (); + } init_gactions (self); @@ -1096,9 +1132,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); @@ -1118,6 +1153,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) { @@ -1125,13 +1283,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; |