diff options
author | Emmanuele Bassi <ebassi@gnome.org> | 2021-11-08 16:36:05 +0000 |
---|---|---|
committer | Emmanuele Bassi <ebassi@gnome.org> | 2021-11-19 13:16:25 +0000 |
commit | c2c7414c17533f190cd33f710a70d41680cb8406 (patch) | |
tree | be8b643c23ad4e350dacda974f95bd2b3ec742ba | |
parent | 7c95b7bed7c33d74d88be258b44aadce8bca8540 (diff) | |
download | gnome-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.
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, + ¤t_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, + ¤t_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) |