/* * 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 . * * Authors: * Charles Kerr */ #include "dbus-battery-info.h" #include "dbus-shared.h" #include "device.h" #include "device-provider.h" #include "notifier.h" #include "service.h" #include #include #include G_DEFINE_TYPE(IndicatorPowerNotifier, indicator_power_notifier, G_TYPE_OBJECT) enum { PROP_0, PROP_DEVICE_PROVIDER, PROP_IS_WARNING, PROP_POWER_LEVEL, LAST_PROP }; #define DEVICE_PROVIDER_NAME "device-provider" #define IS_WARNING_NAME "is-warning" #define POWER_LEVEL_NAME "power-level" static GParamSpec * properties[LAST_PROP]; static int n_notifiers = 0; typedef enum { POWER_LEVEL_OK, POWER_LEVEL_LOW, POWER_LEVEL_VERY_LOW, POWER_LEVEL_CRITICAL } PowerLevel; struct _IndicatorPowerNotifierPrivate { IndicatorPowerDeviceProvider * device_provider; IndicatorPowerDevice * battery; NotifyNotification* notify_notification; gboolean is_warning; PowerLevel power_level; DbusBattery * dbus_battery; GBinding * is_warning_binding; GBinding * power_level_binding; GDBusConnection * bus; }; typedef IndicatorPowerNotifierPrivate priv_t; /*** **** ***/ /* implemented here rather than my_set_property() to guard from public use because this is a read-only property */ static void set_is_warning_property (IndicatorPowerNotifier * self, gboolean is_warning) { priv_t * p = self->priv; if (p->is_warning != is_warning) { p->is_warning = is_warning; g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_IS_WARNING]); } } /* implemented here rather than my_set_property() to guard from public use because this is a read-only property */ static void set_power_level_property (IndicatorPowerNotifier * self, PowerLevel power_level) { priv_t * p = self->priv; if (p->power_level != power_level) { p->power_level = power_level; g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_POWER_LEVEL]); } } /*** **** ***/ static void notification_clear (IndicatorPowerNotifier * self) { priv_t * p = self->priv; if (p->notify_notification != NULL) { set_is_warning_property (self, FALSE); notify_notification_clear_actions(p->notify_notification); g_signal_handlers_disconnect_by_data(p->notify_notification, self); g_clear_object(&p->notify_notification); } } 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 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 { set_is_warning_property (self, TRUE); } g_free (body); } static PowerLevel get_power_level (const IndicatorPowerDevice * device) { static const double percent_critical = 2.0; static const double percent_very_low = 5.0; static const double percent_low = 48.0; const gdouble p = indicator_power_device_get_percentage(device); PowerLevel ret; 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; } static void on_devices_changed(IndicatorPowerNotifier * self) { priv_t * p = self->priv; GList * devices; IndicatorPowerDevice * primary; /* find the primary battery */ devices = indicator_power_device_provider_get_devices (p->device_provider); primary = indicator_power_service_choose_primary_device (devices); g_clear_object (&p->battery); if ((primary != NULL) && (indicator_power_device_get_kind (primary) == UP_DEVICE_KIND_BATTERY)) p->battery = g_object_ref (primary); g_clear_object(&primary); g_list_free_full (devices, (GDestroyNotify)g_object_unref); /* update our state based on the new primary device */ if (p->battery == NULL) { /* if there's no primary battery, put everything in standby mode */ set_power_level_property (self, POWER_LEVEL_OK); notification_clear(self); } else { const PowerLevel power_level = get_power_level (p->battery); if (p->power_level != power_level) { set_power_level_property (self, power_level); /* maybe update the notifications */ if ((power_level == POWER_LEVEL_OK) || (indicator_power_device_get_state(p->battery) != UP_DEVICE_STATE_DISCHARGING)) { notification_clear (self); } else { notification_show (self, p->battery); } } } } /*** **** 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; case PROP_POWER_LEVEL: g_value_set_int (value, p->power_level); break; case PROP_IS_WARNING: g_value_set_boolean (value, p->is_warning); 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); priv_t * p = self->priv; g_message ("%s %s dispose %p", G_STRLOC, G_STRFUNC, (void*)o); indicator_power_notifier_set_bus(self, NULL); notification_clear(self); indicator_power_notifier_set_device_provider (self, NULL); g_clear_pointer (&p->power_level_binding, g_binding_unbind); g_clear_pointer (&p->is_warning_binding, g_binding_unbind); g_clear_object (&p->dbus_battery); G_OBJECT_CLASS (indicator_power_notifier_parent_class)->dispose (o); } static void my_finalize (GObject * o) { g_message ("%s %s finalize %p", G_STRLOC, G_STRFUNC, (void*)o); 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; g_message ("%s %s init %p", G_STRLOC, G_STRFUNC, (void*)self); p->dbus_battery = dbus_battery_skeleton_new (); /* FIXME: own the busname and export the skeleton */ p->is_warning_binding = g_object_bind_property (self, IS_WARNING_NAME, p->dbus_battery, IS_WARNING_NAME, G_BINDING_SYNC_CREATE); p->power_level_binding = g_object_bind_property (self, POWER_LEVEL_NAME, p->dbus_battery, POWER_LEVEL_NAME, G_BINDING_SYNC_CREATE); 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_NAME, "Device Provider", "Source for power devices", G_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_POWER_LEVEL] = g_param_spec_int ( POWER_LEVEL_NAME, "Power Level", "Power Level of the batteries", POWER_LEVEL_OK, POWER_LEVEL_CRITICAL, POWER_LEVEL_OK, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); properties[PROP_IS_WARNING] = g_param_spec_boolean ( IS_WARNING_NAME, "Is Warning", "Whether or not we're currently warning the user about a low battery", FALSE, G_PARAM_READABLE | 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_NAME, 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->battery); } 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); } } void indicator_power_notifier_set_bus (IndicatorPowerNotifier * self, GDBusConnection * bus) { priv_t * p; g_return_if_fail(INDICATOR_IS_POWER_NOTIFIER(self)); p = self->priv; if (p->bus != NULL) { if (p->dbus_battery != NULL) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON(p->dbus_battery)); } g_clear_object (&p->bus); } if (bus != NULL) { GError * error; p->bus = g_object_ref (bus); error = NULL; g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(p->dbus_battery), bus, BUS_PATH"/Battery", &error); if (error != NULL) { g_warning ("Unable to export LowBattery properties: %s", error->message); g_error_free (error); } } }