1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2  *
3  * Copyright (C) 2010 Red Hat, Inc
4  * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
5  * Copyright (C) 2010,2015 Richard Hughes <richard@hughsie.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include <config.h>
23 
24 #include <libupower-glib/upower.h>
25 #include <glib/gi18n.h>
26 #include <gnome-settings-daemon/gsd-enums.h>
27 #include <gio/gdesktopappinfo.h>
28 #include <handy.h>
29 
30 #include "shell/cc-object-storage.h"
31 #include "list-box-helper.h"
32 #include "cc-battery-row.h"
33 #include "cc-power-profile-row.h"
34 #include "cc-power-profile-info-row.h"
35 #include "cc-power-panel.h"
36 #include "cc-power-resources.h"
37 #include "cc-util.h"
38 
39 /* Uncomment this to test the behaviour of the panel in
40  * battery-less situations:
41  *
42  * #define TEST_NO_BATTERIES
43  */
44 
45 /* Uncomment this to test the behaviour of the panel with
46  * multiple appearing devices
47  *
48  * #define TEST_FAKE_DEVICES
49  */
50 
51 /* Uncomment this to test the behaviour of a desktop machine
52  * with a UPS
53  *
54  * #define TEST_UPS
55  */
56 
57 struct _CcPowerPanel
58 {
59   CcPanel            parent_instance;
60 
61   GtkListBoxRow     *als_row;
62   GtkSwitch         *als_switch;
63   GtkDialog         *automatic_suspend_dialog;
64   GtkLabel          *automatic_suspend_label;
65   GtkListBoxRow     *automatic_suspend_row;
66   GtkListBox        *battery_listbox;
67   HdyActionRow      *battery_percentage_row;
68   GtkSwitch         *battery_percentage_switch;
69   GtkSizeGroup      *battery_row_sizegroup;
70   HdyPreferencesGroup *battery_section;
71   HdyComboRow       *blank_screen_row;
72   GtkListBox        *device_listbox;
73   HdyPreferencesGroup *device_section;
74   GtkListBoxRow     *dim_screen_row;
75   GtkSwitch         *dim_screen_switch;
76   HdyPreferencesGroup *general_section;
77   GtkSizeGroup      *level_sizegroup;
78   HdyComboRow       *power_button_row;
79   GtkListBox        *power_profile_listbox;
80   GtkListBox        *power_profile_info_listbox;
81   HdyPreferencesGroup *power_profile_section;
82   HdyActionRow      *power_saver_low_battery_row;
83   GtkSwitch         *power_saver_low_battery_switch;
84   GtkSizeGroup      *row_sizegroup;
85   GtkComboBox       *suspend_on_battery_delay_combo;
86   GtkLabel          *suspend_on_battery_delay_label;
87   GtkLabel          *suspend_on_battery_label;
88   GtkSwitch         *suspend_on_battery_switch;
89   GtkComboBox       *suspend_on_ac_delay_combo;
90   GtkLabel          *suspend_on_ac_label;
91   GtkSwitch         *suspend_on_ac_switch;
92 
93   GSettings     *gsd_settings;
94   GSettings     *session_settings;
95   GSettings     *interface_settings;
96   UpClient      *up_client;
97   GPtrArray     *devices;
98   gboolean       has_batteries;
99   char          *chassis_type;
100 
101   GDBusProxy    *iio_proxy;
102   guint          iio_proxy_watch_id;
103   gboolean       has_brightness;
104 
105   GDBusProxy    *power_profiles_proxy;
106   guint          power_profiles_prop_id;
107   CcPowerProfileRow *power_profiles_row[NUM_CC_POWER_PROFILES];
108   gboolean       power_profiles_in_update;
109   gboolean       has_performance_degraded;
110 };
111 
112 CC_PANEL_REGISTER (CcPowerPanel, cc_power_panel)
113 
114 enum
115 {
116   ACTION_MODEL_TEXT,
117   ACTION_MODEL_VALUE
118 };
119 
120 static void
cc_power_panel_dispose(GObject * object)121 cc_power_panel_dispose (GObject *object)
122 {
123   CcPowerPanel *self = CC_POWER_PANEL (object);
124 
125   g_clear_pointer (&self->chassis_type, g_free);
126   g_clear_object (&self->gsd_settings);
127   g_clear_object (&self->session_settings);
128   g_clear_object (&self->interface_settings);
129   g_clear_pointer ((GtkWidget **) &self->automatic_suspend_dialog, gtk_widget_destroy);
130   g_clear_pointer (&self->devices, g_ptr_array_unref);
131   g_clear_object (&self->up_client);
132   g_clear_object (&self->iio_proxy);
133   g_clear_object (&self->power_profiles_proxy);
134   if (self->iio_proxy_watch_id != 0)
135     g_bus_unwatch_name (self->iio_proxy_watch_id);
136   self->iio_proxy_watch_id = 0;
137 
138   G_OBJECT_CLASS (cc_power_panel_parent_class)->dispose (object);
139 }
140 
141 static const char *
cc_power_panel_get_help_uri(CcPanel * panel)142 cc_power_panel_get_help_uri (CcPanel *panel)
143 {
144   return "help:gnome-help/power";
145 }
146 
147 static char *
get_chassis_type(GCancellable * cancellable)148 get_chassis_type (GCancellable *cancellable)
149 {
150   g_autoptr(GError) error = NULL;
151   g_autoptr(GVariant) inner = NULL;
152   g_autoptr(GVariant) variant = NULL;
153   g_autoptr(GDBusConnection) connection = NULL;
154 
155   connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
156                                cancellable,
157                                &error);
158   if (!connection)
159     {
160       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
161         g_warning ("system bus not available: %s", error->message);
162       return NULL;
163     }
164 
165   variant = g_dbus_connection_call_sync (connection,
166                                          "org.freedesktop.hostname1",
167                                          "/org/freedesktop/hostname1",
168                                          "org.freedesktop.DBus.Properties",
169                                          "Get",
170                                          g_variant_new ("(ss)",
171                                                         "org.freedesktop.hostname1",
172                                                         "Chassis"),
173                                          NULL,
174                                          G_DBUS_CALL_FLAGS_NONE,
175                                          -1,
176                                          cancellable,
177                                          &error);
178   if (!variant)
179     {
180       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
181         g_debug ("Failed to get property '%s': %s", "Chassis", error->message);
182       return NULL;
183     }
184 
185   g_variant_get (variant, "(v)", &inner);
186   return g_variant_dup_string (inner, NULL);
187 }
188 
189 static void
load_custom_css(CcPowerPanel * self,const char * path)190 load_custom_css (CcPowerPanel *self,
191                  const char   *path)
192 {
193   g_autoptr(GtkCssProvider) provider = NULL;
194 
195   /* use custom CSS */
196   provider = gtk_css_provider_new ();
197   gtk_css_provider_load_from_resource (provider, path);
198   gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
199                                              GTK_STYLE_PROVIDER (provider),
200                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
201 }
202 
203 static void
add_battery(CcPowerPanel * panel,UpDevice * device,gboolean primary)204 add_battery (CcPowerPanel *panel, UpDevice *device, gboolean primary)
205 {
206   CcBatteryRow *row = cc_battery_row_new (device, primary);
207   cc_battery_row_set_level_sizegroup (row, panel->level_sizegroup);
208   cc_battery_row_set_row_sizegroup (row, panel->battery_row_sizegroup);
209 
210   gtk_container_add (GTK_CONTAINER (panel->battery_listbox), GTK_WIDGET (row));
211   gtk_widget_set_visible (GTK_WIDGET (panel->battery_section), TRUE);
212 }
213 
214 static void
add_device(CcPowerPanel * self,UpDevice * device)215 add_device (CcPowerPanel *self, UpDevice *device)
216 {
217   CcBatteryRow *row = cc_battery_row_new (device, FALSE);
218   cc_battery_row_set_level_sizegroup (row, self->level_sizegroup);
219   cc_battery_row_set_row_sizegroup (row, self->row_sizegroup);
220 
221   gtk_container_add (GTK_CONTAINER (self->device_listbox), GTK_WIDGET (row));
222   gtk_widget_set_visible (GTK_WIDGET (self->device_section), TRUE);
223 }
224 
225 static void
empty_listbox(GtkListBox * listbox)226 empty_listbox (GtkListBox *listbox)
227 {
228   g_autoptr(GList) children = NULL;
229   GList *l;
230 
231   children = gtk_container_get_children (GTK_CONTAINER (listbox));
232   for (l = children; l != NULL; l = l->next)
233     gtk_container_remove (GTK_CONTAINER (listbox), l->data);
234 }
235 
236 static void
update_power_saver_low_battery_row_visibility(CcPowerPanel * self)237 update_power_saver_low_battery_row_visibility (CcPowerPanel *self)
238 {
239   g_autoptr(UpDevice) composite = NULL;
240   UpDeviceKind kind;
241 
242   composite = up_client_get_display_device (self->up_client);
243   g_object_get (composite, "kind", &kind, NULL);
244   gtk_widget_set_visible (GTK_WIDGET (self->power_saver_low_battery_row),
245                           self->power_profiles_proxy && kind == UP_DEVICE_KIND_BATTERY);
246 }
247 
248 static void
up_client_changed(CcPowerPanel * self)249 up_client_changed (CcPowerPanel *self)
250 {
251   gint i;
252   UpDeviceKind kind;
253   guint n_batteries;
254   gboolean on_ups;
255   g_autoptr(UpDevice) composite = NULL;
256 
257   empty_listbox (self->battery_listbox);
258   gtk_widget_hide (GTK_WIDGET (self->battery_section));
259 
260   empty_listbox (self->device_listbox);
261   gtk_widget_hide (GTK_WIDGET (self->device_section));
262 
263 #ifdef TEST_FAKE_DEVICES
264   {
265     static gboolean fake_devices_added = FALSE;
266     UpDevice *device;
267 
268     if (!fake_devices_added)
269       {
270         fake_devices_added = TRUE;
271         g_print ("adding fake devices\n");
272         device = up_device_new ();
273         g_object_set (device,
274                       "kind", UP_DEVICE_KIND_MOUSE,
275                       "native-path", "dummy:native-path1",
276                       "model", "My mouse",
277                       "percentage", 71.0,
278                       "state", UP_DEVICE_STATE_DISCHARGING,
279                       "time-to-empty", 287,
280                       "icon-name", "battery-full-symbolic",
281                       "power-supply", FALSE,
282                       "is-present", TRUE,
283                       "battery-level", UP_DEVICE_LEVEL_NORMAL,
284                       NULL);
285         g_ptr_array_add (self->devices, device);
286         device = up_device_new ();
287         g_object_set (device,
288                       "kind", UP_DEVICE_KIND_KEYBOARD,
289                       "native-path", "dummy:native-path2",
290                       "model", "My keyboard",
291                       "percentage", 59.0,
292                       "state", UP_DEVICE_STATE_DISCHARGING,
293                       "time-to-empty", 250,
294                       "icon-name", "battery-good-symbolic",
295                       "power-supply", FALSE,
296                       "is-present", TRUE,
297                       "battery-level", UP_DEVICE_LEVEL_NONE,
298                       NULL);
299         g_ptr_array_add (self->devices, device);
300         device = up_device_new ();
301         g_object_set (device,
302                       "kind", UP_DEVICE_KIND_BATTERY,
303                       "native-path", "dummy:native-path3",
304                       "model", "Battery from some factory",
305                       "percentage", 100.0,
306                       "state", UP_DEVICE_STATE_FULLY_CHARGED,
307                       "energy", 55.0,
308                       "energy-full", 55.0,
309                       "energy-rate", 15.0,
310                       "time-to-empty", 400,
311                       "time-to-full", 0,
312                       "icon-name", "battery-full-charged-symbolic",
313                       "power-supply", TRUE,
314                       "is-present", TRUE,
315                       "battery-level", UP_DEVICE_LEVEL_NONE,
316                       NULL);
317         g_ptr_array_add (self->devices, device);
318       }
319   }
320 #endif
321 
322 #ifdef TEST_UPS
323   {
324     static gboolean fake_devices_added = FALSE;
325     UpDevice *device;
326 
327     if (!fake_devices_added)
328       {
329         fake_devices_added = TRUE;
330         g_print ("adding fake UPS\n");
331         device = up_device_new ();
332         g_object_set (device,
333                       "kind", UP_DEVICE_KIND_UPS,
334                       "native-path", "dummy:usb-hiddev0",
335                       "model", "APC UPS",
336                       "percentage", 70.0,
337                       "state", UP_DEVICE_STATE_DISCHARGING,
338                       "is-present", TRUE,
339                       "power-supply", TRUE,
340                       "battery-level", UP_DEVICE_LEVEL_NONE,
341                       NULL);
342         g_ptr_array_add (self->devices, device);
343       }
344   }
345 #endif
346 
347   on_ups = FALSE;
348   n_batteries = 0;
349   composite = up_client_get_display_device (self->up_client);
350   g_object_get (composite, "kind", &kind, NULL);
351   if (kind == UP_DEVICE_KIND_UPS)
352     {
353       on_ups = TRUE;
354     }
355   else
356     {
357       gboolean is_extra_battery = FALSE;
358 
359       /* Count the batteries */
360       for (i = 0; self->devices != NULL && i < self->devices->len; i++)
361         {
362           UpDevice *device = (UpDevice*) g_ptr_array_index (self->devices, i);
363           gboolean is_power_supply = FALSE;
364           g_object_get (device,
365                         "kind", &kind,
366                         "power-supply", &is_power_supply,
367                         NULL);
368           if (kind == UP_DEVICE_KIND_BATTERY &&
369               is_power_supply)
370             {
371               n_batteries++;
372               if (is_extra_battery == FALSE)
373                 {
374                   is_extra_battery = TRUE;
375                   g_object_set_data (G_OBJECT (device), "is-main-battery", GINT_TO_POINTER(TRUE));
376                 }
377             }
378         }
379     }
380 
381   if (n_batteries > 1)
382     hdy_preferences_group_set_title (self->battery_section, _("Batteries"));
383   else
384     hdy_preferences_group_set_title (self->battery_section, _("Battery"));
385 
386   if (!on_ups && n_batteries > 1)
387     add_battery (self, composite, TRUE);
388 
389   for (i = 0; self->devices != NULL && i < self->devices->len; i++)
390     {
391       UpDevice *device = (UpDevice*) g_ptr_array_index (self->devices, i);
392       gboolean is_power_supply = FALSE;
393       g_object_get (device,
394                     "kind", &kind,
395                     "power-supply", &is_power_supply,
396                     NULL);
397       if (kind == UP_DEVICE_KIND_LINE_POWER)
398         {
399           /* do nothing */
400         }
401       else if (kind == UP_DEVICE_KIND_UPS && on_ups)
402         {
403           add_battery (self, device, TRUE);
404         }
405       else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply && !on_ups && n_batteries == 1)
406         {
407           add_battery (self, device, TRUE);
408         }
409       else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply)
410         {
411           add_battery (self, device, FALSE);
412         }
413       else
414         {
415           add_device (self, device);
416         }
417     }
418 
419   update_power_saver_low_battery_row_visibility (self);
420 }
421 
422 static void
up_client_device_removed(CcPowerPanel * self,const char * object_path)423 up_client_device_removed (CcPowerPanel *self,
424                           const char   *object_path)
425 {
426   guint i;
427 
428   if (self->devices == NULL)
429     return;
430 
431   for (i = 0; i < self->devices->len; i++)
432     {
433       UpDevice *device = g_ptr_array_index (self->devices, i);
434 
435       if (g_strcmp0 (object_path, up_device_get_object_path (device)) == 0)
436         {
437           g_ptr_array_remove_index (self->devices, i);
438           break;
439         }
440     }
441 
442   up_client_changed (self);
443 }
444 
445 static void
up_client_device_added(CcPowerPanel * self,UpDevice * device)446 up_client_device_added (CcPowerPanel *self,
447                         UpDevice     *device)
448 {
449   g_ptr_array_add (self->devices, g_object_ref (device));
450   g_signal_connect_object (G_OBJECT (device), "notify",
451                            G_CALLBACK (up_client_changed), self, G_CONNECT_SWAPPED);
452   up_client_changed (self);
453 }
454 
455 static void
als_switch_changed_cb(CcPowerPanel * self)456 als_switch_changed_cb (CcPowerPanel *self)
457 {
458   gboolean enabled;
459   enabled = gtk_switch_get_active (self->als_switch);
460   g_debug ("Setting ALS enabled %s", enabled ? "on" : "off");
461   g_settings_set_boolean (self->gsd_settings, "ambient-enabled", enabled);
462 }
463 
464 static void
als_enabled_state_changed(CcPowerPanel * self)465 als_enabled_state_changed (CcPowerPanel *self)
466 {
467   gboolean enabled;
468   gboolean visible = FALSE;
469 
470   if (self->iio_proxy != NULL)
471     {
472       g_autoptr(GVariant) v = g_dbus_proxy_get_cached_property (self->iio_proxy, "HasAmbientLight");
473       if (v != NULL)
474         visible = g_variant_get_boolean (v);
475     }
476 
477   enabled = g_settings_get_boolean (self->gsd_settings, "ambient-enabled");
478   g_debug ("ALS enabled: %s", enabled ? "on" : "off");
479   g_signal_handlers_block_by_func (self->als_switch, als_switch_changed_cb, self);
480   gtk_switch_set_active (self->als_switch, enabled);
481   gtk_widget_set_visible (GTK_WIDGET (self->als_row), visible && self->has_brightness);
482   g_signal_handlers_unblock_by_func (self->als_switch, als_switch_changed_cb, self);
483 }
484 
485 static void
combo_time_changed_cb(CcPowerPanel * self,GtkWidget * widget)486 combo_time_changed_cb (CcPowerPanel *self, GtkWidget *widget)
487 {
488   GtkTreeIter iter;
489   GtkTreeModel *model;
490   gint value;
491   gboolean ret;
492   const gchar *key = (const gchar *)g_object_get_data (G_OBJECT(widget), "_gsettings_key");
493 
494   /* no selection */
495   ret = gtk_combo_box_get_active_iter (GTK_COMBO_BOX(widget), &iter);
496   if (!ret)
497     return;
498 
499   /* get entry */
500   model = gtk_combo_box_get_model (GTK_COMBO_BOX(widget));
501   gtk_tree_model_get (model, &iter,
502                       1, &value,
503                       -1);
504 
505   /* set both keys */
506   g_settings_set_int (self->gsd_settings, key, value);
507 }
508 
509 static void
set_value_for_combo(GtkComboBox * combo_box,gint value)510 set_value_for_combo (GtkComboBox *combo_box, gint value)
511 {
512   GtkTreeIter iter;
513   g_autoptr(GtkTreeIter) insert = NULL;
514   GtkTreeIter new;
515   GtkTreeModel *model;
516   gint value_tmp;
517   gint value_last = 0;
518   g_autofree gchar *text = NULL;
519   gboolean ret;
520 
521   /* get entry */
522   model = gtk_combo_box_get_model (combo_box);
523   ret = gtk_tree_model_get_iter_first (model, &iter);
524   if (!ret)
525     return;
526 
527   /* try to make the UI match the setting */
528   do
529     {
530       gtk_tree_model_get (model, &iter,
531                           ACTION_MODEL_VALUE, &value_tmp,
532                           -1);
533       if (value_tmp == value)
534         {
535           gtk_combo_box_set_active_iter (combo_box, &iter);
536           return;
537         }
538 
539       /* Insert before if the next value is larger or the value is lower
540        * again (i.e. "Never" is zero and last). */
541       if (!insert && (value_tmp > value || value_last > value_tmp))
542         insert = gtk_tree_iter_copy (&iter);
543 
544       value_last = value_tmp;
545     } while (gtk_tree_model_iter_next (model, &iter));
546 
547   /* The value is not listed, so add it at the best point (or the end). */
548   gtk_list_store_insert_before (GTK_LIST_STORE (model), &new, insert);
549 
550   text = cc_util_time_to_string_text (value * 1000);
551   gtk_list_store_set (GTK_LIST_STORE (model), &new,
552                       ACTION_MODEL_TEXT, text,
553                       ACTION_MODEL_VALUE, value,
554                       -1);
555   gtk_combo_box_set_active_iter (combo_box, &new);
556 }
557 
558 static void
set_value_for_combo_row(HdyComboRow * combo_row,gint value)559 set_value_for_combo_row (HdyComboRow *combo_row, gint value)
560 {
561   gboolean insert = FALSE;
562   guint insert_before = 0;
563   guint i;
564   HdyValueObject *new;
565   GListModel *model;
566   gint value_last = 0;
567   g_autofree gchar *text = NULL;
568 
569   /* try to make the UI match the setting */
570   model = hdy_combo_row_get_model (combo_row);
571   for (i = 0; i < g_list_model_get_n_items (model); i++)
572     {
573       HdyValueObject *value_object = g_list_model_get_item (model, i);
574       gint value_tmp = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value"));
575       if (value_tmp == value)
576         {
577           hdy_combo_row_set_selected_index (combo_row, i);
578           return;
579         }
580 
581       /* Insert before if the next value is larger or the value is lower
582        * again (i.e. "Never" is zero and last). */
583       if (!insert && (value_tmp > value || value_last > value_tmp))
584         {
585           insert = TRUE;
586           insert_before = i;
587         }
588 
589       value_last = value_tmp;
590     }
591 
592   /* The value is not listed, so add it at the best point (or the end). */
593   text = cc_util_time_to_string_text (value * 1000);
594   new = hdy_value_object_new_string (text);
595   g_object_set_data (G_OBJECT (new), "value",
596                      GUINT_TO_POINTER (value));
597   g_list_store_insert (G_LIST_STORE (model), insert_before, new);
598   hdy_combo_row_set_selected_index (combo_row, insert_before);
599 }
600 
601 static void
set_ac_battery_ui_mode(CcPowerPanel * self)602 set_ac_battery_ui_mode (CcPowerPanel *self)
603 {
604   gboolean has_batteries = FALSE;
605   GPtrArray *devices;
606   guint i;
607 
608   devices = up_client_get_devices2 (self->up_client);
609   g_debug ("got %d devices from upower\n", devices ? devices->len : 0);
610 
611   for (i = 0; devices != NULL && i < devices->len; i++)
612     {
613       UpDevice *device;
614       gboolean is_power_supply;
615       UpDeviceKind kind;
616 
617       device = g_ptr_array_index (devices, i);
618       g_object_get (device,
619                     "kind", &kind,
620                     "power-supply", &is_power_supply,
621                     NULL);
622       if (kind == UP_DEVICE_KIND_UPS ||
623           (kind == UP_DEVICE_KIND_BATTERY && is_power_supply))
624         {
625           has_batteries = TRUE;
626           break;
627         }
628     }
629   g_clear_pointer (&devices, g_ptr_array_unref);
630 
631 #ifdef TEST_NO_BATTERIES
632   g_print ("forcing no batteries\n");
633   has_batteries = FALSE;
634 #endif
635 
636   self->has_batteries = has_batteries;
637 
638   if (!has_batteries)
639     {
640       gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_switch));
641       gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_label));
642       gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_delay_label));
643       gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_delay_combo));
644       gtk_label_set_label (self->suspend_on_ac_label, _("When _idle"));
645     }
646 }
647 
648 static gboolean
keynav_failed_cb(CcPowerPanel * self,GtkDirectionType direction,GtkWidget * list)649 keynav_failed_cb (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *list)
650 {
651   if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
652     return FALSE;
653 
654   direction = GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
655 
656   return gtk_widget_child_focus (GTK_WIDGET (self), direction);
657 }
658 
659 static void
blank_screen_row_changed_cb(CcPowerPanel * self)660 blank_screen_row_changed_cb (CcPowerPanel *self)
661 {
662   GListModel *model;
663   gint selected_index;
664   HdyValueObject *value_object;
665   gint value;
666 
667   model = hdy_combo_row_get_model (self->blank_screen_row);
668   selected_index = hdy_combo_row_get_selected_index (self->blank_screen_row);
669   value_object = g_list_model_get_item (model, selected_index);
670   value = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value"));
671 
672   g_settings_set_uint (self->session_settings, "idle-delay", value);
673 }
674 
675 static void
power_button_row_changed_cb(CcPowerPanel * self)676 power_button_row_changed_cb (CcPowerPanel *self)
677 {
678   GListModel *model;
679   gint selected_index;
680   HdyValueObject *value_object;
681   gint value;
682 
683   model = hdy_combo_row_get_model (self->power_button_row);
684   selected_index = hdy_combo_row_get_selected_index (self->power_button_row);
685   value_object = g_list_model_get_item (model, selected_index);
686   value = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (value_object), "value"));
687 
688   g_settings_set_enum (self->gsd_settings, "power-button-action", value);
689 }
690 
691 static void
als_enabled_setting_changed(CcPowerPanel * self)692 als_enabled_setting_changed (CcPowerPanel *self)
693 {
694   als_enabled_state_changed (self);
695 }
696 
697 static void
iio_proxy_appeared_cb(GDBusConnection * connection,const gchar * name,const gchar * name_owner,gpointer user_data)698 iio_proxy_appeared_cb (GDBusConnection *connection,
699                        const gchar *name,
700                        const gchar *name_owner,
701                        gpointer user_data)
702 {
703   CcPowerPanel *self = CC_POWER_PANEL (user_data);
704   g_autoptr(GError) error = NULL;
705 
706   self->iio_proxy =
707     cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM,
708                                               G_DBUS_PROXY_FLAGS_NONE,
709                                               "net.hadess.SensorProxy",
710                                               "/net/hadess/SensorProxy",
711                                               "net.hadess.SensorProxy",
712                                               NULL, &error);
713   if (error != NULL)
714     {
715       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
716         g_warning ("Could not create IIO sensor proxy: %s", error->message);
717       return;
718     }
719 
720   g_signal_connect_object (G_OBJECT (self->iio_proxy), "g-properties-changed",
721                            G_CALLBACK (als_enabled_state_changed), self,
722                            G_CONNECT_SWAPPED);
723   als_enabled_state_changed (self);
724 }
725 
726 static void
iio_proxy_vanished_cb(GDBusConnection * connection,const gchar * name,gpointer user_data)727 iio_proxy_vanished_cb (GDBusConnection *connection,
728                        const gchar *name,
729                        gpointer user_data)
730 {
731   CcPowerPanel *self = CC_POWER_PANEL (user_data);
732   g_clear_object (&self->iio_proxy);
733   als_enabled_state_changed (self);
734 }
735 
736 static void
automatic_suspend_row_activated_cb(CcPowerPanel * self)737 automatic_suspend_row_activated_cb (CcPowerPanel *self)
738 {
739   GtkWidget *toplevel;
740 
741   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
742   gtk_window_set_transient_for (GTK_WINDOW (self->automatic_suspend_dialog), GTK_WINDOW (toplevel));
743   gtk_window_set_modal (GTK_WINDOW (self->automatic_suspend_dialog), TRUE);
744   gtk_window_present (GTK_WINDOW (self->automatic_suspend_dialog));
745 }
746 
747 static gboolean
automatic_suspend_label_mnemonic_activate_cb(CcPowerPanel * self)748 automatic_suspend_label_mnemonic_activate_cb (CcPowerPanel *self)
749 {
750   automatic_suspend_row_activated_cb (self);
751   return TRUE;
752 }
753 
754 static gboolean
get_sleep_type(GValue * value,GVariant * variant,gpointer data)755 get_sleep_type (GValue   *value,
756                 GVariant *variant,
757                 gpointer  data)
758 {
759   gboolean enabled;
760 
761   if (g_strcmp0 (g_variant_get_string (variant, NULL), "nothing") == 0)
762     enabled = FALSE;
763   else
764     enabled = TRUE;
765 
766   g_value_set_boolean (value, enabled);
767 
768   return TRUE;
769 }
770 
771 static GVariant *
set_sleep_type(const GValue * value,const GVariantType * expected_type,gpointer data)772 set_sleep_type (const GValue       *value,
773                 const GVariantType *expected_type,
774                 gpointer            data)
775 {
776   GVariant *res;
777 
778   if (g_value_get_boolean (value))
779     res = g_variant_new_string ("suspend");
780   else
781     res = g_variant_new_string ("nothing");
782 
783   return res;
784 }
785 
786 static void
populate_power_button_row(HdyComboRow * combo_row,gboolean can_suspend,gboolean can_hibernate)787 populate_power_button_row (HdyComboRow *combo_row,
788                            gboolean     can_suspend,
789                            gboolean     can_hibernate)
790 {
791   g_autoptr (GListStore) list_store = NULL;
792   struct {
793     char *name;
794     GsdPowerButtonActionType value;
795   } actions[] = {
796     { N_("Suspend"), GSD_POWER_BUTTON_ACTION_SUSPEND },
797     { N_("Power Off"), GSD_POWER_BUTTON_ACTION_INTERACTIVE },
798     { N_("Hibernate"), GSD_POWER_BUTTON_ACTION_HIBERNATE },
799     { N_("Nothing"), GSD_POWER_BUTTON_ACTION_NOTHING }
800   };
801   guint i;
802 
803   list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
804   for (i = 0; i < G_N_ELEMENTS (actions); i++)
805     {
806       g_autoptr (HdyValueObject) value_object = NULL;
807 
808       if (!can_suspend && actions[i].value == GSD_POWER_BUTTON_ACTION_SUSPEND)
809         continue;
810 
811       if (!can_hibernate && actions[i].value == GSD_POWER_BUTTON_ACTION_HIBERNATE)
812         continue;
813 
814       value_object = hdy_value_object_new_string (_(actions[i].name));
815       g_object_set_data (G_OBJECT (value_object),
816                          "value",
817                          GUINT_TO_POINTER (actions[i].value));
818       g_list_store_append (list_store, value_object);
819     }
820 
821   hdy_combo_row_bind_name_model (combo_row,
822                                  G_LIST_MODEL (list_store),
823                                  (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
824                                  NULL, NULL);
825 }
826 
827 #define NEVER 0
828 
829 static void
update_automatic_suspend_label(CcPowerPanel * self)830 update_automatic_suspend_label (CcPowerPanel *self)
831 {
832   GsdPowerActionType ac_action;
833   GsdPowerActionType battery_action;
834   gint ac_timeout;
835   gint battery_timeout;
836   const gchar *s;
837 
838   ac_action = g_settings_get_enum (self->gsd_settings, "sleep-inactive-ac-type");
839   battery_action = g_settings_get_enum (self->gsd_settings, "sleep-inactive-battery-type");
840   ac_timeout = g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout");
841   battery_timeout = g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout");
842 
843   if (ac_timeout < 0)
844     g_warning ("Invalid negative timeout for 'sleep-inactive-ac-timeout': %d", ac_timeout);
845   if (battery_timeout < 0)
846     g_warning ("Invalid negative timeout for 'sleep-inactive-battery-timeout': %d", battery_timeout);
847 
848   if (ac_action == GSD_POWER_ACTION_NOTHING || ac_timeout < 0)
849     ac_timeout = NEVER;
850   if (battery_action == GSD_POWER_ACTION_NOTHING || battery_timeout < 0)
851     battery_timeout = NEVER;
852 
853   if (self->has_batteries)
854     {
855       if (ac_timeout == NEVER && battery_timeout == NEVER)
856         s = _("Off");
857       else if (ac_timeout == NEVER && battery_timeout > 0)
858         s = _("When on battery power");
859       else if (ac_timeout > 0 && battery_timeout == NEVER)
860         s = _("When plugged in");
861       else
862         s = _("On");
863     }
864   else
865     {
866       if (ac_timeout == NEVER)
867         s = _("Off");
868       else
869         s = _("On");
870     }
871 
872   if (self->automatic_suspend_label)
873     gtk_label_set_label (self->automatic_suspend_label, s);
874 }
875 
876 static void
on_suspend_settings_changed(CcPowerPanel * self,const char * key)877 on_suspend_settings_changed (CcPowerPanel *self,
878                              const char   *key)
879 {
880   if (g_str_has_prefix (key, "sleep-inactive-"))
881     {
882       update_automatic_suspend_label (self);
883     }
884 }
885 
886 static gboolean
can_suspend_or_hibernate(CcPowerPanel * self,const char * method_name)887 can_suspend_or_hibernate (CcPowerPanel *self,
888                           const char   *method_name)
889 {
890   g_autoptr(GDBusConnection) connection = NULL;
891   g_autoptr(GVariant) variant = NULL;
892   g_autoptr(GError) error = NULL;
893   const char *s;
894 
895   connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
896                                cc_panel_get_cancellable (CC_PANEL (self)),
897                                &error);
898   if (!connection)
899     {
900       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
901         g_warning ("system bus not available: %s", error->message);
902       return FALSE;
903     }
904 
905   variant = g_dbus_connection_call_sync (connection,
906                                          "org.freedesktop.login1",
907                                          "/org/freedesktop/login1",
908                                          "org.freedesktop.login1.Manager",
909                                          method_name,
910                                          NULL,
911                                          NULL,
912                                          G_DBUS_CALL_FLAGS_NONE,
913                                          -1,
914                                          cc_panel_get_cancellable (CC_PANEL (self)),
915                                          &error);
916 
917   if (!variant)
918     {
919       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
920         g_debug ("Failed to call %s(): %s", method_name, error->message);
921       return FALSE;
922     }
923 
924   g_variant_get (variant, "(&s)", &s);
925   return g_strcmp0 (s, "yes") == 0;
926 }
927 
928 static void
got_brightness_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)929 got_brightness_cb (GObject      *source_object,
930                    GAsyncResult *res,
931                    gpointer      user_data)
932 {
933   g_autoptr(GVariant) result = NULL;
934   g_autoptr(GError) error = NULL;
935   gint32 brightness = -1.0;
936   CcPowerPanel *self;
937 
938   result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
939   if (!result)
940     {
941       g_debug ("Failed to get Brightness property: %s", error->message);
942       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
943         return;
944     }
945   else
946     {
947       g_autoptr(GVariant) v = NULL;
948       g_variant_get (result, "(v)", &v);
949       brightness = v ? g_variant_get_int32 (v) : -1.0;
950     }
951 
952   self = user_data;
953   self->has_brightness = brightness >= 0.0;
954 
955   gtk_widget_set_visible (GTK_WIDGET (self->dim_screen_row), self->has_brightness);
956   als_enabled_state_changed (self);
957 }
958 
959 static void
populate_blank_screen_row(HdyComboRow * combo_row)960 populate_blank_screen_row (HdyComboRow *combo_row)
961 {
962   g_autoptr (GListStore) list_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
963   gint minutes[] = { 1, 2, 3, 4, 5, 8, 10, 12, 15 };
964   guint i;
965   g_autoptr (HdyValueObject) never_value_object = NULL;
966 
967   for (i = 0; i < G_N_ELEMENTS (minutes); i++)
968     {
969       gchar *text = NULL;
970       g_autoptr (HdyValueObject) value_object = NULL;
971 
972       /* Translators: Option for "Blank Screen" in "Power" panel */
973       text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", minutes[i]), minutes[i]);
974       value_object = hdy_value_object_new_take_string (text);
975 
976       g_object_set_data (G_OBJECT (value_object), "value", GUINT_TO_POINTER (minutes[i] * 60));
977       g_list_store_append (list_store, value_object);
978     }
979 
980   never_value_object = hdy_value_object_new_string (C_("Idle time", "Never"));
981   g_object_set_data (G_OBJECT (never_value_object), "value", GUINT_TO_POINTER (0));
982   g_list_store_append (list_store, never_value_object);
983 
984   hdy_combo_row_bind_name_model (combo_row,
985                                  G_LIST_MODEL (list_store),
986                                  (HdyComboRowGetNameFunc) hdy_value_object_dup_string,
987                                  NULL, NULL);
988 }
989 
990 static void
setup_power_saving(CcPowerPanel * self)991 setup_power_saving (CcPowerPanel *self)
992 {
993   g_autoptr(GDBusConnection) connection = NULL;
994   g_autoptr(GError) error = NULL;
995   int value;
996 
997   /* ambient light sensor */
998   self->iio_proxy_watch_id =
999     g_bus_watch_name (G_BUS_TYPE_SYSTEM,
1000                       "net.hadess.SensorProxy",
1001                       G_BUS_NAME_WATCHER_FLAGS_NONE,
1002                       iio_proxy_appeared_cb,
1003                       iio_proxy_vanished_cb,
1004                       self, NULL);
1005   g_signal_connect_object (self->gsd_settings, "changed",
1006                            G_CALLBACK (als_enabled_setting_changed), self, G_CONNECT_SWAPPED);
1007 
1008   connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
1009                                cc_panel_get_cancellable (CC_PANEL (self)),
1010                                &error);
1011   if (connection)
1012     {
1013       g_dbus_connection_call (connection,
1014                               "org.gnome.SettingsDaemon.Power",
1015                               "/org/gnome/SettingsDaemon/Power",
1016                               "org.freedesktop.DBus.Properties",
1017                               "Get",
1018                               g_variant_new ("(ss)",
1019                                              "org.gnome.SettingsDaemon.Power.Screen",
1020                                              "Brightness"),
1021                               NULL,
1022                               G_DBUS_CALL_FLAGS_NONE,
1023                               -1,
1024                               cc_panel_get_cancellable (CC_PANEL (self)),
1025                               got_brightness_cb,
1026                               self);
1027     }
1028   else
1029     {
1030       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1031         g_warning ("session bus not available: %s", error->message);
1032     }
1033 
1034 
1035   g_settings_bind (self->gsd_settings, "idle-dim",
1036                    self->dim_screen_switch, "active",
1037                    G_SETTINGS_BIND_DEFAULT);
1038 
1039   g_signal_handlers_block_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self);
1040   populate_blank_screen_row (self->blank_screen_row);
1041   value = g_settings_get_uint (self->session_settings, "idle-delay");
1042   set_value_for_combo_row (self->blank_screen_row, value);
1043   g_signal_handlers_unblock_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self);
1044 
1045   /* The default values for these settings are unfortunate for us;
1046    * timeout == 0, action == suspend means 'do nothing' - just
1047    * as timout === anything, action == nothing.
1048    * For our switch/combobox combination, the second choice works
1049    * much better, so translate the first to the second here.
1050    */
1051   if (g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout") == 0)
1052     {
1053       g_settings_set_enum (self->gsd_settings, "sleep-inactive-ac-type", GSD_POWER_ACTION_NOTHING);
1054       g_settings_set_int (self->gsd_settings, "sleep-inactive-ac-timeout", 3600);
1055     }
1056   if (g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout") == 0)
1057     {
1058       g_settings_set_enum (self->gsd_settings, "sleep-inactive-battery-type", GSD_POWER_ACTION_NOTHING);
1059       g_settings_set_int (self->gsd_settings, "sleep-inactive-battery-timeout", 1800);
1060     }
1061 
1062   /* Automatic suspend row */
1063   if (can_suspend_or_hibernate (self, "CanSuspend"))
1064     {
1065       gtk_widget_show (GTK_WIDGET (self->automatic_suspend_row));
1066       atk_object_set_name (ATK_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (self->automatic_suspend_row))), _("Automatic suspend"));
1067 
1068       g_signal_connect (self->automatic_suspend_dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1069       g_signal_connect_object (self->gsd_settings, "changed", G_CALLBACK (on_suspend_settings_changed), self, G_CONNECT_SWAPPED);
1070 
1071       g_settings_bind_with_mapping (self->gsd_settings, "sleep-inactive-battery-type",
1072                                     self->suspend_on_battery_switch, "active",
1073                                     G_SETTINGS_BIND_DEFAULT,
1074                                     get_sleep_type, set_sleep_type, NULL, NULL);
1075 
1076       g_object_set_data (G_OBJECT (self->suspend_on_battery_delay_combo), "_gsettings_key", "sleep-inactive-battery-timeout");
1077       value = g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout");
1078       set_value_for_combo (self->suspend_on_battery_delay_combo, value);
1079       g_signal_connect_object (self->suspend_on_battery_delay_combo, "changed",
1080                                G_CALLBACK (combo_time_changed_cb), self, G_CONNECT_SWAPPED);
1081       g_object_bind_property (self->suspend_on_battery_switch, "active", self->suspend_on_battery_delay_combo, "sensitive",
1082                               G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1083 
1084       g_settings_bind_with_mapping (self->gsd_settings, "sleep-inactive-ac-type",
1085                                     self->suspend_on_ac_switch, "active",
1086                                     G_SETTINGS_BIND_DEFAULT,
1087                                     get_sleep_type, set_sleep_type, NULL, NULL);
1088 
1089       g_object_set_data (G_OBJECT (self->suspend_on_ac_delay_combo), "_gsettings_key", "sleep-inactive-ac-timeout");
1090       value = g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout");
1091       set_value_for_combo (self->suspend_on_ac_delay_combo, value);
1092       g_signal_connect_object (self->suspend_on_ac_delay_combo, "changed",
1093                                G_CALLBACK (combo_time_changed_cb), self, G_CONNECT_SWAPPED);
1094       g_object_bind_property (self->suspend_on_ac_switch, "active", self->suspend_on_ac_delay_combo, "sensitive",
1095                               G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1096 
1097       set_ac_battery_ui_mode (self);
1098       update_automatic_suspend_label (self);
1099     }
1100 }
1101 
1102 static const char *
variant_lookup_string(GVariant * dict,const char * key)1103 variant_lookup_string (GVariant   *dict,
1104                        const char *key)
1105 {
1106   GVariant *variant;
1107 
1108   variant = g_variant_lookup_value (dict, key, G_VARIANT_TYPE_STRING);
1109   if (!variant)
1110     return NULL;
1111   return g_variant_get_string (variant, NULL);
1112 }
1113 
1114 static void
performance_profile_set_active(CcPowerPanel * self,const char * profile_str)1115 performance_profile_set_active (CcPowerPanel  *self,
1116                                 const char    *profile_str)
1117 {
1118   CcPowerProfile profile = cc_power_profile_from_str (profile_str);
1119   GtkRadioButton *button;
1120 
1121   button = cc_power_profile_row_get_radio_button (CC_POWER_PROFILE_ROW (self->power_profiles_row[profile]));
1122   g_assert (button);
1123   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
1124 }
1125 
1126 static void
performance_profile_set_inhibited(CcPowerPanel * self,const char * performance_inhibited)1127 performance_profile_set_inhibited (CcPowerPanel  *self,
1128                                    const char    *performance_inhibited)
1129 {
1130   CcPowerProfileRow *row;
1131 
1132   row = self->power_profiles_row[CC_POWER_PROFILE_PERFORMANCE];
1133   if (!row)
1134       return;
1135   cc_power_profile_row_set_performance_inhibited (row, performance_inhibited);
1136 }
1137 
1138 static void
power_profile_update_info_boxes(CcPowerPanel * self)1139 power_profile_update_info_boxes (CcPowerPanel *self)
1140 {
1141   g_autoptr(GVariant) degraded_variant = NULL;
1142   g_autoptr(GVariant) holds_variant = NULL;
1143   g_autoptr(GVariant) profile_variant = NULL;
1144   guint i, num_children;
1145   const char *degraded = NULL;
1146   const char *profile;
1147   CcPowerProfileInfoRow *row;
1148   int next_insert = 0;
1149 
1150   empty_listbox (self->power_profile_info_listbox);
1151   gtk_widget_hide (GTK_WIDGET (self->power_profile_info_listbox));
1152 
1153   profile_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfile");
1154   if (!profile_variant)
1155     {
1156       g_warning ("No 'ActiveProfile' property on power-profiles-daemon service");
1157       return;
1158     }
1159   profile = g_variant_get_string (profile_variant, NULL);
1160 
1161   degraded_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded");
1162   if (degraded_variant)
1163     degraded = g_variant_get_string (degraded_variant, NULL);
1164   if (degraded && *degraded != '\0')
1165     {
1166       const char *text;
1167 
1168       gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox));
1169 
1170       if (g_str_equal (degraded, "high-operating-temperature"))
1171         text = _("Performance mode temporarily disabled due to high operating temperature.");
1172       else if (g_str_equal (degraded, "lap-detected"))
1173         text = _("Lap detected: performance mode temporarily unavailable. Move the device to a stable surface to restore.");
1174       else
1175         text = _("Performance mode temporarily disabled.");
1176 
1177       row = cc_power_profile_info_row_new (text);
1178       gtk_widget_show (GTK_WIDGET (row));
1179       gtk_container_add (GTK_CONTAINER (self->power_profile_info_listbox), GTK_WIDGET (row));
1180       if (g_str_equal (profile, "performance"))
1181         next_insert = 1;
1182     }
1183 
1184   holds_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfileHolds");
1185   if (!holds_variant)
1186     {
1187       g_warning ("No 'ActiveProfileHolds' property on power-profiles-daemon service");
1188       return;
1189     }
1190 
1191   num_children = g_variant_n_children (holds_variant);
1192   for (i = 0; i < num_children; i++)
1193     {
1194       g_autoptr(GDesktopAppInfo) app_info = NULL;
1195       g_autoptr(GVariant) hold_variant = NULL;
1196       g_autofree char *text = NULL;
1197       const char *app_id, *held_profile, *reason, *name;
1198 
1199       hold_variant = g_variant_get_child_value (holds_variant, i);
1200       if (!hold_variant || !g_variant_is_of_type (hold_variant, G_VARIANT_TYPE ("a{sv}")))
1201         continue;
1202 
1203       app_id = variant_lookup_string (hold_variant, "ApplicationId");
1204       if (!app_id)
1205         continue;
1206 
1207       gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox));
1208 
1209       app_info = g_desktop_app_info_new (app_id);
1210       name = app_info ? g_app_info_get_name (G_APP_INFO (app_info)) : app_id;
1211       held_profile = variant_lookup_string (hold_variant, "Profile");
1212       reason = variant_lookup_string (hold_variant, "Reason");
1213       g_debug ("Adding info row for %s hold by %s: %s", held_profile, app_id, reason);
1214 
1215       if (g_strcmp0 (held_profile, "power-saver") == 0 &&
1216           g_strcmp0 (app_id, "org.gnome.SettingsDaemon.Power") == 0)
1217         {
1218           text = g_strdup (_("Low battery: power saver enabled. Previous mode will be restored when battery is sufficiently charged."));
1219         }
1220       else
1221         {
1222           switch (cc_power_profile_from_str (held_profile))
1223           {
1224           case CC_POWER_PROFILE_POWER_SAVER:
1225             /* translators: "%s" is an application name */
1226             text = g_strdup_printf (_("Power Saver mode activated by “%s”."), name);
1227             break;
1228           case CC_POWER_PROFILE_PERFORMANCE:
1229             /* translators: "%s" is an application name */
1230             text = g_strdup_printf (_("Performance mode activated by “%s”."), name);
1231             break;
1232           default:
1233             g_assert_not_reached ();
1234           }
1235         }
1236 
1237       row = cc_power_profile_info_row_new (text);
1238       gtk_widget_show (GTK_WIDGET (row));
1239       if (g_strcmp0 (held_profile, profile) != 0)
1240         gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), -1);
1241       else
1242         gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), next_insert);
1243     }
1244 }
1245 
1246 static void
power_profiles_row_activated_cb(GtkListBox * box,GtkListBoxRow * box_row,gpointer user_data)1247 power_profiles_row_activated_cb (GtkListBox    *box,
1248                                  GtkListBoxRow *box_row,
1249                                  gpointer       user_data)
1250 {
1251   if (!gtk_widget_is_sensitive (GTK_WIDGET (box_row)))
1252     return;
1253 
1254   cc_power_profile_row_set_active (CC_POWER_PROFILE_ROW(box_row), TRUE);
1255 }
1256 
1257 static gint
perf_profile_list_box_sort(GtkListBoxRow * row1,GtkListBoxRow * row2,gpointer user_data)1258 perf_profile_list_box_sort (GtkListBoxRow *row1,
1259                             GtkListBoxRow *row2,
1260                             gpointer       user_data)
1261 {
1262   CcPowerProfile row1_profile, row2_profile;
1263 
1264   row1_profile = cc_power_profile_row_get_profile (CC_POWER_PROFILE_ROW (row1));
1265   row2_profile = cc_power_profile_row_get_profile (CC_POWER_PROFILE_ROW (row2));
1266 
1267   if (row1_profile < row2_profile)
1268     return -1;
1269   if (row1_profile > row2_profile)
1270     return 1;
1271   return 0;
1272 }
1273 
1274 static void
power_profiles_properties_changed_cb(CcPowerPanel * self,GVariant * changed_properties,GStrv invalidated_properties,GDBusProxy * proxy)1275 power_profiles_properties_changed_cb (CcPowerPanel *self,
1276                                       GVariant   *changed_properties,
1277                                       GStrv       invalidated_properties,
1278                                       GDBusProxy *proxy)
1279 {
1280   g_autoptr(GVariantIter) iter = NULL;
1281   const char *key;
1282   g_autoptr(GVariant) value = NULL;
1283 
1284   g_variant_get (changed_properties, "a{sv}", &iter);
1285   while (g_variant_iter_next (iter, "{&sv}", &key, &value))
1286     {
1287       if (g_strcmp0 (key, "PerformanceInhibited") == 0)
1288         {
1289           if (!self->has_performance_degraded)
1290             performance_profile_set_inhibited (self,
1291                                                g_variant_get_string (value, NULL));
1292         }
1293       else if (g_strcmp0 (key, "PerformanceDegraded") == 0 ||
1294                g_strcmp0 (key, "ActiveProfileHolds") == 0)
1295         {
1296           power_profile_update_info_boxes (self);
1297         }
1298       else if (g_strcmp0 (key, "ActiveProfile") == 0)
1299         {
1300           self->power_profiles_in_update = TRUE;
1301           performance_profile_set_active (self, g_variant_get_string (value, NULL));
1302           self->power_profiles_in_update = FALSE;
1303         }
1304       else
1305         {
1306           g_debug ("Unhandled change on '%s' property", key);
1307         }
1308     }
1309 }
1310 
1311 static void
set_active_profile_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1312 set_active_profile_cb (GObject      *source_object,
1313                        GAsyncResult *res,
1314                        gpointer      user_data)
1315 {
1316   g_autoptr(GVariant) variant = NULL;
1317   g_autoptr(GError) error = NULL;
1318 
1319   variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
1320                                            res, &error);
1321   if (!variant)
1322     {
1323       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1324         g_warning ("Could not set active profile: %s", error->message);
1325     }
1326 }
1327 
1328 static void
power_profile_button_toggled_cb(CcPowerProfileRow * row,gpointer user_data)1329 power_profile_button_toggled_cb (CcPowerProfileRow *row,
1330                                  gpointer         user_data)
1331 {
1332   CcPowerPanel *self = user_data;
1333   CcPowerProfile profile;
1334   g_autoptr(GDBusConnection) connection = NULL;
1335   g_autoptr(GError) error = NULL;
1336 
1337   if (!cc_power_profile_row_get_active (row))
1338     return;
1339   if (self->power_profiles_in_update)
1340     return;
1341 
1342   profile = cc_power_profile_row_get_profile (row);
1343 
1344   connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
1345                                cc_panel_get_cancellable (CC_PANEL (self)),
1346                                &error);
1347   if (!connection)
1348     {
1349       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1350         g_warning ("system bus not available: %s", error->message);
1351       return;
1352     }
1353 
1354   g_dbus_connection_call (connection,
1355                           "net.hadess.PowerProfiles",
1356                           "/net/hadess/PowerProfiles",
1357                           "org.freedesktop.DBus.Properties",
1358                           "Set",
1359                           g_variant_new ("(ssv)",
1360                                          "net.hadess.PowerProfiles",
1361                                          "ActiveProfile",
1362                                          g_variant_new_string (cc_power_profile_to_str (profile))),
1363                           NULL,
1364                           G_DBUS_CALL_FLAGS_NONE,
1365                           -1,
1366                           cc_panel_get_cancellable (CC_PANEL (self)),
1367                           set_active_profile_cb,
1368                           NULL);
1369 }
1370 
1371 static void
setup_power_profiles(CcPowerPanel * self)1372 setup_power_profiles (CcPowerPanel *self)
1373 {
1374   g_autoptr(GDBusConnection) connection = NULL;
1375   g_autoptr(GVariant) variant = NULL;
1376   g_autoptr(GVariant) props = NULL;
1377   guint i, num_children;
1378   g_autoptr(GError) error = NULL;
1379   const char *performance_inhibited = NULL;
1380   const char *performance_degraded;
1381   const char *active_profile;
1382   g_autoptr(GVariant) profiles = NULL;
1383   GtkRadioButton *last_button;
1384 
1385   self->power_profiles_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM,
1386                                                                          G_DBUS_PROXY_FLAGS_NONE,
1387                                                                          "net.hadess.PowerProfiles",
1388                                                                          "/net/hadess/PowerProfiles",
1389                                                                          "net.hadess.PowerProfiles",
1390                                                                          NULL,
1391                                                                          &error);
1392 
1393   if (!self->power_profiles_proxy)
1394     {
1395       g_debug ("Could not create Power Profiles proxy: %s", error->message);
1396       return;
1397     }
1398 
1399   connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
1400                                cc_panel_get_cancellable (CC_PANEL (self)),
1401                                &error);
1402   if (!connection)
1403     {
1404       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1405         g_warning ("system bus not available: %s", error->message);
1406       return;
1407     }
1408 
1409   variant = g_dbus_connection_call_sync (connection,
1410                                          "net.hadess.PowerProfiles",
1411                                          "/net/hadess/PowerProfiles",
1412                                          "org.freedesktop.DBus.Properties",
1413                                          "GetAll",
1414                                          g_variant_new ("(s)",
1415                                                         "net.hadess.PowerProfiles"),
1416                                          NULL,
1417                                          G_DBUS_CALL_FLAGS_NONE,
1418                                          -1,
1419                                          NULL,
1420                                          &error);
1421 
1422   if (!variant)
1423     {
1424       g_debug ("Failed to get properties for Power Profiles: %s",
1425                error->message);
1426       g_clear_object (&self->power_profiles_proxy);
1427       return;
1428     }
1429 
1430   gtk_widget_show (GTK_WIDGET (self->power_profile_section));
1431 
1432   props = g_variant_get_child_value (variant, 0);
1433   performance_degraded = variant_lookup_string (props, "PerformanceDegraded");
1434   self->has_performance_degraded = performance_degraded != NULL;
1435   if (performance_degraded == NULL)
1436     performance_inhibited = variant_lookup_string (props, "PerformanceInhibited");
1437   active_profile = variant_lookup_string (props, "ActiveProfile");
1438 
1439   last_button = NULL;
1440   profiles = g_variant_lookup_value (props, "Profiles", NULL);
1441   num_children = g_variant_n_children (profiles);
1442   for (i = 0; i < num_children; i++)
1443     {
1444       g_autoptr(GVariant) profile_variant;
1445       const char *name;
1446       GtkRadioButton *button;
1447       CcPowerProfile profile;
1448       CcPowerProfileRow *row;
1449 
1450       profile_variant = g_variant_get_child_value (profiles, i);
1451       if (!profile_variant ||
1452           !g_variant_is_of_type (profile_variant, G_VARIANT_TYPE ("a{sv}")))
1453         continue;
1454 
1455       name = variant_lookup_string (profile_variant, "Profile");
1456       if (!name)
1457         continue;
1458       g_debug ("Adding row for profile '%s' (driver: %s)",
1459                name, variant_lookup_string (profile_variant, "Driver"));
1460 
1461       profile = cc_power_profile_from_str (name);
1462       row = cc_power_profile_row_new (cc_power_profile_from_str (name));
1463       cc_power_profile_row_set_performance_inhibited (row, performance_inhibited);
1464       g_signal_connect_object (G_OBJECT (row), "button-toggled",
1465                                G_CALLBACK (power_profile_button_toggled_cb), self,
1466                                0);
1467       self->power_profiles_row[profile] = row;
1468       gtk_widget_show (GTK_WIDGET (row));
1469       gtk_container_add (GTK_CONTAINER (self->power_profile_listbox), GTK_WIDGET (row));
1470       gtk_size_group_add_widget (self->row_sizegroup, GTK_WIDGET (row));
1471 
1472       /* Connect radio button to group */
1473       button = cc_power_profile_row_get_radio_button (row);
1474       gtk_radio_button_join_group (button, last_button);
1475       last_button = button;
1476     }
1477 
1478   self->power_profiles_in_update = TRUE;
1479   performance_profile_set_active (self, active_profile);
1480   self->power_profiles_in_update = FALSE;
1481 
1482   self->power_profiles_prop_id = g_signal_connect_object (G_OBJECT (self->power_profiles_proxy), "g-properties-changed",
1483                                                           G_CALLBACK (power_profiles_properties_changed_cb), self, G_CONNECT_SWAPPED);
1484 
1485   if (self->has_performance_degraded)
1486     power_profile_update_info_boxes (self);
1487 
1488   update_power_saver_low_battery_row_visibility (self);
1489 }
1490 
1491 static void
setup_general_section(CcPowerPanel * self)1492 setup_general_section (CcPowerPanel *self)
1493 {
1494   gboolean can_suspend, can_hibernate, show_section = FALSE;
1495 
1496   can_suspend = can_suspend_or_hibernate (self, "CanSuspend");
1497   can_hibernate = can_suspend_or_hibernate (self, "CanHibernate");
1498 
1499   if ((can_hibernate || can_suspend) &&
1500       g_strcmp0 (self->chassis_type, "vm") != 0 &&
1501       g_strcmp0 (self->chassis_type, "tablet") != 0 &&
1502       g_strcmp0 (self->chassis_type, "handset") != 0)
1503     {
1504       gtk_widget_show (GTK_WIDGET (self->power_button_row));
1505 
1506       g_signal_handlers_block_by_func (self->power_button_row,
1507                                        power_button_row_changed_cb,
1508                                        self);
1509       populate_power_button_row (self->power_button_row,
1510                                  can_suspend,
1511                                  can_hibernate);
1512       set_value_for_combo_row (self->power_button_row,
1513                                g_settings_get_enum (self->gsd_settings, "power-button-action"));
1514       g_signal_handlers_unblock_by_func (self->power_button_row,
1515                                          power_button_row_changed_cb,
1516                                          self);
1517 
1518       show_section = TRUE;
1519     }
1520 
1521   if (self->has_batteries)
1522     {
1523       gtk_widget_show (GTK_WIDGET (self->battery_percentage_row));
1524 
1525       g_settings_bind (self->interface_settings, "show-battery-percentage",
1526                        self->battery_percentage_switch, "active",
1527                        G_SETTINGS_BIND_DEFAULT);
1528 
1529       show_section = TRUE;
1530     }
1531 
1532   gtk_widget_set_visible (GTK_WIDGET (self->general_section), show_section);
1533 }
1534 
1535 static gint
battery_sort_func(GtkListBoxRow * a,GtkListBoxRow * b,gpointer data)1536 battery_sort_func (GtkListBoxRow *a, GtkListBoxRow *b, gpointer data)
1537 {
1538   CcBatteryRow *row_a = CC_BATTERY_ROW (a);
1539   CcBatteryRow *row_b = CC_BATTERY_ROW (b);
1540   gboolean a_primary;
1541   gboolean b_primary;
1542   UpDeviceKind a_kind;
1543   UpDeviceKind b_kind;
1544 
1545   a_primary = cc_battery_row_get_primary(row_a);
1546   b_primary = cc_battery_row_get_primary(row_b);
1547 
1548   if (a_primary)
1549     return -1;
1550   else if (b_primary)
1551     return 1;
1552 
1553   a_kind = cc_battery_row_get_kind(row_a);
1554   b_kind = cc_battery_row_get_kind(row_b);
1555 
1556   return a_kind - b_kind;
1557 }
1558 
1559 static void
cc_power_panel_class_init(CcPowerPanelClass * klass)1560 cc_power_panel_class_init (CcPowerPanelClass *klass)
1561 {
1562   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1563   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1564   CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
1565 
1566   object_class->dispose = cc_power_panel_dispose;
1567 
1568   panel_class->get_help_uri = cc_power_panel_get_help_uri;
1569 
1570   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-panel.ui");
1571 
1572   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, als_row);
1573   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, als_switch);
1574   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_dialog);
1575   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_label);
1576   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_row);
1577   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_listbox);
1578   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_percentage_row);
1579   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_percentage_switch);
1580   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_row_sizegroup);
1581   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_section);
1582   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, blank_screen_row);
1583   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, device_listbox);
1584   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, device_section);
1585   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, dim_screen_row);
1586   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, dim_screen_switch);
1587   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, general_section);
1588   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, level_sizegroup);
1589   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_row);
1590   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_listbox);
1591   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_info_listbox);
1592   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_section);
1593   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_saver_low_battery_row);
1594   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_saver_low_battery_switch);
1595   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, row_sizegroup);
1596   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_delay_combo);
1597   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_delay_label);
1598   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_label);
1599   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_switch);
1600   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_delay_combo);
1601   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_label);
1602   gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_switch);
1603 
1604   gtk_widget_class_bind_template_callback (widget_class, als_switch_changed_cb);
1605   gtk_widget_class_bind_template_callback (widget_class, automatic_suspend_label_mnemonic_activate_cb);
1606   gtk_widget_class_bind_template_callback (widget_class, blank_screen_row_changed_cb);
1607   gtk_widget_class_bind_template_callback (widget_class, keynav_failed_cb);
1608   gtk_widget_class_bind_template_callback (widget_class, power_button_row_changed_cb);
1609   gtk_widget_class_bind_template_callback (widget_class, power_profiles_row_activated_cb);
1610   gtk_widget_class_bind_template_callback (widget_class, automatic_suspend_row_activated_cb);
1611 }
1612 
1613 static void
cc_power_panel_init(CcPowerPanel * self)1614 cc_power_panel_init (CcPowerPanel *self)
1615 {
1616   guint i;
1617 
1618   g_resources_register (cc_power_get_resource ());
1619 
1620   gtk_widget_init_template (GTK_WIDGET (self));
1621   load_custom_css (self, "/org/gnome/control-center/power/battery-levels.css");
1622   load_custom_css (self, "/org/gnome/control-center/power/power-profiles.css");
1623 
1624   self->chassis_type = get_chassis_type (cc_panel_get_cancellable (CC_PANEL (self)));
1625 
1626   self->up_client = up_client_new ();
1627 
1628   self->gsd_settings = g_settings_new ("org.gnome.settings-daemon.plugins.power");
1629   self->session_settings = g_settings_new ("org.gnome.desktop.session");
1630   self->interface_settings = g_settings_new ("org.gnome.desktop.interface");
1631 
1632   gtk_list_box_set_sort_func (self->battery_listbox,
1633                               (GtkListBoxSortFunc)battery_sort_func, NULL, NULL);
1634 
1635   gtk_list_box_set_sort_func (self->device_listbox,
1636                               (GtkListBoxSortFunc)battery_sort_func, NULL, NULL);
1637 
1638   gtk_list_box_set_sort_func (self->power_profile_listbox,
1639                               perf_profile_list_box_sort,
1640                               NULL, NULL);
1641   setup_power_profiles (self);
1642 
1643   setup_power_saving (self);
1644 
1645   setup_general_section (self);
1646 
1647   /* populate batteries */
1648   g_signal_connect_object (self->up_client, "device-added", G_CALLBACK (up_client_device_added), self, G_CONNECT_SWAPPED);
1649   g_signal_connect_object (self->up_client, "device-removed", G_CALLBACK (up_client_device_removed), self, G_CONNECT_SWAPPED);
1650 
1651   self->devices = up_client_get_devices2 (self->up_client);
1652   for (i = 0; self->devices != NULL && i < self->devices->len; i++) {
1653     UpDevice *device = g_ptr_array_index (self->devices, i);
1654     g_signal_connect_object (G_OBJECT (device), "notify",
1655                              G_CALLBACK (up_client_changed), self, G_CONNECT_SWAPPED);
1656   }
1657   up_client_changed (self);
1658 }
1659