Back to index

indicator-power  12.10.0
indicator-power.c
Go to the documentation of this file.
00001 /*
00002 An indicator to power related information in the menubar.
00003 
00004 Copyright 2011 Canonical Ltd.
00005 
00006 Authors:
00007     Javier Jardon <javier.jardon@codethink.co.uk>
00008 
00009 This program is free software: you can redistribute it and/or modify it
00010 under the terms of the GNU General Public License version 3, as published 
00011 by the Free Software Foundation.
00012 
00013 This program is distributed in the hope that it will be useful, but
00014 WITHOUT ANY WARRANTY; without even the implied warranties of
00015 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
00016 PURPOSE.  See the GNU General Public License for more details.
00017 
00018 You should have received a copy of the GNU General Public License along
00019 with this program.  If not, see <http://www.gnu.org/licenses/>.
00020 */
00021 
00022 #ifdef HAVE_CONFIG_H
00023  #include "config.h"
00024 #endif
00025 
00026 /* GStuff */
00027 #include <glib-object.h>
00028 #include <glib/gi18n-lib.h>
00029 #include <gio/gio.h>
00030 
00031 #include "dbus-listener.h"
00032 #include "device.h"
00033 #include "indicator-power.h"
00034 
00035 #define ICON_POLICY_KEY "icon-policy"
00036 
00037 #define DEFAULT_ICON   "gpm-battery-missing"
00038 
00039 enum {
00040   POWER_INDICATOR_ICON_POLICY_PRESENT,
00041   POWER_INDICATOR_ICON_POLICY_CHARGE,
00042   POWER_INDICATOR_ICON_POLICY_NEVER
00043 };
00044 
00045 struct _IndicatorPowerPrivate
00046 {
00047   GtkMenu   *menu;
00048 
00049   GtkLabel *label;
00050   GtkImage *status_image;
00051   gchar    *accessible_desc;
00052 
00053   IndicatorPowerDbusListener * dbus_listener;
00054 
00055   GSList * devices;
00056   IndicatorPowerDevice * device;
00057 
00058   GSettings *settings;
00059 };
00060 
00061 
00062 /* LCOV_EXCL_START */
00063 INDICATOR_SET_VERSION
00064 INDICATOR_SET_TYPE (INDICATOR_POWER_TYPE)
00065 /* LCOV_EXCL_STOP */
00066 
00067 /* Prototypes */
00068 static void             indicator_power_dispose         (GObject *object);
00069 static void             indicator_power_finalize        (GObject *object);
00070 
00071 static GtkLabel*        get_label                       (IndicatorObject * io);
00072 static GtkImage*        get_image                       (IndicatorObject * io);
00073 static GtkMenu*         get_menu                        (IndicatorObject * io);
00074 static const gchar*     get_accessible_desc             (IndicatorObject * io);
00075 static const gchar*     get_name_hint                   (IndicatorObject * io);
00076 
00077 static void             update_visibility               (IndicatorPower * self);
00078 static gboolean         should_be_visible               (IndicatorPower * self);
00079 
00080 static void             on_entry_added                  (IndicatorObject * io, IndicatorObjectEntry * entry, gpointer user_data);
00081 
00082 /*
00083 static void gsd_appeared_callback (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data);
00084 */
00085 
00086 /* LCOV_EXCL_START */
00087 G_DEFINE_TYPE (IndicatorPower, indicator_power, INDICATOR_OBJECT_TYPE);
00088 /* LCOV_EXCL_STOP */
00089 
00090 static void
00091 indicator_power_class_init (IndicatorPowerClass *klass)
00092 {
00093   GObjectClass *object_class = G_OBJECT_CLASS (klass);
00094   IndicatorObjectClass *io_class = INDICATOR_OBJECT_CLASS (klass);
00095 
00096   g_type_class_add_private (klass, sizeof (IndicatorPowerPrivate));
00097 
00098   object_class->dispose = indicator_power_dispose;
00099   object_class->finalize = indicator_power_finalize;
00100 
00101   io_class->get_label = get_label;
00102   io_class->get_image = get_image;
00103   io_class->get_menu = get_menu;
00104   io_class->get_accessible_desc = get_accessible_desc;
00105   io_class->get_name_hint = get_name_hint;
00106 }
00107 
00108 static void
00109 indicator_power_init (IndicatorPower *self)
00110 {
00111   IndicatorPowerPrivate * priv;
00112 
00113   priv = G_TYPE_INSTANCE_GET_PRIVATE (self, INDICATOR_POWER_TYPE, IndicatorPowerPrivate);
00114 
00115   priv->menu = GTK_MENU(gtk_menu_new());
00116 
00117   priv->accessible_desc = NULL;
00118 
00119   priv->dbus_listener = g_object_new (INDICATOR_POWER_DBUS_LISTENER_TYPE, NULL);
00120   g_signal_connect_swapped (priv->dbus_listener, INDICATOR_POWER_DBUS_LISTENER_DEVICES_ENUMERATED,
00121                             G_CALLBACK(indicator_power_set_devices), self);
00122 
00123   priv->settings = g_settings_new ("com.canonical.indicator.power");
00124   g_signal_connect_swapped (priv->settings, "changed::" ICON_POLICY_KEY,
00125                             G_CALLBACK(update_visibility), self);
00126   g_object_set (G_OBJECT(self),
00127                 INDICATOR_OBJECT_DEFAULT_VISIBILITY, FALSE,
00128                 NULL);
00129 
00130   g_signal_connect (INDICATOR_OBJECT(self), INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
00131                     G_CALLBACK(on_entry_added), NULL);
00132 
00133   self->priv = priv;
00134 }
00135 
00136 static void
00137 dispose_devices (IndicatorPower * self)
00138 {
00139   IndicatorPowerPrivate * priv = self->priv;
00140 
00141   g_clear_object (&priv->device);
00142   g_slist_free_full (priv->devices, g_object_unref);
00143   priv->devices = NULL;
00144 }
00145 static void
00146 indicator_power_dispose (GObject *object)
00147 {
00148   IndicatorPower *self = INDICATOR_POWER(object);
00149   IndicatorPowerPrivate * priv = self->priv;
00150 
00151   dispose_devices (self);
00152 
00153   g_clear_object (&priv->dbus_listener);
00154   g_clear_object (&priv->settings);
00155 
00156   G_OBJECT_CLASS (indicator_power_parent_class)->dispose (object);
00157 }
00158 
00159 static void
00160 indicator_power_finalize (GObject *object)
00161 {
00162   IndicatorPower *self = INDICATOR_POWER(object);
00163   IndicatorPowerPrivate * priv = self->priv;
00164 
00165   g_free (priv->accessible_desc);
00166 
00167   G_OBJECT_CLASS (indicator_power_parent_class)->finalize (object);
00168 }
00169 
00170 /***
00171 ****
00172 ***/
00173 
00174 static void
00175 spawn_command_line_async (const char * command)
00176 {
00177   GError * err = NULL;
00178   if (!g_spawn_command_line_async (command, &err))
00179       g_warning ("Couldn't execute command \"%s\": %s", command, err->message);
00180   g_clear_error (&err);
00181 }
00182 
00183 static void
00184 option_toggled_cb (GtkCheckMenuItem *item, IndicatorPower * self)
00185 {
00186   gtk_widget_set_visible (GTK_WIDGET (self->priv->label),
00187                           gtk_check_menu_item_get_active(item));
00188 }
00189 
00190 /* ensure that the entry is using self's accessible description */
00191 static void
00192 refresh_entry_accessible_desc (IndicatorPower * self, IndicatorObjectEntry * entry)
00193 {
00194   const char * newval = self->priv->accessible_desc;
00195 
00196   if (entry->accessible_desc != newval)
00197   {
00198     g_debug ("%s: setting entry %p accessible description to '%s'", G_STRFUNC, entry, newval);
00199     entry->accessible_desc = newval;
00200     g_signal_emit (self, INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE_ID, 0, entry);
00201   }
00202 }
00203 
00204 static void
00205 on_entry_added (IndicatorObject       * io,
00206                 IndicatorObjectEntry  * entry,
00207                 gpointer                user_data G_GNUC_UNUSED)
00208 {
00209   refresh_entry_accessible_desc (INDICATOR_POWER(io), entry);
00210 }
00211 
00212 static void
00213 set_accessible_desc (IndicatorPower *self, const gchar *desc)
00214 {
00215   g_debug ("%s: setting accessible description to '%s'", G_STRFUNC, desc);
00216 
00217   if (desc && *desc)
00218   {
00219     /* update our copy of the string */
00220     char * oldval = self->priv->accessible_desc;
00221     self->priv->accessible_desc = g_strdup (desc);
00222 
00223     /* ensure that the entries are using self's accessible description */
00224     GList * l;
00225     GList * entries = indicator_object_get_entries(INDICATOR_OBJECT(self));
00226     for (l=entries; l!=NULL; l=l->next)
00227       refresh_entry_accessible_desc (self, l->data);
00228 
00229     /* cleanup */
00230     g_list_free (entries);
00231     g_free (oldval);
00232   }
00233 }
00234 
00235 static gboolean
00236 menu_add_device (GtkMenu * menu, const IndicatorPowerDevice * device)
00237 {
00238   gboolean added = FALSE;
00239   const UpDeviceKind kind = indicator_power_device_get_kind (device);
00240 
00241   if (kind != UP_DEVICE_KIND_LINE_POWER)
00242   {
00243     GtkWidget *icon;
00244     GtkWidget *item;
00245     GtkWidget *details_label;
00246     GtkWidget *grid;
00247     GIcon *device_gicon;
00248     gchar *short_details = NULL;
00249     gchar *details = NULL;
00250     gchar *accessible_name = NULL;
00251     AtkObject *atk_object;
00252 
00253     /* Process the data */
00254     device_gicon = indicator_power_device_get_gicon (device);
00255     icon = gtk_image_new_from_gicon (device_gicon, GTK_ICON_SIZE_SMALL_TOOLBAR);
00256     g_clear_object (&device_gicon);
00257 
00258     indicator_power_device_get_time_details (device, &short_details, &details, &accessible_name);
00259 
00260     /* Create menu item */
00261     item = gtk_image_menu_item_new ();
00262     atk_object = gtk_widget_get_accessible(item);
00263     if (atk_object != NULL)
00264       atk_object_set_name (atk_object, accessible_name);
00265 
00266     grid = gtk_grid_new ();
00267     gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
00268     gtk_grid_attach (GTK_GRID (grid), icon, 0, 0, 1, 1);
00269     details_label = gtk_label_new (details);
00270     gtk_grid_attach_next_to (GTK_GRID (grid), details_label, icon, GTK_POS_RIGHT, 1, 1);
00271     gtk_container_add (GTK_CONTAINER (item), grid);
00272     gtk_widget_show (grid);
00273 
00274     gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
00275     added = TRUE;
00276 
00277     g_signal_connect_swapped (G_OBJECT (item), "activate",
00278                               G_CALLBACK (spawn_command_line_async), "gnome-power-statistics");
00279 
00280     g_free (short_details);
00281     g_free (details);
00282     g_free (accessible_name);
00283   }
00284 
00285   return added;
00286 }
00287 
00288 static gsize
00289 menu_add_devices (GtkMenu * menu, GSList * devices)
00290 {
00291   GSList * l;
00292   gsize n_added = 0;
00293 
00294   for (l=devices; l!=NULL; l=l->next)
00295     if (menu_add_device (menu, l->data))
00296       ++n_added;
00297 
00298   return n_added;
00299 }
00300 
00301 static gboolean
00302 get_greeter_mode (void)
00303 {
00304   const gchar *var;
00305   var = g_getenv("INDICATOR_GREETER_MODE");
00306   return (g_strcmp0(var, "1") == 0);
00307 }
00308 
00309 static void
00310 build_menu (IndicatorPower *self)
00311 {
00312   GtkWidget *item;
00313   GtkWidget *image;
00314   GList *children;
00315   gsize n_devices = 0;
00316   IndicatorPowerPrivate * priv = self->priv;
00317 
00318   /* remove the existing menuitems */
00319   children = gtk_container_get_children (GTK_CONTAINER (priv->menu));
00320   g_list_foreach (children, (GFunc) gtk_widget_destroy, NULL);
00321   g_list_free (children);
00322 
00323   /* devices */
00324   n_devices = menu_add_devices (priv->menu, priv->devices);
00325 
00326   if (!get_greeter_mode ()) {
00327     /* only do the separator if we have at least one device */
00328     if (n_devices != 0)
00329       {
00330         item = gtk_separator_menu_item_new ();
00331         gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), item);
00332       }
00333 
00334     /* options */
00335     item = gtk_check_menu_item_new_with_label (_("Show Time in Menu Bar"));
00336     g_signal_connect (item, "toggled", G_CALLBACK(option_toggled_cb), self);
00337     g_settings_bind (priv->settings, "show-time", item, "active", G_SETTINGS_BIND_DEFAULT);
00338     gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), item);
00339 
00340     /* preferences */
00341     item = gtk_image_menu_item_new_with_label (_("Power Settingsā€¦"));
00342     image = gtk_image_new_from_icon_name (GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU);
00343     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
00344     g_signal_connect_swapped (G_OBJECT (item), "activate",
00345                               G_CALLBACK (spawn_command_line_async), "gnome-control-center power");
00346     gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), item);
00347   }
00348 
00349   /* show the menu */
00350   gtk_widget_show_all (GTK_WIDGET (priv->menu));
00351 }
00352 
00353 static IndicatorPowerDevice*
00354 get_primary_device (GSList * devices)
00355 {
00356   IndicatorPowerDevice * primary_device = NULL;
00357   IndicatorPowerDevice * primary_device_charging = NULL;
00358   IndicatorPowerDevice * primary_device_discharging = NULL;
00359   gboolean charging = FALSE;
00360   gboolean discharging = FALSE;
00361   guint64 min_discharging_time = G_MAXUINT64;
00362   guint64 max_charging_time = 0;
00363   GSList * l;
00364 
00365   for (l=devices; l!=NULL; l=l->next)
00366     {
00367       IndicatorPowerDevice * device = INDICATOR_POWER_DEVICE(l->data);
00368       const UpDeviceKind kind = indicator_power_device_get_kind (device);
00369       const UpDeviceState state = indicator_power_device_get_state (device);
00370       const gdouble percentage  = indicator_power_device_get_percentage (device);
00371       const time_t time = indicator_power_device_get_time (device);
00372 
00373       /* Try to fix the case when we get a empty battery bay as a real battery */
00374       if (state == UP_DEVICE_STATE_UNKNOWN &&
00375           percentage == 0)
00376         continue;
00377 
00378       /* not battery */
00379       if (kind != UP_DEVICE_KIND_BATTERY)
00380         continue;
00381 
00382       if (state == UP_DEVICE_STATE_DISCHARGING)
00383         {
00384           discharging = TRUE;
00385           if (time < min_discharging_time)
00386             {
00387               min_discharging_time = time;
00388               primary_device_discharging = device;
00389             }
00390         }
00391       else if (state == UP_DEVICE_STATE_CHARGING)
00392         {
00393           charging = TRUE;
00394           if (time == 0) /* Battery broken */
00395             {
00396               primary_device_charging = device;
00397             }
00398           if (time > max_charging_time)
00399             {
00400               max_charging_time = time;
00401               primary_device_charging = device;
00402             }
00403         }
00404       else
00405         {
00406           primary_device = device;
00407         }
00408     }
00409 
00410   if (discharging)
00411     {
00412       primary_device = primary_device_discharging;
00413     }
00414   else if (charging)
00415     {
00416       primary_device = primary_device_charging;
00417     }
00418 
00419   if (primary_device != NULL)
00420     g_object_ref (primary_device);
00421 
00422   return primary_device;
00423 }
00424 
00425 static void
00426 put_primary_device (IndicatorPower *self, IndicatorPowerDevice *device)
00427 {
00428   IndicatorPowerPrivate * priv = self->priv;
00429 
00430   /* set icon */
00431   GIcon * device_gicon = indicator_power_device_get_gicon (device);
00432   gtk_image_set_from_gicon (priv->status_image, device_gicon, GTK_ICON_SIZE_LARGE_TOOLBAR);
00433   g_clear_object (&device_gicon);
00434   gtk_widget_show (GTK_WIDGET (priv->status_image));
00435 
00436   /* get the description */
00437   gchar * short_details;
00438   gchar * details;
00439   gchar * accessible_name;
00440   indicator_power_device_get_time_details (device, &short_details, &details, &accessible_name);
00441   gtk_label_set_label (GTK_LABEL (priv->label), short_details);
00442   set_accessible_desc (self, accessible_name);
00443   g_free (accessible_name);
00444   g_free (details);
00445   g_free (short_details);
00446 }
00447 
00448 void
00449 indicator_power_set_devices (IndicatorPower * self, GSList * devices)
00450 {
00451   /* LCOV_EXCL_START */
00452   g_return_if_fail (IS_INDICATOR_POWER(self));
00453   /* LCOV_EXCL_STOP */
00454 
00455   IndicatorPowerPrivate * priv = self->priv;
00456 
00457   /* update our devices & primary device */
00458   g_slist_foreach (devices, (GFunc)g_object_ref, NULL);
00459   dispose_devices (self);
00460   priv->devices = g_slist_copy (devices);
00461   priv->device = get_primary_device (priv->devices);
00462 
00463   /* and our menus/visibility from the new device list */
00464   if (priv->device != NULL)
00465       put_primary_device (self, priv->device);
00466   else
00467       g_message ("Couldn't find primary device");
00468   build_menu (self);
00469   update_visibility (self);
00470 }
00471 
00472 static void
00473 update_visibility (IndicatorPower * self)
00474 {
00475   indicator_object_set_visible (INDICATOR_OBJECT (self),
00476                                 should_be_visible (self));
00477 }
00478 
00479 
00480 /* Grabs the label. Creates it if it doesn't
00481    exist already */
00482 static GtkLabel *
00483 get_label (IndicatorObject *io)
00484 {
00485   IndicatorPower *self = INDICATOR_POWER (io);
00486   IndicatorPowerPrivate * priv = self->priv;
00487 
00488   if (priv->label == NULL)
00489     {
00490       /* Create the label if it doesn't exist already */
00491       priv->label = GTK_LABEL (gtk_label_new (""));
00492       gtk_widget_set_visible (GTK_WIDGET (priv->label), FALSE);
00493     }
00494 
00495   return priv->label;
00496 }
00497 
00498 static GtkImage *
00499 get_image (IndicatorObject *io)
00500 {
00501   GIcon *gicon;
00502   IndicatorPower *self = INDICATOR_POWER (io);
00503   IndicatorPowerPrivate * priv = self->priv;
00504 
00505   if (priv->status_image == NULL)
00506   {
00507     /* Will create the status icon if it doesn't exist already */
00508     gicon = g_themed_icon_new (DEFAULT_ICON);
00509     priv->status_image = GTK_IMAGE (gtk_image_new_from_gicon (gicon,
00510                                                               GTK_ICON_SIZE_LARGE_TOOLBAR));
00511   }
00512 
00513   return priv->status_image;
00514 }
00515 
00516 static GtkMenu *
00517 get_menu (IndicatorObject *io)
00518 {
00519   IndicatorPower *self = INDICATOR_POWER (io);
00520 
00521   build_menu (self);
00522 
00523   return GTK_MENU (self->priv->menu);
00524 }
00525 
00526 static const gchar *
00527 get_accessible_desc (IndicatorObject *io)
00528 {
00529   IndicatorPower *self = INDICATOR_POWER (io);
00530 
00531   return self->priv->accessible_desc;
00532 }
00533 
00534 static const gchar *
00535 get_name_hint (IndicatorObject *io)
00536 {
00537   return PACKAGE_NAME;
00538 }
00539 
00540 /***
00541 ****
00542 ***/
00543 
00544 static void
00545 count_batteries (GSList * devices, int *total, int *inuse)
00546 {
00547   GSList * l;
00548 
00549   for (l=devices; l!=NULL; l=l->next)
00550     {
00551       const IndicatorPowerDevice * device = INDICATOR_POWER_DEVICE(l->data);
00552 
00553       if (indicator_power_device_get_kind(device) == UP_DEVICE_KIND_BATTERY)
00554         {
00555           ++*total;
00556 
00557           const UpDeviceState state = indicator_power_device_get_state (device);
00558           if ((state == UP_DEVICE_STATE_CHARGING) || (state == UP_DEVICE_STATE_DISCHARGING))
00559             ++*inuse;
00560         }
00561     }
00562 
00563     g_debug("count_batteries found %d batteries (%d are charging/discharging)", *total, *inuse);
00564 }
00565 
00566 static gboolean
00567 should_be_visible (IndicatorPower * self)
00568 {
00569   gboolean visible = TRUE;
00570   IndicatorPowerPrivate * priv = self->priv;
00571 
00572   const int icon_policy = g_settings_get_enum (priv->settings, ICON_POLICY_KEY);
00573 
00574   g_debug ("icon_policy is: %d (present==0, charge==1, never==2)", icon_policy);
00575 
00576   if (icon_policy == POWER_INDICATOR_ICON_POLICY_NEVER)
00577     {
00578       visible = FALSE;
00579     }
00580     else
00581     {
00582       int batteries=0, inuse=0;
00583       count_batteries (priv->devices, &batteries, &inuse);
00584 
00585       if (icon_policy == POWER_INDICATOR_ICON_POLICY_PRESENT)
00586         {
00587           visible = batteries > 0;
00588         }
00589       else if (icon_policy == POWER_INDICATOR_ICON_POLICY_CHARGE)
00590         {
00591           visible = inuse > 0;
00592         }
00593     }
00594 
00595   g_debug ("should_be_visible: %s", visible?"yes":"no");
00596   return visible;
00597 }