summaryrefslogtreecommitdiff
path: root/libmate-desktop/mate-rr-config.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 22:07:25 -0300
committerPerberos <[email protected]>2011-12-01 22:07:25 -0300
commitd00aab12b6ace2c3dda3efbc2aaa2646d78a9099 (patch)
tree8b0ca776e9234f7eabf3446f12df9a81abd466d0 /libmate-desktop/mate-rr-config.c
downloadmate-desktop-d00aab12b6ace2c3dda3efbc2aaa2646d78a9099.tar.bz2
mate-desktop-d00aab12b6ace2c3dda3efbc2aaa2646d78a9099.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libmate-desktop/mate-rr-config.c')
-rw-r--r--libmate-desktop/mate-rr-config.c1913
1 files changed, 1913 insertions, 0 deletions
diff --git a/libmate-desktop/mate-rr-config.c b/libmate-desktop/mate-rr-config.c
new file mode 100644
index 0000000..8efcf87
--- /dev/null
+++ b/libmate-desktop/mate-rr-config.c
@@ -0,0 +1,1913 @@
+/* mate-rr-config.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Soren Sandmann <[email protected]>
+ */
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+
+#undef MATE_DISABLE_DEPRECATED
+#include "libmateui/mate-rr-config.h"
+
+#include "edid.h"
+#include "mate-rr-private.h"
+
+#define CONFIG_INTENDED_BASENAME "monitors.xml"
+#define CONFIG_BACKUP_BASENAME "monitors.xml.backup"
+
+/* 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>
+ */
+
+/* A helper wrapper around the GMarkup parser stuff */
+static gboolean parse_file_gmarkup (const gchar *file,
+ const GMarkupParser *parser,
+ gpointer data,
+ GError **err);
+
+typedef struct CrtcAssignment CrtcAssignment;
+
+static gboolean crtc_assignment_apply (CrtcAssignment *assign,
+ guint32 timestamp,
+ GError **error);
+static CrtcAssignment *crtc_assignment_new (MateRRScreen *screen,
+ MateOutputInfo **outputs,
+ GError **error);
+static void crtc_assignment_free (CrtcAssignment *assign);
+static void output_free (MateOutputInfo *output);
+static MateOutputInfo *output_copy (MateOutputInfo *output);
+
+typedef struct Parser Parser;
+
+/* Parser for monitor configurations */
+struct Parser
+{
+ int config_file_version;
+ MateOutputInfo * output;
+ MateRRConfig * configuration;
+ GPtrArray * outputs;
+ GPtrArray * configurations;
+ GQueue * stack;
+};
+
+static int
+parse_int (const char *text)
+{
+ return strtol (text, NULL, 0);
+}
+
+static guint
+parse_uint (const char *text)
+{
+ return strtoul (text, NULL, 0);
+}
+
+static gboolean
+stack_is (Parser *parser,
+ 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 = parser->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 void
+handle_start_element (GMarkupParseContext *context,
+ const gchar *name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer user_data,
+ GError **err)
+{
+ Parser *parser = user_data;
+
+ if (strcmp (name, "output") == 0)
+ {
+ int i;
+ g_assert (parser->output == NULL);
+
+ parser->output = g_new0 (MateOutputInfo, 1);
+ parser->output->rotation = 0;
+
+ for (i = 0; attr_names[i] != NULL; ++i)
+ {
+ if (strcmp (attr_names[i], "name") == 0)
+ {
+ parser->output->name = g_strdup (attr_values[i]);
+ break;
+ }
+ }
+
+ if (!parser->output->name)
+ {
+ /* This really shouldn't happen, but it's better to make
+ * something up than to crash later.
+ */
+ g_warning ("Malformed monitor configuration file");
+
+ parser->output->name = g_strdup ("default");
+ }
+ parser->output->connected = FALSE;
+ parser->output->on = FALSE;
+ parser->output->primary = FALSE;
+ }
+ else if (strcmp (name, "configuration") == 0)
+ {
+ g_assert (parser->configuration == NULL);
+
+ parser->configuration = g_new0 (MateRRConfig, 1);
+ parser->configuration->clone = FALSE;
+ parser->configuration->outputs = NULL;
+ }
+ else if (strcmp (name, "monitors") == 0)
+ {
+ int i;
+
+ for (i = 0; attr_names[i] != NULL; i++)
+ {
+ if (strcmp (attr_names[i], "version") == 0)
+ {
+ parser->config_file_version = parse_int (attr_values[i]);
+ break;
+ }
+ }
+ }
+
+ g_queue_push_tail (parser->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+ const gchar *name,
+ gpointer user_data,
+ GError **err)
+{
+ Parser *parser = user_data;
+
+ if (strcmp (name, "output") == 0)
+ {
+ /* If no rotation properties were set, just use MATE_RR_ROTATION_0 */
+ if (parser->output->rotation == 0)
+ parser->output->rotation = MATE_RR_ROTATION_0;
+
+ g_ptr_array_add (parser->outputs, parser->output);
+
+ parser->output = NULL;
+ }
+ else if (strcmp (name, "configuration") == 0)
+ {
+ g_ptr_array_add (parser->outputs, NULL);
+ parser->configuration->outputs =
+ (MateOutputInfo **)g_ptr_array_free (parser->outputs, FALSE);
+ parser->outputs = g_ptr_array_new ();
+ g_ptr_array_add (parser->configurations, parser->configuration);
+ parser->configuration = NULL;
+ }
+
+ g_free (g_queue_pop_tail (parser->stack));
+}
+
+#define TOPLEVEL_ELEMENT (parser->config_file_version > 0 ? "monitors" : NULL)
+
+static void
+handle_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **err)
+{
+ Parser *parser = user_data;
+
+ if (stack_is (parser, "vendor", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->connected = TRUE;
+
+ strncpy (parser->output->vendor, text, 3);
+ parser->output->vendor[3] = 0;
+ }
+ else if (stack_is (parser, "clone", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ parser->configuration->clone = TRUE;
+ }
+ else if (stack_is (parser, "product", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->connected = TRUE;
+
+ parser->output->product = parse_int (text);
+ }
+ else if (stack_is (parser, "serial", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->connected = TRUE;
+
+ parser->output->serial = parse_uint (text);
+ }
+ else if (stack_is (parser, "width", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->width = parse_int (text);
+ }
+ else if (stack_is (parser, "x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->x = parse_int (text);
+ }
+ else if (stack_is (parser, "y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->y = parse_int (text);
+ }
+ else if (stack_is (parser, "height", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->height = parse_int (text);
+ }
+ else if (stack_is (parser, "rate", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->rate = parse_int (text);
+ }
+ else if (stack_is (parser, "rotation", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "normal") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_0;
+ }
+ else if (strcmp (text, "left") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_90;
+ }
+ else if (strcmp (text, "upside_down") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_180;
+ }
+ else if (strcmp (text, "right") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_270;
+ }
+ }
+ else if (stack_is (parser, "reflect_x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->rotation |= MATE_RR_REFLECT_X;
+ }
+ }
+ else if (stack_is (parser, "reflect_y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->rotation |= MATE_RR_REFLECT_Y;
+ }
+ }
+ else if (stack_is (parser, "primary", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->primary = TRUE;
+ }
+ }
+ else
+ {
+ /* Ignore other properties so we can expand the format in the future */
+ }
+}
+
+static void
+parser_free (Parser *parser)
+{
+ int i;
+ GList *list;
+
+ g_assert (parser != NULL);
+
+ if (parser->output)
+ output_free (parser->output);
+
+ if (parser->configuration)
+ mate_rr_config_free (parser->configuration);
+
+ for (i = 0; i < parser->outputs->len; ++i)
+ {
+ MateOutputInfo *output = parser->outputs->pdata[i];
+
+ output_free (output);
+ }
+
+ g_ptr_array_free (parser->outputs, TRUE);
+
+ for (i = 0; i < parser->configurations->len; ++i)
+ {
+ MateRRConfig *config = parser->configurations->pdata[i];
+
+ mate_rr_config_free (config);
+ }
+
+ g_ptr_array_free (parser->configurations, TRUE);
+
+ for (list = parser->stack->head; list; list = list->next)
+ g_free (list->data);
+ g_queue_free (parser->stack);
+
+ g_free (parser);
+}
+
+static MateRRConfig **
+configurations_read_from_file (const gchar *filename, GError **error)
+{
+ Parser *parser = g_new0 (Parser, 1);
+ MateRRConfig **result;
+ GMarkupParser callbacks = {
+ handle_start_element,
+ handle_end_element,
+ handle_text,
+ NULL, /* passthrough */
+ NULL, /* error */
+ };
+
+ parser->config_file_version = 0;
+ parser->configurations = g_ptr_array_new ();
+ parser->outputs = g_ptr_array_new ();
+ parser->stack = g_queue_new ();
+
+ if (!parse_file_gmarkup (filename, &callbacks, parser, error))
+ {
+ result = NULL;
+
+ g_assert (parser->outputs);
+ goto out;
+ }
+
+ g_assert (parser->outputs);
+
+ g_ptr_array_add (parser->configurations, NULL);
+ result = (MateRRConfig **)g_ptr_array_free (parser->configurations, FALSE);
+ parser->configurations = g_ptr_array_new ();
+
+ g_assert (parser->outputs);
+out:
+ parser_free (parser);
+
+ return result;
+}
+
+MateRRConfig *
+mate_rr_config_new_current (MateRRScreen *screen)
+{
+ MateRRConfig *config = g_new0 (MateRRConfig, 1);
+ GPtrArray *a = g_ptr_array_new ();
+ MateRROutput **rr_outputs;
+ int i;
+ int clone_width = -1;
+ int clone_height = -1;
+ int last_x;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+
+ rr_outputs = mate_rr_screen_list_outputs (screen);
+
+ config->clone = FALSE;
+
+ for (i = 0; rr_outputs[i] != NULL; ++i)
+ {
+ MateRROutput *rr_output = rr_outputs[i];
+ MateOutputInfo *output = g_new0 (MateOutputInfo, 1);
+ MateRRMode *mode = NULL;
+ const guint8 *edid_data = mate_rr_output_get_edid_data (rr_output);
+ MateRRCrtc *crtc;
+
+ output->name = g_strdup (mate_rr_output_get_name (rr_output));
+ output->connected = mate_rr_output_is_connected (rr_output);
+
+ if (!output->connected)
+ {
+ output->x = -1;
+ output->y = -1;
+ output->width = -1;
+ output->height = -1;
+ output->rate = -1;
+ output->rotation = MATE_RR_ROTATION_0;
+ }
+ else
+ {
+ MonitorInfo *info = NULL;
+
+ if (edid_data)
+ info = decode_edid (edid_data);
+
+ if (info)
+ {
+ memcpy (output->vendor, info->manufacturer_code,
+ sizeof (output->vendor));
+
+ output->product = info->product_code;
+ output->serial = info->serial_number;
+ output->aspect = info->aspect_ratio;
+ }
+ else
+ {
+ strcpy (output->vendor, "???");
+ output->product = 0;
+ output->serial = 0;
+ }
+
+ if (mate_rr_output_is_laptop (rr_output))
+ output->display_name = g_strdup (_("Laptop"));
+ else
+ output->display_name = make_display_name (info);
+
+ g_free (info);
+
+ crtc = mate_rr_output_get_crtc (rr_output);
+ mode = crtc? mate_rr_crtc_get_current_mode (crtc) : NULL;
+
+ if (crtc && mode)
+ {
+ output->on = TRUE;
+
+ mate_rr_crtc_get_position (crtc, &output->x, &output->y);
+ output->width = mate_rr_mode_get_width (mode);
+ output->height = mate_rr_mode_get_height (mode);
+ output->rate = mate_rr_mode_get_freq (mode);
+ output->rotation = mate_rr_crtc_get_current_rotation (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->on = FALSE;
+ config->clone = FALSE;
+ }
+
+ /* Get preferred size for the monitor */
+ mode = mate_rr_output_get_preferred_mode (rr_output);
+
+ if (!mode)
+ {
+ MateRRMode **modes = mate_rr_output_list_modes (rr_output);
+
+ /* FIXME: we should pick the "best" mode here, where best is
+ * sorted wrt
+ *
+ * - closest aspect ratio
+ * - mode area
+ * - refresh rate
+ * - We may want to extend randrwrap so that get_preferred
+ * returns that - although that could also depend on
+ * the crtc.
+ */
+ if (modes[0])
+ mode = modes[0];
+ }
+
+ if (mode)
+ {
+ output->pref_width = mate_rr_mode_get_width (mode);
+ output->pref_height = mate_rr_mode_get_height (mode);
+ }
+ else
+ {
+ /* Pick some random numbers. This should basically never happen */
+ output->pref_width = 1024;
+ output->pref_height = 768;
+ }
+ }
+
+ output->primary = mate_rr_output_get_is_primary (rr_output);
+
+ g_ptr_array_add (a, output);
+ }
+
+ g_ptr_array_add (a, NULL);
+
+ config->outputs = (MateOutputInfo **)g_ptr_array_free (a, FALSE);
+
+ /* Walk the outputs computing the right-most edge of all
+ * lit-up displays
+ */
+ last_x = 0;
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ last_x = MAX (last_x, output->x + output->width);
+ }
+ }
+
+ /* Now position all off displays to the right of the
+ * on displays
+ */
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->connected && !output->on)
+ {
+ output->x = last_x;
+ last_x = output->x + output->width;
+ }
+ }
+
+ g_assert (mate_rr_config_match (config, config));
+
+ return config;
+}
+
+static void
+output_free (MateOutputInfo *output)
+{
+ if (output->display_name)
+ g_free (output->display_name);
+
+ if (output->name)
+ g_free (output->name);
+
+ g_free (output);
+}
+
+static MateOutputInfo *
+output_copy (MateOutputInfo *output)
+{
+ MateOutputInfo *copy = g_new0 (MateOutputInfo, 1);
+
+ *copy = *output;
+
+ copy->name = g_strdup (output->name);
+ copy->display_name = g_strdup (output->display_name);
+
+ return copy;
+}
+
+static void
+outputs_free (MateOutputInfo **outputs)
+{
+ int i;
+
+ g_assert (outputs != NULL);
+
+ for (i = 0; outputs[i] != NULL; ++i)
+ output_free (outputs[i]);
+
+ g_free (outputs);
+}
+
+void
+mate_rr_config_free (MateRRConfig *config)
+{
+ g_return_if_fail (config != NULL);
+ outputs_free (config->outputs);
+
+ g_free (config);
+}
+
+static void
+configurations_free (MateRRConfig **configurations)
+{
+ int i;
+
+ g_assert (configurations != NULL);
+
+ for (i = 0; configurations[i] != NULL; ++i)
+ mate_rr_config_free (configurations[i]);
+
+ g_free (configurations);
+}
+
+static gboolean
+parse_file_gmarkup (const gchar *filename,
+ const GMarkupParser *parser,
+ gpointer data,
+ GError **err)
+{
+ GMarkupParseContext *context = NULL;
+ gchar *contents = NULL;
+ gboolean result = TRUE;
+ gsize len;
+
+ if (!g_file_get_contents (filename, &contents, &len, err))
+ {
+ result = FALSE;
+ goto out;
+ }
+
+ context = g_markup_parse_context_new (parser, 0, data, NULL);
+
+ if (!g_markup_parse_context_parse (context, contents, len, err))
+ {
+ result = FALSE;
+ goto out;
+ }
+
+ if (!g_markup_parse_context_end_parse (context, err))
+ {
+ result = FALSE;
+ goto out;
+ }
+
+out:
+ if (contents)
+ g_free (contents);
+
+ if (context)
+ g_markup_parse_context_free (context);
+
+ return result;
+}
+
+static gboolean
+output_match (MateOutputInfo *output1, MateOutputInfo *output2)
+{
+ g_assert (output1 != NULL);
+ g_assert (output2 != NULL);
+
+ if (strcmp (output1->name, output2->name) != 0)
+ return FALSE;
+
+ if (strcmp (output1->vendor, output2->vendor) != 0)
+ return FALSE;
+
+ if (output1->product != output2->product)
+ return FALSE;
+
+ if (output1->serial != output2->serial)
+ return FALSE;
+
+ if (output1->connected != output2->connected)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+output_equal (MateOutputInfo *output1, MateOutputInfo *output2)
+{
+ g_assert (output1 != NULL);
+ g_assert (output2 != NULL);
+
+ if (!output_match (output1, output2))
+ return FALSE;
+
+ if (output1->on != output2->on)
+ return FALSE;
+
+ if (output1->on)
+ {
+ 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;
+ }
+
+ return TRUE;
+}
+
+static MateOutputInfo *
+find_output (MateRRConfig *config, const char *name)
+{
+ int i;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *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
+mate_rr_config_match (MateRRConfig *c1, MateRRConfig *c2)
+{
+ int i;
+
+ for (i = 0; c1->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output1 = c1->outputs[i];
+ MateOutputInfo *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
+mate_rr_config_equal (MateRRConfig *c1,
+ MateRRConfig *c2)
+{
+ int i;
+
+ for (i = 0; c1->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output1 = c1->outputs[i];
+ MateOutputInfo *output2;
+
+ output2 = find_output (c2, output1->name);
+ if (!output2 || !output_equal (output1, output2))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static MateOutputInfo **
+make_outputs (MateRRConfig *config)
+{
+ GPtrArray *outputs;
+ MateOutputInfo *first_on;
+ int i;
+
+ outputs = g_ptr_array_new ();
+
+ first_on = NULL;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *old = config->outputs[i];
+ MateOutputInfo *new = output_copy (old);
+
+ if (old->on && !first_on)
+ first_on = old;
+
+ if (config->clone && new->on)
+ {
+ g_assert (first_on);
+
+ new->width = first_on->width;
+ new->height = first_on->height;
+ new->rotation = first_on->rotation;
+ new->x = 0;
+ new->y = 0;
+ }
+
+ g_ptr_array_add (outputs, new);
+ }
+
+ g_ptr_array_add (outputs, NULL);
+
+ return (MateOutputInfo **)g_ptr_array_free (outputs, FALSE);
+}
+
+gboolean
+mate_rr_config_applicable (MateRRConfig *configuration,
+ MateRRScreen *screen,
+ GError **error)
+{
+ MateOutputInfo **outputs;
+ CrtcAssignment *assign;
+ gboolean result;
+
+ g_return_val_if_fail (configuration != NULL, 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;
+ }
+
+ outputs_free (outputs);
+
+ return result;
+}
+
+/* Database management */
+
+static void
+ensure_config_directory (void)
+{
+ g_mkdir_with_parents (g_get_user_config_dir (), 0700);
+}
+
+char *
+mate_rr_config_get_backup_filename (void)
+{
+ ensure_config_directory ();
+ return g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+}
+
+char *
+mate_rr_config_get_intended_filename (void)
+{
+ ensure_config_directory ();
+ return g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
+}
+
+static const char *
+get_rotation_name (MateRRRotation r)
+{
+ if (r & MATE_RR_ROTATION_0)
+ return "normal";
+ if (r & MATE_RR_ROTATION_90)
+ return "left";
+ if (r & MATE_RR_ROTATION_180)
+ return "upside_down";
+ if (r & MATE_RR_ROTATION_270)
+ return "right";
+
+ return "normal";
+}
+
+static const char *
+yes_no (int x)
+{
+ return x? "yes" : "no";
+}
+
+static const char *
+get_reflect_x (MateRRRotation r)
+{
+ return yes_no (r & MATE_RR_REFLECT_X);
+}
+
+static const char *
+get_reflect_y (MateRRRotation r)
+{
+ return yes_no (r & MATE_RR_REFLECT_Y);
+}
+
+static void
+emit_configuration (MateRRConfig *config,
+ GString *string)
+{
+ int j;
+
+ g_string_append_printf (string, " <configuration>\n");
+
+ g_string_append_printf (string, " <clone>%s</clone>\n", yes_no (config->clone));
+
+ for (j = 0; config->outputs[j] != NULL; ++j)
+ {
+ MateOutputInfo *output = config->outputs[j];
+
+ g_string_append_printf (
+ string, " <output name=\"%s\">\n", output->name);
+
+ if (output->connected && *output->vendor != '\0')
+ {
+ g_string_append_printf (
+ string, " <vendor>%s</vendor>\n", output->vendor);
+ g_string_append_printf (
+ string, " <product>0x%04x</product>\n", output->product);
+ g_string_append_printf (
+ string, " <serial>0x%08x</serial>\n", output->serial);
+ }
+
+ /* An unconnected output which is on does not make sense */
+ if (output->connected && output->on)
+ {
+ g_string_append_printf (
+ string, " <width>%d</width>\n", output->width);
+ g_string_append_printf (
+ string, " <height>%d</height>\n", output->height);
+ g_string_append_printf (
+ string, " <rate>%d</rate>\n", output->rate);
+ g_string_append_printf (
+ string, " <x>%d</x>\n", output->x);
+ g_string_append_printf (
+ string, " <y>%d</y>\n", output->y);
+ g_string_append_printf (
+ string, " <rotation>%s</rotation>\n", get_rotation_name (output->rotation));
+ g_string_append_printf (
+ string, " <reflect_x>%s</reflect_x>\n", get_reflect_x (output->rotation));
+ g_string_append_printf (
+ string, " <reflect_y>%s</reflect_y>\n", get_reflect_y (output->rotation));
+ g_string_append_printf (
+ string, " <primary>%s</primary>\n", yes_no (output->primary));
+ }
+
+ g_string_append_printf (string, " </output>\n");
+ }
+
+ g_string_append_printf (string, " </configuration>\n");
+}
+
+void
+mate_rr_config_sanitize (MateRRConfig *config)
+{
+ int i;
+ int x_offset, y_offset;
+ gboolean found;
+
+ /* Offset everything by the top/left-most coordinate to
+ * make sure the configuration starts at (0, 0)
+ */
+ x_offset = y_offset = G_MAXINT;
+ for (i = 0; config->outputs[i]; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ x_offset = MIN (x_offset, output->x);
+ y_offset = MIN (y_offset, output->y);
+ }
+ }
+
+ for (i = 0; config->outputs[i]; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ output->x -= x_offset;
+ output->y -= y_offset;
+ }
+ }
+
+ /* Only one primary, please */
+ found = FALSE;
+ for (i = 0; config->outputs[i]; ++i)
+ {
+ if (config->outputs[i]->primary)
+ {
+ if (found)
+ {
+ config->outputs[i]->primary = FALSE;
+ }
+ else
+ {
+ found = TRUE;
+ }
+ }
+ }
+}
+
+
+gboolean
+mate_rr_config_save (MateRRConfig *configuration, GError **error)
+{
+ MateRRConfig **configurations;
+ GString *output;
+ int i;
+ gchar *intended_filename;
+ gchar *backup_filename;
+ gboolean result;
+
+ g_return_val_if_fail (configuration != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ output = g_string_new ("");
+
+ backup_filename = mate_rr_config_get_backup_filename ();
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ configurations = configurations_read_from_file (intended_filename, NULL); /* NULL-GError */
+
+ g_string_append_printf (output, "<monitors version=\"1\">\n");
+
+ if (configurations)
+ {
+ for (i = 0; configurations[i] != NULL; ++i)
+ {
+ if (!mate_rr_config_match (configurations[i], configuration))
+ emit_configuration (configurations[i], output);
+ }
+
+ configurations_free (configurations);
+ }
+
+ emit_configuration (configuration, output);
+
+ g_string_append_printf (output, "</monitors>\n");
+
+ /* backup the file first */
+ rename (intended_filename, backup_filename); /* no error checking because the intended file may not even exist */
+
+ result = g_file_set_contents (intended_filename, output->str, -1, error);
+
+ if (!result)
+ rename (backup_filename, intended_filename); /* no error checking because the backup may not even exist */
+
+ g_free (backup_filename);
+ g_free (intended_filename);
+
+ return result;
+}
+
+static MateRRConfig *
+mate_rr_config_copy (MateRRConfig *config)
+{
+ MateRRConfig *copy = g_new0 (MateRRConfig, 1);
+ int i;
+ GPtrArray *array = g_ptr_array_new ();
+
+ copy->clone = config->clone;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ g_ptr_array_add (array, output_copy (config->outputs[i]));
+
+ g_ptr_array_add (array, NULL);
+ copy->outputs = (MateOutputInfo **)g_ptr_array_free (array, FALSE);
+
+ return copy;
+}
+
+static MateRRConfig *
+config_new_stored (MateRRScreen *screen, const char *filename, GError **error)
+{
+ MateRRConfig *current;
+ MateRRConfig **configs;
+ MateRRConfig *result;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ current = mate_rr_config_new_current (screen);
+
+ configs = configurations_read_from_file (filename, error);
+
+ result = NULL;
+ if (configs)
+ {
+ int i;
+
+ for (i = 0; configs[i] != NULL; ++i)
+ {
+ if (mate_rr_config_match (configs[i], current))
+ {
+ result = mate_rr_config_copy (configs[i]);
+ break;
+ }
+ }
+
+ if (result == NULL)
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_NO_MATCHING_CONFIG,
+ _("none of the saved display configurations matched the active configuration"));
+
+ configurations_free (configs);
+ }
+
+ mate_rr_config_free (current);
+
+ return result;
+}
+
+MateRRConfig *
+mate_rr_config_new_stored (MateRRScreen *screen, GError **error)
+{
+ char *intended_filename;
+ MateRRConfig *config;
+
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ config = config_new_stored (screen, intended_filename, error);
+
+ g_free (intended_filename);
+
+ return config;
+}
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+gboolean
+mate_rr_config_apply (MateRRConfig *config,
+ MateRRScreen *screen,
+ GError **error)
+{
+ return mate_rr_config_apply_with_time (config, screen, GDK_CURRENT_TIME, error);
+}
+#endif
+
+gboolean
+mate_rr_config_apply_with_time (MateRRConfig *config,
+ MateRRScreen *screen,
+ guint32 timestamp,
+ GError **error)
+{
+ CrtcAssignment *assignment;
+ MateOutputInfo **outputs;
+ gboolean result = FALSE;
+
+ outputs = make_outputs (config);
+
+ assignment = crtc_assignment_new (screen, outputs, error);
+
+ outputs_free (outputs);
+
+ if (assignment)
+ {
+ if (crtc_assignment_apply (assignment, timestamp, error))
+ result = TRUE;
+
+ crtc_assignment_free (assignment);
+
+ gdk_flush ();
+ }
+
+ return result;
+}
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+/**
+ * mate_rr_config_apply_stored:
+ * @screen: A #MateRRScreen
+ * @error: Location to store error, or %NULL
+ *
+ * See the documentation for mate_rr_config_apply_from_filename(). This
+ * function simply calls that other function with a filename of
+ * mate_rr_config_get_intended_filename().
+
+ * @Deprecated: 2.26: Use mate_rr_config_apply_from_filename() instead and pass it
+ * the filename from mate_rr_config_get_intended_filename().
+ */
+gboolean
+mate_rr_config_apply_stored (MateRRScreen *screen, GError **error)
+{
+ char *filename;
+ gboolean result;
+
+ filename = mate_rr_config_get_intended_filename ();
+ result = mate_rr_config_apply_from_filename_with_time (screen, filename, GDK_CURRENT_TIME, error);
+ g_free (filename);
+
+ return result;
+}
+#endif
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+/* mate_rr_config_apply_from_filename:
+ * @screen: A #MateRRScreen
+ * @filename: Path of the file to look in for stored RANDR configurations.
+ * @error: Location to store error, or %NULL
+ *
+ * First, this function refreshes the @screen to match the current RANDR
+ * configuration from the X server. Then, it tries to load the file in
+ * @filename and looks for suitable matching RANDR configurations in the file;
+ * if one is found, that configuration will be applied to the current set of
+ * RANDR outputs.
+ *
+ * Typically, @filename is the result of mate_rr_config_get_intended_filename() or
+ * mate_rr_config_get_backup_filename().
+ *
+ * Returns: TRUE if the RANDR configuration was loaded and applied from
+ * $(XDG_CONFIG_HOME)/monitors.xml, or FALSE otherwise:
+ *
+ * If the current RANDR configuration could not be refreshed, the @error will
+ * have a domain of #MATE_RR_ERROR and a corresponding error code.
+ *
+ * If the file in question is loaded successfully but the configuration cannot
+ * be applied, the @error will have a domain of #MATE_RR_ERROR. Note that an
+ * error code of #MATE_RR_ERROR_NO_MATCHING_CONFIG is not a real error; it
+ * simply means that there were no stored configurations that match the current
+ * set of RANDR outputs.
+ *
+ * If the file in question cannot be loaded, the @error will have a domain of
+ * #G_FILE_ERROR. Note that an error code of G_FILE_ERROR_NOENT is not really
+ * an error, either; it means that there was no stored configuration file and so
+ * nothing is changed.
+ *
+ * @Deprecated: 2.28: use mate_rr_config_apply_from_filename_with_time() instead.
+ */
+gboolean
+mate_rr_config_apply_from_filename (MateRRScreen *screen, const char *filename, GError **error)
+{
+ return mate_rr_config_apply_from_filename_with_time (screen, filename, GDK_CURRENT_TIME, error);
+}
+#endif
+
+/* mate_rr_config_apply_from_filename_with_time:
+ * @screen: A #MateRRScreen
+ * @filename: Path of the file to look in for stored RANDR configurations.
+ * @timestamp: X server timestamp from the event that causes the screen configuration to change (a user's button press, for example)
+ * @error: Location to store error, or %NULL
+ *
+ * First, this function refreshes the @screen to match the current RANDR
+ * configuration from the X server. Then, it tries to load the file in
+ * @filename and looks for suitable matching RANDR configurations in the file;
+ * if one is found, that configuration will be applied to the current set of
+ * RANDR outputs.
+ *
+ * Typically, @filename is the result of mate_rr_config_get_intended_filename() or
+ * mate_rr_config_get_backup_filename().
+ *
+ * Returns: TRUE if the RANDR configuration was loaded and applied from
+ * $(XDG_CONFIG_HOME)/monitors.xml, or FALSE otherwise:
+ *
+ * If the current RANDR configuration could not be refreshed, the @error will
+ * have a domain of #MATE_RR_ERROR and a corresponding error code.
+ *
+ * If the file in question is loaded successfully but the configuration cannot
+ * be applied, the @error will have a domain of #MATE_RR_ERROR. Note that an
+ * error code of #MATE_RR_ERROR_NO_MATCHING_CONFIG is not a real error; it
+ * simply means that there were no stored configurations that match the current
+ * set of RANDR outputs.
+ *
+ * If the file in question cannot be loaded, the @error will have a domain of
+ * #G_FILE_ERROR. Note that an error code of G_FILE_ERROR_NOENT is not really
+ * an error, either; it means that there was no stored configuration file and so
+ * nothing is changed.
+ */
+gboolean
+mate_rr_config_apply_from_filename_with_time (MateRRScreen *screen, const char *filename, guint32 timestamp, GError **error)
+{
+ MateRRConfig *stored;
+ GError *my_error;
+
+ g_return_val_if_fail (screen != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ my_error = NULL;
+ if (!mate_rr_screen_refresh (screen, &my_error)) {
+ if (my_error) {
+ g_propagate_error (error, my_error);
+ return FALSE; /* This is a genuine error */
+ }
+
+ /* This means the screen didn't change, so just proceed */
+ }
+
+ stored = config_new_stored (screen, filename, error);
+
+ if (stored)
+ {
+ gboolean result;
+
+ result = mate_rr_config_apply_with_time (stored, screen, timestamp, error);
+
+ mate_rr_config_free (stored);
+
+ return result;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/*
+ * CRTC assignment
+ */
+typedef struct CrtcInfo CrtcInfo;
+
+struct CrtcInfo
+{
+ MateRRMode *mode;
+ int x;
+ int y;
+ MateRRRotation rotation;
+ GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+ MateRRScreen *screen;
+ GHashTable *info;
+ MateRROutput *primary;
+};
+
+static gboolean
+can_clone (CrtcInfo *info,
+ MateRROutput *output)
+{
+ int i;
+
+ for (i = 0; i < info->outputs->len; ++i)
+ {
+ MateRROutput *clone = info->outputs->pdata[i];
+
+ if (!mate_rr_output_can_clone (clone, output))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment *assign,
+ MateRRCrtc *crtc,
+ MateRRMode *mode,
+ int x,
+ int y,
+ MateRRRotation rotation,
+ gboolean primary,
+ MateRROutput *output,
+ GError **error)
+{
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ guint32 crtc_id;
+ const char *output_name;
+
+ crtc_id = mate_rr_crtc_get_id (crtc);
+ output_name = mate_rr_output_get_name (output);
+
+ if (!mate_rr_crtc_can_drive_output (crtc, output))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("CRTC %d cannot drive output %s"), crtc_id, output_name);
+ return FALSE;
+ }
+
+ if (!mate_rr_output_supports_mode (output, mode))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("output %s does not support mode %dx%d@%dHz"),
+ output_name,
+ mate_rr_mode_get_width (mode),
+ mate_rr_mode_get_height (mode),
+ mate_rr_mode_get_freq (mode));
+ return FALSE;
+ }
+
+ if (!mate_rr_crtc_supports_rotation (crtc, rotation))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("CRTC %d does not support rotation=%s"),
+ crtc_id,
+ get_rotation_name (rotation));
+ return FALSE;
+ }
+
+ if (info)
+ {
+ if (!(info->mode == mode &&
+ info->x == x &&
+ info->y == y &&
+ info->rotation == rotation))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_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 = %s, new rotation = %s"),
+ output_name,
+ mate_rr_mode_get_id (info->mode), mate_rr_mode_get_id (mode),
+ info->x, info->y,
+ x, y,
+ get_rotation_name (info->rotation), get_rotation_name (rotation));
+ return FALSE;
+ }
+
+ if (!can_clone (info, output))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_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
+ {
+ CrtcInfo *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,
+ MateRRCrtc *crtc,
+ MateRROutput *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);
+}
+
+typedef struct {
+ guint32 timestamp;
+ gboolean has_error;
+ GError **error;
+} ConfigureCrtcState;
+
+static void
+configure_crtc (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ MateRRCrtc *crtc = key;
+ CrtcInfo *info = value;
+ ConfigureCrtcState *state = data;
+
+ if (state->has_error)
+ return;
+
+ if (!mate_rr_crtc_set_config_with_time (crtc,
+ state->timestamp,
+ info->x, info->y,
+ info->mode,
+ info->rotation,
+ (MateRROutput **)info->outputs->pdata,
+ info->outputs->len,
+ state->error))
+ state->has_error = TRUE;
+}
+
+static gboolean
+mode_is_rotated (CrtcInfo *info)
+{
+ if ((info->rotation & MATE_RR_ROTATION_270) ||
+ (info->rotation & MATE_RR_ROTATION_90))
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+crtc_is_rotated (MateRRCrtc *crtc)
+{
+ MateRRRotation r = mate_rr_crtc_get_current_rotation (crtc);
+
+ if ((r & MATE_RR_ROTATION_270) ||
+ (r & MATE_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 (MateRRScreen *screen,
+ MateOutputInfo **outputs,
+ CrtcAssignment *assignment,
+ GError **error)
+{
+ MateRRCrtc **crtcs = mate_rr_screen_list_crtcs (screen);
+ MateOutputInfo *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->on)
+ {
+ 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)
+ {
+ MateRRCrtc *crtc = crtcs[i];
+ int crtc_id = mate_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)
+ {
+ MateRROutput *mate_rr_output = mate_rr_screen_get_output_by_name (screen, output->name);
+ MateRRMode **modes = mate_rr_output_list_modes (mate_rr_output);
+ int j;
+
+ for (j = 0; modes[j] != NULL; ++j)
+ {
+ MateRRMode *mode = modes[j];
+ int mode_width;
+ int mode_height;
+ int mode_freq;
+
+ mode_width = mate_rr_mode_get_width (mode);
+ mode_height = mate_rr_mode_get_height (mode);
+ mode_freq = mate_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,
+ mate_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, mate_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, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("could not assign CRTCs to outputs:\n%s"),
+ str);
+ else
+ g_set_error (error, MATE_RR_ERROR, MATE_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)
+ {
+ MateRRCrtc *crtc = list->data;
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ int w, h;
+
+ w = mate_rr_mode_get_width (info->mode);
+ h = mate_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 (MateRRScreen *screen, MateOutputInfo **outputs, GError **error)
+{
+ CrtcAssignment *assignment = g_new0 (CrtcAssignment, 1);
+
+ 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;
+ int required_pixels, min_pixels, max_pixels;
+
+ get_required_virtual_size (assignment, &width, &height);
+
+ mate_rr_screen_get_ranges (
+ screen, &min_width, &max_width, &min_height, &max_height);
+
+ required_pixels = width * height;
+ min_pixels = min_width * min_height;
+ max_pixels = max_width * max_height;
+
+ if (required_pixels < min_pixels || required_pixels > max_pixels)
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_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;
+}
+
+static gboolean
+crtc_assignment_apply (CrtcAssignment *assign, guint32 timestamp, GError **error)
+{
+ MateRRCrtc **all_crtcs = mate_rr_screen_list_crtcs (assign->screen);
+ int width, height;
+ int i;
+ int min_width, max_width, min_height, max_height;
+ int width_mm, height_mm;
+ gboolean success = TRUE;
+
+ /* Compute size of the screen */
+ get_required_virtual_size (assign, &width, &height);
+
+ mate_rr_screen_get_ranges (
+ assign->screen, &min_width, &max_width, &min_height, &max_height);
+
+ /* We should never get here if the dimensions don't fit in the virtual size,
+ * but just in case we do, fix it up.
+ */
+ width = MAX (min_width, width);
+ width = MIN (max_width, width);
+ height = MAX (min_height, height);
+ height = MIN (max_height, height);
+
+ /* FMQ: do we need to check the sizes instead of clamping them? */
+
+ /* Grab the server while we fiddle with the CRTCs and the screen, so that
+ * apps that listen for RANDR notifications will only receive the final
+ * status.
+ */
+
+ gdk_x11_display_grab (gdk_screen_get_display (assign->screen->gdk_screen));
+
+ /* Turn off all crtcs that are currently displaying outside the new screen,
+ * or are not used in the new setup
+ */
+ for (i = 0; all_crtcs[i] != NULL; ++i)
+ {
+ MateRRCrtc *crtc = all_crtcs[i];
+ MateRRMode *mode = mate_rr_crtc_get_current_mode (crtc);
+ int x, y;
+
+ if (mode)
+ {
+ int w, h;
+ mate_rr_crtc_get_position (crtc, &x, &y);
+
+ w = mate_rr_mode_get_width (mode);
+ h = mate_rr_mode_get_height (mode);
+
+ if (crtc_is_rotated (crtc))
+ {
+ int tmp = h;
+ h = w;
+ w = tmp;
+ }
+
+ if (x + w > width || y + h > height || !g_hash_table_lookup (assign->info, crtc))
+ {
+ if (!mate_rr_crtc_set_config_with_time (crtc, timestamp, 0, 0, NULL, MATE_RR_ROTATION_0, NULL, 0, error))
+ {
+ success = FALSE;
+ break;
+ }
+
+ }
+ }
+ }
+
+ /* The 'physical size' of an X screen is meaningless if that screen
+ * can consist of many monitors. So just pick a size that make the
+ * dpi 96.
+ *
+ * Firefox and Evince apparently believe what X tells them.
+ */
+ width_mm = (width / 96.0) * 25.4 + 0.5;
+ height_mm = (height / 96.0) * 25.4 + 0.5;
+
+ if (success)
+ {
+ ConfigureCrtcState state;
+
+ mate_rr_screen_set_size (assign->screen, width, height, width_mm, height_mm);
+
+ state.timestamp = timestamp;
+ state.has_error = FALSE;
+ state.error = error;
+
+ g_hash_table_foreach (assign->info, configure_crtc, &state);
+
+ success = !state.has_error;
+ }
+
+ mate_rr_screen_set_primary_output (assign->screen, assign->primary);
+
+ gdk_x11_display_ungrab (gdk_screen_get_display (assign->screen->gdk_screen));
+
+ return success;
+}