summaryrefslogtreecommitdiffstats
path: root/panels/power
diff options
context:
space:
mode:
Diffstat (limited to 'panels/power')
-rw-r--r--panels/power/battery-levels.css14
-rw-r--r--panels/power/cc-battery-row.c357
-rw-r--r--panels/power/cc-battery-row.h49
-rw-r--r--panels/power/cc-battery-row.ui82
-rw-r--r--panels/power/cc-power-panel.c1539
-rw-r--r--panels/power/cc-power-panel.h29
-rw-r--r--panels/power/cc-power-panel.ui364
-rw-r--r--panels/power/cc-power-profile-info-row.c67
-rw-r--r--panels/power/cc-power-profile-info-row.h36
-rw-r--r--panels/power/cc-power-profile-info-row.ui42
-rw-r--r--panels/power/cc-power-profile-row.c180
-rw-r--r--panels/power/cc-power-profile-row.h51
-rw-r--r--panels/power/cc-power-profile-row.ui76
-rw-r--r--panels/power/gnome-power-panel.desktop.in.in20
-rw-r--r--panels/power/icons/info-symbolic.svg1
-rw-r--r--panels/power/icons/meson.build10
-rw-r--r--panels/power/icons/scalable/org.gnome.Settings-power-symbolic.svg4
-rw-r--r--panels/power/meson.build52
-rwxr-xr-xpanels/power/power-panel-scenario-tester.py311
-rw-r--r--panels/power/power-profiles.css7
-rw-r--r--panels/power/power.gresource.xml11
21 files changed, 3302 insertions, 0 deletions
diff --git a/panels/power/battery-levels.css b/panels/power/battery-levels.css
new file mode 100644
index 0000000..f8c7330
--- /dev/null
+++ b/panels/power/battery-levels.css
@@ -0,0 +1,14 @@
+levelbar block.warning-battery-offset {
+ background-color: @error_color;
+ border-color: @error_color;
+}
+
+levelbar block.low-battery-offset {
+ background-color: @warning_color;
+ border-color: @warning_color;
+}
+
+levelbar block.high-battery-offset {
+ background-color: @success_color;
+ border-color: @success_color;
+}
diff --git a/panels/power/cc-battery-row.c b/panels/power/cc-battery-row.c
new file mode 100644
index 0000000..fe80622
--- /dev/null
+++ b/panels/power/cc-battery-row.c
@@ -0,0 +1,357 @@
+/* cc-brightness-scale.c
+ *
+ * Copyright (C) 2010 Red Hat, Inc
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ * Copyright (C) 2010,2015 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2020 System76, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib/gi18n.h>
+
+#include "cc-battery-row.h"
+
+struct _CcBatteryRow {
+ GtkListBoxRow parent_instance;
+
+ GtkBox *battery_box;
+ GtkLabel *details_label;
+ GtkImage *icon;
+ GtkLevelBar *levelbar;
+ GtkLabel *name_label;
+ GtkLabel *percentage_label;
+ GtkBox *primary_bottom_box;
+ GtkLabel *primary_percentage_label;
+
+ UpDeviceKind kind;
+ gboolean primary;
+};
+
+G_DEFINE_TYPE (CcBatteryRow, cc_battery_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_battery_row_class_init (CcBatteryRowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-battery-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, battery_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, details_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, icon);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, levelbar);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, name_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, percentage_label);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, primary_bottom_box);
+ gtk_widget_class_bind_template_child (widget_class, CcBatteryRow, primary_percentage_label);
+}
+
+static void
+cc_battery_row_init (CcBatteryRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static gchar *
+get_timestring (guint64 time_secs)
+{
+ gchar* timestring = NULL;
+ gint hours;
+ gint minutes;
+
+ /* Add 0.5 to do rounding */
+ minutes = (int) ( ( time_secs / 60.0 ) + 0.5 );
+
+ if (minutes == 0)
+ return g_strdup (_("Unknown time"));
+
+ if (minutes < 60)
+ return timestring = g_strdup_printf (ngettext ("%i minute",
+ "%i minutes",
+ minutes), minutes);
+
+ hours = minutes / 60;
+ minutes = minutes % 60;
+
+ if (minutes == 0)
+ return timestring = g_strdup_printf (ngettext (
+ "%i hour",
+ "%i hours",
+ hours), hours);
+
+ /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes"
+ * Swap order with "%2$s %2$i %1$s %1$i if needed */
+ return timestring = g_strdup_printf (_("%i %s %i %s"),
+ hours, ngettext ("hour", "hours", hours),
+ minutes, ngettext ("minute", "minutes", minutes));
+}
+
+static gchar *
+get_details_string (gdouble percentage, UpDeviceState state, guint64 time)
+{
+ g_autofree gchar *details = NULL;
+
+ if (time > 0)
+ {
+ g_autofree gchar *time_string = NULL;
+
+ time_string = get_timestring (time);
+ switch (state)
+ {
+ case UP_DEVICE_STATE_CHARGING:
+ /* TRANSLATORS: %1 is a time string, e.g. "1 hour 5 minutes" */
+ details = g_strdup_printf (_("%s until fully charged"), time_string);
+ break;
+ case UP_DEVICE_STATE_DISCHARGING:
+ case UP_DEVICE_STATE_PENDING_DISCHARGE:
+ if (percentage < 20)
+ {
+ /* TRANSLATORS: %1 is a time string, e.g. "1 hour 5 minutes" */
+ details = g_strdup_printf (_("Caution: %s remaining"), time_string);
+ }
+ else
+ {
+ /* TRANSLATORS: %1 is a time string, e.g. "1 hour 5 minutes" */
+ details = g_strdup_printf (_("%s remaining"), time_string);
+ }
+ break;
+ case UP_DEVICE_STATE_FULLY_CHARGED:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Fully charged"));
+ break;
+ case UP_DEVICE_STATE_PENDING_CHARGE:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Not charging"));
+ break;
+ case UP_DEVICE_STATE_EMPTY:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Empty"));
+ break;
+ default:
+ details = g_strdup_printf ("error: %s", up_device_state_to_string (state));
+ break;
+ }
+ }
+ else
+ {
+ switch (state)
+ {
+ case UP_DEVICE_STATE_CHARGING:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Charging"));
+ break;
+ case UP_DEVICE_STATE_DISCHARGING:
+ case UP_DEVICE_STATE_PENDING_DISCHARGE:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Discharging"));
+ break;
+ case UP_DEVICE_STATE_FULLY_CHARGED:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Fully charged"));
+ break;
+ case UP_DEVICE_STATE_PENDING_CHARGE:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Not charging"));
+ break;
+ case UP_DEVICE_STATE_EMPTY:
+ /* TRANSLATORS: primary battery */
+ details = g_strdup (_("Empty"));
+ break;
+ default:
+ details = g_strdup_printf ("error: %s",
+ up_device_state_to_string (state));
+ break;
+ }
+ }
+
+ return g_steal_pointer (&details);
+}
+
+static const char *
+kind_to_description (UpDeviceKind kind)
+{
+ switch (kind)
+ {
+ case UP_DEVICE_KIND_MOUSE:
+ /* TRANSLATORS: secondary battery */
+ return N_("Wireless mouse");
+ case UP_DEVICE_KIND_KEYBOARD:
+ /* TRANSLATORS: secondary battery */
+ return N_("Wireless keyboard");
+ case UP_DEVICE_KIND_UPS:
+ /* TRANSLATORS: secondary battery */
+ return N_("Uninterruptible power supply");
+ case UP_DEVICE_KIND_PDA:
+ /* TRANSLATORS: secondary battery */
+ return N_("Personal digital assistant");
+ case UP_DEVICE_KIND_PHONE:
+ /* TRANSLATORS: secondary battery */
+ return N_("Cellphone");
+ case UP_DEVICE_KIND_MEDIA_PLAYER:
+ /* TRANSLATORS: secondary battery */
+ return N_("Media player");
+ case UP_DEVICE_KIND_TABLET:
+ /* TRANSLATORS: secondary battery */
+ return N_("Tablet");
+ case UP_DEVICE_KIND_COMPUTER:
+ /* TRANSLATORS: secondary battery */
+ return N_("Computer");
+ case UP_DEVICE_KIND_GAMING_INPUT:
+ /* TRANSLATORS: secondary battery */
+ return N_("Gaming input device");
+ default:
+ /* TRANSLATORS: secondary battery, misc */
+ return N_("Battery");
+ }
+
+ g_assert_not_reached ();
+}
+
+CcBatteryRow*
+cc_battery_row_new (UpDevice *device,
+ gboolean primary)
+{
+ g_autofree gchar *details = NULL;
+ gdouble percentage;
+ UpDeviceKind kind;
+ UpDeviceState state;
+ g_autofree gchar *s = NULL;
+ g_autofree gchar *icon_name = NULL;
+ const gchar *name;
+ CcBatteryRow *self;
+ guint64 time_empty, time_full, time;
+ gdouble energy_full, energy_rate;
+ gboolean is_kind_battery;
+ UpDeviceLevel battery_level;
+
+ self = g_object_new (CC_TYPE_BATTERY_ROW, NULL);
+
+ g_object_get (device,
+ "kind", &kind,
+ "state", &state,
+ "model", &name,
+ "percentage", &percentage,
+ "icon-name", &icon_name,
+ "time-to-empty", &time_empty,
+ "time-to-full", &time_full,
+ "energy-full", &energy_full,
+ "energy-rate", &energy_rate,
+ "battery-level", &battery_level,
+ NULL);
+ if (state == UP_DEVICE_STATE_DISCHARGING)
+ time = time_empty;
+ else
+ time = time_full;
+
+ is_kind_battery = (kind == UP_DEVICE_KIND_BATTERY || kind == UP_DEVICE_KIND_UPS);
+
+ /* Name label */
+ if (is_kind_battery)
+ {
+ if (g_object_get_data (G_OBJECT (device), "is-main-battery") != NULL)
+ name = C_("Battery name", "Main");
+ else
+ name = C_("Battery name", "Extra");
+ }
+ else if (name == NULL || name[0] == '\0')
+ {
+ name = _(kind_to_description (kind));
+ }
+ gtk_label_set_text (self->name_label, name);
+
+ /* Icon */
+ if (is_kind_battery && icon_name != NULL && icon_name[0] != '\0')
+ {
+ gtk_image_set_from_icon_name (self->icon, icon_name);
+ gtk_widget_show (GTK_WIDGET (self->icon));
+ }
+ else
+ gtk_widget_hide (GTK_WIDGET (self->icon));
+
+ /* Percentage label */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ {
+ s = g_strdup_printf ("%d%%", (int)percentage);
+ gtk_label_set_text (self->percentage_label, s);
+ gtk_label_set_text (self->primary_percentage_label, s);
+ }
+
+ /* Level bar */
+ gtk_level_bar_set_value (self->levelbar, percentage / 100.0);
+
+ /* Details label (primary only) */
+ details = get_details_string (percentage, state, time);
+ gtk_label_set_text (self->details_label, details);
+
+ /* Handle "primary" row differently */
+ gtk_widget_set_visible (GTK_WIDGET (self->battery_box), !primary);
+ gtk_widget_set_visible (GTK_WIDGET (self->percentage_label), !primary);
+ gtk_widget_set_visible (GTK_WIDGET (self->primary_bottom_box), primary);
+ /*
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (self->levelbar),
+ GTK_ACCESSIBLE_RELATION_LABELLED_BY, primary ? self->primary_percentage_label
+ : self->percentage_label,
+ NULL);
+ */
+
+ self->kind = kind;
+ self->primary = primary;
+
+ return self;
+}
+
+
+
+void
+cc_battery_row_set_level_sizegroup (CcBatteryRow *self,
+ GtkSizeGroup *sizegroup)
+{
+ gtk_size_group_add_widget (sizegroup, GTK_WIDGET (self->levelbar));
+}
+
+void
+cc_battery_row_set_row_sizegroup (CcBatteryRow *self,
+ GtkSizeGroup *sizegroup)
+{
+ gtk_size_group_add_widget (sizegroup, GTK_WIDGET (self));
+}
+
+void
+cc_battery_row_set_charge_sizegroup (CcBatteryRow *self,
+ GtkSizeGroup *sizegroup)
+{
+ gtk_size_group_add_widget (sizegroup, GTK_WIDGET (self->percentage_label));
+}
+
+void
+cc_battery_row_set_battery_sizegroup (CcBatteryRow *self,
+ GtkSizeGroup *sizegroup)
+{
+ gtk_size_group_add_widget (sizegroup, GTK_WIDGET (self->battery_box));
+}
+
+gboolean
+cc_battery_row_get_primary (CcBatteryRow *self)
+{
+ return self->primary;
+}
+
+UpDeviceKind
+cc_battery_row_get_kind (CcBatteryRow *self)
+{
+ return self->kind;
+}
diff --git a/panels/power/cc-battery-row.h b/panels/power/cc-battery-row.h
new file mode 100644
index 0000000..8a5b5fa
--- /dev/null
+++ b/panels/power/cc-battery-row.h
@@ -0,0 +1,49 @@
+/* cc-brightness-scale.h
+ *
+ * Copyright (C) 2020 System76, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libupower-glib/upower.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BATTERY_ROW (cc_battery_row_get_type())
+G_DECLARE_FINAL_TYPE (CcBatteryRow, cc_battery_row, CC, BATTERY_ROW, GtkListBoxRow)
+
+CcBatteryRow* cc_battery_row_new (UpDevice *device,
+ gboolean primary);
+
+void cc_battery_row_set_level_sizegroup (CcBatteryRow *row,
+ GtkSizeGroup *sizegroup);
+
+void cc_battery_row_set_row_sizegroup (CcBatteryRow *row,
+ GtkSizeGroup *sizegroup);
+
+void cc_battery_row_set_charge_sizegroup (CcBatteryRow *row,
+ GtkSizeGroup *sizegroup);
+
+void cc_battery_row_set_battery_sizegroup (CcBatteryRow *row,
+ GtkSizeGroup *sizegroup);
+
+gboolean cc_battery_row_get_primary (CcBatteryRow *row);
+UpDeviceKind cc_battery_row_get_kind (CcBatteryRow *row);
+
+G_END_DECLS \ No newline at end of file
diff --git a/panels/power/cc-battery-row.ui b/panels/power/cc-battery-row.ui
new file mode 100644
index 0000000..a99d80f
--- /dev/null
+++ b/panels/power/cc-battery-row.ui
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcBatteryRow" parent="GtkListBoxRow">
+ <property name="selectable">False</property>
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">16</property>
+ <property name="margin-bottom">14</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox" id="battery_box">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="icon">
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="percentage_label">
+ <property name="halign">end</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLevelBar" id="levelbar">
+ <property name="hexpand">True</property>
+ <property name="halign">fill</property>
+ <property name="valign">center</property>
+ <offsets>
+ <offset name="warning-battery-offset" value="0.03"/>
+ <offset name="low-battery-offset" value="0.1"/>
+ <offset name="high-battery-offset" value="1.0"/>
+ </offsets>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="primary_bottom_box">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkLabel" id="details_label">
+ <property name="hexpand">True</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="primary_percentage_label" />
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c
new file mode 100644
index 0000000..9fb0bbd
--- /dev/null
+++ b/panels/power/cc-power-panel.c
@@ -0,0 +1,1539 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ * Copyright (C) 2010,2015 Richard Hughes <richard@hughsie.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <libupower-glib/upower.h>
+#include <glib/gi18n.h>
+#include <gnome-settings-daemon/gsd-enums.h>
+#include <gio/gdesktopappinfo.h>
+
+#include "shell/cc-object-storage.h"
+#include "cc-battery-row.h"
+#include "cc-power-profile-row.h"
+#include "cc-power-profile-info-row.h"
+#include "cc-power-panel.h"
+#include "cc-power-resources.h"
+#include "cc-util.h"
+
+struct _CcPowerPanel
+{
+ CcPanel parent_instance;
+
+ GtkListBoxRow *als_row;
+ GtkSwitch *als_switch;
+ GtkDialog *automatic_suspend_dialog;
+ GtkLabel *automatic_suspend_label;
+ GtkListBoxRow *automatic_suspend_row;
+ GtkListBox *battery_listbox;
+ AdwActionRow *battery_percentage_row;
+ GtkSwitch *battery_percentage_switch;
+ GtkSizeGroup *battery_row_sizegroup;
+ AdwPreferencesGroup *battery_section;
+ AdwComboRow *blank_screen_row;
+ GtkListBox *device_listbox;
+ AdwPreferencesGroup *device_section;
+ GtkListBoxRow *dim_screen_row;
+ GtkSwitch *dim_screen_switch;
+ AdwPreferencesGroup *general_section;
+ GtkSizeGroup *level_sizegroup;
+ AdwComboRow *power_button_row;
+ GtkListBox *power_profile_listbox;
+ GtkListBox *power_profile_info_listbox;
+ AdwPreferencesGroup *power_profile_section;
+ AdwActionRow *power_saver_low_battery_row;
+ GtkSwitch *power_saver_low_battery_switch;
+ GtkSizeGroup *row_sizegroup;
+ GtkComboBox *suspend_on_battery_delay_combo;
+ GtkLabel *suspend_on_battery_delay_label;
+ GtkLabel *suspend_on_battery_label;
+ GtkSwitch *suspend_on_battery_switch;
+ GtkComboBox *suspend_on_ac_delay_combo;
+ GtkLabel *suspend_on_ac_label;
+ GtkSwitch *suspend_on_ac_switch;
+
+ GSettings *gsd_settings;
+ GSettings *session_settings;
+ GSettings *interface_settings;
+ UpClient *up_client;
+ GPtrArray *devices;
+ gboolean has_batteries;
+ char *chassis_type;
+
+ GDBusProxy *iio_proxy;
+ guint iio_proxy_watch_id;
+ gboolean has_brightness;
+
+ GDBusProxy *power_profiles_proxy;
+ guint power_profiles_prop_id;
+ CcPowerProfileRow *power_profiles_row[NUM_CC_POWER_PROFILES];
+ gboolean power_profiles_in_update;
+ gboolean has_performance_degraded;
+};
+
+CC_PANEL_REGISTER (CcPowerPanel, cc_power_panel)
+
+enum
+{
+ ACTION_MODEL_TEXT,
+ ACTION_MODEL_VALUE
+};
+
+static const char *
+cc_power_panel_get_help_uri (CcPanel *panel)
+{
+ return "help:gnome-help/power";
+}
+
+static char *
+get_chassis_type (GCancellable *cancellable)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) inner = NULL;
+ g_autoptr(GVariant) variant = NULL;
+ g_autoptr(GDBusConnection) connection = NULL;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+ cancellable,
+ &error);
+ if (!connection)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("system bus not available: %s", error->message);
+ return NULL;
+ }
+
+ variant = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.freedesktop.hostname1",
+ "Chassis"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cancellable,
+ &error);
+ if (!variant)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_debug ("Failed to get property '%s': %s", "Chassis", error->message);
+ return NULL;
+ }
+
+ g_variant_get (variant, "(v)", &inner);
+ return g_variant_dup_string (inner, NULL);
+}
+
+static void
+load_custom_css (CcPowerPanel *self,
+ const char *path)
+{
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ /* use custom CSS */
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, path);
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+static void
+add_battery (CcPowerPanel *panel, UpDevice *device, gboolean primary)
+{
+ CcBatteryRow *row = cc_battery_row_new (device, primary);
+ cc_battery_row_set_level_sizegroup (row, panel->level_sizegroup);
+ cc_battery_row_set_row_sizegroup (row, panel->battery_row_sizegroup);
+
+ gtk_list_box_append (panel->battery_listbox, GTK_WIDGET (row));
+ gtk_widget_set_visible (GTK_WIDGET (panel->battery_section), TRUE);
+}
+
+static void
+add_device (CcPowerPanel *self, UpDevice *device)
+{
+ CcBatteryRow *row = cc_battery_row_new (device, FALSE);
+ cc_battery_row_set_level_sizegroup (row, self->level_sizegroup);
+ cc_battery_row_set_row_sizegroup (row, self->row_sizegroup);
+
+ gtk_list_box_append (self->device_listbox, GTK_WIDGET (row));
+ gtk_widget_set_visible (GTK_WIDGET (self->device_section), TRUE);
+}
+
+static void
+empty_listbox (GtkListBox *listbox)
+{
+ GtkWidget *child;
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (listbox))) != NULL)
+ gtk_list_box_remove (listbox, child);
+}
+
+static void
+update_power_saver_low_battery_row_visibility (CcPowerPanel *self)
+{
+ g_autoptr(UpDevice) composite = NULL;
+ UpDeviceKind kind;
+
+ composite = up_client_get_display_device (self->up_client);
+ g_object_get (composite, "kind", &kind, NULL);
+ gtk_widget_set_visible (GTK_WIDGET (self->power_saver_low_battery_row),
+ self->power_profiles_proxy && kind == UP_DEVICE_KIND_BATTERY);
+}
+
+static void
+up_client_changed (CcPowerPanel *self)
+{
+ gint i;
+ UpDeviceKind kind;
+ guint n_batteries;
+ gboolean on_ups;
+ g_autoptr(UpDevice) composite = NULL;
+
+ empty_listbox (self->battery_listbox);
+ gtk_widget_hide (GTK_WIDGET (self->battery_section));
+
+ empty_listbox (self->device_listbox);
+ gtk_widget_hide (GTK_WIDGET (self->device_section));
+
+ on_ups = FALSE;
+ n_batteries = 0;
+ composite = up_client_get_display_device (self->up_client);
+ g_object_get (composite, "kind", &kind, NULL);
+ if (kind == UP_DEVICE_KIND_UPS)
+ {
+ on_ups = TRUE;
+ }
+ else
+ {
+ gboolean is_extra_battery = FALSE;
+
+ /* Count the batteries */
+ for (i = 0; self->devices != NULL && i < self->devices->len; i++)
+ {
+ UpDevice *device = (UpDevice*) g_ptr_array_index (self->devices, i);
+ gboolean is_power_supply = FALSE;
+ g_object_get (device,
+ "kind", &kind,
+ "power-supply", &is_power_supply,
+ NULL);
+ if (kind == UP_DEVICE_KIND_BATTERY &&
+ is_power_supply)
+ {
+ n_batteries++;
+ if (is_extra_battery == FALSE)
+ {
+ is_extra_battery = TRUE;
+ g_object_set_data (G_OBJECT (device), "is-main-battery", GINT_TO_POINTER(TRUE));
+ }
+ }
+ }
+ }
+
+ if (n_batteries > 1)
+ adw_preferences_group_set_title (self->battery_section, _("Batteries"));
+ else
+ adw_preferences_group_set_title (self->battery_section, _("Battery"));
+
+ if (!on_ups && n_batteries > 1)
+ add_battery (self, composite, TRUE);
+
+ for (i = 0; self->devices != NULL && i < self->devices->len; i++)
+ {
+ UpDevice *device = (UpDevice*) g_ptr_array_index (self->devices, i);
+ gboolean is_power_supply = FALSE;
+ g_object_get (device,
+ "kind", &kind,
+ "power-supply", &is_power_supply,
+ NULL);
+ if (kind == UP_DEVICE_KIND_LINE_POWER)
+ {
+ /* do nothing */
+ }
+ else if (kind == UP_DEVICE_KIND_UPS && on_ups)
+ {
+ add_battery (self, device, TRUE);
+ }
+ else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply && !on_ups && n_batteries == 1)
+ {
+ add_battery (self, device, TRUE);
+ }
+ else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply)
+ {
+ add_battery (self, device, FALSE);
+ }
+ else
+ {
+ add_device (self, device);
+ }
+ }
+
+ update_power_saver_low_battery_row_visibility (self);
+}
+
+static void
+up_client_device_removed (CcPowerPanel *self,
+ const char *object_path)
+{
+ guint i;
+
+ if (self->devices == NULL)
+ return;
+
+ for (i = 0; i < self->devices->len; i++)
+ {
+ UpDevice *device = g_ptr_array_index (self->devices, i);
+
+ if (g_strcmp0 (object_path, up_device_get_object_path (device)) == 0)
+ {
+ g_ptr_array_remove_index (self->devices, i);
+ break;
+ }
+ }
+
+ up_client_changed (self);
+}
+
+static void
+up_client_device_added (CcPowerPanel *self,
+ UpDevice *device)
+{
+ g_ptr_array_add (self->devices, g_object_ref (device));
+ g_signal_connect_object (G_OBJECT (device), "notify",
+ G_CALLBACK (up_client_changed), self, G_CONNECT_SWAPPED);
+ up_client_changed (self);
+}
+
+static void
+als_switch_changed_cb (CcPowerPanel *self)
+{
+ gboolean enabled;
+ enabled = gtk_switch_get_active (self->als_switch);
+ g_debug ("Setting ALS enabled %s", enabled ? "on" : "off");
+ g_settings_set_boolean (self->gsd_settings, "ambient-enabled", enabled);
+}
+
+static void
+als_enabled_state_changed (CcPowerPanel *self)
+{
+ gboolean enabled;
+ gboolean visible = FALSE;
+
+ if (self->iio_proxy != NULL)
+ {
+ g_autoptr(GVariant) v = g_dbus_proxy_get_cached_property (self->iio_proxy, "HasAmbientLight");
+ if (v != NULL)
+ visible = g_variant_get_boolean (v);
+ }
+
+ if (gtk_widget_get_visible (GTK_WIDGET (self->als_row)) == visible)
+ return;
+
+ enabled = g_settings_get_boolean (self->gsd_settings, "ambient-enabled");
+ g_debug ("ALS enabled: %s", enabled ? "on" : "off");
+ g_signal_handlers_block_by_func (self->als_switch, als_switch_changed_cb, self);
+ gtk_switch_set_active (self->als_switch, enabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->als_row), visible && self->has_brightness);
+ g_signal_handlers_unblock_by_func (self->als_switch, als_switch_changed_cb, self);
+}
+
+static void
+combo_time_changed_cb (CcPowerPanel *self, GtkWidget *widget)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gint value;
+ gboolean ret;
+ const gchar *key = (const gchar *)g_object_get_data (G_OBJECT(widget), "_gsettings_key");
+
+ /* no selection */
+ ret = gtk_combo_box_get_active_iter (GTK_COMBO_BOX(widget), &iter);
+ if (!ret)
+ return;
+
+ /* get entry */
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX(widget));
+ gtk_tree_model_get (model, &iter,
+ 1, &value,
+ -1);
+
+ /* set both keys */
+ g_settings_set_int (self->gsd_settings, key, value);
+}
+
+static void
+set_value_for_combo (GtkComboBox *combo_box, gint value)
+{
+ GtkTreeIter iter;
+ g_autoptr(GtkTreeIter) insert = NULL;
+ GtkTreeIter new;
+ GtkTreeModel *model;
+ gint value_tmp;
+ gint value_last = 0;
+ g_autofree gchar *text = NULL;
+ gboolean ret;
+
+ /* get entry */
+ model = gtk_combo_box_get_model (combo_box);
+ ret = gtk_tree_model_get_iter_first (model, &iter);
+ if (!ret)
+ return;
+
+ /* try to make the UI match the setting */
+ do
+ {
+ gtk_tree_model_get (model, &iter,
+ ACTION_MODEL_VALUE, &value_tmp,
+ -1);
+ if (value_tmp == value)
+ {
+ gtk_combo_box_set_active_iter (combo_box, &iter);
+ return;
+ }
+
+ /* Insert before if the next value is larger or the value is lower
+ * again (i.e. "Never" is zero and last). */
+ if (!insert && (value_tmp > value || value_last > value_tmp))
+ insert = gtk_tree_iter_copy (&iter);
+
+ value_last = value_tmp;
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ /* The value is not listed, so add it at the best point (or the end). */
+ gtk_list_store_insert_before (GTK_LIST_STORE (model), &new, insert);
+
+ text = cc_util_time_to_string_text (value * 1000);
+ gtk_list_store_set (GTK_LIST_STORE (model), &new,
+ ACTION_MODEL_TEXT, text,
+ ACTION_MODEL_VALUE, value,
+ -1);
+ gtk_combo_box_set_active_iter (combo_box, &new);
+}
+
+static void
+set_value_for_combo_row (AdwComboRow *combo_row, gint value)
+{
+ g_autoptr (GObject) new_item = NULL;
+ gboolean insert = FALSE;
+ guint insert_before = 0;
+ guint i;
+ GListModel *model;
+ gint value_last = 0;
+ g_autofree gchar *text = NULL;
+
+ /* try to make the UI match the setting */
+ model = adw_combo_row_get_model (combo_row);
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ g_autoptr (GObject) item = g_list_model_get_item (model, i);
+ gint value_tmp = GPOINTER_TO_UINT (g_object_get_data (item, "value"));
+ if (value_tmp == value)
+ {
+ adw_combo_row_set_selected (combo_row, i);
+ return;
+ }
+
+ /* Insert before if the next value is larger or the value is lower
+ * again (i.e. "Never" is zero and last). */
+ if (!insert && (value_tmp > value || value_last > value_tmp))
+ {
+ insert = TRUE;
+ insert_before = i;
+ }
+
+ value_last = value_tmp;
+ }
+
+ /* The value is not listed, so add it at the best point (or the end). */
+ text = cc_util_time_to_string_text (value * 1000);
+ gtk_string_list_append (GTK_STRING_LIST (model), text);
+
+ new_item = g_list_model_get_item (model, i);
+ g_object_set_data (G_OBJECT (new_item), "value", GUINT_TO_POINTER (value));
+
+ adw_combo_row_set_selected (combo_row, insert_before);
+}
+
+static void
+set_ac_battery_ui_mode (CcPowerPanel *self)
+{
+ GPtrArray *devices;
+ guint i;
+
+ self->has_batteries = FALSE;
+ devices = up_client_get_devices2 (self->up_client);
+ g_debug ("got %d devices from upower\n", devices ? devices->len : 0);
+
+ for (i = 0; devices != NULL && i < devices->len; i++)
+ {
+ UpDevice *device;
+ gboolean is_power_supply;
+ UpDeviceKind kind;
+
+ device = g_ptr_array_index (devices, i);
+ g_object_get (device,
+ "kind", &kind,
+ "power-supply", &is_power_supply,
+ NULL);
+ if (kind == UP_DEVICE_KIND_UPS ||
+ (kind == UP_DEVICE_KIND_BATTERY && is_power_supply))
+ {
+ self->has_batteries = TRUE;
+ break;
+ }
+ }
+ g_clear_pointer (&devices, g_ptr_array_unref);
+
+ if (!self->has_batteries)
+ {
+ gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_switch));
+ gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_label));
+ gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_delay_label));
+ gtk_widget_hide (GTK_WIDGET (self->suspend_on_battery_delay_combo));
+ gtk_label_set_label (self->suspend_on_ac_label, _("When _idle"));
+ }
+}
+
+static gboolean
+keynav_failed_cb (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *list)
+{
+ if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
+ return FALSE;
+
+ direction = GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
+
+ return gtk_widget_child_focus (GTK_WIDGET (self), direction);
+}
+
+static void
+blank_screen_row_changed_cb (CcPowerPanel *self)
+{
+ g_autoptr (GObject) item = NULL;
+ GListModel *model;
+ gint selected_index;
+ gint value;
+
+ model = adw_combo_row_get_model (self->blank_screen_row);
+ selected_index = adw_combo_row_get_selected (self->blank_screen_row);
+ if (selected_index == -1)
+ return;
+
+ item = g_list_model_get_item (model, selected_index);
+ value = GPOINTER_TO_UINT (g_object_get_data (item, "value"));
+
+ g_settings_set_uint (self->session_settings, "idle-delay", value);
+}
+
+static void
+power_button_row_changed_cb (CcPowerPanel *self)
+{
+ g_autoptr (GObject) item = NULL;
+ GListModel *model;
+ gint selected_index;
+ gint value;
+
+ model = adw_combo_row_get_model (self->power_button_row);
+ selected_index = adw_combo_row_get_selected (self->power_button_row);
+ if (selected_index == -1)
+ return;
+
+ item = g_list_model_get_item (model, selected_index);
+ value = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "value"));
+
+ g_settings_set_enum (self->gsd_settings, "power-button-action", value);
+}
+
+static void
+als_enabled_setting_changed (CcPowerPanel *self)
+{
+ als_enabled_state_changed (self);
+}
+
+static void
+iio_proxy_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ CcPowerPanel *self = CC_POWER_PANEL (user_data);
+ g_autoptr(GError) error = NULL;
+
+ self->iio_proxy =
+ cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "net.hadess.SensorProxy",
+ "/net/hadess/SensorProxy",
+ "net.hadess.SensorProxy",
+ NULL, &error);
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not create IIO sensor proxy: %s", error->message);
+ return;
+ }
+
+ g_signal_connect_object (G_OBJECT (self->iio_proxy), "g-properties-changed",
+ G_CALLBACK (als_enabled_state_changed), self,
+ G_CONNECT_SWAPPED);
+ als_enabled_state_changed (self);
+}
+
+static void
+iio_proxy_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ CcPowerPanel *self = CC_POWER_PANEL (user_data);
+ g_clear_object (&self->iio_proxy);
+ als_enabled_state_changed (self);
+}
+
+static void
+automatic_suspend_row_activated_cb (CcPowerPanel *self)
+{
+ GtkWidget *toplevel;
+ CcShell *shell;
+
+ shell = cc_panel_get_shell (CC_PANEL (self));
+ toplevel = cc_shell_get_toplevel (shell);
+ gtk_window_set_transient_for (GTK_WINDOW (self->automatic_suspend_dialog), GTK_WINDOW (toplevel));
+ gtk_window_set_modal (GTK_WINDOW (self->automatic_suspend_dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (self->automatic_suspend_dialog));
+}
+
+static gboolean
+automatic_suspend_label_mnemonic_activate_cb (CcPowerPanel *self)
+{
+ automatic_suspend_row_activated_cb (self);
+ return TRUE;
+}
+
+static gboolean
+get_sleep_type (GValue *value,
+ GVariant *variant,
+ gpointer data)
+{
+ gboolean enabled;
+
+ if (g_strcmp0 (g_variant_get_string (variant, NULL), "nothing") == 0)
+ enabled = FALSE;
+ else
+ enabled = TRUE;
+
+ g_value_set_boolean (value, enabled);
+
+ return TRUE;
+}
+
+static GVariant *
+set_sleep_type (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer data)
+{
+ GVariant *res;
+
+ if (g_value_get_boolean (value))
+ res = g_variant_new_string ("suspend");
+ else
+ res = g_variant_new_string ("nothing");
+
+ return res;
+}
+
+static void
+populate_power_button_row (AdwComboRow *combo_row,
+ gboolean can_suspend,
+ gboolean can_hibernate)
+{
+ g_autoptr (GtkStringList) string_list = NULL;
+ struct {
+ char *name;
+ GsdPowerButtonActionType value;
+ } actions[] = {
+ { N_("Suspend"), GSD_POWER_BUTTON_ACTION_SUSPEND },
+ { N_("Power Off"), GSD_POWER_BUTTON_ACTION_INTERACTIVE },
+ { N_("Hibernate"), GSD_POWER_BUTTON_ACTION_HIBERNATE },
+ { N_("Nothing"), GSD_POWER_BUTTON_ACTION_NOTHING }
+ };
+ guint item_index = 0;
+ guint i;
+
+ string_list = gtk_string_list_new (NULL);
+ for (i = 0; i < G_N_ELEMENTS (actions); i++)
+ {
+ g_autoptr (GObject) item = NULL;
+
+ if (!can_suspend && actions[i].value == GSD_POWER_BUTTON_ACTION_SUSPEND)
+ continue;
+
+ if (!can_hibernate && actions[i].value == GSD_POWER_BUTTON_ACTION_HIBERNATE)
+ continue;
+
+ gtk_string_list_append (string_list, _(actions[i].name));
+
+ item = g_list_model_get_item (G_LIST_MODEL (string_list), item_index++);
+ g_object_set_data (item, "value", GUINT_TO_POINTER (actions[i].value));
+ }
+
+ adw_combo_row_set_model (combo_row, G_LIST_MODEL (string_list));
+}
+
+#define NEVER 0
+
+static void
+update_automatic_suspend_label (CcPowerPanel *self)
+{
+ GsdPowerActionType ac_action;
+ GsdPowerActionType battery_action;
+ gint ac_timeout;
+ gint battery_timeout;
+ const gchar *s;
+
+ ac_action = g_settings_get_enum (self->gsd_settings, "sleep-inactive-ac-type");
+ battery_action = g_settings_get_enum (self->gsd_settings, "sleep-inactive-battery-type");
+ ac_timeout = g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout");
+ battery_timeout = g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout");
+
+ if (ac_timeout < 0)
+ g_warning ("Invalid negative timeout for 'sleep-inactive-ac-timeout': %d", ac_timeout);
+ if (battery_timeout < 0)
+ g_warning ("Invalid negative timeout for 'sleep-inactive-battery-timeout': %d", battery_timeout);
+
+ if (ac_action == GSD_POWER_ACTION_NOTHING || ac_timeout < 0)
+ ac_timeout = NEVER;
+ if (battery_action == GSD_POWER_ACTION_NOTHING || battery_timeout < 0)
+ battery_timeout = NEVER;
+
+ if (self->has_batteries)
+ {
+ if (ac_timeout == NEVER && battery_timeout == NEVER)
+ s = _("Off");
+ else if (ac_timeout == NEVER && battery_timeout > 0)
+ s = _("When on battery power");
+ else if (ac_timeout > 0 && battery_timeout == NEVER)
+ s = _("When plugged in");
+ else
+ s = _("On");
+ }
+ else
+ {
+ if (ac_timeout == NEVER)
+ s = _("Off");
+ else
+ s = _("On");
+ }
+
+ if (self->automatic_suspend_label)
+ gtk_label_set_label (self->automatic_suspend_label, s);
+}
+
+static void
+on_suspend_settings_changed (CcPowerPanel *self,
+ const char *key)
+{
+ if (g_str_has_prefix (key, "sleep-inactive-"))
+ {
+ update_automatic_suspend_label (self);
+ }
+}
+
+static gboolean
+can_suspend_or_hibernate (CcPowerPanel *self,
+ const char *method_name)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) variant = NULL;
+ g_autoptr(GError) error = NULL;
+ const char *s;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ &error);
+ if (!connection)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("system bus not available: %s", error->message);
+ return FALSE;
+ }
+
+ variant = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ method_name,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ &error);
+
+ if (!variant)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_debug ("Failed to call %s(): %s", method_name, error->message);
+ return FALSE;
+ }
+
+ g_variant_get (variant, "(&s)", &s);
+ return g_strcmp0 (s, "yes") == 0;
+}
+
+static void
+got_brightness_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) result = NULL;
+ g_autoptr(GError) error = NULL;
+ gint32 brightness = -1.0;
+ CcPowerPanel *self;
+
+ result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+ if (!result)
+ {
+ g_debug ("Failed to get Brightness property: %s", error->message);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ }
+ else
+ {
+ g_autoptr(GVariant) v = NULL;
+ g_variant_get (result, "(v)", &v);
+ brightness = v ? g_variant_get_int32 (v) : -1.0;
+ }
+
+ self = user_data;
+ self->has_brightness = brightness >= 0.0;
+
+ gtk_widget_set_visible (GTK_WIDGET (self->dim_screen_row), self->has_brightness);
+ als_enabled_state_changed (self);
+}
+
+static void
+populate_blank_screen_row (AdwComboRow *combo_row)
+{
+ g_autoptr (GtkStringList) string_list = NULL;
+ g_autoptr (GObject) never_object = NULL;
+ gint minutes[] = { 1, 2, 3, 4, 5, 8, 10, 12, 15 };
+ guint i;
+
+ string_list = gtk_string_list_new (NULL);
+ for (i = 0; i < G_N_ELEMENTS (minutes); i++)
+ {
+ g_autoptr (GObject) item = NULL;
+ gchar *text = NULL;
+
+ /* Translators: Option for "Blank Screen" in "Power" panel */
+ text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", minutes[i]), minutes[i]);
+ gtk_string_list_append (string_list, text);
+
+ item = g_list_model_get_item (G_LIST_MODEL (string_list), i);
+ g_object_set_data (item, "value", GUINT_TO_POINTER (minutes[i] * 60));
+ }
+
+ gtk_string_list_append (string_list, C_("Idle time", "Never"));
+ never_object = g_list_model_get_item (G_LIST_MODEL (string_list), i);
+ g_object_set_data (never_object, "value", GUINT_TO_POINTER (0));
+
+ adw_combo_row_set_model (combo_row, G_LIST_MODEL (string_list));
+}
+
+static void
+setup_power_saving (CcPowerPanel *self)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GError) error = NULL;
+ int value;
+
+ /* ambient light sensor */
+ self->iio_proxy_watch_id =
+ g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+ "net.hadess.SensorProxy",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ iio_proxy_appeared_cb,
+ iio_proxy_vanished_cb,
+ self, NULL);
+ g_signal_connect_object (self->gsd_settings, "changed",
+ G_CALLBACK (als_enabled_setting_changed), self, G_CONNECT_SWAPPED);
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ &error);
+ if (connection)
+ {
+ g_dbus_connection_call (connection,
+ "org.gnome.SettingsDaemon.Power",
+ "/org/gnome/SettingsDaemon/Power",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.gnome.SettingsDaemon.Power.Screen",
+ "Brightness"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ got_brightness_cb,
+ self);
+ }
+ else
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("session bus not available: %s", error->message);
+ }
+
+
+ g_settings_bind (self->gsd_settings, "idle-dim",
+ self->dim_screen_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_signal_handlers_block_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self);
+ populate_blank_screen_row (self->blank_screen_row);
+ value = g_settings_get_uint (self->session_settings, "idle-delay");
+ set_value_for_combo_row (self->blank_screen_row, value);
+ g_signal_handlers_unblock_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self);
+
+ /* The default values for these settings are unfortunate for us;
+ * timeout == 0, action == suspend means 'do nothing' - just
+ * as timout === anything, action == nothing.
+ * For our switch/combobox combination, the second choice works
+ * much better, so translate the first to the second here.
+ */
+ if (g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout") == 0)
+ {
+ g_settings_set_enum (self->gsd_settings, "sleep-inactive-ac-type", GSD_POWER_ACTION_NOTHING);
+ g_settings_set_int (self->gsd_settings, "sleep-inactive-ac-timeout", 3600);
+ }
+ if (g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout") == 0)
+ {
+ g_settings_set_enum (self->gsd_settings, "sleep-inactive-battery-type", GSD_POWER_ACTION_NOTHING);
+ g_settings_set_int (self->gsd_settings, "sleep-inactive-battery-timeout", 1800);
+ }
+
+ /* Automatic suspend row */
+ if (can_suspend_or_hibernate (self, "CanSuspend"))
+ {
+ gtk_widget_show (GTK_WIDGET (self->automatic_suspend_row));
+ gtk_accessible_update_property (GTK_ACCESSIBLE (self->automatic_suspend_row),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Automatic suspend"),
+ -1);
+
+ g_signal_connect_object (self->gsd_settings, "changed", G_CALLBACK (on_suspend_settings_changed), self, G_CONNECT_SWAPPED);
+
+ g_settings_bind_with_mapping (self->gsd_settings, "sleep-inactive-battery-type",
+ self->suspend_on_battery_switch, "active",
+ G_SETTINGS_BIND_DEFAULT,
+ get_sleep_type, set_sleep_type, NULL, NULL);
+
+ g_object_set_data (G_OBJECT (self->suspend_on_battery_delay_combo), "_gsettings_key", "sleep-inactive-battery-timeout");
+ value = g_settings_get_int (self->gsd_settings, "sleep-inactive-battery-timeout");
+ set_value_for_combo (self->suspend_on_battery_delay_combo, value);
+ g_signal_connect_object (self->suspend_on_battery_delay_combo, "changed",
+ G_CALLBACK (combo_time_changed_cb), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->suspend_on_battery_switch, "active", self->suspend_on_battery_delay_combo, "sensitive",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ g_settings_bind_with_mapping (self->gsd_settings, "sleep-inactive-ac-type",
+ self->suspend_on_ac_switch, "active",
+ G_SETTINGS_BIND_DEFAULT,
+ get_sleep_type, set_sleep_type, NULL, NULL);
+
+ g_object_set_data (G_OBJECT (self->suspend_on_ac_delay_combo), "_gsettings_key", "sleep-inactive-ac-timeout");
+ value = g_settings_get_int (self->gsd_settings, "sleep-inactive-ac-timeout");
+ set_value_for_combo (self->suspend_on_ac_delay_combo, value);
+ g_signal_connect_object (self->suspend_on_ac_delay_combo, "changed",
+ G_CALLBACK (combo_time_changed_cb), self, G_CONNECT_SWAPPED);
+ g_object_bind_property (self->suspend_on_ac_switch, "active", self->suspend_on_ac_delay_combo, "sensitive",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ set_ac_battery_ui_mode (self);
+ update_automatic_suspend_label (self);
+ }
+}
+
+static const char *
+variant_lookup_string (GVariant *dict,
+ const char *key)
+{
+ GVariant *variant;
+
+ variant = g_variant_lookup_value (dict, key, G_VARIANT_TYPE_STRING);
+ if (!variant)
+ return NULL;
+ return g_variant_get_string (variant, NULL);
+}
+
+static void
+performance_profile_set_active (CcPowerPanel *self,
+ const char *profile_str)
+{
+ CcPowerProfile profile = cc_power_profile_from_str (profile_str);
+ GtkCheckButton *button;
+
+ button = cc_power_profile_row_get_radio_button (CC_POWER_PROFILE_ROW (self->power_profiles_row[profile]));
+ if (!button) {
+ g_warning ("Not setting profile '%s' as it doesn't have a widget", profile_str);
+ return;
+ }
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (button), TRUE);
+}
+
+static void
+power_profile_update_info_boxes (CcPowerPanel *self)
+{
+ g_autoptr(GVariant) degraded_variant = NULL;
+ g_autoptr(GVariant) holds_variant = NULL;
+ g_autoptr(GVariant) profile_variant = NULL;
+ guint i, num_children;
+ const char *degraded = NULL;
+ const char *profile;
+ CcPowerProfileInfoRow *row;
+ int next_insert = 0;
+
+ empty_listbox (self->power_profile_info_listbox);
+ gtk_widget_hide (GTK_WIDGET (self->power_profile_info_listbox));
+
+ profile_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfile");
+ if (!profile_variant)
+ {
+ g_warning ("No 'ActiveProfile' property on power-profiles-daemon service");
+ return;
+ }
+ profile = g_variant_get_string (profile_variant, NULL);
+
+ degraded_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "PerformanceDegraded");
+ if (degraded_variant)
+ degraded = g_variant_get_string (degraded_variant, NULL);
+ if (degraded && *degraded != '\0')
+ {
+ const char *text;
+
+ gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox));
+
+ if (g_str_equal (degraded, "high-operating-temperature"))
+ text = _("Performance mode temporarily disabled due to high operating temperature.");
+ else if (g_str_equal (degraded, "lap-detected"))
+ text = _("Lap detected: performance mode temporarily unavailable. Move the device to a stable surface to restore.");
+ else
+ text = _("Performance mode temporarily disabled.");
+
+ row = cc_power_profile_info_row_new (text);
+ gtk_list_box_append (self->power_profile_info_listbox, GTK_WIDGET (row));
+ if (g_str_equal (profile, "performance"))
+ next_insert = 1;
+ }
+
+ holds_variant = g_dbus_proxy_get_cached_property (self->power_profiles_proxy, "ActiveProfileHolds");
+ if (!holds_variant)
+ {
+ g_warning ("No 'ActiveProfileHolds' property on power-profiles-daemon service");
+ return;
+ }
+
+ num_children = g_variant_n_children (holds_variant);
+ for (i = 0; i < num_children; i++)
+ {
+ g_autoptr(GDesktopAppInfo) app_info = NULL;
+ g_autoptr(GVariant) hold_variant = NULL;
+ g_autofree char *text = NULL;
+ const char *app_id, *held_profile, *reason, *name;
+
+ hold_variant = g_variant_get_child_value (holds_variant, i);
+ if (!hold_variant || !g_variant_is_of_type (hold_variant, G_VARIANT_TYPE ("a{sv}")))
+ continue;
+
+ app_id = variant_lookup_string (hold_variant, "ApplicationId");
+ if (!app_id)
+ continue;
+
+ gtk_widget_show (GTK_WIDGET (self->power_profile_info_listbox));
+
+ app_info = g_desktop_app_info_new (app_id);
+ name = app_info ? g_app_info_get_name (G_APP_INFO (app_info)) : app_id;
+ held_profile = variant_lookup_string (hold_variant, "Profile");
+ reason = variant_lookup_string (hold_variant, "Reason");
+ g_debug ("Adding info row for %s hold by %s: %s", held_profile, app_id, reason);
+
+ if (g_strcmp0 (held_profile, "power-saver") == 0 &&
+ g_strcmp0 (app_id, "org.gnome.SettingsDaemon.Power") == 0)
+ {
+ text = g_strdup (_("Low battery: power saver enabled. Previous mode will be restored when battery is sufficiently charged."));
+ }
+ else
+ {
+ switch (cc_power_profile_from_str (held_profile))
+ {
+ case CC_POWER_PROFILE_POWER_SAVER:
+ /* translators: "%s" is an application name */
+ text = g_strdup_printf (_("Power Saver mode activated by “%s”."), name);
+ break;
+ case CC_POWER_PROFILE_PERFORMANCE:
+ /* translators: "%s" is an application name */
+ text = g_strdup_printf (_("Performance mode activated by “%s”."), name);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ row = cc_power_profile_info_row_new (text);
+ gtk_widget_show (GTK_WIDGET (row));
+ if (g_strcmp0 (held_profile, profile) != 0)
+ gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), -1);
+ else
+ gtk_list_box_insert (GTK_LIST_BOX (self->power_profile_info_listbox), GTK_WIDGET (row), next_insert);
+ }
+}
+
+static void
+power_profiles_row_activated_cb (GtkListBox *box,
+ GtkListBoxRow *box_row,
+ gpointer user_data)
+{
+ if (!gtk_widget_is_sensitive (GTK_WIDGET (box_row)))
+ return;
+
+ cc_power_profile_row_set_active (CC_POWER_PROFILE_ROW(box_row), TRUE);
+}
+
+static gint
+perf_profile_list_box_sort (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ CcPowerProfile row1_profile, row2_profile;
+
+ row1_profile = cc_power_profile_row_get_profile (CC_POWER_PROFILE_ROW (row1));
+ row2_profile = cc_power_profile_row_get_profile (CC_POWER_PROFILE_ROW (row2));
+
+ if (row1_profile < row2_profile)
+ return -1;
+ if (row1_profile > row2_profile)
+ return 1;
+ return 0;
+}
+
+static void
+power_profiles_properties_changed_cb (CcPowerPanel *self,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ GDBusProxy *proxy)
+{
+ g_autoptr(GVariantIter) iter = NULL;
+ const char *key;
+ g_autoptr(GVariant) value = NULL;
+
+ g_variant_get (changed_properties, "a{sv}", &iter);
+ while (g_variant_iter_next (iter, "{&sv}", &key, &value))
+ {
+ if (g_strcmp0 (key, "PerformanceDegraded") == 0 ||
+ g_strcmp0 (key, "ActiveProfileHolds") == 0)
+ {
+ power_profile_update_info_boxes (self);
+ }
+ else if (g_strcmp0 (key, "ActiveProfile") == 0)
+ {
+ self->power_profiles_in_update = TRUE;
+ performance_profile_set_active (self, g_variant_get_string (value, NULL));
+ self->power_profiles_in_update = FALSE;
+ }
+ else
+ {
+ g_debug ("Unhandled change on '%s' property", key);
+ }
+ }
+}
+
+static void
+set_active_profile_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) variant = NULL;
+ g_autoptr(GError) error = NULL;
+
+ variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+ res, &error);
+ if (!variant)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not set active profile: %s", error->message);
+ }
+}
+
+static void
+power_profile_button_toggled_cb (CcPowerProfileRow *row,
+ gpointer user_data)
+{
+ CcPowerPanel *self = user_data;
+ CcPowerProfile profile;
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!cc_power_profile_row_get_active (row))
+ return;
+ if (self->power_profiles_in_update)
+ return;
+
+ profile = cc_power_profile_row_get_profile (row);
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ &error);
+ if (!connection)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("system bus not available: %s", error->message);
+ return;
+ }
+
+ g_dbus_connection_call (connection,
+ "net.hadess.PowerProfiles",
+ "/net/hadess/PowerProfiles",
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new ("(ssv)",
+ "net.hadess.PowerProfiles",
+ "ActiveProfile",
+ g_variant_new_string (cc_power_profile_to_str (profile))),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ set_active_profile_cb,
+ NULL);
+}
+
+static void
+setup_power_profiles (CcPowerPanel *self)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) variant = NULL;
+ g_autoptr(GVariant) props = NULL;
+ guint i, num_children;
+ g_autoptr(GError) error = NULL;
+ const char *performance_degraded;
+ const char *active_profile;
+ g_autoptr(GVariant) profiles = NULL;
+ GtkCheckButton *last_button;
+
+ self->power_profiles_proxy = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "net.hadess.PowerProfiles",
+ "/net/hadess/PowerProfiles",
+ "net.hadess.PowerProfiles",
+ NULL,
+ &error);
+
+ if (!self->power_profiles_proxy)
+ {
+ g_debug ("Could not create Power Profiles proxy: %s", error->message);
+ return;
+ }
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+ cc_panel_get_cancellable (CC_PANEL (self)),
+ &error);
+ if (!connection)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("system bus not available: %s", error->message);
+ return;
+ }
+
+ variant = g_dbus_connection_call_sync (connection,
+ "net.hadess.PowerProfiles",
+ "/net/hadess/PowerProfiles",
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)",
+ "net.hadess.PowerProfiles"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (!variant)
+ {
+ g_debug ("Failed to get properties for Power Profiles: %s",
+ error->message);
+ g_clear_object (&self->power_profiles_proxy);
+ return;
+ }
+
+ gtk_widget_show (GTK_WIDGET (self->power_profile_section));
+
+ props = g_variant_get_child_value (variant, 0);
+ performance_degraded = variant_lookup_string (props, "PerformanceDegraded");
+ self->has_performance_degraded = performance_degraded != NULL;
+ active_profile = variant_lookup_string (props, "ActiveProfile");
+
+ last_button = NULL;
+ profiles = g_variant_lookup_value (props, "Profiles", NULL);
+ num_children = g_variant_n_children (profiles);
+ for (i = 0; i < num_children; i++)
+ {
+ g_autoptr(GVariant) profile_variant;
+ const char *name;
+ GtkCheckButton *button;
+ CcPowerProfile profile;
+ CcPowerProfileRow *row;
+
+ profile_variant = g_variant_get_child_value (profiles, i);
+ if (!profile_variant ||
+ !g_variant_is_of_type (profile_variant, G_VARIANT_TYPE ("a{sv}")))
+ continue;
+
+ name = variant_lookup_string (profile_variant, "Profile");
+ if (!name)
+ continue;
+ g_debug ("Adding row for profile '%s' (driver: %s)",
+ name, variant_lookup_string (profile_variant, "Driver"));
+
+ profile = cc_power_profile_from_str (name);
+ row = cc_power_profile_row_new (cc_power_profile_from_str (name));
+ g_signal_connect_object (G_OBJECT (row), "button-toggled",
+ G_CALLBACK (power_profile_button_toggled_cb), self,
+ 0);
+ self->power_profiles_row[profile] = row;
+ gtk_widget_show (GTK_WIDGET (row));
+ gtk_list_box_append (self->power_profile_listbox, GTK_WIDGET (row));
+ gtk_size_group_add_widget (self->row_sizegroup, GTK_WIDGET (row));
+
+ /* Connect radio button to group */
+ button = cc_power_profile_row_get_radio_button (row);
+ gtk_check_button_set_group (button, last_button);
+ last_button = button;
+ }
+
+ self->power_profiles_in_update = TRUE;
+ performance_profile_set_active (self, active_profile);
+ self->power_profiles_in_update = FALSE;
+
+ self->power_profiles_prop_id = g_signal_connect_object (G_OBJECT (self->power_profiles_proxy), "g-properties-changed",
+ G_CALLBACK (power_profiles_properties_changed_cb), self, G_CONNECT_SWAPPED);
+
+ if (self->has_performance_degraded)
+ power_profile_update_info_boxes (self);
+
+ update_power_saver_low_battery_row_visibility (self);
+}
+
+static void
+setup_general_section (CcPowerPanel *self)
+{
+ gboolean can_suspend, can_hibernate, show_section = FALSE;
+
+ can_suspend = can_suspend_or_hibernate (self, "CanSuspend");
+ can_hibernate = can_suspend_or_hibernate (self, "CanHibernate");
+
+ if ((can_hibernate || can_suspend) &&
+ g_strcmp0 (self->chassis_type, "vm") != 0 &&
+ g_strcmp0 (self->chassis_type, "tablet") != 0 &&
+ g_strcmp0 (self->chassis_type, "handset") != 0)
+ {
+ gtk_widget_show (GTK_WIDGET (self->power_button_row));
+
+ g_signal_handlers_block_by_func (self->power_button_row,
+ power_button_row_changed_cb,
+ self);
+ populate_power_button_row (self->power_button_row,
+ can_suspend,
+ can_hibernate);
+ set_value_for_combo_row (self->power_button_row,
+ g_settings_get_enum (self->gsd_settings, "power-button-action"));
+ g_signal_handlers_unblock_by_func (self->power_button_row,
+ power_button_row_changed_cb,
+ self);
+
+ show_section = TRUE;
+ }
+
+ if (self->has_batteries)
+ {
+ gtk_widget_show (GTK_WIDGET (self->battery_percentage_row));
+
+ g_settings_bind (self->interface_settings, "show-battery-percentage",
+ self->battery_percentage_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ show_section = TRUE;
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->general_section), show_section);
+}
+
+static gint
+battery_sort_func (GtkListBoxRow *a, GtkListBoxRow *b, gpointer data)
+{
+ CcBatteryRow *row_a = CC_BATTERY_ROW (a);
+ CcBatteryRow *row_b = CC_BATTERY_ROW (b);
+ gboolean a_primary;
+ gboolean b_primary;
+ UpDeviceKind a_kind;
+ UpDeviceKind b_kind;
+
+ a_primary = cc_battery_row_get_primary(row_a);
+ b_primary = cc_battery_row_get_primary(row_b);
+
+ if (a_primary)
+ return -1;
+ else if (b_primary)
+ return 1;
+
+ a_kind = cc_battery_row_get_kind(row_a);
+ b_kind = cc_battery_row_get_kind(row_b);
+
+ return a_kind - b_kind;
+}
+
+static void
+cc_power_panel_dispose (GObject *object)
+{
+ CcPowerPanel *self = CC_POWER_PANEL (object);
+
+ g_signal_handlers_disconnect_by_func (self->blank_screen_row, blank_screen_row_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (self->power_button_row, power_button_row_changed_cb, self);
+
+ g_clear_pointer (&self->chassis_type, g_free);
+ g_clear_object (&self->gsd_settings);
+ g_clear_object (&self->session_settings);
+ g_clear_object (&self->interface_settings);
+ g_clear_pointer ((GtkWindow **) &self->automatic_suspend_dialog, gtk_window_destroy);
+ g_clear_pointer (&self->devices, g_ptr_array_unref);
+ g_clear_object (&self->up_client);
+ g_clear_object (&self->iio_proxy);
+ g_clear_object (&self->power_profiles_proxy);
+ if (self->iio_proxy_watch_id != 0)
+ g_bus_unwatch_name (self->iio_proxy_watch_id);
+ self->iio_proxy_watch_id = 0;
+
+ G_OBJECT_CLASS (cc_power_panel_parent_class)->dispose (object);
+}
+
+static void
+cc_power_panel_class_init (CcPowerPanelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+
+ object_class->dispose = cc_power_panel_dispose;
+
+ panel_class->get_help_uri = cc_power_panel_get_help_uri;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, als_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, als_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_label);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_percentage_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_percentage_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_row_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, battery_section);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, blank_screen_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, device_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, device_section);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, dim_screen_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, dim_screen_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, general_section);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, level_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_button_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_info_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_profile_section);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_saver_low_battery_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, power_saver_low_battery_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, row_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_delay_combo);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_delay_label);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_label);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_delay_combo);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_label);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_ac_switch);
+
+ gtk_widget_class_bind_template_callback (widget_class, als_switch_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, automatic_suspend_label_mnemonic_activate_cb);
+ gtk_widget_class_bind_template_callback (widget_class, blank_screen_row_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, keynav_failed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, power_button_row_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, power_profiles_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, automatic_suspend_row_activated_cb);
+}
+
+static void
+cc_power_panel_init (CcPowerPanel *self)
+{
+ guint i;
+
+ g_resources_register (cc_power_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+ load_custom_css (self, "/org/gnome/control-center/power/battery-levels.css");
+ load_custom_css (self, "/org/gnome/control-center/power/power-profiles.css");
+
+ self->chassis_type = get_chassis_type (cc_panel_get_cancellable (CC_PANEL (self)));
+
+ self->up_client = up_client_new ();
+
+ self->gsd_settings = g_settings_new ("org.gnome.settings-daemon.plugins.power");
+ self->session_settings = g_settings_new ("org.gnome.desktop.session");
+ self->interface_settings = g_settings_new ("org.gnome.desktop.interface");
+
+ gtk_list_box_set_sort_func (self->battery_listbox,
+ (GtkListBoxSortFunc)battery_sort_func, NULL, NULL);
+
+ gtk_list_box_set_sort_func (self->device_listbox,
+ (GtkListBoxSortFunc)battery_sort_func, NULL, NULL);
+
+ gtk_list_box_set_sort_func (self->power_profile_listbox,
+ perf_profile_list_box_sort,
+ NULL, NULL);
+ setup_power_profiles (self);
+
+ setup_power_saving (self);
+ g_settings_bind (self->gsd_settings, "power-saver-profile-on-low-battery",
+ self->power_saver_low_battery_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ setup_general_section (self);
+
+ /* populate batteries */
+ g_signal_connect_object (self->up_client, "device-added", G_CALLBACK (up_client_device_added), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->up_client, "device-removed", G_CALLBACK (up_client_device_removed), self, G_CONNECT_SWAPPED);
+
+ self->devices = up_client_get_devices2 (self->up_client);
+ for (i = 0; self->devices != NULL && i < self->devices->len; i++) {
+ UpDevice *device = g_ptr_array_index (self->devices, i);
+ g_signal_connect_object (G_OBJECT (device), "notify",
+ G_CALLBACK (up_client_changed), self, G_CONNECT_SWAPPED);
+ }
+ up_client_changed (self);
+}
diff --git a/panels/power/cc-power-panel.h b/panels/power/cc-power-panel.h
new file mode 100644
index 0000000..e8b922d
--- /dev/null
+++ b/panels/power/cc-power-panel.h
@@ -0,0 +1,29 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_POWER_PANEL (cc_power_panel_get_type ())
+G_DECLARE_FINAL_TYPE (CcPowerPanel, cc_power_panel, CC, POWER_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/power/cc-power-panel.ui b/panels/power/cc-power-panel.ui
new file mode 100644
index 0000000..44c515b
--- /dev/null
+++ b/panels/power/cc-power-panel.ui
@@ -0,0 +1,364 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkListStore" id="time_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name value -->
+ <column type="gint"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">15 minutes</col>
+ <col id="1">900</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">20 minutes</col>
+ <col id="1">1200</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">25 minutes</col>
+ <col id="1">1500</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">30 minutes</col>
+ <col id="1">1800</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">45 minutes</col>
+ <col id="1">2700</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">1 hour</col>
+ <col id="1">3600</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">80 minutes</col>
+ <col id="1">4800</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">90 minutes</col>
+ <col id="1">5400</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">100 minutes</col>
+ <col id="1">6000</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for &quot;Delay&quot; in &quot;Automatic suspend&quot; dialog.">2 hours</col>
+ <col id="1">7200</col>
+ </row>
+ </data>
+ </object>
+ <template class="CcPowerPanel" parent="CcPanel">
+ <child type="content">
+ <object class="AdwPreferencesPage">
+ <child>
+ <object class="AdwPreferencesGroup" id="battery_section">
+ <property name="title" translatable="yes">Battery</property>
+ <child>
+ <object class="GtkListBox" id="battery_listbox">
+ <property name="selection-mode">none</property>
+ <signal name="keynav-failed" handler="keynav_failed_cb" object="CcPowerPanel" swapped="yes"/>
+ <accessibility>
+ <relation name="labelled-by">battery_section</relation>
+ </accessibility>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup" id="device_section">
+ <property name="title" translatable="yes">Devices</property>
+ <child>
+ <object class="GtkListBox" id="device_listbox">
+ <property name="selection-mode">none</property>
+ <signal name="keynav-failed" handler="keynav_failed_cb" object="CcPowerPanel" swapped="yes"/>
+ <accessibility>
+ <relation name="labelled-by">device_section</relation>
+ </accessibility>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup" id="power_profile_section">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">Power Mode</property>
+ <property name="description" translatable="yes">Affects system performance and power usage.</property>
+ <child>
+ <object class="GtkListBox" id="power_profile_listbox">
+ <property name="selection-mode">none</property>
+ <signal name="keynav-failed" handler="keynav_failed_cb" object="CcPowerPanel" swapped="yes"/>
+ <signal name="row-activated" handler="power_profiles_row_activated_cb" object="CcPowerPanel" swapped="yes"/>
+ <accessibility>
+ <relation name="labelled-by">power_profile_section</relation>
+ </accessibility>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="power_profile_info_listbox">
+ <property name="visible">False</property>
+ <property name="selection-mode">none</property>
+ <property name="margin_top">12</property>
+ <signal name="keynav-failed" handler="keynav_failed_cb" object="CcPowerPanel" swapped="yes"/>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup" id="power_saving_section">
+ <property name="title" translatable="yes">Power Saving Options</property>
+ <child>
+ <object class="AdwActionRow" id="als_row">
+ <property name="title" translatable="yes">Automatic Screen Brightness</property>
+ <property name="subtitle" translatable="yes">Screen brightness adjusts to the surrounding light.</property>
+ <property name="activatable_widget">als_switch</property>
+ <child>
+ <object class="GtkSwitch" id="als_switch">
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="als_switch_changed_cb" object="CcPowerPanel" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="dim_screen_row">
+ <property name="title" translatable="yes">Dim Screen</property>
+ <property name="subtitle" translatable="yes">Reduces the screen brightness when the computer is inactive.</property>
+ <property name="activatable_widget">dim_screen_switch</property>
+ <child>
+ <object class="GtkSwitch" id="dim_screen_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwComboRow" id="blank_screen_row">
+ <property name="title" translatable="yes">Screen _Blank</property>
+ <property name="subtitle" translatable="yes">Turns the screen off after a period of inactivity.</property>
+ <property name="use_underline">True</property>
+ <signal name="notify::selected-item" handler="blank_screen_row_changed_cb" object="CcPowerPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="power_saver_low_battery_row">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">Automatic Power Saver</property>
+ <property name="subtitle" translatable="yes">Enables power saver mode when battery is low.</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">power_saver_low_battery_switch</property>
+ <child>
+ <object class="GtkSwitch" id="power_saver_low_battery_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="automatic_suspend_row">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">_Automatic Suspend</property>
+ <property name="subtitle" translatable="yes">Pauses the computer after a period of inactivity.</property>
+ <property name="use_underline">True</property>
+ <property name="activatable">True</property>
+ <signal name="activated" handler="automatic_suspend_row_activated_cb" swapped="yes"/>
+ <child>
+ <object class="GtkLabel" id="automatic_suspend_label">
+ <property name="halign">end</property>
+ <signal name="mnemonic-activate" handler="automatic_suspend_label_mnemonic_activate_cb" object="CcPowerPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="valign">center</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup" id="general_section">
+ <child>
+ <object class="AdwComboRow" id="power_button_row">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">Po_wer Button Behavior</property>
+ <property name="use_underline">True</property>
+ <signal name="notify::selected-item" handler="power_button_row_changed_cb" object="CcPowerPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="battery_percentage_row">
+ <property name="visible">False</property>
+ <property name="title" translatable="yes">Show Battery _Percentage</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">battery_percentage_switch</property>
+ <child>
+ <object class="GtkSwitch" id="battery_percentage_switch">
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup" id="battery_row_sizegroup">
+ <property name="mode">vertical</property>
+ </object>
+ <object class="GtkSizeGroup" id="level_sizegroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="row_sizegroup">
+ <property name="mode">vertical</property>
+ <widgets>
+ <widget name="als_row"/>
+ <widget name="dim_screen_row"/>
+ <widget name="blank_screen_row"/>
+ <widget name="automatic_suspend_row"/>
+ <widget name="power_button_row"/>
+ <widget name="battery_percentage_row"/>
+ </widgets>
+ </object>
+
+ <object class="GtkDialog" id="automatic_suspend_dialog">
+ <property name="title" translatable="yes">Automatic Suspend</property>
+ <property name="resizable">False</property>
+ <property name="use_header_bar">1</property>
+ <property name="hide-on-close">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="margin_start">12</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="suspend_on_ac_label">
+ <property name="margin_top">12</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Plugged In</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">suspend_on_ac_switch</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="suspend_on_battery_label">
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">On _Battery Power</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">suspend_on_battery_switch</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="suspend_on_battery_switch">
+ <property name="halign">end</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="suspend_on_battery_delay_combo">
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <property name="model">time_liststore</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="suspend_on_battery_delay_label">
+ <property name="margin_start">20</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Delay</property>
+ <property name="mnemonic_widget">suspend_on_battery_delay_combo</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="suspend_on_ac_switch">
+ <property name="halign">end</property>
+ <property name="margin_top">12</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="suspend_on_ac_delay_combo">
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ <property name="model">time_liststore</property>
+ <layout>
+ <property name="column">2</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="margin_start">20</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Delay</property>
+ <property name="mnemonic_widget">suspend_on_ac_delay_combo</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/power/cc-power-profile-info-row.c b/panels/power/cc-power-profile-info-row.c
new file mode 100644
index 0000000..92bb788
--- /dev/null
+++ b/panels/power/cc-power-profile-info-row.c
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-list-row.c
+ *
+ * Copyright 2020 Red Hat Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-power-profile-info-row"
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include "cc-power-profile-info-row.h"
+
+struct _CcPowerProfileInfoRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkLabel *title_label;
+};
+
+G_DEFINE_TYPE (CcPowerProfileInfoRow, cc_power_profile_info_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_power_profile_info_row_class_init (CcPowerProfileInfoRowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-profile-info-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcPowerProfileInfoRow, title_label);
+}
+
+static void
+cc_power_profile_info_row_init (CcPowerProfileInfoRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcPowerProfileInfoRow *
+cc_power_profile_info_row_new (const char *text)
+{
+ CcPowerProfileInfoRow *self;
+
+ self = g_object_new (CC_TYPE_POWER_PROFILE_INFO_ROW, NULL);
+ gtk_label_set_markup (self->title_label, text);
+
+ return self;
+}
diff --git a/panels/power/cc-power-profile-info-row.h b/panels/power/cc-power-profile-info-row.h
new file mode 100644
index 0000000..52d055a
--- /dev/null
+++ b/panels/power/cc-power-profile-info-row.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-list-row.h
+ *
+ * Copyright 2020 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_POWER_PROFILE_INFO_ROW (cc_power_profile_info_row_get_type())
+G_DECLARE_FINAL_TYPE (CcPowerProfileInfoRow, cc_power_profile_info_row, CC, POWER_PROFILE_INFO_ROW, GtkListBoxRow)
+
+CcPowerProfileInfoRow *cc_power_profile_info_row_new (const char *text);
+
+G_END_DECLS
diff --git a/panels/power/cc-power-profile-info-row.ui b/panels/power/cc-power-profile-info-row.ui
new file mode 100644
index 0000000..4400244
--- /dev/null
+++ b/panels/power/cc-power-profile-info-row.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcPowerProfileInfoRow" parent="GtkListBoxRow">
+ <property name="activatable">False</property>
+ <property name="selectable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="margin-start">4</property>
+ <property name="margin-end">8</property>
+ <property name="margin-top">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkImage" id="icon_image">
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="icon-name">info-symbolic</property>
+ <property name="icon-size">large</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="use-markup">True</property>
+ <property name="use-underline">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="margin-end">6</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/power/cc-power-profile-row.c b/panels/power/cc-power-profile-row.c
new file mode 100644
index 0000000..dbb609f
--- /dev/null
+++ b/panels/power/cc-power-profile-row.c
@@ -0,0 +1,180 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-list-row.c
+ *
+ * Copyright 2020 Red Hat Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cc-power-profile-row"
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include "cc-power-profile-row.h"
+
+struct _CcPowerProfileRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkCheckButton *button;
+ GtkLabel *subtitle_label;
+ GtkLabel *title_label;
+
+ CcPowerProfile power_profile;
+};
+
+G_DEFINE_TYPE (CcPowerProfileRow, cc_power_profile_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ BUTTON_TOGGLED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void
+cc_power_profile_row_button_toggled_cb (CcPowerProfileRow *self)
+{
+ g_signal_emit (self, signals[BUTTON_TOGGLED], 0);
+}
+
+static void
+cc_power_profile_row_class_init (CcPowerProfileRowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/power/cc-power-profile-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, button);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, subtitle_label);
+ gtk_widget_class_bind_template_child (widget_class, CcPowerProfileRow, title_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, cc_power_profile_row_button_toggled_cb);
+
+ signals[BUTTON_TOGGLED] =
+ g_signal_new ("button-toggled",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+cc_power_profile_row_init (CcPowerProfileRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcPowerProfile
+cc_power_profile_row_get_profile (CcPowerProfileRow *self)
+{
+ g_return_val_if_fail (CC_IS_POWER_PROFILE_ROW (self), -1);
+
+ return self->power_profile;
+}
+
+GtkCheckButton *
+cc_power_profile_row_get_radio_button (CcPowerProfileRow *self)
+{
+ g_return_val_if_fail (CC_IS_POWER_PROFILE_ROW (self), NULL);
+
+ return self->button;
+}
+
+void
+cc_power_profile_row_set_active (CcPowerProfileRow *self,
+ gboolean active)
+{
+ g_return_if_fail (CC_IS_POWER_PROFILE_ROW (self));
+
+ gtk_check_button_set_active (GTK_CHECK_BUTTON (self->button), active);
+}
+
+gboolean
+cc_power_profile_row_get_active (CcPowerProfileRow *self)
+{
+ g_return_val_if_fail (CC_IS_POWER_PROFILE_ROW (self), FALSE);
+
+ return gtk_check_button_get_active (GTK_CHECK_BUTTON (self->button));
+}
+
+CcPowerProfileRow *
+cc_power_profile_row_new (CcPowerProfile power_profile)
+{
+ CcPowerProfileRow *self;
+ const char *text, *subtext;
+
+ self = g_object_new (CC_TYPE_POWER_PROFILE_ROW, NULL);
+
+ self->power_profile = power_profile;
+ switch (self->power_profile)
+ {
+ case CC_POWER_PROFILE_PERFORMANCE:
+ text = C_("Power profile", "Performance");
+ subtext = _("High performance and power usage.");
+ break;
+ case CC_POWER_PROFILE_BALANCED:
+ text = C_("Power profile", "Balanced");
+ subtext = _("Standard performance and power usage.");
+ break;
+ case CC_POWER_PROFILE_POWER_SAVER:
+ text = C_("Power profile", "Power Saver");
+ subtext = _("Reduced performance and power usage.");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ gtk_label_set_markup (self->title_label, text);
+ gtk_label_set_markup (self->subtitle_label, subtext);
+
+ return self;
+}
+
+CcPowerProfile
+cc_power_profile_from_str (const char *profile)
+{
+ if (g_strcmp0 (profile, "power-saver") == 0)
+ return CC_POWER_PROFILE_POWER_SAVER;
+ if (g_strcmp0 (profile, "balanced") == 0)
+ return CC_POWER_PROFILE_BALANCED;
+ if (g_strcmp0 (profile, "performance") == 0)
+ return CC_POWER_PROFILE_PERFORMANCE;
+
+ g_assert_not_reached ();
+}
+
+const char *
+cc_power_profile_to_str (CcPowerProfile profile)
+{
+ switch (profile)
+ {
+ case CC_POWER_PROFILE_POWER_SAVER:
+ return "power-saver";
+ case CC_POWER_PROFILE_BALANCED:
+ return "balanced";
+ case CC_POWER_PROFILE_PERFORMANCE:
+ return "performance";
+ default:
+ g_assert_not_reached ();
+ }
+}
diff --git a/panels/power/cc-power-profile-row.h b/panels/power/cc-power-profile-row.h
new file mode 100644
index 0000000..a6406e9
--- /dev/null
+++ b/panels/power/cc-power-profile-row.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* cc-list-row.h
+ *
+ * Copyright 2020 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s):
+ * Bastien Nocera <hadess@hadess.net>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ CC_POWER_PROFILE_PERFORMANCE = 0,
+ CC_POWER_PROFILE_BALANCED = 1,
+ CC_POWER_PROFILE_POWER_SAVER = 2,
+ NUM_CC_POWER_PROFILES
+} CcPowerProfile;
+
+#define CC_TYPE_POWER_PROFILE_ROW (cc_power_profile_row_get_type())
+G_DECLARE_FINAL_TYPE (CcPowerProfileRow, cc_power_profile_row, CC, POWER_PROFILE_ROW, GtkListBoxRow)
+
+CcPowerProfileRow *cc_power_profile_row_new (CcPowerProfile power_profile);
+CcPowerProfile cc_power_profile_row_get_profile (CcPowerProfileRow *row);
+GtkCheckButton *cc_power_profile_row_get_radio_button (CcPowerProfileRow *row);
+void cc_power_profile_row_set_active (CcPowerProfileRow *row, gboolean active);
+gboolean cc_power_profile_row_get_active (CcPowerProfileRow *row);
+
+CcPowerProfile cc_power_profile_from_str (const char *profile);
+const char *cc_power_profile_to_str (CcPowerProfile profile);
+
+G_END_DECLS
diff --git a/panels/power/cc-power-profile-row.ui b/panels/power/cc-power-profile-row.ui
new file mode 100644
index 0000000..6edcf7c
--- /dev/null
+++ b/panels/power/cc-power-profile-row.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <template class="CcPowerProfileRow" parent="GtkListBoxRow">
+ <property name="selectable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <child>
+ <object class="GtkCheckButton" id="button">
+ <property name="margin-start">6</property>
+ <property name="margin-end">18</property>
+ <accessibility>
+ <property name="label" translatable="yes">Active</property>
+ </accessibility>
+ <signal name="toggled" handler="cc_power_profile_row_button_toggled_cb" object="CcPowerProfileRow" swapped="yes"/>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="halign">start</property>
+ <property name="ellipsize">end</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="use-markup">True</property>
+ <property name="use-underline">True</property>
+ <property name="xalign">0</property>
+ <property name="margin-end">6</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle_label">
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="use-markup">True</property>
+ <property name="use-underline">True</property>
+ <property name="xalign">0</property>
+ <property name="margin-end">6</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ <attributes>
+ <attribute name="scale" value="0.9"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/panels/power/gnome-power-panel.desktop.in.in b/panels/power/gnome-power-panel.desktop.in.in
new file mode 100644
index 0000000..18a1cda
--- /dev/null
+++ b/panels/power/gnome-power-panel.desktop.in.in
@@ -0,0 +1,20 @@
+[Desktop Entry]
+Name=Power
+Comment=View your battery status and change power saving settings
+Exec=gnome-control-center power
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings-power-symbolic
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-DevicesSettings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=power
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: Search terms to find the Power panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Power;Sleep;Suspend;Hibernate;Battery;Brightness;Dim;Blank;Monitor;DPMS;Idle;Energy;
+# Notifications are emitted by gnome-settings-daemon
+X-GNOME-UsesNotifications=true
diff --git a/panels/power/icons/info-symbolic.svg b/panels/power/icons/info-symbolic.svg
new file mode 100644
index 0000000..65d5d5d
--- /dev/null
+++ b/panels/power/icons/info-symbolic.svg
@@ -0,0 +1 @@
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M7.906 1A7.002 7.002 0 0 0 1 8c0 3.867 3.133 7 7 7s7-3.133 7-7-3.133-7-7-7h-.094zM7.5 4h1c.277 0 .5.223.5.5v1c0 .277-.223.5-.5.5h-1a.498.498 0 0 1-.5-.5v-1c0-.277.223-.5.5-.5zM7 7h2v5H7zm0 0" fill="#2e3436"/></svg> \ No newline at end of file
diff --git a/panels/power/icons/meson.build b/panels/power/icons/meson.build
new file mode 100644
index 0000000..4cc814b
--- /dev/null
+++ b/panels/power/icons/meson.build
@@ -0,0 +1,10 @@
+install_data(
+ 'scalable/org.gnome.Settings-power-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
+
+# what an oddity. icons should install to the same location.
+install_data(
+ 'info-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'status')
+)
diff --git a/panels/power/icons/scalable/org.gnome.Settings-power-symbolic.svg b/panels/power/icons/scalable/org.gnome.Settings-power-symbolic.svg
new file mode 100644
index 0000000..72e54cb
--- /dev/null
+++ b/panels/power/icons/scalable/org.gnome.Settings-power-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 5 0 c -1 0 -1 1 -1 1 v 1 h -1 s -0.707031 -0.015625 -1.449219 0.355469 c -0.738281 0.371093 -1.550781 1.3125 -1.550781 2.644531 v 8 s -0.015625 0.707031 0.355469 1.449219 c 0.367187 0.738281 1.3125 1.550781 2.644531 1.550781 h 3 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -3 c -0.554688 0 -1 -0.445312 -1 -1 v -8 c 0 -0.554688 0.445312 -1 1 -1 h 6 c 0.554688 0 1 0.445312 1 1 h 2 c 0 -1.332031 -0.8125 -2.273438 -1.554688 -2.644531 c -0.738281 -0.371094 -1.445312 -0.355469 -1.445312 -0.355469 h -1 v -1 c 0 -1 -1 -1 -1 -1 z m 5 6 c -1.933594 0.277344 -2.722656 2.898438 -3 4 h -2 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 2 c 0.277344 1.101562 1.066406 3.722656 3 4 h 2 s 1 0 1 -1 v -1 h 3 v -2 h -3 v -2 h 3 v -2 h -3 v -1 s 0 -1 -1 -1 z m 0 0" fill="#2e3434"/>
+</svg>
diff --git a/panels/power/meson.build b/panels/power/meson.build
new file mode 100644
index 0000000..1cafe2a
--- /dev/null
+++ b/panels/power/meson.build
@@ -0,0 +1,52 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+sources = files(
+ 'cc-battery-row.c',
+ 'cc-power-panel.c',
+ 'cc-power-profile-row.c',
+ 'cc-power-profile-info-row.c'
+)
+
+resource_data = files(
+ 'cc-battery-row.ui',
+ 'cc-power-panel.ui'
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+deps = common_deps + [
+ gnome_settings_dep,
+ upower_glib_dep
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [ top_inc, common_inc ],
+ dependencies: deps,
+ c_args: cflags
+)
+
+subdir('icons')
diff --git a/panels/power/power-panel-scenario-tester.py b/panels/power/power-panel-scenario-tester.py
new file mode 100755
index 0000000..59860f7
--- /dev/null
+++ b/panels/power/power-panel-scenario-tester.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2021 Red Hat Inc.
+#
+# Author: Bastien Nocera <hadess@hadess.net>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import dbus
+import dbusmock
+import sys
+import os
+import fcntl
+import gi
+import subprocess
+import time
+from collections import OrderedDict
+from dbusmock import DBusTestCase
+from dbus.mainloop.glib import DBusGMainLoop
+from consolemenu import *
+from consolemenu.items import *
+
+gi.require_version('UPowerGlib', '1.0')
+gi.require_version('UMockdev', '1.0')
+
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import UPowerGlib
+from gi.repository import UMockdev
+
+DBusGMainLoop(set_as_default=True)
+
+
+def set_nonblock(fd):
+ '''Set a file object to non-blocking'''
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+def get_templates_dir():
+ return os.path.join(os.path.dirname(__file__), 'dbusmock-templates')
+
+def get_template_path(template_name):
+ return os.path.join(get_templates_dir(), template_name + '.py')
+
+class GccDBusTestCase(DBusTestCase):
+ @classmethod
+ def setUpClass(klass):
+ klass.mocks = OrderedDict()
+
+ # Start system bus
+ DBusTestCase.setUpClass()
+ klass.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE)
+ klass.test_bus.up()
+ os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = klass.test_bus.get_bus_address()
+
+ # Find upower
+ if os.environ.get('UNDER_JHBUILD', False):
+ jhbuild_prefix = os.environ['JHBUILD_PREFIX']
+ klass.upowerd_path = os.path.join(jhbuild_prefix, 'libexec', 'upowerd')
+ if not GLib.file_test(klass.upowerd_path, GLib.FileTest.IS_EXECUTABLE):
+ klass.upowerd_path = None
+
+ if not os.environ.get('UNDER_JHBUILD', False) or klass.upowerd_path == None:
+ klass.upowerd_path = None
+ with open('/usr/share/dbus-1/system-services/org.freedesktop.UPower.service') as f:
+ for line in f:
+ if line.startswith('Exec='):
+ klass.upowerd_path = line.split('=', 1)[1].strip()
+ break
+ assert klass.upowerd_path, 'could not determine daemon path from D-BUS .service file'
+
+ # Start mock udev
+ klass.testbed = UMockdev.Testbed.new()
+
+ # Start ppd and logind
+ klass.start_from_template('power_profiles_daemon')
+ klass.start_from_template('logind')
+
+ klass.system_bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+
+ @classmethod
+ def tearDownClass(klass):
+ for (mock_server, mock_obj) in reversed(klass.mocks.values()):
+ mock_server.terminate()
+ mock_server.wait()
+
+ DBusTestCase.tearDownClass()
+
+ @classmethod
+ def start_from_template(klass, template, params={}):
+ mock_server, mock_obj = \
+ klass.spawn_server_template(template,
+ params,
+ stdout=subprocess.PIPE)
+ set_nonblock(mock_server.stdout)
+
+ mocks = (mock_server, mock_obj)
+ assert klass.mocks.setdefault(template, mocks) == mocks
+ return mocks
+
+ def get_upower_property(self, name):
+ '''Get property value from UPower D-Bus interface.'''
+
+ proxy = Gio.DBusProxy.new_sync(
+ self.system_bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, 'org.freedesktop.UPower',
+ '/org/freedesktop/UPower', 'org.freedesktop.DBus.Properties', None)
+ return proxy.Get('(ss)', 'org.freedesktop.UPower', name)
+
+ def __init__(self):
+ self.devices = {}
+ self.ppd = self.mocks['power_profiles_daemon'][1]
+
+ os.environ['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
+ # See https://github.com/systemd/systemd/pull/21761
+ # os.environ['SYSTEMD_LOG_LEVEL'] = 'debug'
+ self.upowerd = subprocess.Popen([ self.upowerd_path ],
+ env=os.environ, stdout=None,
+ stderr=subprocess.STDOUT)
+
+ # wait until the daemon gets online
+ timeout = 100
+ while timeout > 0:
+ time.sleep(0.1)
+ timeout -= 1
+ try:
+ self.get_upower_property('DaemonVersion')
+ break
+ except GLib.GError:
+ pass
+ else:
+ self.fail('daemon did not start in 10 seconds')
+
+ # self.assertEqual(self.upowerd.poll(), None, 'daemon crashed')
+
+ def toggle_devices(self, device_types):
+ for _type in device_types:
+ if _type not in self.devices:
+ self.devices[_type] = self.add_device(_type)
+ # print('added ' + _type)
+ else:
+ # print('removing ' + _type)
+ devs = self.devices[_type]
+ devs.reverse()
+ for dev in devs:
+ self.testbed.uevent(dev, 'remove')
+ self.testbed.remove_device(dev)
+ del self.devices[_type]
+
+ # out = subprocess.check_output(['upower', '--dump'],
+ # universal_newlines=True)
+ # print(out)
+
+ def add_device(self, device):
+ if device == 'battery':
+ dev = self.testbed.add_device('power_supply', 'BAT0', None,
+ ['type', 'Battery',
+ 'present', '1',
+ 'status', 'Discharging',
+ 'energy_full', '60000000',
+ 'energy_full_design', '80000000',
+ 'energy_now', '48000000',
+ 'voltage_now', '12000000',
+ 'cycle_count', '250'], [])
+ return [ dev ]
+
+ elif device == '2nd-battery':
+ # Not charging or discharging
+ # No cycle count available
+ dev = self.testbed.add_device('power_supply', 'BAT1', None,
+ ['type', 'Battery',
+ 'present', '1',
+ 'status', 'Not charging',
+ 'energy_full', '30000000',
+ 'energy_full_design', '40000000',
+ 'energy_now', '20000000',
+ 'voltage_now', '12000000',
+ 'cycle_count', '-1'], [])
+ return [ dev ]
+
+ elif device == 'ac':
+ dev = self.testbed.add_device('power_supply', 'AC', None,
+ ['type', 'Mains', 'online', '0'], [])
+ return [ dev ]
+
+ elif device == 'keyboard':
+ dev = self.testbed.add_device('bluetooth',
+ 'usb2/bluetooth/hci0/hci0:1',
+ None,
+ [], [])
+ devs = [ dev ]
+
+ parent = dev
+ devs.append(self.testbed.add_device(
+ 'input',
+ 'input3/event4',
+ parent,
+ [], ['DEVNAME', 'input/event4', 'ID_INPUT_KEYBOARD', '1']))
+
+ devs.append(self.testbed.add_device(
+ 'power_supply',
+ 'power_supply/hid-00:22:33:44:55:66-battery',
+ parent,
+ ['type', 'Battery',
+ 'scope', 'Device',
+ 'present', '1',
+ 'online', '1',
+ 'status', 'Discharging',
+ 'capacity', '40',
+ 'model_name', 'Monster Typist'],
+ []))
+ return devs
+
+ elif device == 'mouse':
+ dev = self.testbed.add_device('hid',
+ '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A',
+ None,
+ [], [])
+ devs = [ dev ]
+
+ parent = dev
+ devs.append(self.testbed.add_device('input',
+ '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A/input/input22',
+ parent,
+ [], ['DEVNAME', 'input/mouse3', 'ID_INPUT_MOUSE', '1']))
+
+ devs.append(self.testbed.add_device('power_supply',
+ '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A/power_supply/hidpp_battery_3',
+ parent,
+ ['type', 'Battery',
+ 'scope', 'Device',
+ 'present', '1',
+ 'online', '1',
+ 'status', 'Discharging',
+ 'capacity', '30',
+ 'serial_number', '123456',
+ 'model_name', 'Fancy Mouse'],
+ []))
+ return devs
+
+ elif device == 'ups':
+ dev = self.testbed.add_device('usb', 'hiddev0', None, [],
+ ['DEVNAME', 'null',
+ 'UPOWER_VENDOR', 'APC',
+ 'UPOWER_BATTERY_TYPE', 'ups',
+ 'UPOWER_FAKE_DEVICE', '1',
+ 'UPOWER_FAKE_HID_CHARGING', '0',
+ 'UPOWER_FAKE_HID_PERCENTAGE', '70'])
+ return [ dev ]
+
+ print('Unhandled device')
+ return None
+
+ def cycle_degraded(self):
+ perf = self.ppd.Get('net.hadess.PowerProfiles', 'PerformanceDegraded')
+ if perf == '':
+ perf = 'lap-detected'
+ elif perf == 'lap-detected':
+ perf = 'high-operating-temperature'
+ elif perf == 'high-operating-temperature':
+ perf = ''
+ mock_iface = dbus.Interface(self.ppd, dbusmock.MOCK_IFACE)
+ mock_iface.UpdateProperties('net.hadess.PowerProfiles', {
+ 'PerformanceDegraded': dbus.String(perf, variant_level=1)
+ })
+
+ def start_menu(self):
+ menu = ConsoleMenu("Power Panel", "Scenario Tester", clear_screen = False)
+ function_item = FunctionItem("Toggle Keyboard", self.toggle_devices, [["keyboard"]])
+ menu.append_item(function_item)
+
+ function_item = FunctionItem("Toggle Mouse", self.toggle_devices, [["mouse"]])
+ menu.append_item(function_item)
+
+ function_item = FunctionItem("Toggle UPS", self.toggle_devices, [["ups"]])
+ menu.append_item(function_item)
+
+ function_item = FunctionItem("Toggle laptop battery", self.toggle_devices, [['ac', 'battery']])
+ menu.append_item(function_item)
+
+ function_item = FunctionItem("Toggle 2nd battery", self.toggle_devices, [['2nd-battery']])
+ menu.append_item(function_item)
+
+ function_item = FunctionItem("Cycle degraded performance", self.cycle_degraded, [])
+ menu.append_item(function_item)
+
+ menu.start(show_exit_option=False)
+
+ def wrap_call(self):
+ os.environ['GSETTINGS_BACKEND'] = 'memory'
+
+ wrapper = os.environ.get('META_DBUS_RUNNER_WRAPPER')
+ args = ['gnome-control-center', 'power']
+ if wrapper == 'gdb':
+ args = ['gdb', '-ex', 'r', '-ex', 'bt full', '--args'] + args
+ elif wrapper:
+ args = wrapper.split(' ') + args
+
+ p = subprocess.Popen(args, env=os.environ)
+ p.wait()
+
+if __name__ == '__main__':
+ if 'umockdev' not in os.environ.get('LD_PRELOAD', ''):
+ os.execvp('umockdev-wrapper', ['umockdev-wrapper'] + sys.argv)
+
+ GccDBusTestCase.setUpClass()
+ test_case = GccDBusTestCase()
+ test_case.start_menu()
+ try:
+ test_case.wrap_call()
+ finally:
+ GccDBusTestCase.tearDownClass()
diff --git a/panels/power/power-profiles.css b/panels/power/power-profiles.css
new file mode 100644
index 0000000..1c31493
--- /dev/null
+++ b/panels/power/power-profiles.css
@@ -0,0 +1,7 @@
+.power-profile.low-power {
+ color: @success_color;
+}
+
+.power-profile.performance {
+ color: @error_color;
+}
diff --git a/panels/power/power.gresource.xml b/panels/power/power.gresource.xml
new file mode 100644
index 0000000..5a33c8e
--- /dev/null
+++ b/panels/power/power.gresource.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/power">
+ <file preprocess="xml-stripblanks">cc-battery-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-power-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-power-profile-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-power-profile-info-row.ui</file>
+ <file>battery-levels.css</file>
+ <file>power-profiles.css</file>
+ </gresource>
+</gresources>