diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:36:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:36:24 +0000 |
commit | 9b6d8e63db85c30007b463e91f91a791969fa83f (patch) | |
tree | 0899af51d73c1bf986f73ae39a03c4436083018a /panels/power | |
parent | Initial commit. (diff) | |
download | gnome-control-center-upstream.tar.xz gnome-control-center-upstream.zip |
Adding upstream version 1:3.38.4.upstream/1%3.38.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | panels/power/battery-levels.css | 14 | ||||
-rw-r--r-- | panels/power/cc-brightness-scale.c | 282 | ||||
-rw-r--r-- | panels/power/cc-brightness-scale.h | 38 | ||||
-rw-r--r-- | panels/power/cc-power-panel.c | 2406 | ||||
-rw-r--r-- | panels/power/cc-power-panel.h | 29 | ||||
-rw-r--r-- | panels/power/cc-power-panel.ui | 315 | ||||
-rw-r--r-- | panels/power/gnome-power-panel.desktop.in.in | 20 | ||||
-rw-r--r-- | panels/power/icons/16x16/gnome-power-manager.png | bin | 0 -> 721 bytes | |||
-rw-r--r-- | panels/power/icons/22x22/gnome-power-manager.png | bin | 0 -> 985 bytes | |||
-rw-r--r-- | panels/power/icons/24x24/gnome-power-manager.png | bin | 0 -> 941 bytes | |||
-rw-r--r-- | panels/power/icons/256x256/gnome-power-manager.png | bin | 0 -> 32499 bytes | |||
-rw-r--r-- | panels/power/icons/32x32/gnome-power-manager.png | bin | 0 -> 1989 bytes | |||
-rw-r--r-- | panels/power/icons/48x48/gnome-power-manager.png | bin | 0 -> 3383 bytes | |||
-rw-r--r-- | panels/power/icons/meson.build | 15 | ||||
-rw-r--r-- | panels/power/meson.build | 60 | ||||
-rw-r--r-- | panels/power/power.gresource.xml | 7 |
16 files changed, 3186 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-brightness-scale.c b/panels/power/cc-brightness-scale.c new file mode 100644 index 0000000..5171777 --- /dev/null +++ b/panels/power/cc-brightness-scale.c @@ -0,0 +1,282 @@ +/* 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 "cc-brightness-scale.h" +#include "shell/cc-object-storage.h" + +struct _CcBrightnessScale { + GtkScale parent_instance; + + GCancellable *cancellable; + BrightnessDevice device; + gboolean has_brightness; + GDBusProxy *proxy; + gboolean setting_brightness; +}; + +enum +{ + PROP_0, + PROP_HAS_BRIGHTNESS, + PROP_DEVICE, +}; + +G_DEFINE_TYPE (CcBrightnessScale, cc_brightness_scale, GTK_TYPE_SCALE) + +static void +set_brightness_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) result = NULL; + GDBusProxy *proxy = G_DBUS_PROXY (source_object); + + result = g_dbus_proxy_call_finish (proxy, res, &error); + if (result == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_printerr ("Error setting brightness: %s\n", error->message); + return; + } + + CcBrightnessScale *self = CC_BRIGHTNESS_SCALE (user_data); + + /* not setting, so pay attention to changed signals */ + self->setting_brightness = FALSE; +} + +static void +brightness_slider_value_changed_cb (CcBrightnessScale *self, GtkRange *range) +{ + guint percentage; + g_autoptr(GVariant) variant = NULL; + + percentage = (guint) gtk_range_get_value (range); + + /* do not loop */ + if (self->setting_brightness) + return; + + self->setting_brightness = TRUE; + + if (self->device == BRIGHTNESS_DEVICE_KBD) + variant = g_variant_new_parsed ("('org.gnome.SettingsDaemon.Power.Keyboard'," + "'Brightness', %v)", + g_variant_new_int32 (percentage)); + else + variant = g_variant_new_parsed ("('org.gnome.SettingsDaemon.Power.Screen'," + "'Brightness', %v)", + g_variant_new_int32 (percentage)); + + /* push this to g-s-d */ + g_dbus_proxy_call (self->proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_ref_sink (variant), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->cancellable, + set_brightness_cb, + self); +} + +static void +sync_brightness (CcBrightnessScale *self) +{ + g_autoptr(GVariant) result = NULL; + gint brightness; + GtkRange *range; + + result = g_dbus_proxy_get_cached_property (self->proxy, "Brightness"); + + if (result) + { + /* set the slider */ + brightness = g_variant_get_int32 (result); + self->has_brightness = brightness >= 0.0; + } + else + { + self->has_brightness = FALSE; + } + + g_object_notify (G_OBJECT (self), "has-brightness"); + + if (self->has_brightness) + { + range = GTK_RANGE (self); + self->setting_brightness = TRUE; + gtk_range_set_value (range, brightness); + self->setting_brightness = FALSE; + } +} + +static void +got_proxy_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GError) error = NULL; + CcBrightnessScale *self; + GDBusProxy *proxy; + + self = CC_BRIGHTNESS_SCALE (user_data); + + proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); + if (proxy == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_printerr ("Error creating proxy: %s\n", error->message); + return; + } + + self->proxy = proxy; + + g_signal_connect_object (proxy, "g-properties-changed", + G_CALLBACK (sync_brightness), self, G_CONNECT_SWAPPED); + + sync_brightness (self); +} + +static void +cc_brightness_scale_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcBrightnessScale *self; + + self = CC_BRIGHTNESS_SCALE (object); + + switch (prop_id) { + case PROP_HAS_BRIGHTNESS: + g_value_set_boolean (value, self->has_brightness); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_brightness_scale_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcBrightnessScale *self; + + self = CC_BRIGHTNESS_SCALE (object); + + switch (prop_id) { + case PROP_DEVICE: + self->device = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_brightness_scale_constructed (GObject *object) +{ + CcBrightnessScale *self; + const gchar *interface; + + G_OBJECT_CLASS (cc_brightness_scale_parent_class)->constructed (object); + + self = CC_BRIGHTNESS_SCALE (object); + + self->cancellable = g_cancellable_new(); + + g_signal_connect_object (GTK_SCALE (self), "value-changed", + G_CALLBACK (brightness_slider_value_changed_cb), self, G_CONNECT_SWAPPED); + + if (self->device == BRIGHTNESS_DEVICE_KBD) + interface = "org.gnome.SettingsDaemon.Power.Keyboard"; + else + interface = "org.gnome.SettingsDaemon.Power.Screen"; + + cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Power", + "/org/gnome/SettingsDaemon/Power", + interface, + self->cancellable, + got_proxy_cb, + self); + + gtk_range_set_range (GTK_RANGE (self), 0, 100); + gtk_range_set_increments (GTK_RANGE (self), 1, 10); + gtk_range_set_round_digits (GTK_RANGE (self), 0); + gtk_scale_set_draw_value (GTK_SCALE (self), FALSE); +} + +static void +cc_brightness_scale_finalize (GObject *object) +{ + CcBrightnessScale *self = CC_BRIGHTNESS_SCALE (object); + + g_cancellable_cancel (self->cancellable); + + G_OBJECT_CLASS (cc_brightness_scale_parent_class)->finalize (object); +} + +void +cc_brightness_scale_class_init (CcBrightnessScaleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_brightness_scale_get_property; + object_class->set_property = cc_brightness_scale_set_property; + object_class->constructed = cc_brightness_scale_constructed; + object_class->finalize = cc_brightness_scale_finalize; + + g_object_class_install_property (object_class, + PROP_DEVICE, + g_param_spec_enum ("device", + "device", + "device", + brightness_device_get_type(), + BRIGHTNESS_DEVICE_SCREEN, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_HAS_BRIGHTNESS, + g_param_spec_boolean ("has-brightness", + "has brightness", + "has brightness", + FALSE, + G_PARAM_READABLE)); +} + +static void +cc_brightness_scale_init (CcBrightnessScale *self) +{ +} + + +gboolean +cc_brightness_scale_get_has_brightness (CcBrightnessScale *self) +{ + g_return_val_if_fail (CC_IS_BRIGHTNESS_SCALE (self), FALSE); + + return self->has_brightness; +} diff --git a/panels/power/cc-brightness-scale.h b/panels/power/cc-brightness-scale.h new file mode 100644 index 0000000..b55d05b --- /dev/null +++ b/panels/power/cc-brightness-scale.h @@ -0,0 +1,38 @@ +/* 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 "cc-brightness-scale-types.h" + +G_BEGIN_DECLS + +typedef enum { + BRIGHTNESS_DEVICE_SCREEN, + BRIGHTNESS_DEVICE_KBD, +} BrightnessDevice; + +#define CC_TYPE_BRIGHTNESS_SCALE (cc_brightness_scale_get_type()) +G_DECLARE_FINAL_TYPE (CcBrightnessScale, cc_brightness_scale, CC, BRIGHTNESS_SCALE, GtkBox) + +gboolean cc_brightness_scale_get_has_brightness (CcBrightnessScale*); + +G_END_DECLS diff --git a/panels/power/cc-power-panel.c b/panels/power/cc-power-panel.c new file mode 100644 index 0000000..044f633 --- /dev/null +++ b/panels/power/cc-power-panel.c @@ -0,0 +1,2406 @@ +/* -*- 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> + +#ifdef HAVE_NETWORK_MANAGER +#include <NetworkManager.h> +#endif + +#include "shell/cc-object-storage.h" +#include "list-box-helper.h" +#include "cc-brightness-scale.h" +#include "cc-power-panel.h" +#include "cc-power-resources.h" +#include "cc-util.h" + +/* Uncomment this to test the behaviour of the panel in + * battery-less situations: + * + * #define TEST_NO_BATTERIES + */ + +/* Uncomment this to test the behaviour of the panel with + * multiple appearing devices + * + * #define TEST_FAKE_DEVICES + */ + +/* Uncomment this to test the behaviour of a desktop machine + * with a UPS + * + * #define TEST_UPS + */ + +struct _CcPowerPanel +{ + CcPanel parent_instance; + + GSettings *gsd_settings; + GSettings *session_settings; + GSettings *interface_settings; + GtkWidget *main_scroll; + GtkWidget *main_box; + GtkWidget *vbox_power; + GtkWidget *suspend_on_battery_switch; + GtkWidget *suspend_on_battery_label; + GtkWidget *suspend_on_battery_delay_label; + GtkWidget *suspend_on_battery_delay_combo; + GtkWidget *suspend_on_ac_switch; + GtkWidget *suspend_on_ac_label; + GtkWidget *suspend_on_ac_delay_combo; + GtkWidget *automatic_suspend_dialog; + GtkListStore *liststore_idle_time; + GtkListStore *liststore_power_button; + UpClient *up_client; + GPtrArray *devices; + gboolean has_batteries; + char *chassis_type; + + GList *boxes; + GList *boxes_reverse; + + GtkSizeGroup *battery_row_sizegroup; + GtkSizeGroup *row_sizegroup; + GtkSizeGroup *battery_sizegroup; + GtkSizeGroup *charge_sizegroup; + GtkSizeGroup *level_sizegroup; + + GtkWidget *battery_heading; + GtkWidget *battery_section; + GtkWidget *battery_list; + + GtkWidget *device_heading; + GtkWidget *device_section; + GtkWidget *device_list; + + GtkWidget *dim_screen_row; + GtkWidget *brightness_row; + CcBrightnessScale *brightness_scale; + GtkWidget *kbd_brightness_row; + CcBrightnessScale *kbd_brightness_scale; + + GtkWidget *automatic_suspend_row; + GtkWidget *automatic_suspend_label; + + GDBusProxy *bt_rfkill; + GDBusProxy *bt_properties; + GtkWidget *bt_switch; + GtkWidget *bt_row; + + GDBusProxy *iio_proxy; + guint iio_proxy_watch_id; + GtkWidget *als_switch; + GtkWidget *als_row; + + GtkWidget *power_button_combo; + GtkWidget *idle_delay_combo; + +#ifdef HAVE_NETWORK_MANAGER + NMClient *nm_client; + GtkWidget *wifi_switch; + GtkWidget *wifi_row; + GtkWidget *mobile_switch; + GtkWidget *mobile_row; +#endif + + GtkAdjustment *focus_adjustment; +}; + +CC_PANEL_REGISTER (CcPowerPanel, cc_power_panel) + +enum +{ + ACTION_MODEL_TEXT, + ACTION_MODEL_VALUE +}; + +static void +cc_power_panel_dispose (GObject *object) +{ + CcPowerPanel *self = CC_POWER_PANEL (object); + + 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 (&self->automatic_suspend_dialog, gtk_widget_destroy); + g_clear_pointer (&self->devices, g_ptr_array_unref); + g_clear_object (&self->up_client); + g_clear_object (&self->bt_rfkill); + g_clear_object (&self->bt_properties); + g_clear_object (&self->iio_proxy); +#ifdef HAVE_NETWORK_MANAGER + g_clear_object (&self->nm_client); +#endif + g_clear_pointer (&self->boxes, g_list_free); + g_clear_pointer (&self->boxes_reverse, g_list_free); + 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 const char * +cc_power_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/power"; +} + +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, main_scroll); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, main_box); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, vbox_power); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_dialog); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, suspend_on_battery_switch); + 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_delay_label); + 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_ac_switch); + 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_delay_combo); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, automatic_suspend_dialog); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, liststore_idle_time); + gtk_widget_class_bind_template_child (widget_class, CcPowerPanel, liststore_power_button); +} + +static GtkWidget * +no_prelight_row_new (void) +{ + return (GtkWidget *) g_object_new (GTK_TYPE_LIST_BOX_ROW, + "selectable", FALSE, + "activatable", FALSE, + NULL); +} + +static GtkWidget * +row_box_new (void) +{ + return (GtkWidget *) g_object_new (GTK_TYPE_BOX, + "margin-end", 12, + "margin-start", 12, + "spacing", 12, + "visible", TRUE, + NULL); +} + +static GtkWidget * +row_title_new (const gchar *title, + const gchar *subtitle, + GtkWidget **title_label) +{ + PangoAttrList *attributes; + GtkWidget *box, *label; + + box = (GtkWidget *) g_object_new (GTK_TYPE_BOX, + "spacing", 4, + "margin-bottom", 6, + "margin-top", 6, + "orientation", GTK_ORIENTATION_VERTICAL, + "valign", GTK_ALIGN_CENTER, + "visible", TRUE, + NULL); + + label = (GtkWidget *) g_object_new (GTK_TYPE_LABEL, + "ellipsize", PANGO_ELLIPSIZE_END, + "halign", GTK_ALIGN_START, + "label", title, + "use-markup", TRUE, + "use-underline", TRUE, + "visible", TRUE, + "xalign", 0.0, + NULL); + if (title_label) + *title_label = label; + gtk_container_add (GTK_CONTAINER (box), label); + + if (subtitle == NULL) + return box; + + attributes = pango_attr_list_new (); + pango_attr_list_insert (attributes, pango_attr_scale_new (0.9)); + + label = (GtkWidget *) g_object_new (GTK_TYPE_LABEL, + "ellipsize", PANGO_ELLIPSIZE_END, + "halign", GTK_ALIGN_START, + "label", subtitle, + "use-markup", TRUE, + "use-underline", TRUE, + "visible", TRUE, + "xalign", 0.0, + "attributes", attributes, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (label), + GTK_STYLE_CLASS_DIM_LABEL); + gtk_container_add (GTK_CONTAINER (box), label); + + pango_attr_list_unref (attributes); + + return box; +} + +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 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) + { + timestring = g_strdup (_("Unknown time")); + return timestring; + } + + if (minutes < 60) + { + timestring = g_strdup_printf (ngettext ("%i minute", + "%i minutes", + minutes), minutes); + return timestring; + } + + hours = minutes / 60; + minutes = minutes % 60; + + if (minutes == 0) + { + timestring = g_strdup_printf (ngettext ( + "%i hour", + "%i hours", + hours), hours); + return timestring; + } + + /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes" + * Swap order with "%2$s %2$i %1$s %1$i if needed */ + timestring = g_strdup_printf (_("%i %s %i %s"), + hours, ngettext ("hour", "hours", hours), + minutes, ngettext ("minute", "minutes", minutes)); + return timestring; +} + +static gchar * +get_details_string (gdouble percentage, UpDeviceState state, guint64 time) +{ + gchar *details; + + 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 details; +} + +static void +load_custom_css (CcPowerPanel *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + /* use custom CSS */ + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, "/org/gnome/control-center/power/battery-levels.css"); + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +static void +set_primary (CcPowerPanel *panel, UpDevice *device) +{ + g_autofree gchar *details = NULL; + gdouble percentage; + guint64 time_empty, time_full, time; + UpDeviceState state; + GtkWidget *box, *box2, *label; + GtkWidget *levelbar, *row; + g_autofree gchar *s = NULL; + gdouble energy_full, energy_rate; + + g_object_get (device, + "state", &state, + "percentage", &percentage, + "time-to-empty", &time_empty, + "time-to-full", &time_full, + "energy-full", &energy_full, + "energy-rate", &energy_rate, + NULL); + if (state == UP_DEVICE_STATE_DISCHARGING) + time = time_empty; + else + time = time_full; + + /* Sometimes the reported state is fully charged but battery is at 99%, + refusing to reach 100%. In these cases, just assume 100%. */ + if (state == UP_DEVICE_STATE_FULLY_CHARGED && (100.0 - percentage <= 1.0)) + percentage = 100.0; + + details = get_details_string (percentage, state, time); + + row = no_prelight_row_new (); + gtk_widget_show (row); + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (row), box); + + gtk_widget_set_margin_start (box, 12); + gtk_widget_set_margin_end (box, 12); + gtk_widget_set_margin_top (box, 16); + gtk_widget_set_margin_bottom (box, 14); + + levelbar = gtk_level_bar_new (); + gtk_widget_show (levelbar); + gtk_level_bar_set_value (GTK_LEVEL_BAR (levelbar), percentage / 100.0); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "warning-battery-offset", 0.03); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "low-battery-offset", 0.1); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "high-battery-offset", 1.0); + gtk_widget_set_hexpand (levelbar, TRUE); + gtk_widget_set_halign (levelbar, GTK_ALIGN_FILL); + gtk_widget_set_valign (levelbar, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), levelbar, TRUE, TRUE, 0); + + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (box2); + gtk_box_pack_start (GTK_BOX (box), box2, FALSE, TRUE, 0); + + label = gtk_label_new (details); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (box2), label, TRUE, TRUE, 0); + + s = g_strdup_printf ("%d%%", (int)(percentage + 0.5)); + label = gtk_label_new (s); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL); + gtk_box_pack_start (GTK_BOX (box2), label, FALSE, TRUE, 0); + + atk_object_add_relationship (gtk_widget_get_accessible (levelbar), + ATK_RELATION_LABELLED_BY, + gtk_widget_get_accessible (label)); + + gtk_container_add (GTK_CONTAINER (panel->battery_list), row); + gtk_size_group_add_widget (panel->battery_row_sizegroup, row); + + g_object_set_data (G_OBJECT (row), "primary", GINT_TO_POINTER (TRUE)); + + gtk_widget_set_visible (panel->battery_section, TRUE); +} + +static void +add_battery (CcPowerPanel *panel, UpDevice *device) +{ + gdouble percentage; + UpDeviceKind kind; + UpDeviceState state; + GtkWidget *row; + GtkWidget *box; + GtkWidget *box2; + GtkWidget *label; + GtkWidget *title; + GtkWidget *levelbar; + GtkWidget *widget; + g_autofree gchar *s = NULL; + g_autofree gchar *icon_name = NULL; + const gchar *name; + + g_object_get (device, + "kind", &kind, + "state", &state, + "percentage", &percentage, + "icon-name", &icon_name, + NULL); + + if (g_object_get_data (G_OBJECT (device), "is-main-battery") != NULL) + name = C_("Battery name", "Main"); + else + name = C_("Battery name", "Extra"); + + row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_box_set_spacing (GTK_BOX (box), 10); + gtk_container_add (GTK_CONTAINER (row), box); + + gtk_widget_set_margin_start (box, 12); + gtk_widget_set_margin_end (box, 12); + gtk_widget_set_margin_top (box, 16); + gtk_widget_set_margin_bottom (box, 14); + + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (box2); + title = row_title_new (name, NULL, NULL); + gtk_size_group_add_widget (panel->battery_sizegroup, box2); + gtk_box_pack_start (GTK_BOX (box2), title, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), box2, FALSE, TRUE, 0); + +#if 1 + if (icon_name != NULL && *icon_name != '\0') + { + widget = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (widget); + gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_DIM_LABEL); + gtk_widget_set_halign (widget, GTK_ALIGN_END); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box2), widget, TRUE, TRUE, 0); + } +#endif + + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (box2); + + s = g_strdup_printf ("%d%%", (int)percentage); + label = gtk_label_new (s); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL); + gtk_box_pack_start (GTK_BOX (box2), label, FALSE, TRUE, 0); + gtk_size_group_add_widget (panel->charge_sizegroup, label); + + levelbar = gtk_level_bar_new (); + gtk_widget_show (levelbar); + gtk_level_bar_set_value (GTK_LEVEL_BAR (levelbar), percentage / 100.0); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "warning-battery-offset", 0.05); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "low-battery-offset", 0.1); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (levelbar), "high-battery-offset", 1.0); + gtk_widget_set_hexpand (levelbar, TRUE); + gtk_widget_set_halign (levelbar, GTK_ALIGN_FILL); + gtk_widget_set_valign (levelbar, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box2), levelbar, TRUE, TRUE, 0); + gtk_size_group_add_widget (panel->level_sizegroup, levelbar); + gtk_box_pack_start (GTK_BOX (box), box2, TRUE, TRUE, 0); + + atk_object_add_relationship (gtk_widget_get_accessible (levelbar), + ATK_RELATION_LABELLED_BY, + gtk_widget_get_accessible (label)); + + + g_object_set_data (G_OBJECT (row), "kind", GINT_TO_POINTER (kind)); + gtk_container_add (GTK_CONTAINER (panel->battery_list), row); + gtk_size_group_add_widget (panel->battery_row_sizegroup, row); + + gtk_widget_set_visible (panel->battery_section, TRUE); +} + +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 (); +} + +static UpDeviceLevel +get_battery_level (UpDevice *device) +{ + UpDeviceLevel battery_level; + + if (!g_object_class_find_property (G_OBJECT_CLASS (G_OBJECT_GET_CLASS (device)), "battery-level")) + return UP_DEVICE_LEVEL_NONE; + + g_object_get (device, "battery-level", &battery_level, NULL); + return battery_level; +} + +static void +add_device (CcPowerPanel *panel, UpDevice *device) +{ + UpDeviceKind kind; + UpDeviceState state; + GtkWidget *row; + GtkWidget *hbox; + GtkWidget *box2; + GtkWidget *widget; + GtkWidget *title; + g_autoptr(GString) status = NULL; + g_autoptr(GString) description = NULL; + gdouble percentage; + g_autofree gchar *name = NULL; + gboolean show_caution = FALSE; + gboolean is_present; + UpDeviceLevel battery_level; + + g_object_get (device, + "kind", &kind, + "percentage", &percentage, + "state", &state, + "model", &name, + "is-present", &is_present, + NULL); + battery_level = get_battery_level (device); + + if (!is_present) + return; + + if (kind == UP_DEVICE_KIND_UPS) + show_caution = TRUE; + + if (name == NULL || *name == '\0') + description = g_string_new (_(kind_to_description (kind))); + else + description = g_string_new (name); + + switch (state) + { + case UP_DEVICE_STATE_CHARGING: + case UP_DEVICE_STATE_PENDING_CHARGE: + /* TRANSLATORS: secondary battery */ + status = g_string_new(C_("Battery power", "Charging")); + break; + case UP_DEVICE_STATE_DISCHARGING: + case UP_DEVICE_STATE_PENDING_DISCHARGE: + if (percentage < 10 && show_caution) + { + /* TRANSLATORS: secondary battery */ + status = g_string_new (C_("Battery power", "Caution")); + } + else if (percentage < 30) + { + /* TRANSLATORS: secondary battery */ + status = g_string_new (C_("Battery power", "Low")); + } + else + { + /* TRANSLATORS: secondary battery */ + status = g_string_new (C_("Battery power", "Good")); + } + break; + case UP_DEVICE_STATE_FULLY_CHARGED: + /* TRANSLATORS: primary battery */ + status = g_string_new (C_("Battery power", "Fully charged")); + break; + case UP_DEVICE_STATE_EMPTY: + /* TRANSLATORS: primary battery */ + status = g_string_new (C_("Battery power", "Empty")); + break; + default: + status = g_string_new (up_device_state_to_string (state)); + break; + } + g_string_prepend (status, "<small>"); + g_string_append (status, "</small>"); + + /* create the new widget */ + row = no_prelight_row_new (); + gtk_widget_show (row); + hbox = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), hbox); + title = row_title_new (description->str, NULL, NULL); + gtk_box_pack_start (GTK_BOX (hbox), title, FALSE, TRUE, 0); + gtk_size_group_add_widget (panel->battery_sizegroup, title); + + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (box2); + + if (battery_level == UP_DEVICE_LEVEL_NONE) + { + g_autofree gchar *s = NULL; + + s = g_strdup_printf ("%d%%", (int)(percentage + 0.5)); + widget = gtk_label_new (s); + } + else + { + widget = gtk_label_new (""); + } + + gtk_widget_show (widget); + gtk_widget_set_halign (widget, GTK_ALIGN_END); + gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); + gtk_label_set_xalign (GTK_LABEL (widget), 0.0); + gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_DIM_LABEL); + gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, TRUE, 0); + gtk_size_group_add_widget (panel->charge_sizegroup, widget); + + widget = gtk_level_bar_new (); + gtk_widget_show (widget); + gtk_widget_set_halign (widget, TRUE); + gtk_widget_set_halign (widget, GTK_ALIGN_FILL); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_level_bar_set_value (GTK_LEVEL_BAR (widget), percentage / 100.0f); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (widget), "warning-battery-offset", 0.03); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (widget), "low-battery-offset", 0.1); + gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (widget), "high-battery-offset", 1.0); + gtk_box_pack_start (GTK_BOX (box2), widget, TRUE, TRUE, 0); + gtk_size_group_add_widget (panel->level_sizegroup, widget); + gtk_box_pack_start (GTK_BOX (hbox), box2, TRUE, TRUE, 0); + + gtk_container_add (GTK_CONTAINER (panel->device_list), row); + gtk_size_group_add_widget (panel->row_sizegroup, row); + g_object_set_data (G_OBJECT (row), "kind", GINT_TO_POINTER (kind)); + + gtk_widget_set_visible (panel->device_section, TRUE); +} + +static void +up_client_changed (CcPowerPanel *self) +{ + g_autoptr(GList) battery_children = NULL; + g_autoptr(GList) device_children = NULL; + GList *l; + gint i; + UpDeviceKind kind; + guint n_batteries; + gboolean on_ups; + g_autoptr(UpDevice) composite = NULL; + g_autofree gchar *s = NULL; + + battery_children = gtk_container_get_children (GTK_CONTAINER (self->battery_list)); + for (l = battery_children; l != NULL; l = l->next) + gtk_container_remove (GTK_CONTAINER (self->battery_list), l->data); + gtk_widget_hide (self->battery_section); + + device_children = gtk_container_get_children (GTK_CONTAINER (self->device_list)); + for (l = device_children; l != NULL; l = l->next) + gtk_container_remove (GTK_CONTAINER (self->device_list), l->data); + gtk_widget_hide (self->device_section); + +#ifdef TEST_FAKE_DEVICES + { + static gboolean fake_devices_added = FALSE; + UpDevice *device; + + if (!fake_devices_added) + { + fake_devices_added = TRUE; + g_print ("adding fake devices\n"); + device = up_device_new (); + g_object_set (device, + "kind", UP_DEVICE_KIND_MOUSE, + "native-path", "dummy:native-path1", + "model", "My mouse", + "percentage", 71.0, + "state", UP_DEVICE_STATE_DISCHARGING, + "time-to-empty", 287, + "icon-name", "battery-full-symbolic", + "power-supply", FALSE, + "is-present", TRUE, + "battery-level", UP_DEVICE_LEVEL_NORMAL, + NULL); + g_ptr_array_add (self->devices, device); + device = up_device_new (); + g_object_set (device, + "kind", UP_DEVICE_KIND_KEYBOARD, + "native-path", "dummy:native-path2", + "model", "My keyboard", + "percentage", 59.0, + "state", UP_DEVICE_STATE_DISCHARGING, + "time-to-empty", 250, + "icon-name", "battery-good-symbolic", + "power-supply", FALSE, + "is-present", TRUE, + "battery-level", UP_DEVICE_LEVEL_NONE, + NULL); + g_ptr_array_add (self->devices, device); + device = up_device_new (); + g_object_set (device, + "kind", UP_DEVICE_KIND_BATTERY, + "native-path", "dummy:native-path3", + "model", "Battery from some factory", + "percentage", 100.0, + "state", UP_DEVICE_STATE_FULLY_CHARGED, + "energy", 55.0, + "energy-full", 55.0, + "energy-rate", 15.0, + "time-to-empty", 400, + "time-to-full", 0, + "icon-name", "battery-full-charged-symbolic", + "power-supply", TRUE, + "is-present", TRUE, + "battery-level", UP_DEVICE_LEVEL_NONE, + NULL); + g_ptr_array_add (self->devices, device); + } + } +#endif + +#ifdef TEST_UPS + { + static gboolean fake_devices_added = FALSE; + UpDevice *device; + + if (!fake_devices_added) + { + fake_devices_added = TRUE; + g_print ("adding fake UPS\n"); + device = up_device_new (); + g_object_set (device, + "kind", UP_DEVICE_KIND_UPS, + "native-path", "dummy:usb-hiddev0", + "model", "APC UPS", + "percentage", 70.0, + "state", UP_DEVICE_STATE_DISCHARGING, + "is-present", TRUE, + "power-supply", TRUE, + "battery-level", UP_DEVICE_LEVEL_NONE, + NULL); + g_ptr_array_add (self->devices, device); + } + } +#endif + + 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) + s = g_strdup_printf ("<b>%s</b>", _("Batteries")); + else + s = g_strdup_printf ("<b>%s</b>", _("Battery")); + gtk_label_set_label (GTK_LABEL (self->battery_heading), s); + + if (!on_ups && n_batteries > 1) + set_primary (self, composite); + + 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) + { + set_primary (self, device); + } + else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply && !on_ups && n_batteries == 1) + { + set_primary (self, device); + } + else if (kind == UP_DEVICE_KIND_BATTERY && is_power_supply) + { + add_battery (self, device); + } + else + { + add_device (self, device); + } + } +} + +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 (CcPowerPanel *self) +{ + gboolean enabled; + enabled = gtk_switch_get_active (GTK_SWITCH (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 has_brightness = FALSE; + gboolean visible = FALSE; + + has_brightness = cc_brightness_scale_get_has_brightness (self->brightness_scale); + + 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); + } + + 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, self); + gtk_switch_set_active (GTK_SWITCH (self->als_switch), enabled); + gtk_widget_set_visible (self->als_row, visible && has_brightness); + g_signal_handlers_unblock_by_func (self->als_switch, als_switch_changed, 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_ac_battery_ui_mode (CcPowerPanel *self) +{ + gboolean has_batteries = FALSE; + GPtrArray *devices; + guint i; + + 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)) + { + has_batteries = TRUE; + break; + } + } + g_clear_pointer (&devices, g_ptr_array_unref); + +#ifdef TEST_NO_BATTERIES + g_print ("forcing no batteries\n"); + has_batteries = FALSE; +#endif + + self->has_batteries = has_batteries; + + if (!has_batteries) + { + gtk_widget_hide (self->suspend_on_battery_switch); + gtk_widget_hide (self->suspend_on_battery_label); + gtk_widget_hide (self->suspend_on_battery_delay_label); + gtk_widget_hide (self->suspend_on_battery_delay_combo); + gtk_label_set_label (GTK_LABEL (self->suspend_on_ac_label), + _("When _idle")); + } +} + +static void +bt_set_powered (CcPowerPanel *self, + gboolean powered) +{ + g_dbus_proxy_call (self->bt_properties, + "Set", + g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill', 'BluetoothAirplaneMode', %v)", + g_variant_new_boolean (!powered)), + G_DBUS_CALL_FLAGS_NONE, + -1, + cc_panel_get_cancellable (CC_PANEL (self)), + NULL, NULL); +} + +static void +bt_switch_changed (CcPowerPanel *self) +{ + gboolean powered; + + powered = gtk_switch_get_active (GTK_SWITCH (self->bt_switch)); + + g_debug ("Setting bt power %s", powered ? "on" : "off"); + + bt_set_powered (self, powered); +} + +static void +bt_powered_state_changed (CcPowerPanel *panel) +{ + gboolean powered, has_airplane_mode; + g_autoptr(GVariant) v1 = NULL; + g_autoptr(GVariant) v2 = NULL; + + v1 = g_dbus_proxy_get_cached_property (panel->bt_rfkill, "BluetoothHasAirplaneMode"); + has_airplane_mode = g_variant_get_boolean (v1); + + if (!has_airplane_mode) + { + g_debug ("BluetoothHasAirplaneMode is false, hiding Bluetooth power row"); + gtk_widget_hide (panel->bt_row); + return; + } + + v2 = g_dbus_proxy_get_cached_property (panel->bt_rfkill, "BluetoothAirplaneMode"); + powered = !g_variant_get_boolean (v2); + + g_debug ("bt powered state changed to %s", powered ? "on" : "off"); + + gtk_widget_show (panel->bt_row); + + g_signal_handlers_block_by_func (panel->bt_switch, bt_switch_changed, panel); + gtk_switch_set_active (GTK_SWITCH (panel->bt_switch), powered); + g_signal_handlers_unblock_by_func (panel->bt_switch, bt_switch_changed, panel); +} + +#ifdef HAVE_NETWORK_MANAGER +static gboolean +has_wifi_devices (NMClient *client) +{ + const GPtrArray *devices; + NMDevice *device; + gint i; + + if (!nm_client_get_nm_running (client)) + return FALSE; + + devices = nm_client_get_devices (client); + if (devices == NULL) + return FALSE; + + for (i = 0; i < devices->len; i++) + { + device = g_ptr_array_index (devices, i); + switch (nm_device_get_device_type (device)) + { + case NM_DEVICE_TYPE_WIFI: + return TRUE; + default: + break; + } + } + + return FALSE; +} + +static void +wifi_switch_changed (CcPowerPanel *self) +{ + gboolean enabled; + + enabled = gtk_switch_get_active (GTK_SWITCH (self->wifi_switch)); + g_debug ("Setting wifi %s", enabled ? "enabled" : "disabled"); + nm_client_wireless_set_enabled (self->nm_client, enabled); +} + +static gboolean +has_mobile_devices (NMClient *client) +{ + const GPtrArray *devices; + NMDevice *device; + gint i; + + if (!nm_client_get_nm_running (client)) + return FALSE; + + devices = nm_client_get_devices (client); + if (devices == NULL) + return FALSE; + + for (i = 0; i < devices->len; i++) + { + device = g_ptr_array_index (devices, i); + switch (nm_device_get_device_type (device)) + { + case NM_DEVICE_TYPE_WIMAX: + case NM_DEVICE_TYPE_MODEM: + return TRUE; + default: + break; + } + } + + return FALSE; +} + +static void +mobile_switch_changed (CcPowerPanel *self) +{ + gboolean enabled; + + enabled = gtk_switch_get_active (GTK_SWITCH (self->mobile_switch)); + g_debug ("Setting wwan %s", enabled ? "enabled" : "disabled"); + nm_client_wwan_set_enabled (self->nm_client, enabled); + g_debug ("Setting wimax %s", enabled ? "enabled" : "disabled"); + nm_client_wimax_set_enabled (self->nm_client, enabled); +} + +static void +nm_client_state_changed (CcPowerPanel *self) +{ + gboolean visible; + gboolean active; + gboolean sensitive; + + visible = has_wifi_devices (self->nm_client); + active = nm_client_networking_get_enabled (self->nm_client) && + nm_client_wireless_get_enabled (self->nm_client) && + nm_client_wireless_hardware_get_enabled (self->nm_client); + sensitive = nm_client_networking_get_enabled (self->nm_client) && + nm_client_wireless_hardware_get_enabled (self->nm_client); + + g_debug ("wifi state changed to %s", active ? "enabled" : "disabled"); + + g_signal_handlers_block_by_func (self->wifi_switch, wifi_switch_changed, self); + gtk_switch_set_active (GTK_SWITCH (self->wifi_switch), active); + gtk_widget_set_sensitive (self->wifi_switch, sensitive); + gtk_widget_set_visible (self->wifi_row, visible); + g_signal_handlers_unblock_by_func (self->wifi_switch, wifi_switch_changed, self); + + visible = has_mobile_devices (self->nm_client); + + /* Set the switch active, if either of wimax or wwan is enabled. */ + active = nm_client_networking_get_enabled (self->nm_client) && + ((nm_client_wimax_get_enabled (self->nm_client) && + nm_client_wimax_hardware_get_enabled (self->nm_client)) || + (nm_client_wwan_get_enabled (self->nm_client) && + nm_client_wwan_hardware_get_enabled (self->nm_client))); + sensitive = nm_client_networking_get_enabled (self->nm_client) && + (nm_client_wwan_hardware_get_enabled (self->nm_client) || + nm_client_wimax_hardware_get_enabled (self->nm_client)); + + g_debug ("mobile state changed to %s", active ? "enabled" : "disabled"); + + g_signal_handlers_block_by_func (self->mobile_switch, mobile_switch_changed, self); + gtk_switch_set_active (GTK_SWITCH (self->mobile_switch), active); + gtk_widget_set_sensitive (self->mobile_switch, sensitive); + gtk_widget_set_visible (self->mobile_row, visible); + g_signal_handlers_unblock_by_func (self->mobile_switch, mobile_switch_changed, self); +} + +static void +nm_device_changed (CcPowerPanel *self) +{ + gtk_widget_set_visible (self->wifi_row, has_wifi_devices (self->nm_client)); + gtk_widget_set_visible (self->mobile_row, has_mobile_devices (self->nm_client)); +} + +static void +setup_nm_client (CcPowerPanel *self, + NMClient *client) +{ + self->nm_client = client; + + g_signal_connect_object (self->nm_client, "notify", + G_CALLBACK (nm_client_state_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->nm_client, "device-added", + G_CALLBACK (nm_device_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (self->nm_client, "device-removed", + G_CALLBACK (nm_device_changed), self, G_CONNECT_SWAPPED); + + nm_client_state_changed (self); + nm_device_changed (self); +} + +static void +nm_client_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcPowerPanel *self; + NMClient *client; + g_autoptr(GError) error = NULL; + + client = nm_client_new_finish (res, &error); + if (!client) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("Failed to create NetworkManager client: %s", + error->message); + + self = user_data; + gtk_widget_set_sensitive (self->wifi_row, FALSE); + gtk_widget_set_sensitive (self->mobile_row, FALSE); + } + return; + } + + self = user_data; + + /* Setup the client */ + setup_nm_client (self, client); + + /* Store the object in the cache too */ + cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client); +} + +#endif + +static gboolean +keynav_failed (CcPowerPanel *self, GtkDirectionType direction, GtkWidget *list) +{ + GtkWidget *next_list = NULL; + GList *item, *boxes_list; + gdouble value, lower, upper, page; + + /* Find the list in the list of GtkListBoxes */ + if (direction == GTK_DIR_DOWN) + boxes_list = self->boxes; + else + boxes_list = self->boxes_reverse; + + item = g_list_find (boxes_list, list); + g_assert (item); + item = item->next; + while (1) + { + if (item == NULL) + item = boxes_list; + + /* Avoid looping */ + if (item->data == list) + break; + + if (gtk_widget_is_visible (item->data)) + { + next_list = item->data; + break; + } + + item = item->next; + } + + if (next_list) + { + gtk_widget_child_focus (next_list, direction); + return TRUE; + } + + value = gtk_adjustment_get_value (self->focus_adjustment); + lower = gtk_adjustment_get_lower (self->focus_adjustment); + upper = gtk_adjustment_get_upper (self->focus_adjustment); + page = gtk_adjustment_get_page_size (self->focus_adjustment); + + if (direction == GTK_DIR_UP && value > lower) + { + gtk_adjustment_set_value (self->focus_adjustment, lower); + return TRUE; + } + else if (direction == GTK_DIR_DOWN && value < upper - page) + { + gtk_adjustment_set_value (self->focus_adjustment, upper - page); + return TRUE; + } + + return FALSE; +} + +static void +combo_idle_delay_changed_cb (CcPowerPanel *self) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gint value; + gboolean ret; + + /* no selection */ + ret = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->idle_delay_combo), &iter); + if (!ret) + return; + + /* get entry */ + model = gtk_combo_box_get_model (GTK_COMBO_BOX (self->idle_delay_combo)); + gtk_tree_model_get (model, &iter, + 1, &value, + -1); + + /* set both keys */ + g_settings_set_uint (self->session_settings, "idle-delay", value); +} + +static void +combo_power_button_changed_cb (CcPowerPanel *self) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gint value; + gboolean ret; + + /* no selection */ + ret = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->power_button_combo), &iter); + if (!ret) + return; + + /* get entry */ + model = gtk_combo_box_get_model (GTK_COMBO_BOX (self->power_button_combo)); + gtk_tree_model_get (model, &iter, + 1, &value, + -1); + + /* set both keys */ + g_settings_set_enum (self->gsd_settings, "power-button-action", value); +} + +static GtkWidget * +add_brightness_row (CcPowerPanel *self, + BrightnessDevice device, + const char *text, + CcBrightnessScale **brightness_scale) +{ + GtkWidget *row, *box, *label, *title, *box2, *w, *scale; + + row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (text, NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, FALSE, TRUE, 0); + gtk_size_group_add_widget (self->battery_sizegroup, title); + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (box2); + w = gtk_label_new (""); + gtk_widget_show (w); + gtk_box_pack_start (GTK_BOX (box2), w, FALSE, TRUE, 0); + gtk_size_group_add_widget (self->charge_sizegroup, w); + + scale = g_object_new (CC_TYPE_BRIGHTNESS_SCALE, + "device", device, + NULL); + gtk_widget_show (scale); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), scale); + gtk_box_pack_start (GTK_BOX (box2), scale, TRUE, TRUE, 0); + gtk_size_group_add_widget (self->level_sizegroup, scale); + *brightness_scale = CC_BRIGHTNESS_SCALE (scale); + + gtk_box_pack_start (GTK_BOX (box), box2, TRUE, TRUE, 0); + + return row; +} + +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 +activate_row (CcPowerPanel *self, + GtkListBoxRow *row) +{ + GtkWidget *w; + GtkWidget *toplevel; + + if (row == GTK_LIST_BOX_ROW (self->automatic_suspend_row)) + { + w = self->automatic_suspend_dialog; + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); + gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (toplevel)); + gtk_window_set_modal (GTK_WINDOW (w), TRUE); + gtk_window_present (GTK_WINDOW (w)); + } +} + +static gboolean +automatic_suspend_activate (CcPowerPanel *self) +{ + activate_row (self, GTK_LIST_BOX_ROW (self->automatic_suspend_row)); + 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_model (GtkTreeModel *model, + gboolean can_suspend, + gboolean can_hibernate) +{ + 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 i; + + for (i = 0; i < G_N_ELEMENTS (actions); i++) + { + 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_list_store_insert_with_values (GTK_LIST_STORE (model), + NULL, -1, + 0, _(actions[i].name), + 1, actions[i].value, + -1); + } +} + +#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 (GTK_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 +has_brightness_cb (CcPowerPanel *self) +{ + gboolean has_brightness; + + has_brightness = cc_brightness_scale_get_has_brightness (self->brightness_scale); + + gtk_widget_set_visible (self->brightness_row, has_brightness); + gtk_widget_set_visible (self->dim_screen_row, has_brightness); + + als_enabled_state_changed (self); + +} + +static void +has_kbd_brightness_cb (CcPowerPanel *self, + GParamSpec *pspec, + GObject *object) +{ + gboolean has_brightness; + + has_brightness = cc_brightness_scale_get_has_brightness (self->kbd_brightness_scale); + + gtk_widget_set_visible (self->kbd_brightness_row, has_brightness); +} + +static void +add_power_saving_section (CcPowerPanel *self) +{ + GtkWidget *widget, *box, *label, *row; + GtkWidget *title; + GtkWidget *sw; + int value; + g_autofree gchar *s = NULL; + gboolean can_suspend; + + s = g_strdup_printf ("<b>%s</b>", _("Power Saving")); + label = gtk_label_new (s); + gtk_widget_show (label); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_set_margin_bottom (label, 12); + gtk_box_pack_start (GTK_BOX (self->vbox_power), label, FALSE, TRUE, 0); + gtk_widget_show (label); + + widget = gtk_list_box_new (); + gtk_widget_show (widget); + self->boxes_reverse = g_list_prepend (self->boxes_reverse, widget); + g_signal_connect_object (widget, "keynav-failed", G_CALLBACK (keynav_failed), self, G_CONNECT_SWAPPED); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (widget), GTK_SELECTION_NONE); + gtk_list_box_set_header_func (GTK_LIST_BOX (widget), + cc_list_box_update_header_func, + NULL, NULL); + g_signal_connect_object (widget, "row-activated", + G_CALLBACK (activate_row), self, G_CONNECT_SWAPPED); + + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (label)), + ATK_RELATION_LABEL_FOR, + ATK_OBJECT (gtk_widget_get_accessible (widget))); + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (widget)), + ATK_RELATION_LABELLED_BY, + ATK_OBJECT (gtk_widget_get_accessible (label))); + + box = gtk_frame_new (NULL); + gtk_widget_show (box); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_IN); + gtk_widget_set_margin_bottom (box, 32); + gtk_container_add (GTK_CONTAINER (box), widget); + gtk_box_pack_start (GTK_BOX (self->vbox_power), box, FALSE, TRUE, 0); + + row = add_brightness_row (self, BRIGHTNESS_DEVICE_SCREEN, _("_Screen Brightness"), &self->brightness_scale); + g_signal_connect_object (self->brightness_scale, "notify::has-brightness", + G_CALLBACK (has_brightness_cb), self, G_CONNECT_SWAPPED); + gtk_widget_show (row); + self->brightness_row = row; + + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + /* 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); + self->als_row = row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("Automatic Brightness"), NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->als_switch = gtk_switch_new (); + gtk_widget_show (self->als_switch); + gtk_widget_set_valign (self->als_switch, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->als_switch, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->als_switch); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + g_signal_connect_object (self->als_switch, "notify::active", + G_CALLBACK (als_switch_changed), self, G_CONNECT_SWAPPED); + + row = add_brightness_row (self, BRIGHTNESS_DEVICE_KBD, _("_Keyboard Brightness"), &self->kbd_brightness_scale); + g_signal_connect_object (self->kbd_brightness_scale, "notify::has-brightness", + G_CALLBACK (has_kbd_brightness_cb), self, G_CONNECT_SWAPPED); + gtk_widget_show (row); + self->kbd_brightness_row = row; + + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + self->dim_screen_row = row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Dim Screen When Inactive"), NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + sw = gtk_switch_new (); + gtk_widget_show (sw); + g_settings_bind (self->gsd_settings, "idle-dim", + sw, "active", + G_SETTINGS_BIND_DEFAULT); + gtk_widget_set_valign (sw, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), sw, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), sw); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Blank Screen"), NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->idle_delay_combo = gtk_combo_box_text_new (); + gtk_widget_show (self->idle_delay_combo); + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (self->idle_delay_combo), 0); + gtk_combo_box_set_model (GTK_COMBO_BOX (self->idle_delay_combo), + GTK_TREE_MODEL (self->liststore_idle_time)); + value = g_settings_get_uint (self->session_settings, "idle-delay"); + set_value_for_combo (GTK_COMBO_BOX (self->idle_delay_combo), value); + g_signal_connect_object (self->idle_delay_combo, "changed", + G_CALLBACK (combo_idle_delay_changed_cb), self, G_CONNECT_SWAPPED); + gtk_widget_set_valign (self->idle_delay_combo, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->idle_delay_combo, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->idle_delay_combo); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + can_suspend = can_suspend_or_hibernate (self, "CanSuspend"); + + /* 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) + { + GtkWidget *dialog; + + self->automatic_suspend_row = row = gtk_list_box_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Automatic Suspend"), NULL, NULL); + atk_object_set_name (ATK_OBJECT (gtk_widget_get_accessible (self->automatic_suspend_row)), _("Automatic suspend")); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->automatic_suspend_label = gtk_label_new (""); + gtk_widget_show (self->automatic_suspend_label); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->automatic_suspend_label); + g_signal_connect_object (self->automatic_suspend_label, "mnemonic-activate", + G_CALLBACK (automatic_suspend_activate), self, G_CONNECT_SWAPPED); + gtk_widget_set_halign (self->automatic_suspend_label, GTK_ALIGN_END); + gtk_box_pack_start (GTK_BOX (box), self->automatic_suspend_label, FALSE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + dialog = self->automatic_suspend_dialog; + g_signal_connect (dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); + 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 (GTK_COMBO_BOX (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 (GTK_COMBO_BOX (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); + } + +#ifdef HAVE_NETWORK_MANAGER + self->wifi_row = row = no_prelight_row_new (); + gtk_widget_hide (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Wi-Fi"), + _("Wi-Fi can be turned off to save power."), + NULL); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->wifi_switch = gtk_switch_new (); + gtk_widget_show (self->wifi_switch); + gtk_widget_set_valign (self->wifi_switch, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->wifi_switch, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->wifi_switch); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + self->mobile_row = row = no_prelight_row_new (); + gtk_widget_hide (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Mobile Broadband"), + _("Mobile broadband (LTE, 4G, 3G, etc.) can be turned off to save power."), + NULL); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->mobile_switch = gtk_switch_new (); + gtk_widget_show (self->mobile_switch); + gtk_widget_set_valign (self->mobile_switch, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->mobile_switch, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->mobile_switch); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + g_signal_connect_object (G_OBJECT (self->mobile_switch), "notify::active", + G_CALLBACK (mobile_switch_changed), self, G_CONNECT_SWAPPED); + + /* Create and store a NMClient instance if it doesn't exist yet */ + if (cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) + setup_nm_client (self, cc_object_storage_get_object (CC_OBJECT_NMCLIENT)); + else + nm_client_new_async (cc_panel_get_cancellable (CC_PANEL (self)), nm_client_ready_cb, self); + + g_signal_connect_object (G_OBJECT (self->wifi_switch), "notify::active", + G_CALLBACK (wifi_switch_changed), self, G_CONNECT_SWAPPED); +#endif + +#ifdef HAVE_BLUETOOTH + + self->bt_rfkill = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Rfkill", + "/org/gnome/SettingsDaemon/Rfkill", + "org.gnome.SettingsDaemon.Rfkill", + NULL, + NULL); + + if (self->bt_rfkill) + { + self->bt_properties = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Rfkill", + "/org/gnome/SettingsDaemon/Rfkill", + "org.freedesktop.DBus.Properties", + NULL, + NULL); + } + + row = no_prelight_row_new (); + gtk_widget_hide (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("_Bluetooth"), + _("Bluetooth can be turned off to save power."), + NULL); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->bt_switch = gtk_switch_new (); + gtk_widget_show (self->bt_switch); + gtk_widget_set_valign (self->bt_switch, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->bt_switch, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->bt_switch); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + self->bt_row = row; + g_signal_connect_object (self->bt_rfkill, "g-properties-changed", + G_CALLBACK (bt_powered_state_changed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (self->bt_switch), "notify::active", + G_CALLBACK (bt_switch_changed), self, G_CONNECT_SWAPPED); + + bt_powered_state_changed (self); +#endif +} + +static void +add_battery_percentage (CcPowerPanel *self, + GtkListBox *listbox) +{ + GtkWidget *box, *label, *title; + GtkWidget *row; + GtkWidget *sw; + + if (!self->has_batteries) + return; + + /* Show Battery Percentage */ + row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + title = row_title_new (_("Show Battery _Percentage"), NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + sw = gtk_switch_new (); + gtk_widget_show (sw); + g_settings_bind (self->interface_settings, "show-battery-percentage", + sw, "active", + G_SETTINGS_BIND_DEFAULT); + gtk_widget_set_valign (sw, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), sw, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), sw); + gtk_container_add (GTK_CONTAINER (listbox), row); + gtk_size_group_add_widget (self->row_sizegroup, row); +} + +static void +add_general_section (CcPowerPanel *self) +{ + GtkWidget *widget, *box, *label, *title; + GtkWidget *row; + g_autofree gchar *s = NULL; + GtkTreeModel *model; + GsdPowerButtonActionType button_value; + gboolean can_suspend, can_hibernate; + + /* Frame header */ + s = g_markup_printf_escaped ("<b>%s</b>", _("Suspend & Power Button")); + label = gtk_label_new (s); + gtk_widget_show (label); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_set_margin_bottom (label, 12); + gtk_box_pack_start (GTK_BOX (self->vbox_power), label, FALSE, TRUE, 0); + gtk_widget_show (label); + + widget = gtk_list_box_new (); + gtk_widget_show (widget); + self->boxes_reverse = g_list_prepend (self->boxes_reverse, widget); + g_signal_connect_object (widget, "keynav-failed", G_CALLBACK (keynav_failed), self, G_CONNECT_SWAPPED); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (widget), GTK_SELECTION_NONE); + gtk_list_box_set_header_func (GTK_LIST_BOX (widget), + cc_list_box_update_header_func, + NULL, NULL); + + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (label)), + ATK_RELATION_LABEL_FOR, + ATK_OBJECT (gtk_widget_get_accessible (widget))); + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (widget)), + ATK_RELATION_LABELLED_BY, + ATK_OBJECT (gtk_widget_get_accessible (label))); + + box = gtk_frame_new (NULL); + gtk_widget_show (box); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_IN); + gtk_widget_set_margin_bottom (box, 32); + gtk_container_add (GTK_CONTAINER (box), widget); + gtk_box_pack_start (GTK_BOX (self->vbox_power), box, FALSE, TRUE, 0); + + 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) + { + add_battery_percentage (self, GTK_LIST_BOX (widget)); + return; + } + + /* Power button row */ + row = no_prelight_row_new (); + gtk_widget_show (row); + box = row_box_new (); + gtk_container_add (GTK_CONTAINER (row), box); + + title = row_title_new (_("Po_wer Button Behavior"), NULL, &label); + gtk_box_pack_start (GTK_BOX (box), title, TRUE, TRUE, 0); + + self->power_button_combo = gtk_combo_box_text_new (); + gtk_widget_show (self->power_button_combo); + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (self->power_button_combo), 0); + model = GTK_TREE_MODEL (self->liststore_power_button); + populate_power_button_model (model, can_suspend, can_hibernate); + gtk_combo_box_set_model (GTK_COMBO_BOX (self->power_button_combo), model); + button_value = g_settings_get_enum (self->gsd_settings, "power-button-action"); + set_value_for_combo (GTK_COMBO_BOX (self->power_button_combo), button_value); + g_signal_connect_object (self->power_button_combo, "changed", + G_CALLBACK (combo_power_button_changed_cb), self, G_CONNECT_SWAPPED); + gtk_widget_set_valign (self->power_button_combo, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (box), self->power_button_combo, FALSE, TRUE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->power_button_combo); + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_size_group_add_widget (self->row_sizegroup, row); + + add_battery_percentage (self, GTK_LIST_BOX (widget)); +} + +static gint +battery_sort_func (gconstpointer a, gconstpointer b, gpointer data) +{ + GObject *row_a = (GObject*)a; + GObject *row_b = (GObject*)b; + gboolean a_primary; + gboolean b_primary; + gint a_kind; + gint b_kind; + + a_primary = GPOINTER_TO_INT (g_object_get_data (row_a, "primary")); + b_primary = GPOINTER_TO_INT (g_object_get_data (row_b, "primary")); + + if (a_primary) + return -1; + else if (b_primary) + return 1; + + a_kind = GPOINTER_TO_INT (g_object_get_data (row_a, "kind")); + b_kind = GPOINTER_TO_INT (g_object_get_data (row_b, "kind")); + + return a_kind - b_kind; +} + +static void +add_battery_section (CcPowerPanel *self) +{ + GtkWidget *widget, *box; + GtkWidget *frame; + g_autofree gchar *s = NULL; + + self->battery_section = box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_show (box); + gtk_widget_set_margin_bottom (box, 32); + gtk_box_pack_start (GTK_BOX (self->vbox_power), box, FALSE, TRUE, 0); + + s = g_markup_printf_escaped ("<b>%s</b>", _("Battery")); + self->battery_heading = widget = gtk_label_new (s); + gtk_widget_show (widget); + gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_margin_bottom (widget, 12); + gtk_widget_set_margin_bottom (box, 32); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, TRUE, 0); + + self->battery_list = widget = GTK_WIDGET (gtk_list_box_new ()); + gtk_widget_show (widget); + self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->battery_list); + g_signal_connect_object (widget, "keynav-failed", G_CALLBACK (keynav_failed), self, G_CONNECT_SWAPPED); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (widget), GTK_SELECTION_NONE); + gtk_list_box_set_header_func (GTK_LIST_BOX (widget), + cc_list_box_update_header_func, + NULL, NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (widget), + (GtkListBoxSortFunc)battery_sort_func, NULL, NULL); + + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (self->battery_heading)), + ATK_RELATION_LABEL_FOR, + ATK_OBJECT (gtk_widget_get_accessible (self->battery_list))); + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (self->battery_list)), + ATK_RELATION_LABELLED_BY, + ATK_OBJECT (gtk_widget_get_accessible (self->battery_heading))); + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (frame), widget); + gtk_box_pack_start (GTK_BOX (box), frame, FALSE, TRUE, 0); +} + +static void +add_device_section (CcPowerPanel *self) +{ + GtkWidget *widget, *box; + GtkWidget *frame; + g_autofree gchar *s = NULL; + + self->device_section = box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_show (box); + gtk_widget_set_margin_top (box, 6); + gtk_widget_set_margin_bottom (box, 32); + gtk_box_pack_start (GTK_BOX (self->vbox_power), box, FALSE, TRUE, 0); + + s = g_markup_printf_escaped ("<b>%s</b>", _("Devices")); + self->device_heading = widget = gtk_label_new (s); + gtk_widget_show (widget); + gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); + gtk_label_set_xalign (GTK_LABEL (widget), 0.0); + gtk_label_set_use_markup (GTK_LABEL (widget), TRUE); + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_margin_bottom (widget, 12); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, TRUE, 0); + + self->device_list = widget = gtk_list_box_new (); + gtk_widget_show (widget); + self->boxes_reverse = g_list_prepend (self->boxes_reverse, self->device_list); + g_signal_connect_object (widget, "keynav-failed", G_CALLBACK (keynav_failed), self, G_CONNECT_SWAPPED); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (widget), GTK_SELECTION_NONE); + gtk_list_box_set_header_func (GTK_LIST_BOX (widget), + cc_list_box_update_header_func, + NULL, NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (widget), + (GtkListBoxSortFunc)battery_sort_func, NULL, NULL); + + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (self->device_heading)), + ATK_RELATION_LABEL_FOR, + ATK_OBJECT (gtk_widget_get_accessible (self->device_list))); + atk_object_add_relationship (ATK_OBJECT (gtk_widget_get_accessible (self->device_list)), + ATK_RELATION_LABELLED_BY, + ATK_OBJECT (gtk_widget_get_accessible (self->device_heading))); + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (frame), widget); + gtk_box_pack_start (GTK_BOX (box), frame, FALSE, TRUE, 0); +} + +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); + + 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"); + + self->battery_row_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + self->row_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + self->battery_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->charge_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->level_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + add_battery_section (self); + add_device_section (self); + add_power_saving_section (self); + add_general_section (self); + + self->boxes = g_list_copy (self->boxes_reverse); + self->boxes = g_list_reverse (self->boxes); + + /* 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); + + self->focus_adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->main_scroll)); + gtk_container_set_focus_vadjustment (GTK_CONTAINER (self->main_box), self->focus_adjustment); +} 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..74aed35 --- /dev/null +++ b/panels/power/cc-power-panel.ui @@ -0,0 +1,315 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkListStore" id="liststore_time"> + <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> + <object class="GtkListStore" id="liststore_idle_time"> + <columns> + <!-- column-name name --> + <column type="gchararray"/> + <!-- column-name value --> + <column type="gint"/> + </columns> + <data> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">1 minute</col> + <col id="1">60</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">2 minutes</col> + <col id="1">120</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">3 minutes</col> + <col id="1">180</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">4 minutes</col> + <col id="1">240</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">5 minutes</col> + <col id="1">300</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">8 minutes</col> + <col id="1">480</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">10 minutes</col> + <col id="1">600</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">12 minutes</col> + <col id="1">720</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">15 minutes</col> + <col id="1">900</col> + </row> + <row> + <col id="0" translatable="yes" context="blank_screen" comments="Translators: Option for "Blank screen" in "Power" panel.">Never</col> + <col id="1">0</col> + </row> + </data> + </object> + <object class="GtkListStore" id="liststore_power_button"> + <columns> + <!-- column-name name --> + <column type="gchararray"/> + <!-- column-name value --> + <column type="gint"/> + </columns> + </object> + <template class="CcPowerPanel" parent="CcPanel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkScrolledWindow" id="main_scroll"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hscrollbar_policy">never</property> + <child> + <object class="HdyClamp" id="main_box"> + <property name="visible">True</property> + <property name="margin_top">32</property> + <property name="margin_bottom">32</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + <child> + <object class="GtkBox" id="vbox_power"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">3</property> + <property name="hexpand">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + + <object class="GtkDialog" id="automatic_suspend_dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Automatic Suspend</property> + <property name="type_hint">dialog</property> + <property name="resizable">False</property> + <property name="use_header_bar">1</property> + <child internal-child="vbox"> + <object class="GtkBox" id="asdf"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <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="visible">True</property> + <property name="can_focus">False</property> + <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> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="suspend_on_battery_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <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> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="suspend_on_battery_switch"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="suspend_on_battery_delay_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <property name="model">liststore_time</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="suspend_on_battery_delay_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <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> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="suspend_on_ac_switch"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">end</property> + <property name="margin_top">12</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="suspend_on_ac_delay_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <property name="model">liststore_time</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <object class="GtkLabel" id="suspend_on_ac_delay_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <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> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</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..554213e --- /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=gnome-power-manager +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
\ No newline at end of file diff --git a/panels/power/icons/16x16/gnome-power-manager.png b/panels/power/icons/16x16/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..4e72c76 --- /dev/null +++ b/panels/power/icons/16x16/gnome-power-manager.png diff --git a/panels/power/icons/22x22/gnome-power-manager.png b/panels/power/icons/22x22/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..6ad7e5d --- /dev/null +++ b/panels/power/icons/22x22/gnome-power-manager.png diff --git a/panels/power/icons/24x24/gnome-power-manager.png b/panels/power/icons/24x24/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..9f806af --- /dev/null +++ b/panels/power/icons/24x24/gnome-power-manager.png diff --git a/panels/power/icons/256x256/gnome-power-manager.png b/panels/power/icons/256x256/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..bde1807 --- /dev/null +++ b/panels/power/icons/256x256/gnome-power-manager.png diff --git a/panels/power/icons/32x32/gnome-power-manager.png b/panels/power/icons/32x32/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..579f709 --- /dev/null +++ b/panels/power/icons/32x32/gnome-power-manager.png diff --git a/panels/power/icons/48x48/gnome-power-manager.png b/panels/power/icons/48x48/gnome-power-manager.png Binary files differnew file mode 100644 index 0000000..f013b42 --- /dev/null +++ b/panels/power/icons/48x48/gnome-power-manager.png diff --git a/panels/power/icons/meson.build b/panels/power/icons/meson.build new file mode 100644 index 0000000..8165371 --- /dev/null +++ b/panels/power/icons/meson.build @@ -0,0 +1,15 @@ +icon_sizes = [ + '16x16', + '22x22', + '24x24', + '32x32', + '48x48', + '256x256' +] + +foreach icon_size: icon_sizes + install_data( + join_paths(icon_size, 'gnome-power-manager.png'), + install_dir: join_paths(control_center_icondir, 'hicolor', icon_size, 'apps') + ) +endforeach diff --git a/panels/power/meson.build b/panels/power/meson.build new file mode 100644 index 0000000..1700c91 --- /dev/null +++ b/panels/power/meson.build @@ -0,0 +1,60 @@ +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( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) + +sources = files( + 'cc-brightness-scale.c', + 'cc-power-panel.c' +) + +sources += gnome.mkenums_simple( + 'cc-brightness-scale-types', + sources: ['cc-brightness-scale.h']) + +resource_data = files('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 +] + +if host_is_linux + deps += network_manager_deps +endif + +if host_is_linux_not_s390 + deps += gnome_bluetooth_dep +endif + +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.gresource.xml b/panels/power/power.gresource.xml new file mode 100644 index 0000000..f0bcb1a --- /dev/null +++ b/panels/power/power.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/power"> + <file preprocess="xml-stripblanks">cc-power-panel.ui</file> + <file>battery-levels.css</file> + </gresource> +</gresources> |