[mutter] Migrate old monitor configuration files to new system



commit bc3162460fa9da6bcadb60cec7318eef24e4000a
Author: Jonas Ådahl <jadahl gmail com>
Date:   Thu Aug 10 15:12:41 2017 +0800

    Migrate old monitor configuration files to new system
    
    This commit changes the new configuration system to use monitors.xml
    instead of monitors-experimental.xml. When starting up and the
    monitors.xml file is loaded, if a legacy monitors.xml file is
    discovered (it has the version number 1), an attempt is made to migrate
    the stored configuration onto the new system.
    
    This is done in two steps:
    
    1) Parsing and translation of the old configuration. This works by
    parsing file using the mostly the old parser, but then translating the
    resulting configuration structs into the new configuration system. As
    the legacy configuration system doesn't carry over some state (such as
    tiling and scale used), some things are not available. For tiling, the
    migration paths makes an attempt to discover tiled monitors by
    comparing EDID data, and guessing what the main tile is. Determination
    of the scale of a migrated configuration is postponed until the
    configuration is actually applied. This works by flagging the
    configuration as 'migrated'.
    
    2) Finishing the migration when applying. When a configuration with the
    'migrated' flag is retrieved from the configuration store, the final
    step of the migration is taken place. This involves calculating the
    preferred scale given the mode configured, while making sure this
    doesn't result in any overlapping logical monitor regions etc.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=777732

 src/Makefile.am                              |    2 +
 src/backends/meta-monitor-config-manager.c   |   48 +-
 src/backends/meta-monitor-config-manager.h   |   11 +-
 src/backends/meta-monitor-config-migration.c | 1193 ++++++++++++++++++++++++++
 src/backends/meta-monitor-config-migration.h |   38 +
 src/backends/meta-monitor-config-store.c     |  182 ++++-
 src/backends/meta-monitor-config-store.h     |    8 +-
 src/backends/meta-monitor-manager.c          |    6 +-
 src/tests/monitor-test-utils.c               |    3 +-
 9 files changed, 1454 insertions(+), 37 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index d39b73b..5c0a553 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -111,6 +111,8 @@ libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES =      \
        backends/meta-logical-monitor.h         \
        backends/meta-monitor-config-manager.c  \
        backends/meta-monitor-config-manager.h  \
+       backends/meta-monitor-config-migration.c        \
+       backends/meta-monitor-config-migration.h        \
        backends/meta-monitor-config-store.c    \
        backends/meta-monitor-config-store.h    \
        backends/meta-monitor.c                 \
diff --git a/src/backends/meta-monitor-config-manager.c b/src/backends/meta-monitor-config-manager.c
index 90514b6..d84f7c5 100644
--- a/src/backends/meta-monitor-config-manager.c
+++ b/src/backends/meta-monitor-config-manager.c
@@ -23,6 +23,7 @@
 
 #include "backends/meta-monitor-config-manager.h"
 
+#include "backends/meta-monitor-config-migration.h"
 #include "backends/meta-monitor-config-store.h"
 #include "backends/meta-monitor-manager-private.h"
 #include "core/boxes-private.h"
@@ -361,10 +362,12 @@ create_key_for_current_state (MetaMonitorManager *monitor_manager)
 MetaMonitorsConfig *
 meta_monitor_config_manager_get_stored (MetaMonitorConfigManager *config_manager)
 {
+  MetaMonitorManager *monitor_manager = config_manager->monitor_manager;
   MetaMonitorsConfigKey *config_key;
   MetaMonitorsConfig *config;
+  GError *error = NULL;
 
-  config_key = create_key_for_current_state (config_manager->monitor_manager);
+  config_key = create_key_for_current_state (monitor_manager);
   if (!config_key)
     return NULL;
 
@@ -372,6 +375,22 @@ meta_monitor_config_manager_get_stored (MetaMonitorConfigManager *config_manager
                                              config_key);
   meta_monitors_config_key_free (config_key);
 
+  if (!config)
+    return NULL;
+
+  if (config->flags & META_MONITORS_CONFIG_FLAG_MIGRATED)
+    {
+      if (!meta_finish_monitors_config_migration (monitor_manager, config,
+                                                  &error))
+        {
+          g_warning ("Failed to finish monitors config migration: %s",
+                     error->message);
+          g_error_free (error);
+          meta_monitor_config_store_remove (config_manager->config_store, config);
+          return NULL;
+        }
+    }
+
   return config;
 }
 
@@ -591,7 +610,8 @@ meta_monitor_config_manager_create_linear (MetaMonitorConfigManager *config_mana
       x += logical_monitor_config->layout.width;
     }
 
-  return meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  return meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 MetaMonitorsConfig *
@@ -619,7 +639,8 @@ meta_monitor_config_manager_create_fallback (MetaMonitorConfigManager *config_ma
   logical_monitor_configs = g_list_append (NULL,
                                            primary_logical_monitor_config);
 
-  return meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  return meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 MetaMonitorsConfig *
@@ -694,7 +715,8 @@ meta_monitor_config_manager_create_suggested (MetaMonitorConfigManager *config_m
   if (!logical_monitor_configs)
     return NULL;
 
-  return meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  return meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 static MetaMonitorsConfig *
@@ -741,7 +763,8 @@ create_for_builtin_display_rotation (MetaMonitorConfigManager *config_manager,
   logical_monitor_config->transform = transform;
 
   return meta_monitors_config_new (g_list_append (NULL, logical_monitor_config),
-                                   config_manager->current_config->layout_mode);
+                                   config_manager->current_config->layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 MetaMonitorsConfig *
@@ -761,6 +784,7 @@ static MetaMonitorsConfig *
 create_for_switch_config_all_mirror (MetaMonitorConfigManager *config_manager)
 {
   MetaMonitorManager *monitor_manager = config_manager->monitor_manager;
+  MetaLogicalMonitorLayoutMode layout_mode;
   MetaLogicalMonitorConfig *logical_monitor_config = NULL;
   GList *monitor_configs = NULL;
   gint common_mode_w = 0, common_mode_h = 0;
@@ -859,8 +883,10 @@ create_for_switch_config_all_mirror (MetaMonitorConfigManager *config_manager)
     .monitor_configs = monitor_configs
   };
 
+  layout_mode = meta_monitor_manager_get_default_layout_mode (monitor_manager);
   return meta_monitors_config_new (g_list_append (NULL, logical_monitor_config),
-                                   meta_monitor_manager_get_default_layout_mode (monitor_manager));
+                                   layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 static MetaMonitorsConfig *
@@ -899,7 +925,8 @@ create_for_switch_config_external (MetaMonitorConfigManager *config_manager)
       x += logical_monitor_config->layout.width;
     }
 
-  return meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  return meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 static MetaMonitorsConfig *
@@ -927,7 +954,8 @@ create_for_switch_config_builtin (MetaMonitorConfigManager *config_manager)
   logical_monitor_configs = g_list_append (NULL,
                                            primary_logical_monitor_config);
 
-  return meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  return meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                   META_MONITORS_CONFIG_FLAG_NONE);
 }
 
 MetaMonitorsConfig *
@@ -1117,7 +1145,8 @@ meta_monitors_config_key_equal (gconstpointer data_a,
 
 MetaMonitorsConfig *
 meta_monitors_config_new (GList                       *logical_monitor_configs,
-                          MetaLogicalMonitorLayoutMode layout_mode)
+                          MetaLogicalMonitorLayoutMode layout_mode,
+                          MetaMonitorsConfigFlag       flags)
 {
   MetaMonitorsConfig *config;
 
@@ -1125,6 +1154,7 @@ meta_monitors_config_new (GList                       *logical_monitor_configs,
   config->logical_monitor_configs = logical_monitor_configs;
   config->key = meta_monitors_config_key_new (logical_monitor_configs);
   config->layout_mode = layout_mode;
+  config->flags = flags;
 
   return config;
 }
diff --git a/src/backends/meta-monitor-config-manager.h b/src/backends/meta-monitor-config-manager.h
index 8f6f3dc..aa2646e 100644
--- a/src/backends/meta-monitor-config-manager.h
+++ b/src/backends/meta-monitor-config-manager.h
@@ -51,6 +51,12 @@ typedef struct _MetaMonitorsConfigKey
   GList *monitor_specs;
 } MetaMonitorsConfigKey;
 
+typedef enum _MetaMonitorsConfigFlag
+{
+  META_MONITORS_CONFIG_FLAG_NONE = 0,
+  META_MONITORS_CONFIG_FLAG_MIGRATED = (1 << 0),
+} MetaMonitorsConfigFlag;
+
 struct _MetaMonitorsConfig
 {
   GObject parent;
@@ -58,6 +64,8 @@ struct _MetaMonitorsConfig
   MetaMonitorsConfigKey *key;
   GList *logical_monitor_configs;
 
+  MetaMonitorsConfigFlag flags;
+
   MetaLogicalMonitorLayoutMode layout_mode;
 };
 
@@ -101,7 +109,8 @@ MetaMonitorsConfig * meta_monitor_config_manager_get_previous (MetaMonitorConfig
 void meta_monitor_config_manager_save_current (MetaMonitorConfigManager *config_manager);
 
 MetaMonitorsConfig * meta_monitors_config_new (GList                       *logical_monitor_configs,
-                                               MetaLogicalMonitorLayoutMode layout_mode);
+                                               MetaLogicalMonitorLayoutMode layout_mode,
+                                               MetaMonitorsConfigFlag       flags);
 
 unsigned int meta_monitors_config_key_hash (gconstpointer config_key);
 
diff --git a/src/backends/meta-monitor-config-migration.c b/src/backends/meta-monitor-config-migration.c
new file mode 100644
index 0000000..b3f2111
--- /dev/null
+++ b/src/backends/meta-monitor-config-migration.c
@@ -0,0 +1,1193 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2001, 2002 Havoc Pennington
+ * Copyright (C) 2002, 2003 Red Hat Inc.
+ * Some ICCCM manager selection code derived from fvwm2,
+ * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ * Copyright (C) 2013, 2017 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Portions of this file are derived from gnome-desktop/libgnome-desktop/gnome-rr-config.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * Copyright 2010 Giovanni Campagna
+ *
+ * Author: Soren Sandmann <sandmann redhat com>
+ */
+
+#include "config.h"
+
+#include "backends/meta-monitor-config-migration.h"
+
+#include <gio/gio.h>
+#include <string.h>
+
+#include "backends/meta-monitor-config-manager.h"
+#include "backends/meta-monitor-config-store.h"
+#include "backends/meta-monitor-manager-private.h"
+#include "meta/boxes.h"
+
+#define META_MONITORS_CONFIG_MIGRATION_ERROR (meta_monitors_config_migration_error_quark ())
+static GQuark meta_monitors_config_migration_error_quark (void);
+
+G_DEFINE_QUARK (meta-monitors-config-migration-error-quark,
+                meta_monitors_config_migration_error)
+
+enum _MetaConfigMigrationError
+{
+  META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_TILED,
+  META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_MAIN_TILE
+} MetaConfigMigrationError;
+
+typedef struct
+{
+  char *connector;
+  char *vendor;
+  char *product;
+  char *serial;
+} MetaOutputKey;
+
+typedef struct
+{
+  gboolean enabled;
+  MetaRectangle rect;
+  float refresh_rate;
+  MetaMonitorTransform transform;
+
+  gboolean is_primary;
+  gboolean is_presentation;
+  gboolean is_underscanning;
+} MetaOutputConfig;
+
+typedef struct _MetaLegacyMonitorsConfig
+{
+  MetaOutputKey *keys;
+  MetaOutputConfig *outputs;
+  unsigned int n_outputs;
+} MetaLegacyMonitorsConfig;
+
+typedef enum
+{
+  STATE_INITIAL,
+  STATE_MONITORS,
+  STATE_CONFIGURATION,
+  STATE_OUTPUT,
+  STATE_OUTPUT_FIELD,
+  STATE_CLONE
+} ParserState;
+
+typedef struct
+{
+  ParserState state;
+  int unknown_count;
+
+  GArray *key_array;
+  GArray *output_array;
+  MetaOutputKey key;
+  MetaOutputConfig output;
+
+  char *output_field;
+
+  GHashTable *configs;
+} ConfigParser;
+
+static MetaLegacyMonitorsConfig *
+legacy_config_new (void)
+{
+  return g_new0 (MetaLegacyMonitorsConfig, 1);
+}
+
+static void
+legacy_config_free (gpointer data)
+{
+  MetaLegacyMonitorsConfig *config = data;
+
+  g_free (config->keys);
+  g_free (config->outputs);
+  g_free (config);
+}
+
+static unsigned long
+output_key_hash (const MetaOutputKey *key)
+{
+  return (g_str_hash (key->connector) ^
+          g_str_hash (key->vendor) ^
+          g_str_hash (key->product) ^
+          g_str_hash (key->serial));
+}
+
+static gboolean
+output_key_equal (const MetaOutputKey *one,
+                  const MetaOutputKey *two)
+{
+  return (strcmp (one->connector, two->connector) == 0 &&
+          strcmp (one->vendor, two->vendor) == 0 &&
+          strcmp (one->product, two->product) == 0 &&
+          strcmp (one->serial, two->serial) == 0);
+}
+
+static unsigned int
+legacy_config_hash (gconstpointer data)
+{
+  const MetaLegacyMonitorsConfig *config = data;
+  unsigned int i, hash;
+
+  hash = 0;
+  for (i = 0; i < config->n_outputs; i++)
+    hash ^= output_key_hash (&config->keys[i]);
+
+  return hash;
+}
+
+static gboolean
+legacy_config_equal (gconstpointer one,
+                     gconstpointer two)
+{
+  const MetaLegacyMonitorsConfig *c_one = one;
+  const MetaLegacyMonitorsConfig *c_two = two;
+  unsigned int i;
+  gboolean ok;
+
+  if (c_one->n_outputs != c_two->n_outputs)
+    return FALSE;
+
+  ok = TRUE;
+  for (i = 0; i < c_one->n_outputs && ok; i++)
+    ok = output_key_equal (&c_one->keys[i],
+                           &c_two->keys[i]);
+
+  return ok;
+}
+
+static void
+free_output_key (MetaOutputKey *key)
+{
+  g_free (key->connector);
+  g_free (key->vendor);
+  g_free (key->product);
+  g_free (key->serial);
+}
+
+static void
+handle_start_element (GMarkupParseContext *context,
+                      const char          *element_name,
+                      const char         **attribute_names,
+                      const char         **attribute_values,
+                      gpointer             user_data,
+                      GError             **error)
+{
+  ConfigParser *parser = user_data;
+
+  switch (parser->state)
+    {
+    case STATE_INITIAL:
+      {
+        char *version;
+
+        if (strcmp (element_name, "monitors") != 0)
+          {
+            g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                         "Invalid document element %s", element_name);
+            return;
+          }
+
+        if (!g_markup_collect_attributes (element_name,
+                                          attribute_names,
+                                          attribute_values,
+                                          error,
+                                          G_MARKUP_COLLECT_STRING,
+                                          "version", &version,
+                                          G_MARKUP_COLLECT_INVALID))
+          return;
+
+        if (strcmp (version, "1") != 0)
+          {
+            g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                         "Invalid or unsupported version %s", version);
+            return;
+          }
+
+        parser->state = STATE_MONITORS;
+        return;
+      }
+
+    case STATE_MONITORS:
+      {
+        if (strcmp (element_name, "configuration") != 0)
+          {
+            g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                         "Invalid toplevel element %s", element_name);
+            return;
+          }
+
+        parser->key_array = g_array_new (FALSE, FALSE,
+                                         sizeof (MetaOutputKey));
+        parser->output_array = g_array_new (FALSE, FALSE,
+                                            sizeof (MetaOutputConfig));
+        parser->state = STATE_CONFIGURATION;
+        return;
+      }
+
+    case STATE_CONFIGURATION:
+      {
+        if (strcmp (element_name, "clone") == 0 &&
+            parser->unknown_count == 0)
+          {
+            parser->state = STATE_CLONE;
+          }
+        else if (strcmp (element_name, "output") == 0 &&
+                 parser->unknown_count == 0)
+          {
+            char *name;
+
+            if (!g_markup_collect_attributes (element_name,
+                                              attribute_names,
+                                              attribute_values,
+                                              error,
+                                              G_MARKUP_COLLECT_STRING,
+                                              "name", &name,
+                                              G_MARKUP_COLLECT_INVALID))
+              return;
+
+            memset (&parser->key, 0, sizeof (MetaOutputKey));
+            memset (&parser->output, 0, sizeof (MetaOutputConfig));
+
+            parser->key.connector = g_strdup (name);
+            parser->state = STATE_OUTPUT;
+          }
+        else
+          {
+            parser->unknown_count++;
+          }
+
+        return;
+      }
+
+    case STATE_OUTPUT:
+      {
+        if ((strcmp (element_name, "vendor") == 0 ||
+             strcmp (element_name, "product") == 0 ||
+             strcmp (element_name, "serial") == 0 ||
+             strcmp (element_name, "width") == 0 ||
+             strcmp (element_name, "height") == 0 ||
+             strcmp (element_name, "rate") == 0 ||
+             strcmp (element_name, "x") == 0 ||
+             strcmp (element_name, "y") == 0 ||
+             strcmp (element_name, "rotation") == 0 ||
+             strcmp (element_name, "reflect_x") == 0 ||
+             strcmp (element_name, "reflect_y") == 0 ||
+             strcmp (element_name, "primary") == 0 ||
+             strcmp (element_name, "presentation") == 0 ||
+             strcmp (element_name, "underscanning") == 0) &&
+            parser->unknown_count == 0)
+          {
+            parser->state = STATE_OUTPUT_FIELD;
+
+            parser->output_field = g_strdup (element_name);
+          }
+        else
+          {
+            parser->unknown_count++;
+          }
+
+        return;
+      }
+
+    case STATE_CLONE:
+    case STATE_OUTPUT_FIELD:
+      {
+        g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                     "Unexpected element %s", element_name);
+        return;
+      }
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+                    const char          *element_name,
+                    gpointer             user_data,
+                    GError             **error)
+{
+  ConfigParser *parser = user_data;
+
+  switch (parser->state)
+    {
+    case STATE_MONITORS:
+      {
+        parser->state = STATE_INITIAL;
+        return;
+      }
+
+    case STATE_CONFIGURATION:
+      {
+        if (strcmp (element_name, "configuration") == 0 &&
+            parser->unknown_count == 0)
+          {
+            MetaLegacyMonitorsConfig *config = legacy_config_new ();
+
+            g_assert (parser->key_array->len == parser->output_array->len);
+
+            config->n_outputs = parser->key_array->len;
+            config->keys = (void*)g_array_free (parser->key_array, FALSE);
+            config->outputs = (void*)g_array_free (parser->output_array, FALSE);
+
+            g_hash_table_replace (parser->configs, config, config);
+
+            parser->key_array = NULL;
+            parser->output_array = NULL;
+            parser->state = STATE_MONITORS;
+          }
+        else
+          {
+            parser->unknown_count--;
+
+            g_assert (parser->unknown_count >= 0);
+          }
+
+        return;
+      }
+
+    case STATE_OUTPUT:
+      {
+        if (strcmp (element_name, "output") == 0 && parser->unknown_count == 0)
+          {
+            if (parser->key.vendor == NULL ||
+                parser->key.product == NULL ||
+                parser->key.serial == NULL)
+              {
+                /* Disconnected output, ignore */
+                free_output_key (&parser->key);
+              }
+            else
+              {
+                if (parser->output.rect.width == 0 ||
+                    parser->output.rect.height == 0)
+                  parser->output.enabled = FALSE;
+                else
+                  parser->output.enabled = TRUE;
+
+                g_array_append_val (parser->key_array, parser->key);
+                g_array_append_val (parser->output_array, parser->output);
+              }
+
+            memset (&parser->key, 0, sizeof (MetaOutputKey));
+            memset (&parser->output, 0, sizeof (MetaOutputConfig));
+
+            parser->state = STATE_CONFIGURATION;
+          }
+        else
+          {
+            parser->unknown_count--;
+
+            g_assert (parser->unknown_count >= 0);
+          }
+
+        return;
+      }
+
+    case STATE_CLONE:
+      {
+        parser->state = STATE_CONFIGURATION;
+        return;
+      }
+
+    case STATE_OUTPUT_FIELD:
+      {
+        g_free (parser->output_field);
+        parser->output_field = NULL;
+
+        parser->state = STATE_OUTPUT;
+        return;
+      }
+
+    case STATE_INITIAL:
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+read_int (const char *text,
+          gsize       text_len,
+          gint       *field,
+          GError    **error)
+{
+  char buf[64];
+  gint64 v;
+  char *end;
+
+  strncpy (buf, text, text_len);
+  buf[MIN (63, text_len)] = 0;
+
+  v = g_ascii_strtoll (buf, &end, 10);
+
+  /* Limit reasonable values (actual limits are a lot smaller that these) */
+  if (*end || v < 0 || v > G_MAXINT16)
+    g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                 "Expected a number, got %s", buf);
+  else
+    *field = v;
+}
+
+static void
+read_float (const char  *text,
+            gsize        text_len,
+            gfloat      *field,
+            GError     **error)
+{
+  char buf[64];
+  gfloat v;
+  char *end;
+
+  strncpy (buf, text, text_len);
+  buf[MIN (63, text_len)] = 0;
+
+  v = g_ascii_strtod (buf, &end);
+
+  /* Limit reasonable values (actual limits are a lot smaller that these) */
+  if (*end)
+    g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                 "Expected a number, got %s", buf);
+  else
+    *field = v;
+}
+
+static gboolean
+read_bool (const char  *text,
+           gsize        text_len,
+           GError     **error)
+{
+  if (strncmp (text, "no", text_len) == 0)
+    return FALSE;
+  else if (strncmp (text, "yes", text_len) == 0)
+    return TRUE;
+  else
+    g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                 "Invalid boolean value %.*s", (int)text_len, text);
+
+  return FALSE;
+}
+
+static gboolean
+is_all_whitespace (const char *text,
+                   gsize       text_len)
+{
+  gsize i;
+
+  for (i = 0; i < text_len; i++)
+    if (!g_ascii_isspace (text[i]))
+      return FALSE;
+
+  return TRUE;
+}
+
+static void
+handle_text (GMarkupParseContext *context,
+             const gchar         *text,
+             gsize                text_len,
+             gpointer             user_data,
+             GError             **error)
+{
+  ConfigParser *parser = user_data;
+
+  switch (parser->state)
+    {
+    case STATE_MONITORS:
+      {
+        if (!is_all_whitespace (text, text_len))
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                       "Unexpected content at this point");
+        return;
+      }
+
+    case STATE_CONFIGURATION:
+      {
+        if (parser->unknown_count == 0)
+          {
+            if (!is_all_whitespace (text, text_len))
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Unexpected content at this point");
+          }
+        else
+          {
+            /* Handling unknown element, ignore */
+          }
+
+        return;
+      }
+
+    case STATE_OUTPUT:
+      {
+        if (parser->unknown_count == 0)
+          {
+            if (!is_all_whitespace (text, text_len))
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Unexpected content at this point");
+          }
+        else
+          {
+            /* Handling unknown element, ignore */
+          }
+        return;
+      }
+
+    case STATE_CLONE:
+      {
+        /* Ignore the clone flag */
+        return;
+      }
+
+    case STATE_OUTPUT_FIELD:
+      {
+        if (strcmp (parser->output_field, "vendor") == 0)
+          parser->key.vendor = g_strndup (text, text_len);
+        else if (strcmp (parser->output_field, "product") == 0)
+          parser->key.product = g_strndup (text, text_len);
+        else if (strcmp (parser->output_field, "serial") == 0)
+          parser->key.serial = g_strndup (text, text_len);
+        else if (strcmp (parser->output_field, "width") == 0)
+          read_int (text, text_len, &parser->output.rect.width, error);
+        else if (strcmp (parser->output_field, "height") == 0)
+          read_int (text, text_len, &parser->output.rect.height, error);
+        else if (strcmp (parser->output_field, "rate") == 0)
+          read_float (text, text_len, &parser->output.refresh_rate, error);
+        else if (strcmp (parser->output_field, "x") == 0)
+          read_int (text, text_len, &parser->output.rect.x, error);
+        else if (strcmp (parser->output_field, "y") == 0)
+          read_int (text, text_len, &parser->output.rect.y, error);
+        else if (strcmp (parser->output_field, "rotation") == 0)
+          {
+            if (strncmp (text, "normal", text_len) == 0)
+              parser->output.transform = META_MONITOR_TRANSFORM_NORMAL;
+            else if (strncmp (text, "left", text_len) == 0)
+              parser->output.transform = META_MONITOR_TRANSFORM_90;
+            else if (strncmp (text, "upside_down", text_len) == 0)
+              parser->output.transform = META_MONITOR_TRANSFORM_180;
+            else if (strncmp (text, "right", text_len) == 0)
+              parser->output.transform = META_MONITOR_TRANSFORM_270;
+            else
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Invalid rotation type %.*s", (int)text_len, text);
+          }
+        else if (strcmp (parser->output_field, "reflect_x") == 0)
+          parser->output.transform += read_bool (text, text_len, error) ?
+            META_MONITOR_TRANSFORM_FLIPPED : 0;
+        else if (strcmp (parser->output_field, "reflect_y") == 0)
+          {
+            if (read_bool (text, text_len, error))
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Y reflection is not supported");
+          }
+        else if (strcmp (parser->output_field, "primary") == 0)
+          parser->output.is_primary = read_bool (text, text_len, error);
+        else if (strcmp (parser->output_field, "presentation") == 0)
+          parser->output.is_presentation = read_bool (text, text_len, error);
+        else if (strcmp (parser->output_field, "underscanning") == 0)
+          parser->output.is_underscanning = read_bool (text, text_len, error);
+        else
+          g_assert_not_reached ();
+        return;
+      }
+
+    case STATE_INITIAL:
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static const GMarkupParser config_parser = {
+  .start_element = handle_start_element,
+  .end_element = handle_end_element,
+  .text = handle_text,
+};
+
+static GHashTable *
+load_config_file (GFile   *file,
+                  GError **error)
+{
+  g_autofree char *contents = NULL;
+  gsize size;
+  g_autoptr (GMarkupParseContext) context = NULL;
+  ConfigParser parser = { 0 };
+
+  if (!g_file_load_contents (file, NULL, &contents, &size, NULL, error))
+    return FALSE;
+
+  parser.configs = g_hash_table_new_full (legacy_config_hash,
+                                          legacy_config_equal,
+                                          legacy_config_free,
+                                          NULL);
+  parser.state = STATE_INITIAL;
+
+  context = g_markup_parse_context_new (&config_parser,
+                                        G_MARKUP_TREAT_CDATA_AS_TEXT |
+                                        G_MARKUP_PREFIX_ERROR_POSITION,
+                                        &parser, NULL);
+  if (!g_markup_parse_context_parse (context, contents, size, error))
+    {
+      if (parser.key_array)
+        g_array_free (parser.key_array, TRUE);
+      if (parser.output_array)
+        g_array_free (parser.output_array, TRUE);
+
+      free_output_key (&parser.key);
+      g_hash_table_destroy (parser.configs);
+
+      return NULL;
+    }
+
+  return parser.configs;
+}
+
+static MetaMonitorConfig *
+create_monitor_config (MetaOutputKey    *output_key,
+                       MetaOutputConfig *output_config,
+                       int               mode_width,
+                       int               mode_height,
+                       GError          **error)
+{
+  MetaMonitorModeSpec *mode_spec;
+  MetaMonitorSpec *monitor_spec;
+  MetaMonitorConfig *monitor_config;
+
+  mode_spec = g_new0 (MetaMonitorModeSpec, 1);
+  *mode_spec = (MetaMonitorModeSpec) {
+    .width = mode_width,
+    .height = mode_height,
+    .refresh_rate = output_config->refresh_rate
+  };
+
+  if (!meta_verify_monitor_mode_spec (mode_spec, error))
+    {
+      g_free (mode_spec);
+      return NULL;
+    }
+
+  monitor_spec = g_new0 (MetaMonitorSpec, 1);
+  *monitor_spec = (MetaMonitorSpec) {
+    .connector = output_key->connector,
+    .vendor = output_key->vendor,
+    .product = output_key->product,
+    .serial = output_key->serial
+  };
+
+  monitor_config = g_new0 (MetaMonitorConfig, 1);
+  *monitor_config = (MetaMonitorConfig) {
+    .monitor_spec = monitor_spec,
+    .mode_spec = mode_spec,
+    .enable_underscanning = output_config->is_underscanning
+  };
+
+  if (!meta_verify_monitor_config (monitor_config, error))
+    {
+      meta_monitor_config_free (monitor_config);
+      return NULL;
+    }
+
+  return monitor_config;
+}
+
+typedef struct _MonitorTile
+{
+  MetaOutputKey *output_key;
+  MetaOutputConfig *output_config;
+} MonitorTile;
+
+static MetaMonitorConfig *
+try_derive_tiled_monitor_config (MetaLegacyMonitorsConfig *config,
+                                 MetaOutputKey            *output_key,
+                                 MetaOutputConfig         *output_config,
+                                 MetaMonitorConfigStore   *config_store,
+                                 MetaRectangle            *out_layout,
+                                 GError                  **error)
+{
+  MonitorTile top_left_tile = { 0 };
+  MonitorTile top_right_tile = { 0 };
+  MonitorTile bottom_left_tile = { 0 };
+  MonitorTile bottom_right_tile = { 0 };
+  MonitorTile origin_tile = { 0 };
+  MetaMonitorTransform transform = output_config->transform;
+  unsigned int i;
+  int max_x = 0;
+  int min_x = INT_MAX;
+  int max_y = 0;
+  int min_y = INT_MAX;
+  int mode_width = 0;
+  int mode_height = 0;
+  MetaMonitorConfig *monitor_config;
+
+  /*
+   * In order to derive a monitor configuration for a tiled monitor,
+   * try to find the origin tile, then combine the discovered output
+   * tiles to given the configured transform a monitor mode.
+   *
+   * If the origin tile is not the main tile (tile always enabled
+   * even for non-tiled modes), this will fail, but since infermation
+   * about tiling is lost, there is no way to discover it.
+   */
+
+  for (i = 0; i < config->n_outputs; i++)
+    {
+      MetaOutputKey *other_output_key = &config->keys[i];
+      MetaOutputConfig *other_output_config = &config->outputs[i];
+      MetaRectangle *rect;
+
+      if (strcmp (output_key->vendor, other_output_key->vendor) != 0 ||
+          strcmp (output_key->product, other_output_key->product) != 0 ||
+          strcmp (output_key->serial, other_output_key->serial) != 0)
+        continue;
+
+      rect = &other_output_config->rect;
+      min_x = MIN (min_x, rect->x);
+      min_y = MIN (min_y, rect->y);
+      max_x = MAX (max_x, rect->x + rect->width);
+      max_y = MAX (max_y, rect->y + rect->height);
+
+      if (min_x == rect->x &&
+          min_y == rect->y)
+        {
+          top_left_tile = (MonitorTile) {
+            .output_key = other_output_key,
+            .output_config = other_output_config
+          };
+        }
+      if (max_x == rect->x + rect->width &&
+          min_y == rect->y)
+        {
+          top_right_tile = (MonitorTile) {
+            .output_key = other_output_key,
+            .output_config = other_output_config
+          };
+        }
+      if (min_x == rect->x &&
+          max_y == rect->y + rect->height)
+        {
+          bottom_left_tile = (MonitorTile) {
+            .output_key = other_output_key,
+            .output_config = other_output_config
+          };
+        }
+      if (max_x == rect->x + rect->width &&
+          max_y == rect->y + rect->height)
+        {
+          bottom_right_tile = (MonitorTile) {
+            .output_key = other_output_key,
+            .output_config = other_output_config
+          };
+        }
+    }
+
+  if (top_left_tile.output_key == bottom_right_tile.output_key)
+    {
+      g_set_error_literal (error,
+                           META_MONITORS_CONFIG_MIGRATION_ERROR,
+                           META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_TILED,
+                           "Not a tiled monitor");
+      return NULL;
+    }
+
+  switch (transform)
+    {
+    case META_MONITOR_TRANSFORM_NORMAL:
+      origin_tile = top_left_tile;
+      mode_width = max_x - min_x;
+      mode_height = max_y - min_y;
+      break;
+    case META_MONITOR_TRANSFORM_90:
+      origin_tile = bottom_left_tile;
+      mode_width = max_y - min_y;
+      mode_height = max_x - min_x;
+      break;
+    case META_MONITOR_TRANSFORM_180:
+      origin_tile = bottom_right_tile;
+      mode_width = max_x - min_x;
+      mode_height = max_y - min_y;
+      break;
+    case META_MONITOR_TRANSFORM_270:
+      origin_tile = top_right_tile;
+      mode_width = max_y - min_y;
+      mode_height = max_x - min_x;
+      break;
+    case META_MONITOR_TRANSFORM_FLIPPED:
+      origin_tile = bottom_left_tile;
+      mode_width = max_x - min_x;
+      mode_height = max_y - min_y;
+      break;
+    case META_MONITOR_TRANSFORM_FLIPPED_90:
+      origin_tile = bottom_right_tile;
+      mode_width = max_y - min_y;
+      mode_height = max_x - min_x;
+      break;
+    case META_MONITOR_TRANSFORM_FLIPPED_180:
+      origin_tile = top_right_tile;
+      mode_width = max_x - min_x;
+      mode_height = max_y - min_y;
+      break;
+    case META_MONITOR_TRANSFORM_FLIPPED_270:
+      origin_tile = top_left_tile;
+      mode_width = max_y - min_y;
+      mode_height = max_x - min_x;
+      break;
+    }
+
+  g_assert (origin_tile.output_key);
+  g_assert (origin_tile.output_config);
+
+  if (origin_tile.output_key != output_key)
+    {
+      g_set_error_literal (error,
+                           META_MONITORS_CONFIG_MIGRATION_ERROR,
+                           META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_MAIN_TILE,
+                           "Not the main tile");
+      return NULL;
+    }
+
+  monitor_config = create_monitor_config (origin_tile.output_key,
+                                          origin_tile.output_config,
+                                          mode_width, mode_height,
+                                          error);
+  if (!monitor_config)
+    return NULL;
+
+  *out_layout = (MetaRectangle) {
+    .x = min_x,
+    .y = min_y,
+    .width = max_x - min_x,
+    .height = max_y - min_y
+  };
+
+  return monitor_config;
+}
+
+static MetaMonitorConfig *
+derive_monitor_config (MetaOutputKey    *output_key,
+                       MetaOutputConfig *output_config,
+                       MetaRectangle    *out_layout,
+                       GError          **error)
+{
+  int mode_width;
+  int mode_height;
+  MetaMonitorConfig *monitor_config;
+
+  if (meta_monitor_transform_is_rotated (output_config->transform))
+    {
+      mode_width = output_config->rect.height;
+      mode_height = output_config->rect.width;
+    }
+  else
+    {
+      mode_width = output_config->rect.width;
+      mode_height = output_config->rect.height;
+    }
+
+  monitor_config = create_monitor_config (output_key, output_config,
+                                          mode_width, mode_height,
+                                          error);
+  if (!monitor_config)
+    return NULL;
+
+  *out_layout = output_config->rect;
+
+  return monitor_config;
+}
+
+static MetaLogicalMonitorConfig *
+ensure_logical_monitor (GList           **logical_monitor_configs,
+                        MetaOutputConfig *output_config,
+                        MetaRectangle    *layout)
+{
+  MetaLogicalMonitorConfig *new_logical_monitor_config;
+  GList *l;
+
+  for (l = *logical_monitor_configs; l; l = l->next)
+    {
+      MetaLogicalMonitorConfig *logical_monitor_config = l->data;
+
+      if (meta_rectangle_equal (&logical_monitor_config->layout, layout))
+        return logical_monitor_config;
+    }
+
+  new_logical_monitor_config = g_new0 (MetaLogicalMonitorConfig, 1);
+  *new_logical_monitor_config = (MetaLogicalMonitorConfig) {
+    .layout = *layout,
+    .is_primary = output_config->is_primary,
+    .is_presentation = output_config->is_presentation,
+    .transform = output_config->transform,
+    .scale = -1.0,
+  };
+
+  *logical_monitor_configs = g_list_append (*logical_monitor_configs,
+                                            new_logical_monitor_config);
+
+  return new_logical_monitor_config;
+}
+
+static GList *
+derive_logical_monitor_configs (MetaLegacyMonitorsConfig *config,
+                                MetaMonitorConfigStore   *config_store,
+                                GError                  **error)
+{
+  GList *logical_monitor_configs = NULL;
+  GList *monitor_configs = NULL;
+  unsigned int i;
+
+  for (i = 0; i < config->n_outputs; i++)
+    {
+      MetaOutputKey *output_key = &config->keys[i];
+      MetaOutputConfig *output_config = &config->outputs[i];
+      MetaMonitorConfig *monitor_config = NULL;
+      MetaRectangle layout;
+      MetaLogicalMonitorConfig *logical_monitor_config;
+
+      if (!output_config->enabled)
+        continue;
+
+      if (output_key->vendor &&
+          g_strcmp0 (output_key->vendor, "unknown") != 0 &&
+          output_key->product &&
+          g_strcmp0 (output_key->product, "unknown") != 0 &&
+          output_key->serial &&
+          g_strcmp0 (output_key->serial, "unknown") != 0)
+        {
+          monitor_config = try_derive_tiled_monitor_config (config,
+                                                            output_key,
+                                                            output_config,
+                                                            config_store,
+                                                            &layout,
+                                                            error);
+          if (!monitor_config)
+            {
+              if ((*error)->domain == META_MONITORS_CONFIG_MIGRATION_ERROR)
+                {
+                  int error_code = (*error)->code;
+
+                  g_clear_error (error);
+
+                  switch (error_code)
+                    {
+                    case META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_TILED:
+                      break;
+                    case META_MONITORS_CONFIG_MIGRATION_ERROR_NOT_MAIN_TILE:
+                      continue;
+                    }
+                }
+              else
+                {
+                  g_list_free_full (monitor_configs,
+                                    (GDestroyNotify) meta_monitor_config_free);
+                  return NULL;
+                }
+            }
+        }
+
+      if (!monitor_config)
+        monitor_config = derive_monitor_config (output_key, output_config,
+                                                &layout,
+                                                error);
+
+      if (!monitor_config)
+        {
+          g_list_free_full (monitor_configs,
+                            (GDestroyNotify) meta_monitor_config_free);
+          return NULL;
+        }
+
+      logical_monitor_config =
+        ensure_logical_monitor (&logical_monitor_configs,
+                                output_config, &layout);
+
+      logical_monitor_config->monitor_configs =
+        g_list_append (logical_monitor_config->monitor_configs, monitor_config);
+    }
+
+  if (!logical_monitor_configs)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Empty configuration");
+      return NULL;
+    }
+
+  return logical_monitor_configs;
+}
+
+static char *
+generate_config_name (MetaLegacyMonitorsConfig *config)
+{
+  char **output_strings;
+  unsigned int i;
+  char *key_name;
+
+  output_strings = g_new0 (char *, config->n_outputs + 1);
+  for (i = 0; i < config->n_outputs; i++)
+    {
+      MetaOutputKey *output_key = &config->keys[i];
+
+      output_strings[i] = g_strdup_printf ("%s:%s:%s:%s",
+                                           output_key->connector,
+                                           output_key->vendor,
+                                           output_key->product,
+                                           output_key->serial);
+    }
+
+  key_name = g_strjoinv (", ", output_strings);
+
+  g_strfreev (output_strings);
+
+  return key_name;
+}
+
+static void
+migrate_config (gpointer key,
+                gpointer value,
+                gpointer user_data)
+{
+  MetaLegacyMonitorsConfig *legacy_config = key;
+  MetaMonitorConfigStore *config_store = user_data;
+  MetaMonitorManager *monitor_manager =
+    meta_monitor_config_store_get_monitor_manager (config_store);
+  GList *logical_monitor_configs;
+  MetaLogicalMonitorLayoutMode layout_mode;
+  GError *error = NULL;
+  MetaMonitorsConfig *config;
+
+  logical_monitor_configs = derive_logical_monitor_configs (legacy_config,
+                                                            config_store,
+                                                            &error);
+  if (!logical_monitor_configs)
+    {
+      g_autofree char *config_name = NULL;
+
+      config_name = generate_config_name (legacy_config);
+      g_warning ("Failed to migrate monitor configuration for %s: %s",
+                 config_name, error->message);
+      return;
+    }
+
+  layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL;
+  config = meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                     META_MONITORS_CONFIG_FLAG_MIGRATED);
+  if (!meta_verify_monitors_config (config, monitor_manager, &error))
+    {
+      g_autofree char *config_name = NULL;
+
+      config_name = generate_config_name (legacy_config);
+      g_warning ("Ignoring invalid monitor configuration for %s: %s",
+                 config_name, error->message);
+      g_object_unref (config);
+      return;
+    }
+
+  meta_monitor_config_store_add (config_store, config);
+}
+
+gboolean
+meta_migrate_old_monitors_config (MetaMonitorConfigStore *config_store,
+                                  GFile                  *in_file,
+                                  GError                **error)
+{
+  g_autoptr (GHashTable) configs = NULL;
+
+  configs = load_config_file (in_file, error);
+  if (!configs)
+    return FALSE;
+
+  g_hash_table_foreach (configs, migrate_config, config_store);
+  g_hash_table_destroy (configs);
+
+  return TRUE;
+}
+
+gboolean
+meta_migrate_old_user_monitors_config (MetaMonitorConfigStore *config_store,
+                                       GError                **error)
+{
+  g_autofree char *backup_path = NULL;
+  g_autoptr (GFile) backup_file = NULL;
+  g_autofree char *user_file_path = NULL;
+  g_autoptr (GFile) user_file = NULL;
+
+  user_file_path = g_build_filename (g_get_user_config_dir (),
+                                     "monitors.xml",
+                                     NULL);
+  user_file = g_file_new_for_path (user_file_path);
+  backup_path = g_build_filename (g_get_user_config_dir (),
+                                  "monitors-v1-backup.xml",
+                                  NULL);
+  backup_file = g_file_new_for_path (backup_path);
+
+  if (!g_file_copy (user_file, backup_file,
+                    G_FILE_COPY_OVERWRITE | G_FILE_COPY_BACKUP,
+                    NULL, NULL, NULL,
+                    error))
+    {
+      g_warning ("Failed to make a backup of monitors.xml: %s",
+                 (*error)->message);
+      g_clear_error (error);
+    }
+
+  return meta_migrate_old_monitors_config (config_store, user_file, error);
+}
+
+gboolean
+meta_finish_monitors_config_migration (MetaMonitorManager *monitor_manager,
+                                       MetaMonitorsConfig *config,
+                                       GError            **error)
+{
+  MetaMonitorConfigManager *config_manager = monitor_manager->config_manager;
+  MetaMonitorConfigStore *config_store =
+    meta_monitor_config_manager_get_store (config_manager);
+  GList *l;
+
+  for (l = config->logical_monitor_configs; l; l = l->next)
+    {
+      MetaLogicalMonitorConfig *logical_monitor_config = l->data;
+      MetaMonitorConfig *monitor_config;
+      MetaMonitorSpec *monitor_spec;
+      MetaMonitor *monitor;
+      MetaMonitorModeSpec *monitor_mode_spec;
+      MetaMonitorMode *monitor_mode;
+      float scale;
+
+      monitor_config = logical_monitor_config->monitor_configs->data;
+      monitor_spec = monitor_config->monitor_spec;
+      monitor = meta_monitor_manager_get_monitor_from_spec (monitor_manager,
+                                                            monitor_spec);
+      monitor_mode_spec = monitor_config->mode_spec;
+      monitor_mode = meta_monitor_get_mode_from_spec (monitor,
+                                                      monitor_mode_spec);
+      scale = meta_monitor_calculate_mode_scale (monitor, monitor_mode);
+
+      logical_monitor_config->scale = scale;
+    }
+
+  config->layout_mode =
+    meta_monitor_manager_get_default_layout_mode (monitor_manager);
+  config->flags &= ~META_MONITORS_CONFIG_FLAG_MIGRATED;
+
+  if (!meta_verify_monitors_config (config, monitor_manager, error))
+    return FALSE;
+
+  meta_monitor_config_store_add (config_store, config);
+
+  return TRUE;
+}
diff --git a/src/backends/meta-monitor-config-migration.h b/src/backends/meta-monitor-config-migration.h
new file mode 100644
index 0000000..4ea21cb
--- /dev/null
+++ b/src/backends/meta-monitor-config-migration.h
@@ -0,0 +1,38 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2017 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_MONITOR_CONFIG_MIGRATION_H
+#define META_MONITOR_CONFIG_MIGRATION_H
+
+#include "backends/meta-monitor-manager-private.h"
+
+gboolean meta_migrate_old_monitors_config (MetaMonitorConfigStore *config_store,
+                                           GFile                  *in_file,
+                                           GError                **error);
+
+gboolean meta_migrate_old_user_monitors_config (MetaMonitorConfigStore *config_store,
+                                                GError                **error);
+
+gboolean meta_finish_monitors_config_migration (MetaMonitorManager *monitor_manager,
+                                                MetaMonitorsConfig *config,
+                                                GError            **error);
+
+#endif /* META_MONITOR_CONFIG_MIGRATION_H */
diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c
index d70cdda..be9de65 100644
--- a/src/backends/meta-monitor-config-store.c
+++ b/src/backends/meta-monitor-config-store.c
@@ -27,6 +27,7 @@
 #include <string.h>
 
 #include "backends/meta-monitor-config-manager.h"
+#include "backends/meta-monitor-config-migration.h"
 
 #define MONITORS_CONFIG_XML_FORMAT_VERSION 2
 
@@ -109,14 +110,27 @@ struct _MetaMonitorConfigStore
   GCancellable *save_cancellable;
 
   GFile *user_file;
-  GFile *custom_file;
+  GFile *custom_read_file;
+  GFile *custom_write_file;
 };
 
+#define META_MONITOR_CONFIG_STORE_ERROR (meta_monitor_config_store_error_quark ())
+static GQuark meta_monitor_config_store_error_quark (void);
+
+enum
+{
+  META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION
+};
+
+G_DEFINE_QUARK (meta-monitor-config-store-error-quark,
+                meta_monitor_config_store_error)
+
 typedef enum
 {
   STATE_INITIAL,
   STATE_MONITORS,
   STATE_CONFIGURATION,
+  STATE_MIGRATED,
   STATE_LOGICAL_MONITOR,
   STATE_LOGICAL_MONITOR_X,
   STATE_LOGICAL_MONITOR_Y,
@@ -145,6 +159,7 @@ typedef struct
   ParserState state;
   MetaMonitorConfigStore *config_store;
 
+  gboolean current_was_migrated;
   GList *current_logical_monitor_configs;
   MetaMonitorSpec *current_monitor_spec;
   gboolean current_transform_flipped;
@@ -189,7 +204,14 @@ handle_start_element (GMarkupParseContext  *context,
                          "Missing config file format version");
           }
         
-        /* TODO: Handle converting version 1 configuration files. */
+        if (g_str_equal (version, "1"))
+          {
+            g_set_error_literal (error,
+                                 META_MONITOR_CONFIG_STORE_ERROR,
+                                 META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION,
+                                 "monitors.xml has the old format");
+            return;
+          }
 
         if (!g_str_equal (version, QUOTE (MONITORS_CONFIG_XML_FORMAT_VERSION)))
           {
@@ -212,22 +234,40 @@ handle_start_element (GMarkupParseContext  *context,
           }
 
         parser->state = STATE_CONFIGURATION;
+        parser->current_was_migrated = FALSE;
+
         return;
       }
 
     case STATE_CONFIGURATION:
       {
-        if (!g_str_equal (element_name, "logicalmonitor"))
+        if (g_str_equal (element_name, "logicalmonitor"))
+          {
+            parser->current_logical_monitor_config =
+              g_new0 (MetaLogicalMonitorConfig, 1);
+
+            parser->state = STATE_LOGICAL_MONITOR;
+          }
+        else if (g_str_equal (element_name, "migrated"))
+          {
+            parser->current_was_migrated = TRUE;
+
+            parser->state = STATE_MIGRATED;
+          }
+        else
           {
             g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
                          "Invalid configuration element '%s'", element_name);
             return;
           }
 
-        parser->current_logical_monitor_config =
-          g_new0 (MetaLogicalMonitorConfig, 1);
+        return;
+      }
 
-        parser->state = STATE_LOGICAL_MONITOR;
+    case STATE_MIGRATED:
+      {
+        g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                     "Unexpected element '%s'", element_name);
         return;
       }
 
@@ -603,7 +643,9 @@ handle_end_element (GMarkupParseContext  *context,
 
         g_assert (g_str_equal (element_name, "logicalmonitor"));
 
-        if (logical_monitor_config->scale == 0)
+        if (parser->current_was_migrated)
+          logical_monitor_config->scale = -1;
+        else if (logical_monitor_config->scale == 0)
           logical_monitor_config->scale = 1;
 
         parser->current_logical_monitor_configs =
@@ -615,17 +657,29 @@ handle_end_element (GMarkupParseContext  *context,
         return;
       }
 
+    case STATE_MIGRATED:
+      {
+        g_assert (g_str_equal (element_name, "migrated"));
+
+        parser->state = STATE_CONFIGURATION;
+        return;
+      }
+
     case STATE_CONFIGURATION:
       {
         MetaMonitorConfigStore *store = parser->config_store;
         MetaMonitorsConfig *config;
         GList *l;
         MetaLogicalMonitorLayoutMode layout_mode;
+        MetaMonitorsConfigFlag config_flags = META_MONITORS_CONFIG_FLAG_NONE;
 
         g_assert (g_str_equal (element_name, "configuration"));
 
-        layout_mode =
-          meta_monitor_manager_get_default_layout_mode (store->monitor_manager);
+        if (parser->current_was_migrated)
+          layout_mode = META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL;
+        else
+          layout_mode =
+            meta_monitor_manager_get_default_layout_mode (store->monitor_manager);
 
         for (l = parser->current_logical_monitor_configs; l; l = l->next)
           {
@@ -643,9 +697,13 @@ handle_end_element (GMarkupParseContext  *context,
               return;
           }
 
+        if (parser->current_was_migrated)
+          config_flags |= META_MONITORS_CONFIG_FLAG_MIGRATED;
+
         config =
           meta_monitors_config_new (parser->current_logical_monitor_configs,
-                                    layout_mode);
+                                    layout_mode,
+                                    config_flags);
 
         parser->current_logical_monitor_configs = NULL;
 
@@ -785,6 +843,7 @@ handle_text (GMarkupParseContext *context,
     case STATE_INITIAL:
     case STATE_MONITORS:
     case STATE_CONFIGURATION:
+    case STATE_MIGRATED:
     case STATE_LOGICAL_MONITOR:
     case STATE_MONITOR:
     case STATE_MONITOR_SPEC:
@@ -1094,6 +1153,7 @@ append_transform (GString             *buffer,
 
 static void
 append_logical_monitor_xml (GString                  *buffer,
+                            MetaMonitorsConfig       *config,
                             MetaLogicalMonitorConfig *logical_monitor_config)
 {
   char scale_str[G_ASCII_DTOSTR_BUF_SIZE];
@@ -1105,8 +1165,9 @@ append_logical_monitor_xml (GString                  *buffer,
                           logical_monitor_config->layout.y);
   g_ascii_dtostr (scale_str, G_ASCII_DTOSTR_BUF_SIZE,
                   logical_monitor_config->scale);
-  g_string_append_printf (buffer, "      <scale>%s</scale>\n",
-                          scale_str);
+  if ((config->flags & META_MONITORS_CONFIG_FLAG_MIGRATED) == 0)
+    g_string_append_printf (buffer, "      <scale>%s</scale>\n",
+                            scale_str);
   if (logical_monitor_config->is_primary)
     g_string_append (buffer, "      <primary>yes</primary>\n");
   if (logical_monitor_config->is_presentation)
@@ -1134,11 +1195,14 @@ generate_config_xml (MetaMonitorConfigStore *config_store)
 
       g_string_append (buffer, "  <configuration>\n");
 
+      if (config->flags & META_MONITORS_CONFIG_FLAG_MIGRATED)
+        g_string_append (buffer, "    <migrated/>\n");
+
       for (l = config->logical_monitor_configs; l; l = l->next)
         {
           MetaLogicalMonitorConfig *logical_monitor_config = l->data;
 
-          append_logical_monitor_xml (buffer, logical_monitor_config);
+          append_logical_monitor_xml (buffer, config, logical_monitor_config);
         }
 
       g_string_append (buffer, "  </configuration>\n");
@@ -1204,6 +1268,31 @@ meta_monitor_config_store_save (MetaMonitorConfigStore *config_store)
     .buffer = buffer
   };
 
+  /*
+   * Custom write file is only ever used by the test suite, and the test suite
+   * will want to have be able to read back the content immediately, so for
+   * custom write files, do the content replacement synchronously.
+   */
+  if (config_store->custom_write_file)
+    {
+      GError *error = NULL;
+
+      if (!g_file_replace_contents (config_store->custom_write_file,
+                                    buffer->str, buffer->len,
+                                    NULL,
+                                    FALSE,
+                                    G_FILE_CREATE_REPLACE_DESTINATION,
+                                    NULL,
+                                    NULL,
+                                    &error))
+        {
+          g_warning ("Saving monitor configuration failed: %s\n",
+                     error->message);
+          g_error_free (error);
+        }
+      return;
+    }
+
   g_file_replace_contents_async (config_store->user_file,
                                  buffer->str, buffer->len,
                                  NULL,
@@ -1213,6 +1302,18 @@ meta_monitor_config_store_save (MetaMonitorConfigStore *config_store)
                                  saved_cb, data);
 }
 
+static void
+maybe_save_configs (MetaMonitorConfigStore *config_store)
+{
+  /*
+   * If a custom file is used, it means we are run by the test suite. When this
+   * is done, avoid replacing the user configuration file with test data,
+   * except if a custom write file is set as well.
+   */
+  if (!config_store->custom_read_file || config_store->custom_write_file)
+    meta_monitor_config_store_save (config_store);
+}
+
 void
 meta_monitor_config_store_add (MetaMonitorConfigStore *config_store,
                                MetaMonitorsConfig     *config)
@@ -1220,21 +1321,33 @@ meta_monitor_config_store_add (MetaMonitorConfigStore *config_store,
   g_hash_table_replace (config_store->configs,
                         config->key, g_object_ref (config));
 
-  if (!config_store->custom_file)
-    meta_monitor_config_store_save (config_store);
+  maybe_save_configs (config_store);
+}
+
+void
+meta_monitor_config_store_remove (MetaMonitorConfigStore *config_store,
+                                  MetaMonitorsConfig     *config)
+{
+  g_hash_table_remove (config_store->configs, config->key);
+
+  maybe_save_configs (config_store);
 }
 
 gboolean
 meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store,
-                                      const char             *path,
+                                      const char             *read_path,
+                                      const char             *write_path,
                                       GError                **error)
 {
-  g_clear_object (&config_store->custom_file);
+  g_clear_object (&config_store->custom_read_file);
+  g_clear_object (&config_store->custom_write_file);
   g_hash_table_remove_all (config_store->configs);
 
-  config_store->custom_file = g_file_new_for_path (path);
+  config_store->custom_read_file = g_file_new_for_path (read_path);
+  if (write_path)
+    config_store->custom_write_file = g_file_new_for_path (write_path);
 
-  return read_config_file (config_store, config_store->custom_file, error);
+  return read_config_file (config_store, config_store->custom_read_file, error);
 }
 
 int
@@ -1243,6 +1356,12 @@ meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store
   return (int) g_hash_table_size (config_store->configs);
 }
 
+MetaMonitorManager *
+meta_monitor_config_store_get_monitor_manager (MetaMonitorConfigStore *config_store)
+{
+  return config_store->monitor_manager;
+}
+
 MetaMonitorConfigStore *
 meta_monitor_config_store_new (MetaMonitorManager *monitor_manager)
 {
@@ -1259,7 +1378,7 @@ meta_monitor_config_store_constructed (GObject *object)
   GError *error = NULL;
 
   user_file_path = g_build_filename (g_get_user_config_dir (),
-                                     "monitors-experimental.xml",
+                                     "monitors.xml",
                                      NULL);
   config_store->user_file = g_file_new_for_path (user_file_path);
 
@@ -1267,9 +1386,23 @@ meta_monitor_config_store_constructed (GObject *object)
     {
       if (!read_config_file (config_store, config_store->user_file, &error))
         {
-          g_warning ("Failed to read monitors config file '%s': %s",
-                     user_file_path, error->message);
-          g_error_free (error);
+          if (error->domain == META_MONITOR_CONFIG_STORE_ERROR &&
+              error->code == META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION)
+            {
+              g_clear_error (&error);
+              if (!meta_migrate_old_user_monitors_config (config_store, &error))
+                {
+                  g_warning ("Failed to migrate old monitors config file: %s",
+                             error->message);
+                  g_error_free (error);
+                }
+            }
+          else
+            {
+              g_warning ("Failed to read monitors config file '%s': %s",
+                         user_file_path, error->message);
+              g_error_free (error);
+            }
         }
     }
 
@@ -1284,7 +1417,8 @@ meta_monitor_config_store_dispose (GObject *object)
   g_clear_pointer (&config_store->configs, g_hash_table_destroy);
 
   g_clear_object (&config_store->user_file);
-  g_clear_object (&config_store->custom_file);
+  g_clear_object (&config_store->custom_read_file);
+  g_clear_object (&config_store->custom_write_file);
 
   G_OBJECT_CLASS (meta_monitor_config_store_parent_class)->dispose (object);
 }
diff --git a/src/backends/meta-monitor-config-store.h b/src/backends/meta-monitor-config-store.h
index 7451fc1..76f97e5 100644
--- a/src/backends/meta-monitor-config-store.h
+++ b/src/backends/meta-monitor-config-store.h
@@ -38,10 +38,16 @@ MetaMonitorsConfig * meta_monitor_config_store_lookup (MetaMonitorConfigStore *c
 void meta_monitor_config_store_add (MetaMonitorConfigStore *config_store,
                                     MetaMonitorsConfig     *config);
 
+void meta_monitor_config_store_remove (MetaMonitorConfigStore *config_store,
+                                       MetaMonitorsConfig     *config);
+
 gboolean meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store,
-                                               const char             *path,
+                                               const char             *read_path,
+                                               const char             *write_path,
                                                GError                **error);
 
 int meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store);
 
+MetaMonitorManager * meta_monitor_config_store_get_monitor_manager (MetaMonitorConfigStore *config_store);
+
 #endif /* META_MONITOR_CONFIG_STORE_H */
diff --git a/src/backends/meta-monitor-manager.c b/src/backends/meta-monitor-manager.c
index a4975fb..4258f71 100644
--- a/src/backends/meta-monitor-manager.c
+++ b/src/backends/meta-monitor-manager.c
@@ -429,6 +429,9 @@ meta_monitor_manager_apply_monitors_config (MetaMonitorManager      *manager,
   MetaMonitorManagerClass *manager_class =
     META_MONITOR_MANAGER_GET_CLASS (manager);
 
+  g_assert (!config ||
+            !(config->flags & META_MONITORS_CONFIG_FLAG_MIGRATED));
+
   if (!manager_class->apply_monitors_config (manager, config, method, error))
     return FALSE;
 
@@ -1874,7 +1877,8 @@ meta_monitor_manager_handle_apply_monitors_config (MetaDBusDisplayConfig *skelet
                                                logical_monitor_config);
     }
 
-  config = meta_monitors_config_new (logical_monitor_configs, layout_mode);
+  config = meta_monitors_config_new (logical_monitor_configs, layout_mode,
+                                     META_MONITORS_CONFIG_FLAG_NONE);
   if (!meta_verify_monitors_config (config, manager, &error))
     {
       g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
diff --git a/src/tests/monitor-test-utils.c b/src/tests/monitor-test-utils.c
index 354f4a7..b063a55 100644
--- a/src/tests/monitor-test-utils.c
+++ b/src/tests/monitor-test-utils.c
@@ -42,6 +42,7 @@ set_custom_monitor_config (const char *filename)
 
   path = g_test_get_filename (G_TEST_DIST, "tests", "monitor-configs",
                               filename, NULL);
-  if (!meta_monitor_config_store_set_custom (config_store, path, &error))
+  if (!meta_monitor_config_store_set_custom (config_store, path, NULL,
+                                             &error))
     g_error ("Failed to set custom config: %s", error->message);
 }


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