diff options
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/main.c | 12 | ||||
-rw-r--r-- | src/notifier.c | 336 | ||||
-rw-r--r-- | src/notifier.h | 68 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/test-notify.cc | 59 |
6 files changed, 474 insertions, 4 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a39b945..4747b12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ set(SERVICE_MANUAL_SOURCES ib-brightness-uscreen-control.c device-provider.c device.c + notifier.c service.c) # generated sources @@ -25,6 +25,7 @@ #include "device.h" #include "device-provider-upower.h" +#include "notifier.h" #include "service.h" /*** @@ -41,9 +42,10 @@ 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; + IndicatorPowerNotifier * notifier; + IndicatorPowerService * service; + GMainLoop * loop; /* boilerplate i18n */ setlocale (LC_ALL, ""); @@ -52,6 +54,7 @@ main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) /* run */ device_provider = indicator_power_device_provider_upower_new (); + notifier = indicator_power_notifier_new (device_provider); service = indicator_power_service_new (device_provider); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (service, INDICATOR_POWER_SERVICE_SIGNAL_NAME_LOST, @@ -59,8 +62,9 @@ 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 (¬ifier); + g_clear_object (&device_provider); return 0; } diff --git a/src/notifier.c b/src/notifier.c new file mode 100644 index 0000000..79ea4cc --- /dev/null +++ b/src/notifier.c @@ -0,0 +1,336 @@ +/* + * 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 "device.h" +#include "device-provider.h" +#include "notifier.h" +#include "service.h" + +#include <libnotify/notify.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +G_DEFINE_TYPE(IndicatorPowerNotifier, + indicator_power_notifier, + G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_DEVICE_PROVIDER, + LAST_PROP +}; + +static GParamSpec * properties[LAST_PROP]; + +static int n_notifiers = 0; + +struct _IndicatorPowerNotifierPrivate +{ + IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerDevice * primary_device; + gdouble battery_level; + time_t time_remaining; + NotifyNotification* notify_notification; +}; + +typedef IndicatorPowerNotifierPrivate priv_t; + +/*** +**** +***/ + +static void +emit_critical_signal(IndicatorPowerNotifier * self G_GNUC_UNUSED) +{ + g_message("FIXME %s %s", G_STRFUNC, G_STRLOC); +} + +static void +emit_hide_signal(IndicatorPowerNotifier * self G_GNUC_UNUSED) +{ + g_message("FIXME %s %s", G_STRFUNC, G_STRLOC); +} + +static void +emit_show_signal(IndicatorPowerNotifier * self G_GNUC_UNUSED) +{ + g_message("FIXME %s %s", G_STRFUNC, G_STRLOC); +} + +static void +notification_clear(IndicatorPowerNotifier * self) +{ + priv_t * p = self->priv; + + if (p->notify_notification != NULL) + { + notify_notification_clear_actions(p->notify_notification); + g_signal_handlers_disconnect_by_data(p->notify_notification, self); + g_clear_object(&p->notify_notification); + emit_hide_signal(self); + } +} + +static void +on_notification_clicked(NotifyNotification * notify_notification G_GNUC_UNUSED, + char * action G_GNUC_UNUSED, + gpointer gself G_GNUC_UNUSED) +{ + /* no-op */ +} + +static void +notification_show(IndicatorPowerNotifier * self, + IndicatorPowerDevice * device) + +{ + priv_t * p = self->priv; + char * body; + NotifyNotification * nn; + + // if there's already a notification, tear it down + if (p->notify_notification != NULL) + { + notification_clear (self); + } + + // create the notification + body = g_strdup_printf(_("%d%% charge remaining"), (int)indicator_power_device_get_percentage(device)); + p->notify_notification = nn = notify_notification_new(_("Battery Low"), body, NULL); + notify_notification_set_hint(nn, "x-canonical-snap-decisions", g_variant_new_boolean(TRUE)); + notify_notification_set_hint(nn, "x-canonical-private-button-tint", g_variant_new_boolean(TRUE)); + notify_notification_add_action(nn, "OK", _("OK"), on_notification_clicked, self, NULL); + g_signal_connect_swapped(nn, "closed", G_CALLBACK(notification_clear), self); + + // show the notification + GError* error = NULL; + notify_notification_show(nn, &error); + if (error != NULL) + { + g_critical("Unable to show snap decision for '%s': %s", body, error->message); + g_error_free(error); + } + else + { + emit_show_signal(self); + } + + g_free (body); +} + +static void +on_battery_level_changed(IndicatorPowerNotifier * self G_GNUC_UNUSED, + IndicatorPowerDevice * device, + gdouble old_value, + gdouble new_value) +{ + static const double critical_level = 2.0; + static const double very_low_level = 5.0; + static const double low_level = 48.0; + + if (indicator_power_device_get_state(device) != UP_DEVICE_STATE_DISCHARGING) + return; + +g_message ("%s - %s - %f - %f", G_STRFUNC, indicator_power_device_get_object_path(device), old_value, new_value); + + if ((old_value > critical_level) && (new_value <= critical_level)) + { + emit_critical_signal(self); + } + else if ((old_value > very_low_level) && (new_value <= very_low_level)) + { + notification_show(self, device); + } + else if ((old_value > low_level) && (new_value <= low_level)) + { + notification_show(self, device); + } +} + +static void +on_devices_changed(IndicatorPowerNotifier * self) +{ + priv_t * p = self->priv; + GList * devices; + + // find the primary device + devices = indicator_power_device_provider_get_devices(p->device_provider); + g_clear_object(&p->primary_device); + p->primary_device = indicator_power_service_choose_primary_device (devices); + g_list_free_full (devices, (GDestroyNotify)g_object_unref); + + if (p->primary_device != NULL) + { + // test for battery level change + const gdouble old_level = (int)(p->battery_level*1000) ? p->battery_level : 100.0; + const gdouble new_level = indicator_power_device_get_percentage(p->primary_device); + if ((int)(old_level*1000) != (int)(new_level*1000)) + on_battery_level_changed (self, p->primary_device, old_level, new_level); + + p->battery_level = new_level; + p->time_remaining = indicator_power_device_get_time (p->primary_device); + } +} + +/*** +**** GObject virtual functions +***/ + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IndicatorPowerNotifier * self = INDICATOR_POWER_NOTIFIER (o); + priv_t * p = self->priv; + + switch (property_id) + { + case PROP_DEVICE_PROVIDER: + g_value_set_object (value, p->device_provider); + 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 * self = INDICATOR_POWER_NOTIFIER (o); + + switch (property_id) + { + case PROP_DEVICE_PROVIDER: + indicator_power_notifier_set_device_provider (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 * self = INDICATOR_POWER_NOTIFIER(o); + + indicator_power_notifier_set_device_provider(self, NULL); + notification_clear(self); + + G_OBJECT_CLASS (indicator_power_notifier_parent_class)->dispose (o); +} + +static void +my_finalize (GObject * o G_GNUC_UNUSED) +{ + if (!--n_notifiers) + notify_uninit(); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_notifier_init (IndicatorPowerNotifier * self) +{ + priv_t * p = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_TYPE_POWER_NOTIFIER, + IndicatorPowerNotifierPrivate); + self->priv = p; + + //p->battery_levels = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); + + if (!n_notifiers++ && !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; + + g_type_class_add_private (klass, sizeof (IndicatorPowerNotifierPrivate)); + + properties[PROP_0] = NULL; + + properties[PROP_DEVICE_PROVIDER] = g_param_spec_object ( + "device-provider", + "Device Provider", + "Source for power devices", + 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 (IndicatorPowerDeviceProvider * device_provider) +{ + GObject * o = g_object_new (INDICATOR_TYPE_POWER_NOTIFIER, + "device-provider", device_provider, + NULL); + + return INDICATOR_POWER_NOTIFIER (o); +} + +void +indicator_power_notifier_set_device_provider(IndicatorPowerNotifier * self, + IndicatorPowerDeviceProvider * dp) +{ + priv_t * p; + + g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); + g_return_if_fail(!dp || INDICATOR_IS_POWER_DEVICE_PROVIDER(dp)); + p = self->priv; + + if (p->device_provider != NULL) + { + g_signal_handlers_disconnect_by_data(p->device_provider, self); + g_clear_object(&p->device_provider); + g_clear_object(&p->primary_device); + } + + if (dp != NULL) + { + p->device_provider = g_object_ref(dp); + + g_signal_connect_swapped(p->device_provider, "devices-changed", + G_CALLBACK(on_devices_changed), self); + + on_devices_changed(self); + } +} diff --git a/src/notifier.h b/src/notifier.h new file mode 100644 index 0000000..e8dfaab --- /dev/null +++ b/src/notifier.h @@ -0,0 +1,68 @@ +/* + * 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 <glib.h> +#include <glib-object.h> + +#include "device-provider.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)) + +typedef struct _IndicatorPowerNotifier IndicatorPowerNotifier; +typedef struct _IndicatorPowerNotifierClass IndicatorPowerNotifierClass; +typedef struct _IndicatorPowerNotifierPrivate IndicatorPowerNotifierPrivate; + +/** + * The Indicator Power Notifier. + */ +struct _IndicatorPowerNotifier +{ + /*< private >*/ + GObject parent; + IndicatorPowerNotifierPrivate * priv; +}; + +struct _IndicatorPowerNotifierClass +{ + GObjectClass parent_class; +}; + +/*** +**** +***/ + +GType indicator_power_notifier_get_type (void); + +IndicatorPowerNotifier * indicator_power_notifier_new (IndicatorPowerDeviceProvider * provider); + +void indicator_power_notifier_set_device_provider (IndicatorPowerNotifier * self, + IndicatorPowerDeviceProvider * provider); + + +G_END_DECLS + +#endif /* __INDICATOR_POWER_NOTIFIER_H__ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5ad09d..4489bdc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,5 +42,7 @@ function(add_test_by_name name) add_dependencies (${TEST_NAME} libindicatorpowerservice) target_link_libraries (${TEST_NAME} indicatorpowerservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) endfunction() +add_test_by_name(test-notify) +add_test(NAME dear-reader-the-next-test-takes-80-seconds COMMAND true) add_test_by_name(test-device) diff --git a/tests/test-notify.cc b/tests/test-notify.cc new file mode 100644 index 0000000..0b75177 --- /dev/null +++ b/tests/test-notify.cc @@ -0,0 +1,59 @@ +/* + * 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 "device.h" +#include "service.h" + +#include <gtest/gtest.h> + +#include <gio/gio.h> + +class NotifyTest : public ::testing::Test +{ + private: + + typedef ::testing::Test super; + + protected: + + virtual void SetUp() + { + super::SetUp(); + } + + virtual void TearDown() + { + super::TearDown(); + } +}; + +/*** +**** +***/ + +// mock device provider + +// send notifications of a device going down from 50% to 3% by 1% increments + +// popup should appear exactly twice: once at 10%, once at 5% + +TEST_F(NotifyTest, HelloWorld) +{ +} + |