[gnome-settings-daemon/wip/benzea/backlight: 1/7] power: Refactor backlight handling to be asynchronous



commit 2591327402c0f6f2a24b318b1e6288c96b4f142d
Author: Benjamin Berg <bberg redhat com>
Date:   Wed Jan 10 20:09:30 2018 +0100

    power: Refactor backlight handling to be asynchronous
    
    Writing the backlight value can take quite long, in particular when we
    also get the overhead of starting up the helper process for writing.
    
    Add code to handle the writing asynchronously and compress multiple set
    actions into one write. The notification of the change only happens once
    all of the queued up set operations have finished.
    
    A trivial bugfix included is that the OSD display is again shown only on
    the affected (internal) screen.
    
    Note that a possible further improvement would be to just run the
    backlight helper once and communicate with it through stdin or some
    other methods, avoiding the overhead of starting it up all the time.
    There are a few minor disadvantages with this approach though, see
    comment #16 and following for more information.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=758413

 meson.build                       |   1 +
 plugins/power/gpm-common.c        | 498 +---------------------
 plugins/power/gpm-common.h        |  16 +-
 plugins/power/gsd-backlight.c     | 839 ++++++++++++++++++++++++++++++++++++++
 plugins/power/gsd-backlight.h     |  73 ++++
 plugins/power/gsd-power-manager.c | 231 ++++++-----
 plugins/power/gsd-power-manager.h |   3 +-
 plugins/power/meson.build         |   2 +-
 8 files changed, 1041 insertions(+), 622 deletions(-)
---
diff --git a/meson.build b/meson.build
index e882d42c..a1aeb1a2 100644
--- a/meson.build
+++ b/meson.build
@@ -82,6 +82,7 @@ endif
 
 add_project_arguments(common_flags + compiler_flags, language: 'c')
 
+glib_dep = dependency('glib-2.0', version: '>= 2.56')
 colord_dep = dependency('colord', version: '>= 1.0.2')
 geocode_glib_dep = dependency('geocode-glib-1.0', version: '>= 3.10.0')
 gio_dep = dependency('gio-2.0', version: '>= 2.53.0')
diff --git a/plugins/power/gpm-common.c b/plugins/power/gpm-common.c
index e83f1549..60f3795f 100644
--- a/plugins/power/gpm-common.c
+++ b/plugins/power/gpm-common.c
@@ -38,18 +38,11 @@
 #include "gpm-common.h"
 #include "gsd-power-constants.h"
 #include "gsd-power-manager.h"
-#include "gsd-backlight-linux.h"
 
 #define XSCREENSAVER_WATCHDOG_TIMEOUT           120 /* seconds */
 #define UPS_SOUND_LOOP_ID                        99
 #define GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT  5 /* seconds */
 
-enum BacklightHelperCommand {
-        BACKLIGHT_HELPER_GET,
-        BACKLIGHT_HELPER_GET_MAX,
-        BACKLIGHT_HELPER_SET
-};
-
 /* take a discrete value with offset and convert to percentage */
 int
 gsd_power_backlight_abs_to_percentage (int min, int max, int value)
@@ -321,29 +314,6 @@ gsd_power_enable_screensaver_watchdog (void)
         return id;
 }
 
-static GnomeRROutput *
-get_primary_output (GnomeRRScreen *rr_screen)
-{
-        GnomeRROutput *output = NULL;
-        GnomeRROutput **outputs;
-        guint i;
-
-        /* search all X11 outputs for the device id */
-        outputs = gnome_rr_screen_list_outputs (rr_screen);
-        if (outputs == NULL)
-                goto out;
-
-        for (i = 0; outputs[i] != NULL; i++) {
-                if (gnome_rr_output_is_builtin_display (outputs[i]) &&
-                    gnome_rr_output_get_backlight (outputs[i]) >= 0) {
-                        output = outputs[i];
-                        break;
-                }
-        }
-out:
-        return output;
-}
-
 static gpointer
 parse_mocked (gpointer data)
 {
@@ -354,7 +324,7 @@ parse_mocked (gpointer data)
        return GINT_TO_POINTER (TRUE);
 }
 
-static gboolean
+gboolean
 is_mocked (void)
 {
          static GOnce mocked_once = G_ONCE_INIT;
@@ -362,472 +332,6 @@ is_mocked (void)
          return GPOINTER_TO_INT (mocked_once.retval);
 }
 
-static void
-backlight_set_mock_value (gint value)
-{
-       const char *filename;
-       char *contents;
-       GError *error = NULL;
-
-       g_debug ("Setting mock brightness: %d", value);
-
-       filename = "GSD_MOCK_brightness";
-       contents = g_strdup_printf ("%d", value);
-       if (!g_file_set_contents (filename, contents, -1, &error))
-               g_warning ("Setting mock brightness failed: %s", error->message);
-       g_clear_error (&error);
-       g_free (contents);
-}
-
-static gint64
-backlight_get_mock_value (enum BacklightHelperCommand command)
-{
-       char *contents;
-       gint64 ret;
-
-        if (command == BACKLIGHT_HELPER_GET_MAX) {
-               g_debug ("Returning max mock brightness: %d", GSD_MOCK_MAX_BRIGHTNESS);
-               return GSD_MOCK_MAX_BRIGHTNESS;
-       }
-
-        g_assert (command == BACKLIGHT_HELPER_GET);
-
-       if (g_file_get_contents ("GSD_MOCK_brightness", &contents, NULL, NULL)) {
-               ret = g_ascii_strtoll (contents, NULL, 0);
-               g_free (contents);
-               g_debug ("Returning mock brightness: %"G_GINT64_FORMAT, ret);
-       } else {
-               ret = GSD_MOCK_DEFAULT_BRIGHTNESS;
-               backlight_set_mock_value (GSD_MOCK_DEFAULT_BRIGHTNESS);
-               g_debug ("Returning default mock brightness: %"G_GINT64_FORMAT, ret);
-       }
-
-       return ret;
-}
-
-gboolean
-backlight_available (GnomeRRScreen *rr_screen)
-{
-        char *path;
-
-       if (is_mocked ())
-               return TRUE;
-
-#ifndef __linux__
-        return (get_primary_output (rr_screen) != NULL);
-#endif
-
-        path = gsd_backlight_helper_get_best_backlight (NULL);
-        if (path == NULL)
-                return FALSE;
-
-        g_free (path);
-        return TRUE;
-}
-
-static gchar **
-get_backlight_helper_environ (void)
-{
-        static gchar **environ = NULL;
-
-        if (environ)
-                return environ;
-
-        environ = g_environ_unsetenv (g_get_environ (), "SHELL");
-        return environ;
-}
-
-static gboolean
-run_backlight_helper (enum BacklightHelperCommand   command,
-                      gchar                        *value,
-                      gchar                       **stdout_data,
-                      gint                         *exit_status,
-                      GError                      **error)
-{
-        static gchar *helper_args[] = {
-                "--get-brightness",
-                "--get-max-brightness",
-                "--set-brightness",
-        };
-        gchar *argv[5] = { 0 };
-
-        argv[0] = "pkexec";
-        argv[1] = LIBEXECDIR "/gsd-backlight-helper";
-        argv[2] = helper_args[command];
-        argv[3] = value;
-
-        return g_spawn_sync (NULL,
-                             command == BACKLIGHT_HELPER_SET ? argv : &argv[1],
-                             get_backlight_helper_environ (),
-                             G_SPAWN_SEARCH_PATH,
-                             NULL,
-                             NULL,
-                             stdout_data,
-                             NULL,
-                             exit_status,
-                             error);
-}
-
-/**
- * backlight_helper_get_value:
- *
- * Gets a brightness value from the PolicyKit helper.
- *
- * Return value: the signed integer value from the helper, or -1
- * for failure. If -1 then @error is set.
- **/
-static gint64
-backlight_helper_get_value (enum BacklightHelperCommand command, GError **error)
-{
-        gboolean ret;
-        gchar *stdout_data = NULL;
-        gint exit_status = 0;
-        gint64 value = -1;
-        gchar *endptr = NULL;
-
-       if (is_mocked ())
-               return backlight_get_mock_value (command);
-
-#ifndef __linux__
-        /* non-Linux platforms won't have /sys/class/backlight */
-        g_set_error_literal (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "The sysfs backlight helper is only for Linux");
-        goto out;
-#endif
-
-        /* get the data */
-        ret = run_backlight_helper (command, NULL,
-                                    &stdout_data, &exit_status, error);
-        if (!ret)
-                goto out;
-
-        if (WEXITSTATUS (exit_status) != 0) {
-                 g_set_error (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "gsd-backlight-helper failed: %s",
-                             stdout_data ? stdout_data : "No reason");
-                goto out;
-        }
-
-        /* parse */
-        value = g_ascii_strtoll (stdout_data, &endptr, 10);
-
-        /* parsing error */
-        if (endptr == stdout_data) {
-                value = -1;
-                g_set_error (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "failed to parse value: %s",
-                             stdout_data);
-                goto out;
-        }
-
-        /* out of range */
-        if (value > G_MAXINT) {
-                value = -1;
-                g_set_error (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "value out of range: %s",
-                             stdout_data);
-                goto out;
-        }
-
-        /* Fetching the value failed, for some other reason */
-        if (value < 0) {
-                g_set_error (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "value negative, but helper did not fail: %s",
-                             stdout_data);
-                goto out;
-        }
-
-out:
-        g_free (stdout_data);
-        return value;
-}
-
-/**
- * backlight_helper_set_value:
- *
- * Sets a brightness value using the PolicyKit helper.
- *
- * Return value: Success. If FALSE then @error is set.
- **/
-static gboolean
-backlight_helper_set_value (gint value,
-                            GError **error)
-{
-        gboolean ret = FALSE;
-        gint exit_status = 0;
-        gchar *vstr = NULL;
-
-       if (is_mocked ()) {
-               backlight_set_mock_value (value);
-               return TRUE;
-       }
-
-#ifndef __linux__
-        /* non-Linux platforms won't have /sys/class/backlight */
-        g_set_error_literal (error,
-                             GSD_POWER_MANAGER_ERROR,
-                             GSD_POWER_MANAGER_ERROR_FAILED,
-                             "The sysfs backlight helper is only for Linux");
-        return FALSE;
-#endif
-
-        /* get the data */
-        vstr = g_strdup_printf ("%i", value);
-        ret = run_backlight_helper (BACKLIGHT_HELPER_SET, vstr,
-                                    NULL, &exit_status, error);
-        g_free (vstr);
-        return ret;
-}
-
-int
-backlight_get_output_id (GnomeRRScreen *rr_screen)
-{
-        GnomeRROutput *output;
-        GnomeRRCrtc *crtc;
-        GdkScreen *gdk_screen;
-        gint x, y;
-
-        output = get_primary_output (rr_screen);
-        if (output == NULL)
-                return -1;
-
-        crtc = gnome_rr_output_get_crtc (output);
-        if (crtc == NULL)
-                return -1;
-
-        gdk_screen = gdk_screen_get_default ();
-        gnome_rr_crtc_get_position (crtc, &x, &y);
-
-        return gdk_screen_get_monitor_at_point (gdk_screen, x, y);
-}
-
-int
-backlight_get_abs (GnomeRRScreen *rr_screen, GError **error)
-{
-#ifndef __linux__
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL) {
-                return gnome_rr_output_get_backlight (output);
-        }
-        return -1;
-#else
-        return backlight_helper_get_value (BACKLIGHT_HELPER_GET, error);
-#endif
-}
-
-int
-backlight_get_percentage (GnomeRRScreen *rr_screen, GError **error)
-{
-        gint now;
-        gint value = -1;
-        gint max;
-#ifndef __linux__
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL) {
-                now = gnome_rr_output_get_backlight (output);
-                if (now < 0)
-                        return value;
-                value = ABS_TO_PERCENTAGE (0, 100, now);
-        }
-        return value;
-#else
-        max = backlight_helper_get_value (BACKLIGHT_HELPER_GET_MAX, error);
-        if (max < 0)
-                return value;
-        now = backlight_helper_get_value (BACKLIGHT_HELPER_GET, error);
-        if (now < 0)
-                return value;
-        value = ABS_TO_PERCENTAGE (0, max, now);
-        return value;
-#endif
-}
-
-int
-backlight_get_min (GnomeRRScreen *rr_screen)
-{
-        return 0;
-}
-
-int
-backlight_get_max (GnomeRRScreen *rr_screen, GError **error)
-{
-#ifndef __linux__
-        return 100;
-#else
-        return  backlight_helper_get_value (BACKLIGHT_HELPER_GET_MAX, error);
-#endif
-}
-
-gboolean
-backlight_set_percentage (GnomeRRScreen *rr_screen,
-                          gint *value,
-                          GError **error)
-{
-        gboolean ret = FALSE;
-        gint max;
-        guint discrete;
-#ifndef __linux__
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL) {
-                if (!gnome_rr_output_set_backlight (output, *value, error))
-                        return ret;
-                *value = gnome_rr_output_get_backlight (output);
-                ret = TRUE;
-        }
-        return ret;
-#else
-        max = backlight_helper_get_value (BACKLIGHT_HELPER_GET_MAX, error);
-        if (max < 0)
-                return ret;
-        discrete = PERCENTAGE_TO_ABS (0, max, *value);
-        ret = backlight_helper_set_value (discrete, error);
-        if (ret)
-                *value = ABS_TO_PERCENTAGE (0, max, discrete);
-
-        return ret;
-#endif
-}
-
-int
-backlight_step_up (GnomeRRScreen *rr_screen, GError **error)
-{
-        gboolean ret = FALSE;
-        gint percentage_value = -1;
-        gint max;
-        gint now;
-        gint step;
-        guint discrete;
-#ifndef __linux__
-        GnomeRRCrtc *crtc;
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL) {
-
-                crtc = gnome_rr_output_get_crtc (output);
-                if (crtc == NULL) {
-                        g_set_error (error,
-                                     GSD_POWER_MANAGER_ERROR,
-                                     GSD_POWER_MANAGER_ERROR_FAILED,
-                                     "no crtc for %s",
-                                     gnome_rr_output_get_name (output));
-                        return percentage_value;
-                }
-                max = 100;
-                now = gnome_rr_output_get_backlight (output);
-                if (now < 0)
-                       return percentage_value;
-                step = MAX(gnome_rr_output_get_min_backlight_step (output), BRIGHTNESS_STEP_AMOUNT(max + 1));
-                discrete = MIN (now + step, max);
-                ret = gnome_rr_output_set_backlight (output,
-                                                     discrete,
-                                                     error);
-                if (ret)
-                        percentage_value = ABS_TO_PERCENTAGE (0, max, discrete);
-        }
-        return percentage_value;
-#else
-        now = backlight_helper_get_value (BACKLIGHT_HELPER_GET, error);
-        if (now < 0)
-                return percentage_value;
-        max = backlight_helper_get_value (BACKLIGHT_HELPER_GET_MAX, error);
-        if (max < 0)
-                return percentage_value;
-        step = BRIGHTNESS_STEP_AMOUNT (max + 1);
-        discrete = MIN (now + step, max);
-        ret = backlight_helper_set_value (discrete, error);
-        if (ret)
-                percentage_value = ABS_TO_PERCENTAGE (0, max, discrete);
-
-        return percentage_value;
-#endif
-}
-
-int
-backlight_step_down (GnomeRRScreen *rr_screen, GError **error)
-{
-        gboolean ret = FALSE;
-        gint percentage_value = -1;
-        gint max;
-        gint now;
-        gint step;
-        guint discrete;
-#ifndef __linux__
-        GnomeRRCrtc *crtc;
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL) {
-
-                crtc = gnome_rr_output_get_crtc (output);
-                if (crtc == NULL) {
-                        g_set_error (error,
-                                     GSD_POWER_MANAGER_ERROR,
-                                     GSD_POWER_MANAGER_ERROR_FAILED,
-                                     "no crtc for %s",
-                                     gnome_rr_output_get_name (output));
-                        return percentage_value;
-                }
-                max = 100;
-                now = gnome_rr_output_get_backlight (output);
-                if (now < 0)
-                       return percentage_value;
-                step = MAX (gnome_rr_output_get_min_backlight_step (output), BRIGHTNESS_STEP_AMOUNT (max + 
1));
-                discrete = MAX (now - step, 0);
-                ret = gnome_rr_output_set_backlight (output,
-                                                     discrete,
-                                                     error);
-                if (ret)
-                        percentage_value = ABS_TO_PERCENTAGE (0, max, discrete);
-        }
-        return percentage_value;
-#else
-        now = backlight_helper_get_value (BACKLIGHT_HELPER_GET, error);
-        if (now < 0)
-                return percentage_value;
-        max = backlight_helper_get_value (BACKLIGHT_HELPER_GET_MAX, error);
-        if (max < 0)
-                return percentage_value;
-        step = BRIGHTNESS_STEP_AMOUNT (max + 1);
-        discrete = MAX (now - step, 0);
-        ret = backlight_helper_set_value (discrete, error);
-        if (ret)
-                percentage_value = ABS_TO_PERCENTAGE (0, max, discrete);
-
-        return percentage_value;
-#endif
-}
-
-int
-backlight_set_abs (GnomeRRScreen *rr_screen,
-                   guint value,
-                   GError **error)
-{
-        gboolean ret = FALSE;
-#ifndef __linux__
-        GnomeRROutput *output;
-        output = get_primary_output (rr_screen);
-        if (output != NULL)
-                return gnome_rr_output_set_backlight (output, value, error);
-        return ret;
-#else
-        ret = backlight_helper_set_value (value, error);
-
-        return ret;
-#endif
-}
-
 static gboolean
 randr_output_is_on (GnomeRROutput *output)
 {
diff --git a/plugins/power/gpm-common.h b/plugins/power/gpm-common.h
index 251cd319..40c55b2e 100644
--- a/plugins/power/gpm-common.h
+++ b/plugins/power/gpm-common.h
@@ -36,6 +36,8 @@ gboolean         gsd_power_is_hardware_a_tablet         (void);
 guint            gsd_power_enable_screensaver_watchdog  (void);
 void             reset_idletime                         (void);
 
+gboolean        is_mocked (void);
+
 /* Backlight helpers */
 
 /* on ACPI machines we have 4-16 levels, on others it's ~150 */
@@ -46,20 +48,6 @@ void             reset_idletime                         (void);
 
 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);
-gboolean         backlight_available                    (GnomeRRScreen *rr_screen);
-int              backlight_get_output_id                (GnomeRRScreen *rr_screen);
-int              backlight_get_abs                      (GnomeRRScreen *rr_screen, GError **error);
-int              backlight_get_percentage               (GnomeRRScreen *rr_screen, GError **error);
-int              backlight_get_min                      (GnomeRRScreen *rr_screen);
-int              backlight_get_max                      (GnomeRRScreen *rr_screen, GError **error);
-gboolean         backlight_set_percentage               (GnomeRRScreen *rr_screen,
-                                                         gint *value,
-                                                         GError **error);
-int              backlight_step_up                      (GnomeRRScreen *rr_screen, GError **error);
-int              backlight_step_down                    (GnomeRRScreen *rr_screen, GError **error);
-int              backlight_set_abs                      (GnomeRRScreen *rr_screen,
-                                                         guint value,
-                                                         GError **error);
 
 /* RandR helpers */
 void             watch_external_monitor                 (GnomeRRScreen *screen);
diff --git a/plugins/power/gsd-backlight.c b/plugins/power/gsd-backlight.c
new file mode 100644
index 00000000..4851a054
--- /dev/null
+++ b/plugins/power/gsd-backlight.c
@@ -0,0 +1,839 @@
+/* -*- 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"
+
+#define MOCK_BRIGHTNESS_FILE "GSD_MOCK_brightness"
+
+#ifdef HAVE_GUDEV
+#include <gudev/gudev.h>
+#endif /* HAVE_GUDEV */
+
+struct _GsdBacklight
+{
+        GObject object;
+
+        gint brightness_min;
+        gint brightness_max;
+        gint brightness_val;
+        gint brightness_target;
+        gint brightness_step;
+
+#ifdef HAVE_GUDEV
+        GUdevClient *udev;
+        GUdevDevice *udev_device;
+
+        GTask *active_task;
+        GQueue tasks;
+
+        gint idle_update;
+#endif
+
+        GnomeRRScreen *rr_screen;
+};
+
+enum {
+        PROP_RR_SCREEN = 1,
+        PROP_BRIGHTNESS,
+        PROP_LAST,
+};
+
+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 HAVE_GUDEV
+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;
+
+        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_min = 1;
+        backlight->brightness_max = g_udev_device_get_sysfs_attr_as_int (backlight->udev_device,
+                                                                         "max_brightness");
+
+        /* 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);
+        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);
+
+        proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
+                                 &error,
+                                 "pkexec", 
+                                 LIBEXECDIR "/gsd-backlight-helper",
+                                 "--set-brightness",
+                                 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 /* HAVE_GUDEV */
+
+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.
+ *
+ * Returns: The last stable backlight value.
+ **/
+gint
+gsd_backlight_get_brightness (GsdBacklight *backlight, gint *target)
+{
+        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;
+
+        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);
+
+        if (is_mocked ()) {
+                g_autofree gchar *contents = NULL;
+                g_debug ("Setting mock brightness: %d", value);
+
+                contents = g_strdup_printf ("%d", value);
+                if (!g_file_set_contents (MOCK_BRIGHTNESS_FILE, contents, -1, &error)) {
+                        g_warning ("Setting mock brightness failed: %s", error->message);
+                        g_task_return_error (task, error);
+                }
+                priv->brightness_val = priv->brightness_target;
+                g_object_notify_by_pspec (G_OBJECT (backlight), props[PROP_BRIGHTNESS]);
+                g_task_return_int (task, gsd_backlight_get_brightness (backlight, NULL));
+
+                return;
+        }
+
+#ifdef HAVE_GUDEV
+        if (backlight->udev_device != NULL) {
+                BacklightHelperData *task_data;
+
+                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 /* HAVE_GUDEV */
+
+        /* 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() and
+ * gsd_backlight_step_down_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() and
+ * gsd_backlight_step_down_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_get_output_id
+ * @backlight: a #GsdBacklight
+ *
+ * Return the display ID for the display that is being controlled by the
+ * #GsdBacklight object. This display ID can be passed to gnome-shell to show
+ * the on screen display only on the affected screen.
+ *
+ * Returns: The ID for the controlled output or -1 if unknown.
+ **/
+gint
+gsd_backlight_get_output_id (GsdBacklight *backlight)
+{
+        GnomeRROutput *output;
+
+        output = gsd_backlight_rr_find_output (backlight, FALSE);
+        if (output == NULL)
+                return -1;
+
+        /* XXX: Is this really that simple? The old code did a lot more, but
+         * did not return anything sensible these days.
+         * The outputs need to be in the same order as the MetaScreen object
+         * returns to the shell. */
+        return gnome_rr_output_get_id (output);
+}
+
+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);
+                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;
+
+        if (cancellable != NULL) {
+                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                     "GsdBacklight does not support cancelling initialization.");
+                return FALSE;
+        }
+
+        /* If mocked, set as available and set the brightness (which will also
+         * create the file for the test environment). */
+        if (is_mocked ()) {
+                g_debug ("Using mock for backlight.");
+                priv->available = TRUE;
+                priv->brightness_min = 0;
+                priv->brightness_max = 100;
+
+                gsd_backlight_set_brightness_async (backlight, GSD_MOCK_DEFAULT_BRIGHTNESS, NULL, NULL, 
NULL);
+
+                goto done;
+        }
+
+#ifdef HAVE_GUDEV
+        /* Try finding a udev device. */
+        if (gsd_backlight_udev_init (backlight))
+                goto found;
+#endif /* HAVE_GUDEV */
+
+        /* 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 = 0;
+                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));
+
+        return TRUE;
+}
+
+static void
+gsd_backlight_finalize (GObject *object)
+{
+        GsdBacklight *backlight = GSD_BACKLIGHT (object);
+
+#ifdef HAVE_GUDEV
+        g_assert (backlight->active_task == NULL);
+        g_assert (g_queue_is_empty (&backlight->tasks));
+        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 /* HAVE_GUDEV */
+
+        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 HAVE_GUDEV
+        backlight->active_task = NULL;
+        g_queue_init (&backlight->tasks);
+#endif /* HAVE_GUDEV */
+}
+
+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 00000000..2d4f3339
--- /dev/null
+++ b/plugins/power/gsd-backlight.h
@@ -0,0 +1,73 @@
+/* -*- 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);
+
+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_get_output_id         (GsdBacklight         *backlight);
+GsdBacklight* gsd_backlight_new          (GnomeRRScreen        *screen,
+                                          GError              **error);
+
+
+G_END_DECLS
+
+#endif /* _GSD_BACKLIGHT_H */
diff --git a/plugins/power/gsd-power-manager.c b/plugins/power/gsd-power-manager.c
index 67d475cf..da268641 100644
--- a/plugins/power/gsd-power-manager.c
+++ b/plugins/power/gsd-power-manager.c
@@ -43,6 +43,7 @@
 #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"
@@ -157,7 +158,7 @@ struct GsdPowerManagerPrivate
         gboolean                 battery_is_low; /* laptop battery low, or UPS discharging */
 
         /* Brightness */
-        gboolean                 backlight_available;
+        GsdBacklight            *backlight;
         gint                     pre_dim_brightness; /* level, not percentage */
 
         /* Keyboard */
@@ -1031,7 +1032,7 @@ iio_proxy_claim_light (GsdPowerManager *manager, gboolean active)
         GError *error = NULL;
         if (manager->priv->iio_proxy == NULL)
                 return;
-        if (!manager->priv->backlight_available)
+        if (!manager->priv->backlight)
                 return;
        if (active && !manager->priv->session_is_active)
                return;
@@ -1439,52 +1440,30 @@ backlight_iface_emit_changed (GsdPowerManager *manager,
                                        NULL);
 }
 
-static gboolean
-display_backlight_dim (GsdPowerManager *manager,
-                       gint idle_percentage,
-                       GError **error)
+static void
+backlight_notify_brightness_cb (GsdPowerManager *manager, GParamSpec *pspec, GsdBacklight *backlight)
 {
-        gint min;
-        gint max;
-        gint now;
-        gint idle;
-        gboolean ret = FALSE;
-
-        if (!manager->priv->backlight_available)
-                return TRUE;
+        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
+                                      gsd_backlight_get_brightness (backlight, NULL), NULL);
+}
 
-        now = backlight_get_abs (manager->priv->rr_screen, error);
-        if (now < 0) {
-                goto out;
-        }
+static void
+display_backlight_dim (GsdPowerManager *manager,
+                       gint idle_percentage)
+{
+        gint brightness;
 
-        /* is the dim brightness actually *dimmer* than the
-         * brightness we have now? */
-        min = backlight_get_min (manager->priv->rr_screen);
-        max = backlight_get_max (manager->priv->rr_screen, error);
-        if (max < 0) {
-                goto out;
-        }
-        idle = PERCENTAGE_TO_ABS (min, max, idle_percentage);
-        if (idle > now) {
-                g_debug ("brightness already now %i/%i, so "
-                         "ignoring dim to %i/%i",
-                         now, max, idle, max);
-                ret = TRUE;
-                goto out;
-        }
-        ret = backlight_set_abs (manager->priv->rr_screen,
-                                 idle,
-                                 error);
-        if (!ret) {
-                goto out;
-        }
+        if (!manager->priv->backlight)
+                return;
 
-        /* save for undim */
-        manager->priv->pre_dim_brightness = now;
+        /* 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->priv->backlight, &brightness);
+        if (brightness < idle_percentage)
+                return;
 
-out:
-        return ret;
+        manager->priv->pre_dim_brightness = brightness;
+        gsd_backlight_set_brightness_async (manager->priv->backlight, idle_percentage, NULL, NULL, NULL);
 }
 
 static gboolean
@@ -1611,13 +1590,7 @@ idle_set_mode (GsdPowerManager *manager, GsdPowerIdleMode mode)
                 /* display backlight */
                 idle_percentage = g_settings_get_int (manager->priv->settings,
                                                       "idle-brightness");
-                ret = display_backlight_dim (manager, idle_percentage, &error);
-                if (!ret) {
-                        g_warning ("failed to set dim backlight to %i%%: %s",
-                                   idle_percentage,
-                                   error->message);
-                        g_clear_error (&error);
-                }
+                display_backlight_dim (manager, idle_percentage);
 
                 /* keyboard backlight */
                 ret = kbd_backlight_dim (manager, idle_percentage, &error);
@@ -1661,18 +1634,12 @@ idle_set_mode (GsdPowerManager *manager, GsdPowerIdleMode mode)
                 backlight_enable (manager);
 
                 /* reset brightness if we dimmed */
-                if (manager->priv->pre_dim_brightness >= 0) {
-                        ret = backlight_set_abs (manager->priv->rr_screen,
-                                                 manager->priv->pre_dim_brightness,
-                                                 &error);
-                        if (!ret) {
-                                g_warning ("failed to restore backlight to %i: %s",
-                                           manager->priv->pre_dim_brightness,
-                                           error->message);
-                                g_clear_error (&error);
-                        } else {
-                                manager->priv->pre_dim_brightness = -1;
-                        }
+                if (manager->priv->backlight && manager->priv->pre_dim_brightness >= 0) {
+                        gsd_backlight_set_brightness_async (manager->priv->backlight,
+                                                            manager->priv->pre_dim_brightness,
+                                                            NULL, NULL, NULL);
+                        /* XXX: Ideally we would do this from the async callback. */
+                        manager->priv->pre_dim_brightness = -1;
                 }
 
                 /* only toggle keyboard if present and already toggled off */
@@ -2489,8 +2456,14 @@ on_rr_screen_acquired (GObject      *object,
                 on_randr_event (manager->priv->rr_screen, manager);
         }
 
-        /* check whether a backlight is available */
-        manager->priv->backlight_available = backlight_available (manager->priv->rr_screen);
+        /* Resolve screen backlight */
+        manager->priv->backlight = gsd_backlight_new (manager->priv->rr_screen, NULL);
+
+        if (manager->priv->backlight)
+                g_signal_connect_object (manager->priv->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->priv->logind_proxy, "g-signal",
@@ -2566,8 +2539,8 @@ on_rr_screen_acquired (GObject      *object,
         /* 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->priv->backlight_available) {
-                manager->priv->ambient_percentage_old = backlight_get_percentage (manager->priv->rr_screen, 
NULL);
+        if (manager->priv->backlight) {
+                manager->priv->ambient_percentage_old = gsd_backlight_get_brightness 
(manager->priv->backlight, NULL);
                 backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN,
                                               manager->priv->ambient_percentage_old, NULL);
         } else {
@@ -2580,14 +2553,13 @@ on_rr_screen_acquired (GObject      *object,
 static void
 iio_proxy_changed (GsdPowerManager *manager)
 {
-        GError *error = NULL;
         GVariant *val_has = NULL;
         GVariant *val_als = NULL;
         gdouble brightness;
         gint pc;
 
         /* no display hardware */
-        if (!manager->priv->backlight_available)
+        if (!manager->priv->backlight)
                 return;
 
         /* disabled */
@@ -2627,10 +2599,11 @@ iio_proxy_changed (GsdPowerManager *manager)
         g_debug ("Setting brightness from ambient %.1f%%",
                  manager->priv->ambient_accumulator);
         pc = manager->priv->ambient_accumulator;
-        if (!backlight_set_percentage (manager->priv->rr_screen, &pc, &error)) {
-                g_warning ("failed to set brightness: %s", error->message);
-                g_error_free (error);
-        }
+
+        if (manager->priv->backlight)
+                gsd_backlight_set_brightness_async (manager->priv->backlight, pc, NULL, NULL, NULL);
+
+        /* Assume setting worked. */
         manager->priv->ambient_percentage_old = pc;
 out:
         g_clear_pointer (&val_has, g_variant_unref);
@@ -2855,49 +2828,83 @@ handle_method_call_keyboard (GsdPowerManager *manager,
         }
 }
 
+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;
+        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->priv->ambient_percentage_old = brightness;
+        manager->priv->ambient_norm_required = TRUE;
+
+        if (error) {
+                g_dbus_method_invocation_take_error (invocation,
+                                                     error);
+        } else {
+                g_dbus_method_invocation_return_value (invocation,
+                                                       g_variant_new ("(ii)",
+                                                                      brightness,
+                                                                      gsd_backlight_get_output_id 
(backlight)));
+        }
+
+        g_object_unref (manager);
+}
+
+/* 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->priv->ambient_percentage_old = brightness;
+                manager->priv->ambient_norm_required = TRUE;
+        }
+
+        g_object_unref (manager);
+}
+
 static void
 handle_method_call_screen (GsdPowerManager *manager,
                            const gchar *method_name,
                            GVariant *parameters,
                            GDBusMethodInvocation *invocation)
 {
-        gint value = -1;
-        GError *error = NULL;
+        g_object_set_data (G_OBJECT (invocation), "gsd-power-manager", g_object_ref (manager));
 
-        if (!manager->priv->backlight_available) {
-               g_set_error_literal (&error,
-                                    GSD_POWER_MANAGER_ERROR,
-                                    GSD_POWER_MANAGER_ERROR_FAILED,
-                                    "Screen backlight not available");
-                goto out;
+        if (!manager->priv->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;
         }
 
         if (g_strcmp0 (method_name, "StepUp") == 0) {
                 g_debug ("screen step up");
-                value = backlight_step_up (manager->priv->rr_screen, &error);
-                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN, value, NULL);
+                gsd_backlight_step_up_async (manager->priv->backlight, NULL, backlight_brightness_step_cb, 
invocation);
+
         } else if (g_strcmp0 (method_name, "StepDown") == 0) {
                 g_debug ("screen step down");
-                value = backlight_step_down (manager->priv->rr_screen, &error);
-                backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN, value, NULL);
-        } else {
-                g_assert_not_reached ();
-        }
-
-        /* ambient brightness no longer valid */
-        manager->priv->ambient_percentage_old = value;
-        manager->priv->ambient_norm_required = TRUE;
+                gsd_backlight_step_down_async (manager->priv->backlight, NULL, backlight_brightness_step_cb, 
invocation);
 
-out:
-        /* return value */
-        if (value < 0) {
-                g_dbus_method_invocation_take_error (invocation,
-                                                     error);
         } else {
-                g_dbus_method_invocation_return_value (invocation,
-                                                       g_variant_new ("(ii)",
-                                                                      value,
-                                                                      backlight_get_output_id 
(manager->priv->rr_screen)));
+                g_assert_not_reached ();
         }
 }
 
@@ -2953,7 +2960,11 @@ handle_get_property_other (GsdPowerManager *manager,
         }
 
         if (g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_SCREEN) == 0) {
-                value = backlight_get_percentage (manager->priv->rr_screen, NULL);
+                if (manager->priv->backlight)
+                        value = gsd_backlight_get_brightness (manager->priv->backlight, NULL);
+                else
+                        value = -1;
+
                 retval = g_variant_new_int32 (value);
         } else if (manager->priv->upower_kbd_proxy &&
                    g_strcmp0 (interface_name, GSD_POWER_DBUS_INTERFACE_KEYBOARD) == 0) {
@@ -3015,19 +3026,21 @@ handle_set_property_other (GsdPowerManager *manager,
         }
 
         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 (backlight_set_percentage (manager->priv->rr_screen, &brightness_value, error)) {
-                        backlight_iface_emit_changed (manager, GSD_POWER_DBUS_INTERFACE_SCREEN, 
brightness_value, NULL);
-
-                        /* ambient brightness no longer valid */
-                        manager->priv->ambient_percentage_old = brightness_value;
-                        manager->priv->ambient_norm_required = TRUE;
+                if (manager->priv->backlight) {
+                        gsd_backlight_set_brightness_async (manager->priv->backlight, brightness_value,
+                                                            NULL,
+                                                            backlight_brightness_set_cb, g_object_ref 
(manager));
                         return TRUE;
                 } else {
-                        g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
-                                     "Setting %s.%s failed", interface_name, property_name);
+                        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->priv->kbd_brightness_max,
diff --git a/plugins/power/gsd-power-manager.h b/plugins/power/gsd-power-manager.h
index 1343c211..7f6a64bd 100644
--- a/plugins/power/gsd-power-manager.h
+++ b/plugins/power/gsd-power-manager.h
@@ -47,7 +47,8 @@ typedef struct
 
 enum
 {
-        GSD_POWER_MANAGER_ERROR_FAILED
+        GSD_POWER_MANAGER_ERROR_FAILED,
+        GSD_POWER_MANAGER_ERROR_NO_BACKLIGHT,
 };
 
 GType                   gsd_power_manager_get_type            (void);
diff --git a/plugins/power/meson.build b/plugins/power/meson.build
index 1db2a83a..94fcda08 100644
--- a/plugins/power/meson.build
+++ b/plugins/power/meson.build
@@ -1,6 +1,6 @@
 sources = files(
   'gpm-common.c',
-  'gsd-backlight-linux.c',
+  'gsd-backlight.c',
   'gsd-power-manager.c',
   'main.c'
 )


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]