summaryrefslogtreecommitdiffstats
path: root/plugins/power/gsd-power-manager.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/power/gsd-power-manager.c3270
1 files changed, 3270 insertions, 0 deletions
diff --git a/plugins/power/gsd-power-manager.c b/plugins/power/gsd-power-manager.c
new file mode 100644
index 0000000..c93c620
--- /dev/null
+++ b/plugins/power/gsd-power-manager.c
@@ -0,0 +1,3270 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2012, 2015 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2011 Ritesh Khadgaray <khadgaray@gmail.com>
+ * Copyright (C) 2012-2013 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/>.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <libupower-glib/upower.h>
+#include <libnotify/notify.h>
+#include <canberra-gtk.h>
+#include <glib-unix.h>
+#include <gio/gunixfdlist.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+#include <libgnome-desktop/gnome-idle-monitor.h>
+
+#include <gsd-input-helper.h>
+
+#include "gsd-power-constants.h"
+#include "gsm-inhibitor-flag.h"
+#include "gsm-presence-flag.h"
+#include "gsm-manager-logout-mode.h"
+#include "gpm-common.h"
+#include "gsd-backlight.h"
+#include "gnome-settings-profile.h"
+#include "gnome-settings-bus.h"
+#include "gsd-enums.h"
+#include "gsd-power-manager.h"
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define UPOWER_DBUS_NAME "org.freedesktop.UPower"
+#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
+#define UPOWER_DBUS_PATH_KBDBACKLIGHT "/org/freedesktop/UPower/KbdBacklight"
+#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
+#define UPOWER_DBUS_INTERFACE_KBDBACKLIGHT "org.freedesktop.UPower.KbdBacklight"
+
+#define GSD_POWER_SETTINGS_SCHEMA "org.gnome.settings-daemon.plugins.power"
+
+#define GSD_POWER_DBUS_NAME GSD_DBUS_NAME ".Power"
+#define GSD_POWER_DBUS_PATH GSD_DBUS_PATH "/Power"
+#define GSD_POWER_DBUS_INTERFACE GSD_DBUS_BASE_INTERFACE ".Power"
+#define GSD_POWER_DBUS_INTERFACE_SCREEN GSD_POWER_DBUS_INTERFACE ".Screen"
+#define GSD_POWER_DBUS_INTERFACE_KEYBOARD GSD_POWER_DBUS_INTERFACE ".Keyboard"
+
+#define GSD_POWER_MANAGER_NOTIFY_TIMEOUT_SHORT 10 * 1000 /* ms */
+#define GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG 30 * 1000 /* ms */
+
+#define SYSTEMD_DBUS_NAME "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH "/org/freedesktop/login1"
+#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Manager"
+
+/* Time between notifying the user about a critical action and the action itself in UPower. */
+#define GSD_ACTION_DELAY 20
+/* And the time before we stop the warning sound */
+#define GSD_STOP_SOUND_DELAY GSD_ACTION_DELAY - 2
+
+/* The bandwidth of the low-pass filter used to smooth ambient light readings,
+ * measured in Hz. Smaller numbers result in smoother backlight changes.
+ * Larger numbers are more responsive to abrupt changes in ambient light. */
+#define GSD_AMBIENT_BANDWIDTH_HZ 0.1f
+
+/* Convert bandwidth to time constant. Units of constant are microseconds. */
+#define GSD_AMBIENT_TIME_CONSTANT (G_USEC_PER_SEC * 1.0f / (2.0f * G_PI * GSD_AMBIENT_BANDWIDTH_HZ))
+
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Power.Screen'>"
+" <property name='Brightness' type='i' access='readwrite'/>"
+" <method name='StepUp'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='s' name='connector' direction='out'/>"
+" </method>"
+" <method name='StepDown'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='s' name='connector' direction='out'/>"
+" </method>"
+" <method name='Cycle'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" <arg type='i' name='output_id' direction='out'/>"
+" </method>"
+" </interface>"
+" <interface name='org.gnome.SettingsDaemon.Power.Keyboard'>"
+" <property name='Brightness' type='i' access='readwrite'/>"
+" <method name='StepUp'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <method name='StepDown'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <method name='Toggle'>"
+" <arg type='i' name='new_percentage' direction='out'/>"
+" </method>"
+" <signal name='BrightnessChanged'>"
+" <arg name='brightness' type='i'/>"
+" <arg name='source' type='s'/>"
+" </signal>"
+" </interface>"
+"</node>";
+
+typedef enum {
+ GSD_POWER_IDLE_MODE_NORMAL,
+ GSD_POWER_IDLE_MODE_DIM,
+ GSD_POWER_IDLE_MODE_BLANK,
+ GSD_POWER_IDLE_MODE_SLEEP
+} GsdPowerIdleMode;
+
+struct _GsdPowerManager
+{
+ GObject parent;
+
+ /* D-Bus */
+ GsdSessionManager *session;
+ guint name_id;
+ GDBusNodeInfo *introspection_data;
+ GDBusConnection *connection;
+ GCancellable *cancellable;
+
+ /* Settings */
+ GSettings *settings;
+ GSettings *settings_bus;
+ GSettings *settings_screensaver;
+
+ /* Screensaver */
+ GsdScreenSaver *screensaver_proxy;
+ gboolean screensaver_active;
+
+ /* State */
+ gboolean lid_is_present;
+ gboolean lid_is_closed;
+ gboolean session_is_active;
+ UpClient *up_client;
+ GPtrArray *devices_array;
+ UpDevice *device_composite;
+ GnomeRRScreen *rr_screen;
+ NotifyNotification *notification_ups_discharging;
+ NotifyNotification *notification_low;
+ NotifyNotification *notification_sleep_warning;
+ GsdPowerActionType sleep_action_type;
+ GHashTable *devices_notified_ht; /* key = serial str, value = UpDeviceLevel */
+ gboolean battery_is_low; /* laptop battery low, or UPS discharging */
+
+ /* Brightness */
+ GsdBacklight *backlight;
+ gint pre_dim_brightness; /* level, not percentage */
+
+ /* Keyboard */
+ GDBusProxy *upower_kbd_proxy;
+ gint kbd_brightness_now;
+ gint kbd_brightness_max;
+ gint kbd_brightness_old;
+ gint kbd_brightness_pre_dim;
+
+ /* Ambient */
+ GDBusProxy *iio_proxy;
+ guint iio_proxy_watch_id;
+ gboolean ambient_norm_required;
+ gdouble ambient_accumulator;
+ gdouble ambient_norm_value;
+ gdouble ambient_percentage_old;
+ gdouble ambient_last_absolute;
+ gint64 ambient_last_time;
+
+ /* Sound */
+ guint32 critical_alert_timeout_id;
+
+ /* systemd stuff */
+ GDBusProxy *logind_proxy;
+ gint inhibit_lid_switch_fd;
+ gboolean inhibit_lid_switch_taken;
+ gint inhibit_suspend_fd;
+ gboolean inhibit_suspend_taken;
+ guint inhibit_lid_switch_timer_id;
+ gboolean is_virtual_machine;
+
+ /* Idles */
+ GnomeIdleMonitor *idle_monitor;
+ guint idle_dim_id;
+ guint idle_blank_id;
+ guint idle_sleep_warning_id;
+ guint idle_sleep_id;
+ guint user_active_id;
+ GsdPowerIdleMode current_idle_mode;
+
+ guint temporary_unidle_on_ac_id;
+ GsdPowerIdleMode previous_idle_mode;
+
+ guint xscreensaver_watchdog_timer_id;
+};
+
+enum {
+ PROP_0,
+};
+
+static void gsd_power_manager_class_init (GsdPowerManagerClass *klass);
+static void gsd_power_manager_init (GsdPowerManager *power_manager);
+
+static void engine_device_warning_changed_cb (UpDevice *device, GParamSpec *pspec, GsdPowerManager *manager);
+static void do_power_action_type (GsdPowerManager *manager, GsdPowerActionType action_type);
+static void uninhibit_lid_switch (GsdPowerManager *manager);
+static void stop_inhibit_lid_switch_timer (GsdPowerManager *manager);
+static void sync_lid_inhibitor (GsdPowerManager *manager);
+static void main_battery_or_ups_low_changed (GsdPowerManager *manager, gboolean is_low);
+static gboolean idle_is_session_inhibited (GsdPowerManager *manager, guint mask, gboolean *is_inhibited);
+static void idle_triggered_idle_cb (GnomeIdleMonitor *monitor, guint watch_id, gpointer user_data);
+static void idle_became_active_cb (GnomeIdleMonitor *monitor, guint watch_id, gpointer user_data);
+static void iio_proxy_changed (GsdPowerManager *manager);
+static void iio_proxy_changed_cb (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data);
+
+G_DEFINE_TYPE (GsdPowerManager, gsd_power_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+GQuark
+gsd_power_manager_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("gsd_power_manager_error");
+ return quark;
+}
+
+static void
+notify_close_if_showing (NotifyNotification **notification)
+{
+ if (*notification == NULL)
+ return;
+ notify_notification_close (*notification, NULL);
+ g_clear_object (notification);
+}
+
+static void
+engine_device_add (GsdPowerManager *manager, UpDevice *device)
+{
+ UpDeviceKind kind;
+
+ /* Batteries and UPSes are already handled through
+ * the composite battery */
+ g_object_get (device, "kind", &kind, NULL);
+ if (kind == UP_DEVICE_KIND_BATTERY ||
+ kind == UP_DEVICE_KIND_UPS ||
+ kind == UP_DEVICE_KIND_LINE_POWER)
+ return;
+ g_ptr_array_add (manager->devices_array, g_object_ref (device));
+
+ g_signal_connect (device, "notify::warning-level",
+ G_CALLBACK (engine_device_warning_changed_cb), manager);
+
+ engine_device_warning_changed_cb (device, NULL, manager);
+}
+
+static gboolean
+engine_coldplug (GsdPowerManager *manager)
+{
+ guint i;
+ GPtrArray *array = NULL;
+ UpDevice *device;
+
+ /* add to database */
+ array = up_client_get_devices2 (manager->up_client);
+
+ for (i = 0 ; array != NULL && i < array->len ; i++) {
+ device = g_ptr_array_index (array, i);
+ engine_device_add (manager, device);
+ }
+
+ g_clear_pointer (&array, g_ptr_array_unref);
+
+ /* never repeat */
+ return FALSE;
+}
+
+static void
+engine_device_added_cb (UpClient *client, UpDevice *device, GsdPowerManager *manager)
+{
+ engine_device_add (manager, device);
+}
+
+static void
+engine_device_removed_cb (UpClient *client, const char *object_path, GsdPowerManager *manager)
+{
+ guint i;
+
+ for (i = 0; i < manager->devices_array->len; i++) {
+ UpDevice *device = g_ptr_array_index (manager->devices_array, i);
+
+ if (g_strcmp0 (object_path, up_device_get_object_path (device)) == 0) {
+ g_ptr_array_remove_index (manager->devices_array, i);
+ break;
+ }
+ }
+}
+
+static void
+on_notification_closed (NotifyNotification *notification, gpointer data)
+{
+ g_object_unref (notification);
+}
+
+/* See PrivacyScope in messageTray.js in gnome-shell. A notification with
+ * ‘system’ scope has its detailed description shown in the lock screen. ‘user’
+ * scope notifications don’t (because they could contain private information). */
+typedef enum
+{
+ NOTIFICATION_PRIVACY_USER,
+ NOTIFICATION_PRIVACY_SYSTEM,
+} NotificationPrivacyScope;
+
+static const gchar *
+notification_privacy_scope_to_string (NotificationPrivacyScope scope)
+{
+ switch (scope) {
+ case NOTIFICATION_PRIVACY_USER:
+ return "user";
+ case NOTIFICATION_PRIVACY_SYSTEM:
+ return "system";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+create_notification (const char *summary,
+ const char *body,
+ const char *icon_name,
+ NotificationPrivacyScope privacy_scope,
+ NotifyNotification **weak_pointer_location)
+{
+ NotifyNotification *notification;
+
+ notification = notify_notification_new (summary, body, icon_name);
+ /* TRANSLATORS: this is the notification application name */
+ notify_notification_set_app_name (notification, _("Power"));
+ notify_notification_set_hint_string (notification, "desktop-entry", "gnome-power-panel");
+ notify_notification_set_hint_string (notification, "x-gnome-privacy-scope",
+ notification_privacy_scope_to_string (privacy_scope));
+ notify_notification_set_urgency (notification,
+ NOTIFY_URGENCY_CRITICAL);
+ *weak_pointer_location = notification;
+ g_object_add_weak_pointer (G_OBJECT (notification),
+ (gpointer *) weak_pointer_location);
+ g_signal_connect (notification, "closed",
+ G_CALLBACK (on_notification_closed), NULL);
+}
+
+static void
+engine_ups_discharging (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title;
+ gchar *remaining_text = NULL;
+ gdouble percentage;
+ char *icon_name;
+ gint64 time_to_empty;
+ GString *message;
+ UpDeviceKind kind;
+
+ /* get device properties */
+ g_object_get (device,
+ "kind", &kind,
+ "percentage", &percentage,
+ "time-to-empty", &time_to_empty,
+ "icon-name", &icon_name,
+ NULL);
+
+ if (kind != UP_DEVICE_KIND_UPS)
+ return;
+
+ /* only show text if there is a valid time */
+ if (time_to_empty > 0)
+ remaining_text = gpm_get_timestring (time_to_empty);
+
+ /* TRANSLATORS: UPS is now discharging */
+ title = _("UPS Discharging");
+
+ message = g_string_new ("");
+ if (remaining_text != NULL) {
+ /* TRANSLATORS: tell the user how much time they have got */
+ g_string_append_printf (message, _("%s of UPS backup power remaining"),
+ remaining_text);
+ } else {
+ g_string_append (message, _("Unknown amount of UPS backup power remaining"));
+ }
+ g_string_append_printf (message, " (%.0f%%)", percentage);
+
+ /* close any existing notification of this class */
+ notify_close_if_showing (&manager->notification_ups_discharging);
+
+ /* create a new notification */
+ create_notification (title, message->str,
+ icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_ups_discharging);
+ notify_notification_set_timeout (manager->notification_ups_discharging,
+ GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG);
+ notify_notification_set_hint (manager->notification_ups_discharging,
+ "transient", g_variant_new_boolean (TRUE));
+
+ notify_notification_show (manager->notification_ups_discharging, NULL);
+
+ g_string_free (message, TRUE);
+ g_free (icon_name);
+ g_free (remaining_text);
+}
+
+static GsdPowerActionType
+manager_critical_action_get (GsdPowerManager *manager)
+{
+ GsdPowerActionType policy;
+ char *action;
+
+ action = up_client_get_critical_action (manager->up_client);
+ /* We don't make the difference between HybridSleep and Hibernate */
+ if (g_strcmp0 (action, "PowerOff") == 0)
+ policy = GSD_POWER_ACTION_SHUTDOWN;
+ else
+ policy = GSD_POWER_ACTION_HIBERNATE;
+ g_free (action);
+ return policy;
+}
+
+static gboolean
+manager_critical_action_stop_sound_cb (GsdPowerManager *manager)
+{
+ /* stop playing the alert as it's too late to do anything now */
+ play_loop_stop (&manager->critical_alert_timeout_id);
+
+ return FALSE;
+}
+
+static gboolean
+engine_device_debounce_warn (GsdPowerManager *manager,
+ UpDeviceKind kind,
+ UpDeviceLevel warning,
+ const char *serial)
+{
+ gpointer last_warning_ptr;
+ UpDeviceLevel last_warning;
+ gboolean ret = TRUE;
+
+ if (!serial)
+ return TRUE;
+
+ if (kind == UP_DEVICE_KIND_BATTERY ||
+ kind == UP_DEVICE_KIND_UPS)
+ return TRUE;
+
+ if (g_hash_table_lookup_extended (manager->devices_notified_ht, serial,
+ NULL, &last_warning_ptr)) {
+ last_warning = GPOINTER_TO_INT (last_warning_ptr);
+
+ if (last_warning >= warning)
+ ret = FALSE;
+ }
+
+ if (warning != UP_DEVICE_LEVEL_UNKNOWN)
+ g_hash_table_insert (manager->devices_notified_ht,
+ g_strdup (serial),
+ GINT_TO_POINTER (warning));
+
+ return ret;
+}
+
+static void
+engine_charge_low (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title = NULL;
+ gboolean ret;
+ gchar *message = NULL;
+ gchar *tmp;
+ gchar *remaining_text;
+ gdouble percentage;
+ guint battery_level;
+ char *icon_name;
+ gint64 time_to_empty;
+ UpDeviceKind kind;
+
+ /* get device properties */
+ g_object_get (device,
+ "kind", &kind,
+ "percentage", &percentage,
+ "time-to-empty", &time_to_empty,
+ "battery-level", &battery_level,
+ "icon-name", &icon_name,
+ NULL);
+
+ if (battery_level == UP_DEVICE_LEVEL_UNKNOWN)
+ battery_level = UP_DEVICE_LEVEL_NONE;
+
+ if (kind == UP_DEVICE_KIND_BATTERY) {
+
+ /* if the user has no other batteries, drop the "Laptop" wording */
+ ret = (manager->devices_array->len > 0);
+ if (ret) {
+ /* TRANSLATORS: laptop battery low, and we only have one battery */
+ title = _("Battery low");
+ } else {
+ /* TRANSLATORS: laptop battery low, and we have more than one kind of battery */
+ title = _("Laptop battery low");
+ }
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: tell the user how much time they have got */
+ message = g_strdup_printf (_("Approximately %s remaining (%.0f%%)"), remaining_text, percentage);
+ g_free (remaining_text);
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ /* TRANSLATORS: UPS is starting to get a little low */
+ title = _("UPS low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: tell the user how much time they have got */
+ message = g_strdup_printf (_("Approximately %s of remaining UPS backup power (%.0f%%)"),
+ remaining_text, percentage);
+ g_free (remaining_text);
+ } else if (kind == UP_DEVICE_KIND_MOUSE) {
+ /* TRANSLATORS: mouse is getting a little low */
+ title = _("Mouse battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless mouse is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Wireless mouse is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_KEYBOARD) {
+ /* TRANSLATORS: keyboard is getting a little low */
+ title = _("Keyboard battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless keyboard is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Wireless keyboard is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_PDA) {
+ /* TRANSLATORS: PDA is getting a little low */
+ title = _("PDA battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("PDA is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("PDA is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_PHONE) {
+ /* TRANSLATORS: cell phone (mobile) is getting a little low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Cell phone is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Cell phone is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_MEDIA_PLAYER) {
+ /* TRANSLATORS: media player, e.g. mp3 is getting a little low */
+ title = _("Media player battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Media player is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Media player is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_TABLET) {
+ /* TRANSLATORS: graphics tablet, e.g. wacom is getting a little low */
+ title = _("Tablet battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Tablet is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Tablet is low in power"));
+
+ } else if (kind == UP_DEVICE_KIND_COMPUTER) {
+ /* TRANSLATORS: computer, e.g. ipad is getting a little low */
+ title = _("Attached computer battery low");
+
+ /* TRANSLATORS: tell user more details */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Attached computer is low in power (%.0f%%)"), percentage);
+ else
+ message = g_strdup_printf (_("Attached computer is low in power"));
+ }
+
+ /* close any existing notification of this class */
+ notify_close_if_showing (&manager->notification_low);
+
+ /* create a new notification */
+ create_notification (title, message,
+ icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_low);
+ notify_notification_set_timeout (manager->notification_low,
+ GSD_POWER_MANAGER_NOTIFY_TIMEOUT_LONG);
+ notify_notification_set_hint (manager->notification_low,
+ "transient", g_variant_new_boolean (TRUE));
+
+ notify_notification_show (manager->notification_low, NULL);
+
+ /* play the sound, using sounds from the naming spec */
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "battery-low",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Battery is low"), NULL);
+
+ g_free (icon_name);
+ g_free (message);
+}
+
+static void
+engine_charge_critical (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title = NULL;
+ gboolean ret;
+ gchar *message = NULL;
+ gdouble percentage;
+ guint battery_level;
+ char *icon_name;
+ gint64 time_to_empty;
+ GsdPowerActionType policy;
+ UpDeviceKind kind;
+
+ /* get device properties */
+ g_object_get (device,
+ "kind", &kind,
+ "percentage", &percentage,
+ "battery-level", &battery_level,
+ "time-to-empty", &time_to_empty,
+ "icon-name", &icon_name,
+ NULL);
+
+ if (battery_level == UP_DEVICE_LEVEL_UNKNOWN)
+ battery_level = UP_DEVICE_LEVEL_NONE;
+
+ if (kind == UP_DEVICE_KIND_BATTERY) {
+
+ /* if the user has no other batteries, drop the "Laptop" wording */
+ ret = (manager->devices_array->len > 0);
+ if (ret) {
+ /* TRANSLATORS: laptop battery critically low, and only have one kind of battery */
+ title = _("Battery critically low");
+ } else {
+ /* TRANSLATORS: laptop battery critically low, and we have more than one type of battery */
+ title = _("Laptop battery critically low");
+ }
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: give the user a ultimatum */
+ message = g_strdup_printf (_("Computer will hibernate very soon unless it is plugged in."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: give the user a ultimatum */
+ message = g_strdup_printf (_("Computer will shutdown very soon unless it is plugged in."));
+ }
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ gchar *remaining_text;
+ gchar *tmp;
+
+ /* TRANSLATORS: the UPS is very low */
+ title = _("UPS critically low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: give the user a ultimatum */
+ message = g_strdup_printf (_("Approximately %s of remaining UPS power (%.0f%%). "
+ "Restore AC power to your computer to avoid losing data."),
+ remaining_text, percentage);
+ g_free (remaining_text);
+ } else if (kind == UP_DEVICE_KIND_MOUSE) {
+ /* TRANSLATORS: the mouse battery is very low */
+ title = _("Mouse battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless mouse is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Wireless mouse is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_KEYBOARD) {
+ /* TRANSLATORS: the keyboard battery is very low */
+ title = _("Keyboard battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Wireless keyboard is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Wireless keyboard is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_PDA) {
+
+ /* TRANSLATORS: the PDA battery is very low */
+ title = _("PDA battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("PDA is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("PDA is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_PHONE) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Cell phone is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Cell phone is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_MEDIA_PLAYER) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Cell phone battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Media player is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Media player is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_TABLET) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Tablet battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Tablet is very low in power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Tablet is very low in power. "
+ "This device will soon stop functioning if not charged."));
+
+ } else if (kind == UP_DEVICE_KIND_COMPUTER) {
+
+ /* TRANSLATORS: the cell battery is very low */
+ title = _("Attached computer battery low");
+
+ /* TRANSLATORS: the device is just going to stop working */
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (_("Attached computer is very low in power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ percentage);
+ else
+ message = g_strdup_printf (_("Attached computer is very low in power. "
+ "The device will soon shutdown if not charged."));
+
+ }
+
+ /* close any existing notification of this class */
+ notify_close_if_showing (&manager->notification_low);
+
+ /* create a new notification */
+ create_notification (title, message,
+ icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_low);
+ notify_notification_set_timeout (manager->notification_low,
+ NOTIFY_EXPIRES_NEVER);
+
+ notify_notification_show (manager->notification_low, NULL);
+
+ switch (kind) {
+
+ case UP_DEVICE_KIND_BATTERY:
+ case UP_DEVICE_KIND_UPS:
+ g_debug ("critical charge level reached, starting sound loop");
+ play_loop_start (&manager->critical_alert_timeout_id);
+ break;
+
+ default:
+ /* play the sound, using sounds from the naming spec */
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "battery-caution",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+ break;
+ }
+
+ g_free (icon_name);
+ g_free (message);
+}
+
+static void
+engine_charge_action (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title = NULL;
+ gchar *message = NULL;
+ char *icon_name;
+ GsdPowerActionType policy;
+ guint timer_id;
+ UpDeviceKind kind;
+
+ /* get device properties */
+ g_object_get (device,
+ "kind", &kind,
+ "icon-name", &icon_name,
+ NULL);
+
+ if (kind == UP_DEVICE_KIND_BATTERY) {
+
+ /* TRANSLATORS: laptop battery is really, really, low */
+ title = _("Laptop battery critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: computer will hibernate */
+ message = g_strdup (_("The battery is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: computer will just shutdown */
+ message = g_strdup (_("The battery is below the critical level and "
+ "this computer is about to shutdown."));
+ }
+
+ /* wait 20 seconds for user-panic */
+ timer_id = g_timeout_add_seconds (GSD_STOP_SOUND_DELAY,
+ (GSourceFunc) manager_critical_action_stop_sound_cb,
+ manager);
+ g_source_set_name_by_id (timer_id, "[GsdPowerManager] battery critical-action");
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ /* TRANSLATORS: UPS is really, really, low */
+ title = _("UPS critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ /* use different text for different actions */
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: computer will hibernate */
+ message = g_strdup (_("UPS is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ /* TRANSLATORS: computer will just shutdown */
+ message = g_strdup (_("UPS is below the critical level and "
+ "this computer is about to shutdown."));
+ }
+
+ /* wait 20 seconds for user-panic */
+ timer_id = g_timeout_add_seconds (GSD_STOP_SOUND_DELAY,
+ (GSourceFunc) manager_critical_action_stop_sound_cb,
+ manager);
+ g_source_set_name_by_id (timer_id, "[GsdPowerManager] ups critical-action");
+ }
+
+ /* not all types have actions */
+ if (title == NULL)
+ return;
+
+ /* close any existing notification of this class */
+ notify_close_if_showing (&manager->notification_low);
+
+ /* create a new notification */
+ create_notification (title, message,
+ icon_name, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_low);
+ notify_notification_set_timeout (manager->notification_low,
+ NOTIFY_EXPIRES_NEVER);
+
+ /* try to show */
+ notify_notification_show (manager->notification_low, NULL);
+
+ /* play the sound, using sounds from the naming spec */
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "battery-caution",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+
+ g_free (icon_name);
+ g_free (message);
+}
+
+static void
+engine_device_warning_changed_cb (UpDevice *device, GParamSpec *pspec, GsdPowerManager *manager)
+{
+ g_autofree char *serial = NULL;
+ UpDeviceLevel warning;
+ UpDeviceKind kind;
+
+ g_object_get (device,
+ "serial", &serial,
+ "warning-level", &warning,
+ "kind", &kind,
+ NULL);
+
+ if (!engine_device_debounce_warn (manager, kind, warning, serial))
+ return;
+
+ if (warning == UP_DEVICE_LEVEL_DISCHARGING) {
+ g_debug ("** EMIT: discharging");
+ engine_ups_discharging (manager, device);
+ } else if (warning == UP_DEVICE_LEVEL_LOW) {
+ g_debug ("** EMIT: charge-low");
+ engine_charge_low (manager, device);
+ } else if (warning == UP_DEVICE_LEVEL_CRITICAL) {
+ g_debug ("** EMIT: charge-critical");
+ engine_charge_critical (manager, device);
+ } else if (warning == UP_DEVICE_LEVEL_ACTION) {
+ g_debug ("** EMIT: charge-action");
+ engine_charge_action (manager, device);
+ } else if (warning == UP_DEVICE_LEVEL_NONE) {
+ /* FIXME: this only handles one notification
+ * for the whole system, instead of one per device */
+ g_debug ("fully charged or charging, hiding notifications if any");
+ play_loop_stop (&manager->critical_alert_timeout_id);
+ if (kind != UP_DEVICE_KIND_UPS)
+ notify_close_if_showing (&manager->notification_low);
+ else
+ notify_close_if_showing (&manager->notification_ups_discharging);
+ }
+
+ if (kind == UP_DEVICE_KIND_BATTERY ||
+ kind == UP_DEVICE_KIND_UPS)
+ main_battery_or_ups_low_changed (manager, (warning != UP_DEVICE_LEVEL_NONE));
+}
+
+static void
+gnome_session_shutdown_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (result == NULL) {
+ g_warning ("couldn't shutdown using gnome-session: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gnome_session_shutdown (GsdPowerManager *manager)
+{
+ g_dbus_proxy_call (G_DBUS_PROXY (manager->session),
+ "Shutdown",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL,
+ gnome_session_shutdown_cb, NULL);
+}
+
+static void
+gnome_session_logout_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (result == NULL) {
+ g_warning ("couldn't log out using gnome-session: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (result);
+ }
+}
+
+static void
+gnome_session_logout (GsdPowerManager *manager,
+ guint logout_mode)
+{
+ if (g_getenv ("RUNNING_UNDER_GDM")) {
+ g_warning ("Prevented logout from GDM session! This indicates an issue in gsd-power.");
+ return;
+ }
+
+ g_dbus_proxy_call (G_DBUS_PROXY (manager->session),
+ "Logout",
+ g_variant_new ("(u)", logout_mode),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL,
+ gnome_session_logout_cb, NULL);
+}
+
+static void
+dbus_call_log_error (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GVariant) result = NULL;
+ g_autoptr(GError) error = NULL;
+ const gchar *msg = user_data;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (result == NULL)
+ g_warning ("%s: %s", msg, error->message);
+}
+
+static void
+action_poweroff (GsdPowerManager *manager)
+{
+ if (manager->logind_proxy == NULL) {
+ g_warning ("no systemd support");
+ return;
+ }
+ g_dbus_proxy_call (manager->logind_proxy,
+ "PowerOff",
+ g_variant_new ("(b)", FALSE),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ dbus_call_log_error,
+ "Error calling PowerOff");
+}
+
+static void
+action_suspend (GsdPowerManager *manager)
+{
+ if (manager->logind_proxy == NULL) {
+ g_warning ("no systemd support");
+ return;
+ }
+ g_dbus_proxy_call (manager->logind_proxy,
+ "Suspend",
+ g_variant_new ("(b)", FALSE),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ dbus_call_log_error,
+ "Error calling suspend action");
+}
+
+static void
+action_hibernate (GsdPowerManager *manager)
+{
+ if (manager->logind_proxy == NULL) {
+ g_warning ("no systemd support");
+ return;
+ }
+ g_dbus_proxy_call (manager->logind_proxy,
+ "Hibernate",
+ g_variant_new ("(b)", FALSE),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ NULL,
+ dbus_call_log_error,
+ "Error calling Hibernate");
+}
+
+static void
+iio_proxy_claim_light (GsdPowerManager *manager, gboolean active)
+{
+ GError *error = NULL;
+ if (manager->iio_proxy == NULL)
+ return;
+ if (!manager->backlight)
+ return;
+ if (active && !manager->session_is_active)
+ return;
+
+ /* FIXME:
+ * Remove when iio-sensor-proxy sends events only to clients instead
+ * of all listeners:
+ * https://github.com/hadess/iio-sensor-proxy/issues/210 */
+
+ /* disconnect, otherwise callback can be added multiple times */
+ g_signal_handlers_disconnect_by_func (manager->iio_proxy,
+ G_CALLBACK (iio_proxy_changed_cb),
+ manager);
+
+ if (active)
+ g_signal_connect (manager->iio_proxy, "g-properties-changed",
+ G_CALLBACK (iio_proxy_changed_cb), manager);
+
+ if (!g_dbus_proxy_call_sync (manager->iio_proxy,
+ active ? "ClaimLight" : "ReleaseLight",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error)) {
+ g_warning ("Call to iio-proxy failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (active)
+ iio_proxy_changed (manager);
+}
+
+static void
+backlight_enable (GsdPowerManager *manager)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ iio_proxy_claim_light (manager, TRUE);
+ ret = gnome_rr_screen_set_dpms_mode (manager->rr_screen,
+ GNOME_RR_DPMS_ON,
+ &error);
+ if (!ret) {
+ g_warning ("failed to turn the panel on: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_debug ("TESTSUITE: Unblanked screen");
+}
+
+static void
+backlight_disable (GsdPowerManager *manager)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ iio_proxy_claim_light (manager, FALSE);
+ ret = gnome_rr_screen_set_dpms_mode (manager->rr_screen,
+ GNOME_RR_DPMS_OFF,
+ &error);
+ if (!ret) {
+ g_warning ("failed to turn the panel off: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_debug ("TESTSUITE: Blanked screen");
+}
+
+static void
+do_power_action_type (GsdPowerManager *manager,
+ GsdPowerActionType action_type)
+{
+ switch (action_type) {
+ case GSD_POWER_ACTION_SUSPEND:
+ action_suspend (manager);
+ break;
+ case GSD_POWER_ACTION_INTERACTIVE:
+ gnome_session_shutdown (manager);
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ action_hibernate (manager);
+ break;
+ case GSD_POWER_ACTION_SHUTDOWN:
+ /* this is only used on critically low battery where
+ * hibernate is not available and is marginally better
+ * than just powering down the computer mid-write */
+ action_poweroff (manager);
+ break;
+ case GSD_POWER_ACTION_BLANK:
+ backlight_disable (manager);
+ break;
+ case GSD_POWER_ACTION_NOTHING:
+ break;
+ case GSD_POWER_ACTION_LOGOUT:
+ gnome_session_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE);
+ break;
+ }
+}
+
+static GsmInhibitorFlag
+get_idle_inhibitors_for_action (GsdPowerActionType action_type)
+{
+ switch (action_type) {
+ case GSD_POWER_ACTION_BLANK:
+ case GSD_POWER_ACTION_SHUTDOWN:
+ case GSD_POWER_ACTION_INTERACTIVE:
+ return GSM_INHIBITOR_FLAG_IDLE;
+ case GSD_POWER_ACTION_HIBERNATE:
+ case GSD_POWER_ACTION_SUSPEND:
+ return GSM_INHIBITOR_FLAG_SUSPEND; /* in addition to idle */
+ case GSD_POWER_ACTION_NOTHING:
+ return 0;
+ case GSD_POWER_ACTION_LOGOUT:
+ return GSM_INHIBITOR_FLAG_LOGOUT; /* in addition to idle */
+ }
+ return 0;
+}
+
+static gboolean
+is_action_inhibited (GsdPowerManager *manager, GsdPowerActionType action_type)
+{
+ GsmInhibitorFlag flag;
+ gboolean is_inhibited;
+
+ flag = get_idle_inhibitors_for_action (action_type);
+ if (!flag)
+ return FALSE;
+ idle_is_session_inhibited (manager,
+ flag,
+ &is_inhibited);
+ return is_inhibited;
+}
+
+static gboolean
+upower_kbd_set_brightness (GsdPowerManager *manager, guint value, GError **error)
+{
+ GVariant *retval;
+
+ /* same as before */
+ if (manager->kbd_brightness_now == value)
+ return TRUE;
+ if (manager->upower_kbd_proxy == NULL)
+ return TRUE;
+
+ /* update h/w value */
+ retval = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+ "SetBrightness",
+ g_variant_new ("(i)", (gint) value),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ manager->cancellable,
+ error);
+ if (retval == NULL)
+ return FALSE;
+
+ /* save new value */
+ manager->kbd_brightness_now = value;
+ g_variant_unref (retval);
+ return TRUE;
+}
+
+static int
+upower_kbd_toggle (GsdPowerManager *manager,
+ GError **error)
+{
+ gboolean ret;
+ int value = -1;
+
+ if (manager->kbd_brightness_old >= 0) {
+ g_debug ("keyboard toggle off");
+ ret = upower_kbd_set_brightness (manager,
+ manager->kbd_brightness_old,
+ error);
+ if (ret) {
+ /* succeeded, set to -1 since now no old value */
+ manager->kbd_brightness_old = -1;
+ value = 0;
+ }
+ } else {
+ g_debug ("keyboard toggle on");
+ /* save the current value to restore later when untoggling */
+ manager->kbd_brightness_old = manager->kbd_brightness_now;
+ ret = upower_kbd_set_brightness (manager, 0, error);
+ if (!ret) {
+ /* failed, reset back to -1 */
+ manager->kbd_brightness_old = -1;
+ } else {
+ value = 0;
+ }
+ }
+
+ if (ret)
+ return value;
+ return -1;
+}
+
+static gboolean
+suspend_on_lid_close (GsdPowerManager *manager)
+{
+ return !external_monitor_is_connected (manager->rr_screen) || !manager->session_is_active;
+}
+
+static gboolean
+inhibit_lid_switch_timer_cb (GsdPowerManager *manager)
+{
+ stop_inhibit_lid_switch_timer (manager);
+
+ if (suspend_on_lid_close (manager)) {
+ g_debug ("no external monitors or session inactive for a while; uninhibiting lid close");
+ uninhibit_lid_switch (manager);
+ }
+
+ /* This is a one shot timer. */
+ return G_SOURCE_REMOVE;
+}
+
+/* Sets up a timer to be triggered some seconds after closing the laptop lid
+ * when the laptop is *not* suspended for some reason. We'll check conditions
+ * again in the timeout handler to see if we can suspend then.
+ */
+static void
+setup_inhibit_lid_switch_timer (GsdPowerManager *manager)
+{
+ if (manager->inhibit_lid_switch_timer_id != 0) {
+ g_debug ("lid close safety timer already set up");
+ return;
+ }
+
+ g_debug ("setting up lid close safety timer");
+
+ manager->inhibit_lid_switch_timer_id = g_timeout_add_seconds (LID_CLOSE_SAFETY_TIMEOUT,
+ (GSourceFunc) inhibit_lid_switch_timer_cb,
+ manager);
+ g_source_set_name_by_id (manager->inhibit_lid_switch_timer_id, "[GsdPowerManager] lid close safety timer");
+}
+
+static void
+stop_inhibit_lid_switch_timer (GsdPowerManager *manager) {
+ if (manager->inhibit_lid_switch_timer_id != 0) {
+ g_debug ("stopping lid close safety timer");
+ g_source_remove (manager->inhibit_lid_switch_timer_id);
+ manager->inhibit_lid_switch_timer_id = 0;
+ }
+}
+
+static void
+restart_inhibit_lid_switch_timer (GsdPowerManager *manager)
+{
+ stop_inhibit_lid_switch_timer (manager);
+ g_debug ("restarting lid close safety timer");
+ setup_inhibit_lid_switch_timer (manager);
+}
+
+static void
+do_lid_open_action (GsdPowerManager *manager)
+{
+ /* play a sound, using sounds from the naming spec */
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "lid-open",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Lid has been opened"),
+ NULL);
+}
+
+static void
+lock_screensaver (GsdPowerManager *manager)
+{
+ gboolean do_lock;
+
+ do_lock = g_settings_get_boolean (manager->settings_screensaver,
+ "lock-enabled");
+ if (!do_lock) {
+ g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->screensaver_proxy),
+ "SetActive",
+ g_variant_new ("(b)", TRUE),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL);
+ return;
+ }
+
+ g_dbus_proxy_call_sync (G_DBUS_PROXY (manager->screensaver_proxy),
+ "Lock",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL);
+}
+
+static void
+do_lid_closed_action (GsdPowerManager *manager)
+{
+ /* play a sound, using sounds from the naming spec */
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "lid-close",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Lid has been closed"),
+ NULL);
+
+ /* refresh RANDR so we get an accurate view of what monitors are plugged in when the lid is closed */
+ gnome_rr_screen_refresh (manager->rr_screen, NULL); /* NULL-GError */
+
+ if (suspend_on_lid_close (manager)) {
+ gboolean is_inhibited;
+
+ idle_is_session_inhibited (manager,
+ GSM_INHIBITOR_FLAG_SUSPEND,
+ &is_inhibited);
+ if (is_inhibited) {
+ g_debug ("Suspend is inhibited but lid is closed, locking the screen");
+ /* We put the screensaver on * as we're not suspending,
+ * but the lid is closed */
+ lock_screensaver (manager);
+ }
+ }
+}
+
+static void
+lid_state_changed_cb (UpClient *client, GParamSpec *pspec, GsdPowerManager *manager)
+{
+ gboolean tmp;
+
+ if (!manager->lid_is_present)
+ return;
+
+ /* same lid state */
+ tmp = up_client_get_lid_is_closed (manager->up_client);
+ if (manager->lid_is_closed == tmp)
+ return;
+ manager->lid_is_closed = tmp;
+ g_debug ("up changed: lid is now %s", tmp ? "closed" : "open");
+
+ if (manager->lid_is_closed)
+ do_lid_closed_action (manager);
+ else
+ do_lid_open_action (manager);
+}
+
+static const gchar *
+idle_mode_to_string (GsdPowerIdleMode mode)
+{
+ if (mode == GSD_POWER_IDLE_MODE_NORMAL)
+ return "normal";
+ if (mode == GSD_POWER_IDLE_MODE_DIM)
+ return "dim";
+ if (mode == GSD_POWER_IDLE_MODE_BLANK)
+ return "blank";
+ if (mode == GSD_POWER_IDLE_MODE_SLEEP)
+ return "sleep";
+ return "unknown";
+}
+
+static const char *
+idle_watch_id_to_string (GsdPowerManager *manager, guint id)
+{
+ if (id == manager->idle_dim_id)
+ return "dim";
+ if (id == manager->idle_blank_id)
+ return "blank";
+ if (id == manager->idle_sleep_id)
+ return "sleep";
+ if (id == manager->idle_sleep_warning_id)
+ return "sleep-warning";
+ return NULL;
+}
+
+static void
+backlight_iface_emit_changed (GsdPowerManager *manager,
+ const char *interface_name,
+ gint32 value,
+ const char *source)
+{
+ GVariant *params;
+
+ /* not yet connected to the bus */
+ if (manager->connection == NULL)
+ return;
+
+ params = g_variant_new_parsed ("(%s, [{'Brightness', <%i>}], @as [])", interface_name,
+ value);
+ g_dbus_connection_emit_signal (manager->connection,
+ NULL,
+ GSD_POWER_DBUS_PATH,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ params, NULL);
+
+ if (!source)
+ return;
+
+ g_dbus_connection_emit_signal (manager->connection,
+ NULL,
+ GSD_POWER_DBUS_PATH,
+ GSD_POWER_DBUS_INTERFACE_KEYBOARD,
+ "BrightnessChanged",
+ g_variant_new ("(is)", value, source),
+ NULL);
+}
+
+static void
+backlight_notify_brightness_cb (GsdPowerManager *manager, GParamSpec *pspec, GsdBacklight *backlight)
+{
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
+ gsd_backlight_get_brightness (backlight, NULL), NULL);
+}
+
+static void
+display_backlight_dim (GsdPowerManager *manager,
+ gint idle_percentage)
+{
+ gint brightness;
+
+ if (!manager->backlight)
+ return;
+
+ /* Fetch the current target brightness (not the actual display brightness)
+ * and return if it is already lower than the idle percentage. */
+ gsd_backlight_get_brightness (manager->backlight, &brightness);
+ if (brightness < idle_percentage)
+ return;
+
+ manager->pre_dim_brightness = brightness;
+ gsd_backlight_set_brightness_async (manager->backlight, idle_percentage, NULL, NULL, NULL);
+}
+
+static gboolean
+kbd_backlight_dim (GsdPowerManager *manager,
+ gint idle_percentage,
+ GError **error)
+{
+ gboolean ret;
+ gint idle;
+ gint max;
+ gint now;
+
+ if (manager->upower_kbd_proxy == NULL)
+ return TRUE;
+
+ now = manager->kbd_brightness_now;
+ max = manager->kbd_brightness_max;
+ idle = PERCENTAGE_TO_ABS (0, max, idle_percentage);
+ if (idle > now) {
+ g_debug ("kbd brightness already now %i/%i, so "
+ "ignoring dim to %i/%i",
+ now, max, idle, max);
+ return TRUE;
+ }
+ ret = upower_kbd_set_brightness (manager, idle, error);
+ if (!ret)
+ return FALSE;
+
+ /* save for undim */
+ manager->kbd_brightness_pre_dim = now;
+ return TRUE;
+}
+
+static void
+upower_kbd_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ gint brightness, percentage;
+ const gchar *source;
+
+ if (g_strcmp0 (signal_name, "BrightnessChangedWithSource") != 0)
+ return;
+
+ g_variant_get (parameters, "(i&s)", &brightness, &source);
+
+ /* Ignore changes caused by us calling UPower's SetBrightness method,
+ * we already call backlight_iface_emit_changed for these after the
+ * SetBrightness method call completes. */
+ if (g_strcmp0 (source, "external") == 0)
+ return;
+
+ manager->kbd_brightness_now = brightness;
+ percentage = ABS_TO_PERCENTAGE (0,
+ manager->kbd_brightness_max,
+ manager->kbd_brightness_now);
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, source);
+}
+
+static gboolean
+is_session_active (GsdPowerManager *manager)
+{
+ GVariant *variant;
+ gboolean is_session_active = FALSE;
+
+ variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (manager->session),
+ "SessionIsActive");
+ if (variant) {
+ is_session_active = g_variant_get_boolean (variant);
+ g_variant_unref (variant);
+ }
+
+ return is_session_active;
+}
+
+static void
+idle_set_mode (GsdPowerManager *manager, GsdPowerIdleMode mode)
+{
+ gboolean ret = FALSE;
+ GError *error = NULL;
+ gint idle_percentage;
+ GsdPowerActionType action_type;
+
+ /* Ignore attempts to set "less idle" modes */
+ if (mode <= manager->current_idle_mode &&
+ mode != GSD_POWER_IDLE_MODE_NORMAL) {
+ g_debug ("Not going to 'less idle' mode %s (current: %s)",
+ idle_mode_to_string (mode),
+ idle_mode_to_string (manager->current_idle_mode));
+ return;
+ }
+
+ /* ensure we're still on an active console */
+ if (!manager->session_is_active) {
+ g_debug ("ignoring state transition to %s as inactive",
+ idle_mode_to_string (mode));
+ return;
+ }
+
+ manager->current_idle_mode = mode;
+ g_debug ("Doing a state transition: %s", idle_mode_to_string (mode));
+
+ /* if we're moving to an idle mode, make sure
+ * we add a watch to take us back to normal */
+ if (mode != GSD_POWER_IDLE_MODE_NORMAL) {
+ if (manager->user_active_id < 1) {
+ manager->user_active_id = gnome_idle_monitor_add_user_active_watch (manager->idle_monitor,
+ idle_became_active_cb,
+ manager,
+ NULL);
+ g_debug ("installing idle_became_active_cb to clear sleep warning when transitioning away from normal (%i)",
+ manager->user_active_id);
+ }
+ }
+
+ /* save current brightness, and set dim level */
+ if (mode == GSD_POWER_IDLE_MODE_DIM) {
+ /* display backlight */
+ idle_percentage = g_settings_get_int (manager->settings,
+ "idle-brightness");
+ display_backlight_dim (manager, idle_percentage);
+
+ /* keyboard backlight */
+ ret = kbd_backlight_dim (manager, idle_percentage, &error);
+ if (!ret) {
+ g_warning ("failed to set dim kbd backlight to %i%%: %s",
+ idle_percentage,
+ error->message);
+ g_clear_error (&error);
+ }
+
+ /* turn off screen and kbd */
+ } else if (mode == GSD_POWER_IDLE_MODE_BLANK) {
+
+ backlight_disable (manager);
+
+ /* only toggle keyboard if present and not already toggled */
+ if (manager->upower_kbd_proxy &&
+ manager->kbd_brightness_old == -1) {
+ if (upower_kbd_toggle (manager, &error) < 0) {
+ g_warning ("failed to turn the kbd backlight off: %s",
+ error->message);
+ g_error_free (error);
+ }
+ }
+
+ /* sleep */
+ } else if (mode == GSD_POWER_IDLE_MODE_SLEEP) {
+
+ if (up_client_get_on_battery (manager->up_client)) {
+ action_type = g_settings_get_enum (manager->settings,
+ "sleep-inactive-battery-type");
+ } else {
+ action_type = g_settings_get_enum (manager->settings,
+ "sleep-inactive-ac-type");
+ }
+ do_power_action_type (manager, action_type);
+
+ /* turn on screen and restore user-selected brightness level */
+ } else if (mode == GSD_POWER_IDLE_MODE_NORMAL) {
+
+ backlight_enable (manager);
+
+ /* reset brightness if we dimmed */
+ if (manager->backlight && manager->pre_dim_brightness >= 0) {
+ gsd_backlight_set_brightness_async (manager->backlight,
+ manager->pre_dim_brightness,
+ NULL, NULL, NULL);
+ /* XXX: Ideally we would do this from the async callback. */
+ manager->pre_dim_brightness = -1;
+ }
+
+ /* only toggle keyboard if present and already toggled off */
+ if (manager->upower_kbd_proxy &&
+ manager->kbd_brightness_old != -1) {
+ if (upower_kbd_toggle (manager, &error) < 0) {
+ g_warning ("failed to turn the kbd backlight on: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ /* reset kbd brightness if we dimmed */
+ if (manager->kbd_brightness_pre_dim >= 0) {
+ ret = upower_kbd_set_brightness (manager,
+ manager->kbd_brightness_pre_dim,
+ &error);
+ if (!ret) {
+ g_warning ("failed to restore kbd backlight to %i: %s",
+ manager->kbd_brightness_pre_dim,
+ error->message);
+ g_error_free (error);
+ }
+ manager->kbd_brightness_pre_dim = -1;
+ }
+
+ }
+}
+
+static gboolean
+idle_is_session_inhibited (GsdPowerManager *manager,
+ GsmInhibitorFlag mask,
+ gboolean *is_inhibited)
+{
+ GVariant *variant;
+ GsmInhibitorFlag inhibited_actions;
+
+ /* not yet connected to gnome-session */
+ if (manager->session == NULL)
+ return FALSE;
+
+ variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (manager->session),
+ "InhibitedActions");
+ if (!variant)
+ return FALSE;
+
+ inhibited_actions = g_variant_get_uint32 (variant);
+ g_variant_unref (variant);
+
+ *is_inhibited = (inhibited_actions & mask);
+
+ return TRUE;
+}
+
+static void
+clear_idle_watch (GnomeIdleMonitor *monitor,
+ guint *id)
+{
+ if (*id == 0)
+ return;
+ gnome_idle_monitor_remove_watch (monitor, *id);
+ *id = 0;
+}
+
+static void
+idle_configure (GsdPowerManager *manager)
+{
+ gboolean is_idle_inhibited;
+ GsdPowerActionType action_type;
+ guint timeout_sleep;
+ guint timeout_dim;
+ gboolean on_battery;
+
+ if (!idle_is_session_inhibited (manager,
+ GSM_INHIBITOR_FLAG_IDLE,
+ &is_idle_inhibited)) {
+ /* Session isn't available yet, postpone */
+ return;
+ }
+
+ /* set up blank callback only when the screensaver is on,
+ * as it's what will drive the blank */
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_blank_id);
+ if (manager->screensaver_active) {
+ /* The tail is wagging the dog.
+ * The screensaver coming on will blank the screen.
+ * If an event occurs while the screensaver is on,
+ * the aggressive idle watch will handle it */
+ guint timeout_blank = SCREENSAVER_TIMEOUT_BLANK;
+ g_debug ("setting up blank callback for %is", timeout_blank);
+ manager->idle_blank_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+ timeout_blank * 1000,
+ idle_triggered_idle_cb, manager, NULL);
+ }
+
+ /* are we inhibited from going idle */
+ if (!manager->session_is_active ||
+ (is_idle_inhibited && !manager->screensaver_active)) {
+ if (is_idle_inhibited && !manager->screensaver_active)
+ g_debug ("inhibited and screensaver not active, so using normal state");
+ else
+ g_debug ("inactive, so using normal state");
+ idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_sleep_id);
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_dim_id);
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_sleep_warning_id);
+ notify_close_if_showing (&manager->notification_sleep_warning);
+ return;
+ }
+
+ /* only do the sleep timeout when the session is idle
+ * and we aren't inhibited from sleeping (or logging out, etc.) */
+ on_battery = up_client_get_on_battery (manager->up_client);
+ action_type = g_settings_get_enum (manager->settings, on_battery ?
+ "sleep-inactive-battery-type" : "sleep-inactive-ac-type");
+ timeout_sleep = 0;
+ if (!is_action_inhibited (manager, action_type)) {
+ gint timeout_sleep_;
+ timeout_sleep_ = g_settings_get_int (manager->settings, on_battery ?
+ "sleep-inactive-battery-timeout" : "sleep-inactive-ac-timeout");
+ timeout_sleep = CLAMP (timeout_sleep_, 0, G_MAXINT);
+ }
+
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_sleep_id);
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_sleep_warning_id);
+
+ /* don't do any power saving if we're a VM */
+ if (manager->is_virtual_machine &&
+ (action_type == GSD_POWER_ACTION_SUSPEND ||
+ action_type == GSD_POWER_ACTION_HIBERNATE)) {
+ g_debug ("Ignoring sleep timeout with suspend action inside VM");
+ timeout_sleep = 0;
+ }
+
+ /* don't do any automatic logout if we are in GDM */
+ if (g_getenv ("RUNNING_UNDER_GDM") &&
+ (action_type == GSD_POWER_ACTION_LOGOUT)) {
+ g_debug ("Ignoring sleep timeout with logout action inside GDM");
+ timeout_sleep = 0;
+ }
+
+ if (timeout_sleep != 0) {
+ g_debug ("setting up sleep callback %is", timeout_sleep);
+
+ if (action_type != GSD_POWER_ACTION_NOTHING) {
+ manager->idle_sleep_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+ timeout_sleep * 1000,
+ idle_triggered_idle_cb, manager, NULL);
+ }
+
+ if (action_type == GSD_POWER_ACTION_LOGOUT ||
+ action_type == GSD_POWER_ACTION_SUSPEND ||
+ action_type == GSD_POWER_ACTION_HIBERNATE) {
+ guint timeout_sleep_warning_msec;
+
+ manager->sleep_action_type = action_type;
+ timeout_sleep_warning_msec = timeout_sleep * IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER * 1000;
+ if (timeout_sleep_warning_msec * 1000 < MINIMUM_IDLE_DIM_DELAY) {
+ /* 0 is not a valid idle timeout */
+ timeout_sleep_warning_msec = 1;
+ }
+
+ g_debug ("setting up sleep warning callback %i msec", timeout_sleep_warning_msec);
+
+ manager->idle_sleep_warning_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+ timeout_sleep_warning_msec,
+ idle_triggered_idle_cb, manager, NULL);
+ }
+ }
+
+ if (manager->idle_sleep_warning_id == 0)
+ notify_close_if_showing (&manager->notification_sleep_warning);
+
+ /* set up dim callback for when the screen lock is not active,
+ * but only if we actually want to dim. */
+ timeout_dim = 0;
+ if (manager->screensaver_active) {
+ /* Don't dim when the screen lock is active */
+ } else if (!on_battery) {
+ /* Don't dim when charging */
+ } else if (manager->battery_is_low) {
+ /* Aggressively blank when battery is low */
+ timeout_dim = SCREENSAVER_TIMEOUT_BLANK;
+ } else {
+ if (g_settings_get_boolean (manager->settings, "idle-dim")) {
+ timeout_dim = g_settings_get_uint (manager->settings_bus,
+ "idle-delay");
+ if (timeout_dim == 0) {
+ timeout_dim = IDLE_DIM_BLANK_DISABLED_MIN;
+ } else {
+ timeout_dim *= IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER;
+ /* Don't bother dimming if the idle-delay is
+ * too low, we'll do that when we bring down the
+ * screen lock */
+ if (timeout_dim < MINIMUM_IDLE_DIM_DELAY)
+ timeout_dim = 0;
+ }
+ }
+ }
+
+ clear_idle_watch (manager->idle_monitor,
+ &manager->idle_dim_id);
+
+ if (timeout_dim != 0) {
+ g_debug ("setting up dim callback for %is", timeout_dim);
+
+ manager->idle_dim_id = gnome_idle_monitor_add_idle_watch (manager->idle_monitor,
+ timeout_dim * 1000,
+ idle_triggered_idle_cb, manager, NULL);
+ }
+}
+
+static void
+main_battery_or_ups_low_changed (GsdPowerManager *manager,
+ gboolean is_low)
+{
+ if (is_low == manager->battery_is_low)
+ return;
+ manager->battery_is_low = is_low;
+ idle_configure (manager);
+}
+
+static gboolean
+temporary_unidle_done_cb (GsdPowerManager *manager)
+{
+ idle_set_mode (manager, manager->previous_idle_mode);
+ manager->temporary_unidle_on_ac_id = 0;
+ return FALSE;
+}
+
+static void
+set_temporary_unidle_on_ac (GsdPowerManager *manager,
+ gboolean enable)
+{
+ if (!enable) {
+ /* Don't automatically go back to the previous idle
+ mode. The caller probably has a better idea of
+ which state to move to when disabling us. */
+ if (manager->temporary_unidle_on_ac_id != 0) {
+ g_source_remove (manager->temporary_unidle_on_ac_id);
+ manager->temporary_unidle_on_ac_id = 0;
+ }
+ } else {
+ /* Don't overwrite the previous idle mode when an unidle is
+ * already on-going */
+ if (manager->temporary_unidle_on_ac_id != 0) {
+ g_source_remove (manager->temporary_unidle_on_ac_id);
+ } else {
+ manager->previous_idle_mode = manager->current_idle_mode;
+ idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+ }
+ manager->temporary_unidle_on_ac_id = g_timeout_add_seconds (POWER_UP_TIME_ON_AC,
+ (GSourceFunc) temporary_unidle_done_cb,
+ manager);
+ g_source_set_name_by_id (manager->temporary_unidle_on_ac_id, "[gnome-settings-daemon] temporary_unidle_done_cb");
+ }
+}
+
+static void
+up_client_on_battery_cb (UpClient *client,
+ GParamSpec *pspec,
+ GsdPowerManager *manager)
+{
+ if (up_client_get_on_battery (manager->up_client)) {
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "power-unplug",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("On battery power"), NULL);
+ } else {
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "power-plug",
+ /* TRANSLATORS: this is the sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("On AC power"), NULL);
+
+ }
+
+ idle_configure (manager);
+
+ if (manager->lid_is_closed)
+ return;
+
+ if (manager->current_idle_mode == GSD_POWER_IDLE_MODE_BLANK ||
+ manager->current_idle_mode == GSD_POWER_IDLE_MODE_DIM ||
+ manager->temporary_unidle_on_ac_id != 0)
+ set_temporary_unidle_on_ac (manager, TRUE);
+}
+
+static void
+gsd_power_manager_finalize (GObject *object)
+{
+ GsdPowerManager *manager;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_POWER_MANAGER (object));
+
+ manager = GSD_POWER_MANAGER (object);
+
+ g_return_if_fail (manager != NULL);
+
+ gsd_power_manager_stop (manager);
+
+ g_clear_object (&manager->connection);
+
+ if (manager->name_id != 0)
+ g_bus_unown_name (manager->name_id);
+
+ if (manager->iio_proxy_watch_id != 0)
+ g_bus_unwatch_name (manager->iio_proxy_watch_id);
+ manager->iio_proxy_watch_id = 0;
+
+ G_OBJECT_CLASS (gsd_power_manager_parent_class)->finalize (object);
+}
+
+static void
+gsd_power_manager_class_init (GsdPowerManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_power_manager_finalize;
+
+ notify_init ("gnome-settings-daemon");
+}
+
+static void
+handle_screensaver_active (GsdPowerManager *manager,
+ GVariant *parameters)
+{
+ gboolean active;
+
+ g_variant_get (parameters, "(b)", &active);
+ g_debug ("Received screensaver ActiveChanged signal: %d (old: %d)", active, manager->screensaver_active);
+ if (manager->screensaver_active != active) {
+ manager->screensaver_active = active;
+ idle_configure (manager);
+
+ /* Setup blank as soon as the screensaver comes on,
+ * and its fade has finished.
+ *
+ * See also idle_configure() */
+ if (active)
+ idle_set_mode (manager, GSD_POWER_IDLE_MODE_BLANK);
+ }
+}
+
+static void
+handle_wake_up_screen (GsdPowerManager *manager)
+{
+ set_temporary_unidle_on_ac (manager, TRUE);
+}
+
+static void
+screensaver_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ if (g_strcmp0 (signal_name, "ActiveChanged") == 0)
+ handle_screensaver_active (GSD_POWER_MANAGER (user_data), parameters);
+ else if (g_strcmp0 (signal_name, "WakeUpScreen") == 0)
+ handle_wake_up_screen (GSD_POWER_MANAGER (user_data));
+}
+
+static void
+power_keyboard_proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *k_now = NULL;
+ GVariant *k_max = NULL;
+ GError *error = NULL;
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ gint percentage;
+
+ manager->upower_kbd_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (manager->upower_kbd_proxy == NULL) {
+ g_warning ("Could not connect to UPower: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_signal_connect (manager->upower_kbd_proxy, "g-signal",
+ G_CALLBACK (upower_kbd_proxy_signal_cb),
+ manager);
+
+ k_now = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+ "GetBrightness",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ manager->cancellable,
+ &error);
+ if (k_now == NULL) {
+ if (error->domain != G_DBUS_ERROR ||
+ error->code != G_DBUS_ERROR_UNKNOWN_METHOD) {
+ g_warning ("Failed to get brightness: %s",
+ error->message);
+ } else {
+ /* Keyboard brightness is not available */
+ g_clear_object (&manager->upower_kbd_proxy);
+ }
+ g_error_free (error);
+ goto out;
+ }
+
+ k_max = g_dbus_proxy_call_sync (manager->upower_kbd_proxy,
+ "GetMaxBrightness",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ manager->cancellable,
+ &error);
+ if (k_max == NULL) {
+ g_warning ("Failed to get max brightness: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_variant_get (k_now, "(i)", &manager->kbd_brightness_now);
+ g_variant_get (k_max, "(i)", &manager->kbd_brightness_max);
+
+ /* set brightness to max if not currently set so is something
+ * sensible */
+ if (manager->kbd_brightness_now < 0) {
+ gboolean ret;
+ ret = upower_kbd_set_brightness (manager,
+ manager->kbd_brightness_max,
+ &error);
+ if (!ret) {
+ g_warning ("failed to initialize kbd backlight to %i: %s",
+ manager->kbd_brightness_max,
+ error->message);
+ g_error_free (error);
+ }
+ }
+
+ /* Tell the front-end that the brightness changed from
+ * its default "-1/no keyboard backlight available" default */
+ percentage = ABS_TO_PERCENTAGE (0,
+ manager->kbd_brightness_max,
+ manager->kbd_brightness_now);
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, "initial value");
+
+out:
+ if (k_now != NULL)
+ g_variant_unref (k_now);
+ if (k_max != NULL)
+ g_variant_unref (k_max);
+}
+
+static void
+show_sleep_warning (GsdPowerManager *manager)
+{
+ /* close any existing notification of this class */
+ notify_close_if_showing (&manager->notification_sleep_warning);
+
+ /* create a new notification */
+ switch (manager->sleep_action_type) {
+ case GSD_POWER_ACTION_LOGOUT:
+ create_notification (_("Automatic logout"), _("You will soon log out because of inactivity."),
+ NULL, NOTIFICATION_PRIVACY_USER,
+ &manager->notification_sleep_warning);
+ break;
+ case GSD_POWER_ACTION_SUSPEND:
+ create_notification (_("Automatic suspend"), _("Computer will suspend very soon because of inactivity."),
+ NULL, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_sleep_warning);
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ create_notification (_("Automatic hibernation"), _("Computer will suspend very soon because of inactivity."),
+ NULL, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_sleep_warning);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ notify_notification_set_timeout (manager->notification_sleep_warning,
+ NOTIFY_EXPIRES_NEVER);
+ notify_notification_set_urgency (manager->notification_sleep_warning,
+ NOTIFY_URGENCY_CRITICAL);
+
+ notify_notification_show (manager->notification_sleep_warning, NULL);
+}
+
+static void
+idle_set_mode_no_temp (GsdPowerManager *manager,
+ GsdPowerIdleMode mode)
+{
+ if (manager->temporary_unidle_on_ac_id != 0) {
+ manager->previous_idle_mode = mode;
+ return;
+ }
+
+ idle_set_mode (manager, mode);
+}
+
+static void
+idle_triggered_idle_cb (GnomeIdleMonitor *monitor,
+ guint watch_id,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ const char *id_name;
+
+ id_name = idle_watch_id_to_string (manager, watch_id);
+ if (id_name == NULL)
+ g_debug ("idletime watch: %i", watch_id);
+ else
+ g_debug ("idletime watch: %s (%i)", id_name, watch_id);
+
+ if (watch_id == manager->idle_dim_id) {
+ idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_DIM);
+ } else if (watch_id == manager->idle_blank_id) {
+ idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_BLANK);
+ } else if (watch_id == manager->idle_sleep_id) {
+ idle_set_mode_no_temp (manager, GSD_POWER_IDLE_MODE_SLEEP);
+ } else if (watch_id == manager->idle_sleep_warning_id) {
+ show_sleep_warning (manager);
+
+ if (manager->user_active_id < 1) {
+ manager->user_active_id = gnome_idle_monitor_add_user_active_watch (manager->idle_monitor,
+ idle_became_active_cb,
+ manager,
+ NULL);
+ g_debug ("installing idle_became_active_cb to clear sleep warning on activity (%i)",
+ manager->user_active_id);
+ }
+ }
+}
+
+static void
+idle_became_active_cb (GnomeIdleMonitor *monitor,
+ guint watch_id,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ g_debug ("idletime reset (%i)", watch_id);
+
+ set_temporary_unidle_on_ac (manager, FALSE);
+
+ /* close any existing notification about idleness */
+ notify_close_if_showing (&manager->notification_sleep_warning);
+
+ idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+ manager->user_active_id = 0;
+}
+
+static void
+ch_backlight_renormalize (GsdPowerManager *manager)
+{
+ if (manager->ambient_percentage_old < 0)
+ return;
+ if (manager->ambient_last_absolute < 0)
+ return;
+ manager->ambient_norm_value = manager->ambient_last_absolute /
+ (gdouble) manager->ambient_percentage_old;
+ manager->ambient_norm_value *= 100.f;
+ manager->ambient_norm_required = FALSE;
+}
+
+static void
+engine_settings_key_changed_cb (GSettings *settings,
+ const gchar *key,
+ GsdPowerManager *manager)
+{
+ if (g_str_has_prefix (key, "sleep-inactive") ||
+ g_str_equal (key, "idle-delay") ||
+ g_str_equal (key, "idle-dim")) {
+ idle_configure (manager);
+ return;
+ }
+}
+
+static void
+engine_session_properties_changed_cb (GDBusProxy *session,
+ GVariant *changed,
+ char **invalidated,
+ GsdPowerManager *manager)
+{
+ GVariant *v;
+
+ v = g_variant_lookup_value (changed, "SessionIsActive", G_VARIANT_TYPE_BOOLEAN);
+ if (v) {
+ gboolean active;
+
+ active = g_variant_get_boolean (v);
+ g_debug ("Received session is active change: now %s", active ? "active" : "inactive");
+ manager->session_is_active = active;
+ /* when doing the fast-user-switch into a new account,
+ * ensure the new account is undimmed and with the backlight on */
+ if (active) {
+ idle_set_mode (manager, GSD_POWER_IDLE_MODE_NORMAL);
+ iio_proxy_claim_light (manager, TRUE);
+ } else {
+ iio_proxy_claim_light (manager, FALSE);
+ }
+ g_variant_unref (v);
+
+ sync_lid_inhibitor (manager);
+ }
+
+ v = g_variant_lookup_value (changed, "InhibitedActions", G_VARIANT_TYPE_UINT32);
+ if (v) {
+ g_variant_unref (v);
+ g_debug ("Received gnome session inhibitor change");
+ idle_configure (manager);
+ }
+}
+
+static void
+inhibit_lid_switch_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+ if (res == NULL) {
+ g_warning ("Unable to inhibit lid switch: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ manager->inhibit_lid_switch_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (manager->inhibit_lid_switch_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", manager->inhibit_lid_switch_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+}
+
+static void
+inhibit_lid_switch (GsdPowerManager *manager)
+{
+ GVariant *params;
+
+ if (manager->inhibit_lid_switch_taken) {
+ g_debug ("already inhibited lid-switch");
+ return;
+ }
+ g_debug ("Adding lid switch system inhibitor");
+ manager->inhibit_lid_switch_taken = TRUE;
+
+ params = g_variant_new ("(ssss)",
+ "handle-lid-switch",
+ g_get_user_name (),
+ "External monitor attached or configuration changed recently",
+ "block");
+ g_dbus_proxy_call_with_unix_fd_list (manager->logind_proxy,
+ "Inhibit",
+ params,
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_lid_switch_done,
+ manager);
+}
+
+static void
+uninhibit_lid_switch (GsdPowerManager *manager)
+{
+ if (manager->inhibit_lid_switch_fd == -1) {
+ g_debug ("no lid-switch inhibitor");
+ return;
+ }
+ g_debug ("Removing lid switch system inhibitor");
+ close (manager->inhibit_lid_switch_fd);
+ manager->inhibit_lid_switch_fd = -1;
+ manager->inhibit_lid_switch_taken = FALSE;
+}
+
+static void
+inhibit_suspend_done (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ GError *error = NULL;
+ GVariant *res;
+ GUnixFDList *fd_list = NULL;
+ gint idx;
+
+ res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error);
+ if (res == NULL) {
+ g_warning ("Unable to inhibit suspend: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_variant_get (res, "(h)", &idx);
+ manager->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error);
+ if (manager->inhibit_suspend_fd == -1) {
+ g_warning ("Failed to receive system inhibitor fd: %s", error->message);
+ g_error_free (error);
+ }
+ g_debug ("System inhibitor fd is %d", manager->inhibit_suspend_fd);
+ g_object_unref (fd_list);
+ g_variant_unref (res);
+ }
+}
+
+/* We take a delay inhibitor here, which causes logind to send a
+ * PrepareForSleep signal, which gives us a chance to lock the screen
+ * and do some other preparations.
+ */
+static void
+inhibit_suspend (GsdPowerManager *manager)
+{
+ if (manager->inhibit_suspend_taken) {
+ g_debug ("already inhibited lid-switch");
+ return;
+ }
+ g_debug ("Adding suspend delay inhibitor");
+ manager->inhibit_suspend_taken = TRUE;
+ g_dbus_proxy_call_with_unix_fd_list (manager->logind_proxy,
+ "Inhibit",
+ g_variant_new ("(ssss)",
+ "sleep",
+ g_get_user_name (),
+ "GNOME needs to lock the screen",
+ "delay"),
+ 0,
+ G_MAXINT,
+ NULL,
+ NULL,
+ inhibit_suspend_done,
+ manager);
+}
+
+static void
+uninhibit_suspend (GsdPowerManager *manager)
+{
+ if (manager->inhibit_suspend_fd == -1) {
+ g_debug ("no suspend delay inhibitor");
+ return;
+ }
+ g_debug ("Removing suspend delay inhibitor");
+ close (manager->inhibit_suspend_fd);
+ manager->inhibit_suspend_fd = -1;
+ manager->inhibit_suspend_taken = FALSE;
+}
+
+static void
+sync_lid_inhibitor (GsdPowerManager *manager)
+{
+ g_debug ("Syncing lid inhibitor and grabbing it temporarily");
+
+ /* Uninhibiting is done in inhibit_lid_switch_timer_cb,
+ * since we want to give users a few seconds when unplugging
+ * and replugging an external monitor, not suspend right away.
+ */
+ inhibit_lid_switch (manager);
+ restart_inhibit_lid_switch_timer (manager);
+}
+
+static void
+on_randr_event (GnomeRRScreen *screen, gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ g_debug ("Screen configuration changed");
+
+ sync_lid_inhibitor (manager);
+}
+
+static void
+handle_suspend_actions (GsdPowerManager *manager)
+{
+ backlight_disable (manager);
+ uninhibit_suspend (manager);
+}
+
+static void
+handle_resume_actions (GsdPowerManager *manager)
+{
+ /* ensure we turn the panel back on after resume */
+ backlight_enable (manager);
+
+ /* set up the delay again */
+ inhibit_suspend (manager);
+}
+
+static void
+logind_proxy_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ gboolean is_about_to_suspend;
+
+ if (g_strcmp0 (signal_name, "PrepareForSleep") != 0)
+ return;
+ g_variant_get (parameters, "(b)", &is_about_to_suspend);
+ if (is_about_to_suspend) {
+ handle_suspend_actions (manager);
+ } else {
+ handle_resume_actions (manager);
+ }
+}
+
+static void
+on_rr_screen_acquired (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = user_data;
+ GError *error = NULL;
+
+ gnome_settings_profile_start (NULL);
+
+ manager->rr_screen = gnome_rr_screen_new_finish (result, &error);
+
+ if (error) {
+ g_warning ("Could not create GnomeRRScreen: %s\n", error->message);
+ g_error_free (error);
+ gnome_settings_profile_end (NULL);
+
+ return;
+ }
+
+ /* Resolve screen backlight */
+ manager->backlight = gsd_backlight_new (manager->rr_screen, NULL);
+
+ if (manager->backlight)
+ g_signal_connect_object (manager->backlight,
+ "notify::brightness",
+ G_CALLBACK (backlight_notify_brightness_cb),
+ manager, G_CONNECT_SWAPPED);
+
+ /* Set up a delay inhibitor to be informed about suspend attempts */
+ g_signal_connect (manager->logind_proxy, "g-signal",
+ G_CALLBACK (logind_proxy_signal_cb),
+ manager);
+ inhibit_suspend (manager);
+
+ /* track the active session */
+ manager->session = gnome_settings_bus_get_session_proxy ();
+ g_signal_connect_object (manager->session, "g-properties-changed",
+ G_CALLBACK (engine_session_properties_changed_cb),
+ manager, 0);
+ manager->session_is_active = is_session_active (manager);
+
+ /* set up the screens */
+ if (manager->lid_is_present) {
+ g_signal_connect (manager->rr_screen, "changed", G_CALLBACK (on_randr_event), manager);
+ watch_external_monitor (manager->rr_screen);
+ on_randr_event (manager->rr_screen, manager);
+ }
+
+ manager->screensaver_proxy = gnome_settings_bus_get_screen_saver_proxy ();
+
+ g_signal_connect (manager->screensaver_proxy, "g-signal",
+ G_CALLBACK (screensaver_signal_cb), manager);
+
+ manager->kbd_brightness_old = -1;
+ manager->kbd_brightness_pre_dim = -1;
+ manager->pre_dim_brightness = -1;
+ g_signal_connect (manager->settings, "changed",
+ G_CALLBACK (engine_settings_key_changed_cb), manager);
+ g_signal_connect (manager->settings_bus, "changed",
+ G_CALLBACK (engine_settings_key_changed_cb), manager);
+ g_signal_connect (manager->up_client, "device-added",
+ G_CALLBACK (engine_device_added_cb), manager);
+ g_signal_connect (manager->up_client, "device-removed",
+ G_CALLBACK (engine_device_removed_cb), manager);
+ g_signal_connect_after (manager->up_client, "notify::lid-is-closed",
+ G_CALLBACK (lid_state_changed_cb), manager);
+ g_signal_connect (manager->up_client, "notify::on-battery",
+ G_CALLBACK (up_client_on_battery_cb), manager);
+
+ /* connect to UPower for keyboard backlight control */
+ manager->kbd_brightness_now = -1;
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ UPOWER_DBUS_NAME,
+ UPOWER_DBUS_PATH_KBDBACKLIGHT,
+ UPOWER_DBUS_INTERFACE_KBDBACKLIGHT,
+ NULL,
+ power_keyboard_proxy_ready_cb,
+ manager);
+
+ manager->devices_array = g_ptr_array_new_with_free_func (g_object_unref);
+ manager->devices_notified_ht = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ /* create a fake virtual composite battery */
+ manager->device_composite = up_client_get_display_device (manager->up_client);
+ g_signal_connect (manager->device_composite, "notify::warning-level",
+ G_CALLBACK (engine_device_warning_changed_cb), manager);
+
+ /* create IDLETIME watcher */
+ manager->idle_monitor = gnome_idle_monitor_new ();
+
+ /* coldplug the engine */
+ engine_coldplug (manager);
+ idle_configure (manager);
+
+ /* ensure the default dpms timeouts are cleared */
+ backlight_enable (manager);
+
+ if (!gnome_settings_is_wayland ())
+ manager->xscreensaver_watchdog_timer_id = gsd_power_enable_screensaver_watchdog ();
+
+ /* don't blank inside a VM */
+ manager->is_virtual_machine = gsd_power_is_hardware_a_vm ();
+
+ /* queue a signal in case the proxy from gnome-shell was created before we got here
+ (likely, considering that to get here we need a reply from gnome-shell)
+ */
+ if (manager->backlight) {
+ manager->ambient_percentage_old = gsd_backlight_get_brightness (manager->backlight, NULL);
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
+ manager->ambient_percentage_old, NULL);
+ } else {
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN, -1, NULL);
+ }
+
+ gnome_settings_profile_end (NULL);
+}
+
+static void
+iio_proxy_changed (GsdPowerManager *manager)
+{
+ GVariant *val_has = NULL;
+ GVariant *val_als = NULL;
+ gdouble brightness;
+ gdouble alpha;
+ gint64 current_time;
+ gint pc;
+
+ /* no display hardware */
+ if (!manager->backlight)
+ return;
+
+ /* disabled */
+ if (!g_settings_get_boolean (manager->settings, "ambient-enabled"))
+ return;
+
+ /* get latest results, which do not have to be Lux */
+ val_has = g_dbus_proxy_get_cached_property (manager->iio_proxy, "HasAmbientLight");
+ if (val_has == NULL || !g_variant_get_boolean (val_has))
+ goto out;
+ val_als = g_dbus_proxy_get_cached_property (manager->iio_proxy, "LightLevel");
+ if (val_als == NULL || g_variant_get_double (val_als) == 0.0)
+ goto out;
+ manager->ambient_last_absolute = g_variant_get_double (val_als);
+ g_debug ("Read last absolute light level: %f", manager->ambient_last_absolute);
+
+ /* the user has asked to renormalize */
+ if (manager->ambient_norm_required) {
+ g_debug ("Renormalizing light level from old light percentage: %.1f%%",
+ manager->ambient_percentage_old);
+ manager->ambient_accumulator = manager->ambient_percentage_old;
+ ch_backlight_renormalize (manager);
+ }
+
+ /* time-weighted constant for moving average */
+ current_time = g_get_monotonic_time();
+ if (manager->ambient_last_time)
+ alpha = 1.0f / (1.0f + (GSD_AMBIENT_TIME_CONSTANT / (current_time - manager->ambient_last_time)));
+ else
+ alpha = 0.0f;
+ manager->ambient_last_time = current_time;
+
+ /* calculate exponential weighted moving average */
+ brightness = manager->ambient_last_absolute * 100.f / manager->ambient_norm_value;
+ brightness = MIN (brightness, 100.f);
+ brightness = MAX (brightness, 0.f);
+
+ manager->ambient_accumulator = (alpha * brightness) +
+ (1.0 - alpha) * manager->ambient_accumulator;
+
+ /* no valid readings yet */
+ if (manager->ambient_accumulator < 0.f)
+ goto out;
+
+ /* set new value */
+ g_debug ("Setting brightness from ambient %.1f%%",
+ manager->ambient_accumulator);
+ pc = manager->ambient_accumulator;
+
+ if (manager->backlight)
+ gsd_backlight_set_brightness_async (manager->backlight, pc, NULL, NULL, NULL);
+
+ /* Assume setting worked. */
+ manager->ambient_percentage_old = pc;
+out:
+ g_clear_pointer (&val_has, g_variant_unref);
+ g_clear_pointer (&val_als, g_variant_unref);
+}
+
+static void
+iio_proxy_changed_cb (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ gpointer user_data)
+{
+ iio_proxy_changed ((GsdPowerManager *) user_data);
+}
+
+static void
+iio_proxy_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ manager->iio_proxy =
+ g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ 0,
+ NULL,
+ "net.hadess.SensorProxy",
+ "/net/hadess/SensorProxy",
+ "net.hadess.SensorProxy",
+ NULL,
+ NULL);
+ iio_proxy_claim_light (manager, TRUE);
+}
+
+static void
+iio_proxy_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ g_clear_object (&manager->iio_proxy);
+}
+
+gboolean
+gsd_power_manager_start (GsdPowerManager *manager,
+ GError **error)
+{
+ g_debug ("Starting power manager");
+ gnome_settings_profile_start (NULL);
+
+ /* Check whether we have a lid first */
+ manager->up_client = up_client_new ();
+ manager->lid_is_present = up_client_get_lid_is_present (manager->up_client);
+ if (manager->lid_is_present)
+ manager->lid_is_closed = up_client_get_lid_is_closed (manager->up_client);
+
+ /* Set up the logind proxy */
+ manager->logind_proxy =
+ g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ 0,
+ NULL,
+ SYSTEMD_DBUS_NAME,
+ SYSTEMD_DBUS_PATH,
+ SYSTEMD_DBUS_INTERFACE,
+ NULL,
+ error);
+ if (manager->logind_proxy == NULL) {
+ g_debug ("No systemd (logind) support, disabling plugin");
+ return FALSE;
+ }
+
+ /* coldplug the list of screens */
+ gnome_rr_screen_new_async (gdk_screen_get_default (),
+ on_rr_screen_acquired, manager);
+
+ manager->settings = g_settings_new (GSD_POWER_SETTINGS_SCHEMA);
+ manager->settings_screensaver = g_settings_new ("org.gnome.desktop.screensaver");
+ manager->settings_bus = g_settings_new ("org.gnome.desktop.session");
+
+ /* setup ambient light support */
+ manager->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,
+ manager, NULL);
+ manager->ambient_norm_required = TRUE;
+ manager->ambient_accumulator = -1.f;
+ manager->ambient_norm_value = -1.f;
+ manager->ambient_percentage_old = -1.f;
+ manager->ambient_last_absolute = -1.f;
+ manager->ambient_last_time = 0;
+
+ gnome_settings_profile_end (NULL);
+ return TRUE;
+}
+
+void
+gsd_power_manager_stop (GsdPowerManager *manager)
+{
+ g_debug ("Stopping power manager");
+
+ if (manager->inhibit_lid_switch_timer_id != 0) {
+ g_source_remove (manager->inhibit_lid_switch_timer_id);
+ manager->inhibit_lid_switch_timer_id = 0;
+ }
+
+ if (manager->cancellable != NULL) {
+ g_cancellable_cancel (manager->cancellable);
+ g_clear_object (&manager->cancellable);
+ }
+
+ g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+
+ if (manager->up_client)
+ g_signal_handlers_disconnect_by_data (manager->up_client, manager);
+
+ g_clear_object (&manager->session);
+ g_clear_object (&manager->settings);
+ g_clear_object (&manager->settings_screensaver);
+ g_clear_object (&manager->settings_bus);
+ g_clear_object (&manager->up_client);
+
+ iio_proxy_claim_light (manager, FALSE);
+ g_clear_object (&manager->iio_proxy);
+
+ if (manager->inhibit_lid_switch_fd != -1) {
+ close (manager->inhibit_lid_switch_fd);
+ manager->inhibit_lid_switch_fd = -1;
+ manager->inhibit_lid_switch_taken = FALSE;
+ }
+ if (manager->inhibit_suspend_fd != -1) {
+ close (manager->inhibit_suspend_fd);
+ manager->inhibit_suspend_fd = -1;
+ manager->inhibit_suspend_taken = FALSE;
+ }
+
+ g_clear_object (&manager->logind_proxy);
+ g_clear_object (&manager->rr_screen);
+
+ g_clear_pointer (&manager->devices_array, g_ptr_array_unref);
+ g_clear_object (&manager->device_composite);
+ g_clear_pointer (&manager->devices_notified_ht, g_hash_table_destroy);
+
+ g_clear_object (&manager->screensaver_proxy);
+
+ play_loop_stop (&manager->critical_alert_timeout_id);
+
+ g_clear_object (&manager->idle_monitor);
+ g_clear_object (&manager->upower_kbd_proxy);
+
+ if (manager->xscreensaver_watchdog_timer_id > 0) {
+ g_source_remove (manager->xscreensaver_watchdog_timer_id);
+ manager->xscreensaver_watchdog_timer_id = 0;
+ }
+}
+
+static void
+gsd_power_manager_init (GsdPowerManager *manager)
+{
+ manager->inhibit_lid_switch_fd = -1;
+ manager->inhibit_suspend_fd = -1;
+ manager->cancellable = g_cancellable_new ();
+}
+
+/* returns new level */
+static void
+handle_method_call_keyboard (GsdPowerManager *manager,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ gint step;
+ gint value = -1;
+ gboolean ret;
+ guint percentage;
+ GError *error = NULL;
+
+ if (g_strcmp0 (method_name, "StepUp") == 0) {
+ g_debug ("keyboard step up");
+ step = BRIGHTNESS_STEP_AMOUNT (manager->kbd_brightness_max);
+ value = MIN (manager->kbd_brightness_now + step,
+ manager->kbd_brightness_max);
+ ret = upower_kbd_set_brightness (manager, value, &error);
+
+ } else if (g_strcmp0 (method_name, "StepDown") == 0) {
+ g_debug ("keyboard step down");
+ step = BRIGHTNESS_STEP_AMOUNT (manager->kbd_brightness_max);
+ value = MAX (manager->kbd_brightness_now - step, 0);
+ ret = upower_kbd_set_brightness (manager, value, &error);
+
+ } else if (g_strcmp0 (method_name, "Toggle") == 0) {
+ value = upower_kbd_toggle (manager, &error);
+ ret = (value >= 0);
+
+ } else {
+ g_assert_not_reached ();
+ }
+
+ /* return value */
+ if (!ret) {
+ g_dbus_method_invocation_take_error (invocation,
+ error);
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, -1, method_name);
+ } else {
+ percentage = ABS_TO_PERCENTAGE (0,
+ manager->kbd_brightness_max,
+ value);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(i)",
+ percentage));
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, percentage, method_name);
+ }
+}
+
+static void
+backlight_brightness_step_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (object);
+ GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
+ GsdPowerManager *manager;
+ GError *error = NULL;
+ const char *connector;
+ gint brightness;
+
+ manager = g_object_get_data (G_OBJECT (invocation), "gsd-power-manager");
+ brightness = gsd_backlight_set_brightness_finish (backlight, res, &error);
+
+ /* ambient brightness no longer valid */
+ manager->ambient_percentage_old = brightness;
+ manager->ambient_norm_required = TRUE;
+
+ if (error) {
+ g_dbus_method_invocation_take_error (invocation,
+ error);
+ } else {
+ connector = gsd_backlight_get_connector (backlight);
+
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(is)",
+ brightness,
+ connector ? connector : ""));
+ }
+}
+
+/* Callback */
+static void
+backlight_brightness_set_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+ GsdBacklight *backlight = GSD_BACKLIGHT (object);
+ gint brightness;
+
+ /* Return the invocation. */
+ brightness = gsd_backlight_set_brightness_finish (backlight, res, NULL);
+
+ if (brightness >= 0) {
+ manager->ambient_percentage_old = brightness;
+ manager->ambient_norm_required = TRUE;
+ }
+
+ g_object_unref (manager);
+}
+
+static void
+handle_method_call_screen (GsdPowerManager *manager,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ if (!manager->backlight) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+ "No usable backlight could be found!");
+ return;
+ }
+
+ g_object_set_data_full (G_OBJECT (invocation), "gsd-power-manager", g_object_ref (manager), g_object_unref);
+
+ if (g_strcmp0 (method_name, "StepUp") == 0) {
+ g_debug ("screen step up");
+ gsd_backlight_step_up_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+ } else if (g_strcmp0 (method_name, "StepDown") == 0) {
+ g_debug ("screen step down");
+ gsd_backlight_step_down_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+ } else if (g_strcmp0 (method_name, "Cycle") == 0) {
+ g_debug ("screen cycle up");
+ gsd_backlight_cycle_up_async (manager->backlight, NULL, backlight_brightness_step_cb, invocation);
+
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ /* Check session pointer as a proxy for whether the manager is in the
+ start or stop state */
+ if (manager->session == NULL) {
+ return;
+ }
+
+ g_debug ("Calling method '%s.%s' for Power",
+ interface_name, method_name);
+
+ if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+ handle_method_call_screen (manager,
+ method_name,
+ parameters,
+ invocation);
+ } else if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+ handle_method_call_keyboard (manager,
+ method_name,
+ parameters,
+ invocation);
+ } else {
+ g_warning ("not recognised interface: %s", interface_name);
+ }
+}
+
+static GVariant *
+handle_get_property_other (GsdPowerManager *manager,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error)
+{
+ GVariant *retval = NULL;
+ gint32 value;
+
+ if (g_strcmp0 (property_name, "Brightness") != 0) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such property: %s", property_name);
+ return NULL;
+ }
+
+ if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+ if (manager->backlight)
+ value = gsd_backlight_get_brightness (manager->backlight, NULL);
+ else
+ value = -1;
+
+ retval = g_variant_new_int32 (value);
+ } else if (manager->upower_kbd_proxy &&
+ g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+ value = ABS_TO_PERCENTAGE (0,
+ manager->kbd_brightness_max,
+ manager->kbd_brightness_now);
+ retval = g_variant_new_int32 (value);
+ }
+
+ if (retval == NULL) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Failed to get property %s on interface %s",
+ property_name, interface_name);
+ }
+ return retval;
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error, gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ /* Check session pointer as a proxy for whether the manager is in the
+ start or stop state */
+ if (manager->session == NULL) {
+ g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No session");
+ return NULL;
+ }
+
+ if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0 ||
+ g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+ return handle_get_property_other (manager, interface_name, property_name, error);
+ } else {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return NULL;
+ }
+}
+
+static gboolean
+handle_set_property_other (GsdPowerManager *manager,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error)
+{
+ gint32 brightness_value;
+
+ if (g_strcmp0 (property_name, "Brightness") != 0) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such property: %s", property_name);
+ return FALSE;
+ }
+
+ if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
+ /* To do error reporting we would need to handle the Set call
+ * instead of doing it through set_property.
+ * But none of our DBus API users actually read the result. */
+ g_variant_get (value, "i", &brightness_value);
+ if (manager->backlight) {
+ gsd_backlight_set_brightness_async (manager->backlight, brightness_value,
+ NULL,
+ backlight_brightness_set_cb, g_object_ref (manager));
+ return TRUE;
+ } else {
+ g_set_error_literal (error, GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+ "No usable backlight could be found!");
+ return FALSE;
+ }
+
+ } else if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+ g_variant_get (value, "i", &brightness_value);
+ brightness_value = PERCENTAGE_TO_ABS (0, manager->kbd_brightness_max,
+ brightness_value);
+ if (upower_kbd_set_brightness (manager, brightness_value, error)) {
+ brightness_value = ABS_TO_PERCENTAGE (0,
+ manager->kbd_brightness_max,
+ manager->kbd_brightness_now);
+ backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_KEYBOARD, brightness_value, "set property");
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return FALSE;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error, gpointer user_data)
+{
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ /* Check session pointer as a proxy for whether the manager is in the
+ start or stop state */
+ if (manager->session == NULL) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Manager is starting or stopping");
+ return FALSE;
+ }
+
+ if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0 ||
+ g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
+ return handle_set_property_other (manager, interface_name, property_name, value, error);
+ } else {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return FALSE;
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ handle_get_property,
+ handle_set_property
+};
+
+static void
+on_bus_gotten (GObject *source_object,
+ GAsyncResult *res,
+ GsdPowerManager *manager)
+{
+ GDBusConnection *connection;
+ GDBusInterfaceInfo **infos;
+ GError *error = NULL;
+ guint i;
+
+ connection = g_bus_get_finish (res, &error);
+ if (connection == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get session bus: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ manager->connection = connection;
+
+ infos = manager->introspection_data->interfaces;
+ for (i = 0; infos[i] != NULL; i++) {
+ g_dbus_connection_register_object (connection,
+ GSD_POWER_DBUS_PATH,
+ infos[i],
+ &interface_vtable,
+ manager,
+ NULL,
+ NULL);
+ }
+
+ manager->name_id = g_bus_own_name_on_connection (connection,
+ GSD_POWER_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+register_manager_dbus (GsdPowerManager *manager)
+{
+ manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (manager->introspection_data != NULL);
+
+ g_bus_get (G_BUS_TYPE_SESSION,
+ manager->cancellable,
+ (GAsyncReadyCallback) on_bus_gotten,
+ manager);
+}
+
+GsdPowerManager *
+gsd_power_manager_new (void)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ manager_object = g_object_new (GSD_TYPE_POWER_MANAGER, NULL);
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+ register_manager_dbus (manager_object);
+ }
+ return GSD_POWER_MANAGER (manager_object);
+}