[gnome-desktop/ebassi/update-for-gtk4: 19/23] Port gnome-bg and gnome-rr to GTK4




commit 358b17071140c29518e1507ad8d80f95599f2e6e
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Mon Nov 8 16:36:05 2021 +0000

    Port gnome-bg and gnome-rr to GTK4
    
    In order to port the GnomeBG and GnomeRR APIs to GTK4 we need to copy
    the files into their own subdirectories, as we want to keep the older
    GTK3-based implementations available for the legacy
    libgnome-desktop-3.0. It also makes it easier for us to spin off these
    libraries into their own projects, if we decide to do so.

 libgnome-desktop/gnome-bg/gnome-bg-slide-show.c  |  838 ++++++++
 libgnome-desktop/gnome-bg/gnome-bg-slide-show.h  |   96 +
 libgnome-desktop/gnome-bg/gnome-bg.c             | 2401 +++++++++++++++++++++
 libgnome-desktop/gnome-bg/gnome-bg.h             |   89 +
 libgnome-desktop/gnome-bg/meson.build            |   69 +
 libgnome-desktop/gnome-rr/gnome-rr-config.c      | 1152 ++++++++++
 libgnome-desktop/gnome-rr/gnome-rr-config.h      |   52 +
 libgnome-desktop/gnome-rr/gnome-rr-output-info.c |  595 ++++++
 libgnome-desktop/gnome-rr/gnome-rr-output-info.h |   76 +
 libgnome-desktop/gnome-rr/gnome-rr-private.h     |  155 ++
 libgnome-desktop/gnome-rr/gnome-rr-screen.c      | 2464 ++++++++++++++++++++++
 libgnome-desktop/gnome-rr/gnome-rr-screen.h      |  173 ++
 libgnome-desktop/gnome-rr/gnome-rr-types.h       |   49 +
 libgnome-desktop/gnome-rr/gnome-rr.h             |   15 +
 libgnome-desktop/gnome-rr/meson.build            |   75 +
 libgnome-desktop/meson.build                     |  144 +-
 meson.build                                      |    6 +-
 17 files changed, 8305 insertions(+), 144 deletions(-)
---
diff --git a/libgnome-desktop/gnome-bg/gnome-bg-slide-show.c b/libgnome-desktop/gnome-bg/gnome-bg-slide-show.c
new file mode 100644
index 00000000..97efde30
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/gnome-bg-slide-show.c
@@ -0,0 +1,838 @@
+/* gnome-bg-slide-show.h
+ *
+ * Copyright (C) 2008, 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-bg-slide-show.h"
+
+struct _GnomeBGSlideShowPrivate
+{
+        GFile *file;
+
+        double start_time;
+        double total_duration;
+
+        GQueue *slides;
+
+        gboolean has_multiple_sizes;
+
+        /* used during parsing */
+        struct tm start_tm;
+        GQueue *stack;
+};
+
+typedef struct _Slide Slide;
+
+struct _Slide
+{
+       double   duration;              /* in seconds */
+       gboolean fixed;
+
+       GSList  *file1;
+       GSList  *file2;         /* NULL if fixed is TRUE */
+};
+
+typedef struct _FileSize FileSize;
+struct _FileSize
+{
+       gint width;
+       gint height;
+
+       char *file;
+};
+
+enum {
+        PROP_0,
+        PROP_FILE,
+        PROP_START_TIME,
+        PROP_TOTAL_DURATION,
+        PROP_HAS_MULTIPLE_SIZES,
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GnomeBGSlideShow, gnome_bg_slide_show, G_TYPE_OBJECT)
+
+static void
+gnome_bg_slide_show_set_property (GObject       *object,
+                                  guint          property_id,
+                                  const GValue  *value,
+                                  GParamSpec    *pspec)
+{
+        GnomeBGSlideShow *self;
+
+        g_assert (GNOME_BG_IS_SLIDE_SHOW (object));
+
+        self = GNOME_BG_SLIDE_SHOW (object);
+
+        switch (property_id)
+        {
+        case PROP_FILE:
+                self->priv->file = g_object_ref (g_value_get_object (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+                break;
+        }
+}
+
+static void
+gnome_bg_slide_show_get_property (GObject     *object,
+                                  guint        property_id,
+                                  GValue      *value,
+                                  GParamSpec  *pspec)
+{
+        GnomeBGSlideShow *self;
+
+        g_assert (GNOME_BG_IS_SLIDE_SHOW (object));
+
+        self = GNOME_BG_SLIDE_SHOW (object);
+
+        switch (property_id)
+        {
+        case PROP_FILE:
+                g_value_set_object (value, self->priv->file);
+                break;
+        case PROP_START_TIME:
+                g_value_set_int (value, self->priv->start_time);
+                break;
+        case PROP_TOTAL_DURATION:
+                g_value_set_int (value, self->priv->total_duration);
+                break;
+        case PROP_HAS_MULTIPLE_SIZES:
+                g_value_set_boolean (value, self->priv->has_multiple_sizes);
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+                break;
+        }
+}
+
+static void
+gnome_bg_slide_show_finalize (GObject *object)
+{
+        GnomeBGSlideShow *self;
+
+        GList *list;
+        GSList *slist;
+        FileSize *size;
+
+        self = GNOME_BG_SLIDE_SHOW (object);
+
+        for (list = self->priv->slides->head; list != NULL; list = list->next) {
+                Slide *slide = list->data;
+
+                for (slist = slide->file1; slist != NULL; slist = slist->next) {
+                        size = slist->data;
+                        g_free (size->file);
+                        g_free (size);
+                }
+                g_slist_free (slide->file1);
+
+                for (slist = slide->file2; slist != NULL; slist = slist->next) {
+                        size = slist->data;
+                        g_free (size->file);
+                        g_free (size);
+                }
+                g_slist_free (slide->file2);
+
+                g_free (slide);
+        }
+
+        g_queue_free (self->priv->slides);
+
+        g_queue_free_full (self->priv->stack, g_free);
+
+        g_object_unref (self->priv->file);
+}
+
+static void
+gnome_bg_slide_show_class_init (GnomeBGSlideShowClass *self_class)
+{
+        GObjectClass *gobject_class;
+
+        gobject_class = G_OBJECT_CLASS (self_class);
+
+        gobject_class->get_property = gnome_bg_slide_show_get_property;
+        gobject_class->set_property = gnome_bg_slide_show_set_property;
+        gobject_class->finalize = gnome_bg_slide_show_finalize;
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_FILE,
+                                         g_param_spec_object ("file",
+                                                              "File",
+                                                              "File",
+                                                              G_TYPE_FILE,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_START_TIME,
+                                         g_param_spec_double ("start-time",
+                                                              "Start time",
+                                                              "start time",
+                                                              0.0, G_MAXDOUBLE, 0.0,
+                                                              G_PARAM_READABLE));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_TOTAL_DURATION,
+                                         g_param_spec_double ("total-duration",
+                                                              "Start duration",
+                                                              "total duration",
+                                                              0.0, G_MAXDOUBLE, 0.0,
+                                                              G_PARAM_READABLE));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_HAS_MULTIPLE_SIZES,
+                                         g_param_spec_boolean ("has-multiple-sizes",
+                                                               "Has multiple sizes",
+                                                               "Has multiple sizes",
+                                                               FALSE,
+                                                               G_PARAM_READABLE));
+}
+
+static void
+gnome_bg_slide_show_init (GnomeBGSlideShow *self)
+{
+        self->priv = gnome_bg_slide_show_get_instance_private (self);
+
+        self->priv->stack = g_queue_new ();
+        self->priv->slides = g_queue_new ();
+}
+
+/**
+ * gnome_bg_slide_show_new:
+ * @filename: The filename of the slide show
+ *
+ * Creates a new object to manage a slide show.
+ * window background between two #cairo_surface_ts.
+ *
+ * Return value: the new #GnomeBGSlideShow
+ **/
+GnomeBGSlideShow *
+gnome_bg_slide_show_new (const char *filename)
+{
+       GFile *file;
+       GnomeBGSlideShow *self;
+
+       file = g_file_new_for_path (filename);
+
+        self = GNOME_BG_SLIDE_SHOW (g_object_new (GNOME_BG_TYPE_SLIDE_SHOW,
+                                                  "file", file,
+                                                  NULL));
+       g_object_unref (file);
+
+       return self;
+}
+
+static void
+threadsafe_localtime (time_t time, struct tm *tm)
+{
+       struct tm *res;
+
+       G_LOCK_DEFINE_STATIC (localtime_mutex);
+
+       G_LOCK (localtime_mutex);
+
+       res = localtime (&time);
+       if (tm) {
+               *tm = *res;
+       }
+
+       G_UNLOCK (localtime_mutex);
+}
+static gboolean stack_is (GnomeBGSlideShow *self, const char *s1, ...);
+
+/* Parser for fading background */
+static void
+handle_start_element (GMarkupParseContext *context,
+                      const gchar         *name,
+                      const gchar        **attr_names,
+                      const gchar        **attr_values,
+                      gpointer             user_data,
+                      GError             **err)
+{
+        GnomeBGSlideShow *self = user_data;
+        gint i;
+
+        if (strcmp (name, "static") == 0 || strcmp (name, "transition") == 0) {
+                Slide *slide = g_new0 (Slide, 1);
+
+                if (strcmp (name, "static") == 0)
+                        slide->fixed = TRUE;
+
+                g_queue_push_tail (self->priv->slides, slide);
+        }
+        else if (strcmp (name, "size") == 0) {
+                Slide *slide = self->priv->slides->tail->data;
+                FileSize *size = g_new0 (FileSize, 1);
+                for (i = 0; attr_names[i]; i++) {
+                        if (strcmp (attr_names[i], "width") == 0)
+                                size->width = atoi (attr_values[i]);
+                        else if (strcmp (attr_names[i], "height") == 0)
+                                size->height = atoi (attr_values[i]);
+                }
+                if (self->priv->stack->tail &&
+                    (strcmp (self->priv->stack->tail->data, "file") == 0 ||
+                     strcmp (self->priv->stack->tail->data, "from") == 0)) {
+                        slide->file1 = g_slist_prepend (slide->file1, size);
+                }
+                else if (self->priv->stack->tail &&
+                         strcmp (self->priv->stack->tail->data, "to") == 0) {
+                        slide->file2 = g_slist_prepend (slide->file2, size);
+                }
+        }
+        g_queue_push_tail (self->priv->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+                    const gchar         *name,
+                    gpointer             user_data,
+                    GError             **err)
+{
+        GnomeBGSlideShow *self = user_data;
+
+        g_free (g_queue_pop_tail (self->priv->stack));
+}
+
+static gboolean
+stack_is (GnomeBGSlideShow *self,
+          const char *s1,
+          ...)
+{
+        GList *stack = NULL;
+        const char *s;
+        GList *l1, *l2;
+        va_list args;
+
+        stack = g_list_prepend (stack, (gpointer)s1);
+
+        va_start (args, s1);
+
+        s = va_arg (args, const char *);
+        while (s) {
+                stack = g_list_prepend (stack, (gpointer)s);
+                s = va_arg (args, const char *);
+        }
+
+        l1 = stack;
+        l2 = self->priv->stack->head;
+
+        while (l1 && l2) {
+                if (strcmp (l1->data, l2->data) != 0) {
+                        g_list_free (stack);
+                        return FALSE;
+                }
+
+                l1 = l1->next;
+                l2 = l2->next;
+        }
+
+        g_list_free (stack);
+
+        return (!l1 && !l2);
+}
+
+static int
+parse_int (const char *text)
+{
+        return strtol (text, NULL, 10);
+}
+
+static void
+handle_text (GMarkupParseContext *context,
+             const gchar         *text,
+             gsize                text_len,
+             gpointer             user_data,
+             GError             **err)
+{
+        GnomeBGSlideShow *self = user_data;
+        Slide *slide = self->priv->slides->tail? self->priv->slides->tail->data : NULL;
+        FileSize *fs;
+        gint i;
+
+        if (stack_is (self, "year", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_year = parse_int (text) - 1900;
+        }
+        else if (stack_is (self, "month", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_mon = parse_int (text) - 1;
+        }
+        else if (stack_is (self, "day", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_mday = parse_int (text);
+        }
+        else if (stack_is (self, "hour", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_hour = parse_int (text);
+        }
+        else if (stack_is (self, "minute", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_min = parse_int (text);
+        }
+        else if (stack_is (self, "second", "starttime", "background", NULL)) {
+                self->priv->start_tm.tm_sec = parse_int (text);
+        }
+        else if (stack_is (self, "duration", "static", "background", NULL) ||
+                 stack_is (self, "duration", "transition", "background", NULL)) {
+                slide->duration = g_strtod (text, NULL);
+                self->priv->total_duration += slide->duration;
+        }
+        else if (stack_is (self, "file", "static", "background", NULL) ||
+                 stack_is (self, "from", "transition", "background", NULL)) {
+                for (i = 0; text[i]; i++) {
+                        if (!g_ascii_isspace (text[i]))
+                                break;
+                }
+                if (text[i] == 0)
+                        return;
+                fs = g_new (FileSize, 1);
+                fs->width = -1;
+                fs->height = -1;
+                fs->file = g_strdup (text);
+                slide->file1 = g_slist_prepend (slide->file1, fs);
+                if (slide->file1->next != NULL)
+                        self->priv->has_multiple_sizes = TRUE;
+        }
+        else if (stack_is (self, "size", "file", "static", "background", NULL) ||
+                 stack_is (self, "size", "from", "transition", "background", NULL)) {
+                fs = slide->file1->data;
+                fs->file = g_strdup (text);
+                if (slide->file1->next != NULL)
+                        self->priv->has_multiple_sizes = TRUE;
+        }
+        else if (stack_is (self, "to", "transition", "background", NULL)) {
+                for (i = 0; text[i]; i++) {
+                        if (!g_ascii_isspace (text[i]))
+                                break;
+                }
+                if (text[i] == 0)
+                        return;
+                fs = g_new (FileSize, 1);
+                fs->width = -1;
+                fs->height = -1;
+                fs->file = g_strdup (text);
+                slide->file2 = g_slist_prepend (slide->file2, fs);
+                if (slide->file2->next != NULL)
+                        self->priv->has_multiple_sizes = TRUE;
+        }
+        else if (stack_is (self, "size", "to", "transition", "background", NULL)) {
+                fs = slide->file2->data;
+                fs->file = g_strdup (text);
+                if (slide->file2->next != NULL)
+                        self->priv->has_multiple_sizes = TRUE;
+        }
+}
+
+/*
+ * Find the FileSize that best matches the given size.
+ * Do two passes; the first pass only considers FileSizes
+ * that are larger than the given size.
+ * We are looking for the image that best matches the aspect ratio.
+ * When two images have the same aspect ratio, prefer the one whose
+ * width is closer to the given width.
+ */
+static const char *
+find_best_size (GSList *sizes, gint width, gint height)
+{
+       GSList *s;
+       gdouble a, d, distance;
+       FileSize *best = NULL;
+       gint pass;
+
+       a = width/(gdouble)height;
+       distance = 10000.0;
+
+       for (pass = 0; pass < 2; pass++) {
+               for (s = sizes; s; s = s->next) {
+                       FileSize *size = s->data;
+
+                       if (pass == 0 && (size->width < width || size->height < height))
+                               continue;
+
+                       d = fabs (a - size->width/(gdouble)size->height);
+                       if (d < distance) {
+                               distance = d;
+                               best = size;
+                        }
+                       else if (d == distance) {
+                               if (abs (size->width - width) < abs (best->width - width)) {
+                                       best = size;
+                               }
+                       }
+               }
+
+               if (best)
+                       break;
+       }
+
+       return best->file;
+}
+
+static double
+now (void)
+{
+        return (double) g_get_real_time () / 1000000.0;
+}
+
+/**
+ * gnome_bg_slide_show_get_current_slide:
+ * @self: a #GnomeBGSlideShow
+ * @width: monitor width
+ * @height: monitor height
+ * @progress: (out) (allow-none): slide progress
+ * @duration: (out) (allow-none): slide duration
+ * @is_fixed: (out) (allow-none): if slide is fixed
+ * @file1: (out) (allow-none) (transfer none): first file in slide
+ * @file2: (out) (allow-none) (transfer none): second file in slide
+ *
+ * Returns the current slides progress.
+ **/
+void
+gnome_bg_slide_show_get_current_slide (GnomeBGSlideShow  *self,
+                                       int                width,
+                                       int                height,
+                                       gdouble           *progress,
+                                       double            *duration,
+                                       gboolean          *is_fixed,
+                                       const char       **file1,
+                                       const char       **file2)
+{
+       double delta = fmod (now () - self->priv->start_time, self->priv->total_duration);
+       GList *list;
+       double elapsed;
+       int i;
+
+       if (delta < 0)
+               delta += self->priv->total_duration;
+
+       elapsed = 0;
+       i = 0;
+       for (list = self->priv->slides->head; list != NULL; list = list->next) {
+               Slide *slide = list->data;
+
+               if (elapsed + slide->duration > delta) {
+                        if (progress)
+                            *progress = (delta - elapsed) / (double)slide->duration;
+                        if (duration)
+                            *duration = slide->duration;
+
+                        if (is_fixed)
+                            *is_fixed = slide->fixed;
+
+                        if (file1 && slide->file1)
+                            *file1 = find_best_size (slide->file1, width, height);
+
+                        if (file2 && slide->file2)
+                            *file2 = find_best_size (slide->file2, width, height);
+
+                        return;
+               }
+
+               i++;
+               elapsed += slide->duration;
+       }
+
+       /* this should never happen since we have slides and we should always
+        * find a current slide for the elapsed time since beginning -- we're
+        * looping with fmod() */
+       g_assert_not_reached ();
+}
+
+/**
+ * gnome_bg_slide_show_get_slide:
+ * @self: a #GnomeBGSlideShow
+ * @frame_number: frame number
+ * @width: monitor width
+ * @height: monitor height
+ * @progress: (out) (allow-none): slide progress
+ * @duration: (out) (allow-none): slide duration
+ * @is_fixed: (out) (allow-none): if slide is fixed
+ * @file1: (out) (allow-none) (transfer none): first file in slide
+ * @file2: (out) (allow-none) (transfer none): second file in slide
+ *
+ * Retrieves slide by frame number
+ *
+ * Return value: %TRUE if successful
+ **/
+gboolean
+gnome_bg_slide_show_get_slide (GnomeBGSlideShow  *self,
+                               int                frame_number,
+                               int                width,
+                               int                height,
+                               double            *progress,
+                               double            *duration,
+                               gboolean          *is_fixed,
+                               const char       **file1,
+                               const char       **file2)
+{
+       double delta = fmod (now () - self->priv->start_time, self->priv->total_duration);
+        GList *l;
+        int i, skipped;
+        gboolean found;
+        double elapsed;
+        Slide *slide;
+
+       if (delta < 0)
+               delta += self->priv->total_duration;
+
+        elapsed = 0;
+       i = 0;
+       skipped = 0;
+       found = FALSE;
+       for (l = self->priv->slides->head; l; l = l->next) {
+               slide = l->data;
+
+               if (!slide->fixed) {
+                        elapsed += slide->duration;
+
+                       skipped++;
+                       continue;
+               }
+               if (i == frame_number) {
+                       found = TRUE;
+                       break;
+               }
+               i++;
+                elapsed += slide->duration;
+       }
+       if (!found)
+               return FALSE;
+
+        if (progress) {
+            if (elapsed + slide->duration > delta) {
+                    *progress = (delta - elapsed) / (double)slide->duration;
+            } else {
+                    *progress = 0.0;
+            }
+        }
+
+        if (duration)
+                *duration = slide->duration;
+
+        if (is_fixed)
+                *is_fixed = slide->fixed;
+
+        if (file1)
+                *file1 = find_best_size (slide->file1, width, height);
+
+        if (file2 && slide->file2)
+                *file2 = find_best_size (slide->file2, width, height);
+
+        return TRUE;
+}
+
+static gboolean
+parse_file_contents (GnomeBGSlideShow  *self,
+                     const char        *contents,
+                     gsize              len,
+                     GError           **error)
+{
+        GMarkupParser parser = {
+                handle_start_element,
+                handle_end_element,
+                handle_text,
+                NULL, /* passthrough */
+                NULL, /* error */
+        };
+
+        GMarkupParseContext *context = NULL;
+        time_t t;
+        gboolean failed = FALSE;
+
+        threadsafe_localtime ((time_t)0, &self->priv->start_tm);
+
+        context = g_markup_parse_context_new (&parser, 0, self, NULL);
+
+        if (!g_markup_parse_context_parse (context, contents, len, error)) {
+                failed = TRUE;
+        }
+
+        if (!failed && !g_markup_parse_context_end_parse (context, error)) {
+                failed = TRUE;
+        }
+
+        g_markup_parse_context_free (context);
+
+        if (!failed) {
+                guint queue_length;
+
+                /* Per the API of mktime, use a negative value on tm_isdst, in order to
+                 * use the system databases to get the active timezone DST status.  */
+                self->priv->start_tm.tm_isdst = -1;
+                t = mktime (&self->priv->start_tm);
+
+                self->priv->start_time = (double)t;
+
+                queue_length = g_queue_get_length (self->priv->slides);
+
+                /* no slides, that's not a slideshow */
+                if (queue_length == 0) {
+                        g_set_error_literal (error,
+                                             G_MARKUP_ERROR,
+                                             G_MARKUP_ERROR_INVALID_CONTENT,
+                                             "file is not a slide show since it has no slides");
+                        failed = TRUE;
+                /* one slide, there's no transition */
+                } else if (queue_length == 1) {
+                        Slide *slide = self->priv->slides->head->data;
+                        slide->duration = self->priv->total_duration = G_MAXUINT;
+                }
+        }
+
+        return !failed;
+}
+
+/**
+ * gnome_bg_slide_show_load:
+ * @self: a #GnomeBGSlideShow
+ * @error: a #GError
+ *
+ * Tries to load the slide show.
+ *
+ * Return value: %TRUE if successful
+ **/
+gboolean
+gnome_bg_slide_show_load (GnomeBGSlideShow  *self,
+                          GError           **error)
+{
+        char  *contents;
+        gsize  length;
+        gboolean parsed;
+
+        if (!g_file_load_contents (self->priv->file, NULL, &contents, &length,
+                                   NULL, NULL)) {
+                return FALSE;
+        }
+
+        parsed = parse_file_contents (self, contents, length, error);
+        g_free (contents);
+
+        return parsed;
+}
+
+static void
+on_file_loaded (GFile        *file,
+                GAsyncResult *result,
+                GTask        *task)
+{
+    gboolean  loaded;
+    char     *contents;
+    gsize    length;
+    GError   *error = NULL;
+
+    loaded = g_file_load_contents_finish (file, result, &contents, &length, NULL, &error);
+
+    if (!loaded) {
+            g_task_return_error (task, error);
+            g_object_unref (task);
+            return;
+    }
+
+    if (!parse_file_contents (g_task_get_source_object (task), contents, length, &error)) {
+            g_task_return_error (task, error);
+            g_object_unref (task);
+            g_free (contents);
+            return;
+    }
+    g_free (contents);
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+/**
+ * gnome_bg_slide_show_load_async:
+ * @self: a #GnomeBGSlideShow
+ * @cancellable: a #GCancellable
+ * @callback: the callback
+ * @user_data: user data
+ *
+ * Tries to load the slide show asynchronously.
+ **/
+void
+gnome_bg_slide_show_load_async (GnomeBGSlideShow    *self,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+    GTask *task;
+
+    task = g_task_new (self, cancellable, callback, user_data);
+
+    g_file_load_contents_async (self->priv->file, cancellable,
+                                (GAsyncReadyCallback) on_file_loaded, task);
+}
+
+/**
+ * gnome_bg_slide_show_get_start_time:
+ * @self: a #GnomeBGSlideShow
+ *
+ * gets the start time of the slide show
+ *
+ * Return value: a timestamp
+ **/
+double
+gnome_bg_slide_show_get_start_time (GnomeBGSlideShow *self)
+{
+        return self->priv->start_time;
+}
+
+/**
+ * gnome_bg_slide_show_get_total_duration:
+ * @self: a #GnomeBGSlideShow
+ *
+ * gets the total duration of the slide show
+ *
+ * Return value: a timestamp
+ **/
+double
+gnome_bg_slide_show_get_total_duration (GnomeBGSlideShow *self)
+{
+        return self->priv->total_duration;
+}
+
+/**
+ * gnome_bg_slide_show_get_has_multiple_sizes:
+ * @self: a #GnomeBGSlideShow
+ *
+ * gets whether or not the slide show has multiple sizes for different monitors
+ *
+ * Return value: %TRUE if multiple sizes
+ **/
+gboolean
+gnome_bg_slide_show_get_has_multiple_sizes (GnomeBGSlideShow *self)
+{
+        return self->priv->has_multiple_sizes;
+}
+
+/**
+ * gnome_bg_slide_show_get_num_slides:
+ * @self: a #GnomeBGSlideShow
+ *
+ * Returns number of slides in slide show
+ **/
+int
+gnome_bg_slide_show_get_num_slides (GnomeBGSlideShow *self)
+{
+        return g_queue_get_length (self->priv->slides);
+}
diff --git a/libgnome-desktop/gnome-bg/gnome-bg-slide-show.h b/libgnome-desktop/gnome-bg/gnome-bg-slide-show.h
new file mode 100644
index 00000000..58eb5b96
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/gnome-bg-slide-show.h
@@ -0,0 +1,96 @@
+/* gnome-bg-slide_show.h - fade window background between two surfaces
+
+   Copyright 2008, Red Hat, Inc.
+
+   This file is part of the Gnome Library.
+
+   The Gnome Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Author: Ray Strode <rstrode redhat com>
+*/
+
+#ifndef __GNOME_BG_SLIDE_SHOW_H__
+#define __GNOME_BG_SLIDE_SHOW_H__
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error    GnomeBGSlideShow is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-bg-slide_show.h
+#endif
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_BG_TYPE_SLIDE_SHOW            (gnome_bg_slide_show_get_type ())
+#define GNOME_BG_SLIDE_SHOW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_BG_TYPE_SLIDE_SHOW, 
GnomeBGSlideShow))
+#define GNOME_BG_SLIDE_SHOW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GNOME_BG_TYPE_SLIDE_SHOW, 
GnomeBGSlideShowClass))
+#define GNOME_BG_IS_SLIDE_SHOW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_BG_TYPE_SLIDE_SHOW))
+#define GNOME_BG_IS_SLIDE_SHOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GNOME_BG_TYPE_SLIDE_SHOW))
+#define GNOME_BG_SLIDE_SHOW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GNOME_BG_TYPE_SLIDE_SHOW, 
GnomeBGSlideShowClass))
+
+typedef struct _GnomeBGSlideShowPrivate GnomeBGSlideShowPrivate;
+typedef struct _GnomeBGSlideShow GnomeBGSlideShow;
+typedef struct _GnomeBGSlideShowClass GnomeBGSlideShowClass;
+
+struct _GnomeBGSlideShow
+{
+       GObject parent_object;
+
+       GnomeBGSlideShowPrivate *priv;
+};
+
+struct _GnomeBGSlideShowClass
+{
+       GObjectClass parent_class;
+};
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GnomeBGSlideShow, g_object_unref)
+
+GType             gnome_bg_slide_show_get_type (void);
+GnomeBGSlideShow *gnome_bg_slide_show_new (const char *filename);
+gboolean          gnome_bg_slide_show_load (GnomeBGSlideShow  *self,
+                                            GError           **error);
+
+void              gnome_bg_slide_show_load_async (GnomeBGSlideShow    *self,
+                                                  GCancellable        *cancellable,
+                                                  GAsyncReadyCallback  callback,
+                                                  gpointer             user_data);
+gboolean          gnome_bg_slide_show_get_slide (GnomeBGSlideShow *self,
+                                                 int               frame_number,
+                                                 int               width,
+                                                 int               height,
+                                                 gdouble          *progress,
+                                                 double           *duration,
+                                                 gboolean         *is_fixed,
+                                                 const char      **file1,
+                                                 const char      **file2);
+
+void              gnome_bg_slide_show_get_current_slide (GnomeBGSlideShow  *self,
+                                                         int                width,
+                                                         int                height,
+                                                         gdouble           *progress,
+                                                         double            *duration,
+                                                         gboolean          *is_fixed,
+                                                         const char       **file1,
+                                                         const char       **file2);
+
+
+double gnome_bg_slide_show_get_start_time (GnomeBGSlideShow *self);
+double gnome_bg_slide_show_get_total_duration (GnomeBGSlideShow *self);
+gboolean gnome_bg_slide_show_get_has_multiple_sizes (GnomeBGSlideShow *self);
+int  gnome_bg_slide_show_get_num_slides (GnomeBGSlideShow *self);
+G_END_DECLS
+
+#endif
diff --git a/libgnome-desktop/gnome-bg/gnome-bg.c b/libgnome-desktop/gnome-bg/gnome-bg.c
new file mode 100644
index 00000000..2584db37
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/gnome-bg.c
@@ -0,0 +1,2401 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+gnomebg.c: Object for the desktop background.
+
+Copyright (C) 2000 Eazel, Inc.
+Copyright (C) 2007-2008 Red Hat, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this program; if not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+Boston, MA 02110-1301, USA.
+
+Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by
+Darin Adler <darin eazel com> and Ramiro Estrugo <ramiro eazel com>
+
+Author: Soren Sandmann <sandmann redhat com>
+
+*/
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include <cairo.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-bg.h"
+#include "gnome-bg-slide-show.h"
+
+#define BG_KEY_PRIMARY_COLOR      "primary-color"
+#define BG_KEY_SECONDARY_COLOR    "secondary-color"
+#define BG_KEY_COLOR_TYPE         "color-shading-type"
+#define BG_KEY_PICTURE_PLACEMENT  "picture-options"
+#define BG_KEY_PICTURE_OPACITY    "picture-opacity"
+#define BG_KEY_PICTURE_URI        "picture-uri"
+
+/* We keep the large pixbufs around if the next update
+   in the slideshow is less than 60 seconds away */
+#define KEEP_EXPENSIVE_CACHE_SECS 60
+
+/* This is the size of the GdkRGB dither matrix, in order to avoid
+ * bad dithering when tiling the gradient
+ */
+#define GRADIENT_PIXMAP_TILE_SIZE 128
+#define THUMBNAIL_SIZE 256
+
+typedef struct FileCacheEntry FileCacheEntry;
+#define CACHE_SIZE 4
+
+/*
+ *   Implementation of the GnomeBG class
+ */
+struct _GnomeBG
+{
+       GObject                 parent_instance;
+       char *                  filename;
+       GDesktopBackgroundStyle placement;
+       GDesktopBackgroundShading       color_type;
+       GdkRGBA                 primary;
+       GdkRGBA                 secondary;
+
+       GFileMonitor *          file_monitor;
+
+       guint                   changed_id;
+       guint                   transitioned_id;
+       guint                   blow_caches_id;
+
+       /* Cached information, only access through cache accessor functions */
+        GnomeBGSlideShow *     slideshow;
+       time_t                  file_mtime;
+       GdkPixbuf *             pixbuf_cache;
+       int                     timeout_id;
+
+       GList *                 file_cache;
+};
+
+struct _GnomeBGClass
+{
+       GObjectClass parent_class;
+};
+
+enum {
+       CHANGED,
+       TRANSITIONED,
+       N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (GnomeBG, gnome_bg, G_TYPE_OBJECT)
+
+/* Pixbuf utils */
+static void       pixbuf_average_value (GdkPixbuf  *pixbuf,
+                                        GdkRGBA    *result);
+static GdkPixbuf *pixbuf_scale_to_fit  (GdkPixbuf  *src,
+                                       int         max_width,
+                                       int         max_height);
+static GdkPixbuf *pixbuf_scale_to_min  (GdkPixbuf  *src,
+                                       int         min_width,
+                                       int         min_height);
+static void       pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
+                                       gboolean      horizontal,
+                                       GdkRGBA      *c1,
+                                       GdkRGBA      *c2,
+                                       GdkRectangle *rect);
+static void       pixbuf_tile          (GdkPixbuf  *src,
+                                       GdkPixbuf  *dest);
+static void       pixbuf_blend         (GdkPixbuf  *src,
+                                       GdkPixbuf  *dest,
+                                       int         src_x,
+                                       int         src_y,
+                                       int         width,
+                                       int         height,
+                                       int         dest_x,
+                                       int         dest_y,
+                                       double      alpha);
+
+/* Thumbnail utilities */
+static GdkPixbuf *create_thumbnail_for_filename (GnomeDesktopThumbnailFactory *factory,
+                                                const char            *filename);
+static gboolean   get_thumb_annotations (GdkPixbuf             *thumb,
+                                        int                   *orig_width,
+                                        int                   *orig_height);
+
+/* Cache */
+static GdkPixbuf *get_pixbuf_for_size  (GnomeBG               *bg,
+                                       gint                   num_monitor,
+                                       int                    width,
+                                       int                    height);
+static void       clear_cache          (GnomeBG               *bg);
+static gboolean   is_different         (GnomeBG               *bg,
+                                       const char            *filename);
+static time_t     get_mtime            (const char            *filename);
+static GdkPixbuf *create_img_thumbnail (GnomeBG *bg,
+                                       GnomeDesktopThumbnailFactory *factory,
+                                        const cairo_rectangle_int_t *screen_area,
+                                        int dest_width,
+                                        int dest_height,
+                                        int frame_num);
+static GnomeBGSlideShow * get_as_slideshow    (GnomeBG               *bg,
+                                               const char            *filename);
+static GnomeBGSlideShow *read_slideshow_file (const char *filename,
+                                      GError     **err);
+
+static void
+color_from_string (const char *string,
+                  GdkRGBA   *colorp)
+{
+       /* If all else fails use black */
+       gdk_rgba_parse (colorp, "black");
+
+       if (!string)
+               return;
+
+       gdk_rgba_parse (colorp, string);
+}
+
+static char *
+color_to_string (const GdkRGBA *color)
+{
+       return g_strdup_printf ("#%02x%02x%02x",
+                               (int) (0.5 + color->red * 255),
+                               (int) (0.5 + color->green * 255),
+                               (int) (0.5 + color->blue * 255));
+}
+
+static gboolean
+do_changed (GnomeBG *bg)
+{
+       gboolean ignore_pending_change;
+       bg->changed_id = 0;
+
+       ignore_pending_change =
+               GPOINTER_TO_INT (g_object_get_data (G_OBJECT (bg),
+                                                   "ignore-pending-change"));
+
+       if (!ignore_pending_change) {
+               g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0);
+       }
+
+       return FALSE;
+}
+
+static void
+queue_changed (GnomeBG *bg)
+{
+       if (bg->changed_id > 0) {
+               g_source_remove (bg->changed_id);
+       }
+
+       /* We unset this here to allow apps to set it if they don't want
+          to get the change event. This is used by nautilus when it
+          gets the pixmap from the bg (due to a reason other than the changed
+          event). Because if there is no other change after this time the
+          pending changed event will just uselessly cause us to recreate
+          the pixmap. */
+       g_object_set_data (G_OBJECT (bg), "ignore-pending-change",
+                          GINT_TO_POINTER (FALSE));
+       bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW,
+                                            100,
+                                            (GSourceFunc)do_changed,
+                                            bg,
+                                            NULL);
+}
+
+static gboolean
+do_transitioned (GnomeBG *bg)
+{
+       bg->transitioned_id = 0;
+
+       if (bg->pixbuf_cache) {
+               g_object_unref (bg->pixbuf_cache);
+               bg->pixbuf_cache = NULL;
+       }
+
+       g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
+
+       return FALSE;
+}
+
+static void
+queue_transitioned (GnomeBG *bg)
+{
+       if (bg->transitioned_id > 0) {
+               g_source_remove (bg->transitioned_id);
+       }
+
+       bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
+                                            100,
+                                            (GSourceFunc)do_transitioned,
+                                            bg,
+                                            NULL);
+}
+
+static gboolean 
+bg_gsettings_mapping (GVariant *value,
+                       gpointer *result,
+                       gpointer user_data)
+{
+       const gchar *bg_key_value;
+       char *filename = NULL;
+
+       /* The final fallback if nothing matches is with a NULL value. */
+       if (value == NULL) {
+               *result = NULL;
+               return TRUE;
+       }
+
+       bg_key_value = g_variant_get_string (value, NULL);
+
+       if (bg_key_value && *bg_key_value != '\0') {
+               filename = g_filename_from_uri (bg_key_value, NULL, NULL);
+
+               if (filename != NULL && g_file_test (filename, G_FILE_TEST_EXISTS) == FALSE) {
+                       g_free (filename);
+                       return FALSE;
+               }
+
+               if (filename != NULL) {
+                       *result = filename;
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+void
+gnome_bg_load_from_preferences (GnomeBG   *bg,
+                               GSettings *settings)
+{
+       char    *tmp;
+       char    *filename;
+       GDesktopBackgroundShading ctype;
+       GdkRGBA c1, c2;
+       GDesktopBackgroundStyle placement;
+
+       g_return_if_fail (GNOME_IS_BG (bg));
+       g_return_if_fail (G_IS_SETTINGS (settings));
+
+       /* Filename */
+       filename = g_settings_get_mapped (settings, BG_KEY_PICTURE_URI, bg_gsettings_mapping, NULL);
+
+       /* Colors */
+       tmp = g_settings_get_string (settings, BG_KEY_PRIMARY_COLOR);
+       color_from_string (tmp, &c1);
+       g_free (tmp);
+
+       tmp = g_settings_get_string (settings, BG_KEY_SECONDARY_COLOR);
+       color_from_string (tmp, &c2);
+       g_free (tmp);
+
+       /* Color type */
+       ctype = g_settings_get_enum (settings, BG_KEY_COLOR_TYPE);
+
+       /* Placement */
+       placement = g_settings_get_enum (settings, BG_KEY_PICTURE_PLACEMENT);
+
+       gnome_bg_set_rgba (bg, ctype, &c1, &c2);
+       gnome_bg_set_placement (bg, placement);
+       gnome_bg_set_filename (bg, filename);
+
+       g_free (filename);
+}
+
+void
+gnome_bg_save_to_preferences (GnomeBG   *bg,
+                             GSettings *settings)
+{
+       gchar *primary;
+       gchar *secondary;
+       gchar *uri;
+
+       g_return_if_fail (GNOME_IS_BG (bg));
+       g_return_if_fail (G_IS_SETTINGS (settings));
+
+       primary = color_to_string (&bg->primary);
+       secondary = color_to_string (&bg->secondary);
+
+       g_settings_delay (settings);
+
+       uri = NULL;
+       if (bg->filename != NULL)
+               uri = g_filename_to_uri (bg->filename, NULL, NULL);
+       if (uri == NULL)
+               uri = g_strdup ("");
+       g_settings_set_string (settings, BG_KEY_PICTURE_URI, uri);
+       g_settings_set_string (settings, BG_KEY_PRIMARY_COLOR, primary);
+       g_settings_set_string (settings, BG_KEY_SECONDARY_COLOR, secondary);
+       g_settings_set_enum (settings, BG_KEY_COLOR_TYPE, bg->color_type);
+       g_settings_set_enum (settings, BG_KEY_PICTURE_PLACEMENT, bg->placement);
+
+       /* Apply changes atomically. */
+       g_settings_apply (settings);
+
+       g_free (primary);
+       g_free (secondary);
+       g_free (uri);
+}
+
+
+static void
+gnome_bg_init (GnomeBG *bg)
+{
+}
+
+static void
+gnome_bg_dispose (GObject *object)
+{
+       GnomeBG *bg = GNOME_BG (object);
+
+       if (bg->file_monitor) {
+               g_object_unref (bg->file_monitor);
+               bg->file_monitor = NULL;
+       }
+
+       clear_cache (bg);
+
+       G_OBJECT_CLASS (gnome_bg_parent_class)->dispose (object);
+}
+
+static void
+gnome_bg_finalize (GObject *object)
+{
+       GnomeBG *bg = GNOME_BG (object);
+
+       if (bg->changed_id != 0) {
+               g_source_remove (bg->changed_id);
+               bg->changed_id = 0;
+       }
+
+       if (bg->transitioned_id != 0) {
+               g_source_remove (bg->transitioned_id);
+               bg->transitioned_id = 0;
+       }
+       
+       if (bg->blow_caches_id != 0) {
+               g_source_remove (bg->blow_caches_id);
+               bg->blow_caches_id = 0;
+       }
+       
+       g_free (bg->filename);
+       bg->filename = NULL;
+
+       G_OBJECT_CLASS (gnome_bg_parent_class)->finalize (object);
+}
+
+static void
+gnome_bg_class_init (GnomeBGClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gnome_bg_dispose;
+       object_class->finalize = gnome_bg_finalize;
+
+       signals[CHANGED] = g_signal_new ("changed",
+                                        G_OBJECT_CLASS_TYPE (object_class),
+                                        G_SIGNAL_RUN_LAST,
+                                        0,
+                                        NULL, NULL,
+                                        g_cclosure_marshal_VOID__VOID,
+                                        G_TYPE_NONE, 0);
+
+       signals[TRANSITIONED] = g_signal_new ("transitioned",
+                                        G_OBJECT_CLASS_TYPE (object_class),
+                                        G_SIGNAL_RUN_LAST,
+                                        0,
+                                        NULL, NULL,
+                                        g_cclosure_marshal_VOID__VOID,
+                                        G_TYPE_NONE, 0);
+}
+
+GnomeBG *
+gnome_bg_new (void)
+{
+       return g_object_new (GNOME_TYPE_BG, NULL);
+}
+
+void
+gnome_bg_set_rgba (GnomeBG *bg,
+                  GDesktopBackgroundShading type,
+                  GdkRGBA *primary,
+                  GdkRGBA *secondary)
+{
+       g_return_if_fail (bg != NULL);
+       g_return_if_fail (primary != NULL);
+
+       if (bg->color_type != type                      ||
+           !gdk_rgba_equal (&bg->primary, primary)                     ||
+           (secondary && !gdk_rgba_equal (&bg->secondary, secondary))) {
+
+               bg->color_type = type;
+               bg->primary = *primary;
+               if (secondary) {
+                       bg->secondary = *secondary;
+               }
+
+               queue_changed (bg);
+       }
+}
+
+void
+gnome_bg_set_placement (GnomeBG                 *bg,
+                       GDesktopBackgroundStyle  placement)
+{
+       g_return_if_fail (bg != NULL);
+       
+       if (bg->placement != placement) {
+               bg->placement = placement;
+               
+               queue_changed (bg);
+       }
+}
+
+GDesktopBackgroundStyle
+gnome_bg_get_placement (GnomeBG *bg)
+{
+       g_return_val_if_fail (bg != NULL, -1);
+
+       return bg->placement;
+}
+
+void
+gnome_bg_get_rgba (GnomeBG                   *bg,
+                  GDesktopBackgroundShading *type,
+                  GdkRGBA                   *primary,
+                  GdkRGBA                   *secondary)
+{
+       g_return_if_fail (bg != NULL);
+
+       if (type)
+               *type = bg->color_type;
+
+       if (primary)
+               *primary = bg->primary;
+
+       if (secondary)
+               *secondary = bg->secondary;
+}
+
+const gchar *
+gnome_bg_get_filename (GnomeBG *bg)
+{
+       g_return_val_if_fail (bg != NULL, NULL);
+
+       return bg->filename;
+}
+
+static inline gchar *
+get_wallpaper_cache_dir (void)
+{
+       return g_build_filename (g_get_user_cache_dir(), "wallpaper", NULL);
+}
+
+static inline gchar *
+get_wallpaper_cache_prefix_name (gint                     num_monitor,
+                                GDesktopBackgroundStyle  placement,
+                                gint                     width,
+                                gint                     height)
+{
+       return g_strdup_printf ("%i_%i_%i_%i", num_monitor, (gint) placement, width, height);
+}
+
+static char *
+get_wallpaper_cache_filename (const char              *filename,
+                             gint                     num_monitor,
+                             GDesktopBackgroundStyle  placement,
+                             gint                     width,
+                             gint                     height)
+{
+       gchar *cache_filename;
+       gchar *cache_prefix_name;
+       gchar *md5_filename;
+       gchar *cache_basename;
+       gchar *cache_dir;
+
+       md5_filename = g_compute_checksum_for_data (G_CHECKSUM_MD5, (const guchar *) filename, strlen 
(filename));
+       cache_prefix_name = get_wallpaper_cache_prefix_name (num_monitor, placement, width, height);
+       cache_basename = g_strdup_printf ("%s_%s", cache_prefix_name, md5_filename);
+       cache_dir = get_wallpaper_cache_dir ();
+       cache_filename = g_build_filename (cache_dir, cache_basename, NULL);
+
+       g_free (cache_prefix_name);
+       g_free (md5_filename);
+       g_free (cache_basename);
+       g_free (cache_dir);
+
+       return cache_filename;
+}
+
+static void
+cleanup_cache_for_monitor (gchar *cache_dir,
+                          gint   num_monitor)
+{
+       GDir            *g_cache_dir;
+       gchar           *monitor_prefix;
+       const gchar     *file;
+
+       g_cache_dir = g_dir_open (cache_dir, 0, NULL);
+       monitor_prefix = g_strdup_printf ("%i_", num_monitor);
+
+       file = g_dir_read_name (g_cache_dir);
+       while (file != NULL) {
+               gchar *path;
+
+               path = g_build_filename (cache_dir, file, NULL);
+               /* purge files with same monitor id */
+               if (g_str_has_prefix (file, monitor_prefix) &&
+                   g_file_test (path, G_FILE_TEST_IS_REGULAR))
+                       g_unlink (path);
+
+               g_free (path);
+
+               file = g_dir_read_name (g_cache_dir);
+       }
+
+       g_free (monitor_prefix);
+       g_dir_close (g_cache_dir);
+}
+
+static gboolean
+cache_file_is_valid (const char *filename,
+                    const char *cache_filename)
+{
+       time_t mtime;
+       time_t cache_mtime;
+
+       if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
+               return FALSE;
+
+       mtime = get_mtime (filename);
+       cache_mtime = get_mtime (cache_filename);
+
+       return (mtime < cache_mtime);
+}
+
+static void
+refresh_cache_file (GnomeBG     *bg,
+                   GdkPixbuf   *new_pixbuf,
+                   gint         num_monitor,
+                   gint         width,
+                   gint         height)
+{
+       gchar           *cache_filename;
+       gchar           *cache_dir;
+       GdkPixbufFormat *format;
+       gchar           *format_name;
+
+       if ((num_monitor == -1) || (width <= 300) || (height <= 300))
+               return;
+
+       cache_filename = get_wallpaper_cache_filename (bg->filename, num_monitor, bg->placement, width, 
height);
+       cache_dir = get_wallpaper_cache_dir ();
+
+       /* Only refresh scaled file on disk if useful (and don't cache slideshow) */
+       if (!cache_file_is_valid (bg->filename, cache_filename)) {
+               format = gdk_pixbuf_get_file_info (bg->filename, NULL, NULL);
+
+               if (format != NULL) {
+                       if (!g_file_test (cache_dir, G_FILE_TEST_IS_DIR)) {
+                               g_mkdir_with_parents (cache_dir, 0700);
+                       } else {
+                               cleanup_cache_for_monitor (cache_dir, num_monitor);
+                       }
+
+                       format_name = gdk_pixbuf_format_get_name (format);
+
+                       if (strcmp (format_name, "jpeg") == 0)
+                               gdk_pixbuf_save (new_pixbuf, cache_filename, format_name, NULL, "quality", 
"100", NULL);
+                       else
+                               gdk_pixbuf_save (new_pixbuf, cache_filename, format_name, NULL, NULL);
+
+                       g_free (format_name);
+               }
+       }
+
+       g_free (cache_filename);
+       g_free (cache_dir);
+}
+
+static void
+file_changed (GFileMonitor *file_monitor,
+             GFile *child,
+             GFile *other_file,
+             GFileMonitorEvent event_type,
+             gpointer user_data)
+{
+       GnomeBG *bg = GNOME_BG (user_data);
+
+       clear_cache (bg);
+       queue_changed (bg);
+}
+
+void
+gnome_bg_set_filename (GnomeBG     *bg,
+                      const char  *filename)
+{
+       g_return_if_fail (bg != NULL);
+       
+       if (is_different (bg, filename)) {
+               g_free (bg->filename);
+               
+               bg->filename = g_strdup (filename);
+               bg->file_mtime = get_mtime (bg->filename);
+
+               if (bg->file_monitor) {
+                       g_object_unref (bg->file_monitor);
+                       bg->file_monitor = NULL;
+               }
+
+               if (bg->filename) {
+                       GFile *f = g_file_new_for_path (bg->filename);
+                       
+                       bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL);
+                       g_signal_connect (bg->file_monitor, "changed",
+                                         G_CALLBACK (file_changed), bg);
+
+                       g_object_unref (f);
+               }
+               
+               clear_cache (bg);
+               
+               queue_changed (bg);
+       }
+}
+
+static void
+draw_color_area (GnomeBG *bg,
+                GdkPixbuf *dest,
+                GdkRectangle *rect)
+{
+       guint32 pixel;
+        GdkRectangle extent;
+
+        extent.x = 0;
+        extent.y = 0;
+        extent.width = gdk_pixbuf_get_width (dest);
+        extent.height = gdk_pixbuf_get_height (dest);
+
+        gdk_rectangle_intersect (rect, &extent, rect);
+       
+       switch (bg->color_type) {
+       case G_DESKTOP_BACKGROUND_SHADING_SOLID:
+               /* not really a big deal to ignore the area of interest */
+               pixel = ((int) (0.5 + bg->primary.red * 255) << 24)      |
+                       ((int) (0.5 + bg->primary.green * 255) << 16)    |
+                       ((int) (0.5 + bg->primary.blue * 255) << 8)      |
+                       (0xff);
+               
+               gdk_pixbuf_fill (dest, pixel);
+               break;
+               
+       case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
+               pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect);
+               break;
+               
+       case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
+               pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect);
+               break;
+               
+       default:
+               break;
+       }
+}
+
+static void
+draw_color (GnomeBG *bg,
+           GdkPixbuf *dest)
+{
+       GdkRectangle rect;
+       rect.x = 0;
+       rect.y = 0;
+       rect.width = gdk_pixbuf_get_width (dest);
+       rect.height = gdk_pixbuf_get_height (dest);
+       draw_color_area (bg, dest, &rect);
+}
+
+static GdkPixbuf *
+pixbuf_clip_to_fit (GdkPixbuf *src,
+                   int        max_width,
+                   int        max_height)
+{
+       int src_width, src_height;
+       int w, h;
+       int src_x, src_y;
+       GdkPixbuf *pixbuf;
+
+       src_width = gdk_pixbuf_get_width (src);
+       src_height = gdk_pixbuf_get_height (src);
+
+       if (src_width < max_width && src_height < max_height)
+               return g_object_ref (src);
+
+       w = MIN(src_width, max_width);
+       h = MIN(src_height, max_height);
+
+       pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                gdk_pixbuf_get_has_alpha (src),
+                                8, w, h);
+
+       src_x = (src_width - w) / 2;
+       src_y = (src_height - h) / 2;
+       gdk_pixbuf_copy_area (src,
+                             src_x, src_y,
+                             w, h,
+                             pixbuf,
+                             0, 0);
+       return pixbuf;
+}
+
+static GdkPixbuf *
+get_scaled_pixbuf (GDesktopBackgroundStyle placement,
+                  GdkPixbuf *pixbuf,
+                  int width, int height,
+                  int *x, int *y,
+                  int *w, int *h)
+{
+       GdkPixbuf *new;
+
+#if 0
+       g_print ("original_width: %d %d\n",
+                gdk_pixbuf_get_width (pixbuf),
+                gdk_pixbuf_get_height (pixbuf));
+#endif
+       
+       switch (placement) {
+       case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+                new = pixbuf_scale_to_fit (pixbuf, width, height);
+               break;
+       case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
+               new = pixbuf_scale_to_min (pixbuf, width, height);
+               break;
+               
+       case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
+               new = gdk_pixbuf_scale_simple (pixbuf, width, height,
+                                              GDK_INTERP_BILINEAR);
+               break;
+               
+       case G_DESKTOP_BACKGROUND_STYLE_SCALED:
+               new = pixbuf_scale_to_fit (pixbuf, width, height);
+               break;
+               
+       case G_DESKTOP_BACKGROUND_STYLE_NONE:
+               /* This shouldn’t be true, but if it is, assert and
+                * fall through, in case assertions are disabled.
+                */
+               g_assert_not_reached ();
+       case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
+       case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
+       default:
+               new = pixbuf_clip_to_fit (pixbuf, width, height);
+               break;
+       }
+       
+       *w = gdk_pixbuf_get_width (new);
+       *h = gdk_pixbuf_get_height (new);
+       *x = (width - *w) / 2;
+       *y = (height - *h) / 2;
+       
+       return new;
+}
+
+static void
+draw_image_area (GnomeBG         *bg,
+                gint             num_monitor,
+                GdkPixbuf       *pixbuf,
+                GdkPixbuf       *dest,
+                GdkRectangle    *area)
+{
+       int dest_width = area->width;
+       int dest_height = area->height;
+       int x, y, w, h;
+       GdkPixbuf *scaled;
+       
+       if (!pixbuf)
+               return;
+
+       scaled = get_scaled_pixbuf (bg->placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h);
+
+       switch (bg->placement) {
+       case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
+               pixbuf_tile (scaled, dest);
+               break;
+       case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
+       case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
+       case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
+       case G_DESKTOP_BACKGROUND_STYLE_SCALED:
+               pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0);
+               break;
+       case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+               pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0);
+               break;
+       case G_DESKTOP_BACKGROUND_STYLE_NONE:
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+
+       refresh_cache_file (bg, scaled, num_monitor, dest_width, dest_height);
+
+       g_object_unref (scaled);
+}
+
+static void
+draw_image_for_thumb (GnomeBG       *bg,
+           GdkPixbuf               *pixbuf,
+           GdkPixbuf               *dest)
+{
+       GdkRectangle rect;
+
+       rect.x = 0;
+       rect.y = 0;
+       rect.width = gdk_pixbuf_get_width (dest);
+       rect.height = gdk_pixbuf_get_height (dest);
+
+       draw_image_area (bg, -1, pixbuf, dest, &rect);
+}
+
+static void
+draw_once (GnomeBG   *bg,
+          GdkPixbuf *dest)
+{
+       GdkRectangle rect;
+       GdkPixbuf   *pixbuf;
+       gint         num_monitor;
+
+       /* we just draw on the whole screen */
+       num_monitor = 0;
+
+       rect.x = 0;
+       rect.y = 0;
+       rect.width = gdk_pixbuf_get_width (dest);
+       rect.height = gdk_pixbuf_get_height (dest);
+
+       pixbuf = get_pixbuf_for_size (bg, num_monitor, rect.width, rect.height);
+       if (pixbuf) {
+               GdkPixbuf *rotated;
+
+               rotated = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+               if (rotated != NULL) {
+                       g_object_unref (pixbuf);
+                       pixbuf = rotated;
+               }
+
+               draw_image_area (bg,
+                                num_monitor,
+                                pixbuf,
+                                dest,
+                                &rect);
+               g_object_unref (pixbuf);
+       }
+}
+
+void
+gnome_bg_draw (GnomeBG   *bg,
+               GdkPixbuf *dest)
+{
+       draw_color (bg, dest);
+       if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
+               draw_once (bg, dest);
+       }
+}
+
+gboolean
+gnome_bg_has_multiple_sizes (GnomeBG *bg)
+{
+       GnomeBGSlideShow *show;
+       gboolean ret;
+
+       g_return_val_if_fail (bg != NULL, FALSE);
+
+       ret = FALSE;
+
+       show = get_as_slideshow (bg, bg->filename);
+       if (show) {
+               ret = gnome_bg_slide_show_get_has_multiple_sizes (show);
+               g_object_unref (show);
+       }
+
+       return ret;
+}
+
+static void
+gnome_bg_get_pixmap_size (GnomeBG   *bg,
+                         int        width,
+                         int        height,
+                         int       *pixmap_width,
+                         int       *pixmap_height)
+{
+       int dummy;
+       
+       if (!pixmap_width)
+               pixmap_width = &dummy;
+       if (!pixmap_height)
+               pixmap_height = &dummy;
+       
+       *pixmap_width = width;
+       *pixmap_height = height;
+
+       if (!bg->filename) {
+               switch (bg->color_type) {
+               case G_DESKTOP_BACKGROUND_SHADING_SOLID:
+                       *pixmap_width = 1;
+                       *pixmap_height = 1;
+                       break;
+                       
+               case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
+               case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
+               default:
+                       break;
+               }
+               
+               return;
+       }
+}
+
+static cairo_surface_t *
+gnome_bg_cairo_surface_create_from_pixbuf (GdkPixbuf *pixbuf,
+                                           int scale,
+                                           cairo_surface_t *target)
+{
+        cairo_format_t format;
+
+        if (gdk_pixbuf_get_n_channels (pixbuf) == 3)
+                format = CAIRO_FORMAT_RGB24;
+        else
+                format = CAIRO_FORMAT_ARGB32;
+
+        int pixbuf_width = gdk_pixbuf_get_width (pixbuf);
+        int pixbuf_height = gdk_pixbuf_get_height (pixbuf);
+        cairo_surface_t *pixbuf_surface =
+                cairo_surface_create_similar_image (target, format,
+                                                    pixbuf_width,
+                                                    pixbuf_height);
+
+        if (cairo_surface_status (pixbuf_surface) != CAIRO_STATUS_SUCCESS)
+                return pixbuf_surface;
+
+        cairo_surface_set_device_scale (pixbuf_surface, scale, scale);
+
+        cairo_surface_flush (pixbuf_surface);
+
+        guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
+        int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+        int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+        guchar *cairo_pixels = cairo_image_surface_get_data (pixbuf_surface);
+        int cairo_stride = cairo_image_surface_get_stride (pixbuf_surface);
+
+        for (int j = pixbuf_height; j; j--) {
+                guchar *p = gdk_pixels;
+                guchar *q = cairo_pixels;
+
+                if (n_channels == 3) {
+                        guchar *end = p + 3 * pixbuf_width;
+
+                        while (p < end) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                q[0] = p[2];
+                                q[1] = p[1];
+                                q[2] = p[0];
+#else
+                                q[1] = p[0];
+                                q[2] = p[1];
+                                q[3] = p[2];
+#endif
+                                p += 3;
+                                q += 4;
+                        }
+                } else {
+                        guchar *end = p + 4 * pixbuf_width;
+
+#define MULT(d,c,a) G_STMT_START { guint __t = c * a + 0x80; d = ((__t >> 8) + __t) >> 8; } G_STMT_END
+
+                        while (p < end) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                MULT (q[0], p[2], p[3]);
+                                MULT (q[1], p[1], p[3]);
+                                MULT (q[2], p[0], p[3]);
+                                q[3] = p[3];
+#else
+                                q[0] = p[3];
+                                MULT(q[1], p[0], p[3]);
+                                MULT(q[2], p[1], p[3]);
+                                MULT(q[3], p[2], p[3]);
+#endif
+
+                                p += 4;
+                                q += 4;
+                        }
+#undef MULT
+                }
+
+                gdk_pixels += gdk_rowstride;
+                cairo_pixels += cairo_stride;
+        }
+
+        cairo_surface_mark_dirty (pixbuf_surface);
+
+        return pixbuf_surface;
+}
+
+/**
+ * gnome_bg_create_surface:
+ * @bg: GnomeBG 
+ * @window: 
+ * @width: 
+ * @height:
+ *
+ * Create a surface that can be set as background for @window.
+ *
+ * Returns: %NULL on error (e.g. out of X connections)
+ **/
+cairo_surface_t *
+gnome_bg_create_surface (GnomeBG    *bg,
+                         GdkSurface *surface,
+                         int         width,
+                         int         height)
+{
+       gint scale;
+       int pm_width, pm_height;
+       cairo_surface_t *target_surface;
+       cairo_t *cr;
+       
+       g_return_val_if_fail (GNOME_IS_BG (bg), NULL);
+       g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
+
+       scale = gdk_surface_get_scale_factor (surface);
+
+        if (bg->pixbuf_cache &&
+            gdk_pixbuf_get_width (bg->pixbuf_cache) != width &&
+            gdk_pixbuf_get_height (bg->pixbuf_cache) != height) {
+                g_object_unref (bg->pixbuf_cache);
+                bg->pixbuf_cache = NULL;
+        }
+
+       /* has the side effect of loading and caching pixbuf only when in tile mode */
+       gnome_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height);
+       target_surface = gdk_surface_create_similar_surface (surface,
+                                                             CAIRO_CONTENT_COLOR,
+                                                             pm_width,
+                                                             pm_height);
+
+       if (target_surface == NULL)
+               return NULL;
+
+       cr = cairo_create (target_surface);
+       if (!bg->filename && bg->color_type == G_DESKTOP_BACKGROUND_SHADING_SOLID) {
+               gdk_cairo_set_source_rgba (cr, &(bg->primary));
+       }
+       else {
+               GdkPixbuf *pixbuf;
+               cairo_surface_t *pixbuf_surface;
+               
+               pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+                                        scale * width,
+                                         scale * height);
+
+               gnome_bg_draw (bg, pixbuf);
+
+               pixbuf_surface = gnome_bg_cairo_surface_create_from_pixbuf (pixbuf, scale, cairo_get_target 
(cr));
+               cairo_set_source_surface (cr, pixbuf_surface, 0, 0);
+               cairo_surface_destroy (pixbuf_surface);
+               g_object_unref (pixbuf);
+       }
+
+       cairo_paint (cr);
+       
+       cairo_destroy (cr);
+
+       return target_surface;
+}
+
+
+/* determine if a background is darker or lighter than average, to help
+ * clients know what colors to draw on top with
+ */
+gboolean
+gnome_bg_is_dark (GnomeBG *bg,
+                 int      width,
+                 int      height)
+{
+       GdkRGBA color;
+       gdouble intensity;
+       GdkPixbuf *pixbuf;
+       
+       g_return_val_if_fail (bg != NULL, FALSE);
+       
+       if (bg->color_type == G_DESKTOP_BACKGROUND_SHADING_SOLID) {
+               color = bg->primary;
+       } else {
+               color.red = (bg->primary.red + bg->secondary.red) / 2;
+               color.green = (bg->primary.green + bg->secondary.green) / 2;
+               color.blue = (bg->primary.blue + bg->secondary.blue) / 2;
+       }
+       pixbuf = get_pixbuf_for_size (bg, -1, width, height);
+       if (pixbuf) {
+               GdkRGBA average;
+
+               pixbuf_average_value (pixbuf, &average);
+               
+               color.red = color.red * (1.0 - average.alpha) + average.red * average.alpha;
+               color.green = color.green * (1.0 - average.alpha) + average.green * average.alpha;
+               color.blue = color.blue * (1.0 - average.alpha) + average.blue * average.alpha;
+               g_object_unref (pixbuf);
+       }
+       
+       intensity = color.red * 77 +
+                   color.green * 150 +
+                   color.blue * 28;
+       
+       return intensity < 160; /* biased slightly to be dark */
+}
+
+static gboolean
+get_original_size (const char *filename,
+                  int        *orig_width,
+                  int        *orig_height)
+{
+       gboolean result;
+
+        if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height))
+               result = TRUE;
+       else
+               result = FALSE;
+
+       return result;
+}
+
+static const char *
+get_filename_for_size (GnomeBG *bg, gint best_width, gint best_height)
+{
+       GnomeBGSlideShow *show;
+        const char *file = NULL;
+
+       if (!bg->filename)
+               return NULL;
+
+       show = get_as_slideshow (bg, bg->filename);
+       if (!show) {
+               return bg->filename;
+       }
+
+        gnome_bg_slide_show_get_current_slide (show, best_width, best_height, NULL, NULL, NULL, &file, NULL);
+        g_object_unref (show);
+
+        return file;
+}
+
+gboolean
+gnome_bg_get_image_size (GnomeBG              *bg,
+                        GnomeDesktopThumbnailFactory *factory,
+                        int                    best_width,
+                        int                    best_height,
+                        int                   *width,
+                        int                   *height)
+{
+       GdkPixbuf *thumb;
+       gboolean result = FALSE;
+       const gchar *filename;
+       
+       g_return_val_if_fail (bg != NULL, FALSE);
+       g_return_val_if_fail (factory != NULL, FALSE);
+       
+       if (!bg->filename)
+               return FALSE;
+       
+       filename = get_filename_for_size (bg, best_width, best_height);
+       thumb = create_thumbnail_for_filename (factory, filename);
+       if (thumb) {
+               if (get_thumb_annotations (thumb, width, height))
+                       result = TRUE;
+               
+               g_object_unref (thumb);
+       }
+
+       if (!result) {
+               if (get_original_size (filename, width, height))
+                       result = TRUE;
+       }
+
+       return result;
+}
+
+static double
+fit_factor (int from_width, int from_height,
+           int to_width,   int to_height)
+{
+       return MIN (to_width  / (double) from_width, to_height / (double) from_height);
+}
+
+/**
+ * gnome_bg_create_thumbnail:
+ *
+ * Returns: (transfer full): a #GdkPixbuf showing the background as a thumbnail
+ */
+GdkPixbuf *
+gnome_bg_create_thumbnail (GnomeBG                      *bg,
+                          GnomeDesktopThumbnailFactory *factory,
+                           const cairo_rectangle_int_t  *screen_area,
+                           int                           dest_width,
+                          int                           dest_height)
+{
+       GdkPixbuf *result;
+       GdkPixbuf *thumb;
+       
+       g_return_val_if_fail (bg != NULL, NULL);
+       
+       result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+       
+       draw_color (bg, result);
+       
+       if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
+               thumb = create_img_thumbnail (bg, factory, screen_area, dest_width, dest_height, -1);
+               
+               if (thumb) {
+                       draw_image_for_thumb (bg, thumb, result);
+                       g_object_unref (thumb);
+               }
+       }
+       
+       return result;
+}
+
+/* Implementation of the pixbuf cache */
+struct _SlideShow
+{
+       gint ref_count;
+       double start_time;
+       double total_duration;
+
+       GQueue *slides;
+       
+       gboolean has_multiple_sizes;
+
+       /* used during parsing */
+       struct tm start_tm;
+       GQueue *stack;
+};
+
+
+static GdkPixbuf *
+blend (GdkPixbuf *p1,
+       GdkPixbuf *p2,
+       double alpha)
+{
+       GdkPixbuf *result = gdk_pixbuf_copy (p1);
+       GdkPixbuf *tmp;
+
+       if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) ||
+            gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) {
+               tmp = gdk_pixbuf_scale_simple (p2, 
+                                              gdk_pixbuf_get_width (p1),
+                                              gdk_pixbuf_get_height (p1),
+                                              GDK_INTERP_BILINEAR);
+       }
+        else {
+               tmp = g_object_ref (p2);
+       }
+       
+       pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha);
+        
+        g_object_unref (tmp);  
+
+       return result;
+}
+
+typedef        enum {
+       PIXBUF,
+       SLIDESHOW,
+       THUMBNAIL
+} FileType;
+
+struct FileCacheEntry
+{
+       FileType type;
+       char *filename;
+       union {
+               GdkPixbuf *pixbuf;
+               GnomeBGSlideShow *slideshow;
+               GdkPixbuf *thumbnail;
+       } u;
+};
+
+static void
+file_cache_entry_delete (FileCacheEntry *ent)
+{
+       g_free (ent->filename);
+       
+       switch (ent->type) {
+       case PIXBUF:
+               g_object_unref (ent->u.pixbuf);
+               break;
+       case SLIDESHOW:
+               g_object_unref (ent->u.slideshow);
+               break;
+       case THUMBNAIL:
+               g_object_unref (ent->u.thumbnail);
+               break;
+       default:
+               break;
+       }
+
+       g_free (ent);
+}
+
+static void
+bound_cache (GnomeBG *bg)
+{
+      while (g_list_length (bg->file_cache) >= CACHE_SIZE) {
+             GList *last_link = g_list_last (bg->file_cache);
+             FileCacheEntry *ent = last_link->data;
+
+             file_cache_entry_delete (ent);
+
+             bg->file_cache = g_list_delete_link (bg->file_cache, last_link);
+      }
+}
+
+static const FileCacheEntry *
+file_cache_lookup (GnomeBG *bg, FileType type, const char *filename)
+{
+       GList *list;
+
+       for (list = bg->file_cache; list != NULL; list = list->next) {
+               FileCacheEntry *ent = list->data;
+
+               if (ent && ent->type == type &&
+                   strcmp (ent->filename, filename) == 0) {
+                       return ent;
+               }
+       }
+
+       return NULL;
+}
+
+static FileCacheEntry *
+file_cache_entry_new (GnomeBG *bg,
+                     FileType type,
+                     const char *filename)
+{
+       FileCacheEntry *ent = g_new0 (FileCacheEntry, 1);
+
+       g_assert (!file_cache_lookup (bg, type, filename));
+       
+       ent->type = type;
+       ent->filename = g_strdup (filename);
+
+       bg->file_cache = g_list_prepend (bg->file_cache, ent);
+
+       bound_cache (bg);
+       
+       return ent;
+}
+
+static void
+file_cache_add_pixbuf (GnomeBG *bg,
+                      const char *filename,
+                      GdkPixbuf *pixbuf)
+{
+       FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename);
+       ent->u.pixbuf = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_thumbnail (GnomeBG *bg,
+                         const char *filename,
+                         GdkPixbuf *pixbuf)
+{
+       FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename);
+       ent->u.thumbnail = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_slide_show (GnomeBG *bg,
+                          const char *filename,
+                          GnomeBGSlideShow *show)
+{
+       FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename);
+       ent->u.slideshow = g_object_ref (show);
+}
+
+static GdkPixbuf *
+load_from_cache_file (GnomeBG    *bg,
+                     const char *filename,
+                     gint        num_monitor,
+                     gint        best_width,
+                     gint        best_height)
+{
+       GdkPixbuf *pixbuf = NULL;
+       gchar *cache_filename;
+
+       cache_filename = get_wallpaper_cache_filename (filename, num_monitor, bg->placement, best_width, 
best_height);
+       if (cache_file_is_valid (filename, cache_filename))
+               pixbuf = gdk_pixbuf_new_from_file (cache_filename, NULL);
+       g_free (cache_filename);
+
+       return pixbuf;
+}
+
+static GdkPixbuf *
+get_as_pixbuf_for_size (GnomeBG    *bg,
+                       const char *filename,
+                       gint        num_monitor,
+                       gint        best_width,
+                       gint        best_height)
+{
+       const FileCacheEntry *ent;
+       if ((ent = file_cache_lookup (bg, PIXBUF, filename))) {
+               return g_object_ref (ent->u.pixbuf);
+       }
+       else {
+               GdkPixbufFormat *format;
+               GdkPixbuf *pixbuf;
+                gchar *tmp;
+               pixbuf = NULL;
+
+               /* Try to hit local cache first if relevant */
+               if (num_monitor != -1)
+                       pixbuf = load_from_cache_file (bg, filename, num_monitor, best_width, best_height);
+
+               if (!pixbuf) {
+                       /* If scalable choose maximum size */
+                       format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
+
+                       if (format != NULL) {
+                               tmp = gdk_pixbuf_format_get_name (format);
+                       } else {
+                               tmp = NULL;
+                       }
+
+                       if (tmp != NULL &&
+                           strcmp (tmp, "svg") == 0 &&
+                           (best_width > 0 && best_height > 0) &&
+                           (bg->placement == G_DESKTOP_BACKGROUND_STYLE_STRETCHED ||
+                            bg->placement == G_DESKTOP_BACKGROUND_STYLE_SCALED ||
+                            bg->placement == G_DESKTOP_BACKGROUND_STYLE_ZOOM))
+                               pixbuf = gdk_pixbuf_new_from_file_at_size (filename, best_width, best_height, 
NULL);
+                       else
+                               pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+                       g_free (tmp);
+               }
+
+               if (pixbuf)
+                       file_cache_add_pixbuf (bg, filename, pixbuf);
+
+               return pixbuf;
+       }
+}
+
+static GnomeBGSlideShow *
+get_as_slideshow (GnomeBG *bg, const char *filename)
+{
+       const FileCacheEntry *ent;
+       if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) {
+               return g_object_ref (ent->u.slideshow);
+       }
+       else {
+               GnomeBGSlideShow *show = read_slideshow_file (filename, NULL);
+
+               if (show)
+                       file_cache_add_slide_show (bg, filename, show);
+
+               return show;
+       }
+}
+
+static GdkPixbuf *
+get_as_thumbnail (GnomeBG *bg, GnomeDesktopThumbnailFactory *factory, const char *filename)
+{
+       const FileCacheEntry *ent;
+       if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) {
+               return g_object_ref (ent->u.thumbnail);
+       }
+       else {
+               GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename);
+
+               if (thumb)
+                       file_cache_add_thumbnail (bg, filename, thumb);
+
+               return thumb;
+       }
+}
+
+static gboolean
+blow_expensive_caches (gpointer data)
+{
+       GnomeBG *bg = data;
+       GList *list, *next;
+       
+       bg->blow_caches_id = 0;
+       
+       for (list = bg->file_cache; list != NULL; list = next) {
+               FileCacheEntry *ent = list->data;
+               next = list->next;
+               
+               if (ent->type == PIXBUF) {
+                       file_cache_entry_delete (ent);
+                       bg->file_cache = g_list_delete_link (bg->file_cache,
+                                                            list);
+               }
+       }
+
+       if (bg->pixbuf_cache) {
+               g_object_unref (bg->pixbuf_cache);
+               bg->pixbuf_cache = NULL;
+       }
+
+       return FALSE;
+}
+
+static void
+blow_expensive_caches_in_idle (GnomeBG *bg)
+{
+       if (bg->blow_caches_id == 0) {
+               bg->blow_caches_id =
+                       g_idle_add (blow_expensive_caches,
+                                   bg);
+       }
+}
+
+
+static gboolean
+on_timeout (gpointer data)
+{
+       GnomeBG *bg = data;
+
+       bg->timeout_id = 0;
+       
+       queue_transitioned (bg);
+
+       return FALSE;
+}
+
+static double
+get_slide_timeout (gboolean is_fixed,
+                   gdouble  duration)
+{
+       double timeout;
+       if (is_fixed) {
+               timeout = duration;
+       } else {
+               /* Maybe the number of steps should be configurable? */
+               
+               /* In the worst case we will do a fade from 0 to 256, which mean
+                * we will never use more than 255 steps, however in most cases
+                * the first and last value are similar and users can't percieve
+                * changes in pixel values as small as 1/255th. So, lets not waste
+                * CPU cycles on transitioning to often.
+                *
+                * 64 steps is enough for each step to be just detectable in a 16bit
+                * color mode in the worst case, so we'll use this as an approximation
+                * of whats detectable.
+                */
+               timeout = duration / 64.0;
+       }
+       return timeout;
+}
+
+static void
+ensure_timeout (GnomeBG *bg,
+                gdouble  timeout)
+{
+       if (!bg->timeout_id) {
+               /* G_MAXUINT means "only one slide" */
+               if (timeout < G_MAXUINT) {
+                       bg->timeout_id = g_timeout_add_full (
+                               G_PRIORITY_LOW,
+                               timeout * 1000, on_timeout, bg, NULL);
+               }
+       }
+}
+
+static time_t
+get_mtime (const char *filename)
+{
+       GFile     *file;
+       GFileInfo *info;
+       time_t     mtime;
+       
+       mtime = (time_t)-1;
+       
+       if (filename) {
+               file = g_file_new_for_path (filename);
+               info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                         G_FILE_QUERY_INFO_NONE, NULL, NULL);
+               if (info) {
+                       mtime = g_file_info_get_attribute_uint64 (info,
+                                                                 G_FILE_ATTRIBUTE_TIME_MODIFIED);
+                       g_object_unref (info);
+               }
+               g_object_unref (file);
+       }
+       
+       return mtime;
+}
+
+static GdkPixbuf *
+scale_thumbnail (GDesktopBackgroundStyle placement,
+                const char *filename,
+                GdkPixbuf *thumb,
+                 const cairo_rectangle_int_t *screen_area,
+                int dest_width,
+                int dest_height)
+{
+       int o_width;
+       int o_height;
+       
+       if (placement != G_DESKTOP_BACKGROUND_STYLE_WALLPAPER &&
+           placement != G_DESKTOP_BACKGROUND_STYLE_CENTERED) {
+               
+               /* In this case, the pixbuf will be scaled to fit the screen anyway,
+                * so just return the pixbuf here
+                */
+               return g_object_ref (thumb);
+       }
+       
+       if (get_thumb_annotations (thumb, &o_width, &o_height)          ||
+           (filename && get_original_size (filename, &o_width, &o_height))) {
+               int scr_width = screen_area->width;
+               int scr_height = screen_area->height;
+               int thumb_width = gdk_pixbuf_get_width (thumb);
+               int thumb_height = gdk_pixbuf_get_height (thumb);
+               double screen_to_dest = fit_factor (scr_width, scr_height, dest_width, dest_height);
+               double thumb_to_orig = fit_factor (thumb_width, thumb_height, o_width, o_height);
+               double f = thumb_to_orig * screen_to_dest;
+               int new_width, new_height;
+               
+               new_width = floor (thumb_width * f + 0.5);
+               new_height = floor (thumb_height * f + 0.5);
+
+               if (placement == G_DESKTOP_BACKGROUND_STYLE_WALLPAPER) {
+                       /* Heuristic to make sure tiles don't become so small that
+                        * they turn into a blur.
+                        *
+                        * This is strictly speaking incorrect, but the resulting
+                        * thumbnail gives a much better idea what the background
+                        * will actually look like.
+                        */
+                       
+                       if ((new_width < 32 || new_height < 32) &&
+                           (new_width < o_width / 4 || new_height < o_height / 4)) {
+                               new_width = o_width / 4;
+                               new_height = o_height / 4;
+                       }
+               }
+                       
+               thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height,
+                                                GDK_INTERP_BILINEAR);
+       }
+       else
+               g_object_ref (thumb);
+       
+       return thumb;
+}
+
+/* frame_num determines which slide to thumbnail.
+ * -1 means 'current slide'.
+ */
+static GdkPixbuf *
+create_img_thumbnail (GnomeBG *bg,
+                     GnomeDesktopThumbnailFactory *factory,
+                     const cairo_rectangle_int_t *screen_area,
+                     int dest_width,
+                     int dest_height,
+                     int frame_num)
+{
+       if (bg->filename) {
+               GdkPixbuf *thumb;
+
+               thumb = get_as_thumbnail (bg, factory, bg->filename);
+
+               if (thumb) {
+                       GdkPixbuf *result;
+                       result = scale_thumbnail (bg->placement,
+                                                 bg->filename,
+                                                 thumb,
+                                                 screen_area,
+                                                 dest_width,
+                                                 dest_height);
+                       g_object_unref (thumb);
+                       return result;
+               }
+               else {
+                       GnomeBGSlideShow *show = get_as_slideshow (bg, bg->filename);
+
+                       if (show) {
+                               double alpha;
+                               double duration;
+                                gboolean is_fixed;
+                                const char *file1;
+                                const char *file2;
+                                GdkPixbuf *tmp;
+
+                               if (frame_num == -1)
+                                       gnome_bg_slide_show_get_current_slide (show,
+                                                                               dest_width,
+                                                                               dest_height,
+                                                                               &alpha,
+                                                                               &duration,
+                                                                               &is_fixed,
+                                                                               &file1,
+                                                                               &file2);
+                               else
+                                       gnome_bg_slide_show_get_slide (show,
+                                                                       frame_num,
+                                                                       dest_width,
+                                                                       dest_height,
+                                                                       &alpha,
+                                                                       &duration,
+                                                                       &is_fixed,
+                                                                       &file1,
+                                                                       &file2);
+
+                               if (is_fixed) {
+                                       tmp = get_as_thumbnail (bg, factory, file1);
+                                       if (tmp) {
+                                               thumb = scale_thumbnail (bg->placement,
+                                                                        file1,
+                                                                        tmp,
+                                                                        screen_area,
+                                                                        dest_width,
+                                                                        dest_height);
+                                               g_object_unref (tmp);
+                                       }
+                               }
+                               else {
+                                       GdkPixbuf *p1, *p2;
+                                       p1 = get_as_thumbnail (bg, factory, file1);
+                                       p2 = get_as_thumbnail (bg, factory, file2);
+
+                                       if (p1 && p2) {
+                                               GdkPixbuf *thumb1, *thumb2;
+
+                                               thumb1 = scale_thumbnail (bg->placement,
+                                                                         file1,
+                                                                         p1,
+                                                                         screen_area,
+                                                                         dest_width,
+                                                                         dest_height);
+
+                                               thumb2 = scale_thumbnail (bg->placement,
+                                                                         file2,
+                                                                         p2,
+                                                                         screen_area,
+                                                                         dest_width,
+                                                                         dest_height);
+
+                                               thumb = blend (thumb1, thumb2, alpha);
+
+                                               g_object_unref (thumb1);
+                                               g_object_unref (thumb2);
+                                       }
+                                       if (p1)
+                                               g_object_unref (p1);
+                                       if (p2)
+                                               g_object_unref (p2);
+                               }
+
+                               ensure_timeout (bg, (guint)get_slide_timeout (is_fixed, duration));
+
+                               g_object_unref (show);
+                       }
+               }
+
+               return thumb;
+       }
+
+       return NULL;
+}
+
+static GdkPixbuf *
+get_pixbuf_for_size (GnomeBG *bg,
+                    gint num_monitor,
+                    gint best_width,
+                    gint best_height)
+{
+       guint time_until_next_change;
+       gboolean hit_cache = FALSE;
+
+       /* only hit the cache if the aspect ratio matches */
+       if (bg->pixbuf_cache) {
+               int width, height;
+               width = gdk_pixbuf_get_width (bg->pixbuf_cache);
+               height = gdk_pixbuf_get_height (bg->pixbuf_cache);
+               hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height));
+               if (!hit_cache) {
+                       g_object_unref (bg->pixbuf_cache);
+                       bg->pixbuf_cache = NULL;
+               }
+       }
+
+       if (!hit_cache && bg->filename) {
+               bg->file_mtime = get_mtime (bg->filename);
+
+               bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, num_monitor, best_width, 
best_height);
+               time_until_next_change = G_MAXUINT;
+               if (!bg->pixbuf_cache) {
+                       GnomeBGSlideShow *show = get_as_slideshow (bg, bg->filename);
+
+                       if (show) {
+                               double alpha;
+                                double duration;
+                                gboolean is_fixed;
+                                const char *file1;
+                                const char *file2;
+
+                               g_object_ref (show);
+
+                               gnome_bg_slide_show_get_current_slide (show,
+                                                                       best_width,
+                                                                       best_height,
+                                                                       &alpha,
+                                                                       &duration,
+                                                                       &is_fixed,
+                                                                       &file1,
+                                                                       &file2);
+                               time_until_next_change = (guint)get_slide_timeout (is_fixed, duration);
+                               if (is_fixed) {
+                                       bg->pixbuf_cache = get_as_pixbuf_for_size (bg, file1, num_monitor, 
best_width, best_height);
+                               }
+                               else {
+                                       GdkPixbuf *p1, *p2;
+                                       p1 = get_as_pixbuf_for_size (bg, file1, num_monitor, best_width, 
best_height);
+                                       p2 = get_as_pixbuf_for_size (bg, file2, num_monitor, best_width, 
best_height);
+
+                                       if (p1 && p2) {
+                                               bg->pixbuf_cache = blend (p1, p2, alpha);
+                                       }
+                                       if (p1)
+                                               g_object_unref (p1);
+                                       if (p2)
+                                               g_object_unref (p2);
+                               }
+
+                               ensure_timeout (bg, time_until_next_change);
+
+                               g_object_unref (show);
+                       }
+               }
+
+               /* If the next slideshow step is a long time away then
+                  we blow away the expensive stuff (large pixbufs) from
+                  the cache */
+               if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS)
+                   blow_expensive_caches_in_idle (bg);
+       }
+
+       if (bg->pixbuf_cache)
+               g_object_ref (bg->pixbuf_cache);
+
+       return bg->pixbuf_cache;
+}
+
+static gboolean
+is_different (GnomeBG    *bg,
+             const char *filename)
+{
+       if (!filename && bg->filename) {
+               return TRUE;
+       }
+       else if (filename && !bg->filename) {
+               return TRUE;
+       }
+       else if (!filename && !bg->filename) {
+               return FALSE;
+       }
+       else {
+               time_t mtime = get_mtime (filename);
+               
+               if (mtime != bg->file_mtime)
+                       return TRUE;
+               
+               if (strcmp (filename, bg->filename) != 0)
+                       return TRUE;
+               
+               return FALSE;
+       }
+}
+
+static void
+clear_cache (GnomeBG *bg)
+{
+       GList *list;
+
+       if (bg->file_cache) {
+               for (list = bg->file_cache; list != NULL; list = list->next) {
+                       FileCacheEntry *ent = list->data;
+                       
+                       file_cache_entry_delete (ent);
+               }
+               g_list_free (bg->file_cache);
+               bg->file_cache = NULL;
+       }
+       
+       if (bg->pixbuf_cache) {
+               g_object_unref (bg->pixbuf_cache);
+               
+               bg->pixbuf_cache = NULL;
+       }
+
+       if (bg->timeout_id) {
+               g_source_remove (bg->timeout_id);
+
+               bg->timeout_id = 0;
+       }
+}
+
+/* Pixbuf utilities */
+static void
+pixbuf_average_value (GdkPixbuf *pixbuf,
+                      GdkRGBA   *result)
+{
+       guint64 a_total, r_total, g_total, b_total;
+       guint row, column;
+       int row_stride;
+       const guchar *pixels, *p;
+       int r, g, b, a;
+       guint64 dividend;
+       guint width, height;
+       gdouble dd;
+       
+       width = gdk_pixbuf_get_width (pixbuf);
+       height = gdk_pixbuf_get_height (pixbuf);
+       row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+       pixels = gdk_pixbuf_get_pixels (pixbuf);
+       
+       /* iterate through the pixbuf, counting up each component */
+       a_total = 0;
+       r_total = 0;
+       g_total = 0;
+       b_total = 0;
+       
+       if (gdk_pixbuf_get_has_alpha (pixbuf)) {
+               for (row = 0; row < height; row++) {
+                       p = pixels + (row * row_stride);
+                       for (column = 0; column < width; column++) {
+                               r = *p++;
+                               g = *p++;
+                               b = *p++;
+                               a = *p++;
+                               
+                               a_total += a;
+                               r_total += r * a;
+                               g_total += g * a;
+                               b_total += b * a;
+                       }
+               }
+               dividend = height * width * 0xFF;
+               a_total *= 0xFF;
+       } else {
+               for (row = 0; row < height; row++) {
+                       p = pixels + (row * row_stride);
+                       for (column = 0; column < width; column++) {
+                               r = *p++;
+                               g = *p++;
+                               b = *p++;
+                               
+                               r_total += r;
+                               g_total += g;
+                               b_total += b;
+                       }
+               }
+               dividend = height * width;
+               a_total = dividend * 0xFF;
+       }
+
+       dd = dividend * 0xFF;
+       result->alpha = a_total / dd;
+       result->red = r_total / dd;
+       result->green = g_total / dd;
+       result->blue = b_total / dd;
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height)
+{
+       double factor;
+       int src_width, src_height;
+       int new_width, new_height;
+       
+       src_width = gdk_pixbuf_get_width (src);
+       src_height = gdk_pixbuf_get_height (src);
+       
+       factor = MIN (max_width  / (double) src_width, max_height / (double) src_height);
+       
+       new_width  = floor (src_width * factor + 0.5);
+       new_height = floor (src_height * factor + 0.5);
+       
+       return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR);       
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height)
+{
+       double factor;
+       int src_width, src_height;
+       int new_width, new_height;
+       GdkPixbuf *dest;
+
+       src_width = gdk_pixbuf_get_width (src);
+       src_height = gdk_pixbuf_get_height (src);
+
+       factor = MAX (min_width / (double) src_width, min_height / (double) src_height);
+
+       new_width = floor (src_width * factor + 0.5);
+       new_height = floor (src_height * factor + 0.5);
+
+       dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                              gdk_pixbuf_get_has_alpha (src),
+                              8, min_width, min_height);
+       if (!dest)
+               return NULL;
+
+       /* crop the result */
+       gdk_pixbuf_scale (src, dest,
+                         0, 0,
+                         min_width, min_height,
+                         (new_width - min_width) / -2,
+                         (new_height - min_height) / -2,
+                         factor,
+                         factor,
+                         GDK_INTERP_BILINEAR);
+       return dest;
+}
+
+static guchar *
+create_gradient (const GdkRGBA *primary,
+                const GdkRGBA *secondary,
+                int            n_pixels)
+{
+       guchar *result = g_malloc (n_pixels * 3);
+       int i;
+       
+       for (i = 0; i < n_pixels; ++i) {
+               double ratio = (i + 0.5) / n_pixels;
+               
+               result[3 * i + 0] = (int) (0.5 + (primary->red * (1 - ratio) + secondary->red * ratio) * 255);
+               result[3 * i + 1] = (int) (0.5 + (primary->green * (1 - ratio) + secondary->green * ratio) * 
255);
+               result[3 * i + 2] = (int) (0.5 + (primary->blue * (1 - ratio) + secondary->blue * ratio) * 
255);
+       }
+       
+       return result;
+}      
+
+static void
+pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
+                     gboolean      horizontal,
+                     GdkRGBA      *primary,
+                     GdkRGBA      *secondary,
+                     GdkRectangle *rect)
+{
+       int width;
+       int height;
+       int rowstride;
+       guchar *dst;
+       int n_channels = 3;
+
+       rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+       width = rect->width;
+       height = rect->height;
+       dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y;
+
+       if (horizontal) {
+               guchar *gradient = create_gradient (primary, secondary, width);
+               int copy_bytes_per_row = width * n_channels;
+               int i;
+
+               for (i = 0; i < height; i++) {
+                       guchar *d;
+                       d = dst + rowstride * i;
+                       memcpy (d, gradient, copy_bytes_per_row);
+               }
+               g_free (gradient);
+       } else {
+               guchar *gb, *gradient;
+               int i;
+
+               gradient = create_gradient (primary, secondary, height);
+               for (i = 0; i < height; i++) {
+                       int j;
+                       guchar *d;
+
+                       d = dst + rowstride * i;
+                       gb = gradient + n_channels * i;
+                       for (j = width; j > 0; j--) {
+                               int k;
+
+                               for (k = 0; k < n_channels; k++) {
+                                       *(d++) = gb[k];
+                               }
+                       }
+               }
+
+               g_free (gradient);
+       }
+}
+
+static void
+pixbuf_blend (GdkPixbuf *src,
+             GdkPixbuf *dest,
+             int        src_x,
+             int        src_y,
+             int        src_width,
+             int        src_height,
+             int        dest_x,
+             int        dest_y,
+             double     alpha)
+{
+       int dest_width = gdk_pixbuf_get_width (dest);
+       int dest_height = gdk_pixbuf_get_height (dest);
+       int offset_x = dest_x - src_x;
+       int offset_y = dest_y - src_y;
+
+       if (src_width < 0)
+               src_width = gdk_pixbuf_get_width (src);
+
+       if (src_height < 0)
+               src_height = gdk_pixbuf_get_height (src);
+       
+       if (dest_x < 0)
+               dest_x = 0;
+       
+       if (dest_y < 0)
+               dest_y = 0;
+       
+       if (dest_x + src_width > dest_width) {
+               src_width = dest_width - dest_x;
+       }
+       
+       if (dest_y + src_height > dest_height) {
+               src_height = dest_height - dest_y;
+       }
+
+       gdk_pixbuf_composite (src, dest,
+                             dest_x, dest_y,
+                             src_width, src_height,
+                             offset_x, offset_y,
+                             1, 1, GDK_INTERP_NEAREST,
+                             alpha * 0xFF + 0.5);
+}
+
+static void
+pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest)
+{
+       int x, y;
+       int tile_width, tile_height;
+       int dest_width = gdk_pixbuf_get_width (dest);
+       int dest_height = gdk_pixbuf_get_height (dest);
+       tile_width = gdk_pixbuf_get_width (src);
+       tile_height = gdk_pixbuf_get_height (src);
+       
+       for (y = 0; y < dest_height; y += tile_height) {
+               for (x = 0; x < dest_width; x += tile_width) {
+                       pixbuf_blend (src, dest, 0, 0,
+                                     tile_width, tile_height, x, y, 1.0);
+               }
+       }
+}
+
+static GnomeBGSlideShow *
+read_slideshow_file (const char *filename,
+                    GError     **err)
+{
+        GnomeBGSlideShow *show;
+
+        show = gnome_bg_slide_show_new (filename);
+
+        if (!gnome_bg_slide_show_load (show, err)) {
+            g_object_unref (show);
+            return NULL;
+        }
+
+        return show;
+}
+
+/* Thumbnail utilities */
+static GdkPixbuf *
+create_thumbnail_for_filename (GnomeDesktopThumbnailFactory *factory,
+                              const char            *filename)
+{
+       char *thumb;
+       time_t mtime;
+       GdkPixbuf *orig, *result = NULL;
+       char *uri;
+       
+       mtime = get_mtime (filename);
+       
+       if (mtime == (time_t)-1)
+               return NULL;
+       
+       uri = g_filename_to_uri (filename, NULL, NULL);
+       
+       if (uri == NULL)
+               return NULL;
+       
+       thumb = gnome_desktop_thumbnail_factory_lookup (factory, uri, mtime);
+       
+       if (thumb) {
+               result = gdk_pixbuf_new_from_file (thumb, NULL);
+               g_free (thumb);
+       }
+       else {
+               orig = gdk_pixbuf_new_from_file (filename, NULL);
+               if (orig) {
+                       int orig_width, orig_height;
+                       GdkPixbuf *rotated;
+
+                       rotated = gdk_pixbuf_apply_embedded_orientation (orig);
+                       if (rotated != NULL) {
+                               g_object_unref (orig);
+                               orig = rotated;
+                       }
+
+                       orig_width = gdk_pixbuf_get_width (orig);
+                       orig_height = gdk_pixbuf_get_height (orig);
+                       
+                       result = pixbuf_scale_to_fit (orig, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
+                       
+                       g_object_set_data_full (G_OBJECT (result), "gnome-thumbnail-height",
+                                               g_strdup_printf ("%d", orig_height), g_free);
+                       g_object_set_data_full (G_OBJECT (result), "gnome-thumbnail-width",
+                                               g_strdup_printf ("%d", orig_width), g_free);
+                       
+                       g_object_unref (orig);
+                       
+                       gnome_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime);
+               }
+               else {
+                       gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime);
+               }
+       }
+
+       g_free (uri);
+
+       return result;
+}
+
+static gboolean
+get_thumb_annotations (GdkPixbuf *thumb,
+                      int       *orig_width,
+                      int       *orig_height)
+{
+       char *end;
+       const char *wstr, *hstr;
+       
+       wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width");
+       hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height");
+       
+       if (hstr && wstr) {
+               *orig_width = strtol (wstr, &end, 10);
+               if (*end != 0)
+                       return FALSE;
+               
+               *orig_height = strtol (hstr, &end, 10);
+               if (*end != 0)
+                       return FALSE;
+               
+               return TRUE;
+       }
+       
+       return FALSE;
+}
+
+/*
+ * Returns whether the background is a slideshow.
+ */
+gboolean
+gnome_bg_changes_with_time (GnomeBG *bg)
+{
+       GnomeBGSlideShow *show;
+       gboolean ret = FALSE;
+
+       g_return_val_if_fail (bg != NULL, FALSE);
+
+       if (!bg->filename)
+               return FALSE;
+
+       show = get_as_slideshow (bg, bg->filename);
+       if (show) {
+               ret = gnome_bg_slide_show_get_num_slides (show) > 1;
+               g_object_unref (show);
+       }
+
+       return ret;
+}
+
+/**
+ * gnome_bg_create_frame_thumbnail:
+ *
+ * Creates a thumbnail for a certain frame, where 'frame' is somewhat
+ * vaguely defined as 'suitable point to show while single-stepping
+ * through the slideshow'.
+ *
+ * Returns: (transfer full): the newly created thumbnail or
+ * or NULL if frame_num is out of bounds.
+ */
+GdkPixbuf *
+gnome_bg_create_frame_thumbnail (GnomeBG                      *bg,
+                                 GnomeDesktopThumbnailFactory *factory,
+                                 const cairo_rectangle_int_t  *screen_area,
+                                 int                           dest_width,
+                                 int                           dest_height,
+                                 int                           frame_num)
+{
+       GnomeBGSlideShow *show;
+       GdkPixbuf *result;
+       GdkPixbuf *thumb;
+        int skipped;
+        gboolean is_fixed;
+
+       g_return_val_if_fail (bg != NULL, FALSE);
+
+       show = get_as_slideshow (bg, bg->filename);
+
+       if (!show)
+               return NULL;
+
+
+       if (frame_num < 0 || frame_num >= gnome_bg_slide_show_get_num_slides (show))
+               return NULL;
+
+
+        gnome_bg_slide_show_get_slide (show, frame_num, dest_width, dest_height, NULL, NULL, &is_fixed, 
NULL, NULL);
+
+       skipped = 0;
+        while (!is_fixed) {
+            skipped++;
+            gnome_bg_slide_show_get_slide (show, frame_num, dest_width, dest_height, NULL, NULL, &is_fixed, 
NULL, NULL);
+        }
+
+       result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+
+       draw_color (bg, result);
+
+       if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
+               thumb = create_img_thumbnail (bg, factory, screen_area, dest_width, dest_height, frame_num + 
skipped);
+
+               if (thumb) {
+                       draw_image_for_thumb (bg, thumb, result);
+                       g_object_unref (thumb);
+               }
+       }
+
+       return result;
+}
+
diff --git a/libgnome-desktop/gnome-bg/gnome-bg.h b/libgnome-desktop/gnome-bg/gnome-bg.h
new file mode 100644
index 00000000..e6770b79
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/gnome-bg.h
@@ -0,0 +1,89 @@
+/* gnome-bg.h: Desktop background API
+ *
+ * SPDX-FileCopyrightText: 2007, Red Hat, Inc.
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ *
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeBG is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-bg.h
+#endif
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gdesktop-enums.h>
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#include <gdesktop-enums.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_TYPE_BG                   (gnome_bg_get_type ())
+#define GNOME_BG(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_BG, GnomeBG))
+#define GNOME_BG_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass),  GNOME_TYPE_BG, GnomeBGClass))
+#define GNOME_IS_BG(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_BG))
+#define GNOME_IS_BG_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass),  GNOME_TYPE_BG))
+#define GNOME_BG_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj),  GNOME_TYPE_BG, GnomeBGClass))
+
+typedef struct _GnomeBG         GnomeBG;
+typedef struct _GnomeBGClass    GnomeBGClass;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GnomeBG, g_object_unref)
+
+GType            gnome_bg_get_type              (void);
+GnomeBG *        gnome_bg_new                   (void);
+void             gnome_bg_load_from_preferences (GnomeBG               *bg,
+                                                GSettings             *settings);
+void             gnome_bg_save_to_preferences   (GnomeBG               *bg,
+                                                GSettings             *settings);
+/* Setters */
+void             gnome_bg_set_filename          (GnomeBG               *bg,
+                                                const char            *filename);
+void             gnome_bg_set_placement         (GnomeBG               *bg,
+                                                GDesktopBackgroundStyle placement);
+void             gnome_bg_set_rgba              (GnomeBG               *bg,
+                                                GDesktopBackgroundShading type,
+                                                GdkRGBA               *primary,
+                                                GdkRGBA               *secondary);
+
+/* Getters */
+GDesktopBackgroundStyle gnome_bg_get_placement  (GnomeBG               *bg);
+void            gnome_bg_get_rgba              (GnomeBG               *bg,
+                                                GDesktopBackgroundShading *type,
+                                                GdkRGBA               *primary,
+                                                GdkRGBA               *secondary);
+const gchar *    gnome_bg_get_filename          (GnomeBG               *bg);
+
+/* Drawing and thumbnailing */
+void             gnome_bg_draw                  (GnomeBG               *bg,
+                                                GdkPixbuf             *dest);
+cairo_surface_t *gnome_bg_create_surface        (GnomeBG               *bg,
+                                                GdkSurface            *window,
+                                                int                    width,
+                                                int                    height);
+gboolean         gnome_bg_get_image_size        (GnomeBG               *bg,
+                                                GnomeDesktopThumbnailFactory *factory,
+                                                 int                    best_width,
+                                                 int                    best_height,
+                                                int                   *width,
+                                                int                   *height);
+GdkPixbuf *      gnome_bg_create_thumbnail      (GnomeBG               *bg,
+                                                GnomeDesktopThumbnailFactory *factory,
+                                                 const cairo_rectangle_int_t  *screen_area,
+                                                int                    dest_width,
+                                                int                    dest_height);
+gboolean         gnome_bg_is_dark               (GnomeBG               *bg,
+                                                 int                    dest_width,
+                                                int                    dest_height);
+gboolean         gnome_bg_has_multiple_sizes    (GnomeBG               *bg);
+gboolean         gnome_bg_changes_with_time     (GnomeBG               *bg);
+GdkPixbuf *      gnome_bg_create_frame_thumbnail (GnomeBG              *bg,
+                                                GnomeDesktopThumbnailFactory *factory,
+                                                 const cairo_rectangle_int_t  *screen_area,
+                                                int                    dest_width,
+                                                int                    dest_height,
+                                                int                    frame_num);
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-bg/meson.build b/libgnome-desktop/gnome-bg/meson.build
new file mode 100644
index 00000000..c93f99d4
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/meson.build
@@ -0,0 +1,69 @@
+### gnome-bg
+
+libgnome_bg_sources = [
+  'gnome-bg.c',
+  'gnome-bg-slide-show.c',
+]
+
+libgnome_bg_headers = [
+  'gnome-bg.h',
+  'gnome-bg-slide-show.h',
+]
+
+install_headers(libgnome_bg_headers,
+  subdir: 'gnome-desktop-4.0/gnome-bg'
+)
+
+libgnome_bg_deps = [
+  libgnome_desktop_base_dep,
+  gdk_pixbuf_dep,
+  gtk4_dep,
+]
+
+libgnome_bg = library('gnome-bg-4',
+  sources: libgnome_bg_sources,
+  dependencies: libgnome_bg_deps,
+  soversion: 0,
+  version: libversion,
+  c_args: libargs,
+  link_args: ui_ldflags,
+  install: true,
+  include_directories: [
+    include_directories('.'),
+    include_directories('..'),
+  ],
+)
+
+libgnome_bg_gir = gnome.generate_gir(libgnome_bg,
+  sources: [libgnome_bg_headers, libgnome_bg_sources],
+  export_packages: 'gnome-bg-4',
+  namespace: 'GnomeBG',
+  nsversion: '4.0',
+  includes: [libgnome_desktop_base_gir[0], 'GdkPixbuf-2.0', 'Gdk-4.0'],
+  extra_args: ['-DGNOME_DESKTOP_USE_UNSTABLE_API', '--quiet', '--warn-all'],
+  identifier_prefix: 'Gnome',
+  symbol_prefix: 'gnome',
+  install: true,
+)
+
+libgnome_bg_dep = declare_dependency(
+  sources: [
+    libgnome_bg_gir,
+  ],
+  dependencies: libgnome_bg_deps,
+  link_with: libgnome_bg,
+  include_directories: [
+    include_directories('.'),
+    include_directories('..'),
+  ],
+)
+
+pkg.generate(
+  libgnome_bg,
+  requires: ['gsettings-desktop-schemas'],
+  version: meson.project_version(),
+  name: 'gnome-bg-4',
+  filebase: 'gnome-bg-4',
+  description: 'Background image utility library for GNOME components',
+  subdirs: 'gnome-desktop-4.0',
+)
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-config.c b/libgnome-desktop/gnome-rr/gnome-rr-config.c
new file mode 100644
index 00000000..78914e42
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-config.c
@@ -0,0 +1,1152 @@
+/* gnome-rr-config.c: Display configuration
+ *
+ * SPDX-FileCopyrightText: 2007, 2008, 2013 Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2010 Giovanni Campagna <scampa giovanni gmail com>
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * 
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include "config.h"
+
+#include "gnome-rr-config.h"
+
+#include "gnome-rr-output-info.h"
+#include "gnome-rr-private.h"
+
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#define CONFIG_INTENDED_BASENAME "monitors.xml"
+#define CONFIG_BACKUP_BASENAME "monitors.xml.backup"
+
+/* Look for DPI_FALLBACK in:
+ * http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/xsettings/gsd-xsettings-manager.c
+ * for the reasoning */
+#define DPI_FALLBACK 96.0
+
+/* In version 0 of the config file format, we had several <configuration>
+ * toplevel elements and no explicit version number.  So, the filed looked
+ * like
+ *
+ *   <configuration>
+ *     ...
+ *   </configuration>
+ *   <configuration>
+ *     ...
+ *   </configuration>
+ *
+ * Since version 1 of the config file, the file has a toplevel <monitors>
+ * element to group all the configurations.  That element has a "version"
+ * attribute which is an integer. So, the file looks like this:
+ *
+ *   <monitors version="1">
+ *     <configuration>
+ *       ...
+ *     </configuration>
+ *     <configuration>
+ *       ...
+ *     </configuration>
+ *   </monitors>
+ */
+
+typedef struct CrtcAssignment CrtcAssignment;
+
+static gboolean         crtc_assignment_apply (CrtcAssignment   *assign,
+                                              gboolean          persistent,
+                                              GError          **error);
+static CrtcAssignment  *crtc_assignment_new   (GnomeRRScreen      *screen,
+                                              GnomeRROutputInfo **outputs,
+                                              GError            **error);
+static void             crtc_assignment_free  (CrtcAssignment   *assign);
+
+enum {
+  PROP_0,
+  PROP_SCREEN,
+  PROP_LAST
+};
+
+G_DEFINE_TYPE (GnomeRRConfig, gnome_rr_config, G_TYPE_OBJECT)
+
+static void
+gnome_rr_config_init (GnomeRRConfig *self)
+{
+    self->clone = FALSE;
+    self->screen = NULL;
+    self->outputs = NULL;
+}
+
+static void
+gnome_rr_config_set_property (GObject *gobject,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *property)
+{
+    GnomeRRConfig *self = GNOME_RR_CONFIG (gobject);
+
+    switch (property_id) {
+       case PROP_SCREEN:
+           self->screen = g_value_dup_object (value);
+           return;
+       default:
+           G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+    }
+}
+
+static void
+gnome_rr_config_finalize (GObject *gobject)
+{
+    GnomeRRConfig *self = GNOME_RR_CONFIG (gobject);
+
+    g_clear_object (&self->screen);
+
+    if (self->outputs) {
+        for (int i = 0; self->outputs[i] != NULL; i++) {
+           GnomeRROutputInfo *output = self->outputs[i];
+           g_object_unref (output);
+       }
+
+       g_free (self->outputs);
+    }
+
+    G_OBJECT_CLASS (gnome_rr_config_parent_class)->finalize (gobject);
+}
+
+gboolean
+gnome_rr_config_load_current (GnomeRRConfig *config,
+                              GError **error)
+{
+    GPtrArray *a;
+    GnomeRROutput **rr_outputs;
+    int clone_width = -1;
+    int clone_height = -1;
+    int last_x;
+
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (config), FALSE);
+
+    a = g_ptr_array_new ();
+    rr_outputs = gnome_rr_screen_list_outputs (config->screen);
+
+    config->clone = FALSE;
+    
+    for (int i = 0; rr_outputs[i] != NULL; ++i) {
+       GnomeRROutput *rr_output = rr_outputs[i];
+       GnomeRROutputInfo *output = g_object_new (GNOME_RR_TYPE_OUTPUT_INFO, NULL);
+       GnomeRRMode *mode = NULL;
+       GnomeRRCrtc *crtc;
+
+       output->name = g_strdup (gnome_rr_output_get_name (rr_output));
+       output->connected = TRUE;
+       output->display_name = g_strdup (gnome_rr_output_get_display_name (rr_output));
+       output->connector_type = g_strdup (gnome_rr_output_get_connector_type (rr_output));
+       output->config = config;
+       output->is_tiled = gnome_rr_output_get_tile_info (rr_output, &output->tile);
+       if (output->is_tiled) {
+            gnome_rr_output_get_tiled_display_size (rr_output,
+                                                    NULL,
+                                                    NULL,
+                                                    &output->total_tiled_width,
+                                                    &output->total_tiled_height);
+       }
+
+       if (!output->connected) {
+           output->x = -1;
+           output->y = -1;
+           output->width = -1;
+           output->height = -1;
+           output->rate = -1;
+       } else {
+           gnome_rr_output_get_ids_from_edid (rr_output,
+                                              &output->vendor,
+                                              &output->product,
+                                              &output->serial);
+               
+           crtc = gnome_rr_output_get_crtc (rr_output);
+           mode = crtc ? gnome_rr_crtc_get_current_mode (crtc) : NULL;
+
+           if (crtc && mode) {
+               output->active = TRUE;
+
+               gnome_rr_crtc_get_position (crtc, &output->x, &output->y);
+
+               output->width = gnome_rr_mode_get_width (mode);
+               output->height = gnome_rr_mode_get_height (mode);
+               output->rate = gnome_rr_mode_get_freq (mode);
+               output->rotation = gnome_rr_crtc_get_current_rotation (crtc);
+                output->available_rotations = gnome_rr_crtc_get_rotations (crtc);
+
+               if (output->x == 0 && output->y == 0) {
+                   if (clone_width == -1) {
+                       clone_width = output->width;
+                       clone_height = output->height;
+                   } else if (clone_width == output->width &&
+                              clone_height == output->height) {
+                       config->clone = TRUE;
+                   }
+               }
+           } else {
+               output->active = FALSE;
+               config->clone = FALSE;
+           }
+
+           /* Get preferred size for the monitor */
+           mode = gnome_rr_output_get_preferred_mode (rr_output);
+           output->pref_width = gnome_rr_mode_get_width (mode);
+           output->pref_height = gnome_rr_mode_get_height (mode);
+       }
+
+        output->primary = gnome_rr_output_get_is_primary (rr_output);
+        output->underscanning = gnome_rr_output_get_is_underscanning (rr_output);
+
+       g_ptr_array_add (a, output);
+    }
+
+    g_ptr_array_add (a, NULL);
+    
+    config->outputs = (GnomeRROutputInfo **) g_ptr_array_free (a, FALSE);
+
+    /* Walk the outputs computing the right-most edge of all
+     * lit-up displays
+     */
+    last_x = 0;
+    for (int i = 0; config->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *output = config->outputs[i];
+
+       if (output->active) {
+           last_x = MAX (last_x, output->x + output->width);
+       }
+    }
+
+    /* Now position all off displays to the right of the
+     * on displays
+     */
+    for (int i = 0; config->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *output = config->outputs[i];
+
+       if (output->connected && !output->active) {
+           output->x = last_x;
+           last_x = output->x + output->width;
+       }
+    }
+    
+    g_assert (gnome_rr_config_match (config, config));
+
+    return TRUE;
+}
+
+static void
+gnome_rr_config_class_init (GnomeRRConfigClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->set_property = gnome_rr_config_set_property;
+    gobject_class->finalize = gnome_rr_config_finalize;
+
+    g_object_class_install_property (gobject_class, PROP_SCREEN,
+                                    g_param_spec_object ("screen",
+                                                          "Screen",
+                                                          "The GnomeRRScreen this config applies to",
+                                                          GNOME_RR_TYPE_SCREEN,
+                                                         G_PARAM_WRITABLE |
+                                                          G_PARAM_CONSTRUCT_ONLY |
+                                                          G_PARAM_STATIC_NICK |
+                                                          G_PARAM_STATIC_BLURB));
+}
+
+GnomeRRConfig *
+gnome_rr_config_new_current (GnomeRRScreen *screen,
+                             GError **error)
+{
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+
+    GnomeRRConfig *self = g_object_new (GNOME_RR_TYPE_CONFIG, "screen", screen, NULL);
+
+    if (gnome_rr_config_load_current (self, error))
+      return self;
+    else
+      {
+       g_object_unref (self);
+       return NULL;
+      }
+}
+
+static gboolean
+output_match (GnomeRROutputInfo *output1,
+              GnomeRROutputInfo *output2)
+{
+    g_assert (GNOME_RR_IS_OUTPUT_INFO (output1));
+    g_assert (GNOME_RR_IS_OUTPUT_INFO (output2));
+
+    if (g_strcmp0 (output1->name, output2->name) != 0)
+       return FALSE;
+
+    if (g_strcmp0 (output1->vendor, output2->vendor) != 0)
+       return FALSE;
+
+    if (g_strcmp0 (output1->product, output2->product) != 0)
+       return FALSE;
+
+    if (g_strcmp0 (output1->serial, output2->serial) != 0)
+       return FALSE;
+    
+    return TRUE;
+}
+
+static gboolean
+output_equal (GnomeRROutputInfo *output1,
+              GnomeRROutputInfo *output2)
+{
+    g_assert (GNOME_RR_IS_OUTPUT_INFO (output1));
+    g_assert (GNOME_RR_IS_OUTPUT_INFO (output2));
+
+    if (!output_match (output1, output2))
+       return FALSE;
+
+    if (output1->active != output2->active)
+       return FALSE;
+
+    if (output1->active) {
+       if (output1->width != output2->width)
+           return FALSE;
+       
+       if (output1->height != output2->height)
+           return FALSE;
+       
+       if (output1->rate != output2->rate)
+           return FALSE;
+       
+       if (output1->x != output2->x)
+           return FALSE;
+       
+       if (output1->y != output2->y)
+           return FALSE;
+       
+       if (output1->rotation != output2->rotation)
+           return FALSE;
+
+       if (output1->underscanning != output2->underscanning)
+           return FALSE;
+    }
+
+    return TRUE;
+}
+
+static GnomeRROutputInfo *
+find_output (GnomeRRConfig *config,
+             const char *name)
+{
+    for (int i = 0; config->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *output = config->outputs[i];
+       
+       if (strcmp (name, output->name) == 0)
+           return output;
+    }
+
+    return NULL;
+}
+
+/* Match means "these configurations apply to the same hardware
+ * setups"
+ */
+gboolean
+gnome_rr_config_match (GnomeRRConfig *c1,
+                       GnomeRRConfig *c2)
+{
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (c1), FALSE);
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (c2), FALSE);
+
+    for (int i = 0; c1->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *output1 = c1->outputs[i];
+       GnomeRROutputInfo *output2;
+
+       output2 = find_output (c2, output1->name);
+       if (!output2 || !output_match (output1, output2))
+           return FALSE;
+    }
+    
+    return TRUE;
+}
+
+/* Equal means "the configurations will result in the same
+ * modes being set on the outputs"
+ */
+gboolean
+gnome_rr_config_equal (GnomeRRConfig *c1,
+                      GnomeRRConfig *c2)
+{
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (c1), FALSE);
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (c2), FALSE);
+
+    for (int i = 0; c1->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *output1 = c1->outputs[i];
+       GnomeRROutputInfo *output2;
+
+       output2 = find_output (c2, output1->name);
+       if (!output2 || !output_equal (output1, output2))
+           return FALSE;
+    }
+    
+    return TRUE;
+}
+
+static GnomeRROutputInfo **
+make_outputs (GnomeRRConfig *config)
+{
+    GPtrArray *outputs;
+    GnomeRROutputInfo *first_active = NULL;
+
+    outputs = g_ptr_array_new ();
+
+    for (int i = 0; config->outputs[i] != NULL; ++i) {
+       GnomeRROutputInfo *old = config->outputs[i];
+       GnomeRROutputInfo *new = g_object_new (GNOME_RR_TYPE_OUTPUT_INFO, NULL);
+       *(new) = *(old);
+
+        new->name = g_strdup (old->name);
+        new->display_name = g_strdup (old->display_name);
+        new->connector_type = g_strdup (old->connector_type);
+        new->vendor = g_strdup (old->vendor);
+        new->product = g_strdup (old->product);
+        new->serial = g_strdup (old->serial);
+
+       if (old->active && !first_active)
+           first_active = old;
+       
+       if (config->clone && new->active) {
+           g_assert (first_active);
+
+           new->width = first_active->width;
+           new->height = first_active->height;
+           new->rotation = first_active->rotation;
+           new->x = 0;
+           new->y = 0;
+       }
+
+       g_ptr_array_add (outputs, new);
+    }
+
+    g_ptr_array_add (outputs, NULL);
+
+    return (GnomeRROutputInfo **) g_ptr_array_free (outputs, FALSE);
+}
+
+gboolean
+gnome_rr_config_applicable (GnomeRRConfig  *configuration,
+                           GnomeRRScreen  *screen,
+                           GError        **error)
+{
+    GnomeRROutputInfo **outputs;
+    CrtcAssignment *assign;
+    gboolean result;
+
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (configuration), FALSE);
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    outputs = make_outputs (configuration);
+    assign = crtc_assignment_new (screen, outputs, error);
+
+    if (assign) {
+       result = TRUE;
+       crtc_assignment_free (assign);
+    } else {
+       result = FALSE;
+    }
+
+    for (int i = 0; outputs[i] != NULL; i++) {
+        g_object_unref (outputs[i]);
+    }
+
+    g_free (outputs);
+
+    return result;
+}
+
+/* Database management */
+
+void
+gnome_rr_config_sanitize (GnomeRRConfig *config)
+{
+    int x_offset, y_offset;
+
+    /* Offset everything by the top/left-most coordinate to
+     * make sure the configuration starts at (0, 0)
+     */
+    x_offset = y_offset = G_MAXINT;
+    for (int i = 0; config->outputs[i]; ++i)
+    {
+       GnomeRROutputInfo *output = config->outputs[i];
+
+       if (output->active) {
+           x_offset = MIN (x_offset, output->x);
+           y_offset = MIN (y_offset, output->y);
+       }
+    }
+
+    for (int i = 0; config->outputs[i]; ++i) {
+       GnomeRROutputInfo *output = config->outputs[i];
+       
+       if (output->active) {
+           output->x -= x_offset;
+           output->y -= y_offset;
+       }
+    }
+
+    /* Only one primary, please */
+    gboolean found = FALSE;
+    for (int i = 0; config->outputs[i]; ++i) {
+        if (config->outputs[i]->primary) {
+            if (found) {
+                config->outputs[i]->primary = FALSE;
+            } else {
+                found = TRUE;
+            }
+        }
+    }
+}
+
+gboolean
+gnome_rr_config_ensure_primary (GnomeRRConfig *self)
+{
+    int i;
+    GnomeRROutputInfo *builtin_display;
+    GnomeRROutputInfo *top_left;
+    gboolean found;
+
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (self), FALSE);
+
+    builtin_display = NULL;
+    top_left = NULL;
+    found = FALSE;
+
+    for (i = 0; self->outputs[i] != NULL; ++i) {
+            GnomeRROutputInfo *info = self->outputs[i];
+
+            if (!info->active) {
+                   info->primary = FALSE;
+                   continue;
+            }
+
+            /* ensure only one */
+            if (info->primary) {
+                    if (found) {
+                            info->primary = FALSE;
+                    } else {
+                            found = TRUE;
+                    }
+            }
+
+            if (top_left == NULL
+                || (info->x < top_left->x
+                    && info->y < top_left->y)) {
+                    top_left = info;
+            }
+            if (builtin_display == NULL
+                && gnome_rr_output_connector_type_is_builtin_display (info->connector_type)) {
+                    builtin_display = info;
+            }
+    }
+
+    if (!found) {
+            if (builtin_display != NULL) {
+                    builtin_display->primary = TRUE;
+            } else if (top_left != NULL) {
+                    /* Note: top_left can be NULL if all outputs are off */
+                    top_left->primary = TRUE;
+            }
+    }
+
+    return !found;
+}
+
+static gboolean
+gnome_rr_config_apply_helper (GnomeRRConfig *config,
+                             GnomeRRScreen *screen,
+                             gboolean       persistent,
+                             GError       **error)
+{
+    CrtcAssignment *assignment;
+    GnomeRROutputInfo **outputs;
+    gboolean result = FALSE;
+    int i;
+
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (config), FALSE);
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), FALSE);
+
+    outputs = make_outputs (config);
+
+    assignment = crtc_assignment_new (screen, outputs, error);
+
+    if (assignment)
+    {
+       if (crtc_assignment_apply (assignment, persistent, error))
+           result = TRUE;
+
+       crtc_assignment_free (assignment);
+    }
+
+    for (i = 0; outputs[i] != NULL; i++)
+       g_object_unref (outputs[i]);
+    g_free (outputs);
+
+    return result;
+}
+
+gboolean
+gnome_rr_config_apply (GnomeRRConfig *config,
+                      GnomeRRScreen *screen,
+                      GError       **error)
+{
+    return gnome_rr_config_apply_helper (config, screen, FALSE, error);
+}
+
+gboolean
+gnome_rr_config_apply_persistent (GnomeRRConfig *config,
+                                 GnomeRRScreen *screen,
+                                 GError       **error)
+{
+    return gnome_rr_config_apply_helper (config, screen, TRUE, error);
+}
+
+/**
+ * gnome_rr_config_get_outputs:
+ *
+ * Returns: (array zero-terminated=1) (element-type GnomeRROutputInfo) (transfer none): the output 
configuration for this #GnomeRRConfig
+ */
+GnomeRROutputInfo **
+gnome_rr_config_get_outputs (GnomeRRConfig *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (self), NULL);
+
+    return self->outputs;
+}
+
+/**
+ * gnome_rr_config_get_clone:
+ *
+ * Returns: whether at least two outputs are at (0, 0) offset and they
+ * have the same width/height.  Those outputs are of course connected and on
+ * (i.e. they have a CRTC assigned).
+ */
+gboolean
+gnome_rr_config_get_clone (GnomeRRConfig *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_CONFIG (self), FALSE);
+
+    return self->clone;
+}
+
+void
+gnome_rr_config_set_clone (GnomeRRConfig *self, gboolean clone)
+{
+    g_return_if_fail (GNOME_RR_IS_CONFIG (self));
+
+    self->clone = clone;
+}
+
+/*
+ * CRTC assignment
+ */
+typedef struct CrtcInfo CrtcInfo;
+
+struct CrtcInfo
+{
+    GnomeRRMode    *mode;
+    int        x;
+    int        y;
+    GnomeRRRotation rotation;
+    GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+    GnomeRROutputInfo **outputs;
+    GnomeRRScreen *screen;
+    GHashTable *info;
+    GnomeRROutput *primary;
+};
+
+static gboolean
+can_clone (CrtcInfo *info,
+          GnomeRROutput *output)
+{
+    guint i;
+
+    for (i = 0; i < info->outputs->len; ++i)
+    {
+       GnomeRROutput *clone = info->outputs->pdata[i];
+
+       if (!gnome_rr_output_can_clone (clone, output))
+           return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment   *assign,
+                       GnomeRRCrtc      *crtc,
+                       GnomeRRMode      *mode,
+                       int               x,
+                       int               y,
+                       GnomeRRRotation   rotation,
+                        gboolean          primary,
+                       GnomeRROutput    *output,
+                       GError          **error)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+    guint32 crtc_id;
+    const char *output_name;
+
+    crtc_id = gnome_rr_crtc_get_id (crtc);
+    output_name = gnome_rr_output_get_name (output);
+
+    if (!gnome_rr_crtc_can_drive_output (crtc, output))
+    {
+       g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                    _("CRTC %d cannot drive output %s"), crtc_id, output_name);
+       return FALSE;
+    }
+
+    if (!gnome_rr_output_supports_mode (output, mode))
+    {
+       g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                    _("output %s does not support mode %dx%d@%dHz"),
+                    output_name,
+                    gnome_rr_mode_get_width (mode),
+                    gnome_rr_mode_get_height (mode),
+                    gnome_rr_mode_get_freq (mode));
+       return FALSE;
+    }
+
+    if (!gnome_rr_crtc_supports_rotation (crtc, rotation))
+    {
+       g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                    _("CRTC %d does not support rotation=%d"),
+                    crtc_id, rotation);
+       return FALSE;
+    }
+
+    if (info)
+    {
+       if (!(info->mode == mode        &&
+             info->x == x              &&
+             info->y == y              &&
+             info->rotation == rotation))
+       {
+           g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                        _("output %s does not have the same parameters as another cloned output:\n"
+                          "existing mode = %d, new mode = %d\n"
+                          "existing coordinates = (%d, %d), new coordinates = (%d, %d)\n"
+                          "existing rotation = %d, new rotation = %d"),
+                        output_name,
+                        gnome_rr_mode_get_id (info->mode), gnome_rr_mode_get_id (mode),
+                        info->x, info->y,
+                        x, y,
+                        info->rotation, rotation);
+           return FALSE;
+       }
+
+       if (!can_clone (info, output))
+       {
+           g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                        _("cannot clone to output %s"),
+                        output_name);
+           return FALSE;
+       }
+
+       g_ptr_array_add (info->outputs, output);
+
+       if (primary && !assign->primary)
+       {
+           assign->primary = output;
+       }
+
+       return TRUE;
+    }
+    else
+    {  
+       info = g_new0 (CrtcInfo, 1);
+       
+       info->mode = mode;
+       info->x = x;
+       info->y = y;
+       info->rotation = rotation;
+       info->outputs = g_ptr_array_new ();
+       
+       g_ptr_array_add (info->outputs, output);
+       
+       g_hash_table_insert (assign->info, crtc, info);
+           
+        if (primary && !assign->primary)
+        {
+            assign->primary = output;
+        }
+
+       return TRUE;
+    }
+}
+
+static void
+crtc_assignment_unassign (CrtcAssignment *assign,
+                         GnomeRRCrtc         *crtc,
+                         GnomeRROutput       *output)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+    if (info)
+    {
+       g_ptr_array_remove (info->outputs, output);
+
+        if (assign->primary == output)
+        {
+            assign->primary = NULL;
+        }
+
+       if (info->outputs->len == 0)
+           g_hash_table_remove (assign->info, crtc);
+    }
+}
+
+static void
+crtc_assignment_free (CrtcAssignment *assign)
+{
+    g_hash_table_destroy (assign->info);
+
+    g_free (assign);
+}
+
+static gboolean
+mode_is_rotated (CrtcInfo *info)
+{
+    if ((info->rotation & GNOME_RR_ROTATION_270)               ||
+       (info->rotation & GNOME_RR_ROTATION_90))
+    {
+       return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+accumulate_error (GString *accumulated_error, GError *error)
+{
+    g_string_append_printf (accumulated_error, "    %s\n", error->message);
+    g_error_free (error);
+}
+
+/* Check whether the given set of settings can be used
+ * at the same time -- ie. whether there is an assignment
+ * of CRTC's to outputs.
+ *
+ * Brute force - the number of objects involved is small
+ * enough that it doesn't matter.
+ */
+static gboolean
+real_assign_crtcs (GnomeRRScreen *screen,
+                  GnomeRROutputInfo **outputs,
+                  CrtcAssignment *assignment,
+                  GError **error)
+{
+    GnomeRRCrtc **crtcs = gnome_rr_screen_list_crtcs (screen);
+    GnomeRROutputInfo *output;
+    int i;
+    gboolean tried_mode;
+    GError *my_error;
+    GString *accumulated_error;
+    gboolean success;
+
+    output = *outputs;
+    if (!output)
+       return TRUE;
+
+    /* It is always allowed for an output to be turned off */
+    if (!output->active) {
+       return real_assign_crtcs (screen, outputs + 1, assignment, error);
+    }
+
+    success = FALSE;
+    tried_mode = FALSE;
+    accumulated_error = g_string_new (NULL);
+
+    for (i = 0; crtcs[i] != NULL; ++i)
+    {
+       GnomeRRCrtc *crtc = crtcs[i];
+       int crtc_id = gnome_rr_crtc_get_id (crtc);
+       int pass;
+
+       g_string_append_printf (accumulated_error,
+                               _("Trying modes for CRTC %d\n"),
+                               crtc_id);
+
+       /* Make two passes, one where frequencies must match, then
+        * one where they don't have to
+        */
+       for (pass = 0; pass < 2; ++pass)
+       {
+           GnomeRROutput *gnome_rr_output = gnome_rr_screen_get_output_by_name (screen, output->name);
+           GnomeRRMode **modes = gnome_rr_output_list_modes (gnome_rr_output);
+           int j;
+
+           for (j = 0; modes[j] != NULL; ++j)
+           {
+               GnomeRRMode *mode = modes[j];
+               int mode_width;
+               int mode_height;
+               int mode_freq;
+
+               mode_width = gnome_rr_mode_get_width (mode);
+               mode_height = gnome_rr_mode_get_height (mode);
+               mode_freq = gnome_rr_mode_get_freq (mode);
+
+               g_string_append_printf (accumulated_error,
+                                       _("CRTC %d: trying mode %dx%d@%dHz with output at %dx%d@%dHz (pass 
%d)\n"),
+                                       crtc_id,
+                                       mode_width, mode_height, mode_freq,
+                                       output->width, output->height, output->rate,
+                                       pass);
+
+               if (mode_width == output->width &&
+                   mode_height == output->height &&
+                   (pass == 1 || mode_freq == output->rate))
+               {
+                   tried_mode = TRUE;
+
+                   my_error = NULL;
+                   if (crtc_assignment_assign (
+                           assignment, crtc, modes[j],
+                           output->x, output->y,
+                           output->rotation,
+                            output->primary,
+                           gnome_rr_output,
+                           &my_error))
+                   {
+                       my_error = NULL;
+                       if (real_assign_crtcs (screen, outputs + 1, assignment, &my_error)) {
+                           success = TRUE;
+                           goto out;
+                       } else
+                           accumulate_error (accumulated_error, my_error);
+
+                       crtc_assignment_unassign (assignment, crtc, gnome_rr_output);
+                   } else
+                       accumulate_error (accumulated_error, my_error);
+               }
+           }
+       }
+    }
+
+out:
+
+    if (success)
+       g_string_free (accumulated_error, TRUE);
+    else {
+       char *str;
+
+       str = g_string_free (accumulated_error, FALSE);
+
+       if (tried_mode)
+           g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                        _("could not assign CRTCs to outputs:\n%s"),
+                        str);
+       else
+           g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+                        _("none of the selected modes were compatible with the possible modes:\n%s"),
+                        str);
+
+       g_free (str);
+    }
+
+    return success;
+}
+
+static void
+crtc_info_free (CrtcInfo *info)
+{
+    g_ptr_array_free (info->outputs, TRUE);
+    g_free (info);
+}
+
+static void
+get_required_virtual_size (CrtcAssignment *assign, int *width, int *height)
+{
+    GList *active_crtcs = g_hash_table_get_keys (assign->info);
+    GList *list;
+    int d;
+
+    if (!width)
+       width = &d;
+    if (!height)
+       height = &d;
+    
+    /* Compute size of the screen */
+    *width = *height = 1;
+    for (list = active_crtcs; list != NULL; list = list->next)
+    {
+       GnomeRRCrtc *crtc = list->data;
+       CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+       int w, h;
+
+       w = gnome_rr_mode_get_width (info->mode);
+       h = gnome_rr_mode_get_height (info->mode);
+       
+       if (mode_is_rotated (info))
+       {
+           int tmp = h;
+           h = w;
+           w = tmp;
+       }
+       
+       *width = MAX (*width, info->x + w);
+       *height = MAX (*height, info->y + h);
+    }
+
+    g_list_free (active_crtcs);
+}
+
+static CrtcAssignment *
+crtc_assignment_new (GnomeRRScreen      *screen,
+                    GnomeRROutputInfo **outputs,
+                    GError            **error)
+{
+    CrtcAssignment *assignment = g_new0 (CrtcAssignment, 1);
+
+    assignment->outputs = outputs;
+    assignment->info = g_hash_table_new_full (
+       g_direct_hash, g_direct_equal, NULL, (GFreeFunc)crtc_info_free);
+
+    if (real_assign_crtcs (screen, outputs, assignment, error))
+    {
+       int width, height;
+       int min_width, max_width, min_height, max_height;
+
+       get_required_virtual_size (assignment, &width, &height);
+
+       gnome_rr_screen_get_ranges (
+           screen, &min_width, &max_width, &min_height, &max_height);
+    
+       if (width < min_width || width > max_width ||
+           height < min_height || height > max_height)
+       {
+           g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_BOUNDS_ERROR,
+                        /* Translators: the "requested", "minimum", and
+                         * "maximum" words here are not keywords; please
+                         * translate them as usual. */
+                        _("required virtual size does not fit available size: "
+                          "requested=(%d, %d), minimum=(%d, %d), maximum=(%d, %d)"),
+                        width, height,
+                        min_width, min_height,
+                        max_width, max_height);
+           goto fail;
+       }
+
+       assignment->screen = screen;
+       
+       return assignment;
+    }
+
+fail:
+    crtc_assignment_free (assignment);
+    
+    return NULL;
+}
+
+#define ROTATION_MASK 0x7F
+
+static enum wl_output_transform
+rotation_to_transform (GnomeRRRotation rotation)
+{
+    static const enum wl_output_transform y_reflected_map[] = {
+        WL_OUTPUT_TRANSFORM_FLIPPED_180,
+        WL_OUTPUT_TRANSFORM_FLIPPED_90,
+        WL_OUTPUT_TRANSFORM_FLIPPED,
+        WL_OUTPUT_TRANSFORM_FLIPPED_270
+    };
+
+    enum wl_output_transform ret;
+
+    switch (rotation & ROTATION_MASK) {
+        default:
+        case GNOME_RR_ROTATION_0:
+            ret = WL_OUTPUT_TRANSFORM_NORMAL;
+            break;
+        case GNOME_RR_ROTATION_90:
+            ret = WL_OUTPUT_TRANSFORM_90;
+            break;
+        case GNOME_RR_ROTATION_180:
+            ret = WL_OUTPUT_TRANSFORM_180;
+            break;
+        case GNOME_RR_ROTATION_270:
+            ret = WL_OUTPUT_TRANSFORM_270;
+            break;
+    }
+
+    if (rotation & GNOME_RR_REFLECT_X) {
+        return ret + 4;
+    } else if (rotation & GNOME_RR_REFLECT_Y) {
+        return y_reflected_map[ret];
+    } else {
+        return ret;
+    }
+}
+
+static gboolean
+crtc_assignment_apply (CrtcAssignment *assign,
+                       gboolean persistent,
+                       GError **error)
+{
+    GVariantBuilder crtc_builder, output_builder, nested_outputs;
+    GHashTableIter iter;
+    GnomeRRCrtc *crtc;
+    CrtcInfo *info;
+
+    g_variant_builder_init (&crtc_builder, G_VARIANT_TYPE ("a(uiiiuaua{sv})"));
+    g_variant_builder_init (&output_builder, G_VARIANT_TYPE ("a(ua{sv})"));
+
+    g_hash_table_iter_init (&iter, assign->info);
+    while (g_hash_table_iter_next (&iter, (gpointer) &crtc, (gpointer) &info)) {
+       g_variant_builder_init (&nested_outputs, G_VARIANT_TYPE ("au"));
+
+       for (unsigned int i = 0; i < info->outputs->len; i++) {
+           GnomeRROutput *output = g_ptr_array_index (info->outputs, i);
+
+           g_variant_builder_add (&nested_outputs, "u",
+                                  gnome_rr_output_get_id (output));
+       }
+
+       g_variant_builder_add (&crtc_builder, "(uiiiuaua{sv})",
+                              gnome_rr_crtc_get_id (crtc),
+                              info->mode ?
+                              gnome_rr_mode_get_id (info->mode) : (guint32) -1,
+                              info->x,
+                              info->y,
+                              rotation_to_transform (info->rotation),
+                              &nested_outputs,
+                              NULL);
+    }
+
+    for (unsigned int i = 0; assign->outputs[i]; i++) {
+       GnomeRROutputInfo *output = assign->outputs[i];
+       GnomeRROutput *gnome_rr_output = gnome_rr_screen_get_output_by_name (assign->screen,
+                                                                            output->name);
+
+       g_variant_builder_add (&output_builder, "(u@a{sv})",
+                              gnome_rr_output_get_id (gnome_rr_output),
+                              g_variant_new_parsed ("{ 'primary': <%b>,"
+                                                    "  'presentation': <%b>,"
+                                                    "  'underscanning': <%b> }",
+                                                    output->primary,
+                                                    FALSE,
+                                                    output->underscanning));
+    }
+
+    return gnome_rr_screen_apply_configuration (assign->screen,
+                                               persistent,
+                                               g_variant_builder_end (&crtc_builder),
+                                               g_variant_builder_end (&output_builder),
+                                               error);
+}
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-config.h b/libgnome-desktop/gnome-rr/gnome-rr-config.h
new file mode 100644
index 00000000..592d6a49
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-config.h
@@ -0,0 +1,52 @@
+/* gnome-rr-config.h: Display configuration
+ *
+ * SPDX-FileCopyrightText: 2007, 2008, Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2010 Giovanni Campagna
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * 
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-rr/gnome-rr.h
+#endif
+
+#include <gnome-rr/gnome-rr-types.h>
+#include <gnome-rr/gnome-rr-output-info.h>
+#include <gnome-rr/gnome-rr-screen.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_RR_TYPE_CONFIG (gnome_rr_config_get_type())
+
+G_DECLARE_FINAL_TYPE (GnomeRRConfig, gnome_rr_config, GNOME_RR, CONFIG, GObject)
+
+GnomeRRConfig *         gnome_rr_config_new_current             (GnomeRRScreen  *screen,
+                                                                GError        **error);
+gboolean                gnome_rr_config_load_current            (GnomeRRConfig  *self,
+                                                                GError        **error);
+gboolean                gnome_rr_config_match                   (GnomeRRConfig  *config1,
+                                                                GnomeRRConfig  *config2);
+gboolean                gnome_rr_config_equal                  (GnomeRRConfig  *config1,
+                                                                GnomeRRConfig  *config2);
+void                    gnome_rr_config_sanitize                (GnomeRRConfig  *self);
+gboolean                gnome_rr_config_ensure_primary          (GnomeRRConfig  *self);
+
+gboolean               gnome_rr_config_apply                   (GnomeRRConfig  *self,
+                                                                GnomeRRScreen  *screen,
+                                                                GError        **error);
+gboolean               gnome_rr_config_apply_persistent        (GnomeRRConfig  *self,
+                                                                GnomeRRScreen  *screen,
+                                                                GError        **error);
+
+gboolean                gnome_rr_config_applicable              (GnomeRRConfig  *self,
+                                                                GnomeRRScreen  *screen,
+                                                                GError        **error);
+
+gboolean                gnome_rr_config_get_clone               (GnomeRRConfig  *self);
+void                    gnome_rr_config_set_clone               (GnomeRRConfig  *self,
+                                                                 gboolean        clone);
+GnomeRROutputInfo **    gnome_rr_config_get_outputs             (GnomeRRConfig  *self);
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-output-info.c 
b/libgnome-desktop/gnome-rr/gnome-rr-output-info.c
new file mode 100644
index 00000000..051f8f47
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-output-info.c
@@ -0,0 +1,595 @@
+/* gnome-rr-output-info.c
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This file is part of the Gnome Desktop Library.
+ *
+ * The Gnome Desktop Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Desktop Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include "config.h"
+
+#include "gnome-rr-output-info.h"
+
+#include "gnome-rr-config.h"
+#include "gnome-rr-private.h"
+#include "gnome-rr-screen.h"
+
+G_DEFINE_TYPE (GnomeRROutputInfo, gnome_rr_output_info, G_TYPE_OBJECT)
+
+static void
+gnome_rr_output_info_finalize (GObject *gobject)
+{
+    GnomeRROutputInfo *self = GNOME_RR_OUTPUT_INFO (gobject);
+
+    g_free (self->name);
+    g_free (self->display_name);
+    g_free (self->connector_type);
+    g_free (self->product);
+    g_free (self->serial);
+    g_free (self->vendor);
+
+    G_OBJECT_CLASS (gnome_rr_output_info_parent_class)->finalize (gobject);
+}
+
+static void
+gnome_rr_output_info_class_init (GnomeRROutputInfoClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize = gnome_rr_output_info_finalize;
+}
+
+static void
+gnome_rr_output_info_init (GnomeRROutputInfo *self)
+{
+    self->name = NULL;
+    self->active = FALSE;
+    self->rotation = GNOME_RR_ROTATION_0;
+    self->display_name = NULL;
+    self->connector_type = NULL;
+}
+
+/**
+ * gnome_rr_output_info_get_name:
+ * @self: the output information
+ *
+ * Retrieves the output name.
+ *
+ * Returns: (transfer none): the output name
+ */
+const char *
+gnome_rr_output_info_get_name (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), NULL);
+
+    return self->name;
+}
+
+/**
+ * gnome_rr_output_info_is_active:
+ *
+ * Returns: whether there is a CRTC assigned to this output (i.e. a signal is being sent to it)
+ */
+gboolean
+gnome_rr_output_info_is_active (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    return self->active;
+}
+
+void
+gnome_rr_output_info_set_active (GnomeRROutputInfo *self, gboolean active)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    active = !!active;
+    if (self->active != active) {
+        self->active = active;
+    }
+}
+
+static void
+gnome_rr_output_info_get_tiled_geometry (GnomeRROutputInfo *self,
+                                         int *x,
+                                         int *y,
+                                         int *width,
+                                         int *height)
+{
+    GnomeRROutputInfo **outputs;
+    gboolean active;
+    int i;
+    guint ht, vt;
+    int total_w = 0, total_h = 0;
+
+    outputs = gnome_rr_config_get_outputs (self->config);
+
+    /*
+     * iterate over all the outputs from 0,0 -> h,v
+     * find the output for each tile,
+     * if it is the 0 tile, store the x/y offsets.
+     * if the tile is active, add the tile to the total w/h
+     * for the output if the tile is in the 0 row or 0 column.
+     */
+    for (ht = 0; ht < self->tile.max_horiz_tiles; ht++)
+    {
+        for (vt = 0; vt < self->tile.max_vert_tiles; vt++)
+        {
+            for (i = 0; outputs[i]; i++)
+            {
+                GnomeRRTile *this_tile = &outputs[i]->tile;
+
+                if (!outputs[i]->is_tiled)
+                    continue;
+
+                if (this_tile->group_id != self->tile.group_id)
+                    continue;
+
+                if (this_tile->loc_horiz != ht ||
+                    this_tile->loc_vert != vt)
+                    continue;
+
+                if (vt == 0 && ht == 0)
+                {
+                    if (x)
+                        *x = outputs[i]->x;
+                    if (y)
+                        *y = outputs[i]->y;
+                }
+
+                active = gnome_rr_output_info_is_active (outputs[i]);
+                if (!active)
+                    continue;
+
+                if (this_tile->loc_horiz == 0)
+                    total_h += outputs[i]->height;
+
+                if (this_tile->loc_vert == 0)
+                    total_w += outputs[i]->width;
+            }
+        }
+    }
+
+    if (width)
+        *width = total_w;
+    if (height)
+        *height = total_h;
+}
+
+/**
+ * gnome_rr_output_info_get_geometry:
+ * @self: a #GnomeRROutputInfo
+ * @x: (out) (optional):
+ * @y: (out) (optional):
+ * @width: (out) (optional):
+ * @height: (out) (optional):
+ *
+ * Get the geometry for the monitor connected to the specified output.
+ *
+ * If the monitor is a tiled monitor, it returns the geometry for the complete monitor.
+ */
+void
+gnome_rr_output_info_get_geometry (GnomeRROutputInfo *self,
+                                   int *x,
+                                   int *y,
+                                   int *width,
+                                   int *height)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    if (self->is_tiled) {
+        gnome_rr_output_info_get_tiled_geometry (self, x, y, width, height);
+        return;
+    }
+
+    if (x)
+       *x = self->x;
+    if (y)
+       *y = self->y;
+    if (width)
+       *width = self->width;
+    if (height)
+       *height = self->height;
+}
+
+static void
+gnome_rr_output_info_set_tiled_geometry (GnomeRROutputInfo *self,
+                                         int x,
+                                         int y,
+                                         int width,
+                                         int height)
+{
+    GnomeRROutputInfo **outputs;
+    gboolean primary_tile_only = FALSE;
+    guint ht, vt;
+    int i, x_off;
+
+    primary_tile_only = TRUE;
+
+    if (width == self->total_tiled_width &&
+        height == self->total_tiled_height)
+        primary_tile_only = FALSE;
+
+    outputs = gnome_rr_config_get_outputs (self->config);
+    /*
+     * iterate over all the outputs from 0,0 -> h,v
+     * find the output for each tile,
+     * if only the primary tile is being set, disable
+     * the non-primary tiles, and set the output up
+     * for tile 0 only.
+     * if all tiles are being set, then store the
+     * dimensions for this tile, and increase the offsets.
+     * y_off is reset per column of tiles,
+     * addx is incremented for the first row of tiles
+     * to set the correct x offset.
+     */
+    x_off = 0;
+    for (ht = 0; ht < self->tile.max_horiz_tiles; ht++)
+    {
+        int y_off = 0;
+        int addx = 0;
+        for (vt = 0; vt < self->tile.max_vert_tiles; vt++)
+        {
+            for (i = 0; outputs[i]; i++)
+            {
+                GnomeRRTile *this_tile = &outputs[i]->tile;
+
+                if (!outputs[i]->is_tiled)
+                    continue;
+
+                if (this_tile->group_id != self->tile.group_id)
+                    continue;
+
+                if (this_tile->loc_horiz != ht ||
+                    this_tile->loc_vert != vt)
+                    continue;
+
+                /* for primary tile only configs turn off non-primary
+                   tiles - turn them on for tiled ones */
+                if (ht != 0 || vt != 0)
+                {
+                    if (!self->active)
+                        outputs[i]->active = FALSE;
+                    else
+                        outputs[i]->active = !primary_tile_only;
+                }
+
+                if (primary_tile_only)
+                {
+                        if (ht == 0 && vt == 0)
+                        {
+                            outputs[i]->x = x;
+                            outputs[i]->y = y;
+                            outputs[i]->width = width;
+                            outputs[i]->height = height;
+                        }
+                }
+                else
+                {
+                    outputs[i]->x = x + x_off;
+                    outputs[i]->y = y + y_off;
+                    outputs[i]->width = this_tile->width;
+                    outputs[i]->height = this_tile->height;
+
+                    y_off += this_tile->height;
+                    if (vt == 0)
+                        addx = this_tile->width;
+                }
+            }
+        }
+        x_off += addx;
+    }
+}
+
+/**
+ * gnome_rr_output_info_set_geometry:
+ * @self: a #GnomeRROutputInfo
+ * @x: x offset for monitor
+ * @y: y offset for monitor
+ * @width: monitor width
+ * @height: monitor height
+ *
+ * Set the geometry for the monitor connected to the specified output.
+ *
+ * If the monitor is a tiled monitor, it sets the geometry for the complete monitor.
+ */
+
+void
+gnome_rr_output_info_set_geometry (GnomeRROutputInfo *self,
+                                   int x,
+                                   int y,
+                                   int width,
+                                   int height)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    if (self->is_tiled) {
+        gnome_rr_output_info_set_tiled_geometry (self, x, y, width, height);
+    } else {
+        self->x = x;
+        self->y = y;
+        self->width = width;
+        self->height = height;
+    }
+}
+
+int
+gnome_rr_output_info_get_refresh_rate (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), 0);
+
+    return self->rate;
+}
+
+void
+gnome_rr_output_info_set_refresh_rate (GnomeRROutputInfo *self,
+                                       int rate)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    if (self->rate != rate) {
+        self->rate = rate;
+    }
+}
+
+GnomeRRRotation
+gnome_rr_output_info_get_rotation (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), GNOME_RR_ROTATION_0);
+
+    return self->rotation;
+}
+
+static void
+gnome_rr_output_info_set_tiled_rotation (GnomeRROutputInfo *self,
+                                         GnomeRRRotation rotation)
+{
+    GnomeRROutputInfo **outputs = gnome_rr_config_get_outputs (self->config);
+
+    int base_x = 0, base_y = 0;
+    int x_off = 0;
+
+    /*
+     * iterate over all the outputs from 0,0 -> h,v
+     * find the output for each tile,
+     * for all tiles set the rotation,
+     * for the 0 tile use the base X/Y offsets
+     * for non-0 tile, rotate the offsets of each
+     * tile so things display correctly.
+     */
+    for (guint ht = 0; ht < self->tile.max_horiz_tiles; ht++) {
+        int y_off = 0;
+        int addx = 0;
+
+        for (guint vt = 0; vt < self->tile.max_vert_tiles; vt++) {
+            for (int i = 0; outputs[i] != NULL; i++) {
+                GnomeRRTile *this_tile = &outputs[i]->tile;
+                int new_x, new_y;
+
+                if (!outputs[i]->is_tiled)
+                    continue;
+
+                if (this_tile->group_id != self->tile.group_id)
+                    continue;
+
+                if (this_tile->loc_horiz != ht ||
+                    this_tile->loc_vert != vt)
+                    continue;
+
+                /* set tile rotation */
+                outputs[i]->rotation = rotation;
+
+                /* for non-zero tiles - change the offsets */
+                if (ht == 0 && vt == 0) {
+                    base_x = outputs[i]->x;
+                    base_y = outputs[i]->y;
+                } else {
+                    if ((rotation & GNOME_RR_ROTATION_90) ||
+                        (rotation & GNOME_RR_ROTATION_270)) {
+                        new_x = base_x + y_off;
+                        new_y = base_y + x_off;
+                    } else {
+                        new_x = base_x + x_off;
+                        new_y = base_y + y_off;
+                    }
+
+                    outputs[i]->x = new_x;
+                    outputs[i]->y = new_y;
+                    outputs[i]->width = this_tile->width;
+                    outputs[i]->height = this_tile->height;
+                }
+
+                y_off += this_tile->height;
+                if (vt == 0) {
+                    addx = this_tile->width;
+                }
+            }
+        }
+        x_off += addx;
+    }
+}
+
+void
+gnome_rr_output_info_set_rotation (GnomeRROutputInfo *self,
+                                   GnomeRRRotation rotation)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    if (self->is_tiled) {
+        gnome_rr_output_info_set_tiled_rotation (self, rotation);
+    } else {
+        if (self->rotation != rotation) {
+                self->rotation = rotation;
+        }
+    }
+}
+
+gboolean
+gnome_rr_output_info_supports_rotation (GnomeRROutputInfo *self,
+                                        GnomeRRRotation rotation)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    return (self->available_rotations & rotation);
+}
+
+/**
+ * gnome_rr_output_info_is_connected:
+ *
+ * Returns: whether the output is physically connected to a monitor
+ */
+gboolean gnome_rr_output_info_is_connected (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    return self->connected;
+}
+
+/**
+ * gnome_rr_output_info_get_vendor:
+ * @self: the output information
+ *
+ * Returns: (transfer none): the output's vendor string
+ */
+const char *
+gnome_rr_output_info_get_vendor (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), NULL);
+
+    return self->vendor;
+}
+
+const char *
+gnome_rr_output_info_get_product (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), NULL);
+
+    return self->product;
+}
+
+const char *
+gnome_rr_output_info_get_serial (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), NULL);
+
+    return self->serial;
+}
+
+double
+gnome_rr_output_info_get_aspect_ratio (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), 0);
+
+    return self->aspect;
+}
+
+/**
+ * gnome_rr_output_info_get_display_name:
+ *
+ * Returns: (transfer none): the display name of this output
+ */
+const char *
+gnome_rr_output_info_get_display_name (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), NULL);
+
+    return self->display_name;
+}
+
+gboolean
+gnome_rr_output_info_get_primary (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    return self->primary;
+}
+
+void
+gnome_rr_output_info_set_primary (GnomeRROutputInfo *self,
+                                  gboolean primary)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    primary = !!primary;
+
+    if (self->primary != primary) {
+        self->primary = primary;
+    }
+}
+
+int
+gnome_rr_output_info_get_preferred_width (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), 0);
+
+    return self->pref_width;
+}
+
+int
+gnome_rr_output_info_get_preferred_height (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), 0);
+
+    return self->pref_height;
+}
+
+gboolean
+gnome_rr_output_info_get_underscanning (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    return self->underscanning;
+}
+
+void
+gnome_rr_output_info_set_underscanning (GnomeRROutputInfo *self,
+                                        gboolean underscanning)
+{
+    g_return_if_fail (GNOME_RR_IS_OUTPUT_INFO (self));
+
+    underscanning = !!underscanning;
+
+    if (self->underscanning != underscanning) {
+        self->underscanning = underscanning;
+    }
+}
+
+/**
+ * gnome_rr_output_info_is_primary_tile
+ * @self: a #GnomeRROutputInfo
+ *
+ * Returns: %TRUE if the specified output is connected to
+ * the primary tile of a monitor or to an untiled monitor,
+ * %FALSE if the output is connected to a secondary tile.
+ */
+gboolean
+gnome_rr_output_info_is_primary_tile (GnomeRROutputInfo *self)
+{
+    g_return_val_if_fail (GNOME_RR_IS_OUTPUT_INFO (self), FALSE);
+
+    if (!self->is_tiled)
+        return TRUE;
+
+    if (self->tile.loc_horiz == 0 &&
+        self->tile.loc_vert == 0)
+        return TRUE;
+
+    return FALSE;
+}
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-output-info.h 
b/libgnome-desktop/gnome-rr/gnome-rr-output-info.h
new file mode 100644
index 00000000..f51f0d62
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-output-info.h
@@ -0,0 +1,76 @@
+/* gnome-rr-output-info.h: Display information
+ *
+ * SPDX-FileCopyrightText: 2007, 2008, Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2010 Giovanni Campagna
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * 
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-rr/gnome-rr.h
+#endif
+
+#include <gnome-rr/gnome-rr-types.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_RR_TYPE_OUTPUT_INFO (gnome_rr_output_info_get_type())
+
+/**
+ * GnomeRROutputInfo:
+ *
+ * The representation of an output, which can be used for
+ * querying and setting display state.
+ */
+G_DECLARE_FINAL_TYPE (GnomeRROutputInfo, gnome_rr_output_info, GNOME_RR, OUTPUT_INFO, GObject)
+
+const char *    gnome_rr_output_info_get_name                   (GnomeRROutputInfo *self);
+
+gboolean        gnome_rr_output_info_is_active                  (GnomeRROutputInfo *self);
+void            gnome_rr_output_info_set_active                 (GnomeRROutputInfo *self,
+                                                                 gboolean active);
+
+void            gnome_rr_output_info_get_geometry               (GnomeRROutputInfo *self,
+                                                                 int *x,
+                                                                 int *y,
+                                                                 int *width,
+                                                                 int *height);
+void            gnome_rr_output_info_set_geometry               (GnomeRROutputInfo *self,
+                                                                 int x,
+                                                                 int y,
+                                                                 int width,
+                                                                 int height);
+
+int             gnome_rr_output_info_get_refresh_rate           (GnomeRROutputInfo *self);
+void            gnome_rr_output_info_set_refresh_rate           (GnomeRROutputInfo *self,
+                                                                 int rate);
+
+GnomeRRRotation gnome_rr_output_info_get_rotation               (GnomeRROutputInfo *self);
+void            gnome_rr_output_info_set_rotation               (GnomeRROutputInfo *self,
+                                                                 GnomeRRRotation rotation);
+gboolean        gnome_rr_output_info_supports_rotation          (GnomeRROutputInfo *self,
+                                                                 GnomeRRRotation rotation);
+
+gboolean        gnome_rr_output_info_is_connected               (GnomeRROutputInfo *self);
+const char *    gnome_rr_output_info_get_vendor                 (GnomeRROutputInfo *self);
+const char *    gnome_rr_output_info_get_product                (GnomeRROutputInfo *self);
+const char *    gnome_rr_output_info_get_serial                 (GnomeRROutputInfo *self);
+double          gnome_rr_output_info_get_aspect_ratio           (GnomeRROutputInfo *self);
+const char *    gnome_rr_output_info_get_display_name           (GnomeRROutputInfo *self);
+
+gboolean        gnome_rr_output_info_get_primary                (GnomeRROutputInfo *self);
+void            gnome_rr_output_info_set_primary                (GnomeRROutputInfo *self,
+                                                                 gboolean primary);
+
+int             gnome_rr_output_info_get_preferred_width        (GnomeRROutputInfo *self);
+int             gnome_rr_output_info_get_preferred_height       (GnomeRROutputInfo *self);
+
+gboolean        gnome_rr_output_info_get_underscanning          (GnomeRROutputInfo *self);
+void            gnome_rr_output_info_set_underscanning          (GnomeRROutputInfo *self,
+                                                                 gboolean underscanning);
+
+gboolean        gnome_rr_output_info_is_primary_tile            (GnomeRROutputInfo *self);
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-private.h b/libgnome-desktop/gnome-rr/gnome-rr-private.h
new file mode 100644
index 00000000..94e29b84
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-private.h
@@ -0,0 +1,155 @@
+/* gnome-rr-private.h: Private type and API
+ *
+ * SPDX-FileCopyrightText: 2009  Novell Inc
+ * SPDX-FileCopyrightText: 2014  Endless
+ * SPDX-FileCopyrightText: 2009, 2014  Red Hat Inc
+ * SPDX-FileCopyrightText: 2019  System76
+ * SPDX-FileCopyrightText: 2021  Emmanuele Bassi
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "gnome-rr-types.h"
+#include "gnome-rr-config.h"
+#include "gnome-rr-output-info.h"
+
+G_BEGIN_DECLS
+
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/wayland/gdkwayland.h>
+#else
+enum wl_output_transform {
+  WL_OUTPUT_TRANSFORM_NORMAL,
+  WL_OUTPUT_TRANSFORM_90,
+  WL_OUTPUT_TRANSFORM_180,
+  WL_OUTPUT_TRANSFORM_270,
+  WL_OUTPUT_TRANSFORM_FLIPPED,
+  WL_OUTPUT_TRANSFORM_FLIPPED_90,
+  WL_OUTPUT_TRANSFORM_FLIPPED_180,
+  WL_OUTPUT_TRANSFORM_FLIPPED_270
+};
+#endif
+
+#include "meta-xrandr-shared.h"
+#include "meta-dbus-xrandr.h"
+
+typedef struct _ScreenInfo ScreenInfo;
+
+struct _ScreenInfo
+{
+    int        min_width;
+    int        max_width;
+    int        min_height;
+    int        max_height;
+
+    guint serial;
+    
+    GnomeRROutput **outputs;
+    GnomeRRCrtc **crtcs;
+    GnomeRRMode **modes;
+    
+    GnomeRRScreen *screen;
+
+    GnomeRRMode **clone_modes;
+
+    GnomeRROutput *primary;
+};
+
+typedef struct _GnomeRRScreenPrivate GnomeRRScreenPrivate;
+struct _GnomeRRScreenPrivate
+{
+    GdkDisplay *gdk_display;
+    ScreenInfo *info;
+
+    int init_name_watch_id;
+    MetaDBusDisplayConfig *proxy;
+};
+
+typedef struct _GnomeRRTile GnomeRRTile;
+
+#define UNDEFINED_GROUP_ID 0
+struct _GnomeRRTile
+{
+    guint group_id;
+    guint flags;
+    guint max_horiz_tiles;
+    guint max_vert_tiles;
+    guint loc_horiz;
+    guint loc_vert;
+    guint width;
+    guint height;
+};
+
+struct _GnomeRROutputInfo
+{
+    char *name;
+
+    gboolean active;
+    int        width;
+    int        height;
+    int        rate;
+    int        x;
+    int        y;
+    GnomeRRRotation rotation;
+    GnomeRRRotation available_rotations;
+
+    gboolean connected;
+    char *vendor;
+    char *product;
+    char *serial;
+    double aspect;
+    int        pref_width;
+    int        pref_height;
+    char *display_name;
+    char *connector_type;
+    gboolean primary;
+    gboolean underscanning;
+
+    gboolean is_tiled;
+    GnomeRRTile tile;
+
+    int total_tiled_width;
+    int total_tiled_height;
+
+    /* weak pointer back to info */
+    GnomeRRConfig *config;
+};
+
+struct _GnomeRRConfig
+{
+  gboolean clone;
+  GnomeRRScreen *screen;
+  GnomeRROutputInfo **outputs;
+};
+
+G_GNUC_INTERNAL
+gboolean
+gnome_rr_output_connector_type_is_builtin_display (const char *connector_type);
+
+G_GNUC_INTERNAL
+gboolean
+gnome_rr_screen_apply_configuration (GnomeRRScreen *screen,
+                                     gboolean persistent,
+                                     GVariant *crtcs,
+                                     GVariant *outputs,
+                                     GError **error);
+
+G_GNUC_INTERNAL
+const char *
+gnome_rr_output_get_connector_type (GnomeRROutput *output);
+
+G_GNUC_INTERNAL
+gboolean
+gnome_rr_output_get_tile_info (const GnomeRROutput *output,
+                               GnomeRRTile *tile);
+
+G_GNUC_INTERNAL
+gboolean
+gnome_rr_output_get_tiled_display_size (const GnomeRROutput *output,
+                                        int *tile_w,
+                                        int *tile_h,
+                                        int *width,
+                                        int *height);
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-screen.c b/libgnome-desktop/gnome-rr/gnome-rr-screen.c
new file mode 100644
index 00000000..90914d1d
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-screen.c
@@ -0,0 +1,2464 @@
+/* gnome-rr-screen.c: Display information
+ *
+ * SPDX-FileCopyrightText: 2007, 2008, 2013 Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2020 NVIDIA CORPORATION
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * 
+ * Author: Soren Sandmann <sandmann redhat com>
+ *         Giovanni Campagna <gcampagn redhat com>
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include "config.h"
+
+#include "gnome-rr-screen.h"
+
+#include "gnome-rr-private.h"
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+/* From xf86drmMode.h: it's ABI so it won't change */
+#define DRM_MODE_FLAG_INTERLACE                        (1<<4)
+
+enum {
+    SCREEN_PROP_0,
+    SCREEN_PROP_GDK_DISPLAY,
+    SCREEN_PROP_DPMS_MODE,
+    SCREEN_PROP_LAST,
+};
+
+enum {
+    SCREEN_CHANGED,
+    SCREEN_OUTPUT_CONNECTED,
+    SCREEN_OUTPUT_DISCONNECTED,
+    SCREEN_SIGNAL_LAST,
+};
+
+static gint screen_signals[SCREEN_SIGNAL_LAST];
+
+struct _GnomeRROutput
+{
+    ScreenInfo *        info;
+    guint                id;
+    glong               winsys_id;
+    
+    char *                name;
+    char *                display_name;
+    char *                connector_type;
+    GnomeRRCrtc *        current_crtc;
+    GnomeRRCrtc **        possible_crtcs;
+    GnomeRROutput **        clones;
+    GnomeRRMode **        modes;
+
+    char *              vendor;
+    char *              product;
+    char *              serial;
+    int                 width_mm;
+    int                 height_mm;
+    GBytes *            edid;
+    char *              edid_file;
+
+    int                 backlight;
+    int                 min_backlight_step;
+
+    gboolean            is_primary;
+    gboolean            is_presentation;
+    gboolean            is_underscanning;
+    gboolean            supports_underscanning;
+    gboolean            supports_color_transform;
+
+    GnomeRRTile         tile_info;
+};
+
+struct _GnomeRRCrtc
+{
+    ScreenInfo *        info;
+    guint                id;
+    glong               winsys_id;
+    
+    GnomeRRMode *        current_mode;
+    GnomeRROutput **        current_outputs;
+    GnomeRROutput **        possible_outputs;
+    int                        x;
+    int                        y;
+    
+    enum wl_output_transform transform;
+    int                 all_transforms;
+    int                        gamma_size;
+};
+
+#define UNDEFINED_MODE_ID 0
+struct _GnomeRRMode
+{
+    ScreenInfo *        info;
+    guint                id;
+    glong               winsys_id;
+    int                        width;
+    int                        height;
+    int                        freq;                /* in mHz */
+    gboolean                tiled;
+    guint32             flags;
+};
+
+/* GnomeRRCrtc */
+static GnomeRRCrtc *  crtc_new          (ScreenInfo         *info,
+                                         guint               id);
+static GnomeRRCrtc *  crtc_copy         (const GnomeRRCrtc  *from);
+static void           crtc_free         (GnomeRRCrtc        *crtc);
+
+static void           crtc_initialize   (GnomeRRCrtc        *crtc,
+                                         GVariant           *res);
+
+/* GnomeRROutput */
+static GnomeRROutput *output_new        (ScreenInfo         *info,
+                                         guint               id);
+
+static void           output_initialize (GnomeRROutput      *output,
+                                         GVariant           *res);
+
+static GnomeRROutput *output_copy       (const GnomeRROutput *from);
+static void           output_free       (GnomeRROutput      *output);
+
+/* GnomeRRMode */
+static GnomeRRMode *  mode_new          (ScreenInfo         *info,
+                                         guint               id);
+
+static void           mode_initialize   (GnomeRRMode        *mode,
+                                         GVariant           *info);
+
+static GnomeRRMode *  mode_copy         (const GnomeRRMode  *from);
+static void           mode_free         (GnomeRRMode        *mode);
+
+static gboolean gnome_rr_screen_initable_init           (GInitable *initable,
+                                                         GCancellable *cancellable,
+                                                         GError **error);
+static void     gnome_rr_screen_initable_iface_init     (GInitableIface *iface);
+static void     gnome_rr_screen_async_initable_init     (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GnomeRRScreen, gnome_rr_screen, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (GnomeRRScreen)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gnome_rr_screen_initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, gnome_rr_screen_async_initable_init))
+
+G_DEFINE_BOXED_TYPE (GnomeRRCrtc, gnome_rr_crtc, crtc_copy, crtc_free)
+G_DEFINE_BOXED_TYPE (GnomeRROutput, gnome_rr_output, output_copy, output_free)
+G_DEFINE_BOXED_TYPE (GnomeRRMode, gnome_rr_mode, mode_copy, mode_free)
+
+/* Errors */
+
+/**
+ * gnome_rr_error_quark:
+ *
+ * Returns the error domain used by the GnomeRR API.
+ *
+ * Return value: the GnomeRR error domain 
+ */
+G_DEFINE_QUARK (gnome-rr-error-quark, gnome_rr_error)
+
+/* Screen */
+static GnomeRROutput *
+gnome_rr_output_by_id (ScreenInfo *info, guint id)
+{
+    GnomeRROutput **output;
+    
+    g_assert (info != NULL);
+    
+    for (output = info->outputs; *output; ++output)
+    {
+        if ((*output)->id == id)
+            return *output;
+    }
+    
+    return NULL;
+}
+
+static GnomeRRCrtc *
+crtc_by_id (ScreenInfo *info, guint id)
+{
+    GnomeRRCrtc **crtc;
+    
+    if (!info)
+        return NULL;
+    
+    for (crtc = info->crtcs; *crtc; ++crtc)
+    {
+        if ((*crtc)->id == id)
+            return *crtc;
+    }
+    
+    return NULL;
+}
+
+static GnomeRRMode *
+mode_by_id (ScreenInfo *info, guint id)
+{
+    GnomeRRMode **mode;
+    
+    g_assert (info != NULL);
+    
+    for (mode = info->modes; *mode; ++mode)
+    {
+        if ((*mode)->id == id)
+            return *mode;
+    }
+    
+    return NULL;
+}
+
+static void
+screen_info_free (ScreenInfo *info)
+{
+    GnomeRROutput **output;
+    GnomeRRCrtc **crtc;
+    GnomeRRMode **mode;
+    
+    g_assert (info != NULL);
+
+    if (info->outputs)
+    {
+        for (output = info->outputs; *output; ++output)
+            output_free (*output);
+        g_free (info->outputs);
+    }
+    
+    if (info->crtcs)
+    {
+        for (crtc = info->crtcs; *crtc; ++crtc)
+            crtc_free (*crtc);
+        g_free (info->crtcs);
+    }
+    
+    if (info->modes)
+    {
+        for (mode = info->modes; *mode; ++mode)
+            mode_free (*mode);
+        g_free (info->modes);
+    }
+
+    if (info->clone_modes)
+    {
+        /* The modes themselves were freed above */
+        g_free (info->clone_modes);
+    }
+    
+    g_free (info);
+}
+
+static gboolean
+has_similar_mode (GnomeRROutput *output, GnomeRRMode *mode)
+{
+    int i;
+    GnomeRRMode **modes = gnome_rr_output_list_modes (output);
+    guint width = gnome_rr_mode_get_width (mode);
+    guint height = gnome_rr_mode_get_height (mode);
+
+    for (i = 0; modes[i] != NULL; ++i)
+    {
+        GnomeRRMode *m = modes[i];
+
+        if (gnome_rr_mode_get_width (m) == width        &&
+            gnome_rr_mode_get_height (m) == height)
+        {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+gboolean
+gnome_rr_output_get_tiled_display_size (const GnomeRROutput *output,
+                                        int *tile_w,
+                                        int *tile_h,
+                                        int *total_width,
+                                        int *total_height)
+{
+    GnomeRRTile tile;
+    guint ht, vt;
+    int i, total_h = 0, total_w = 0;
+
+    if (!gnome_rr_output_get_tile_info (output, &tile))
+        return FALSE;
+
+    if (tile.loc_horiz != 0 ||
+        tile.loc_vert != 0)
+        return FALSE;
+
+    if (tile_w)
+        *tile_w = tile.width;
+    if (tile_h)
+        *tile_h = tile.height;
+
+    for (ht = 0; ht < tile.max_horiz_tiles; ht++) {
+        for (vt = 0; vt < tile.max_vert_tiles; vt++) {
+            for (i = 0; output->info->outputs[i]; i++) {
+                GnomeRRTile this_tile;
+
+                if (!gnome_rr_output_get_tile_info (output->info->outputs[i], &this_tile))
+                    continue;
+
+                if (this_tile.group_id != tile.group_id)
+                    continue;
+
+                if (this_tile.loc_horiz != ht ||
+                    this_tile.loc_vert != vt)
+                    continue;
+
+                if (this_tile.loc_horiz == 0)
+                    total_h += this_tile.height;
+
+                if (this_tile.loc_vert == 0)
+                    total_w += this_tile.width;
+            }
+        }
+    }
+
+    *total_width = total_w;
+    *total_height = total_h;
+
+    return TRUE;
+}
+
+static void
+gather_tile_modes_output (ScreenInfo *info,
+                          GnomeRROutput *output)
+{
+    GPtrArray *a;
+    GnomeRRMode *mode;
+    int width, height;
+    int tile_w, tile_h;
+    int i;
+
+    if (!gnome_rr_output_get_tiled_display_size (output, &tile_w, &tile_h, &width, &height))
+        return;
+
+    /* now stick the mode into the modelist */
+    a = g_ptr_array_new ();
+    mode = mode_new (info, UNDEFINED_MODE_ID);
+    mode->winsys_id = 0;
+    mode->width = width;
+    mode->height = height;
+    mode->freq = 0;
+    mode->tiled = TRUE;
+
+    g_ptr_array_add (a, mode);
+    for (i = 0; output->modes[i]; i++)
+        g_ptr_array_add (a, output->modes[i]);
+
+    g_ptr_array_add (a, NULL);
+    output->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
+}
+
+static void
+gather_tile_modes (ScreenInfo *info)
+{
+    int i;
+
+    for (i = 0; info->outputs[i]; i++)
+        gather_tile_modes_output (info, info->outputs[i]);
+}
+
+static void
+gather_clone_modes (ScreenInfo *info)
+{
+    int i;
+    GPtrArray *result = g_ptr_array_new ();
+
+    for (i = 0; info->outputs[i] != NULL; ++i)
+    {
+        int j;
+        GnomeRROutput *output1, *output2;
+
+        output1 = info->outputs[i];
+        
+        for (j = 0; output1->modes[j] != NULL; ++j)
+        {
+            GnomeRRMode *mode = output1->modes[j];
+            gboolean valid;
+            int k;
+
+            valid = TRUE;
+            for (k = 0; info->outputs[k] != NULL; ++k)
+            {
+                output2 = info->outputs[k];
+                
+                if (!has_similar_mode (output2, mode))
+                {
+                    valid = FALSE;
+                    break;
+                }
+            }
+
+            if (valid)
+                g_ptr_array_add (result, mode);
+        }
+    }
+
+    g_ptr_array_add (result, NULL);
+    
+    info->clone_modes = (GnomeRRMode **)g_ptr_array_free (result, FALSE);
+}
+
+static void
+fill_screen_info_from_resources (ScreenInfo *info,
+                                 guint       serial,
+                                 GVariant   *crtcs,
+                                 GVariant   *outputs,
+                                 GVariant   *modes,
+                                 int         max_width,
+                                 int         max_height)
+{
+    guint i;
+    GPtrArray *a;
+    GnomeRRCrtc **crtc;
+    GnomeRROutput **output;
+    GnomeRRMode **mode;
+    guint ncrtc, noutput, nmode;
+    guint id;
+
+    info->min_width = 312;
+    info->min_height = 312;
+    info->max_width = max_width;
+    info->max_height = max_height;
+    info->serial = serial;
+
+    ncrtc = g_variant_n_children (crtcs);
+    noutput = g_variant_n_children (outputs);
+    nmode = g_variant_n_children (modes);
+
+    /* We create all the structures before initializing them, so
+     * that they can refer to each other.
+     */
+    a = g_ptr_array_new ();
+    for (i = 0; i < ncrtc; ++i)
+    {
+        g_variant_get_child (crtcs, i, META_CRTC_STRUCT, &id,
+                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+        g_ptr_array_add (a, crtc_new (info, id));
+    }
+    g_ptr_array_add (a, NULL);
+    info->crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);
+
+    a = g_ptr_array_new ();
+    for (i = 0; i < noutput; ++i)
+    {
+        g_variant_get_child (outputs, i, META_OUTPUT_STRUCT, &id,
+                             NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+        g_ptr_array_add (a, output_new (info, id));
+    }
+    g_ptr_array_add (a, NULL);
+    info->outputs = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+
+    a = g_ptr_array_new ();
+    for (i = 0;  i < nmode; ++i)
+    {
+        g_variant_get_child (modes, i, META_MONITOR_MODE_STRUCT, &id,
+                             NULL, NULL, NULL, NULL, NULL);
+
+        g_ptr_array_add (a, mode_new (info, id));
+    }
+    g_ptr_array_add (a, NULL);
+    info->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
+
+    /* Initialize */
+    for (i = 0, crtc = info->crtcs; *crtc; ++i, ++crtc)
+    {
+        GVariant *child = g_variant_get_child_value (crtcs, i);
+        crtc_initialize (*crtc, child);
+        g_variant_unref (child);
+    }
+
+    for (i = 0, output = info->outputs; *output; ++i, ++output)
+    {
+        GVariant *child = g_variant_get_child_value (outputs, i);
+        output_initialize (*output, child);
+        g_variant_unref (child);
+    }
+
+    for (i = 0, mode = info->modes; *mode; ++i, ++mode)
+    {
+        GVariant *child = g_variant_get_child_value (modes, i);
+        mode_initialize (*mode, child);
+        g_variant_unref (child);
+    }
+
+    gather_clone_modes (info);
+
+    gather_tile_modes (info);
+}
+
+static gboolean
+fill_out_screen_info (ScreenInfo  *info,
+                      GError     **error)
+{
+    GnomeRRScreenPrivate *priv;
+    guint serial;
+    GVariant *crtcs, *outputs, *modes;
+    int max_width, max_height;
+
+    g_assert (info != NULL);
+
+    priv = gnome_rr_screen_get_instance_private (info->screen);
+
+    if (!meta_dbus_display_config_call_get_resources_sync (priv->proxy,
+                                                           &serial,
+                                                           &crtcs,
+                                                           &outputs,
+                                                           &modes,
+                                                           &max_width,
+                                                           &max_height,
+                                                           NULL,
+                                                           error))
+        return FALSE;
+
+    fill_screen_info_from_resources (info, serial, crtcs, outputs,
+                                     modes, max_width, max_height);
+
+    g_variant_unref (crtcs);
+    g_variant_unref (outputs);
+    g_variant_unref (modes);
+
+    return TRUE;
+}
+
+static ScreenInfo *
+screen_info_new (GnomeRRScreen *screen, GError **error)
+{
+    ScreenInfo *info = g_new0 (ScreenInfo, 1);
+
+    g_assert (screen != NULL);
+
+    info->outputs = NULL;
+    info->crtcs = NULL;
+    info->modes = NULL;
+    info->screen = screen;
+    
+    if (fill_out_screen_info (info, error))
+    {
+        return info;
+    }
+    else
+    {
+        screen_info_free (info);
+        return NULL;
+    }
+}
+
+static GnomeRROutput *
+find_output_by_winsys_id (GnomeRROutput **haystack, glong winsys_id)
+{
+    guint i;
+
+    for (i = 0; haystack[i] != NULL; i++)
+    {
+        if (haystack[i]->winsys_id == winsys_id)
+            return haystack[i];
+    }
+    return NULL;
+}
+
+static void
+diff_outputs_and_emit_signals (ScreenInfo *old, ScreenInfo *new)
+{
+    guint i;
+    gulong winsys_id_old, winsys_id_new;
+    GnomeRROutput *output_old;
+    GnomeRROutput *output_new;
+
+    /* have any outputs been removed/disconnected */
+    for (i = 0; old->outputs[i] != NULL; i++)
+    {
+        winsys_id_old = old->outputs[i]->winsys_id;
+        output_new = find_output_by_winsys_id (new->outputs, winsys_id_old);
+        if (output_new == NULL)
+        {
+            g_signal_emit (G_OBJECT (new->screen),
+                           screen_signals[SCREEN_OUTPUT_DISCONNECTED], 0,
+                           old->outputs[i]);
+        }
+    }
+
+    /* have any outputs been created/connected */
+    for (i = 0; new->outputs[i] != NULL; i++)
+    {
+        winsys_id_new = new->outputs[i]->winsys_id;
+        output_old = find_output_by_winsys_id (old->outputs, winsys_id_new);
+        if (output_old == NULL)
+        {
+            g_signal_emit (G_OBJECT (new->screen),
+                           screen_signals[SCREEN_OUTPUT_CONNECTED], 0,
+                           new->outputs[i]);
+        }
+    }
+}
+
+typedef enum {
+    REFRESH_NONE = 0,
+    REFRESH_IGNORE_SERIAL = 1,
+    REFRESH_FORCE_CALLBACK = 2
+} RefreshFlags;
+
+static gboolean
+screen_update (GnomeRRScreen *screen, RefreshFlags flags, GError **error)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    ScreenInfo *info;
+    gboolean changed = FALSE;
+    
+    g_assert (screen != NULL);
+
+    info = screen_info_new (screen, error);
+    if (!info)
+            return FALSE;
+
+    if ((flags & REFRESH_IGNORE_SERIAL) || info->serial != priv->info->serial)
+            changed = TRUE;
+
+    /* work out if any outputs have changed connected state */
+    diff_outputs_and_emit_signals (priv->info, info);
+
+    screen_info_free (priv->info);
+    priv->info = info;
+
+    if (changed || (flags & REFRESH_FORCE_CALLBACK))
+        g_signal_emit (G_OBJECT (screen), screen_signals[SCREEN_CHANGED], 0);
+    
+    return changed;
+}
+
+static void
+screen_on_monitors_changed (MetaDBusDisplayConfig *proxy,
+                            gpointer data)
+{
+    GnomeRRScreen *screen = data;
+
+    screen_update (screen, REFRESH_FORCE_CALLBACK, NULL);
+}
+
+static void
+name_owner_changed (GObject       *object,
+                    GParamSpec    *pspec,
+                    GnomeRRScreen *self)
+{
+    GError *error;
+    char *new_name_owner;
+
+    new_name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
+    if (new_name_owner == NULL)
+        return;
+
+    error = NULL;
+    if (!screen_update (self, REFRESH_IGNORE_SERIAL | REFRESH_FORCE_CALLBACK, &error))
+        g_warning ("Failed to refresh screen configuration after mutter was restarted: %s",
+                   error->message);
+
+    g_clear_error (&error);
+    g_free (new_name_owner);
+}
+
+static void
+power_save_mode_changed (GObject       *object,
+                         GParamSpec    *pspec,
+                         GnomeRRScreen *self)
+{
+        g_object_notify (G_OBJECT (self), "dpms-mode");
+}
+
+static gboolean
+gnome_rr_screen_initable_init (GInitable *initable, GCancellable *canc, GError **error)
+{
+    GnomeRRScreen *self = GNOME_RR_SCREEN (initable);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+    MetaDBusDisplayConfig *proxy;
+
+    proxy = meta_dbus_display_config_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                             G_DBUS_PROXY_FLAGS_NONE,
+                                                             "org.gnome.Mutter.DisplayConfig",
+                                                             "/org/gnome/Mutter/DisplayConfig",
+                                                             NULL, error);
+    if (!proxy)
+        return FALSE;
+
+    priv->proxy = META_DBUS_DISPLAY_CONFIG (proxy);
+
+    priv->info = screen_info_new (self, error);
+    if (!priv->info)
+        return FALSE;
+
+    g_signal_connect_object (priv->proxy, "notify::g-name-owner",
+                             G_CALLBACK (name_owner_changed), self, 0);
+    g_signal_connect_object (priv->proxy, "monitors-changed",
+                             G_CALLBACK (screen_on_monitors_changed), self, 0);
+    g_signal_connect_object (priv->proxy, "notify::power-save-mode",
+                             G_CALLBACK (power_save_mode_changed), self, 0);
+    return TRUE;
+}
+
+static void
+on_proxy_acquired (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+    GTask *task = user_data;
+    GnomeRRScreen *self = g_task_get_source_object (task);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+    MetaDBusDisplayConfig *proxy;
+    GError *error;
+
+    error = NULL;
+    proxy = meta_dbus_display_config_proxy_new_for_bus_finish (result, &error);
+    if (!proxy)
+        return g_task_return_error (task, error);
+
+    priv->proxy = META_DBUS_DISPLAY_CONFIG (proxy);
+
+    priv->info = screen_info_new (self, &error);
+    if (!priv->info)
+        return g_task_return_error (task, error);
+
+    g_signal_connect_object (priv->proxy, "notify::g-name-owner",
+                             G_CALLBACK (name_owner_changed), self, 0);
+    g_signal_connect_object (priv->proxy, "monitors-changed",
+                             G_CALLBACK (screen_on_monitors_changed), self, 0);
+    g_signal_connect_object (priv->proxy, "notify::power-save-mode",
+                             G_CALLBACK (power_save_mode_changed), self, 0);
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+on_name_appeared (GDBusConnection *connection,
+                  const char      *name,
+                  const char      *name_owner,
+                  gpointer         user_data)
+{
+    GTask *task = user_data;
+    GnomeRRScreen *self = g_task_get_source_object (task);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+
+    meta_dbus_display_config_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                                G_DBUS_PROXY_FLAGS_NONE,
+                                                "org.gnome.Mutter.DisplayConfig",
+                                                "/org/gnome/Mutter/DisplayConfig",
+                                                g_task_get_cancellable (task),
+                                                on_proxy_acquired, g_object_ref (task));
+
+    g_bus_unwatch_name (priv->init_name_watch_id);
+}
+
+static void
+gnome_rr_screen_async_initable_init_async (GAsyncInitable      *initable,
+                                           int                  io_priority,
+                                           GCancellable        *canc,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+    GnomeRRScreen *self = GNOME_RR_SCREEN (initable);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+    GTask *task;
+
+    task = g_task_new (self, canc, callback, user_data);
+
+    priv->init_name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                                                 "org.gnome.Mutter.DisplayConfig",
+                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                                 on_name_appeared,
+                                                 NULL,
+                                                 task, g_object_unref);
+}
+
+static gboolean
+gnome_rr_screen_async_initable_init_finish (GAsyncInitable    *initable,
+                                            GAsyncResult      *result,
+                                            GError           **error)
+{
+    return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gnome_rr_screen_initable_iface_init (GInitableIface *iface)
+{
+    iface->init = gnome_rr_screen_initable_init;
+}
+
+static void
+gnome_rr_screen_async_initable_init (GAsyncInitableIface *iface)
+{
+    iface->init_async = gnome_rr_screen_async_initable_init_async;
+    iface->init_finish = gnome_rr_screen_async_initable_init_finish;
+}
+
+void
+gnome_rr_screen_finalize (GObject *gobject)
+{
+    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+
+    g_clear_pointer (&priv->info, screen_info_free);
+    g_clear_object (&priv->proxy);
+
+    G_OBJECT_CLASS (gnome_rr_screen_parent_class)->finalize (gobject);
+}
+
+void
+gnome_rr_screen_set_property (GObject *gobject,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *property)
+{
+    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+
+    switch (property_id)
+    {
+    case SCREEN_PROP_GDK_DISPLAY:
+        priv->gdk_display = g_value_get_object (value);
+        return;
+    case SCREEN_PROP_DPMS_MODE:
+        gnome_rr_screen_set_dpms_mode (self, g_value_get_enum (value), NULL);
+        return;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+        return;
+    }
+}
+
+void
+gnome_rr_screen_get_property (GObject *gobject,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *property)
+{
+    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+
+    switch (property_id)
+    {
+    case SCREEN_PROP_GDK_DISPLAY:
+        g_value_set_object (value, priv->gdk_display);
+        return;
+    case SCREEN_PROP_DPMS_MODE: {
+        GnomeRRDpmsMode mode;
+        if (gnome_rr_screen_get_dpms_mode (self, &mode, NULL))
+                g_value_set_enum (value, mode);
+        else
+                g_value_set_enum (value, GNOME_RR_DPMS_UNKNOWN);
+        }
+        return;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+        return;
+    }
+}
+
+void
+gnome_rr_screen_class_init (GnomeRRScreenClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+    gobject_class->set_property = gnome_rr_screen_set_property;
+    gobject_class->get_property = gnome_rr_screen_get_property;
+    gobject_class->finalize = gnome_rr_screen_finalize;
+
+    g_object_class_install_property(
+            gobject_class,
+            SCREEN_PROP_GDK_DISPLAY,
+            g_param_spec_object (
+                    "gdk-display",
+                    "Display connection",
+                    "The GDK display connection represented by this GnomeRRScreen",
+                    GDK_TYPE_DISPLAY,
+                    G_PARAM_READWRITE |
+                    G_PARAM_CONSTRUCT_ONLY |
+                    G_PARAM_STATIC_STRINGS)
+            );
+
+    g_object_class_install_property(
+            gobject_class,
+            SCREEN_PROP_DPMS_MODE,
+            g_param_spec_enum (
+                    "dpms-mode",
+                    "DPMS Mode",
+                    "The DPMS mode for this GnomeRRScreen",
+                    GNOME_RR_TYPE_DPMS_MODE,
+                    GNOME_RR_DPMS_UNKNOWN,
+                    G_PARAM_READWRITE |
+                    G_PARAM_STATIC_STRINGS)
+            );
+
+    screen_signals[SCREEN_CHANGED] = g_signal_new("changed",
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+            G_STRUCT_OFFSET (GnomeRRScreenClass, changed),
+            NULL,
+            NULL,
+            g_cclosure_marshal_VOID__VOID,
+            G_TYPE_NONE,
+            0);
+
+    /**
+     * GnomeRRScreen::output-connected:
+     * @screen: the #GnomeRRScreen that emitted the signal
+     * @output: the #GnomeRROutput that was connected
+     *
+     * This signal is emitted when a display device is connected to a
+     * port, or a port is hotplugged with an active output. The latter
+     * can happen if a laptop is docked, and the dock provides a new
+     * active output.
+     *
+     * The @output value is not a #GObject. The returned @output value can
+     * only assume to be valid during the emission of the signal (i.e. within
+     * your signal handler only), as it may change later when the @screen
+     * is modified due to an event from the X server, or due to another
+     * place in the application modifying the @screen and the @output.
+     * Therefore, deal with changes to the @output right in your signal
+     * handler, instead of keeping the @output reference for an async or
+     * idle function.
+     **/
+    screen_signals[SCREEN_OUTPUT_CONNECTED] = g_signal_new("output-connected",
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+            G_STRUCT_OFFSET (GnomeRRScreenClass, output_connected),
+            NULL,
+            NULL,
+            NULL,
+            G_TYPE_NONE,
+            1, GNOME_RR_TYPE_OUTPUT);
+
+    /**
+     * GnomeRRScreen::output-disconnected:
+     * @screen: the #GnomeRRScreen that emitted the signal
+     * @output: the #GnomeRROutput that was disconnected
+     *
+     * This signal is emitted when a display device is disconnected from
+     * a port, or a port output is hot-unplugged. The latter can happen
+     * if a laptop is undocked, and the dock provided the output.
+     *
+     * The @output value is not a #GObject. The returned @output value can
+     * only assume to be valid during the emission of the signal (i.e. within
+     * your signal handler only), as it may change later when the @screen
+     * is modified due to an event from the X server, or due to another
+     * place in the application modifying the @screen and the @output.
+     * Therefore, deal with changes to the @output right in your signal
+     * handler, instead of keeping the @output reference for an async or
+     * idle function.
+     **/
+    screen_signals[SCREEN_OUTPUT_DISCONNECTED] = g_signal_new("output-disconnected",
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+            G_STRUCT_OFFSET (GnomeRRScreenClass, output_disconnected),
+            NULL,
+            NULL,
+            NULL,
+            G_TYPE_NONE,
+            1, GNOME_RR_TYPE_OUTPUT);
+}
+
+void
+gnome_rr_screen_init (GnomeRRScreen *self)
+{
+}
+
+static MetaDBusDisplayConfig *
+gnome_rr_screen_get_dbus_proxy (GnomeRRScreen *self)
+{
+  GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
+
+  return priv->proxy;
+}
+
+/* Weak reference callback set in gnome_rr_screen_new();
+ * we remove the GObject data from the GdkDisplay
+ */
+static void
+rr_screen_weak_notify_cb (gpointer data, GObject *where_the_object_was)
+{
+    GObject *gdk_display = data;
+
+    g_object_set_data (gdk_display, "-gnome-rr-screen-display", NULL);
+}
+
+/**
+ * gnome_rr_screen_new:
+ * @display: the windowing system connection used to query the display data
+ * @error: will be set if XRandR is not supported
+ *
+ * Creates a unique #GnomeRRScreen instance for the specified @display.
+ *
+ * Returns: a unique #GnomeRRScreen instance, specific to the @screen, or `NULL`
+ *   if this could not be created, for instance if the driver does not support
+ *   Xrandr 1.2.  Each #GdkDisplay thus has a single instance of #GnomeRRScreen.
+ */
+GnomeRRScreen *
+gnome_rr_screen_new (GdkDisplay *display,
+                     GError **error)
+{
+    GnomeRRScreen *rr_screen;
+
+    g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+    rr_screen = g_object_get_data (G_OBJECT (display), "-gnome-rr-screen-display");
+    if (rr_screen) {
+        return g_object_ref (rr_screen);
+    } else {
+        rr_screen = g_initable_new (GNOME_RR_TYPE_SCREEN, NULL, error, "gdk-display", display, NULL);
+        if (rr_screen) {
+            g_object_set_data (G_OBJECT (display), "-gnome-rr-screen-display", rr_screen);
+            g_object_weak_ref (G_OBJECT (rr_screen), rr_screen_weak_notify_cb, display);
+        }
+    }
+
+    return rr_screen;
+}
+
+/**
+ * gnome_rr_screen_new_async:
+ * @display: the windowing system connection used to query the display
+ * @callback: the function to call when the #GnomeRRScreen is ready, or on error
+ * @user_data: data to pass to the @callback
+ *
+ * Asynchronously creates a new #GnomeRRScreen instance.
+ *
+ * On both success and error, @callback will be invoked. You should use
+ * gnome_rr_screen_new_finish() to retrieve the newly created #GnomeRRScreen
+ * instance.
+ */
+void
+gnome_rr_screen_new_async (GdkDisplay          *display,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+    g_return_if_fail (GDK_IS_DISPLAY (display));
+
+    g_async_initable_new_async (GNOME_RR_TYPE_SCREEN, G_PRIORITY_DEFAULT,
+                                NULL, callback, user_data,
+                                "gdk-display", display, NULL);
+}
+
+/**
+ * gnome_rr_screen_new_finish:
+ * @result: the result of the asynchronous operation
+ * @error: return location for an error
+ *
+ * Finishes the asynchronous creation of a new #GnomeRRScreen instance.
+ *
+ * Returns: (transfer full): the newly created instance; on error, this
+ *   function will return `NULL` and set the given #GError
+ */
+GnomeRRScreen *
+gnome_rr_screen_new_finish (GAsyncResult  *result,
+                            GError       **error)
+{
+    GObject *source_object;
+    GnomeRRScreen *screen;
+
+    source_object = g_async_result_get_source_object (result);
+    screen = GNOME_RR_SCREEN (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error));
+
+    g_object_unref (source_object);
+
+    return screen;
+}
+
+/**
+ * gnome_rr_screen_get_ranges:
+ * @screen: a #GnomeRRScreen
+ * @min_width: (out): the minimum width
+ * @max_width: (out): the maximum width
+ * @min_height: (out): the minimum height
+ * @max_height: (out): the maximum height
+ *
+ * Get the ranges of the screen
+ */
+void
+gnome_rr_screen_get_ranges (GnomeRRScreen *screen,
+                            int                  *min_width,
+                            int                  *max_width,
+                            int           *min_height,
+                            int                  *max_height)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    g_return_if_fail (GNOME_RR_IS_SCREEN (screen));
+    
+    if (min_width)
+        *min_width = priv->info->min_width;
+    
+    if (max_width)
+        *max_width = priv->info->max_width;
+    
+    if (min_height)
+        *min_height = priv->info->min_height;
+    
+    if (max_height)
+        *max_height = priv->info->max_height;
+}
+
+/**
+ * gnome_rr_screen_refresh:
+ * @screen: a #GnomeRRScreen
+ * @error: location to store error, or %NULL
+ *
+ * Refreshes the screen configuration, and calls the screen's callback if it
+ * exists and if the screen's configuration changed.
+ *
+ * Return value: TRUE if the screen's configuration changed; otherwise, the
+ * function returns FALSE and a NULL error if the configuration didn't change,
+ * or FALSE and a non-NULL error if there was an error while refreshing the
+ * configuration.
+ */
+gboolean
+gnome_rr_screen_refresh (GnomeRRScreen *screen,
+                         GError       **error)
+{
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    return screen_update (screen, REFRESH_NONE, error);
+}
+
+/**
+ * gnome_rr_screen_get_dpms_mode:
+ * @mode: (out): The current #GnomeRRDpmsMode of this screen
+ **/
+gboolean
+gnome_rr_screen_get_dpms_mode (GnomeRRScreen    *screen,
+                               GnomeRRDpmsMode  *mode,
+                               GError          **error)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    MetaPowerSave power_save;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+    g_return_val_if_fail (mode != NULL, FALSE);
+
+    power_save = meta_dbus_display_config_get_power_save_mode (priv->proxy);
+    switch (power_save) {
+    case META_POWER_SAVE_UNKNOWN:
+        g_set_error_literal (error,
+                             GNOME_RR_ERROR,
+                             GNOME_RR_ERROR_NO_DPMS_EXTENSION,
+                             "Display is not DPMS capable");
+        return FALSE;
+    case META_POWER_SAVE_ON:
+        *mode = GNOME_RR_DPMS_ON;
+        break;
+    case META_POWER_SAVE_STANDBY:
+        *mode = GNOME_RR_DPMS_STANDBY;
+        break;
+    case META_POWER_SAVE_SUSPEND:
+        *mode = GNOME_RR_DPMS_SUSPEND;
+        break;
+    case META_POWER_SAVE_OFF:
+        *mode = GNOME_RR_DPMS_OFF;
+        break;
+    default:
+        g_assert_not_reached ();
+        break;
+    }
+
+    return TRUE;
+}
+
+/**
+ * gnome_rr_screen_set_dpms_mode:
+ *
+ * This method also disables the DPMS timeouts.
+ **/
+gboolean
+gnome_rr_screen_set_dpms_mode (GnomeRRScreen    *screen,
+                               GnomeRRDpmsMode   mode,
+                               GError          **error)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    MetaPowerSave power_save;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    switch (mode) {
+    case GNOME_RR_DPMS_UNKNOWN:
+        power_save = META_POWER_SAVE_UNKNOWN;
+        break;
+    case GNOME_RR_DPMS_ON:
+        power_save = META_POWER_SAVE_ON;
+        break;
+    case GNOME_RR_DPMS_STANDBY:
+        power_save = META_POWER_SAVE_STANDBY;
+        break;
+    case GNOME_RR_DPMS_SUSPEND:
+        power_save = META_POWER_SAVE_SUSPEND;
+        break;
+    case GNOME_RR_DPMS_OFF:
+        power_save = META_POWER_SAVE_OFF;
+        break;
+    default:
+        g_assert_not_reached ();
+        break;
+    }
+
+    meta_dbus_display_config_set_power_save_mode (priv->proxy, power_save);
+
+    return TRUE;
+}
+
+/**
+ * gnome_rr_screen_list_modes:
+ * @screen: the screen to query
+ *
+ * Lists all available XRandR modes.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): the available XRandR modes
+ */
+GnomeRRMode **
+gnome_rr_screen_list_modes (GnomeRRScreen *screen)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+    
+    return priv->info->modes;
+}
+
+/**
+ * gnome_rr_screen_list_clone_modes:
+ * @screen: the screen to query
+ *
+ * Lists all available XRandR clone modes.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): the available XRandR clone modes
+ */
+GnomeRRMode **
+gnome_rr_screen_list_clone_modes (GnomeRRScreen *screen)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+
+    return priv->info->clone_modes;
+}
+
+/**
+ * gnome_rr_screen_list_crtcs:
+ * @screen: the screen to query
+ *
+ * List all CRTCs of the given screen.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): the available CRTCs
+ */
+GnomeRRCrtc **
+gnome_rr_screen_list_crtcs (GnomeRRScreen *screen)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+    
+    return priv->info->crtcs;
+}
+
+/**
+ * gnome_rr_screen_list_outputs:
+ * @screen: the screen to query
+ *
+ * List all outputs of the given screen.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): the available outputs
+ */
+GnomeRROutput **
+gnome_rr_screen_list_outputs (GnomeRRScreen *screen)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+    
+    return priv->info->outputs;
+}
+
+/**
+ * gnome_rr_screen_get_crtc_by_id:
+ * @screen: the screen to query
+ * @id: the identifier of a CRTC
+ *
+ * Retrieves the CRTC of the screen using the given identifier.
+ *
+ * Returns: (transfer none): the CRTC identified by @id
+ */
+GnomeRRCrtc *
+gnome_rr_screen_get_crtc_by_id (GnomeRRScreen *screen,
+                                guint32        id)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    GnomeRRCrtc **crtcs;
+    int i;
+    
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+
+    crtcs = priv->info->crtcs;
+    
+    for (i = 0; crtcs[i] != NULL; ++i)
+    {
+        if (crtcs[i]->id == id)
+            return crtcs[i];
+    }
+    
+    return NULL;
+}
+
+/**
+ * gnome_rr_screen_get_output_by_id:
+ * @screen: the screen to query
+ * @id: the identifier of an output
+ *
+ * Retrieves the output of a screen using the given identifier.
+ *
+ * Returns: (transfer none): the output identified by @id
+ */
+GnomeRROutput *
+gnome_rr_screen_get_output_by_id (GnomeRRScreen *screen,
+                                  guint32        id)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    GnomeRROutput **outputs;
+    int i;
+    
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+
+    outputs = priv->info->outputs;
+
+    for (i = 0; outputs[i] != NULL; ++i)
+    {
+        if (outputs[i]->id == id)
+            return outputs[i];
+    }
+    
+    return NULL;
+}
+
+/* GnomeRROutput */
+static GnomeRROutput *
+output_new (ScreenInfo *info, guint id)
+{
+    GnomeRROutput *output = g_slice_new0 (GnomeRROutput);
+    
+    output->id = id;
+    output->info = info;
+    
+    return output;
+}
+
+static void
+append_output_array (GnomeRROutput ***array, GnomeRROutput *output)
+{
+    unsigned i;
+
+    for (i = 0; (*array)[i]; i++);
+
+    *array = g_renew (GnomeRROutput *, *array, i + 2);
+
+    (*array)[i] = output;
+    (*array)[i + 1] = NULL;
+}
+
+static void
+output_initialize (GnomeRROutput *output, GVariant *info)
+{
+    GPtrArray *a;
+    GVariantIter *crtcs, *clones, *modes;
+    GVariant *properties, *edid, *tile;
+    gint32 current_crtc_id;
+    guint32 id;
+
+    g_variant_get (info, META_OUTPUT_STRUCT,
+                   &output->id, &output->winsys_id,
+                   &current_crtc_id, &crtcs,
+                   &output->name,
+                   &modes, &clones, &properties);
+
+    /* Possible crtcs */
+    a = g_ptr_array_new ();
+    while (g_variant_iter_loop (crtcs, "u", &id))
+    {
+        GnomeRRCrtc *crtc = crtc_by_id (output->info, id);
+        
+        if (!crtc)
+            continue;
+
+        g_ptr_array_add (a, crtc);
+
+        if (current_crtc_id != -1 && crtc->id == (guint32) current_crtc_id)
+        {
+            output->current_crtc = crtc;
+            append_output_array (&crtc->current_outputs, output);
+        }
+
+        append_output_array (&crtc->possible_outputs, output);
+    }
+    g_ptr_array_add (a, NULL);
+    output->possible_crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);
+    g_variant_iter_free (crtcs);
+
+    /* Clones */
+    a = g_ptr_array_new ();
+    while (g_variant_iter_loop (clones, "u", &id))
+    {
+        GnomeRROutput *gnome_rr_output = gnome_rr_output_by_id (output->info, id);
+        
+        if (gnome_rr_output)
+            g_ptr_array_add (a, gnome_rr_output);
+    }
+    g_ptr_array_add (a, NULL);
+    output->clones = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+    g_variant_iter_free (clones);
+    
+    /* Modes */
+    a = g_ptr_array_new ();
+    while (g_variant_iter_loop (modes, "u", &id))
+    {
+        GnomeRRMode *mode = mode_by_id (output->info, id);
+        
+        if (mode)
+            g_ptr_array_add (a, mode);
+    }
+    g_ptr_array_add (a, NULL);
+    output->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
+    g_variant_iter_free (modes);
+
+    g_variant_lookup (properties, "vendor", "s", &output->vendor);
+    g_variant_lookup (properties, "product", "s", &output->product);
+    g_variant_lookup (properties, "serial", "s", &output->serial);
+    g_variant_lookup (properties, "width-mm", "i", &output->width_mm);
+    g_variant_lookup (properties, "height-mm", "i", &output->height_mm);
+    g_variant_lookup (properties, "display-name", "s", &output->display_name);
+    g_variant_lookup (properties, "connector-type", "s", &output->connector_type);
+    g_variant_lookup (properties, "backlight", "i", &output->backlight);
+    g_variant_lookup (properties, "min-backlight-step", "i", &output->min_backlight_step);
+    g_variant_lookup (properties, "primary", "b", &output->is_primary);
+    g_variant_lookup (properties, "presentation", "b", &output->is_presentation);
+    g_variant_lookup (properties, "underscanning", "b", &output->is_underscanning);
+    g_variant_lookup (properties, "supports-underscanning", "b", &output->supports_underscanning);
+    g_variant_lookup (properties, "supports-color-transform", "b", &output->supports_color_transform);
+
+    if ((edid = g_variant_lookup_value (properties, "edid", G_VARIANT_TYPE ("ay"))))
+      {
+        output->edid = g_variant_get_data_as_bytes (edid);
+        g_variant_unref (edid);
+      }
+    else
+      g_variant_lookup (properties, "edid-file", "s", &output->edid_file);
+
+    if ((tile = g_variant_lookup_value (properties, "tile", G_VARIANT_TYPE ("(uuuuuuuu)"))))
+      {
+        g_variant_get (tile, "(uuuuuuuu)",
+                       &output->tile_info.group_id, &output->tile_info.flags,
+                       &output->tile_info.max_horiz_tiles, &output->tile_info.max_vert_tiles,
+                       &output->tile_info.loc_horiz, &output->tile_info.loc_vert,
+                       &output->tile_info.width, &output->tile_info.height);
+        g_variant_unref (tile);
+      }
+    else
+      memset(&output->tile_info, 0, sizeof(output->tile_info));
+
+    if (output->is_primary)
+        output->info->primary = output;
+
+    g_variant_unref (properties);
+}
+
+static GnomeRROutput*
+output_copy (const GnomeRROutput *from)
+{
+    GPtrArray *array;
+    GnomeRRCrtc **p_crtc;
+    GnomeRROutput **p_output;
+    GnomeRRMode **p_mode;
+    GnomeRROutput *output = g_slice_new0 (GnomeRROutput);
+
+    output->id = from->id;
+    output->info = from->info;
+    output->name = g_strdup (from->name);
+    output->display_name = g_strdup (from->display_name);
+    output->connector_type = g_strdup (from->connector_type);
+    output->vendor = g_strdup (from->vendor);
+    output->product = g_strdup (from->product);
+    output->serial = g_strdup (from->serial);
+    output->current_crtc = from->current_crtc;
+    output->backlight = from->backlight;
+    if (from->edid)
+      output->edid = g_bytes_ref (from->edid);
+    output->edid_file = g_strdup (from->edid_file);
+
+    output->is_primary = from->is_primary;
+    output->is_presentation = from->is_presentation;
+
+    array = g_ptr_array_new ();
+    for (p_crtc = from->possible_crtcs; *p_crtc != NULL; p_crtc++)
+    {
+        g_ptr_array_add (array, *p_crtc);
+    }
+    output->possible_crtcs = (GnomeRRCrtc**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_output = from->clones; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    output->clones = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_mode = from->modes; *p_mode != NULL; p_mode++)
+    {
+        g_ptr_array_add (array, *p_mode);
+    }
+    output->modes = (GnomeRRMode**) g_ptr_array_free (array, FALSE);
+
+    return output;
+}
+
+static void
+output_free (GnomeRROutput *output)
+{
+    g_free (output->clones);
+    g_free (output->modes);
+    g_free (output->possible_crtcs);
+    g_free (output->name);
+    g_free (output->vendor);
+    g_free (output->product);
+    g_free (output->serial);
+    g_free (output->display_name);
+    g_free (output->connector_type);
+    g_free (output->edid_file);
+    if (output->edid)
+      g_bytes_unref (output->edid);
+    g_slice_free (GnomeRROutput, output);
+}
+
+guint32
+gnome_rr_output_get_id (const GnomeRROutput *output)
+{
+    g_assert (output != NULL);
+    
+    return output->id;
+}
+
+const guint8 *
+gnome_rr_output_get_edid_data (GnomeRROutput *output,
+                               gsize         *size)
+{
+  if (output->edid)
+    return g_bytes_get_data (output->edid, size);
+
+  if (output->edid_file)
+    {
+      GMappedFile *mmap;
+
+      mmap = g_mapped_file_new (output->edid_file, FALSE, NULL);
+
+      if (mmap)
+        {
+          output->edid = g_mapped_file_get_bytes (mmap);
+
+          g_mapped_file_unref (mmap);
+
+          return g_bytes_get_data (output->edid, size);
+        }
+    }
+
+  return NULL;
+}
+
+/**
+ * gnome_rr_output_get_ids_from_edid:
+ * @output: the output to query
+ * @vendor: (out) (optional) (transfer full): the output's vendor string
+ * @product: (out) (optional) (transfer full): the output's product string
+ * @serial: (out) (optional) (transfer full): the output's serial string
+ *
+ * Retrieves the model identifiers from the EDID of the given output.
+ */
+void
+gnome_rr_output_get_ids_from_edid (const GnomeRROutput   *output,
+                                   char                 **vendor,
+                                   char                 **product,
+                                   char                 **serial)
+{
+    g_return_if_fail (output != NULL);
+
+    if (vendor != NULL)
+        *vendor = g_strdup (output->vendor);
+    if (product != NULL)
+        *product = g_strdup (output->product);
+    if (serial != NULL)
+        *serial = g_strdup (output->serial);
+}
+
+/**
+ * gnome_rr_output_get_physical_size:
+ * @output: the output to query
+ * @width_mm: (out) (optional): the width of the output, in millimeters
+ * @height_mm: (out) (optional): the height of the output, in millimeters
+ *
+ * Retrieves the physical size of the given output.
+ */
+void
+gnome_rr_output_get_physical_size (const GnomeRROutput *output,
+                                   int                 *width_mm,
+                                   int                 *height_mm)
+{
+    g_return_if_fail (output != NULL);
+
+    if (width_mm)
+        *width_mm = output->width_mm;
+    if (height_mm)
+        *height_mm = output->height_mm;
+}
+
+/**
+ * gnome_rr_output_get_display_name:
+ * @output: the output to query
+ *
+ * Retrieves the display name of the given output.
+ *
+ * Returns: (transfer none): the display name
+ */
+const char *
+gnome_rr_output_get_display_name (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->display_name;
+}
+
+/**
+ * gnome_rr_output_get_backlight:
+ * @output: the output to query
+ *
+ * Retrieves the backlight brightness of the given output.
+ *
+ * Returns: The currently set backlight brightness
+ */
+int
+gnome_rr_output_get_backlight (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, -1);
+
+    return output->backlight;
+}
+
+/**
+ * gnome_rr_output_get_min_backlight_step:
+ * @output: the output to query
+ *
+ * Retrieves the value of the minimum backlight step for the given output,
+ * as a percentage.
+ *
+ * Returns: The minimum backlight step available in percent
+ */
+int
+gnome_rr_output_get_min_backlight_step (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, -1);
+
+    return output->min_backlight_step;
+}
+
+/**
+ * gnome_rr_output_set_backlight:
+ * @output: the output to set
+ * @value: the absolute value of the backlight
+ *
+ * Sets the backlight level for the given output.
+ *
+ * The value is a percentage, with a range of [0, 100].
+ *
+ * Returns: `TRUE` for success
+ */
+gboolean
+gnome_rr_output_set_backlight (GnomeRROutput  *output,
+                               int             value,
+                               GError        **error)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (output->info->screen);
+
+    return meta_dbus_display_config_call_change_backlight_sync (proxy,
+                                                                output->info->serial,
+                                                                output->id, value,
+                                                                &output->backlight,
+                                                                NULL, error);
+}
+
+/**
+ * gnome_rr_output_set_color_transform:
+ * @output: the output to set
+ * @ctm: the color transformation matrix
+ * @error: return location for an error
+ *
+ * Sets the color transformation matrix for the given output.
+ *
+ * Returns: `TRUE` on success
+ */
+gboolean
+gnome_rr_output_set_color_transform (GnomeRROutput *output,
+                                     GnomeRRCTM ctm,
+                                     GError **error)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    GVariant *values[9];
+    for (int i = 0; i < 9; i++)
+        values[i] = g_variant_new_uint64 (ctm.matrix[i]);
+
+    GVariant *ctm_var = g_variant_new_tuple (values, 9);
+
+    MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (output->info->screen);
+    return meta_dbus_display_config_call_set_output_ctm_sync (proxy,
+                                                              output->info->serial,
+                                                              output->id,
+                                                              ctm_var,
+                                                              NULL, error);
+}
+
+/**
+ * gnome_rr_screen_get_output_by_name:
+ * @screen: the screen to query
+ *
+ * Retrieves the output for the given name.
+ *
+ * Returns: (transfer none): the output identified by @name
+ */
+GnomeRROutput *
+gnome_rr_screen_get_output_by_name (GnomeRRScreen *screen,
+                                    const char    *name)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+    
+    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
+    g_return_val_if_fail (priv->info != NULL, NULL);
+    
+    for (int i = 0; priv->info->outputs[i] != NULL; ++i)
+    {
+        GnomeRROutput *output = priv->info->outputs[i];
+        
+        if (strcmp (output->name, name) == 0)
+            return output;
+    }
+    
+    return NULL;
+}
+
+/**
+ * gnome_rr_output_get_crtc:
+ * @output: the output to query
+ *
+ * Retrieves the CRTC of the given output.
+ *
+ * Returns: (transfer none): the CRTC of the output
+ */
+GnomeRRCrtc *
+gnome_rr_output_get_crtc (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+    
+    return output->current_crtc;
+}
+
+/**
+ * gnome_rr_output_get_possible_crtcs:
+ * @output: the output to query
+ *
+ * Retrieves all the possible CRTC for the given output.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): the list of possible CRTC
+ */
+GnomeRRCrtc **
+gnome_rr_output_get_possible_crtcs (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->possible_crtcs;
+}
+
+gboolean
+gnome_rr_output_connector_type_is_builtin_display (const char *connector_type)
+{
+    if (!connector_type)
+        return FALSE;
+
+    if (strcmp (connector_type, "LVDS") == 0 ||
+        strcmp (connector_type, "eDP") == 0  ||
+        strcmp (connector_type, "DSI") == 0)
+        return TRUE;
+
+    return FALSE;
+}
+
+/**
+ * gnome_rr_output_is_builtin_display:
+ * @output: the output to query
+ *
+ * Checks whether the given output is a built-in display.
+ *
+ * Returns: `TRUE` if the output is a built-in display
+ */
+gboolean
+gnome_rr_output_is_builtin_display (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    return gnome_rr_output_connector_type_is_builtin_display (output->connector_type);
+}
+
+/**
+ * gnome_rr_output_get_current_mode:
+ * @output: the output to query
+ *
+ * Retrieves the current mode of the given output.
+ *
+ * Returns: (transfer none): the current mode of this output
+ */
+GnomeRRMode *
+gnome_rr_output_get_current_mode (const GnomeRROutput *output)
+{
+    GnomeRRCrtc *crtc;
+    GnomeRRMode *mode;
+
+    g_return_val_if_fail (output != NULL, NULL);
+    
+    if ((crtc = gnome_rr_output_get_crtc (output)))
+    {
+        int total_w, total_h, tile_w, tile_h;
+        mode = gnome_rr_crtc_get_current_mode (crtc);
+
+        if (gnome_rr_output_get_tiled_display_size (output, &tile_w, &tile_h, &total_w, &total_h))
+        {
+            if (mode->width == tile_w &&
+                mode->height == tile_h) {
+                if (output->modes[0]->tiled)
+                    return output->modes[0];
+            }
+        }
+        return gnome_rr_crtc_get_current_mode (crtc);
+    }
+    return NULL;
+}
+
+/**
+ * gnome_rr_output_get_position:
+ * @output: the output to query
+ * @x: (out) (optional): the X coordinate of the output
+ * @y: (out) (optional): the Y coordinate of the output
+ */
+void
+gnome_rr_output_get_position (const GnomeRROutput *output,
+                              int                 *x,
+                              int                 *y)
+{
+    GnomeRRCrtc *crtc;
+    
+    g_return_if_fail (output != NULL);
+    
+    if ((crtc = gnome_rr_output_get_crtc (output)))
+        gnome_rr_crtc_get_position (crtc, x, y);
+}
+
+/**
+ * gnome_rr_output_get_name:
+ * @output: the output to query
+ *
+ * Retrieves the name of the given output.
+ *
+ * Returns: (transfer none): the name of the output
+ */
+const char *
+gnome_rr_output_get_name (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->name;
+}
+
+/**
+ * gnome_rr_output_get_preferred_mode:
+ * @output: the output to query
+ *
+ * Retrieves the preferred mode of the given output.
+ *
+ * Returns: (transfer none): the preferred mode of the output
+ */
+GnomeRRMode *
+gnome_rr_output_get_preferred_mode (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->modes[0];
+}
+
+/**
+ * gnome_rr_output_list_modes:
+ * @output: the output to query
+ *
+ * Retrieves all available modes of the given output.
+ *
+ * Returns: (array zero-terminated=1) (transfer none): a list of modes
+ */
+GnomeRRMode **
+gnome_rr_output_list_modes (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->modes;
+}
+
+/**
+ * gnome_rr_output_supports_mode:
+ * @output: the output to query
+ * @mode: the mode to compare
+ *
+ * Checks whether the given output supports a mode.
+ *
+ * Returns: `TRUE` if the mode is supported
+ */
+gboolean
+gnome_rr_output_supports_mode (const GnomeRROutput *output,
+                               const GnomeRRMode   *mode)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+    g_return_val_if_fail (mode != NULL, FALSE);
+    
+    for (int i = 0; output->modes[i] != NULL; ++i)
+    {
+        if (output->modes[i] == mode)
+            return TRUE;
+    }
+    
+    return FALSE;
+}
+
+/**
+ * gnome_rr_output_can_clone:
+ * @output: the output to query
+ * @clone: the output to compare
+ *
+ * Checks whether the given output can clone another output.
+ *
+ * Returns: `TRUE` if the output can clone another output
+ */
+gboolean
+gnome_rr_output_can_clone (const GnomeRROutput *output,
+                           const GnomeRROutput *clone)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+    g_return_val_if_fail (clone != NULL, FALSE);
+    
+    for (int i = 0; output->clones[i] != NULL; ++i)
+    {
+        if (output->clones[i] == clone)
+            return TRUE;
+    }
+    
+    return FALSE;
+}
+
+/**
+ * gnome_rr_output_get_is_primary:
+ * @output: the output to query
+ *
+ * Checks whether the given output is the primary output.
+ *
+ * Returns: `TRUE` if the output is the primary one
+ */
+gboolean
+gnome_rr_output_get_is_primary (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    return output->is_primary;
+}
+
+/* GnomeRRCrtc */
+static const GnomeRRRotation rotation_map[] =
+{
+    GNOME_RR_ROTATION_0,
+    GNOME_RR_ROTATION_90,
+    GNOME_RR_ROTATION_180,
+    GNOME_RR_ROTATION_270,
+    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_0,
+    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_90,
+    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_180,
+    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_270,
+};
+
+static GnomeRRRotation
+gnome_rr_rotation_from_transform (enum wl_output_transform transform)
+{
+    return rotation_map[transform];
+}
+
+/**
+ * gnome_rr_crtc_get_current_mode:
+ * @crtc: a #GnomeRRCrtc
+ * Returns: (transfer none): the current mode of this crtc
+ */
+GnomeRRMode *
+gnome_rr_crtc_get_current_mode (GnomeRRCrtc *crtc)
+{
+    g_return_val_if_fail (crtc != NULL, NULL);
+    
+    return crtc->current_mode;
+}
+
+guint32
+gnome_rr_crtc_get_id (GnomeRRCrtc *crtc)
+{
+    g_return_val_if_fail (crtc != NULL, 0);
+    
+    return crtc->id;
+}
+
+gboolean
+gnome_rr_crtc_can_drive_output (GnomeRRCrtc   *crtc,
+                                GnomeRROutput *output)
+{
+    int i;
+    
+    g_return_val_if_fail (crtc != NULL, FALSE);
+    g_return_val_if_fail (output != NULL, FALSE);
+    
+    for (i = 0; crtc->possible_outputs[i] != NULL; ++i)
+    {
+        if (crtc->possible_outputs[i] == output)
+            return TRUE;
+    }
+    
+    return FALSE;
+}
+
+/**
+ * gnome_rr_crtc_get_position:
+ * @crtc: a #GnomeRRCrtc
+ * @x: (out) (allow-none):
+ * @y: (out) (allow-none):
+ */
+void
+gnome_rr_crtc_get_position (GnomeRRCrtc *crtc,
+                            int         *x,
+                            int         *y)
+{
+    g_return_if_fail (crtc != NULL);
+    
+    if (x)
+        *x = crtc->x;
+    
+    if (y)
+        *y = crtc->y;
+}
+
+GnomeRRRotation
+gnome_rr_crtc_get_current_rotation (GnomeRRCrtc *crtc)
+{
+    g_assert(crtc != NULL);
+    return gnome_rr_rotation_from_transform (crtc->transform);
+}
+
+static GnomeRRRotation
+gnome_rr_rotation_from_all_transforms (int all_transforms)
+{
+    GnomeRRRotation ret = all_transforms & 0xF;
+
+    if (all_transforms & (1 << WL_OUTPUT_TRANSFORM_FLIPPED))
+        ret |= GNOME_RR_REFLECT_X;
+
+    if (all_transforms & (1 << WL_OUTPUT_TRANSFORM_FLIPPED_180))
+        ret |= GNOME_RR_REFLECT_Y;
+
+    return ret;
+}
+
+GnomeRRRotation
+gnome_rr_crtc_get_rotations (GnomeRRCrtc *crtc)
+{
+    g_assert(crtc != NULL);
+    return gnome_rr_rotation_from_all_transforms (crtc->all_transforms);
+}
+
+gboolean
+gnome_rr_crtc_supports_rotation (GnomeRRCrtc *   crtc,
+                                 GnomeRRRotation rotation)
+{
+    g_return_val_if_fail (crtc != NULL, FALSE);
+    return (gnome_rr_rotation_from_all_transforms (crtc->all_transforms) & rotation);
+}
+
+static GnomeRRCrtc *
+crtc_new (ScreenInfo *info, guint id)
+{
+    GnomeRRCrtc *crtc = g_slice_new0 (GnomeRRCrtc);
+    
+    crtc->id = id;
+    crtc->info = info;
+    crtc->current_outputs = g_new0 (GnomeRROutput *, 1);
+    crtc->possible_outputs = g_new0 (GnomeRROutput *, 1);
+    
+    return crtc;
+}
+
+static GnomeRRCrtc *
+crtc_copy (const GnomeRRCrtc *from)
+{
+    GnomeRROutput **p_output;
+    GPtrArray *array;
+    GnomeRRCrtc *to = g_slice_new0 (GnomeRRCrtc);
+
+    to->info = from->info;
+    to->id = from->id;
+    to->current_mode = from->current_mode;
+    to->x = from->x;
+    to->y = from->y;
+    to->transform = from->transform;
+    to->all_transforms = from->all_transforms;
+    to->gamma_size = from->gamma_size;
+
+    array = g_ptr_array_new ();
+    for (p_output = from->current_outputs; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    to->current_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_output = from->possible_outputs; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    to->possible_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+    return to;
+}
+
+static void
+crtc_initialize (GnomeRRCrtc *crtc, GVariant *info)
+{
+    GVariantIter *all_transforms;
+    int current_mode_id;
+    guint transform;
+
+    g_variant_get (info, META_CRTC_STRUCT,
+                   &crtc->id, &crtc->winsys_id,
+                   &crtc->x, &crtc->y,
+                   NULL, NULL,
+                   &current_mode_id,
+                   &crtc->transform, &all_transforms,
+                   NULL);
+
+    if (current_mode_id >= 0)
+      crtc->current_mode = mode_by_id (crtc->info, current_mode_id);
+    
+    while (g_variant_iter_loop (all_transforms, "u", &transform))
+        crtc->all_transforms |= 1 << transform;
+    g_variant_iter_free (all_transforms);
+}
+
+static void
+crtc_free (GnomeRRCrtc *crtc)
+{
+    g_free (crtc->current_outputs);
+    g_free (crtc->possible_outputs);
+    g_slice_free (GnomeRRCrtc, crtc);
+}
+
+/* GnomeRRMode */
+static GnomeRRMode *
+mode_new (ScreenInfo *info, guint id)
+{
+    GnomeRRMode *mode = g_slice_new0 (GnomeRRMode);
+    
+    mode->id = id;
+    mode->info = info;
+    
+    return mode;
+}
+
+guint32
+gnome_rr_mode_get_id (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->id;
+}
+
+guint
+gnome_rr_mode_get_width (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->width;
+}
+
+int
+gnome_rr_mode_get_freq (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return (mode->freq) / 1000;
+}
+
+double
+gnome_rr_mode_get_freq_f (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0.0);
+    return (mode->freq) / 1000.0;
+}
+
+guint
+gnome_rr_mode_get_height (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->height;
+}
+
+/**
+ * gnome_rr_mode_get_is_tiled:
+ * @mode: a #GnomeRRMode
+ *
+ * Returns TRUE if this mode is a tiled
+ * mode created for span a tiled monitor.
+ */
+gboolean
+gnome_rr_mode_get_is_tiled (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, FALSE);
+    return mode->tiled;
+}
+
+gboolean
+gnome_rr_mode_get_is_interlaced (GnomeRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0;
+}
+
+static void
+mode_initialize (GnomeRRMode *mode, GVariant *info)
+{
+    gdouble frequency;
+
+    g_variant_get (info, META_MONITOR_MODE_STRUCT,
+                   &mode->id, &mode->winsys_id,
+                   &mode->width, &mode->height,
+                   &frequency, &mode->flags);
+    
+    mode->freq = frequency * 1000;
+}
+
+static GnomeRRMode *
+mode_copy (const GnomeRRMode *from)
+{
+    GnomeRRMode *to = g_slice_new0 (GnomeRRMode);
+
+    to->id = from->id;
+    to->info = from->info;
+    to->width = from->width;
+    to->height = from->height;
+    to->freq = from->freq;
+
+    return to;
+}
+
+static void
+mode_free (GnomeRRMode *mode)
+{
+    g_slice_free (GnomeRRMode, mode);
+}
+
+gboolean
+gnome_rr_screen_apply_configuration (GnomeRRScreen  *screen,
+                                     gboolean        persistent,
+                                     GVariant       *crtcs,
+                                     GVariant       *outputs,
+                                     GError        **error)
+{
+    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
+
+    return meta_dbus_display_config_call_apply_configuration_sync (priv->proxy,
+                                                                   priv->info->serial,
+                                                                   persistent,
+                                                                   crtcs, outputs,
+                                                                   NULL, error);
+}
+
+gboolean
+gnome_rr_crtc_set_gamma (GnomeRRCrtc    *crtc,
+                         int             size,
+                         unsigned short *red,
+                         unsigned short *green,
+                         unsigned short *blue)
+{
+  GBytes *red_bytes = g_bytes_new (red, size * sizeof (unsigned short));
+  GBytes *green_bytes = g_bytes_new (green, size * sizeof (unsigned short));
+  GBytes *blue_bytes = g_bytes_new (blue, size * sizeof (unsigned short));
+
+  GVariant *red_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), red_bytes, TRUE);
+  GVariant *green_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), green_bytes, TRUE);
+  GVariant *blue_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), blue_bytes, TRUE);
+
+  MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (crtc->info->screen);
+
+  gboolean res =
+    meta_dbus_display_config_call_set_crtc_gamma_sync (proxy,
+                                                       crtc->info->serial,
+                                                       crtc->id,
+                                                       red_v,
+                                                       green_v,
+                                                       blue_v,
+                                                       NULL, NULL);
+
+  g_bytes_unref (red_bytes);
+  g_bytes_unref (green_bytes);
+  g_bytes_unref (blue_bytes);
+  /* The variant above are floating, no need to free them */
+
+  return res;
+}
+
+/**
+ * gnome_rr_crtc_get_gamma:
+ * @crtc: a #GnomeRRCrtc
+ * @size:
+ * @red: (out): the minimum width
+ * @green: (out): the maximum width
+ * @blue: (out): the minimum height
+ *
+ * Returns: %TRUE for success
+ */
+gboolean
+gnome_rr_crtc_get_gamma (GnomeRRCrtc     *crtc,
+                         int             *size,
+                         unsigned short **red,
+                         unsigned short **green,
+                         unsigned short **blue)
+{
+  GBytes *red_bytes, *green_bytes, *blue_bytes;
+  GVariant *red_v, *green_v, *blue_v;
+  gboolean ok;
+  gsize dummy;
+
+  g_return_val_if_fail (crtc != NULL, FALSE);
+
+  MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (crtc->info->screen);
+  ok = meta_dbus_display_config_call_get_crtc_gamma_sync (proxy,
+                                                          crtc->info->serial,
+                                                          crtc->id,
+                                                          &red_v,
+                                                          &green_v,
+                                                          &blue_v,
+                                                          NULL, NULL);
+  if (!ok)
+    return FALSE;
+
+  red_bytes = g_variant_get_data_as_bytes (red_v);
+  green_bytes = g_variant_get_data_as_bytes (green_v);
+  blue_bytes = g_variant_get_data_as_bytes (blue_v);
+
+  /* Unref the variant early so that the bytes hold the only reference to
+     the data and we don't need to copy
+  */
+  g_variant_unref (red_v);
+  g_variant_unref (green_v);
+  g_variant_unref (blue_v);
+
+  if (size)
+    *size = g_bytes_get_size (red_bytes) / sizeof (unsigned short);
+
+  if (red)
+    *red = g_bytes_unref_to_data (red_bytes, &dummy);
+  else
+    g_bytes_unref (red_bytes);
+  if (green)
+    *green = g_bytes_unref_to_data (green_bytes, &dummy);
+  else
+    g_bytes_unref (green_bytes);
+  if (blue)
+    *blue = g_bytes_unref_to_data (blue_bytes, &dummy);
+  else
+    g_bytes_unref (blue_bytes);
+
+  return TRUE;
+}
+
+gboolean
+gnome_rr_output_get_is_underscanning (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    return output->is_underscanning;
+}
+
+gboolean
+gnome_rr_output_supports_underscanning (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    return output->supports_underscanning;
+}
+
+gboolean
+gnome_rr_output_supports_color_transform (const GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    return output->supports_color_transform;
+}
+
+const char *
+gnome_rr_output_get_connector_type (GnomeRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->connector_type;
+}
+
+gboolean
+gnome_rr_output_get_tile_info (const GnomeRROutput *output,
+                               GnomeRRTile *tile)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    if (output->tile_info.group_id == UNDEFINED_GROUP_ID)
+        return FALSE;
+
+    if (!tile)
+        return FALSE;
+
+    *tile = output->tile_info;
+    return TRUE;
+}
+
+GType
+gnome_rr_dpms_mode_get_type (void)
+{
+  static GType etype = 0;
+
+  if (g_once_init_enter (&etype)) {
+    static const GEnumValue values[] = {
+      { GNOME_RR_DPMS_ON, "GNOME_RR_DPMS_ON", "on" },
+      { GNOME_RR_DPMS_STANDBY, "GNOME_RR_DPMS_STANDBY", "standby" },
+      { GNOME_RR_DPMS_SUSPEND, "GNOME_RR_DPMS_SUSPEND", "suspend" },
+      { GNOME_RR_DPMS_OFF, "GNOME_RR_DPMS_OFF", "off" },
+      { GNOME_RR_DPMS_UNKNOWN, "GNOME_RR_DPMS_UNKNOWN", "unknown" },
+      { 0, NULL, NULL }
+    };
+    GType g_define_type = g_enum_register_static ("GnomeRRDpmsModeType", values);
+
+    g_once_init_leave (&etype, g_define_type);
+  }
+  return etype;
+}
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-screen.h b/libgnome-desktop/gnome-rr/gnome-rr-screen.h
new file mode 100644
index 00000000..838be96b
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-screen.h
@@ -0,0 +1,173 @@
+/* gnome-rr-screen.h: Display information
+ *
+ * SPDX-FileCopyrightText: 2007, 2008, Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2020 NVIDIA CORPORATION
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ * 
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-rr/gnome-rr.h
+#endif
+
+#include <gdk/gdk.h>
+#include <gnome-rr/gnome-rr-types.h>
+
+G_BEGIN_DECLS
+
+#define GNOME_RR_TYPE_SCREEN (gnome_rr_screen_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GnomeRRScreen, gnome_rr_screen, GNOME_RR, SCREEN, GObject)
+
+struct _GnomeRRScreenClass
+{
+    /*< private >*/
+    GObjectClass parent_class;
+
+    /*< public >*/
+    void        (*changed)              (GnomeRRScreen *screen);
+    void        (*output_connected)     (GnomeRRScreen *screen,
+                                         GnomeRROutput *output);
+    void        (*output_disconnected)  (GnomeRRScreen *screen,
+                                         GnomeRROutput *output);
+};
+
+/* Error codes */
+
+#define GNOME_RR_ERROR (gnome_rr_error_quark ())
+
+GQuark gnome_rr_error_quark (void);
+
+typedef enum {
+    GNOME_RR_ERROR_UNKNOWN,            /* generic "fail" */
+    GNOME_RR_ERROR_NO_RANDR_EXTENSION, /* RANDR extension is not present */
+    GNOME_RR_ERROR_RANDR_ERROR,                /* generic/undescribed error from the underlying XRR API */
+    GNOME_RR_ERROR_BOUNDS_ERROR,       /* requested bounds of a CRTC are outside the maximum size */
+    GNOME_RR_ERROR_CRTC_ASSIGNMENT,    /* could not assign CRTCs to outputs */
+    GNOME_RR_ERROR_NO_MATCHING_CONFIG, /* none of the saved configurations matched the current configuration 
*/
+    GNOME_RR_ERROR_NO_DPMS_EXTENSION   /* DPMS extension is not present */
+} GnomeRRError;
+
+#define GNOME_RR_CONNECTOR_TYPE_PANEL   "Panel"  /* This is a built-in LCD */
+
+#define GNOME_RR_TYPE_OUTPUT    (gnome_rr_output_get_type ())
+#define GNOME_RR_TYPE_CRTC      (gnome_rr_crtc_get_type ())
+#define GNOME_RR_TYPE_MODE      (gnome_rr_mode_get_type ())
+#define GNOME_RR_TYPE_DPMS_MODE (gnome_rr_dpms_mode_get_type ())
+
+GType gnome_rr_output_get_type (void);
+GType gnome_rr_crtc_get_type (void);
+GType gnome_rr_mode_get_type (void);
+GType gnome_rr_dpms_mode_get_type (void);
+
+/* GnomeRRScreen */
+GnomeRRScreen * gnome_rr_screen_new                (GdkDisplay            *display,
+                                                   GError               **error);
+void            gnome_rr_screen_new_async          (GdkDisplay            *display,
+                                                    GAsyncReadyCallback    callback,
+                                                    gpointer               user_data);
+GnomeRRScreen * gnome_rr_screen_new_finish         (GAsyncResult          *result,
+                                                    GError               **error);
+GnomeRROutput **gnome_rr_screen_list_outputs       (GnomeRRScreen         *screen);
+GnomeRRCrtc **  gnome_rr_screen_list_crtcs         (GnomeRRScreen         *screen);
+GnomeRRMode **  gnome_rr_screen_list_modes         (GnomeRRScreen         *screen);
+GnomeRRMode **  gnome_rr_screen_list_clone_modes   (GnomeRRScreen        *screen);
+GnomeRRCrtc *   gnome_rr_screen_get_crtc_by_id     (GnomeRRScreen         *screen,
+                                                   guint32                id);
+gboolean        gnome_rr_screen_refresh            (GnomeRRScreen         *screen,
+                                                   GError               **error);
+GnomeRROutput * gnome_rr_screen_get_output_by_id   (GnomeRRScreen         *screen,
+                                                   guint32                id);
+GnomeRROutput * gnome_rr_screen_get_output_by_name (GnomeRRScreen         *screen,
+                                                   const char            *name);
+void            gnome_rr_screen_get_ranges         (GnomeRRScreen         *screen,
+                                                   int                   *min_width,
+                                                   int                   *max_width,
+                                                   int                   *min_height,
+                                                   int                   *max_height);
+
+gboolean        gnome_rr_screen_get_dpms_mode      (GnomeRRScreen         *screen,
+                                                    GnomeRRDpmsMode       *mode,
+                                                    GError               **error);
+gboolean        gnome_rr_screen_set_dpms_mode      (GnomeRRScreen         *screen,
+                                                    GnomeRRDpmsMode        mode,
+                                                    GError              **error);
+
+/* GnomeRROutput */
+guint32         gnome_rr_output_get_id                          (const GnomeRROutput  *output);
+const char *    gnome_rr_output_get_name                        (const GnomeRROutput  *output);
+const char *    gnome_rr_output_get_display_name                (const GnomeRROutput  *output);
+const guint8 *  gnome_rr_output_get_edid_data                   (GnomeRROutput        *output,
+                                                                gsize                *size);
+void            gnome_rr_output_get_ids_from_edid               (const GnomeRROutput  *output,
+                                                                 char                **vendor,
+                                                                 char                **product,
+                                                                 char                **serial);
+void            gnome_rr_output_get_physical_size               (const GnomeRROutput  *output,
+                                                                 int                  *width_mm,
+                                                                 int                  *height_mm);
+
+gint            gnome_rr_output_get_backlight                   (const GnomeRROutput  *output);
+gint            gnome_rr_output_get_min_backlight_step          (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_set_backlight                   (GnomeRROutput        *output,
+                                                                 int                   value,
+                                                                 GError              **error);
+gboolean        gnome_rr_output_set_color_transform             (GnomeRROutput        *output,
+                                                                 GnomeRRCTM            ctm,
+                                                                 GError              **error);
+
+GnomeRRCrtc **  gnome_rr_output_get_possible_crtcs              (const GnomeRROutput  *output);
+GnomeRRMode *   gnome_rr_output_get_current_mode                (const GnomeRROutput  *output);
+GnomeRRCrtc *   gnome_rr_output_get_crtc                        (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_is_builtin_display              (const GnomeRROutput  *output);
+void            gnome_rr_output_get_position                    (const GnomeRROutput  *output,
+                                                                int                  *x,
+                                                                int                  *y);
+gboolean        gnome_rr_output_can_clone                       (const GnomeRROutput  *output,
+                                                                const GnomeRROutput  *clone);
+GnomeRRMode **  gnome_rr_output_list_modes                      (const GnomeRROutput  *output);
+GnomeRRMode *   gnome_rr_output_get_preferred_mode              (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_supports_mode                   (const GnomeRROutput  *output,
+                                                                const GnomeRRMode    *mode);
+gboolean        gnome_rr_output_get_is_primary                  (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_get_is_underscanning            (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_supports_underscanning          (const GnomeRROutput  *output);
+gboolean        gnome_rr_output_supports_color_transform        (const GnomeRROutput  *output);
+
+/* GnomeRRMode */
+guint32         gnome_rr_mode_get_id            (GnomeRRMode *mode);
+guint           gnome_rr_mode_get_width         (GnomeRRMode *mode);
+guint           gnome_rr_mode_get_height        (GnomeRRMode *mode);
+int             gnome_rr_mode_get_freq          (GnomeRRMode *mode);
+double          gnome_rr_mode_get_freq_f        (GnomeRRMode *mode);
+gboolean        gnome_rr_mode_get_is_tiled      (GnomeRRMode *mode);
+gboolean        gnome_rr_mode_get_is_interlaced (GnomeRRMode *mode);
+
+/* GnomeRRCrtc */
+guint32         gnome_rr_crtc_get_id                    (GnomeRRCrtc      *crtc);
+
+gboolean        gnome_rr_crtc_can_drive_output          (GnomeRRCrtc      *crtc,
+                                                        GnomeRROutput    *output);
+GnomeRRMode *   gnome_rr_crtc_get_current_mode          (GnomeRRCrtc      *crtc);
+void            gnome_rr_crtc_get_position              (GnomeRRCrtc      *crtc,
+                                                        int              *x,
+                                                        int              *y);
+GnomeRRRotation gnome_rr_crtc_get_current_rotation      (GnomeRRCrtc      *crtc);
+GnomeRRRotation gnome_rr_crtc_get_rotations             (GnomeRRCrtc      *crtc);
+gboolean        gnome_rr_crtc_supports_rotation         (GnomeRRCrtc      *crtc,
+                                                        GnomeRRRotation   rotation);
+
+gboolean        gnome_rr_crtc_get_gamma                 (GnomeRRCrtc      *crtc,
+                                                        int              *size,
+                                                        unsigned short  **red,
+                                                        unsigned short  **green,
+                                                        unsigned short  **blue);
+gboolean        gnome_rr_crtc_set_gamma                 (GnomeRRCrtc      *crtc,
+                                                        int               size,
+                                                        unsigned short   *red,
+                                                        unsigned short   *green,
+                                                        unsigned short   *blue);
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-rr/gnome-rr-types.h b/libgnome-desktop/gnome-rr/gnome-rr-types.h
new file mode 100644
index 00000000..ec535933
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-types.h
@@ -0,0 +1,49 @@
+/* gnome-rr-types.h: Shared types
+ *
+ * SPDX-FileCopyrightText: 2021 Emmanuele Bassi
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-rr/gnome-rr.h
+#endif
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+    GNOME_RR_ROTATION_NEXT =   0,
+    GNOME_RR_ROTATION_0 =      (1 << 0),
+    GNOME_RR_ROTATION_90 =     (1 << 1),
+    GNOME_RR_ROTATION_180 =    (1 << 2),
+    GNOME_RR_ROTATION_270 =    (1 << 3),
+    GNOME_RR_REFLECT_X =       (1 << 4),
+    GNOME_RR_REFLECT_Y =       (1 << 5)
+} GnomeRRRotation;
+
+typedef enum {
+    GNOME_RR_DPMS_ON,
+    GNOME_RR_DPMS_STANDBY,
+    GNOME_RR_DPMS_SUSPEND,
+    GNOME_RR_DPMS_OFF,
+    GNOME_RR_DPMS_UNKNOWN
+} GnomeRRDpmsMode;
+
+/* Identical to drm_color_ctm from <drm_mode.h> */
+typedef struct {
+    /*< private >*/
+
+    /* Conversion matrix in S31.32 sign-magnitude (not two's complement!) format */
+    guint64 matrix[9];
+} GnomeRRCTM;
+
+typedef struct _GnomeRRScreen GnomeRRScreen;
+typedef struct _GnomeRROutput GnomeRROutput;
+typedef struct _GnomeRRCrtc GnomeRRCrtc;
+typedef struct _GnomeRRMode GnomeRRMode;
+
+G_END_DECLS
diff --git a/libgnome-desktop/gnome-rr/gnome-rr.h b/libgnome-desktop/gnome-rr/gnome-rr.h
new file mode 100644
index 00000000..29a90564
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr.h
@@ -0,0 +1,15 @@
+/* gnome-rr.h: Display information gathering
+ *
+ * SPDX-FileCopyrightText: 2021  Emmanuele Bassi
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#pragma once
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+# error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including 
gnome-rr/gnome-rr.h
+#endif
+
+#include <gnome-rr/gnome-rr-config.h>
+#include <gnome-rr/gnome-rr-output-info.h>
+#include <gnome-rr/gnome-rr-screen.h>
diff --git a/libgnome-desktop/gnome-rr/meson.build b/libgnome-desktop/gnome-rr/meson.build
new file mode 100644
index 00000000..6fd5451c
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/meson.build
@@ -0,0 +1,75 @@
+libgnome_rr_deps = [
+  libgnome_desktop_base_dep,
+  gtk4_dep,
+]
+
+libgnome_rr_sources = [
+  'gnome-rr-config.c',
+  'gnome-rr-output-info.c',
+  'gnome-rr-screen.c',
+]
+
+libgnome_rr_headers = [
+  'gnome-rr.h',
+  'gnome-rr-config.h',
+  'gnome-rr-output-info.h',
+  'gnome-rr-screen.h',
+  'gnome-rr-types.h',
+]
+
+install_headers(libgnome_rr_headers,
+  subdir: 'gnome-desktop-4.0/gnome-rr'
+)
+
+libgnome_rr = library('gnome-rr-4',
+  sources: [
+    libgnome_rr_sources,
+    dbus_xrandr_built_sources,
+  ],
+  dependencies: libgnome_rr_deps,
+  soversion: 0,
+  version: libversion,
+  c_args: libargs,
+  link_args: ui_ldflags,
+  install: true,
+  include_directories: [
+    include_directories('.'),
+    include_directories('..'),
+  ],
+)
+
+libgnome_rr_gir = gnome.generate_gir(libgnome_rr,
+  sources: [libgnome_rr_headers, libgnome_rr_sources],
+  export_packages: 'gnome-rr-4',
+  namespace: 'GnomeRR',
+  nsversion: '4.0',
+  includes: [libgnome_desktop_base_gir[0], 'Gdk-4.0'],
+  extra_args: ['-DGNOME_DESKTOP_USE_UNSTABLE_API', '--quiet', '--warn-all'],
+  identifier_prefix: 'GnomeRR',
+  symbol_prefix: 'gnome_rr',
+  install: true,
+)
+
+libgnome_rr_dep = declare_dependency(
+  sources: [
+    dbus_xrandr_built_sources,
+    libgnome_rr_gir,
+  ],
+  dependencies: libgnome_rr_deps,
+  link_with: libgnome_rr,
+  include_directories: [
+    include_directories('.'),
+    include_directories('..'),
+  ],
+)
+
+pkg.generate(
+  libgnome_rr,
+  requires: ['gsettings-desktop-schemas'],
+  version: meson.project_version(),
+  name: 'gnome-rr-4',
+  filebase: 'gnome-rr-4',
+  description: 'Display information utility library for GNOME desktop components',
+  subdirs: 'gnome-desktop-4.0',
+)
+
diff --git a/libgnome-desktop/meson.build b/libgnome-desktop/meson.build
index 6b6320a5..446c66fe 100644
--- a/libgnome-desktop/meson.build
+++ b/libgnome-desktop/meson.build
@@ -156,148 +156,8 @@ libgnome_desktop_base_dep = declare_dependency(
   ],
 )
 
-### gnome-bg
-
-libgnome_bg_sources = [
-  'gnome-bg.c',
-  'gnome-bg-slide-show.c',
-  'gnome-bg-crossfade.c',
-]
-
-libgnome_bg_headers = [
-  'gnome-bg.h',
-  'gnome-bg-crossfade.h',
-  'gnome-bg-slide-show.h',
-]
-
-install_headers(libgnome_bg_headers,
-  subdir: 'gnome-desktop-4.0/gnome-bg'
-)
-
-libgnome_bg_deps = [
-  libgnome_desktop_base_dep,
-  gtk3_dep,
-]
-
-libgnome_bg = library('gnome-bg-4',
-  sources: libgnome_bg_sources,
-  dependencies: libgnome_bg_deps,
-  soversion: 0,
-  version: libversion,
-  c_args: libargs,
-  link_args: ui_ldflags,
-  install: true,
-  include_directories: [
-    include_directories('.'),
-    include_directories('..'),
-  ],
-)
-
-libgnome_bg_gir = gnome.generate_gir(libgnome_bg,
-  sources: [libgnome_bg_headers, libgnome_bg_sources],
-  export_packages: 'gnome-bg-4',
-  namespace: 'GnomeBG',
-  nsversion: '4.0',
-  includes: [libgnome_desktop_base_gir[0], 'Gdk-3.0'],
-  extra_args: ['-DGNOME_DESKTOP_USE_UNSTABLE_API', '--quiet', '--warn-all'],
-  identifier_prefix: 'Gnome',
-  symbol_prefix: 'gnome',
-  install: true,
-)
-
-libgnome_bg_dep = declare_dependency(
-  sources: [
-    libgnome_bg_gir,
-  ],
-  dependencies: libgnome_bg_deps,
-  link_with: libgnome_bg,
-  include_directories: [
-    include_directories('.'),
-    include_directories('..'),
-  ],
-)
-
-pkg.generate(
-  libgnome_bg,
-  requires: ['gsettings-desktop-schemas'],
-  version: meson.project_version(),
-  name: 'gnome-bg-4',
-  filebase: 'gnome-bg-4',
-  description: 'Background image utility library for GNOME components',
-  subdirs: 'gnome-desktop-4.0',
-)
-
-### gnome-rr
-libgnome_rr_deps = [
-  libgnome_desktop_base_dep,
-  gtk3_dep,
-]
-
-libgnome_rr_sources = [
-  'gnome-rr.c',
-  'gnome-rr-config.c',
-  'gnome-rr-output-info.c',
-]
-
-libgnome_rr_headers = [
-  'gnome-rr.h',
-  'gnome-rr-config.h',
-]
-
-install_headers(libgnome_bg_headers,
-  subdir: 'gnome-desktop-4.0/libgnome-rr'
-)
-
-libgnome_rr = library('gnome-rr-4',
-  sources: [
-    libgnome_rr_sources,
-    dbus_xrandr_built_sources,
-  ],
-  dependencies: libgnome_rr_deps,
-  soversion: 0,
-  version: libversion,
-  c_args: libargs,
-  link_args: ui_ldflags,
-  install: true,
-  include_directories: [
-    include_directories('.'),
-    include_directories('..'),
-  ],
-)
-
-libgnome_rr_gir = gnome.generate_gir(libgnome_rr,
-  sources: [libgnome_rr_headers, libgnome_rr_sources],
-  export_packages: 'gnome-rr-4',
-  namespace: 'GnomeRR',
-  nsversion: '4.0',
-  includes: [libgnome_desktop_base_gir[0], 'Gtk-3.0'],
-  extra_args: ['-DGNOME_DESKTOP_USE_UNSTABLE_API', '--quiet', '--warn-all'],
-  identifier_prefix: 'Gnome',
-  symbol_prefix: 'gnome',
-  install: true,
-)
-
-libgnome_rr_dep = declare_dependency(
-  sources: [
-    libgnome_rr_gir,
-  ],
-  dependencies: libgnome_rr_deps,
-  link_with: libgnome_rr,
-  include_directories: [
-    include_directories('.'),
-    include_directories('..'),
-  ],
-)
-
-pkg.generate(
-  libgnome_rr,
-  requires: ['gsettings-desktop-schemas'],
-  version: meson.project_version(),
-  name: 'gnome-rr-4',
-  filebase: 'gnome-rr-4',
-  description: 'Display information utility library for GNOME desktop components',
-  subdirs: 'gnome-desktop-4.0',
-)
+subdir('gnome-bg')
+subdir('gnome-rr')
 
 ### Legacy ###
 introspection_sources = [
diff --git a/meson.build b/meson.build
index 20bc5f6b..a54d3fd0 100644
--- a/meson.build
+++ b/meson.build
@@ -25,7 +25,8 @@ compat_libversion = '19.1.7'
 compat_soversion = compat_libversion.split('.')[0]
 
 gdk_pixbuf_req = '>= 2.36.5'
-gtk_req = '>= 3.3.6'
+gtk3_req = '>= 3.3.6'
+gtk4_req = '>= 4.4.0'
 glib_req = '>= 2.53.0'
 xrandr_req = '>= 1.3'
 schemas_req = '>= 3.27.0'
@@ -47,7 +48,8 @@ test_execdir = libexecdir / 'installed-tests' / meson.project_name()
 versiondir = datadir / 'gnome'
 
 gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req)
-gtk3_dep = dependency('gtk+-3.0', version: gtk_req)
+gtk3_dep = dependency('gtk+-3.0', version: gtk3_req)
+gtk4_dep = dependency('gtk4', version: gtk4_req)
 glib_dep = dependency('glib-2.0', version: glib_req)
 gio_dep = dependency('gio-2.0', version: glib_req)
 gio_unix_dep = dependency('gio-unix-2.0', version: glib_req)


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