summaryrefslogtreecommitdiffstats
path: root/plugins/power/gsd-backlight.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
commitb0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7 (patch)
tree9f1d8a08a8cbd19d28ec2d31027f8a7ccd90de0d /plugins/power/gsd-backlight.c
parentInitial commit. (diff)
downloadgnome-settings-daemon-b0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7.tar.xz
gnome-settings-daemon-b0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7.zip
Adding upstream version 43.0.upstream/43.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/power/gsd-backlight.c')
-rw-r--r--plugins/power/gsd-backlight.c978
1 files changed, 978 insertions, 0 deletions
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));
+}
+