2
* Copyright 2013 Canonical Ltd.
5
* Charles Kerr <charles.kerr@canonical.com>
6
* Ted Gould <ted@canonical.com>
8
* This program is free software: you can redistribute it and/or modify it
9
* under the terms of the GNU General Public License version 3, as published
10
* by the Free Software Foundation.
12
* This program is distributed in the hope that it will be useful, but
13
* WITHOUT ANY WARRANTY; without even the implied warranties of
14
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15
* PURPOSE. See the GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License along
18
* with this program. If not, see <http://www.gnu.org/licenses/>.
23
#include <glib/gi18n.h>
27
#include "device-provider.h"
30
#define BUS_NAME "com.canonical.indicator.power"
31
#define BUS_PATH "/com/canonical/indicator/power"
33
#define SETTINGS_SHOW_TIME_S "show-time"
34
#define SETTINGS_ICON_POLICY_S "icon-policy"
36
G_DEFINE_TYPE (IndicatorPowerService,
37
indicator_power_service,
46
static guint signals[LAST_SIGNAL] = { 0 };
55
static GParamSpec * properties[LAST_PROP];
59
SECTION_HEADER = (1<<0),
60
SECTION_DEVICES = (1<<1),
61
SECTION_SETTINGS = (1<<2),
71
static const char * const menu_names[N_PROFILES] =
79
POWER_INDICATOR_ICON_POLICY_PRESENT,
80
POWER_INDICATOR_ICON_POLICY_CHARGE,
81
POWER_INDICATOR_ICON_POLICY_NEVER
84
struct ProfileMenuInfo
86
/* the root level -- the header is the only child of this */
89
/* parent of the sections. This is the header's submenu */
95
struct _IndicatorPowerServicePrivate
97
GCancellable * cancellable;
102
guint actions_export_id;
103
GDBusConnection * conn;
105
struct ProfileMenuInfo menus[N_PROFILES];
107
GSimpleActionGroup * actions;
108
GSimpleAction * header_action;
109
GSimpleAction * show_time_action;
111
IndicatorPowerDevice * primary_device;
112
GList * devices; /* IndicatorPowerDevice */
114
IndicatorPowerDeviceProvider * device_provider;
117
typedef IndicatorPowerServicePrivate priv_t;
125
/* the higher the weight, the more interesting the device */
127
get_device_kind_weight (const IndicatorPowerDevice * device)
130
static gboolean initialized = FALSE;
131
static int weights[UP_DEVICE_KIND_LAST];
133
kind = indicator_power_device_get_kind (device);
134
g_return_val_if_fail (0<=kind && kind<UP_DEVICE_KIND_LAST, 0);
136
if (G_UNLIKELY(!initialized))
142
for (i=0; i<UP_DEVICE_KIND_LAST; i++)
144
weights[UP_DEVICE_KIND_BATTERY] = 2;
145
weights[UP_DEVICE_KIND_LINE_POWER] = 0;
148
return weights[kind];
151
/* sort devices from most interesting to least interesting on this criteria:
152
1. discharging items from least time remaining until most time remaining
153
2. discharging items with an unknown time remaining
154
3. charging items from most time left to charge to least time left to charge
155
4. charging items with an unknown time remaining
156
5. batteries, then non-line power, then line-power */
158
device_compare_func (gconstpointer ga, gconstpointer gb)
162
const IndicatorPowerDevice * a = ga;
163
const IndicatorPowerDevice * b = gb;
164
const int a_state = indicator_power_device_get_state (a);
165
const int b_state = indicator_power_device_get_state (b);
166
const gdouble a_percentage = indicator_power_device_get_percentage (a);
167
const gdouble b_percentage = indicator_power_device_get_percentage (b);
168
const time_t a_time = indicator_power_device_get_time (a);
169
const time_t b_time = indicator_power_device_get_time (b);
173
state = UP_DEVICE_STATE_DISCHARGING;
174
if (!ret && ((a_state == state) || (b_state == state)))
176
if (a_state != state) /* b is discharging */
180
else if (b_state != state) /* a is discharging */
184
else /* both are discharging; least-time-left goes first */
186
if (!a_time || !b_time) /* known time always trumps unknown time */
187
ret = a_time ? -1 : 1;
188
else if (a_time != b_time)
189
ret = a_time < b_time ? -1 : 1;
191
ret = a_percentage < b_percentage ? -1 : 1;
195
state = UP_DEVICE_STATE_CHARGING;
196
if (!ret && (((a_state == state) && a_time) ||
197
((b_state == state) && b_time)))
199
if (a_state != state) /* b is charging */
203
else if (b_state != state) /* a is charging */
207
else /* both are discharging; most-time-to-charge goes first */
209
if (!a_time || !b_time) /* known time always trumps unknown time */
210
ret = a_time ? -1 : 1;
211
else if (a_time != b_time)
212
ret = a_time > b_time ? -1 : 1;
214
ret = a_percentage < b_percentage ? -1 : 1;
218
if (!ret) /* neither device is charging nor discharging... */
220
const int weight_a = get_device_kind_weight (a);
221
const int weight_b = get_device_kind_weight (b);
223
if (weight_a > weight_b)
227
else if (weight_a < weight_b)
234
ret = a_state - b_state;
246
count_batteries (GList * devices, int *total, int *inuse)
250
for (l=devices; l!=NULL; l=l->next)
252
const IndicatorPowerDevice * device = INDICATOR_POWER_DEVICE(l->data);
254
if (indicator_power_device_get_kind(device) == UP_DEVICE_KIND_BATTERY ||
255
indicator_power_device_get_kind(device) == UP_DEVICE_KIND_UPS)
259
const UpDeviceState state = indicator_power_device_get_state (device);
260
if ((state == UP_DEVICE_STATE_CHARGING) ||
261
(state == UP_DEVICE_STATE_DISCHARGING))
266
g_debug ("count_batteries found %d batteries (%d are charging/discharging)",
271
should_be_visible (IndicatorPowerService * self)
273
gboolean visible = TRUE;
274
priv_t * p = self->priv;
276
const int policy = g_settings_get_enum (p->settings, SETTINGS_ICON_POLICY_S);
277
g_debug ("policy is: %d (present==0, charge==1, never==2)", policy);
279
if (policy == POWER_INDICATOR_ICON_POLICY_NEVER)
285
int batteries=0, inuse=0;
287
count_batteries (p->devices, &batteries, &inuse);
289
if (policy == POWER_INDICATOR_ICON_POLICY_PRESENT)
291
visible = batteries > 0;
293
else if (policy == POWER_INDICATOR_ICON_POLICY_CHARGE)
299
g_debug ("should_be_visible: %s", visible?"yes":"no");
304
create_header_state (IndicatorPowerService * self)
307
gchar * label = NULL;
310
priv_t * p = self->priv;
312
if (p->primary_device != NULL)
316
indicator_power_device_get_time_details (p->primary_device,
321
icon = indicator_power_device_get_gicon (p->primary_device);
326
g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}"));
328
g_variant_builder_add (&b, "{sv}", "visible",
329
g_variant_new_boolean (should_be_visible (self)));
333
if (g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S))
334
g_variant_builder_add (&b, "{sv}", "label",
335
g_variant_new_string (label));
342
g_variant_builder_add (&b, "{sv}", "icon", g_icon_serialize (icon));
344
g_object_unref (icon);
349
g_variant_builder_add (&b, "{sv}", "accessible-desc",
350
g_variant_new_string (a11y));
355
return g_variant_builder_end (&b);
366
append_device_to_menu (GMenu * menu, const IndicatorPowerDevice * device)
368
const UpDeviceKind kind = indicator_power_device_get_kind (device);
370
if (kind != UP_DEVICE_KIND_LINE_POWER)
375
GMenuItem * menu_item;
376
GIcon * icon = indicator_power_device_get_gicon (device);
378
indicator_power_device_get_time_details (device,
383
menu_item = g_menu_item_new (label, "indicator.activate-statistics");
387
g_menu_item_set_attribute_value (menu_item,
388
G_MENU_ATTRIBUTE_ICON,
389
g_icon_serialize (icon));
392
g_menu_append_item (menu, menu_item);
393
g_object_unref (menu_item);
395
g_clear_object (&icon);
404
create_devices_section (IndicatorPowerService * self)
407
GMenu * menu = g_menu_new ();
409
for (l=self->priv->devices; l!=NULL; l=l->next)
410
append_device_to_menu (menu, l->data);
412
return G_MENU_MODEL (menu);
418
**** SETTINGS SECTION
423
create_settings_section (IndicatorPowerService * self G_GNUC_UNUSED)
425
GMenu * menu = g_menu_new ();
428
_("Show Time in Menu Bar"),
429
"indicator.show-time");
432
_("Power Settings\342\200\246"),
433
"indicator.activate-settings");
435
return G_MENU_MODEL (menu);
440
**** SECTION REBUILDING
445
* A small helper function for rebuild_now().
446
* - removes the previous section
447
* - adds and unrefs the new section
450
rebuild_section (GMenu * parent, int pos, GMenuModel * new_section)
452
g_menu_remove (parent, pos);
453
g_menu_insert_section (parent, pos, NULL, new_section);
454
g_object_unref (new_section);
458
rebuild_now (IndicatorPowerService * self, guint sections)
460
priv_t * p = self->priv;
461
struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP];
462
struct ProfileMenuInfo * greeter = &p->menus[PROFILE_GREETER];
464
if (p->conn == NULL) /* we haven't built the menus yet */
467
if (sections & SECTION_HEADER)
469
g_simple_action_set_state (p->header_action, create_header_state (self));
472
if (sections & SECTION_DEVICES)
474
rebuild_section (desktop->submenu, 0, create_devices_section (self));
475
rebuild_section (greeter->submenu, 0, create_devices_section (self));
478
if (sections & SECTION_SETTINGS)
480
rebuild_section (desktop->submenu, 1, create_settings_section (self));
485
rebuild_header_now (IndicatorPowerService * self)
487
rebuild_now (self, SECTION_HEADER);
491
rebuild_devices_section_now (IndicatorPowerService * self)
493
rebuild_now (self, SECTION_DEVICES);
497
rebuild_settings_section_now (IndicatorPowerService * self)
499
rebuild_now (self, SECTION_SETTINGS);
503
create_menu (IndicatorPowerService * self, int profile)
508
GMenuModel * sections[16];
512
g_assert (0<=profile && profile<N_PROFILES);
513
g_assert (self->priv->menus[profile].menu == NULL);
515
if (profile == PROFILE_DESKTOP)
517
sections[n++] = create_devices_section (self);
518
sections[n++] = create_settings_section (self);
520
else if (profile == PROFILE_GREETER)
522
sections[n++] = create_devices_section (self);
525
/* add sections to the submenu */
527
submenu = g_menu_new ();
531
g_menu_append_section (submenu, NULL, sections[i]);
532
g_object_unref (sections[i]);
535
/* add submenu to the header */
536
header = g_menu_item_new (NULL, "indicator._header");
537
g_menu_item_set_attribute (header, "x-canonical-type",
538
"s", "com.canonical.indicator.root");
539
g_menu_item_set_submenu (header, G_MENU_MODEL (submenu));
540
g_object_unref (submenu);
542
/* add header to the menu */
543
menu = g_menu_new ();
544
g_menu_append_item (menu, header);
545
g_object_unref (header);
547
self->priv->menus[profile].menu = menu;
548
self->priv->menus[profile].submenu = submenu;
555
/* Run a particular program based on an activation */
557
execute_command (const gchar * cmd)
561
g_debug ("Issuing command '%s'", cmd);
563
if (!g_spawn_command_line_async (cmd, &err))
565
g_warning ("Unable to start %s: %s", cmd, err->message);
571
on_settings_activated (GSimpleAction * a G_GNUC_UNUSED,
572
GVariant * param G_GNUC_UNUSED,
573
gpointer gself G_GNUC_UNUSED)
575
execute_command ("gnome-control-center power");
579
on_statistics_activated (GSimpleAction * a G_GNUC_UNUSED,
580
GVariant * param G_GNUC_UNUSED,
581
gpointer gself G_GNUC_UNUSED)
583
execute_command ("gnome-power-statistics");
586
/* FIXME: use a GBinding to tie the gaction's state and the GSetting together? */
589
set_show_time_flag (IndicatorPowerService * self, gboolean b)
592
priv_t * p = self->priv;
594
/* update the settings */
595
if (b != g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S))
596
g_settings_set_boolean (p->settings, SETTINGS_SHOW_TIME_S, b);
598
/* update the action state */
599
v = g_action_get_state (G_ACTION(p->show_time_action));
600
if (b != g_variant_get_boolean (v))
601
g_simple_action_set_state (p->show_time_action, g_variant_new_boolean (b));
604
rebuild_header_now (self);
607
on_show_time_setting_changed (GSettings * settings, gchar * key, gpointer gself)
609
set_show_time_flag (INDICATOR_POWER_SERVICE(gself),
610
g_settings_get_boolean (settings, key));
614
on_show_time_action_state_changed (GAction * action,
615
GParamSpec * pspec G_GNUC_UNUSED,
618
GVariant * v = g_action_get_state (action);
619
set_show_time_flag (INDICATOR_POWER_SERVICE(gself),
620
g_variant_get_boolean (v));
624
/* toggles the state */
626
on_show_time_action_activated (GSimpleAction * simple,
627
GVariant * parameter G_GNUC_UNUSED,
628
gpointer unused G_GNUC_UNUSED)
630
GVariant * v = g_action_get_state (G_ACTION (simple));
631
gboolean flag = g_variant_get_boolean (v);
632
g_simple_action_set_state (simple, g_variant_new_boolean (!flag));
637
init_gactions (IndicatorPowerService * self)
641
priv_t * p = self->priv;
643
GActionEntry entries[] = {
644
{ "activate-settings", on_settings_activated },
645
{ "activate-statistics", on_statistics_activated }
648
p->actions = g_simple_action_group_new ();
650
g_action_map_add_action_entries (G_ACTION_MAP(p->actions),
652
G_N_ELEMENTS(entries),
655
/* add the header action */
656
a = g_simple_action_new_stateful ("_header", NULL, create_header_state (self));
657
g_simple_action_group_insert (p->actions, G_ACTION(a));
658
p->header_action = a;
660
/* add the show-time action */
661
show_time = g_settings_get_boolean (p->settings, SETTINGS_SHOW_TIME_S);
662
a = g_simple_action_new_stateful ("show-time",
664
g_variant_new_boolean(show_time));
665
g_signal_connect (a, "activate",
666
G_CALLBACK(on_show_time_action_activated), self);
667
g_signal_connect (a, "notify",
668
G_CALLBACK(on_show_time_action_state_changed), self);
669
g_simple_action_group_insert (p->actions, G_ACTION(a));
670
p->show_time_action = a;
672
rebuild_header_now (self);
676
**** GDBus Name Ownership & Menu / Action Exporting
680
on_bus_acquired (GDBusConnection * connection,
687
IndicatorPowerService * self = INDICATOR_POWER_SERVICE(gself);
688
priv_t * p = self->priv;
689
GString * path = g_string_new (NULL);
691
g_debug ("bus acquired: %s", name);
693
p->conn = g_object_ref (G_OBJECT (connection));
695
/* export the actions */
696
if ((id = g_dbus_connection_export_action_group (connection,
698
G_ACTION_GROUP (p->actions),
701
p->actions_export_id = id;
705
g_warning ("cannot export action group: %s", err->message);
706
g_clear_error (&err);
709
/* export the menus */
710
for (i=0; i<N_PROFILES; ++i)
712
struct ProfileMenuInfo * menu = &p->menus[i];
714
g_string_printf (path, "%s/%s", BUS_PATH, menu_names[i]);
716
if (menu->menu == NULL)
717
create_menu (self, i);
719
if ((id = g_dbus_connection_export_menu_model (connection,
721
G_MENU_MODEL (menu->menu),
724
menu->export_id = id;
728
g_warning ("cannot export %s menu: %s", path->str, err->message);
729
g_clear_error (&err);
733
g_string_free (path, TRUE);
737
unexport (IndicatorPowerService * self)
740
priv_t * p = self->priv;
742
/* unexport the menus */
743
for (i=0; i<N_PROFILES; ++i)
745
guint * id = &self->priv->menus[i].export_id;
749
g_dbus_connection_unexport_menu_model (p->conn, *id);
754
/* unexport the actions */
755
if (p->actions_export_id)
757
g_dbus_connection_unexport_action_group (p->conn, p->actions_export_id);
758
p->actions_export_id = 0;
763
on_name_lost (GDBusConnection * connection G_GNUC_UNUSED,
767
IndicatorPowerService * self = INDICATOR_POWER_SERVICE (gself);
769
g_debug ("%s %s name lost %s", G_STRLOC, G_STRFUNC, name);
773
g_signal_emit (self, signals[SIGNAL_NAME_LOST], 0, NULL);
781
on_devices_changed (IndicatorPowerService * self)
783
priv_t * p = self->priv;
785
/* update the device list */
786
g_list_free_full (p->devices, (GDestroyNotify)g_object_unref);
787
p->devices = indicator_power_device_provider_get_devices (p->device_provider);
789
/* update the primary device */
790
g_clear_object (&p->primary_device);
791
p->primary_device = indicator_power_service_choose_primary_device (p->devices);
793
rebuild_now (self, SECTION_HEADER | SECTION_DEVICES);
798
**** GObject virtual functions
802
my_get_property (GObject * o,
807
IndicatorPowerService * self = INDICATOR_POWER_SERVICE (o);
808
priv_t * p = self->priv;
812
case PROP_DEVICE_PROVIDER:
813
g_value_set_object (value, p->device_provider);
817
G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
822
my_set_property (GObject * o,
824
const GValue * value,
827
IndicatorPowerService * self = INDICATOR_POWER_SERVICE (o);
831
case PROP_DEVICE_PROVIDER:
832
indicator_power_service_set_device_provider (self, g_value_get_object (value));
836
G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
841
my_dispose (GObject * o)
843
IndicatorPowerService * self = INDICATOR_POWER_SERVICE(o);
844
priv_t * p = self->priv;
848
g_bus_unown_name (p->own_id);
854
if (p->cancellable != NULL)
856
g_cancellable_cancel (p->cancellable);
857
g_clear_object (&p->cancellable);
860
if (p->settings != NULL)
862
g_signal_handlers_disconnect_by_data (p->settings, self);
864
g_clear_object (&p->settings);
867
if (p->show_time_action != NULL)
869
g_signal_handlers_disconnect_by_data (p->show_time_action, self);
871
g_clear_object (&p->show_time_action);
874
g_clear_object (&p->header_action);
875
g_clear_object (&p->show_time_action);
876
g_clear_object (&p->actions);
878
g_clear_object (&p->conn);
880
indicator_power_service_set_device_provider (self, NULL);
882
G_OBJECT_CLASS (indicator_power_service_parent_class)->dispose (o);
890
indicator_power_service_init (IndicatorPowerService * self)
892
priv_t * p = G_TYPE_INSTANCE_GET_PRIVATE (self,
893
INDICATOR_TYPE_POWER_SERVICE,
894
IndicatorPowerServicePrivate);
897
p->cancellable = g_cancellable_new ();
899
p->settings = g_settings_new ("com.canonical.indicator.power");
901
init_gactions (self);
903
g_signal_connect_swapped (p->settings, "changed::" SETTINGS_ICON_POLICY_S,
904
G_CALLBACK(rebuild_header_now), self);
905
g_signal_connect (p->settings, "changed::" SETTINGS_SHOW_TIME_S,
906
G_CALLBACK(on_show_time_setting_changed), self);
908
p->own_id = g_bus_own_name (G_BUS_TYPE_SESSION,
910
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
919
indicator_power_service_class_init (IndicatorPowerServiceClass * klass)
921
GObjectClass * object_class = G_OBJECT_CLASS (klass);
923
object_class->dispose = my_dispose;
924
object_class->get_property = my_get_property;
925
object_class->set_property = my_set_property;
927
g_type_class_add_private (klass, sizeof (IndicatorPowerServicePrivate));
929
signals[SIGNAL_NAME_LOST] = g_signal_new (
930
INDICATOR_POWER_SERVICE_SIGNAL_NAME_LOST,
931
G_TYPE_FROM_CLASS(klass),
933
G_STRUCT_OFFSET (IndicatorPowerServiceClass, name_lost),
935
g_cclosure_marshal_VOID__VOID,
938
properties[PROP_0] = NULL;
940
properties[PROP_DEVICE_PROVIDER] = g_param_spec_object (
943
"Source for power devices",
945
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
947
g_object_class_install_properties (object_class, LAST_PROP, properties);
954
IndicatorPowerService *
955
indicator_power_service_new (IndicatorPowerDeviceProvider * device_provider)
957
GObject * o = g_object_new (INDICATOR_TYPE_POWER_SERVICE,
958
"device-provider", device_provider,
961
return INDICATOR_POWER_SERVICE (o);
965
indicator_power_service_set_device_provider (IndicatorPowerService * self,
966
IndicatorPowerDeviceProvider * dp)
970
g_return_if_fail (INDICATOR_IS_POWER_SERVICE (self));
971
g_return_if_fail (!dp || INDICATOR_IS_POWER_DEVICE_PROVIDER (dp));
974
if (p->device_provider != NULL)
976
g_signal_handlers_disconnect_by_func (p->device_provider,
977
G_CALLBACK(on_devices_changed),
979
g_clear_object (&p->device_provider);
981
g_clear_object (&p->primary_device);
983
g_list_free_full (p->devices, g_object_unref);
989
p->device_provider = g_object_ref (dp);
991
g_signal_connect_swapped (p->device_provider, "devices-changed",
992
G_CALLBACK(on_devices_changed), self);
994
on_devices_changed (self);
998
IndicatorPowerDevice *
999
indicator_power_service_choose_primary_device (GList * devices)
1001
IndicatorPowerDevice * primary = NULL;
1003
if (devices != NULL)
1007
tmp = g_list_copy (devices);
1008
tmp = g_list_sort (tmp, device_compare_func);
1009
primary = g_object_ref (tmp->data);