diff options
Diffstat (limited to 'panels/power')
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 "Delay" in "Automatic suspend" dialog.">15 minutes</col> + <col id="1">900</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">20 minutes</col> + <col id="1">1200</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">25 minutes</col> + <col id="1">1500</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">30 minutes</col> + <col id="1">1800</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">45 minutes</col> + <col id="1">2700</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">1 hour</col> + <col id="1">3600</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">80 minutes</col> + <col id="1">4800</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">90 minutes</col> + <col id="1">5400</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" dialog.">100 minutes</col> + <col id="1">6000</col> + </row> + <row> + <col id="0" translatable="yes" context="automatic_suspend" comments="Translators: Option for "Delay" in "Automatic suspend" 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> |