summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2021-11-08 16:36:05 +0000
committerEmmanuele Bassi <ebassi@gnome.org>2021-11-19 13:16:25 +0000
commitc2c7414c17533f190cd33f710a70d41680cb8406 (patch)
treebe8b643c23ad4e350dacda974f95bd2b3ec742ba
parent7c95b7bed7c33d74d88be258b44aadce8bca8540 (diff)
downloadgnome-desktop-c2c7414c17533f190cd33f710a70d41680cb8406.tar.gz
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.
-rw-r--r--libgnome-desktop/gnome-bg/gnome-bg-slide-show.c838
-rw-r--r--libgnome-desktop/gnome-bg/gnome-bg-slide-show.h96
-rw-r--r--libgnome-desktop/gnome-bg/gnome-bg-symbols.map (renamed from libgnome-desktop/ui-symbol.map)2
-rw-r--r--libgnome-desktop/gnome-bg/gnome-bg.c2401
-rw-r--r--libgnome-desktop/gnome-bg/gnome-bg.h89
-rw-r--r--libgnome-desktop/gnome-bg/meson.build75
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-config.c1152
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-config.h52
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-output-info.c595
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-output-info.h76
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-private.h155
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-screen.c2464
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-screen.h173
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-symbols.map6
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr-types.h49
-rw-r--r--libgnome-desktop/gnome-rr/gnome-rr.h15
-rw-r--r--libgnome-desktop/gnome-rr/meson.build81
-rw-r--r--libgnome-desktop/meson.build150
-rw-r--r--meson.build6
19 files changed, 8324 insertions, 151 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/ui-symbol.map b/libgnome-desktop/gnome-bg/gnome-bg-symbols.map
index 671c1560..c1f244ca 100644
--- a/libgnome-desktop/ui-symbol.map
+++ b/libgnome-desktop/gnome-bg/gnome-bg-symbols.map
@@ -1,6 +1,6 @@
{
global:
- gnome_*;
+ gnome_bg_*;
local:
*;
};
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..9753afba
--- /dev/null
+++ b/libgnome-desktop/gnome-bg/meson.build
@@ -0,0 +1,75 @@
+### 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_ldflags = []
+libgnome_bg_symbols_map = '-Wl,--version-script=@0@'.format(meson.current_source_dir() / 'gnome-bg-symbols.map')
+if cc.has_link_argument(libgnome_bg_symbols_map)
+ libgnome_bg_ldflags += libgnome_bg_symbols_map
+endif
+
+libgnome_bg = library('gnome-bg-4',
+ sources: libgnome_bg_sources,
+ dependencies: libgnome_bg_deps,
+ soversion: 0,
+ version: libversion,
+ c_args: libargs,
+ link_args: libgnome_bg_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-symbols.map b/libgnome-desktop/gnome-rr/gnome-rr-symbols.map
new file mode 100644
index 00000000..8a6f2d62
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/gnome-rr-symbols.map
@@ -0,0 +1,6 @@
+{
+global:
+ gnome_rr_*;
+local:
+ *;
+};
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..6825e2a4
--- /dev/null
+++ b/libgnome-desktop/gnome-rr/meson.build
@@ -0,0 +1,81 @@
+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_ldflags = []
+libgnome_rr_symbols_map = '-Wl,--version-script=@0@'.format(meson.current_source_dir() / 'gnome-rr-symbols.map')
+if cc.has_link_argument(libgnome_rr_symbols_map)
+ libgnome_rr_ldflags += libgnome_rr_symbols_map
+endif
+
+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: libgnome_rr_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..f636f532 100644
--- a/libgnome-desktop/meson.build
+++ b/libgnome-desktop/meson.build
@@ -51,12 +51,6 @@ if cc.has_link_argument(base_symbol_map)
base_ldflags += base_symbol_map
endif
-ui_ldflags = []
-ui_symbol_map = '-Wl,--version-script=@0@'.format(meson.current_source_dir() / 'ui-symbol.map')
-if cc.has_link_argument(ui_symbol_map)
- ui_ldflags += ui_symbol_map
-endif
-
### gnome-desktop-base
libgnome_desktop_base_sources = [
'gnome-desktop-thumbnail.c',
@@ -156,148 +150,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)