summaryrefslogtreecommitdiffstats
path: root/plugins/power
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
commitb0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7 (patch)
tree9f1d8a08a8cbd19d28ec2d31027f8a7ccd90de0d /plugins/power
parentInitial commit. (diff)
downloadgnome-settings-daemon-b0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7.tar.xz
gnome-settings-daemon-b0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7.zip
Adding upstream version 43.0.upstream/43.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/power')
-rw-r--r--plugins/power/gpm-common.c456
-rw-r--r--plugins/power/gpm-common.h59
-rw-r--r--plugins/power/gsd-backlight-helper.c149
-rw-r--r--plugins/power/gsd-backlight.c978
-rw-r--r--plugins/power/gsd-backlight.h81
-rwxr-xr-xplugins/power/gsd-power-constants-update.pl49
-rw-r--r--plugins/power/gsd-power-constants.h42
-rw-r--r--plugins/power/gsd-power-enums-update.c44
-rw-r--r--plugins/power/gsd-power-enums.c.in40
-rw-r--r--plugins/power/gsd-power-enums.h.in27
-rw-r--r--plugins/power/gsd-power-manager.c3561
-rw-r--r--plugins/power/gsd-power-manager.h47
-rw-r--r--plugins/power/gsdpowerconstants.py16
-rw-r--r--plugins/power/gsm-inhibitor-flag.h36
-rw-r--r--plugins/power/gsm-manager-logout-mode.h34
-rw-r--r--plugins/power/gsm-presence-flag.h33
-rw-r--r--plugins/power/main.c7
-rw-r--r--plugins/power/meson.build149
-rw-r--r--plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in32
-rwxr-xr-xplugins/power/test-backlight-helper5
-rwxr-xr-xplugins/power/test.py1310
21 files changed, 7155 insertions, 0 deletions
diff --git a/plugins/power/gpm-common.c b/plugins/power/gpm-common.c
new file mode 100644
index 0000000..a7ca87f
--- /dev/null
+++ b/plugins/power/gpm-common.c
@@ -0,0 +1,456 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005-2011 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <math.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <X11/extensions/dpms.h>
+#include <canberra-gtk.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+#include "gnome-settings-bus.h"
+#include "gpm-common.h"
+#include "gsd-power-constants.h"
+#include "gsd-power-manager.h"
+
+#define XSCREENSAVER_WATCHDOG_TIMEOUT 120 /* seconds */
+#define UPS_SOUND_LOOP_ID 99
+#define GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT 5 /* seconds */
+
+static int
+gsd_power_backlight_convert_safe (int value, int from_range, int to_range)
+{
+ /* round (value / from_range) * to_range */
+ return (value * to_range + from_range / 2) / from_range;
+}
+
+/* take a discrete value with offset and convert to percentage */
+int
+gsd_power_backlight_abs_to_percentage (int min, int max, int value)
+{
+ g_return_val_if_fail (max > min, -1);
+ g_return_val_if_fail (value >= min, -1);
+ g_return_val_if_fail (value <= max, -1);
+ return gsd_power_backlight_convert_safe (value - min, max - min, 100);
+}
+
+/* take a percentage and convert to a discrete value with offset */
+int
+gsd_power_backlight_percentage_to_abs (int min, int max, int value)
+{
+ g_return_val_if_fail (max > min, -1);
+ g_return_val_if_fail (value >= 0, -1);
+ g_return_val_if_fail (value <= 100, -1);
+
+ return min + gsd_power_backlight_convert_safe (value, 100, max - min);
+}
+
+#define GPM_UP_TIME_PRECISION 5*60
+#define GPM_UP_TEXT_MIN_TIME 120
+
+/**
+ * Return value: The time string, e.g. "2 hours 3 minutes"
+ **/
+gchar *
+gpm_get_timestring (guint time_secs)
+{
+ char* timestring = NULL;
+ gint hours;
+ gint minutes;
+
+ /* Add 0.5 to do rounding */
+ minutes = (int) ( ( time_secs / 60.0 ) + 0.5 );
+
+ if (minutes == 0) {
+ timestring = g_strdup (_("Unknown time"));
+ return timestring;
+ }
+
+ if (minutes < 60) {
+ timestring = g_strdup_printf (ngettext ("%i minute",
+ "%i minutes",
+ minutes), minutes);
+ return timestring;
+ }
+
+ hours = minutes / 60;
+ minutes = minutes % 60;
+ if (minutes == 0)
+ timestring = g_strdup_printf (ngettext (
+ "%i hour",
+ "%i hours",
+ hours), hours);
+ else
+ /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes"
+ * Swap order with "%2$s %2$i %1$s %1$i if needed */
+ timestring = g_strdup_printf (_("%i %s %i %s"),
+ hours, ngettext ("hour", "hours", hours),
+ minutes, ngettext ("minute", "minutes", minutes));
+ return timestring;
+}
+
+static gboolean
+parse_vm_kernel_cmdline (gboolean *is_virtual_machine)
+{
+ gboolean ret = FALSE;
+ GRegex *regex;
+ GMatchInfo *match;
+ char *contents;
+ char *word;
+ const char *arg;
+
+ if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, NULL))
+ return ret;
+
+ regex = g_regex_new ("gnome.is_vm=(\\S+)", 0, G_REGEX_MATCH_NOTEMPTY, NULL);
+ if (!g_regex_match (regex, contents, G_REGEX_MATCH_NOTEMPTY, &match))
+ goto out;
+
+ word = g_match_info_fetch (match, 0);
+ g_debug ("Found command-line match '%s'", word);
+ arg = word + strlen ("gnome.is_vm=");
+ if (*arg != '0' && *arg != '1') {
+ g_warning ("Invalid value '%s' for gnome.is_vm passed in kernel command line.\n", arg);
+ } else {
+ *is_virtual_machine = atoi (arg);
+ ret = TRUE;
+ }
+ g_free (word);
+
+out:
+ g_match_info_free (match);
+ g_regex_unref (regex);
+ g_free (contents);
+
+ if (ret)
+ g_debug ("Kernel command-line parsed to %d", *is_virtual_machine);
+
+ return ret;
+}
+
+gboolean
+gsd_power_is_hardware_a_vm (void)
+{
+ const gchar *str;
+ gboolean ret = FALSE;
+ GError *error = NULL;
+ GVariant *inner;
+ GVariant *variant = NULL;
+ GDBusConnection *connection;
+
+ if (parse_vm_kernel_cmdline (&ret))
+ return ret;
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+ NULL,
+ &error);
+ if (connection == NULL) {
+ g_warning ("system bus not available: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+ variant = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.freedesktop.systemd1.Manager",
+ "Virtualization"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (variant == NULL) {
+ g_debug ("Failed to get property '%s': %s", "Virtualization", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* on bare-metal hardware this is the empty string,
+ * otherwise an identifier such as "kvm", "vmware", etc. */
+ g_variant_get (variant, "(v)", &inner);
+ str = g_variant_get_string (inner, NULL);
+ if (str != NULL && str[0] != '\0')
+ ret = TRUE;
+ g_variant_unref (inner);
+out:
+ if (connection != NULL)
+ g_object_unref (connection);
+ if (variant != NULL)
+ g_variant_unref (variant);
+ return ret;
+}
+
+/* This timer goes off every few minutes, whether the user is idle or not,
+ to try and clean up anything that has gone wrong.
+
+ It calls disable_builtin_screensaver() so that if xset has been used,
+ or some other program (like xlock) has messed with the XSetScreenSaver()
+ settings, they will be set back to sensible values (if a server extension
+ is in use, messing with xlock can cause the screensaver to never get a wakeup
+ event, and could cause monitor power-saving to occur, and all manner of
+ heinousness.)
+
+ This code was originally part of gnome-screensaver, see
+ http://git.gnome.org/browse/gnome-screensaver/tree/src/gs-watcher-x11.c?id=fec00b12ec46c86334cfd36b37771cc4632f0d4d#n530
+ */
+static gboolean
+disable_builtin_screensaver (gpointer unused)
+{
+ int current_server_timeout, current_server_interval;
+ int current_prefer_blank, current_allow_exp;
+ int desired_server_timeout, desired_server_interval;
+ int desired_prefer_blank, desired_allow_exp;
+
+ XGetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ &current_server_timeout,
+ &current_server_interval,
+ &current_prefer_blank,
+ &current_allow_exp);
+
+ desired_server_timeout = current_server_timeout;
+ desired_server_interval = current_server_interval;
+ desired_prefer_blank = current_prefer_blank;
+ desired_allow_exp = current_allow_exp;
+
+ desired_server_interval = 0;
+
+ /* I suspect (but am not sure) that DontAllowExposures might have
+ something to do with powering off the monitor as well, at least
+ on some systems that don't support XDPMS? Who know... */
+ desired_allow_exp = AllowExposures;
+
+ /* When we're not using an extension, set the server-side timeout to 0,
+ so that the server never gets involved with screen blanking, and we
+ do it all ourselves. (However, when we *are* using an extension,
+ we tell the server when to notify us, and rather than blanking the
+ screen, the server will send us an X event telling us to blank.)
+ */
+ desired_server_timeout = 0;
+
+ if (desired_server_timeout != current_server_timeout
+ || desired_server_interval != current_server_interval
+ || desired_prefer_blank != current_prefer_blank
+ || desired_allow_exp != current_allow_exp) {
+
+ g_debug ("disabling server builtin screensaver:"
+ " (xset s %d %d; xset s %s; xset s %s)",
+ desired_server_timeout,
+ desired_server_interval,
+ (desired_prefer_blank ? "blank" : "noblank"),
+ (desired_allow_exp ? "expose" : "noexpose"));
+
+ XSetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ desired_server_timeout,
+ desired_server_interval,
+ desired_prefer_blank,
+ desired_allow_exp);
+
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE);
+ }
+
+ return TRUE;
+}
+
+guint
+gsd_power_enable_screensaver_watchdog (void)
+{
+ int dummy;
+ guint id;
+
+ /* Make sure that Xorg's DPMS extension never gets in our
+ * way. The defaults are now applied in Fedora 20 from
+ * being "0" by default to being "600" by default */
+ gdk_x11_display_error_trap_push (gdk_display_get_default ());
+ if (DPMSQueryExtension(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &dummy, &dummy))
+ DPMSSetTimeouts (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), 0, 0, 0);
+ gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ());
+ id = g_timeout_add_seconds (XSCREENSAVER_WATCHDOG_TIMEOUT,
+ disable_builtin_screensaver,
+ NULL);
+ g_source_set_name_by_id (id, "[gnome-settings-daemon] disable_builtin_screensaver");
+ return id;
+}
+
+static gpointer
+parse_mock_mock_external_monitor (gpointer data)
+{
+ const char *mocked_file;
+ mocked_file = g_getenv ("GSD_MOCK_EXTERNAL_MONITOR_FILE");
+
+ return g_strdup (mocked_file);
+}
+
+static const gchar *
+get_mock_external_monitor_file (void)
+{
+ static GOnce mocked_once = G_ONCE_INIT;
+ g_once (&mocked_once, parse_mock_mock_external_monitor, NULL);
+ return mocked_once.retval;
+}
+
+static gboolean
+randr_output_is_on (GnomeRROutput *output)
+{
+ GnomeRRCrtc *crtc;
+
+ crtc = gnome_rr_output_get_crtc (output);
+ if (!crtc)
+ return FALSE;
+ return gnome_rr_crtc_get_current_mode (crtc) != NULL;
+}
+
+static void
+mock_monitor_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ GnomeRRScreen *screen = (GnomeRRScreen *) user_data;
+
+ g_debug ("Mock screen configuration changed");
+ g_signal_emit_by_name (G_OBJECT (screen), "changed");
+}
+
+static void
+screen_destroyed (gpointer user_data,
+ GObject *where_the_object_was)
+{
+ g_object_unref (G_OBJECT (user_data));
+}
+
+void
+watch_external_monitor (GnomeRRScreen *screen)
+{
+ const gchar *filename;
+ GFile *file;
+ GFileMonitor *monitor;
+
+ filename = get_mock_external_monitor_file ();
+ if (!filename)
+ return;
+
+ file = g_file_new_for_commandline_arg (filename);
+ monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
+ g_object_unref (file);
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (mock_monitor_changed), screen);
+ g_object_weak_ref (G_OBJECT (screen), screen_destroyed, monitor);
+}
+
+static gboolean
+mock_external_monitor_is_connected (GnomeRRScreen *screen)
+{
+ char *mock_external_monitor_contents;
+ const gchar *filename;
+
+ filename = get_mock_external_monitor_file ();
+ g_assert (filename);
+
+ if (g_file_get_contents (filename, &mock_external_monitor_contents, NULL, NULL)) {
+ if (mock_external_monitor_contents[0] == '1') {
+ g_free (mock_external_monitor_contents);
+ g_debug ("Mock external monitor is on");
+ return TRUE;
+ } else if (mock_external_monitor_contents[0] == '0') {
+ g_free (mock_external_monitor_contents);
+ g_debug ("Mock external monitor is off");
+ return FALSE;
+ }
+
+ g_error ("Unhandled value for GSD_MOCK_EXTERNAL_MONITOR contents: %s", mock_external_monitor_contents);
+ g_free (mock_external_monitor_contents);
+ }
+
+ return FALSE;
+}
+
+gboolean
+external_monitor_is_connected (GnomeRRScreen *screen)
+{
+ GnomeRROutput **outputs;
+ guint i;
+
+ if (get_mock_external_monitor_file ())
+ return mock_external_monitor_is_connected (screen);
+
+ g_assert (screen != NULL);
+
+ /* see if we have more than one screen plugged in */
+ outputs = gnome_rr_screen_list_outputs (screen);
+ for (i = 0; outputs[i] != NULL; i++) {
+ if (randr_output_is_on (outputs[i]) &&
+ !gnome_rr_output_is_builtin_display (outputs[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+play_sound (void)
+{
+ ca_context_play (ca_gtk_context_get (), UPS_SOUND_LOOP_ID,
+ CA_PROP_EVENT_ID, "battery-caution",
+ CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL);
+}
+
+static gboolean
+play_loop_timeout_cb (gpointer user_data)
+{
+ play_sound ();
+ return TRUE;
+}
+
+void
+play_loop_start (guint *id)
+{
+ if (*id != 0)
+ return;
+
+ *id = g_timeout_add_seconds (GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT,
+ (GSourceFunc) play_loop_timeout_cb,
+ NULL);
+ g_source_set_name_by_id (*id, "[gnome-settings-daemon] play_loop_timeout_cb");
+ play_sound ();
+}
+
+void
+play_loop_stop (guint *id)
+{
+ if (*id == 0)
+ return;
+
+ ca_context_cancel (ca_gtk_context_get (), UPS_SOUND_LOOP_ID);
+ g_source_remove (*id);
+ *id = 0;
+}
diff --git a/plugins/power/gpm-common.h b/plugins/power/gpm-common.h
new file mode 100644
index 0000000..88a8e00
--- /dev/null
+++ b/plugins/power/gpm-common.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2005-2011 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GPMCOMMON_H
+#define __GPMCOMMON_H
+
+#include <glib.h>
+#include <libupower-glib/upower.h>
+
+G_BEGIN_DECLS
+
+/* UPower helpers */
+gchar *gpm_get_timestring (guint time);
+
+/* Power helpers */
+gboolean gsd_power_is_hardware_a_vm (void);
+guint gsd_power_enable_screensaver_watchdog (void);
+void reset_idletime (void);
+
+/* Backlight helpers */
+
+/* on ACPI machines we have 4-16 levels, on others it's ~150 */
+#define BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
+
+#define ABS_TO_PERCENTAGE(min, max, value) gsd_power_backlight_abs_to_percentage(min, max, value)
+#define PERCENTAGE_TO_ABS(min, max, value) gsd_power_backlight_percentage_to_abs(min, max, value)
+
+int gsd_power_backlight_abs_to_percentage (int min, int max, int value);
+int gsd_power_backlight_percentage_to_abs (int min, int max, int value);
+
+/* RandR helpers */
+void watch_external_monitor (GnomeRRScreen *screen);
+gboolean external_monitor_is_connected (GnomeRRScreen *screen);
+
+/* Sound helpers */
+void play_loop_start (guint *id);
+void play_loop_stop (guint *id);
+
+G_END_DECLS
+
+#endif /* __GPMCOMMON_H */
diff --git a/plugins/power/gsd-backlight-helper.c b/plugins/power/gsd-backlight-helper.c
new file mode 100644
index 0000000..f0fbbb5
--- /dev/null
+++ b/plugins/power/gsd-backlight-helper.c
@@ -0,0 +1,149 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Benjamin Berg <bberg@redhat.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_SUCCESS 0
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED 1
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_ARGUMENTS_INVALID 3
+#define GSD_BACKLIGHT_HELPER_EXIT_CODE_INVALID_USER 4
+
+#ifndef __linux__
+#error "gsd-backlight-helper does not work on non-Linux"
+#endif
+
+static void
+usage(int argc, char *argv[])
+{
+ fprintf (stderr, "Usage: %s device brightness\n", argv[0]);
+ fprintf (stderr, " device: The backlight directory starting with \"/sys/class/backlight/\"\n");
+ fprintf (stderr, " brightness: The new brightness to write\n");
+}
+
+int
+main (int argc, char *argv[])
+{
+ char tmp[512];
+ char *device = NULL;
+ int fd, len, res;
+ int uid, euid;
+ int brightness;
+ int result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ DIR *dp = NULL;
+ struct dirent *ep;
+
+ /* check calling UID */
+ uid = getuid ();
+ euid = geteuid ();
+ if (uid != 0 || euid != 0) {
+ fprintf (stderr, "This program can only be used by the root user\n");
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_INVALID_USER;
+ goto done;
+ }
+
+ if (argc != 3) {
+ fprintf (stderr, "Error: Need to be called with exactly two arguments\n");
+ usage (argc, argv);
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_ARGUMENTS_INVALID;
+ goto done;
+ }
+
+ errno = 0;
+ brightness = strtol (argv[2], NULL, 0);
+ if (errno) {
+ fprintf (stderr, "Error: Invalid brightness argument (%d: %s)\n", errno, strerror (errno));
+ usage (argc, argv);
+ goto done;
+ }
+
+ dp = opendir ("/sys/class/backlight");
+ if (dp == NULL) {
+ fprintf (stderr, "Error: Could not open /sys/class/backlight (%d: %s)\n", errno, strerror (errno));
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ goto done;
+ }
+
+ /* May be NULL if the path cannot be resolved */
+ device = realpath (argv[1], NULL);
+
+ while ((ep = readdir (dp))) {
+ char *path;
+
+ if (ep->d_name[0] == '.')
+ continue;
+
+ /* Leave room for "/brightness" */
+ snprintf (tmp, sizeof(tmp) - 11, "/sys/class/backlight/%s", ep->d_name);
+ path = realpath (tmp, NULL);
+ if (path && device && strcmp (path, device) == 0) {
+ free (path);
+ strcat (tmp, "/brightness");
+
+ fd = open (tmp, O_WRONLY);
+ if (fd < 0) {
+ fprintf (stderr, "Error: Could not open brightness sysfs file (%d: %s)\n", errno, strerror(errno));
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ goto done;
+ }
+
+ len = snprintf (tmp, sizeof(tmp), "%d", brightness);
+ if ((res = write (fd, tmp, len)) != len) {
+ if (res == -1)
+ fprintf (stderr, "Error: Writing to file (%d: %s)\n", errno, strerror(errno));
+ else
+ fprintf (stderr, "Error: Wrote the wrong length (%d of %d bytes)!\n", res, len);
+
+ close (fd);
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ goto done;
+ }
+ close (fd);
+
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_SUCCESS;
+ goto done;
+ } else {
+ free (path);
+ }
+ }
+
+ result = GSD_BACKLIGHT_HELPER_EXIT_CODE_FAILED;
+ fprintf (stderr, "Error: Could not find the specified backlight \"%s\"\n", argv[1]);
+
+done:
+ if (device)
+ free (device);
+ if (dp)
+ closedir (dp);
+
+ return result;
+}
+
diff --git a/plugins/power/gsd-backlight.c b/plugins/power/gsd-backlight.c
new file mode 100644
index 0000000..ca5f272
--- /dev/null
+++ b/plugins/power/gsd-backlight.c
@@ -0,0 +1,978 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 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 "gsd-backlight.h"
+#include "gpm-common.h"
+#include "gsd-power-constants.h"
+#include "gsd-power-manager.h"
+
+#ifdef __linux__
+#include <gudev/gudev.h>
+#endif /* __linux__ */
+
+struct _GsdBacklight
+{
+ GObject object;
+
+ gint brightness_min;
+ gint brightness_max;
+ gint brightness_val;
+ gint brightness_target;
+ gint brightness_step;
+
+#ifdef __linux__
+ GDBusProxy *logind_proxy;
+
+ GUdevClient *udev;
+ GUdevDevice *udev_device;
+
+ GTask *active_task;
+ GQueue tasks;
+
+ gint idle_update;
+#endif /* __linux__ */
+
+ GnomeRRScreen *rr_screen;
+ gboolean builtin_display_disabled;
+};
+
+enum {
+ PROP_RR_SCREEN = 1,
+ PROP_BRIGHTNESS,
+ PROP_LAST,
+};
+
+#define SYSTEMD_DBUS_NAME "org.freedesktop.login1"
+#define SYSTEMD_DBUS_PATH "/org/freedesktop/login1/session/auto"
+#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Session"
+
+static GParamSpec *props[PROP_LAST];
+
+static void gsd_backlight_initable_iface_init (GInitableIface *iface);
+static gboolean gsd_backlight_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error);
+
+
+G_DEFINE_TYPE_EXTENDED (GsdBacklight, gsd_backlight, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ gsd_backlight_initable_iface_init);)
+
+#ifdef __linux__
+static GUdevDevice*
+gsd_backlight_udev_get_type (GList *devices, const gchar *type)
+{
+ const gchar *type_tmp;
+ GList *d;
+
+ for (d = devices; d != NULL; d = d->next) {
+ type_tmp = g_udev_device_get_sysfs_attr (d->data, "type");
+ if (g_strcmp0 (type_tmp, type) == 0)
+ return G_UDEV_DEVICE (g_object_ref (d->data));
+ }
+ return NULL;
+}
+
+/*
+ * Search for a raw backlight interface, raw backlight interfaces registered
+ * by the drm driver will have the drm-connector as their parent, check the
+ * drm-connector's enabled sysfs attribute so that we pick the right LCD-panel
+ * connector on laptops with hybrid-gfx. Fall back to just picking the first
+ * raw backlight interface if no enabled interface is found.
+ */
+static GUdevDevice*
+gsd_backlight_udev_get_raw (GList *devices)
+{
+ GUdevDevice *parent;
+ const gchar *attr;
+ GList *d;
+
+ for (d = devices; d != NULL; d = d->next) {
+ attr = g_udev_device_get_sysfs_attr (d->data, "type");
+ if (g_strcmp0 (attr, "raw") != 0)
+ continue;
+
+ parent = g_udev_device_get_parent (d->data);
+ if (!parent)
+ continue;
+
+ attr = g_udev_device_get_sysfs_attr (parent, "enabled");
+ if (!attr || g_strcmp0 (attr, "enabled") != 0)
+ continue;
+
+ return G_UDEV_DEVICE (g_object_ref (d->data));
+ }
+
+ return gsd_backlight_udev_get_type (devices, "raw");
+}
+
+static void
+gsd_backlight_udev_resolve (GsdBacklight *backlight)
+{
+ g_autolist(GUdevDevice) devices = NULL;
+
+ g_assert (backlight->udev != NULL);
+
+ devices = g_udev_client_query_by_subsystem (backlight->udev, "backlight");
+ if (devices == NULL)
+ return;
+
+ /* Search the backlight devices and prefer the types:
+ * firmware -> platform -> raw */
+ backlight->udev_device = gsd_backlight_udev_get_type (devices, "firmware");
+ if (backlight->udev_device != NULL)
+ return;
+
+ backlight->udev_device = gsd_backlight_udev_get_type (devices, "platform");
+ if (backlight->udev_device != NULL)
+ return;
+
+ backlight->udev_device = gsd_backlight_udev_get_raw (devices);
+ if (backlight->udev_device != NULL)
+ return;
+}
+
+static gboolean
+gsd_backlight_udev_idle_update_cb (GsdBacklight *backlight)
+{
+ g_autoptr(GError) error = NULL;
+ gint brightness;
+ g_autofree gchar *path = NULL;
+ g_autofree gchar *contents = NULL;
+ backlight->idle_update = 0;
+
+ /* If we are active again now, just stop. */
+ if (backlight->active_task)
+ return FALSE;
+
+ path = g_build_filename (g_udev_device_get_sysfs_path (backlight->udev_device), "brightness", NULL);
+ if (!g_file_get_contents (path, &contents, NULL, &error)) {
+ g_warning ("Could not get brightness from sysfs: %s", error->message);
+ return FALSE;
+ }
+ brightness = g_ascii_strtoll (contents, NULL, 0);
+
+ /* e.g. brightness lower than our minimum. */
+ brightness = CLAMP (brightness, backlight->brightness_min, backlight->brightness_max);
+
+ /* Only notify if brightness has changed. */
+ if (brightness == backlight->brightness_val)
+ return FALSE;
+
+ backlight->brightness_val = brightness;
+ backlight->brightness_target = brightness;
+ g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+
+ return FALSE;
+}
+
+static void
+gsd_backlight_udev_idle_update (GsdBacklight *backlight)
+{
+ if (backlight->idle_update)
+ return;
+
+ backlight->idle_update = g_idle_add ((GSourceFunc) gsd_backlight_udev_idle_update_cb, backlight);
+}
+
+
+static void
+gsd_backlight_udev_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device, gpointer user_data)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (user_data);
+
+ if (g_strcmp0 (action, "change") != 0)
+ return;
+
+ /* We are going to update our state after processing the tasks anyway. */
+ if (!g_queue_is_empty (&backlight->tasks))
+ return;
+
+ if (g_strcmp0 (g_udev_device_get_sysfs_path (device),
+ g_udev_device_get_sysfs_path (backlight->udev_device)) != 0)
+ return;
+
+ g_debug ("GsdBacklight: Got uevent");
+
+ gsd_backlight_udev_idle_update (backlight);
+}
+
+
+static gboolean
+gsd_backlight_udev_init (GsdBacklight *backlight)
+{
+ const gchar* const subsystems[] = {"backlight", NULL};
+ gint brightness_val;
+
+ backlight->udev = g_udev_client_new (subsystems);
+ gsd_backlight_udev_resolve (backlight);
+ if (backlight->udev_device == NULL)
+ return FALSE;
+
+ backlight->brightness_max = g_udev_device_get_sysfs_attr_as_int (backlight->udev_device,
+ "max_brightness");
+ backlight->brightness_min = MAX (1, backlight->brightness_max * 0.01);
+
+ /* If the interface has less than 100 possible values, and it is of type
+ * raw, then assume that 0 does not turn off the backlight completely. */
+ if (backlight->brightness_max < 99 &&
+ g_strcmp0 (g_udev_device_get_sysfs_attr (backlight->udev_device, "type"), "raw") == 0)
+ backlight->brightness_min = 0;
+
+ /* Ignore a backlight which has no steps. */
+ if (backlight->brightness_min >= backlight->brightness_max) {
+ g_warning ("Resolved kernel backlight has an unusable maximum brightness (%d)", backlight->brightness_max);
+ g_clear_object (&backlight->udev_device);
+ return FALSE;
+ }
+
+ brightness_val = g_udev_device_get_sysfs_attr_as_int (backlight->udev_device,
+ "brightness");
+ backlight->brightness_val = CLAMP (brightness_val,
+ backlight->brightness_min,
+ backlight->brightness_max);
+ g_debug ("Using udev device with brightness from %i to %i. Current brightness is %i.",
+ backlight->brightness_min, backlight->brightness_max, backlight->brightness_val);
+
+ g_signal_connect_object (backlight->udev, "uevent",
+ G_CALLBACK (gsd_backlight_udev_uevent),
+ backlight, 0);
+
+ return TRUE;
+}
+
+
+typedef struct {
+ int value;
+ char *value_str;
+} BacklightHelperData;
+
+static void gsd_backlight_process_taskqueue (GsdBacklight *backlight);
+
+static void
+backlight_task_data_destroy (gpointer data)
+{
+ BacklightHelperData *task_data = (BacklightHelperData*) data;
+
+ g_free (task_data->value_str);
+ g_free (task_data);
+}
+
+static void
+gsd_backlight_set_helper_return (GsdBacklight *backlight, GTask *task, gint result, const GError *error)
+{
+ GTask *finished_task;
+ gint percent = ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, result);
+
+ if (error)
+ g_warning ("Error executing backlight helper: %s", error->message);
+
+ /* If the queue will be empty then update the current value. */
+ if (task == g_queue_peek_tail (&backlight->tasks)) {
+ if (error == NULL) {
+ g_assert (backlight->brightness_target == result);
+
+ backlight->brightness_val = backlight->brightness_target;
+ g_debug ("New brightness value is in effect %i (%i..%i)",
+ backlight->brightness_val, backlight->brightness_min, backlight->brightness_max);
+ g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+ }
+
+ /* The udev handler won't read while a write is pending, so queue an
+ * update in case we have missed some events. */
+ gsd_backlight_udev_idle_update (backlight);
+ }
+
+ /* Return all the pending tasks up and including the one we actually
+ * processed. */
+ do {
+ finished_task = g_queue_pop_head (&backlight->tasks);
+
+ if (error)
+ g_task_return_error (finished_task, g_error_copy (error));
+ else
+ g_task_return_int (finished_task, percent);
+
+ g_object_unref (finished_task);
+ } while (finished_task != task);
+}
+
+static void
+gsd_backlight_set_helper_finish (GObject *obj, GAsyncResult *res, gpointer user_data)
+{
+ g_autoptr(GSubprocess) proc = G_SUBPROCESS (obj);
+ GTask *task = G_TASK (user_data);
+ BacklightHelperData *data = g_task_get_task_data (task);
+ GsdBacklight *backlight = g_task_get_source_object (task);
+ g_autoptr(GError) error = NULL;
+
+ g_assert (task == backlight->active_task);
+ backlight->active_task = NULL;
+
+ g_subprocess_wait_finish (proc, res, &error);
+
+ if (error)
+ goto done;
+
+ g_spawn_check_exit_status (g_subprocess_get_exit_status (proc), &error);
+ if (error)
+ goto done;
+
+done:
+ gsd_backlight_set_helper_return (backlight, task, data->value, error);
+ /* Start processing any tasks that were added in the meantime. */
+ gsd_backlight_process_taskqueue (backlight);
+}
+
+static void
+gsd_backlight_run_set_helper (GsdBacklight *backlight, GTask *task)
+{
+ GSubprocess *proc = NULL;
+ BacklightHelperData *data = g_task_get_task_data (task);
+ const gchar *gsd_backlight_helper = NULL;
+ GError *error = NULL;
+
+ g_assert (backlight->active_task == NULL);
+ backlight->active_task = task;
+
+ if (data->value_str == NULL)
+ data->value_str = g_strdup_printf ("%d", data->value);
+
+ /* This is solely for use by the test environment. If given, execute
+ * this helper instead of the internal helper using pkexec */
+ gsd_backlight_helper = g_getenv ("GSD_BACKLIGHT_HELPER");
+ if (!gsd_backlight_helper) {
+ proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
+ &error,
+ "pkexec",
+ LIBEXECDIR "/gsd-backlight-helper",
+ g_udev_device_get_sysfs_path (backlight->udev_device),
+ data->value_str, NULL);
+ } else {
+ proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
+ &error,
+ gsd_backlight_helper,
+ g_udev_device_get_sysfs_path (backlight->udev_device),
+ data->value_str, NULL);
+ }
+
+ if (proc == NULL) {
+ gsd_backlight_set_helper_return (backlight, task, -1, error);
+ return;
+ }
+
+ g_subprocess_wait_async (proc, g_task_get_cancellable (task),
+ gsd_backlight_set_helper_finish,
+ task);
+}
+
+static void
+gsd_backlight_process_taskqueue (GsdBacklight *backlight)
+{
+ GTask *to_run;
+
+ /* There is already a task active, nothing to do. */
+ if (backlight->active_task)
+ return;
+
+ /* Get the last added task, thereby compressing the updates into one. */
+ to_run = G_TASK (g_queue_peek_tail (&backlight->tasks));
+ if (to_run == NULL)
+ return;
+
+ /* And run it! */
+ gsd_backlight_run_set_helper (backlight, to_run);
+}
+#endif /* __linux__ */
+
+static GnomeRROutput*
+gsd_backlight_rr_find_output (GsdBacklight *backlight, gboolean controllable)
+{
+ GnomeRROutput *output = NULL;
+ GnomeRROutput **outputs;
+ guint i;
+
+ /* search all X11 outputs for the device id */
+ outputs = gnome_rr_screen_list_outputs (backlight->rr_screen);
+ if (outputs == NULL)
+ goto out;
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ gboolean builtin = gnome_rr_output_is_builtin_display (outputs[i]);
+ gint backlight = gnome_rr_output_get_backlight (outputs[i]);
+
+ g_debug("Output %d: %s, backlight %d", i, builtin ? "builtin" : "external", backlight);
+ if (builtin && (!controllable || backlight >= 0)) {
+ output = outputs[i];
+ break;
+ }
+ }
+out:
+ return output;
+}
+
+/**
+ * gsd_backlight_get_brightness
+ * @backlight: a #GsdBacklight
+ * @target: Output parameter for the value the target value of pending set operations.
+ *
+ * The backlight value returns the last known stable value. This value will
+ * only update once all pending operations to set a new value have finished.
+ *
+ * As such, this function may return a different value from the return value
+ * of the async brightness setter. This happens when another set operation was
+ * queued after it was already running.
+ *
+ * If the internal display is detected as disabled, then the function will
+ * instead return -1.
+ *
+ * Returns: The last stable backlight value or -1 if the internal display is disabled.
+ **/
+gint
+gsd_backlight_get_brightness (GsdBacklight *backlight, gint *target)
+{
+ if (backlight->builtin_display_disabled)
+ return -1;
+
+ if (target)
+ *target = ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, backlight->brightness_target);
+
+ return ABS_TO_PERCENTAGE (backlight->brightness_min, backlight->brightness_max, backlight->brightness_val);
+}
+
+static void
+gsd_backlight_set_brightness_val_async (GsdBacklight *backlight,
+ int value,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GTask *task = NULL;
+ GnomeRROutput *output;
+ gint percent;
+
+ value = MIN(backlight->brightness_max, value);
+ value = MAX(backlight->brightness_min, value);
+
+ backlight->brightness_target = value;
+
+ task = g_task_new (backlight, cancellable, callback, user_data);
+
+#ifdef __linux__
+ if (backlight->udev_device != NULL) {
+ BacklightHelperData *task_data;
+
+ if (backlight->logind_proxy) {
+ g_dbus_proxy_call (backlight->logind_proxy,
+ "SetBrightness",
+ g_variant_new ("(ssu)",
+ "backlight",
+ g_udev_device_get_name (backlight->udev_device),
+ backlight->brightness_target),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL,
+ NULL, NULL);
+
+ percent = ABS_TO_PERCENTAGE (backlight->brightness_min,
+ backlight->brightness_max,
+ backlight->brightness_target);
+ g_task_return_int (task, percent);
+ } else {
+ task_data = g_new0 (BacklightHelperData, 1);
+ task_data->value = backlight->brightness_target;
+ g_task_set_task_data (task, task_data, backlight_task_data_destroy);
+
+ /* Task is set up now. Queue it and ensure we are working something. */
+ g_queue_push_tail (&backlight->tasks, task);
+ gsd_backlight_process_taskqueue (backlight);
+ }
+
+ return;
+ }
+#endif /* __linux__ */
+
+ /* Fallback to setting via GNOME RR/X11 */
+ output = gsd_backlight_rr_find_output (backlight, TRUE);
+ if (output) {
+ if (!gnome_rr_output_set_backlight (output, value, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ backlight->brightness_val = gnome_rr_output_get_backlight (output);
+ g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+ g_task_return_int (task, gsd_backlight_get_brightness (backlight, NULL));
+ g_object_unref (task);
+
+ return;
+ }
+
+ g_assert_not_reached ();
+
+ g_task_return_new_error (task, GSD_POWER_MANAGER_ERROR,
+ GSD_POWER_MANAGER_ERROR_FAILED,
+ "No method to set brightness!");
+ g_object_unref (task);
+}
+
+void
+gsd_backlight_set_brightness_async (GsdBacklight *backlight,
+ gint percent,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Overflow/underflow is handled by gsd_backlight_set_brightness_val_async. */
+ gsd_backlight_set_brightness_val_async (backlight,
+ PERCENTAGE_TO_ABS (backlight->brightness_min, backlight->brightness_max, percent),
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gsd_backlight_set_brightness_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_set_brightness_async(). Will
+ * return the value that was actually set (which may be different because of
+ * rounding or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_set_brightness_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_int (G_TASK (res), error);
+}
+
+void
+gsd_backlight_step_up_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gint value;
+
+ /* Overflows are handled by gsd_backlight_set_brightness_val_async. */
+ value = backlight->brightness_target + backlight->brightness_step;
+
+ gsd_backlight_set_brightness_val_async (backlight,
+ value,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gsd_backlight_step_up_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_step_up_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_step_up_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_int (G_TASK (res), error);
+}
+
+void
+gsd_backlight_step_down_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gint value;
+
+ /* Underflows are handled by gsd_backlight_set_brightness_val_async. */
+ value = backlight->brightness_target - backlight->brightness_step;
+
+ gsd_backlight_set_brightness_val_async (backlight,
+ value,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gsd_backlight_step_down_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_step_down_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_step_down_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_int (G_TASK (res), error);
+}
+
+/**
+ * gsd_backlight_cycle_up_async
+ * @backlight: a #GsdBacklight
+ * @cancellable: an optional #GCancellable, NULL to ignore
+ * @callback: the #GAsyncReadyCallback invoked for cycle up to be finished
+ * @user_data: the #gpointer passed to the callback
+ *
+ * Start a brightness cycle up operation by gsd_backlight_cycle_up_async().
+ * The brightness will be stepped up if it is not already at the maximum.
+ * If it is already at the maximum, it will be set to the minimum brightness.
+ **/
+void
+gsd_backlight_cycle_up_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ if (backlight->brightness_target < backlight->brightness_max)
+ gsd_backlight_step_up_async (backlight,
+ cancellable,
+ callback,
+ user_data);
+ else
+ gsd_backlight_set_brightness_val_async (backlight,
+ backlight->brightness_min,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * gsd_backlight_cycle_up_finish
+ * @backlight: a #GsdBacklight
+ * @res: the #GAsyncResult passed to the callback
+ * @error: #GError return address
+ *
+ * Finish an operation started by gsd_backlight_cycle_up_async(). Will return
+ * the value that was actually set (which may be different because of rounding
+ * or as multiple set actions were queued up).
+ *
+ * Please note that a call to gsd_backlight_get_brightness() may not in fact
+ * return the same value if further operations to set the value are pending.
+ *
+ * For simplicity it is also valid to call gsd_backlight_set_brightness_finish()
+ * allowing sharing the callback routine for calls to
+ * gsd_backlight_set_brightness_async(), gsd_backlight_step_up_async(),
+ * gsd_backlight_step_down_async() and gsd_backlight_cycle_up_async().
+ *
+ * Returns: The brightness in percent that was set.
+ **/
+gint
+gsd_backlight_cycle_up_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_int (G_TASK (res), error);
+}
+
+/**
+ * gsd_backlight_get_connector
+ * @backlight: a #GsdBacklight
+ *
+ * Return the connector for the display that is being controlled by the
+ * #GsdBacklight object. This connector can be passed to gnome-shell to show
+ * the on screen display only on the affected screen.
+ *
+ * Returns: The connector of the controlled output or NULL if unknown.
+ **/
+const char*
+gsd_backlight_get_connector (GsdBacklight *backlight)
+{
+ GnomeRROutput *output;
+
+ output = gsd_backlight_rr_find_output (backlight, FALSE);
+ if (output == NULL)
+ return NULL;
+
+ return gnome_rr_output_get_name (output);
+}
+
+static void
+gsd_backlight_rr_screen_changed_cb (GnomeRRScreen *screen,
+ gpointer data)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (data);
+ GnomeRROutput *output;
+ gboolean builtin_display_disabled = FALSE;
+
+ /* NOTE: Err on the side of assuming the backlight controlls something
+ * even if we cannot find the output that belongs to it.
+ * This might backfire on us obviously if the hardware claims it
+ * can control a non-existing screen.
+ */
+ output = gsd_backlight_rr_find_output (backlight, FALSE);
+ if (output)
+ builtin_display_disabled = !gnome_rr_output_get_crtc (output);
+
+ if (builtin_display_disabled != backlight->builtin_display_disabled) {
+ backlight->builtin_display_disabled = builtin_display_disabled;
+ g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+ }
+}
+
+static void
+gsd_backlight_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+ switch (prop_id) {
+ case PROP_RR_SCREEN:
+ g_value_set_object (value, backlight->rr_screen);
+ break;
+
+ case PROP_BRIGHTNESS:
+ g_value_set_int (value, gsd_backlight_get_brightness (backlight, NULL));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsd_backlight_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+ switch (prop_id) {
+ case PROP_RR_SCREEN:
+ backlight->rr_screen = g_value_dup_object (value);
+
+ g_signal_connect_object (backlight->rr_screen, "changed",
+ G_CALLBACK (gsd_backlight_rr_screen_changed_cb),
+ object, 0);
+ gsd_backlight_rr_screen_changed_cb (backlight->rr_screen, object);
+
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gsd_backlight_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (initable);
+ GnomeRROutput* output = NULL;
+ GError *logind_error = NULL;
+
+ if (cancellable != NULL) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "GsdBacklight does not support cancelling initialization.");
+ return FALSE;
+ }
+
+#ifdef __linux__
+ backlight->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, &logind_error);
+ if (backlight->logind_proxy) {
+ /* Check that the SetBrightness method does exist */
+ g_dbus_proxy_call_sync (backlight->logind_proxy,
+ "SetBrightness", NULL,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL, &logind_error);
+
+ if (g_error_matches (logind_error, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS)) {
+ /* We are calling the method with no arguments, so
+ * this is expected.
+ */
+ g_clear_error (&logind_error);
+ } else if (g_error_matches (logind_error, G_DBUS_ERROR,
+ G_DBUS_ERROR_UNKNOWN_METHOD)) {
+ /* systemd version is too old, so ignore.
+ */
+ g_clear_error (&logind_error);
+ g_clear_object (&backlight->logind_proxy);
+ } else {
+ /* Fail on anything else */
+ g_clear_object (&backlight->logind_proxy);
+ }
+ }
+
+ if (logind_error) {
+ g_warning ("No logind found: %s", logind_error->message);
+ g_error_free (logind_error);
+ }
+
+ /* Try finding a udev device. */
+ if (gsd_backlight_udev_init (backlight))
+ goto found;
+#endif /* __linux__ */
+
+ /* Try GNOME RR as a fallback. */
+ output = gsd_backlight_rr_find_output (backlight, TRUE);
+ if (output) {
+ g_debug ("Using GNOME RR (mutter) for backlight.");
+ backlight->brightness_min = 1;
+ backlight->brightness_max = 100;
+ backlight->brightness_val = gnome_rr_output_get_backlight (output);
+ backlight->brightness_step = gnome_rr_output_get_min_backlight_step (output);
+
+ goto found;
+ }
+
+ g_debug ("No usable backlight found.");
+
+ g_set_error_literal (error, GSD_POWER_MANAGER_ERROR, GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+ "No usable backlight could be found!");
+
+ return FALSE;
+
+found:
+ backlight->brightness_target = backlight->brightness_val;
+ backlight->brightness_step = MAX(backlight->brightness_step, BRIGHTNESS_STEP_AMOUNT(backlight->brightness_max - backlight->brightness_min + 1));
+
+ g_debug ("Step size for backlight is %i.", backlight->brightness_step);
+
+ return TRUE;
+}
+
+static void
+gsd_backlight_finalize (GObject *object)
+{
+ GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+#ifdef __linux__
+ g_assert (backlight->active_task == NULL);
+ g_assert (g_queue_is_empty (&backlight->tasks));
+ g_clear_object (&backlight->logind_proxy);
+ g_clear_object (&backlight->udev);
+ g_clear_object (&backlight->udev_device);
+ if (backlight->idle_update) {
+ g_source_remove (backlight->idle_update);
+ backlight->idle_update = 0;
+ }
+#endif /* __linux__ */
+
+ g_clear_object (&backlight->rr_screen);
+}
+
+static void
+gsd_backlight_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = gsd_backlight_initable_init;
+}
+
+static void
+gsd_backlight_class_init (GsdBacklightClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_backlight_finalize;
+ object_class->get_property = gsd_backlight_get_property;
+ object_class->set_property = gsd_backlight_set_property;
+
+ props[PROP_RR_SCREEN] = g_param_spec_object ("rr-screen", "GnomeRRScreen",
+ "GnomeRRScreen usable for backlight control.",
+ GNOME_TYPE_RR_SCREEN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ props[PROP_BRIGHTNESS] = g_param_spec_int ("brightness", "The display brightness",
+ "The brightness of the internal display in percent.",
+ 0, 100, 100,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, PROP_LAST, props);
+}
+
+
+static void
+gsd_backlight_init (GsdBacklight *backlight)
+{
+ backlight->brightness_target = -1;
+ backlight->brightness_min = -1;
+ backlight->brightness_max = -1;
+ backlight->brightness_val = -1;
+ backlight->brightness_step = 1;
+
+#ifdef __linux__
+ backlight->active_task = NULL;
+ g_queue_init (&backlight->tasks);
+#endif /* __linux__ */
+}
+
+GsdBacklight *
+gsd_backlight_new (GnomeRRScreen *rr_screen,
+ GError **error)
+{
+ return GSD_BACKLIGHT (g_initable_new (GSD_TYPE_BACKLIGHT, NULL, error,
+ "rr-screen", rr_screen,
+ NULL));
+}
+
diff --git a/plugins/power/gsd-backlight.h b/plugins/power/gsd-backlight.h
new file mode 100644
index 0000000..e4fac6b
--- /dev/null
+++ b/plugins/power/gsd-backlight.h
@@ -0,0 +1,81 @@
+/* -*- mode: c; style: linux -*-
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Written by: Benjamin Berg <bberg@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, 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/>.
+ */
+
+#ifndef _GSD_BACKLIGHT_H
+#define _GSD_BACKLIGHT_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_BACKLIGHT gsd_backlight_get_type ()
+G_DECLARE_FINAL_TYPE (GsdBacklight, gsd_backlight, GSD, BACKLIGHT, GObject);
+
+gint gsd_backlight_get_brightness (GsdBacklight *backlight,
+ gint *target);
+
+void gsd_backlight_set_brightness_async (GsdBacklight *backlight,
+ gint percentage,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+void gsd_backlight_step_up_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+void gsd_backlight_step_down_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+void gsd_backlight_cycle_up_async (GsdBacklight *backlight,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gint gsd_backlight_set_brightness_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error);
+
+gint gsd_backlight_step_up_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error);
+
+gint gsd_backlight_step_down_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error);
+
+gint gsd_backlight_cycle_up_finish (GsdBacklight *backlight,
+ GAsyncResult *res,
+ GError **error);
+
+const char* gsd_backlight_get_connector (GsdBacklight *backlight);
+
+GsdBacklight* gsd_backlight_new (GnomeRRScreen *screen,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif /* _GSD_BACKLIGHT_H */
diff --git a/plugins/power/gsd-power-constants-update.pl b/plugins/power/gsd-power-constants-update.pl
new file mode 100755
index 0000000..1615131
--- /dev/null
+++ b/plugins/power/gsd-power-constants-update.pl
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+
+# Author : Simos Xenitellis <simos at gnome dot org>.
+# Author : Bastien Nocera <hadess@hadess.net>
+# Version : 1.2
+#
+# Input : gsd-power-constants.h
+# Output : gsdpowerconstants.py
+#
+use strict;
+
+# Used for reading the keysymdef symbols.
+my @constantselements;
+
+(scalar @ARGV >= 2) or die "Usage: $0 <input> <output>\n";
+my ($input, $output) = @ARGV;
+
+die "Could not open file gsd-power-constants.h: $!\n" unless open(IN_CONSTANTS, "<:utf8", $input);
+
+# Output: gtk+/gdk/gdkkeysyms.h
+die "Could not open file gsdpowerconstants.py: $!\n" unless open(OUT_CONSTANTS, ">:utf8", $output);
+
+print OUT_CONSTANTS<<EOF;
+
+# File auto-generated from script http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/power/gsd-power-constants-update.pl
+
+# Modified by the GTK+ Team and others 1997-2012. See the AUTHORS
+# file for a list of people on the GTK+ Team. See the ChangeLog
+# files for a list of changes. These files are distributed with
+# GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+
+EOF
+
+while (<IN_CONSTANTS>)
+{
+ next if ( ! /^#define / );
+
+ @constantselements = split(/\s+/);
+ die "Internal error, no \@constantselements: $_\n" unless @constantselements;
+
+ my $constant = $constantselements[1];
+ my $value = $constantselements[2];
+
+ printf OUT_CONSTANTS "%s = %s;\n", $constant, $value;
+}
+
+close IN_CONSTANTS;
+
+printf "We just finished converting $input to $output\nThank you\n";
diff --git a/plugins/power/gsd-power-constants.h b/plugins/power/gsd-power-constants.h
new file mode 100644
index 0000000..8bf9c64
--- /dev/null
+++ b/plugins/power/gsd-power-constants.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 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/>.
+ *
+ */
+
+/* The blank delay when the screensaver is active */
+#define SCREENSAVER_TIMEOUT_BLANK 30 /* seconds */
+
+/* The dim delay when dimming on idle is requested but idle-delay
+ * is set to "Never" */
+#define IDLE_DIM_BLANK_DISABLED_MIN 60 /* seconds */
+
+/* Which fraction of the idle-delay is the idle-dim delay */
+#define IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER 1.0/2.0
+
+/* The dim delay under which we do not bother dimming */
+#define MINIMUM_IDLE_DIM_DELAY 10 /* seconds */
+
+/* The amount of time we'll undim if the machine is idle when plugged in */
+#define POWER_UP_TIME_ON_AC 15 /* seconds */
+
+/* Default brightness values for the mock backlight used in the test suite */
+#define GSD_MOCK_DEFAULT_BRIGHTNESS 50
+#define GSD_MOCK_MAX_BRIGHTNESS 100
+
+/* When unplugging the external monitor, give a certain amount
+ * of time before suspending the laptop */
+#define LID_CLOSE_SAFETY_TIMEOUT 8 /* seconds */
diff --git a/plugins/power/gsd-power-enums-update.c b/plugins/power/gsd-power-enums-update.c
new file mode 100644
index 0000000..546e494
--- /dev/null
+++ b/plugins/power/gsd-power-enums-update.c
@@ -0,0 +1,44 @@
+#include <glib-object.h>
+#include <gsd-power-enums.h>
+#include <stdio.h>
+
+/* XXX: The following functions use printf, because otherwise there were
+ * build failures when the building with the memory sanitizer enabled.
+ * These may be false positives though.
+ */
+
+static void
+output_enum_values (GType class_type)
+{
+ GEnumClass *eclass;
+ guint i;
+
+ eclass = G_ENUM_CLASS (g_type_class_peek (class_type));
+ for (i = 0; i < eclass->n_values; i++) {
+ GEnumValue *value = &(eclass->values[i]);
+ printf ("%s = %d;\n", value->value_name, value->value);
+ }
+}
+
+static void
+output_flags_values (GType class_type)
+{
+ GFlagsClass *fclass;
+ guint i;
+
+ fclass = G_FLAGS_CLASS (g_type_class_peek (class_type));
+ for (i = 0; i < fclass->n_values; i++) {
+ GFlagsValue *value = &(fclass->values[i]);
+ printf ("%s = %d;\n", value->value_name, value->value);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ g_type_class_ref (GSD_POWER_TYPE_INHIBITOR_FLAG);
+ g_type_class_ref (GSD_POWER_TYPE_PRESENCE_STATUS);
+ output_flags_values (GSD_POWER_TYPE_INHIBITOR_FLAG);
+ output_enum_values (GSD_POWER_TYPE_PRESENCE_STATUS);
+ return 0;
+}
diff --git a/plugins/power/gsd-power-enums.c.in b/plugins/power/gsd-power-enums.c.in
new file mode 100644
index 0000000..1b994bb
--- /dev/null
+++ b/plugins/power/gsd-power-enums.c.in
@@ -0,0 +1,40 @@
+/*** BEGIN file-header ***/
+#include "gsd-power-enums.h"
+#include <glib-object.h>
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+#include "@basename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType the_type = 0;
+
+ if (the_type == 0)
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@,
+ "@VALUENAME@",
+ "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ the_type = g_@type@_register_static (
+ g_intern_static_string ("@EnumName@"),
+ values);
+ }
+ return the_type;
+}
+
+/*** END value-tail ***/
diff --git a/plugins/power/gsd-power-enums.h.in b/plugins/power/gsd-power-enums.h.in
new file mode 100644
index 0000000..53e37c0
--- /dev/null
+++ b/plugins/power/gsd-power-enums.h.in
@@ -0,0 +1,27 @@
+/*** BEGIN file-header ***/
+#ifndef GSD_POWER_ENUMS_H
+#define GSD_POWER_ENUMS_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@basename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define GSD_POWER_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !GSD_POWER_ENUMS_H */
+/*** END file-tail ***/
+
diff --git a/plugins/power/gsd-power-manager.c b/plugins/power/gsd-power-manager.c
new file mode 100644
index 0000000..757986e
--- /dev/null
+++ b/plugins/power/gsd-power-manager.c
@@ -0,0 +1,3561 @@
+/* -*- 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 PPD_DBUS_NAME "net.hadess.PowerProfiles"
+#define PPD_DBUS_PATH "/net/hadess/PowerProfiles"
+#define PPD_DBUS_INTERFACE "net.hadess.PowerProfiles"
+
+#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; /* 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;
+
+ /* Power Profiles */
+ GDBusProxy *power_profiles_proxy;
+ guint32 power_saver_cookie;
+ gboolean power_saver_enabled;
+
+ /* 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 && warning != UP_DEVICE_LEVEL_NONE)
+ g_hash_table_insert (manager->devices_notified_ht,
+ g_strdup (serial),
+ GINT_TO_POINTER (warning));
+
+ return ret;
+}
+
+static const struct {
+ UpDeviceKind kind;
+ const char *title;
+ const char *low_body_remain;
+ const char *low_body;
+ const char *low_body_unk;
+ const char *crit_body;
+ const char *crit_body_unk;
+} peripheral_battery_notifications[] = {
+ /* Intentionally skipped types (too uncommon, and name too imprecise):
+ * UP_DEVICE_KIND_MODEM
+ * UP_DEVICE_KIND_NETWORK
+ * UP_DEVICE_KIND_VIDEO
+ * UP_DEVICE_KIND_WEARABLE
+ * UP_DEVICE_KIND_TOY
+ */
+ {
+ .kind = UP_DEVICE_KIND_MOUSE,
+ /* TRANSLATORS: notification title, a wireless mouse is low or very low on power */
+ .title = N_("Mouse battery low"),
+
+ /* TRANSLATORS: notification body, a wireless mouse is low on power */
+ .low_body = N_("Wireless mouse is low on power (%.0f%%)"),
+ .low_body_unk = N_("Wireless mouse is low on power"),
+ /* TRANSLATORS: notification body, a wireless mouse is very low on power */
+ .crit_body = N_("Wireless mouse is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("Wireless mouse is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_KEYBOARD,
+ /* TRANSLATORS: notification title, a wireless keyboard is low or very low on power */
+ .title = N_("Keyboard battery low"),
+
+ /* TRANSLATORS: notification body, a wireless keyboard is low on power */
+ .low_body = N_("Wireless keyboard is low on power (%.0f%%)"),
+ .low_body_unk = N_("Wireless keyboard is low on power"),
+ /* TRANSLATORS: notification body, a wireless keyboard is very low on power */
+ .crit_body = N_("Wireless keyboard is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("Wireless keyboard is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_PDA,
+ /* TRANSLATORS: notification title, a PDA (Personal Digital Assistance device) is low or very on power */
+ .title = N_("PDA battery low"),
+
+ /* TRANSLATORS: notification body, a PDA (Personal Digital Assistance device) is low on power */
+ .low_body = N_("PDA is low on power (%.0f%%)"),
+ .low_body_unk = N_("PDA is low on power"),
+ /* TRANSLATORS: notification body, a PDA (Personal Digital Assistance device) is very low on power */
+ .crit_body = N_("PDA is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("PDA is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_PHONE,
+ /* TRANSLATORS: notification title, a cell phone (mobile phone) is low or very low on power */
+ .title = N_("Cell phone battery low"),
+
+ /* TRANSLATORS: notification body, a cell phone (mobile phone) is low on power */
+ .low_body = N_("Cell phone is low on power (%.0f%%)"),
+ .low_body_unk = N_("Cell phone is low on power"),
+ /* TRANSLATORS: notification body, a cell phone (mobile phone) is very low on power */
+ .crit_body = N_("Cell phone is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("Cell phone is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_MEDIA_PLAYER,
+ /* TRANSLATORS: notification title, a media player (e.g. mp3 player) is low or very low on power */
+ .title = N_("Media player battery low"),
+
+ /* TRANSLATORS: notification body, a media player (e.g. mp3 player) is low on power */
+ .low_body = N_("Media player is low on power (%.0f%%)"),
+ .low_body_unk = N_("Media player is low on power"),
+ /* TRANSLATORS: notification body, a media player (e.g. mp3 player) is very low on power */
+ .crit_body = N_("Media player is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("Media player is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_TABLET,
+ /* TRANSLATORS: notification title, a graphics tablet (e.g. wacom) is low or very low on power */
+ .title = N_("Tablet battery low"),
+
+ /* TRANSLATORS: notification body, a graphics tablet (e.g. wacom) is low on power */
+ .low_body = N_("Tablet is low on power (%.0f%%)"),
+ .low_body_unk = N_("Tablet is low on power"),
+ /* TRANSLATORS: notification body, a graphics tablet (e.g. wacom) is very low on power */
+ .crit_body = N_("Tablet is very low on power (%.0f%%). "
+ "This device will soon stop functioning if not charged."),
+ .crit_body_unk = N_("Tablet is very low on power. "
+ "This device will soon stop functioning if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_COMPUTER,
+ /* TRANSLATORS: notification title, an attached computer (e.g. ipad) is low or very low on power */
+ .title = N_("Attached computer battery low"),
+
+ /* TRANSLATORS: notification body, an attached computer (e.g. ipad) is low on power */
+ .low_body = N_("Attached computer is low on power (%.0f%%)"),
+ .low_body_unk = N_("Attached computer is low on power"),
+ /* TRANSLATORS: notification body, an attached computer (e.g. ipad) is very low on power */
+ .crit_body = N_("Attached computer is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Attached computer is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_GAMING_INPUT,
+ /* TRANSLATORS: notification title, a game controller (e.g. joystick or joypad) is low or very low on power */
+ .title = N_("Game controller battery low"),
+
+ /* TRANSLATORS: notification body, a game controller (e.g. joystick or joypad) is low on power */
+ .low_body = N_("Game controller is low on power (%.0f%%)"),
+ .low_body_unk = N_("Game controller is low on power"),
+ /* TRANSLATORS: notification body, an attached game controller (e.g. joystick or joypad) is very low on power */
+ .crit_body = N_("Game controller is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Game controller is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_PEN,
+ /* TRANSLATORS: notification title, a pen is low or very low on power */
+ .title = N_("Pen battery low"),
+
+ /* TRANSLATORS: notification body, a pen is low on power */
+ .low_body = N_("Pen is low on power (%.0f%%)"),
+ .low_body_unk = N_("Pen is low on power"),
+ /* TRANSLATORS: notification body, a pen is very low on power */
+ .crit_body = N_("Pen is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Pen is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_TOUCHPAD,
+ /* TRANSLATORS: notification title, an external touchpad is low or very low on power */
+ .title = N_("Touchpad battery low"),
+
+ /* TRANSLATORS: notification body, an external touchpad is low on power */
+ .low_body = N_("Touchpad is low on power (%.0f%%)"),
+ .low_body_unk = N_("Touchpad is low on power"),
+ /* TRANSLATORS: notification body, an external touchpad is very low on power */
+ .crit_body = N_("Touchpad is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Touchpad is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_HEADSET,
+ /* TRANSLATORS: notification title, a headset (headphones + microphone) is low or very low on power */
+ .title = N_("Headset battery low"),
+
+ /* TRANSLATORS: notification body, a headset (headphones + microphone) is low on power */
+ .low_body = N_("Headset is low on power (%.0f%%)"),
+ .low_body_unk = N_("Headset is low on power"),
+ /* TRANSLATORS: notification body, a headset (headphones + microphone) is very low on power */
+ .crit_body = N_("Headset is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Headset is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_SPEAKERS,
+ /* TRANSLATORS: notification title, speaker is low or very low on power */
+ .title = N_("Speaker battery low"),
+
+ /* TRANSLATORS: notification body, a speaker is low on power */
+ .low_body = N_("Speaker is low on power (%.0f%%)"),
+ .low_body_unk = N_("Speaker is low on power"),
+ /* TRANSLATORS: notification body, a speaker is very low on power */
+ .crit_body = N_("Speaker is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Speaker is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_HEADPHONES,
+ /* TRANSLATORS: notification title, headphones (no microphone) are low or very low on power */
+ .title = N_("Headphones battery low"),
+
+ /* TRANSLATORS: notification body, headphones (no microphone) are low on power */
+ .low_body = N_("Headphones are low on power (%.0f%%)"),
+ .low_body_unk = N_("Headphones are low on power"),
+ /* TRANSLATORS: notification body, headphones (no microphone) are very low on power */
+ .crit_body = N_("Headphones are very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Headphones are very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_OTHER_AUDIO,
+ /* TRANSLATORS: notification title, an audio device is low or very low on power */
+ .title = N_("Audio device battery low"),
+
+ /* TRANSLATORS: notification body, an audio device is low on power */
+ .low_body = N_("Audio device is low on power (%.0f%%)"),
+ .low_body_unk = N_("Audio device is low on power"),
+ /* TRANSLATORS: notification body, an audio device is very low on power */
+ .crit_body = N_("Audio device is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Audio device is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_REMOTE_CONTROL,
+ /* TRANSLATORS: notification title, a remote control is low or very low on power */
+ .title = N_("Remote battery low"),
+
+ /* TRANSLATORS: notification body, an remote control is low on power */
+ .low_body = N_("Remote is low on power (%.0f%%)"),
+ .low_body_unk = N_("Remote is low on power"),
+ /* TRANSLATORS: notification body, a remote control is very low on power */
+ .crit_body = N_("Remote is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Remote is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_PRINTER,
+ /* TRANSLATORS: notification title, a printer is low or very low on power */
+ .title = N_("Printer battery low"),
+
+ /* TRANSLATORS: notification body, a printer is low on power */
+ .low_body = N_("Printer is low on power (%.0f%%)"),
+ .low_body_unk = N_("Printer is low on power"),
+ /* TRANSLATORS: notification body, a printer is very low on power */
+ .crit_body = N_("Printer is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Printer is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_SCANNER,
+ /* TRANSLATORS: notification title, a scanner is low or very low on power */
+ .title = N_("Scanner battery low"),
+
+ /* TRANSLATORS: notification body, a scanner is low on power */
+ .low_body = N_("Scanner is low on power (%.0f%%)"),
+ .low_body_unk = N_("Scanner is low on power"),
+ /* TRANSLATORS: notification body, a scanner is very low on power */
+ .crit_body = N_("Scanner is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Scanner is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_CAMERA,
+ /* TRANSLATORS: notification title, a camera is low or very low on power */
+ .title = N_("Camera battery low"),
+
+ /* TRANSLATORS: notification body, a camera is low on power */
+ .low_body = N_("Camera is low on power (%.0f%%)"),
+ .low_body_unk = N_("Camera is low on power"),
+ /* TRANSLATORS: notification body, a camera is very low on power */
+ .crit_body = N_("Camera is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Camera is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ .kind = UP_DEVICE_KIND_BLUETOOTH_GENERIC,
+ /* TRANSLATORS: notification title, a Bluetooth device is low or very low on power */
+ .title = N_("Bluetooth device battery low"),
+
+ /* TRANSLATORS: notification body, a Bluetooth device is low on power */
+ .low_body = N_("Bluetooth device is low on power (%.0f%%)"),
+ .low_body_unk = N_("Bluetooth device is low on power"),
+ /* TRANSLATORS: notification body, a Bluetooth device is very low on power */
+ .crit_body = N_("Bluetooth device is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("Bluetooth device is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }, {
+ /* Last entry is the fallback (kind is actually unused)! */
+ .kind = UP_DEVICE_KIND_UNKNOWN,
+ /* TRANSLATORS: notification title, a connected (wireless) device or peripheral of unhandled type is low or very on power */
+ .title = N_("Connected device battery is low"),
+
+ /* TRANSLATORS: notification body, a connected (wireless) device or peripheral of unhandled type is low on power */
+ .low_body = N_("A connected device is low on power (%.0f%%)"),
+ .low_body_unk = N_("A connected device is low on power"),
+ /* TRANSLATORS: notification body, a connected (wireless) device or peripheral of unhandled type is very low on power */
+ .crit_body = N_("A connected device is very low on power (%.0f%%). "
+ "The device will soon shutdown if not charged."),
+ .crit_body_unk = N_("A connected device is very low on power. "
+ "The device will soon shutdown if not charged."),
+ }
+};
+
+static void
+engine_charge_low (GsdPowerManager *manager, UpDevice *device)
+{
+ const gchar *title = NULL;
+ 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) {
+ /* TRANSLATORS: notification title, the battery of this laptop/tablet/phone is running low, shows time remaining */
+ title = _("Battery low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is running low, shows time remaining */
+ message = g_strdup_printf (_("Approximately %s remaining (%.0f%%)"), remaining_text, percentage);
+ g_free (remaining_text);
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ /* TRANSLATORS: notification title, an Uninterruptible Power Supply (UPS) is running low, shows time remaining */
+ title = _("UPS low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, shows time remaining */
+ message = g_strdup_printf (_("Approximately %s of remaining UPS backup power (%.0f%%)"),
+ remaining_text, percentage);
+ g_free (remaining_text);
+ } else {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (peripheral_battery_notifications); i++) {
+ if (peripheral_battery_notifications[i].kind == kind)
+ break;
+ }
+ /* Use the last element if nothing was found*/
+ i = MIN (i, G_N_ELEMENTS (peripheral_battery_notifications) - 1);
+
+ title = gettext (peripheral_battery_notifications[i].title);
+
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (gettext (peripheral_battery_notifications[i].low_body), percentage);
+ else
+ message = g_strdup (gettext (peripheral_battery_notifications[i].low_body_unk));
+ }
+
+ /* 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;
+ 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) {
+ /* TRANSLATORS: notification title, the battery of this laptop/tablet/phone is critically low, warning about action happening soon */
+ title = _("Battery critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is critically low, warning about action happening soon */
+ message = g_strdup_printf (_("Hibernating soon unless plugged in."));
+ } else if (policy == GSD_POWER_ACTION_SHUTDOWN) {
+ message = g_strdup_printf (_("Shutting down soon unless plugged in."));
+ }
+
+ } else if (kind == UP_DEVICE_KIND_UPS) {
+ gchar *remaining_text;
+ gchar *tmp;
+
+ /* TRANSLATORS: notification title, an Uninterruptible Power Supply (UPS) is running low, warning about action happening soon */
+ title = _("UPS critically low");
+ tmp = gpm_get_timestring (time_to_empty);
+ remaining_text = g_strconcat ("<b>", tmp, "</b>", NULL);
+ g_free (tmp);
+
+ /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, warning about action happening soon */
+ 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 {
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (peripheral_battery_notifications); i++) {
+ if (peripheral_battery_notifications[i].kind == kind)
+ break;
+ }
+ /* Use the last element if nothing was found*/
+ i = MIN (i, G_N_ELEMENTS (peripheral_battery_notifications) - 1);
+
+ title = gettext (peripheral_battery_notifications[i].title);
+
+ if (battery_level == UP_DEVICE_LEVEL_NONE)
+ message = g_strdup_printf (gettext (peripheral_battery_notifications[i].crit_body), percentage);
+ else
+ message = g_strdup (gettext (peripheral_battery_notifications[i].crit_body_unk));
+ }
+
+ /* 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: notification title, the battery of this laptop/tablet/phone is critically low, warning about action happening now */
+ title = _("Battery critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: notification body, the battery of this laptop/tablet/phone is critically low, warning about action happening now */
+ message = g_strdup (_("The battery is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_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: notification title, an Uninterruptible Power Supply (UPS) is running low, warning about action happening now */
+ title = _("UPS critically low");
+
+ /* we have to do different warnings depending on the policy */
+ policy = manager_critical_action_get (manager);
+
+ if (policy == GSD_POWER_ACTION_HIBERNATE) {
+ /* TRANSLATORS: notification body, an Uninterruptible Power Supply (UPS) is running low, warning about action happening now */
+ message = g_strdup (_("UPS is below the critical level and "
+ "this computer is about to hibernate."));
+
+ } else if (policy == GSD_POWER_ACTION_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 gboolean
+is_power_save_active (GsdPowerManager *manager)
+{
+ /*
+ * If we have power-profiles-daemon, then we follow its setting,
+ * otherwise we go into power-save mode when the battery is low.
+ */
+ if (manager->power_profiles_proxy &&
+ g_dbus_proxy_get_name_owner (manager->power_profiles_proxy))
+ return manager->power_saver_enabled;
+ else
+ return manager->battery_is_low;
+}
+
+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 (is_power_save_active (manager)) {
+ /* Try to save power by dimming agressively */
+ 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
+hold_profile_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsdPowerManager *manager = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) result = NULL;
+
+ result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+ res,
+ &error);
+ if (result == NULL) {
+ g_warning ("Couldn't hold power-saver profile: %s", error->message);
+ return;
+ }
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(u)"))) {
+ g_variant_get (result, "(u)", &manager->power_saver_cookie);
+ g_debug ("Holding power-saver profile with cookie %u", manager->power_saver_cookie);
+ } else {
+ g_warning ("Calling HoldProfile() did not return a uint32");
+ }
+}
+
+static void
+enable_power_saver (GsdPowerManager *manager)
+{
+ if (!manager->power_profiles_proxy)
+ return;
+ if (!g_settings_get_boolean (manager->settings, "power-saver-profile-on-low-battery"))
+ return;
+
+ g_debug ("Starting hold of power-saver profile");
+
+ g_dbus_proxy_call (manager->power_profiles_proxy,
+ "HoldProfile",
+ g_variant_new("(sss)",
+ "power-saver",
+ "Power saver profile when low on battery",
+ GSD_POWER_DBUS_NAME),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, manager->cancellable, hold_profile_cb, manager);
+}
+
+static void
+disable_power_saver (GsdPowerManager *manager)
+{
+ if (!manager->power_profiles_proxy || manager->power_saver_cookie == 0)
+ return;
+
+ g_debug ("Releasing power-saver profile");
+
+ g_dbus_proxy_call (manager->power_profiles_proxy,
+ "ReleaseProfile",
+ g_variant_new ("(u)", manager->power_saver_cookie),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, dbus_call_log_error, "ReleaseProfile failed");
+ manager->power_saver_cookie = 0;
+}
+
+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);
+ if (is_low)
+ enable_power_saver (manager);
+ else
+ disable_power_saver (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_profiles_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);
+
+ if (g_strcmp0 (signal_name, "ProfileReleased") != 0)
+ return;
+ manager->power_saver_cookie = 0;
+}
+
+static void
+update_active_power_profile (GsdPowerManager *manager)
+{
+ g_autoptr(GVariant) v = NULL;
+ const char *active_profile;
+ gboolean power_saver_enabled;
+
+ v = g_dbus_proxy_get_cached_property (manager->power_profiles_proxy, "ActiveProfile");
+ if (v) {
+ active_profile = g_variant_get_string (v, NULL);
+ power_saver_enabled = g_strcmp0 (active_profile, "power-saver") == 0;
+ if (power_saver_enabled != manager->power_saver_enabled) {
+ manager->power_saver_enabled = power_saver_enabled;
+ idle_configure (manager);
+ }
+ } else {
+ /* p-p-d might have disappeared from the bus */
+ idle_configure (manager);
+ }
+}
+
+static void
+power_profiles_proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ GsdPowerManager *manager = GSD_POWER_MANAGER (user_data);
+
+ manager->power_profiles_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+ if (manager->power_profiles_proxy == NULL) {
+ g_debug ("Could not connect to power-profiles-daemon: %s", error->message);
+ return;
+ }
+
+ g_signal_connect_swapped (manager->power_profiles_proxy,
+ "g-properties-changed",
+ G_CALLBACK (update_active_power_profile),
+ manager);
+ g_signal_connect (manager->power_profiles_proxy, "g-signal",
+ G_CALLBACK (power_profiles_proxy_signal_cb),
+ manager);
+
+ update_active_power_profile (manager);
+}
+
+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"), _("Suspending soon because of inactivity."),
+ NULL, NOTIFICATION_PRIVACY_SYSTEM,
+ &manager->notification_sleep_warning);
+ break;
+ case GSD_POWER_ACTION_HIBERNATE:
+ create_notification (_("Automatic hibernation"), _("Suspending 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;
+ }
+ if (g_str_equal (key, "power-saver-profile-on-low-battery")) {
+ if (manager->battery_is_low &&
+ g_settings_get_boolean (settings, key))
+ enable_power_saver (manager);
+ else
+ disable_power_saver (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)
+{
+ /* close any existing notification about idleness */
+ notify_close_if_showing (&manager->notification_sleep_warning);
+ 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 power-profiles-daemon */
+ g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ PPD_DBUS_NAME,
+ PPD_DBUS_PATH,
+ PPD_DBUS_INTERFACE,
+ manager->cancellable,
+ power_profiles_proxy_ready_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);
+
+ disable_power_saver (manager);
+ g_clear_object (&manager->power_profiles_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);
+}
diff --git a/plugins/power/gsd-power-manager.h b/plugins/power/gsd-power-manager.h
new file mode 100644
index 0000000..75618de
--- /dev/null
+++ b/plugins/power/gsd-power-manager.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * 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/>.
+ *
+ */
+
+#ifndef __GSD_POWER_MANAGER_H
+#define __GSD_POWER_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_POWER_MANAGER (gsd_power_manager_get_type ())
+#define GSD_POWER_MANAGER_ERROR (gsd_power_manager_error_quark ())
+
+G_DECLARE_FINAL_TYPE (GsdPowerManager, gsd_power_manager, GSD, POWER_MANAGER, GObject)
+
+enum
+{
+ GSD_POWER_MANAGER_ERROR_FAILED,
+ GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
+};
+
+GQuark gsd_power_manager_error_quark (void);
+
+GsdPowerManager * gsd_power_manager_new (void);
+gboolean gsd_power_manager_start (GsdPowerManager *manager,
+ GError **error);
+void gsd_power_manager_stop (GsdPowerManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_POWER_MANAGER_H */
diff --git a/plugins/power/gsdpowerconstants.py b/plugins/power/gsdpowerconstants.py
new file mode 100644
index 0000000..26fa5bf
--- /dev/null
+++ b/plugins/power/gsdpowerconstants.py
@@ -0,0 +1,16 @@
+
+# File auto-generated from script http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/power/gsd-power-constants-update.pl
+
+# Modified by the GTK+ Team and others 1997-2012. See the AUTHORS
+# file for a list of people on the GTK+ Team. See the ChangeLog
+# files for a list of changes. These files are distributed with
+# GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+
+SCREENSAVER_TIMEOUT_BLANK = 15;
+IDLE_DIM_BLANK_DISABLED_MIN = 60;
+IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER = 1.0/2.0;
+MINIMUM_IDLE_DIM_DELAY = 10;
+POWER_UP_TIME_ON_AC = 15;
+GSD_MOCK_DEFAULT_BRIGHTNESS = 50;
+GSD_MOCK_MAX_BRIGHTNESS = 100;
+LID_CLOSE_SAFETY_TIMEOUT = 8;
diff --git a/plugins/power/gsm-inhibitor-flag.h b/plugins/power/gsm-inhibitor-flag.h
new file mode 100644
index 0000000..40698f9
--- /dev/null
+++ b/plugins/power/gsm-inhibitor-flag.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 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
+ * Lesser 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/>.
+ */
+
+#ifndef __GSM_INHIBITOR_FLAG_H__
+#define __GSM_INHIBITOR_FLAG_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_INHIBITOR_FLAG_LOGOUT = 1 << 0,
+ GSM_INHIBITOR_FLAG_SWITCH_USER = 1 << 1,
+ GSM_INHIBITOR_FLAG_SUSPEND = 1 << 2,
+ GSM_INHIBITOR_FLAG_IDLE = 1 << 3,
+ GSM_INHIBITOR_FLAG_AUTOMOUNT = 1 << 4
+} GsmInhibitorFlag;
+
+G_END_DECLS
+
+#endif /* __GSM_INHIBITOR_FLAG_H__ */
diff --git a/plugins/power/gsm-manager-logout-mode.h b/plugins/power/gsm-manager-logout-mode.h
new file mode 100644
index 0000000..7b51751
--- /dev/null
+++ b/plugins/power/gsm-manager-logout-mode.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifndef __GSM_MANAGER_LOGOUT_MODE_H
+#define __GSM_MANAGER_LOGOUT_MODE_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_MANAGER_LOGOUT_MODE_NORMAL = 0,
+ GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION,
+ GSM_MANAGER_LOGOUT_MODE_FORCE
+} GsmManagerLogoutMode;
+
+G_END_DECLS
+
+#endif /* __GSM_MANAGER_LOGOUT_MODE_H */
diff --git a/plugins/power/gsm-presence-flag.h b/plugins/power/gsm-presence-flag.h
new file mode 100644
index 0000000..f3bbca4
--- /dev/null
+++ b/plugins/power/gsm-presence-flag.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 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
+ * Lesser 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/>.
+ */
+
+#ifndef __GSM_PRESENCE_FLAG_H__
+#define __GSM_PRESENCE_FLAG_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSM_PRESENCE_STATUS_AVAILABLE = 0,
+ GSM_PRESENCE_STATUS_INVISIBLE,
+ GSM_PRESENCE_STATUS_BUSY,
+ GSM_PRESENCE_STATUS_IDLE,
+} GsmPresenceStatus;
+
+G_END_DECLS
+
+#endif /* __GSM_PRESENCE_FLAG_H__ */
diff --git a/plugins/power/main.c b/plugins/power/main.c
new file mode 100644
index 0000000..4b851ed
--- /dev/null
+++ b/plugins/power/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_power_manager_new
+#define START gsd_power_manager_start
+#define STOP gsd_power_manager_stop
+#define MANAGER GsdPowerManager
+#include "gsd-power-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/power/meson.build b/plugins/power/meson.build
new file mode 100644
index 0000000..014bb6d
--- /dev/null
+++ b/plugins/power/meson.build
@@ -0,0 +1,149 @@
+sources = files(
+ 'gpm-common.c',
+ 'gsd-backlight.c',
+ 'gsd-power-manager.c',
+ 'main.c'
+)
+
+deps = plugins_deps + [
+ libcanberra_gtk_dep,
+ libcommon_dep,
+ libnotify_dep,
+ gio_unix_dep,
+ gnome_desktop_dep,
+ m_dep,
+ upower_glib_dep,
+ x11_dep,
+ dependency('xext')
+]
+
+# required for gsd-power-enums-update, which we have
+# to compile natively
+native_deps = [
+ dependency('glib-2.0', native: true),
+ dependency('gio-2.0', native:true)
+]
+
+if host_is_linux
+ deps += gudev_dep
+endif
+
+cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
+
+gsd_power = executable(
+ 'gsd-' + plugin_name,
+ sources,
+ include_directories: [top_inc, data_inc],
+ dependencies: deps,
+ c_args: cflags,
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+)
+
+sources = files('gsd-power-enums-update.c')
+
+enums_headers = files(
+ 'gsm-inhibitor-flag.h',
+ 'gsm-presence-flag.h'
+)
+
+enums = 'gsd-power-enums'
+
+sources += gnome.mkenums(
+ enums,
+ sources: enums_headers,
+ c_template: enums + '.c.in',
+ h_template: enums + '.h.in'
+)
+
+gsd_power_enums_update = executable(
+ 'gsd-power-enums-update',
+ sources,
+ include_directories: top_inc,
+ dependencies: native_deps,
+ c_args: cflags,
+ native: true
+)
+
+if host_is_linux
+ policy = 'org.gnome.settings-daemon.plugins.power.policy'
+
+ policy_in = configure_file(
+ input: policy + '.in.in',
+ output: policy + '.in',
+ configuration: plugins_conf
+ )
+
+ i18n.merge_file(
+ input: policy_in,
+ output: policy,
+ po_dir: po_dir,
+ install: true,
+ install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions')
+ )
+
+ sources = files(
+ 'gsd-backlight-helper.c',
+ )
+
+ deps = [
+ ]
+
+ executable(
+ 'gsd-backlight-helper',
+ sources,
+ include_directories: top_inc,
+ dependencies: deps,
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+ )
+endif
+
+output = 'gsdpowerconstants.py'
+
+gsdpowerconstants_py = custom_target(
+ output,
+ input: 'gsd-power-constants.h',
+ output: output,
+ build_by_default: true,
+ command: [join_paths(meson.current_source_dir(), 'gsd-power-constants-update.pl'), '@INPUT@', '@OUTPUT@']
+)
+
+output = 'gsdpowerenums.py'
+
+gsdpowerenums_py = custom_target(
+ output,
+ output: output,
+ capture: true,
+ build_by_default: true,
+ command: [gsd_power_enums_update]
+)
+
+test_py = find_program('test.py')
+
+envs = environment()
+#envs.prepend('G_DEBUG', 'fatal-warnings')
+envs.set('BUILDDIR', meson.current_build_dir())
+envs.set('TOP_BUILDDIR', meson.build_root())
+envs.set('LD_PRELOAD', 'libumockdev-preload.so.0')
+envs.set('NO_AT_BRIDGE', '1')
+envs.set('HAVE_SYSFS_BACKLIGHT', host_is_linux ? '1' : '0')
+
+if get_option('b_sanitize').split(',').contains('address')
+ # libasan needs to be loaded first; so we need to explicitly preload it
+ envs.set('POWER_LD_PRELOAD', 'libasan.so.5')
+endif
+
+foreach i : [ 1, 2, 3, 4, 5, 6, 7, 8 ]
+ test(
+ 'test-power @0@/8'.format(i),
+ test_py,
+ args: [ 'PowerPluginTest@0@'.format(i) ],
+ env: envs,
+ # The first set of tests takes very long because of SCREENSAVER_TIMEOUT_BLANK
+ timeout: i == 1 ? 180 : 120
+ )
+endforeach
+
diff --git a/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in b/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in
new file mode 100644
index 0000000..f16300f
--- /dev/null
+++ b/plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+<policyconfig>
+
+ <!--
+ Policy definitions for gnome-settings-daemon system-wide actions.
+ Copyright (c) 2010-2011 Richard Hughes <richard@hughsie.com>
+ -->
+
+ <vendor>GNOME Settings Daemon</vendor>
+ <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url>
+ <icon_name>battery</icon_name>
+
+ <action id="org.gnome.settings-daemon.plugins.power.backlight-helper">
+ <!-- SECURITY:
+ - A normal active user on the local machine does not need permission
+ to change the backlight brightness.
+ -->
+ <description>Modify the laptop brightness</description>
+ <message>Authentication is required to modify the laptop brightness</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-backlight-helper</annotate>
+ </action>
+
+</policyconfig>
+
diff --git a/plugins/power/test-backlight-helper b/plugins/power/test-backlight-helper
new file mode 100755
index 0000000..2dceacd
--- /dev/null
+++ b/plugins/power/test-backlight-helper
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Simulate a slow call and just write the given brightness value to the device
+sleep 0.2
+echo "$2" >"$1/brightness"
diff --git a/plugins/power/test.py b/plugins/power/test.py
new file mode 100755
index 0000000..beb88b4
--- /dev/null
+++ b/plugins/power/test.py
@@ -0,0 +1,1310 @@
+#!/usr/bin/python3 -u
+'''GNOME settings daemon tests for power plugin.'''
+
+__author__ = 'Martin Pitt <martin.pitt@ubuntu.com>'
+__copyright__ = '(C) 2013 Canonical Ltd.'
+__license__ = 'GPL v2 or later'
+
+import unittest
+import subprocess
+import sys
+import time
+import math
+import os
+import os.path
+import signal
+
+project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+builddir = os.environ.get('BUILDDIR', os.path.dirname(__file__))
+
+sys.path.insert(0, os.path.join(project_root, 'tests'))
+sys.path.insert(0, builddir)
+import gsdtestcase
+import gsdpowerconstants
+import gsdpowerenums
+from output_checker import OutputChecker
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+
+DBusGMainLoop(set_as_default=True)
+
+import gi
+gi.require_version('UPowerGlib', '1.0')
+gi.require_version('UMockdev', '1.0')
+
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import UPowerGlib
+from gi.repository import UMockdev
+
+# There must be a better way to do a version comparison ... but this works
+mutter_version = subprocess.run(['mutter', '--version'], stdout=subprocess.PIPE).stdout.decode().strip()
+assert mutter_version.startswith('mutter ')
+mutter_version = mutter_version[7:].split('.')
+
+def mutter_at_least(version):
+ global mutter_version
+ version = version.split('.')
+
+ for i in range(max(len(mutter_version), len(version))):
+ m = mutter_version[i]
+ try:
+ m = int(m)
+ except:
+ pass
+
+ v = version[i]
+ try:
+ v = int(v)
+ except:
+ pass
+
+ try:
+ if m > v:
+ return True
+ elif m < v:
+ return False
+ except TypeError:
+ # String is smaller than integer
+ if isinstance(m, str):
+ return False
+ else:
+ return True
+
+ # assume equal
+ return True
+
+class PowerPluginBase(gsdtestcase.GSDTestCase):
+ '''Test the power plugin'''
+
+ gsd_plugin = 'power'
+ gsd_plugin_case = 'Power'
+
+ COMMON_SUSPEND_METHODS=['Suspend', 'Hibernate', 'SuspendThenHibernate']
+
+ def setUp(self):
+ self.mock_external_monitor_file = os.path.join(self.workdir, 'GSD_MOCK_EXTERNAL_MONITOR')
+ os.environ['GSD_MOCK_EXTERNAL_MONITOR_FILE'] = self.mock_external_monitor_file
+ self.addCleanup(self.delete_external_monitor_file)
+
+ self.check_logind_gnome_session()
+ self.start_logind()
+ self.addCleanup(self.stop_logind)
+
+ # Setup umockdev testbed
+ self.testbed = UMockdev.Testbed.new()
+ self.addCleanup(self.cleanup_testbed)
+ os.environ['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
+
+ # Create a mock backlight device
+ # Note that this function creates a different or even no backlight
+ # device based on the name of the test.
+ self.add_backlight()
+
+ if 'HAVE_SYSFS_BACKLIGHT' in os.environ and os.environ['HAVE_SYSFS_BACKLIGHT'] == '1':
+ self.skip_sysfs_backlight = False
+ else:
+ self.skip_sysfs_backlight = True
+
+ # start mock upowerd
+ (self.upowerd, self.obj_upower) = self.spawn_server_template(
+ 'upower', {'DaemonVersion': '0.99', 'OnBattery': True, 'LidIsClosed': False})
+ self.addCleanup(self.stop_process, self.upowerd)
+
+ # start mock gnome-shell screensaver
+ (self.screensaver, self.obj_screensaver) = self.spawn_server_template(
+ 'gnome_screensaver')
+ self.addCleanup(self.stop_process, self.screensaver)
+
+ # start mock power-profiles-daemon
+ try:
+ (self.ppd, self.obj_ppd) = self.spawn_server_template('power_profiles_daemon')
+ self.addCleanup(self.stop_process, self.ppd)
+ except ModuleNotFoundError:
+ self.ppd = None
+
+ self.start_session()
+ self.addCleanup(self.stop_session)
+
+ self.obj_session_mgr = self.session_bus_con.get_object(
+ 'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+ self.start_mutter()
+ self.addCleanup(self.stop_mutter)
+
+ # Set up the gnome-session presence
+ obj_session_presence = self.session_bus_con.get_object(
+ 'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence')
+ self.obj_session_presence_props = dbus.Interface(obj_session_presence, dbus.PROPERTIES_IFACE)
+
+ # ensure that our tests don't lock the screen when the screensaver
+ # gets active
+ self.settings_screensaver = Gio.Settings(schema_id='org.gnome.desktop.screensaver')
+ self.addCleanup(self.reset_settings, self.settings_screensaver)
+ self.settings_screensaver['lock-enabled'] = False
+
+ # Ensure we set up the external monitor state
+ self.set_has_external_monitor(False)
+
+ self.settings_gsd_power = Gio.Settings(schema_id='org.gnome.settings-daemon.plugins.power')
+ self.addCleanup(self.reset_settings, self.settings_gsd_power)
+
+ Gio.Settings.sync()
+ # avoid painfully long delays of actions for tests
+ env = os.environ.copy()
+ # Disable PulseAudio output from libcanberra
+ env['CANBERRA_DRIVER'] = 'null'
+
+ # Use dummy script as testing backlight helper
+ env['GSD_BACKLIGHT_HELPER'] = os.path.join (project_root, 'plugins', 'power', 'test-backlight-helper')
+ if 'POWER_LD_PRELOAD' in env:
+ if 'LD_PRELOAD' in env and env['LD_PRELOAD']:
+ env['LD_PRELOAD'] = ':'.join((env['POWER_LD_PRELOAD'], env['LD_PRELOAD']))
+ else:
+ env['LD_PRELOAD'] = env['POWER_LD_PRELOAD']
+
+ self.start_plugin(env)
+ self.addCleanup(self.stop_plugin)
+
+ # Store the early-init messages, some tests need them.
+ self.plugin_startup_msgs = self.plugin_log.check_line(b'System inhibitor fd is', timeout=10)
+
+ # always start with zero idle time
+ self.reset_idle_timer()
+
+ self.p_notify_log.clear()
+
+ def cleanup_testbed(self):
+ del self.testbed
+
+ def delete_external_monitor_file(self):
+ try:
+ os.unlink(self.mock_external_monitor_file)
+ except OSError:
+ pass
+
+ def check_logind_gnome_session(self):
+ '''Check that gnome-session is built with logind support'''
+
+ path = GLib.find_program_in_path ('gnome-session')
+ assert(path)
+ (success, data) = GLib.file_get_contents (path)
+ lines = data.split(b'\n')
+ new_path = None
+ for line in lines:
+ items = line.split()
+ if items and items[0] == b'exec':
+ new_path = items[1]
+ if not new_path:
+ self.fail("could not get gnome-session's real path from %s" % path)
+ path = new_path
+ ldd = subprocess.Popen(['ldd', path], stdout=subprocess.PIPE)
+ out = ldd.communicate()[0]
+ if not b'libsystemd.so.0' in out:
+ self.fail('gnome-session is not built with logind support')
+
+ def get_status(self):
+ return self.obj_session_presence_props.Get('org.gnome.SessionManager.Presence', 'status')
+
+ def backlight_defaults(self):
+ # Hack to modify the brightness defaults before starting gsd-power.
+ # The alternative would be to create two separate test files.
+ if 'no_backlight' in self.id():
+ return None, None
+ elif 'legacy_brightness' in self.id():
+ return 15, 15
+ else:
+ return 100, 50
+
+ def add_backlight(self, _type="raw"):
+ max_brightness, brightness = self.backlight_defaults()
+
+ if max_brightness is None:
+ self.backlight = None
+ return
+
+ # Undo mangling done in GSD
+ if max_brightness >= 99:
+ max_brightness += 1
+ brightness += 1
+
+ # This needs to be done before starting gsd-power!
+ self.backlight = self.testbed.add_device('backlight', 'mock_backlight', None,
+ ['type', _type,
+ 'max_brightness', str(max_brightness),
+ 'brightness', str(brightness)],
+ [])
+
+ def get_brightness(self):
+ max_brightness = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'max_brightness')).read())
+
+ # self.backlight contains the leading slash, so os.path.join doesn't quite work
+ res = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'brightness')).read())
+ # Undo mangling done in GSD
+ if max_brightness >= 99:
+ res -= 1
+ return res
+
+ def set_has_external_monitor(self, external):
+ if external:
+ val = b'1'
+ else:
+ val = b'0'
+ GLib.file_set_contents (self.mock_external_monitor_file, val)
+
+ def set_composite_battery_discharging(self, icon='battery-good-symbolic'):
+ self.obj_upower.SetupDisplayDevice(
+ UPowerGlib.DeviceKind.BATTERY,
+ UPowerGlib.DeviceState.DISCHARGING,
+ 50., 50., 100., # 50%, charge 50 of 100
+ 0.01, 600, 0, # Discharge rate 0.01 with 600 seconds remaining, 0 time to full
+ True, # present
+ icon, UPowerGlib.DeviceLevel.NONE
+ )
+
+ def set_composite_battery_critical(self, icon='battery-caution-symbolic'):
+ self.obj_upower.SetupDisplayDevice(
+ UPowerGlib.DeviceKind.BATTERY,
+ UPowerGlib.DeviceState.DISCHARGING,
+ 2., 2., 100., # 2%, charge 2 of 100
+ 0.01, 60, 0, # Discharge rate 0.01 with 60 seconds remaining, 0 time to full
+ True, # present
+ icon, UPowerGlib.DeviceLevel.CRITICAL
+ )
+
+ def check_for_logout(self, timeout):
+ '''Check that logout is requested.
+
+ Fail after the given timeout.
+ '''
+ self.session_log.check_line(b'GsmManager: requesting logout', timeout)
+
+ def check_no_logout(self, seconds):
+ '''Check that no logout is requested in the given time'''
+
+ # wait for specified time to ensure it didn't do anything
+ self.session_log.check_no_line(b'GsmManager: requesting logout', seconds)
+
+ def check_for_suspend(self, timeout, methods=COMMON_SUSPEND_METHODS):
+ '''Check that one of the given suspend methods are requested. Default
+ methods are Suspend() or Hibernate() but also HibernateThenSuspend()
+ is valid.
+
+ Fail after the given timeout.
+ '''
+
+ needle = r'|'.join(' {} '.format(m) for m in methods)
+
+ self.logind_log.check_line_re(needle, timeout,
+ failmsg='timed out waiting for logind suspend call, methods: %s' % ', '.join(methods))
+
+ def check_for_lid_inhibited(self, timeout=0):
+ '''Check that the lid inhibitor has been added.
+
+ Fail after the given timeout.
+ '''
+ self.check_plugin_log('Adding lid switch system inhibitor', timeout,
+ 'Timed out waiting for lid inhibitor')
+
+ def check_for_lid_uninhibited(self, timeout=0):
+ '''Check that the lid inhibitor has been dropped.
+
+ Fail after the given timeout.
+ '''
+ self.check_plugin_log('uninhibiting lid close', timeout,
+ 'Timed out waiting for lid uninhibition')
+
+ def check_no_lid_uninhibited(self, timeout=0):
+ '''Check that the lid inhibitor has been dropped.
+
+ Fail after the given timeout.
+ '''
+ self.plugin_log.check_no_line(b'uninhibiting lid close', wait=timeout)
+
+ def check_no_suspend(self, seconds, methods=COMMON_SUSPEND_METHODS):
+ '''Check that no Suspend or Hibernate is requested in the given time'''
+
+ needle = r'|'.join(' {} '.format(m) for m in methods)
+
+ self.logind_log.check_no_line_re(needle, wait=seconds)
+
+ def check_suspend_no_hibernate(self, seconds):
+ '''Check that Suspend was requested and not Hibernate, in the given time'''
+
+ lines = self.logind_log.check_no_line(b' Hibernate', wait=seconds)
+ # Check that we did suspend
+ for l in lines:
+ if b' Suspend' in l:
+ break
+ else:
+ self.fail('Missing Suspend request')
+
+ def check_plugin_log(self, needle, timeout=0, failmsg=None):
+ '''Check that needle is found in the log within the given timeout.
+ Returns immediately when found.
+
+ Fail after the given timeout.
+ '''
+ self.plugin_log.check_line(needle, timeout=timeout, failmsg=failmsg)
+
+ def check_no_dim(self, seconds):
+ '''Check that mode is not set to dim in the given time'''
+
+ # wait for specified time to ensure it didn't do anything
+ self.plugin_log.check_no_line(b'Doing a state transition: dim', wait=seconds)
+
+ def check_dim(self, timeout):
+ '''Check that mode is set to dim in the given time'''
+
+ self.check_plugin_log('Doing a state transition: dim', timeout,
+ 'timed out waiting for dim')
+
+ def check_undim(self, timeout):
+ '''Check that mode is set to normal in the given time'''
+
+ self.check_plugin_log('Doing a state transition: normal', timeout,
+ 'timed out waiting for normal mode')
+
+ def check_blank(self, timeout):
+ '''Check that blank is requested.
+
+ Fail after the given timeout.
+ '''
+
+ self.check_plugin_log('TESTSUITE: Blanked screen', timeout,
+ 'timed out waiting for blank')
+
+ def check_unblank(self, timeout):
+ '''Check that unblank is requested.
+
+ Fail after the given timeout.
+ '''
+
+ self.check_plugin_log('TESTSUITE: Unblanked screen', timeout,
+ 'timed out waiting for unblank')
+
+ def check_no_blank(self, seconds):
+ '''Check that no blank is requested in the given time'''
+
+ self.plugin_log.check_no_line(b'TESTSUITE: Blanked screen', wait=seconds)
+
+ def check_no_unblank(self, seconds):
+ '''Check that no unblank is requested in the given time'''
+
+ self.plugin_log.check_no_line(b'TESTSUITE: Unblanked screen', wait=seconds)
+
+class PowerPluginTest1(PowerPluginBase):
+ def test_screensaver(self):
+ # Note that the screensaver mock object
+ # doesn't know how to get out of being active,
+ # be it if the lock is disabled, or not.
+
+ self.obj_screensaver.Lock()
+ # 0.3 second animation
+ time.sleep(1)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # blank is supposed to happen straight away
+ self.check_blank(2)
+
+ # Wait a bit for the active watch to be registered through dbus, then
+ # fake user activity and check that the screen is unblanked.
+ time.sleep(0.5)
+ self.reset_idle_timer()
+ self.check_unblank(2)
+
+ # Check for no blank before the normal blank timeout
+ self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # and check for blank after the blank timeout
+ self.check_blank(10)
+
+ # Wait a bit for the active watch to be registered through dbus, then
+ # fake user activity and check that the screen is unblanked.
+ time.sleep(0.5)
+ self.reset_idle_timer()
+ self.check_unblank(2)
+
+ # check no blank and then blank
+ self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+ self.check_blank(10)
+
+ def test_sleep_inactive_blank(self):
+ '''screensaver/blank interaction'''
+
+ # create suspend inhibitor which should have no effect on the idle
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+ dbus_interface='org.gnome.SessionManager')
+
+ self.obj_screensaver.SetActive(True)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # blank is supposed to happen straight away
+ self.check_blank(2)
+
+ # Wait a bit for the active watch to be registered through dbus, then
+ # fake user activity and check that the screen is unblanked.
+ time.sleep(0.5)
+ self.reset_idle_timer()
+ self.check_unblank(2)
+ if not self.skip_sysfs_backlight:
+ self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
+
+ # Check for no blank before the normal blank timeout
+ self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # and check for blank after the blank timeout
+ self.check_blank(10)
+
+ # Drop inhibitor
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest2(PowerPluginBase):
+ def test_screensaver_no_unblank(self):
+ '''Ensure the screensaver is not unblanked for new inhibitors.'''
+
+ # Lower idle delay a lot
+ self.settings_session['idle-delay'] = 1
+ Gio.Settings.sync()
+
+ # Bring down the screensaver
+ self.obj_screensaver.SetActive(True)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # Check that we blank
+ self.check_blank(2)
+
+ # Create the different possible inhibitors
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND | gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
+ dbus_interface='org.gnome.SessionManager')
+
+ self.check_no_unblank(2)
+
+ # Drop inhibitor
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+ self.check_no_unblank(2)
+
+ def test_session_idle_delay(self):
+ '''verify that session idle delay works as expected when changed'''
+
+ # Verify that idle is set after 5 seconds
+ self.settings_session['idle-delay'] = 5
+ Gio.Settings.sync()
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+ time.sleep(7)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+ # Raise the idle delay, and see that we stop being idle
+ # and get idle again after the timeout
+ self.settings_session['idle-delay'] = 10
+ Gio.Settings.sync()
+ # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
+ time.sleep(0.2)
+ self.reset_idle_timer()
+ time.sleep(5)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+ time.sleep(10)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+ # Lower the delay again, and see that we get idle as we should
+ self.settings_session['idle-delay'] = 5
+ Gio.Settings.sync()
+ # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
+ time.sleep(0.2)
+ self.reset_idle_timer()
+ time.sleep(2)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+ time.sleep(5)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+ def test_idle_time_reset_on_resume(self):
+ '''Check that the IDLETIME is reset when resuming'''
+
+ self.settings_screensaver['lock-enabled'] = False
+
+ # Go idle
+ self.settings_session['idle-delay'] = 5
+ Gio.Settings.sync()
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+ time.sleep(7)
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
+
+ # Go to sleep
+ self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # Wake up
+ self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # And check we're not idle
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+class PowerPluginTest3(PowerPluginBase):
+ def test_sleep_inactive_battery(self):
+ '''sleep-inactive-battery-timeout'''
+
+ self.settings_session['idle-delay'] = 2
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+ Gio.Settings.sync()
+
+ # wait for idle delay; should not yet suspend
+ self.check_no_suspend(2)
+
+ # suspend should happen after inactive sleep timeout + 1 s notification
+ # delay + 1 s error margin
+ self.check_for_suspend(7)
+
+ def _test_suspend_no_hibernate(self):
+ '''suspend-no-hibernate'''
+
+ self.settings_session['idle-delay'] = 2
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+ # Hibernate isn't possible, so it should end up suspending
+ # FIXME
+ self.settings_gsd_power['critical-battery-action'] = 'hibernate'
+ Gio.Settings.sync()
+
+ # wait for idle delay; should not yet hibernate
+ self.check_no_suspend(2)
+
+ # suspend should happen after inactive sleep timeout + 1 s notification
+ # delay + 1 s error margin
+ self.check_suspend_no_hibernate(7)
+
+ def test_sleep_inhibition(self):
+ '''Does not sleep under idle inhibition'''
+
+ idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+ self.settings_session['idle-delay'] = idle_delay
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+ Gio.Settings.sync()
+
+ # create inhibitor
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+ dbus_interface='org.gnome.SessionManager')
+ self.check_no_suspend(idle_delay + 2)
+ self.check_no_dim(0)
+
+ # Check that we didn't go to idle either
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest4(PowerPluginBase):
+ def test_lock_on_lid_close(self):
+ '''Check that we do lock on lid closing, if the machine will not suspend'''
+
+ self.settings_screensaver['lock-enabled'] = True
+ Gio.Settings.sync()
+
+ # create inhibitor
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+ dbus_interface='org.gnome.SessionManager')
+
+ # Wait for startup inhibition to be gone
+ self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+ # Close the lid
+ self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check that we've blanked
+ time.sleep(2)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+ self.check_blank(2)
+
+ # Drop the inhibit and see whether we suspend
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+ # At this point logind should suspend for us
+ self.settings_screensaver['lock-enabled'] = False
+ Gio.Settings.sync()
+
+ def test_blank_on_lid_close(self):
+ '''Check that we do blank on lid closing, if the machine will not suspend'''
+
+ # create inhibitor
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+ dbus_interface='org.gnome.SessionManager')
+
+ # Wait for startup inhibition to be gone
+ self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+ # Close the lid
+ self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check that we've blanked
+ self.check_blank(4)
+
+ # Drop the inhibit and see whether we suspend
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+ # At this point logind should suspend for us
+
+ @unittest.skipIf(not mutter_at_least('42.0'), reason="mutter is too old and may be buggy")
+ def test_unblank_on_lid_open(self):
+ '''Check that we do unblank on lid opening, if the machine will not suspend'''
+
+ # create inhibitor
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
+ dbus_interface='org.gnome.SessionManager')
+
+ # Wait for startup inhibition to be gone
+ self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+ # Close the lid
+ self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check that we've blanked
+ self.check_blank(2)
+
+ # Reopen the lid
+ self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', False)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check for unblanking
+ self.check_unblank(2)
+
+ # Drop the inhibit
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest5(PowerPluginBase):
+ def test_dim(self):
+ '''Check that we do go to dim'''
+
+ # Wait for startup inhibition to be gone
+ self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+ idle_delay = math.ceil(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+ self.reset_idle_timer()
+
+ self.settings_session['idle-delay'] = idle_delay
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+ Gio.Settings.sync()
+ # This is an absolute percentage, and our brightness is 0..100
+ dim_level = self.settings_gsd_power['idle-brightness'];
+
+ # Check that we're not idle
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+ # Wait and check we're not idle, but dimmed
+ self.check_dim(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY + 1)
+ # Give time for the brightness to change
+ time.sleep(2)
+ if not self.skip_sysfs_backlight:
+ level = self.get_brightness();
+ self.assertTrue(level == dim_level, 'incorrect dim brightness (%d != %d)' % (level, dim_level))
+
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+ # Bring down the screensaver
+ self.obj_screensaver.SetActive(True)
+ self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
+
+ # Check that we blank
+ self.check_blank(2)
+
+ # Go to sleep
+ self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # Wake up
+ self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # And check that we have the pre-dim brightness
+ if not self.skip_sysfs_backlight:
+ self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
+
+ def test_lid_close_inhibition(self):
+ '''Check that we correctly inhibit suspend with an external monitor'''
+
+ # Wait for startup inhibition to be gone
+ self.check_for_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 2)
+
+ # Add an external monitor
+ self.set_has_external_monitor(True)
+ self.check_for_lid_inhibited(1)
+
+ # Check that we do not uninhibit with the external monitor attached
+ self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
+
+ # Close the lid
+ self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(0.5)
+
+ # Unplug the external monitor
+ self.set_has_external_monitor(False)
+
+ # Check that no action happens during the safety time minus 1 second
+ self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT - 1)
+ # Check that we're uninhibited after the safety time
+ self.check_for_lid_uninhibited(4)
+
+class PowerPluginTest6(PowerPluginBase):
+ def test_notify_critical_battery(self):
+ '''action on critical battery'''
+
+ self.set_composite_battery_discharging()
+
+ time.sleep(2)
+
+ self.set_composite_battery_critical()
+
+ # Check that it was picked up
+ self.check_plugin_log('EMIT: charge-critical', 2)
+
+ self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*[Bb]attery critical.*"', timeout=0.5)
+
+ def test_notify_critical_battery_on_start(self):
+ '''action on critical battery on startup'''
+
+ self.set_composite_battery_critical()
+
+ # Check that it was picked up
+ self.check_plugin_log('EMIT: charge-critical', 2)
+
+ self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*[Bb]attery critical.*"', timeout=0.5)
+
+ def test_notify_device_battery(self):
+ '''critical power level notification for device batteries'''
+
+ # Set internal battery to discharging
+ self.set_composite_battery_discharging()
+
+ # Add a device battery
+ bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+ self.obj_upower.AddObject(bat2_path,
+ 'org.freedesktop.UPower.Device',
+ {
+ 'PowerSupply': dbus.Boolean(False, variant_level=1),
+ 'IsPresent': dbus.Boolean(True, variant_level=1),
+ 'Model': dbus.String('Bat1', variant_level=1),
+ 'Percentage': dbus.Double(40.0, variant_level=1),
+ 'TimeToEmpty': dbus.Int64(1600, variant_level=1),
+ 'EnergyFull': dbus.Double(100.0, variant_level=1),
+ 'Energy': dbus.Double(40.0, variant_level=1),
+ 'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+ 'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+ 'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
+ }, dbus.Array([], signature='(ssss)'))
+
+ obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+ self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # now change the mouse battery to critical charge
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
+ dbus.Int64(30, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
+ dbus.Double(0.5, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
+ dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+ self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+
+ self.check_plugin_log('EMIT: charge-critical', 2)
+
+ self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+ def test_notify_device_spam(self):
+ '''no repeat notifications for device batteries'''
+
+ # Set internal battery to discharging
+ self.set_composite_battery_discharging()
+
+ # Add a device battery
+ bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+ self.obj_upower.AddObject(bat2_path,
+ 'org.freedesktop.UPower.Device',
+ {
+ 'PowerSupply': dbus.Boolean(False, variant_level=1),
+ 'IsPresent': dbus.Boolean(True, variant_level=1),
+ 'Model': dbus.String('Bat1', variant_level=1),
+ 'Serial': dbus.String('12345678', variant_level=1),
+ 'Percentage': dbus.Double(10.0, variant_level=1),
+ 'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+ 'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+ 'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+ }, dbus.Array([], signature='(ssss)'))
+
+ obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+ self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ self.check_plugin_log('EMIT: charge-low', 2)
+
+ self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+ # Disconnect mouse
+ self.obj_upower.RemoveObject(bat2_path)
+ time.sleep(0.5)
+
+ # Reconnect mouse
+ self.obj_upower.AddObject(bat2_path,
+ 'org.freedesktop.UPower.Device',
+ {
+ 'PowerSupply': dbus.Boolean(False, variant_level=1),
+ 'IsPresent': dbus.Boolean(True, variant_level=1),
+ 'Model': dbus.String('Bat1', variant_level=1),
+ 'Serial': dbus.String('12345678', variant_level=1),
+ 'Percentage': dbus.Double(10.0, variant_level=1),
+ 'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+ 'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+ 'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+ }, dbus.Array([], signature='(ssss)'))
+
+ obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+ self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+
+ self.p_notify_log.check_no_line(b'', wait=1)
+
+ # Disconnect mouse
+ self.obj_upower.RemoveObject(bat2_path)
+ time.sleep(0.5)
+
+ # Reconnect mouse with critical battery level
+ self.obj_upower.AddObject(bat2_path,
+ 'org.freedesktop.UPower.Device',
+ {
+ 'PowerSupply': dbus.Boolean(False, variant_level=1),
+ 'IsPresent': dbus.Boolean(True, variant_level=1),
+ 'Model': dbus.String('Bat1', variant_level=1),
+ 'Serial': dbus.String('12345678', variant_level=1),
+ 'Percentage': dbus.Double(5.0, variant_level=1),
+ 'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+ 'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+ 'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+ }, dbus.Array([], signature='(ssss)'))
+
+ obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+ self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # Verify new warning
+ self.check_plugin_log('EMIT: charge-critical', 2)
+
+ self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*very low.* power.*\([0-9.]+%\).*"', timeout=0.5)
+
+ def test_notify_device_battery_coarse_level(self):
+ '''critical power level notification for device batteries with coarse level'''
+
+ # Set internal battery to discharging
+ self.set_composite_battery_discharging()
+
+ # Add a device battery
+ bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
+ self.obj_upower.AddObject(bat2_path,
+ 'org.freedesktop.UPower.Device',
+ {
+ 'PowerSupply': dbus.Boolean(False, variant_level=1),
+ 'IsPresent': dbus.Boolean(True, variant_level=1),
+ 'Model': dbus.String('Bat1', variant_level=1),
+ 'Percentage': dbus.Double(40.0, variant_level=1),
+ 'BatteryLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
+ 'TimeToEmpty': dbus.Int64(1600, variant_level=1),
+ 'EnergyFull': dbus.Double(100.0, variant_level=1),
+ 'Energy': dbus.Double(40.0, variant_level=1),
+ 'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
+ 'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
+ 'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
+ }, dbus.Array([], signature='(ssss)'))
+
+ obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
+ self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+ time.sleep(1)
+
+ # now change the mouse battery to critical charge
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
+ dbus.Int64(30, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
+ dbus.Double(0.5, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
+ dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
+ dbus_interface=dbus.PROPERTIES_IFACE)
+ obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+ self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
+ dbus_interface='org.freedesktop.DBus.Mock')
+
+ self.check_plugin_log('EMIT: charge-critical', 2)
+
+ time.sleep(0.5)
+ lines = self.p_notify_log.check_line_re(b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*"')
+ lines += self.p_notify_log.clear()
+ for l in lines:
+ self.assertNotRegex(l, b'[0-9.]+ Notify "Power" .* ".*" ".*\([0-9.]+%\).*"')
+
+ def test_forced_logout(self):
+ '''Test forced logout'''
+
+ self.plugin_death_expected = True
+ idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+ self.settings_session['idle-delay'] = idle_delay
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
+ Gio.Settings.sync()
+
+ self.check_for_logout(idle_delay + 2)
+
+ self.p_notify_log.check_line(b'You will soon log out because of inactivity.')
+
+ def test_forced_logout_inhibition(self):
+ '''Test we don't force logout when inhibited'''
+
+ idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+ self.settings_session['idle-delay'] = idle_delay
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
+ Gio.Settings.sync()
+
+ # create suspend inhibitor which should stop us logging out
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
+ dbus_interface='org.gnome.SessionManager')
+
+ self.check_no_logout(idle_delay + 3)
+
+ # Drop inhibitor
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+class PowerPluginTest7(PowerPluginBase):
+ def test_check_missing_kbd_brightness(self):
+ ''' https://bugzilla.gnome.org/show_bug.cgi?id=793512 '''
+
+ obj_gsd_power_kbd = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_kbd_props = dbus.Interface(obj_gsd_power_kbd, dbus.PROPERTIES_IFACE)
+
+ # Will return -1 if gsd-power crashed, and an exception if the code caught the problem
+ with self.assertRaises(dbus.DBusException) as exc:
+ kbd_brightness = obj_gsd_power_kbd_props.Get('org.gnome.SettingsDaemon.Power.Keyboard', 'Brightness')
+
+ # We should not have arrived here, if we did then the test failed, let's print this to help debugging
+ print('Got keyboard brightness: {}'.format(kbd_brightness))
+
+ self.assertEqual(exc.exception.get_dbus_message(), 'Failed to get property Brightness on interface org.gnome.SettingsDaemon.Power.Keyboard')
+
+ def test_inhibitor_idletime(self):
+ ''' https://bugzilla.gnome.org/show_bug.cgi?id=705942 '''
+
+ idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+
+ self.settings_session['idle-delay'] = idle_delay
+ self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
+ self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
+ Gio.Settings.sync()
+
+ # create inhibitor
+ inhibit_id = self.obj_session_mgr.Inhibit(
+ 'testsuite', dbus.UInt32(0), 'for testing',
+ dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE),
+ dbus_interface='org.gnome.SessionManager')
+ self.check_no_suspend(idle_delay + 2)
+ self.check_no_dim(0)
+
+ # Check that we didn't go to idle either
+ self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
+
+ self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
+ dbus_interface='org.gnome.SessionManager')
+
+ self.check_no_suspend(2)
+ self.check_no_dim(0)
+
+ time.sleep(5)
+
+ self.check_suspend_no_hibernate(7)
+
+ def disabled_test_unindle_on_ac_plug(self):
+ idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
+ self.settings_session['idle-delay'] = idle_delay
+ Gio.Settings.sync()
+
+ # Wait for idle
+ self.check_dim(idle_delay + 2)
+
+ # Plug in the AC
+ self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', False)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check that we undim
+ self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
+
+ # And wait a little more to see us dim again
+ self.check_dim(idle_delay + 2)
+
+ # Unplug the AC
+ self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', True)
+ self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
+
+ # Check that we undim
+ self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
+
+ # And wait a little more to see us dim again
+ self.check_dim(idle_delay + 2)
+
+class PowerPluginTest8(PowerPluginBase):
+ def test_brightness_stepping(self):
+ '''Check that stepping the backlight works as expected'''
+
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ obj_gsd_power = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_screen_iface = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
+
+ # Each of the step calls will only return when the value was written
+ start = time.time()
+ # We start at 50% and step by 5% each time
+ obj_gsd_power_screen_iface.StepUp()
+ self.assertEqual(self.get_brightness(), 55)
+ obj_gsd_power_screen_iface.StepUp()
+ self.assertEqual(self.get_brightness(), 60)
+ obj_gsd_power_screen_iface.StepUp()
+ self.assertEqual(self.get_brightness(), 65)
+ obj_gsd_power_screen_iface.StepUp()
+ self.assertEqual(self.get_brightness(), 70)
+ stop = time.time()
+ # This needs to take more than 0.8 seconds as each write is delayed by
+ # 0.2 seconds by the test backlight helper
+ self.assertGreater(stop - start, 0.8)
+
+ # Now, the same thing should work fine if we step multiple times,
+ # even if we are so quick that compression will happen.
+ # Use a list to keep rack of replies (as integer is immutable and would
+ # not be modified in the outer scope)
+ replies = [0]
+
+ def handle_reply(*args):
+ replies[0] += 1
+
+ def last_reply(*args):
+ replies[0] += 1
+ loop.quit()
+
+ def error_handler(*args):
+ loop.quit()
+
+ start = time.time()
+ obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+ obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+ obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
+ obj_gsd_power_screen_iface.StepDown(reply_handler=last_reply, error_handler=error_handler)
+ loop = GLib.MainLoop()
+ loop.run()
+ stop = time.time()
+
+ # The calls need to be returned in order. As we got the last reply, all
+ # others must have been received too.
+ self.assertEqual(replies[0], 4)
+ # Four steps down, so back at 50%
+ self.assertEqual(self.get_brightness(), 50)
+ # And compression must have happened, so it should take less than 0.8s
+ self.assertLess(stop - start, 0.8)
+
+ def test_brightness_compression(self):
+ '''Check that compression also happens when setting the property'''
+
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ # Now test that the compression works correctly.
+ # NOTE: Relies on the implementation detail, that the property setter
+ # returns immediately rather than waiting for the brightness to
+ # be updated.
+ # Should this ever be fixed, then this will need to be changed to use
+ # async dbus calls similar to the stepping code
+
+ obj_gsd_power = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+ # Quickly ramp the brightness up
+ for brightness in range(70, 91):
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', brightness)
+
+ # The brightness of 80 should be in effect after slightly more than
+ # 0.4 seconds. If compression does not work as expected, this would take
+ # more than 5 seconds for the 20 steps.
+ time.sleep(2.0)
+ self.assertEqual(self.get_brightness(), 90)
+
+ def test_brightness_uevent(self):
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ obj_gsd_power = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+ brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+ self.assertEqual(50, brightness)
+
+ # Check that the brightness is updated if it was changed through some
+ # other mechanism (e.g. firmware).
+ # Set to 80+1 because of the GSD offset (see add_backlight).
+ self.testbed.set_attribute(self.backlight, 'brightness', '81')
+ self.testbed.uevent(self.backlight, 'change')
+
+ self.check_plugin_log('GsdBacklight: Got uevent', 1, 'gsd-power did not process uevent')
+ time.sleep(0.2)
+
+ brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+ self.assertEqual(80, brightness)
+
+ def test_brightness_step(self):
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ for l in self.plugin_startup_msgs:
+ if b'Step size for backlight is 5.' in l:
+ break
+ else:
+ self.fail('Step size is not 5')
+
+ def test_legacy_brightness_step(self):
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ for l in self.plugin_startup_msgs:
+ if b'Step size for backlight is 1.' in l:
+ break
+ else:
+ self.fail('Step size is not 1')
+
+ def test_legacy_brightness_rounding(self):
+ if self.skip_sysfs_backlight:
+ self.skipTest("sysfs backlight support required for test")
+
+ obj_gsd_power = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 0)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 0)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 10)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 2)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 20)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 3)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 25)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 4)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 49)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 7)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 50)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 8)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 56)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 8)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 57)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 9)
+ obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 98)
+ time.sleep(0.6)
+ self.assertEqual(self.get_brightness(), 15)
+
+ def test_no_backlight(self):
+ '''Check that backlight brightness DBus api without a backlight'''
+
+ obj_gsd_power = self.session_bus_con.get_object(
+ 'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
+ obj_gsd_power_props = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
+ obj_gsd_power_screen = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
+
+ # We expect -1 to be returned
+ brightness = obj_gsd_power_props.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
+ self.assertEqual(brightness, -1)
+
+ # Trying to set the brightness
+ with self.assertRaises(dbus.DBusException) as exc:
+ obj_gsd_power_props.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 1)
+
+ self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+ with self.assertRaises(dbus.DBusException) as exc:
+ obj_gsd_power_screen.StepUp()
+
+ self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+ with self.assertRaises(dbus.DBusException) as exc:
+ obj_gsd_power_screen.StepDown()
+
+ self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
+
+ def test_power_saver_on_low_battery(self):
+ '''Check that the power-saver profile gets held when low on battery'''
+
+ if not self.ppd:
+ self.skipTest("power-profiles-daemon dbusmock support is not available")
+
+ obj_props = dbus.Interface(self.obj_ppd, dbus.PROPERTIES_IFACE)
+
+ self.set_composite_battery_discharging()
+ time.sleep(0.5)
+ holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+ self.assertEqual(len(holds), 0)
+
+ self.set_composite_battery_critical()
+ time.sleep(0.5)
+ holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+ self.assertEqual(len(holds), 1)
+ self.assertEqual(holds[0]['Profile'], 'power-saver')
+ self.assertEqual(holds[0]['ApplicationId'], 'org.gnome.SettingsDaemon.Power')
+
+ self.set_composite_battery_discharging()
+ time.sleep(0.5)
+ holds = obj_props.Get('net.hadess.PowerProfiles', 'ActiveProfileHolds')
+ self.assertEqual(len(holds), 0)
+
+# avoid writing to stderr
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))