diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/power/gpm-common.c | 456 | ||||
-rw-r--r-- | plugins/power/gpm-common.h | 59 | ||||
-rw-r--r-- | plugins/power/gsd-backlight-helper.c | 149 | ||||
-rw-r--r-- | plugins/power/gsd-backlight.c | 978 | ||||
-rw-r--r-- | plugins/power/gsd-backlight.h | 81 | ||||
-rwxr-xr-x | plugins/power/gsd-power-constants-update.pl | 49 | ||||
-rw-r--r-- | plugins/power/gsd-power-constants.h | 42 | ||||
-rw-r--r-- | plugins/power/gsd-power-enums-update.c | 44 | ||||
-rw-r--r-- | plugins/power/gsd-power-enums.c.in | 40 | ||||
-rw-r--r-- | plugins/power/gsd-power-enums.h.in | 27 | ||||
-rw-r--r-- | plugins/power/gsd-power-manager.c | 3561 | ||||
-rw-r--r-- | plugins/power/gsd-power-manager.h | 47 | ||||
-rw-r--r-- | plugins/power/gsdpowerconstants.py | 16 | ||||
-rw-r--r-- | plugins/power/gsm-inhibitor-flag.h | 36 | ||||
-rw-r--r-- | plugins/power/gsm-manager-logout-mode.h | 34 | ||||
-rw-r--r-- | plugins/power/gsm-presence-flag.h | 33 | ||||
-rw-r--r-- | plugins/power/main.c | 7 | ||||
-rw-r--r-- | plugins/power/meson.build | 149 | ||||
-rw-r--r-- | plugins/power/org.gnome.settings-daemon.plugins.power.policy.in.in | 32 | ||||
-rwxr-xr-x | plugins/power/test-backlight-helper | 5 | ||||
-rwxr-xr-x | plugins/power/test.py | 1310 |
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 ()), + ¤t_server_timeout, + ¤t_server_interval, + ¤t_prefer_blank, + ¤t_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)) |