[gnome-flashback] backends: add GfMonitorConfigStore



commit 623574b1dd77590f805607a41a4d0d16e091e521
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Wed Sep 20 19:16:42 2017 +0300

    backends: add GfMonitorConfigStore

 backends/Makefile.am                           |    4 +
 backends/gf-monitor-config-migration-private.h |   38 +
 backends/gf-monitor-config-migration.c         |   60 +
 backends/gf-monitor-config-store-private.h     |   55 +
 backends/gf-monitor-config-store.c             | 1547 ++++++++++++++++++++++++
 backends/gf-monitor-manager-private.h          |   20 +-
 backends/gf-monitor-manager-types-private.h    |    1 +
 backends/gf-monitor-manager.c                  |   10 +
 8 files changed, 1726 insertions(+), 9 deletions(-)
---
diff --git a/backends/Makefile.am b/backends/Makefile.am
index 1c3bea5..c5ae5e8 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -38,7 +38,11 @@ libbackends_la_SOURCES = \
        gf-logical-monitor-private.h \
        gf-logical-monitor.c \
        gf-monitor-config-manager-private.h \
+       gf-monitor-config-migration-private.h \
+       gf-monitor-config-migration.c \
        gf-monitor-config-private.h \
+       gf-monitor-config-store-private.h \
+       gf-monitor-config-store.c \
        gf-monitor-config.c \
        gf-monitor-manager-dummy-private.h \
        gf-monitor-manager-dummy.c \
diff --git a/backends/gf-monitor-config-migration-private.h b/backends/gf-monitor-config-migration-private.h
new file mode 100644
index 0000000..309361d
--- /dev/null
+++ b/backends/gf-monitor-config-migration-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 Red Hat
+ * Copyright (C) 2017 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ *
+ * Adapted from mutter:
+ * - src/backends/meta-monitor-config-migration.h
+ */
+
+#ifndef GF_MONITOR_CONFIG_MIGRATION_PRIVATE_H
+#define GF_MONITOR_CONFIG_MIGRATION_PRIVATE_H
+
+#include "gf-monitors-config-private.h"
+
+G_BEGIN_DECLS
+
+gboolean gf_migrate_old_monitors_config      (GfMonitorConfigStore  *config_store,
+                                              GFile                 *in_file,
+                                              GError               **error);
+
+gboolean gf_migrate_old_user_monitors_config (GfMonitorConfigStore  *config_store,
+                                              GError               **error);
+
+G_END_DECLS
+
+#endif
diff --git a/backends/gf-monitor-config-migration.c b/backends/gf-monitor-config-migration.c
new file mode 100644
index 0000000..f3970f6
--- /dev/null
+++ b/backends/gf-monitor-config-migration.c
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ * Copyright (C) 2017 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ *
+ * Adapted from mutter:
+ * - src/backends/meta-monitor-config-migration.c
+ */
+
+/*
+ * 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 <gio/gio.h>
+
+#include "gf-monitor-config-migration-private.h"
+#include "gf-monitor-config-store-private.h"
+
+gboolean
+gf_migrate_old_monitors_config (GfMonitorConfigStore  *config_store,
+                                GFile                 *in_file,
+                                GError               **error)
+{
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not implemented");
+  return FALSE;
+}
+
+gboolean
+gf_migrate_old_user_monitors_config (GfMonitorConfigStore  *config_store,
+                                     GError               **error)
+{
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not implemented");
+  return FALSE;
+}
diff --git a/backends/gf-monitor-config-store-private.h b/backends/gf-monitor-config-store-private.h
new file mode 100644
index 0000000..573156e
--- /dev/null
+++ b/backends/gf-monitor-config-store-private.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 Red Hat
+ * Copyright (C) 2017 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ *
+ * Adapted from mutter:
+ * - src/backends/meta-monitor-config-store.h
+ */
+
+#ifndef GF_MONITOR_CONFIG_STORE_PRIVATE_H
+#define GF_MONITOR_CONFIG_STORE_PRIVATE_H
+
+#include "gf-monitors-config-private.h"
+
+G_BEGIN_DECLS
+
+#define GF_TYPE_MONITOR_CONFIG_STORE (gf_monitor_config_store_get_type ())
+G_DECLARE_FINAL_TYPE (GfMonitorConfigStore, gf_monitor_config_store,
+                      GF, MONITOR_CONFIG_STORE, GObject)
+
+GfMonitorConfigStore *gf_monitor_config_store_new                 (GfMonitorManager      *monitor_manager);
+
+GfMonitorsConfig     *gf_monitor_config_store_lookup              (GfMonitorConfigStore  *config_store,
+                                                                   GfMonitorsConfigKey   *key);
+
+void                  gf_monitor_config_store_add                 (GfMonitorConfigStore  *config_store,
+                                                                   GfMonitorsConfig      *config);
+
+void                  gf_monitor_config_store_remove              (GfMonitorConfigStore  *config_store,
+                                                                   GfMonitorsConfig      *config);
+
+gboolean              gf_monitor_config_store_set_custom          (GfMonitorConfigStore  *config_store,
+                                                                   const gchar           *read_path,
+                                                                   const gchar           *write_path,
+                                                                   GError               **error);
+
+gint                  gf_monitor_config_store_get_config_count    (GfMonitorConfigStore  *config_store);
+
+GfMonitorManager     *gf_monitor_config_store_get_monitor_manager (GfMonitorConfigStore  *config_store);
+
+G_END_DECLS
+
+#endif
diff --git a/backends/gf-monitor-config-store.c b/backends/gf-monitor-config-store.c
new file mode 100644
index 0000000..cc8d2d8
--- /dev/null
+++ b/backends/gf-monitor-config-store.c
@@ -0,0 +1,1547 @@
+/*
+ * Copyright (C) 2017 Red Hat
+ * Copyright (C) 2017 Alberts Muktupāvels
+ *
+ * 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 3 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/>.
+ *
+ * Adapted from mutter:
+ * - src/backends/meta-monitor-config-store.c
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <math.h>
+#include <string.h>
+
+#include "gf-logical-monitor-config-private.h"
+#include "gf-monitor-config-manager-private.h"
+#include "gf-monitor-config-migration-private.h"
+#include "gf-monitor-config-private.h"
+#include "gf-monitor-config-store-private.h"
+#include "gf-monitor-spec-private.h"
+
+#define MONITORS_CONFIG_XML_FORMAT_VERSION 2
+
+#define QUOTE1(a) #a
+#define QUOTE(a) QUOTE1(a)
+
+/*
+ * Example configuration:
+ *
+ * <monitors version="2">
+ *   <configuration>
+ *     <logicalmonitor>
+ *       <x>0</x>
+ *       <y>0</y>
+ *       <scale>1</scale>
+ *       <monitor>
+ *         <monitorspec>
+ *           <connector>LVDS1</connector>
+ *           <vendor>Vendor A</vendor>
+ *           <product>Product A</product>
+ *           <serial>Serial A</serial>
+ *         </monitorspec>
+ *         <mode>
+ *           <width>1920</width>
+ *           <height>1080</height>
+ *           <rate>60.049972534179688</rate>
+ *           <flag>interlace</flag>
+ *         </mode>
+ *       </monitor>
+ *       <transform>
+ *         <rotation>right</rotation>
+ *         <flipped>no</flipped>
+ *       </transform>
+ *       <primary>yes</primary>
+ *       <presentation>no</presentation>
+ *     </logicalmonitor>
+ *     <logicalmonitor>
+ *       <x>1920</x>
+ *       <y>1080</y>
+ *       <monitor>
+ *         <monitorspec>
+ *           <connector>LVDS2</connector>
+ *           <vendor>Vendor B</vendor>
+ *           <product>Product B</product>
+ *           <serial>Serial B</serial>
+ *         </monitorspec>
+ *         <mode>
+ *           <width>1920</width>
+ *           <height>1080</height>
+ *           <rate>60.049972534179688</rate>
+ *         </mode>
+ *         <underscanning>yes</underscanning>
+ *       </monitor>
+ *       <presentation>yes</presentation>
+ *     </logicalmonitor>
+ *   </configuration>
+ * </monitors>
+ */
+
+struct _GfMonitorConfigStore
+{
+  GObject           parent;
+
+  GfMonitorManager *monitor_manager;
+
+  GHashTable       *configs;
+
+  GCancellable     *save_cancellable;
+
+  GFile            *user_file;
+  GFile            *custom_read_file;
+  GFile            *custom_write_file;
+};
+
+enum
+{
+  GF_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION
+};
+
+#define GF_MONITOR_CONFIG_STORE_ERROR (gf_monitor_config_store_error_quark ())
+static GQuark gf_monitor_config_store_error_quark (void);
+
+G_DEFINE_QUARK (gf-monitor-config-store-error-quark,
+                gf_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,
+  STATE_LOGICAL_MONITOR_PRIMARY,
+  STATE_LOGICAL_MONITOR_PRESENTATION,
+  STATE_LOGICAL_MONITOR_SCALE,
+  STATE_TRANSFORM,
+  STATE_TRANSFORM_ROTATION,
+  STATE_TRANSFORM_FLIPPED,
+  STATE_MONITOR,
+  STATE_MONITOR_SPEC,
+  STATE_MONITOR_SPEC_CONNECTOR,
+  STATE_MONITOR_SPEC_VENDOR,
+  STATE_MONITOR_SPEC_PRODUCT,
+  STATE_MONITOR_SPEC_SERIAL,
+  STATE_MONITOR_MODE,
+  STATE_MONITOR_MODE_WIDTH,
+  STATE_MONITOR_MODE_HEIGHT,
+  STATE_MONITOR_MODE_RATE,
+  STATE_MONITOR_MODE_FLAG,
+  STATE_MONITOR_UNDERSCANNING
+} ParserState;
+
+typedef struct
+{
+  ParserState             state;
+  GfMonitorConfigStore   *config_store;
+
+  gboolean                current_was_migrated;
+  GList                  *current_logical_monitor_configs;
+  GfMonitorSpec          *current_monitor_spec;
+  gboolean                current_transform_flipped;
+  GfMonitorTransform      current_transform;
+  GfMonitorModeSpec      *current_monitor_mode_spec;
+  GfMonitorConfig        *current_monitor_config;
+  GfLogicalMonitorConfig *current_logical_monitor_config;
+} ConfigParser;
+
+typedef struct
+{
+  GfMonitorConfigStore *config_store;
+  GString              *buffer;
+} SaveData;
+
+enum
+{
+  PROP_0,
+
+  PROP_MONITOR_MANAGER,
+
+  LAST_PROP
+};
+
+static GParamSpec *config_store_properties[LAST_PROP] = { NULL };
+
+G_DEFINE_TYPE (GfMonitorConfigStore, gf_monitor_config_store, G_TYPE_OBJECT)
+
+static void
+handle_start_element (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      const gchar         **attribute_names,
+                      const gchar         **attribute_values,
+                      gpointer              user_data,
+                      GError              **error)
+{
+  ConfigParser *parser = user_data;
+
+  switch (parser->state)
+    {
+      case STATE_INITIAL:
+        {
+          gchar *version;
+
+          if (!g_str_equal (element_name, "monitors"))
+            {
+              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))
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Missing config file format version");
+            }
+
+          if (g_str_equal (version, "1"))
+            {
+              g_set_error_literal (error,
+                                   GF_MONITOR_CONFIG_STORE_ERROR,
+                                   GF_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION,
+                                   "monitors.xml has the old format");
+              return;
+            }
+
+          if (!g_str_equal (version, QUOTE (MONITORS_CONFIG_XML_FORMAT_VERSION)))
+            {
+              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 (!g_str_equal (element_name, "configuration"))
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                           "Invalid toplevel element '%s'", element_name);
+              return;
+            }
+
+          parser->state = STATE_CONFIGURATION;
+          parser->current_was_migrated = FALSE;
+
+          return;
+        }
+
+      case STATE_CONFIGURATION:
+        {
+          if (g_str_equal (element_name, "logicalmonitor"))
+            {
+              parser->current_logical_monitor_config = g_new0 (GfLogicalMonitorConfig, 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;
+            }
+
+          return;
+        }
+
+      case STATE_MIGRATED:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Unexpected element '%s'", element_name);
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR:
+        {
+          if (g_str_equal (element_name, "x"))
+            {
+              parser->state = STATE_LOGICAL_MONITOR_X;
+            }
+          else if (g_str_equal (element_name, "y"))
+            {
+              parser->state = STATE_LOGICAL_MONITOR_Y;
+            }
+          else if (g_str_equal (element_name, "scale"))
+            {
+              parser->state = STATE_LOGICAL_MONITOR_SCALE;
+            }
+          else if (g_str_equal (element_name, "primary"))
+            {
+              parser->state = STATE_LOGICAL_MONITOR_PRIMARY;
+            }
+          else if (g_str_equal (element_name, "presentation"))
+            {
+              parser->state = STATE_LOGICAL_MONITOR_PRESENTATION;
+            }
+          else if (g_str_equal (element_name, "transform"))
+            {
+              parser->state = STATE_TRANSFORM;
+            }
+          else if (g_str_equal (element_name, "monitor"))
+            {
+              parser->current_monitor_config = g_new0 (GfMonitorConfig, 1);;
+
+              parser->state = STATE_MONITOR;
+            }
+          else
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                           "Invalid monitor logicalmonitor element '%s'", element_name);
+              return;
+            }
+
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_X:
+      case STATE_LOGICAL_MONITOR_Y:
+      case STATE_LOGICAL_MONITOR_SCALE:
+      case STATE_LOGICAL_MONITOR_PRIMARY:
+      case STATE_LOGICAL_MONITOR_PRESENTATION:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Invalid logical monitor element '%s'", element_name);
+          return;
+        }
+
+      case STATE_TRANSFORM:
+        {
+          if (g_str_equal (element_name, "rotation"))
+            {
+              parser->state = STATE_TRANSFORM_ROTATION;
+            }
+          else if (g_str_equal (element_name, "flipped"))
+            {
+              parser->state = STATE_TRANSFORM_FLIPPED;
+            }
+
+          return;
+        }
+
+      case STATE_TRANSFORM_ROTATION:
+      case STATE_TRANSFORM_FLIPPED:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Invalid transform element '%s'", element_name);
+          return;
+        }
+
+      case STATE_MONITOR:
+        {
+          if (g_str_equal (element_name, "monitorspec"))
+            {
+              parser->current_monitor_spec = g_new0 (GfMonitorSpec, 1);
+
+              parser->state = STATE_MONITOR_SPEC;
+            }
+          else if (g_str_equal (element_name, "mode"))
+            {
+              parser->current_monitor_mode_spec = g_new0 (GfMonitorModeSpec, 1);
+
+              parser->state = STATE_MONITOR_MODE;
+            }
+          else if (g_str_equal (element_name, "underscanning"))
+            {
+              parser->state = STATE_MONITOR_UNDERSCANNING;
+            }
+          else
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                           "Invalid monitor element '%s'", element_name);
+              return;
+            }
+
+          return;
+        }
+
+      case STATE_MONITOR_SPEC:
+        {
+          if (g_str_equal (element_name, "connector"))
+            {
+              parser->state = STATE_MONITOR_SPEC_CONNECTOR;
+            }
+          else if (g_str_equal (element_name, "vendor"))
+            {
+              parser->state = STATE_MONITOR_SPEC_VENDOR;
+            }
+          else if (g_str_equal (element_name, "product"))
+            {
+              parser->state = STATE_MONITOR_SPEC_PRODUCT;
+            }
+          else if (g_str_equal (element_name, "serial"))
+            {
+              parser->state = STATE_MONITOR_SPEC_SERIAL;
+            }
+          else
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                           "Invalid monitor spec element '%s'", element_name);
+              return;
+            }
+
+          return;
+        }
+
+      case STATE_MONITOR_SPEC_CONNECTOR:
+      case STATE_MONITOR_SPEC_VENDOR:
+      case STATE_MONITOR_SPEC_PRODUCT:
+      case STATE_MONITOR_SPEC_SERIAL:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Invalid monitor spec element '%s'", element_name);
+          return;
+        }
+
+      case STATE_MONITOR_MODE:
+        {
+          if (g_str_equal (element_name, "width"))
+            {
+              parser->state = STATE_MONITOR_MODE_WIDTH;
+            }
+          else if (g_str_equal (element_name, "height"))
+            {
+              parser->state = STATE_MONITOR_MODE_HEIGHT;
+            }
+          else if (g_str_equal (element_name, "rate"))
+            {
+              parser->state = STATE_MONITOR_MODE_RATE;
+            }
+          else if (g_str_equal (element_name, "flag"))
+            {
+              parser->state = STATE_MONITOR_MODE_FLAG;
+            }
+          else
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                           "Invalid mode element '%s'", element_name);
+              return;
+            }
+
+          return;
+        }
+
+      case STATE_MONITOR_MODE_WIDTH:
+      case STATE_MONITOR_MODE_HEIGHT:
+      case STATE_MONITOR_MODE_RATE:
+      case STATE_MONITOR_MODE_FLAG:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Invalid mode sub element '%s'", element_name);
+          return;
+        }
+
+      case STATE_MONITOR_UNDERSCANNING:
+        {
+          g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                       "Invalid element '%s' under underscanning", element_name);
+          return;
+        }
+
+      default:
+        break;
+    }
+}
+
+static gboolean
+derive_logical_monitor_layout (GfLogicalMonitorConfig      *logical_monitor_config,
+                               GfLogicalMonitorLayoutMode   layout_mode,
+                               GError                     **error)
+{
+  GfMonitorConfig *monitor_config;
+  gint mode_width, mode_height;
+  gint width = 0, height = 0;
+  GList *l;
+
+  monitor_config = logical_monitor_config->monitor_configs->data;
+  mode_width = monitor_config->mode_spec->width;
+  mode_height = monitor_config->mode_spec->height;
+
+  for (l = logical_monitor_config->monitor_configs->next; l; l = l->next)
+    {
+      monitor_config = l->data;
+
+      if (monitor_config->mode_spec->width != mode_width ||
+          monitor_config->mode_spec->height != mode_height)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Monitors in logical monitor incompatible");
+
+          return FALSE;
+        }
+    }
+
+  if (gf_monitor_transform_is_rotated (logical_monitor_config->transform))
+    {
+      width = mode_height;
+      height = mode_width;
+    }
+  else
+    {
+      width = mode_width;
+      height = mode_height;
+    }
+
+  switch (layout_mode)
+    {
+      case GF_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL:
+        width = roundf (width / logical_monitor_config->scale);
+        height = roundf (height / logical_monitor_config->scale);
+        break;
+
+      case GF_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL:
+      default:
+        break;
+    }
+
+  logical_monitor_config->layout.width = width;
+  logical_monitor_config->layout.height = height;
+
+  return TRUE;
+}
+
+static void
+handle_end_element (GMarkupParseContext  *context,
+                    const gchar          *element_name,
+                    gpointer              user_data,
+                    GError              **error)
+{
+  ConfigParser *parser = user_data;
+
+  switch (parser->state)
+    {
+      case STATE_LOGICAL_MONITOR_X:
+      case STATE_LOGICAL_MONITOR_Y:
+      case STATE_LOGICAL_MONITOR_SCALE:
+      case STATE_LOGICAL_MONITOR_PRIMARY:
+      case STATE_LOGICAL_MONITOR_PRESENTATION:
+        {
+          parser->state = STATE_LOGICAL_MONITOR;
+          return;
+        }
+
+      case STATE_TRANSFORM:
+        {
+          g_assert (g_str_equal (element_name, "transform"));
+
+          parser->current_logical_monitor_config->transform = parser->current_transform;
+          if (parser->current_transform_flipped)
+            {
+              parser->current_logical_monitor_config->transform +=
+                GF_MONITOR_TRANSFORM_FLIPPED;
+            }
+
+          parser->current_transform = GF_MONITOR_TRANSFORM_NORMAL;
+          parser->current_transform_flipped = FALSE;
+
+          parser->state = STATE_LOGICAL_MONITOR;
+          return;
+        }
+
+      case STATE_TRANSFORM_ROTATION:
+      case STATE_TRANSFORM_FLIPPED:
+        {
+          parser->state = STATE_TRANSFORM;
+          return;
+        }
+
+      case STATE_MONITOR_SPEC_CONNECTOR:
+      case STATE_MONITOR_SPEC_VENDOR:
+      case STATE_MONITOR_SPEC_PRODUCT:
+      case STATE_MONITOR_SPEC_SERIAL:
+        {
+          parser->state = STATE_MONITOR_SPEC;
+          return;
+        }
+
+      case STATE_MONITOR_SPEC:
+        {
+          g_assert (g_str_equal (element_name, "monitorspec"));
+
+          if (!gf_verify_monitor_spec (parser->current_monitor_spec, error))
+            return;
+
+          parser->current_monitor_config->monitor_spec = parser->current_monitor_spec;
+          parser->current_monitor_spec = NULL;
+
+          parser->state = STATE_MONITOR;
+          return;
+        }
+
+      case STATE_MONITOR_MODE_WIDTH:
+      case STATE_MONITOR_MODE_HEIGHT:
+      case STATE_MONITOR_MODE_RATE:
+      case STATE_MONITOR_MODE_FLAG:
+        {
+          parser->state = STATE_MONITOR_MODE;
+          return;
+        }
+
+      case STATE_MONITOR_MODE:
+        {
+          g_assert (g_str_equal (element_name, "mode"));
+
+          if (!gf_verify_monitor_mode_spec (parser->current_monitor_mode_spec, error))
+            return;
+
+          parser->current_monitor_config->mode_spec = parser->current_monitor_mode_spec;
+          parser->current_monitor_mode_spec = NULL;
+
+          parser->state = STATE_MONITOR;
+          return;
+        }
+
+      case STATE_MONITOR_UNDERSCANNING:
+        {
+          g_assert (g_str_equal (element_name, "underscanning"));
+
+          parser->state = STATE_MONITOR;
+          return;
+        }
+
+      case STATE_MONITOR:
+        {
+          GfLogicalMonitorConfig *logical_monitor_config;
+
+          g_assert (g_str_equal (element_name, "monitor"));
+
+          if (!gf_verify_monitor_config (parser->current_monitor_config, error))
+            return;
+
+          logical_monitor_config = parser->current_logical_monitor_config;
+
+          logical_monitor_config->monitor_configs =
+            g_list_append (logical_monitor_config->monitor_configs,
+                           parser->current_monitor_config);
+          parser->current_monitor_config = NULL;
+
+          parser->state = STATE_LOGICAL_MONITOR;
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR:
+        {
+          GfLogicalMonitorConfig *logical_monitor_config =
+            parser->current_logical_monitor_config;
+
+          g_assert (g_str_equal (element_name, "logicalmonitor"));
+
+          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 =
+            g_list_append (parser->current_logical_monitor_configs,
+                           logical_monitor_config);
+          parser->current_logical_monitor_config = NULL;
+
+          parser->state = STATE_CONFIGURATION;
+          return;
+        }
+
+      case STATE_MIGRATED:
+        {
+          g_assert (g_str_equal (element_name, "migrated"));
+
+          parser->state = STATE_CONFIGURATION;
+          return;
+        }
+
+      case STATE_CONFIGURATION:
+        {
+          GfMonitorConfigStore *store = parser->config_store;
+          GfMonitorsConfig *config;
+          GList *l;
+          GfLogicalMonitorLayoutMode layout_mode;
+          GfMonitorsConfigFlag config_flags = GF_MONITORS_CONFIG_FLAG_NONE;
+
+          g_assert (g_str_equal (element_name, "configuration"));
+
+          if (parser->current_was_migrated)
+            layout_mode = GF_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL;
+          else
+            layout_mode = gf_monitor_manager_get_default_layout_mode (store->monitor_manager);
+
+          for (l = parser->current_logical_monitor_configs; l; l = l->next)
+            {
+              GfLogicalMonitorConfig *logical_monitor_config = l->data;
+
+              if (!derive_logical_monitor_layout (logical_monitor_config,
+                                                  layout_mode,
+                                                  error))
+                return;
+
+              if (!gf_verify_logical_monitor_config (logical_monitor_config,
+                                                     layout_mode,
+                                                     store->monitor_manager,
+                                                     error))
+                return;
+            }
+
+          if (parser->current_was_migrated)
+            config_flags |= GF_MONITORS_CONFIG_FLAG_MIGRATED;
+
+          config = gf_monitors_config_new (parser->current_logical_monitor_configs,
+                                           layout_mode, config_flags);
+
+          parser->current_logical_monitor_configs = NULL;
+
+          if (!gf_verify_monitors_config (config, store->monitor_manager, error))
+            {
+              g_object_unref (config);
+              return;
+            }
+
+          g_hash_table_replace (parser->config_store->configs,
+                                config->key, config);
+
+          parser->state = STATE_MONITORS;
+          return;
+        }
+
+      case STATE_MONITORS:
+        {
+          g_assert (g_str_equal (element_name, "monitors"));
+
+          parser->state = STATE_INITIAL;
+          return;
+        }
+
+      case STATE_INITIAL:
+      default:
+        {
+          g_assert_not_reached ();
+        }
+    }
+}
+
+static gboolean
+read_int (const gchar  *text,
+          gsize         text_len,
+          gint         *out_value,
+          GError      **error)
+{
+  gchar buf[64];
+  int64_t value;
+  gchar *end;
+
+  strncpy (buf, text, text_len);
+  buf[MIN (63, text_len)] = 0;
+
+  value = g_ascii_strtoll (buf, &end, 10);
+
+  if (*end || value < 0 || value > G_MAXINT16)
+    {
+      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Expected a number, got %s", buf);
+      return FALSE;
+    }
+  else
+    {
+      *out_value = value;
+      return TRUE;
+    }
+}
+
+static gboolean
+read_float (const gchar  *text,
+            gsize         text_len,
+            gfloat       *out_value,
+            GError      **error)
+{
+  gchar buf[64];
+  gfloat value;
+  gchar *end;
+
+  strncpy (buf, text, text_len);
+  buf[MIN (63, text_len)] = 0;
+
+  value = g_ascii_strtod (buf, &end);
+
+  if (*end)
+    {
+      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Expected a number, got %s", buf);
+      return FALSE;
+    }
+  else
+    {
+      *out_value = value;
+      return TRUE;
+    }
+}
+
+static gboolean
+read_bool (const gchar  *text,
+           gsize         text_len,
+           gboolean     *out_value,
+           GError      **error)
+{
+  if (strncmp (text, "no", text_len) == 0)
+    {
+      *out_value = FALSE;
+      return TRUE;
+    }
+  else if (strncmp (text, "yes", text_len) == 0)
+    {
+      *out_value = TRUE;
+      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 gchar *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_INITIAL:
+      case STATE_MONITORS:
+      case STATE_CONFIGURATION:
+      case STATE_MIGRATED:
+      case STATE_LOGICAL_MONITOR:
+      case STATE_MONITOR:
+      case STATE_MONITOR_SPEC:
+      case STATE_MONITOR_MODE:
+      case STATE_TRANSFORM:
+        {
+          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_MONITOR_SPEC_CONNECTOR:
+        {
+          parser->current_monitor_spec->connector = g_strndup (text, text_len);
+          return;
+        }
+
+      case STATE_MONITOR_SPEC_VENDOR:
+        {
+          parser->current_monitor_spec->vendor = g_strndup (text, text_len);
+          return;
+        }
+
+      case STATE_MONITOR_SPEC_PRODUCT:
+        {
+          parser->current_monitor_spec->product = g_strndup (text, text_len);
+          return;
+        }
+
+      case STATE_MONITOR_SPEC_SERIAL:
+        {
+          parser->current_monitor_spec->serial = g_strndup (text, text_len);
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_X:
+        {
+          read_int (text, text_len,
+                    &parser->current_logical_monitor_config->layout.x, error);
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_Y:
+        {
+          read_int (text, text_len,
+                    &parser->current_logical_monitor_config->layout.y, error);
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_SCALE:
+        {
+          if (!read_float (text, text_len,
+                           &parser->current_logical_monitor_config->scale, error))
+            return;
+
+          if (parser->current_logical_monitor_config->scale <= 0.0)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Logical monitor scale '%g' invalid",
+                           parser->current_logical_monitor_config->scale);
+              return;
+            }
+
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_PRIMARY:
+        {
+          read_bool (text, text_len,
+                     &parser->current_logical_monitor_config->is_primary,
+                     error);
+          return;
+        }
+
+      case STATE_LOGICAL_MONITOR_PRESENTATION:
+        {
+          read_bool (text, text_len,
+                     &parser->current_logical_monitor_config->is_presentation,
+                     error);
+          return;
+        }
+
+      case STATE_TRANSFORM_ROTATION:
+        {
+          if (strncmp (text, "normal", text_len) == 0)
+            parser->current_transform = GF_MONITOR_TRANSFORM_NORMAL;
+          else if (strncmp (text, "left", text_len) == 0)
+            parser->current_transform = GF_MONITOR_TRANSFORM_90;
+          else if (strncmp (text, "upside_down", text_len) == 0)
+            parser->current_transform = GF_MONITOR_TRANSFORM_180;
+          else if (strncmp (text, "right", text_len) == 0)
+            parser->current_transform = GF_MONITOR_TRANSFORM_270;
+          else
+            g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                         "Invalid rotation type %.*s", (int)text_len, text);
+
+          return;
+        }
+
+      case STATE_TRANSFORM_FLIPPED:
+        {
+          read_bool (text, text_len,
+                     &parser->current_transform_flipped,
+                     error);
+          return;
+        }
+
+      case STATE_MONITOR_MODE_WIDTH:
+        {
+          read_int (text, text_len,
+                    &parser->current_monitor_mode_spec->width,
+                    error);
+          return;
+        }
+
+      case STATE_MONITOR_MODE_HEIGHT:
+        {
+          read_int (text, text_len,
+                    &parser->current_monitor_mode_spec->height,
+                    error);
+          return;
+        }
+
+      case STATE_MONITOR_MODE_RATE:
+        {
+          read_float (text, text_len,
+                      &parser->current_monitor_mode_spec->refresh_rate,
+                      error);
+          return;
+        }
+
+      case STATE_MONITOR_MODE_FLAG:
+        {
+          if (strncmp (text, "interlace", text_len) == 0)
+            {
+              parser->current_monitor_mode_spec->flags |= GF_CRTC_MODE_FLAG_INTERLACE;
+            }
+          else
+            {
+              g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Invalid mode flag %.*s", (int) text_len, text);
+            }
+
+          return;
+        }
+
+      case STATE_MONITOR_UNDERSCANNING:
+        {
+          read_bool (text, text_len,
+                     &parser->current_monitor_config->enable_underscanning,
+                     error);
+          return;
+        }
+
+      default:
+        break;
+    }
+}
+
+static const GMarkupParser config_parser =
+  {
+    .start_element = handle_start_element,
+    .end_element = handle_end_element,
+    .text = handle_text
+  };
+
+static gboolean
+read_config_file (GfMonitorConfigStore  *config_store,
+                  GFile                 *file,
+                  GError               **error)
+{
+  gchar *buffer;
+  gsize size;
+  ConfigParser parser;
+  GMarkupParseContext *parse_context;
+
+  if (!g_file_load_contents (file, NULL, &buffer, &size, NULL, error))
+    return FALSE;
+
+  parser = (ConfigParser) {
+    .state = STATE_INITIAL,
+    .config_store = config_store
+  };
+
+  parse_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 (parse_context, buffer, size, error))
+    {
+      g_list_free_full (parser.current_logical_monitor_configs,
+                        (GDestroyNotify) gf_logical_monitor_config_free);
+
+      g_clear_pointer (&parser.current_monitor_spec, gf_monitor_spec_free);
+      g_free (parser.current_monitor_mode_spec);
+      g_clear_pointer (&parser.current_monitor_config, gf_monitor_config_free);
+
+      g_clear_pointer (&parser.current_logical_monitor_config,
+                       gf_logical_monitor_config_free);
+
+      g_markup_parse_context_free (parse_context);
+      g_free (buffer);
+
+      return FALSE;
+    }
+
+  g_markup_parse_context_free (parse_context);
+  g_free (buffer);
+
+  return TRUE;
+}
+
+static const gchar *
+bool_to_string (gboolean value)
+{
+  return value ? "yes" : "no";
+}
+
+static void
+append_transform (GString            *buffer,
+                  GfMonitorTransform  transform)
+{
+  const gchar *rotation;
+  gboolean flipped;
+
+  rotation = NULL;
+  flipped = FALSE;
+
+  switch (transform)
+    {
+      case GF_MONITOR_TRANSFORM_90:
+        rotation = "left";
+        break;
+
+      case GF_MONITOR_TRANSFORM_180:
+        rotation = "upside_down";
+        break;
+
+      case GF_MONITOR_TRANSFORM_270:
+        rotation = "right";
+        break;
+
+      case GF_MONITOR_TRANSFORM_FLIPPED:
+        rotation = "normal";
+        flipped = TRUE;
+        break;
+
+      case GF_MONITOR_TRANSFORM_FLIPPED_90:
+        rotation = "left";
+        flipped = TRUE;
+        break;
+
+      case GF_MONITOR_TRANSFORM_FLIPPED_180:
+        rotation = "upside_down";
+        flipped = TRUE;
+        break;
+
+      case GF_MONITOR_TRANSFORM_FLIPPED_270:
+        rotation = "right";
+        flipped = TRUE;
+        break;
+
+      case GF_MONITOR_TRANSFORM_NORMAL:
+      default:
+        return;
+    }
+
+  g_string_append (buffer, "      <transform>\n");
+  g_string_append_printf (buffer, "        <rotation>%s</rotation>\n", rotation);
+  g_string_append_printf (buffer, "        <flipped>%s</flipped>\n", bool_to_string (flipped));
+  g_string_append (buffer, "      </transform>\n");
+}
+
+static void
+append_monitors (GString *buffer,
+                 GList   *monitor_configs)
+{
+  GList *l;
+
+  for (l = monitor_configs; l; l = l->next)
+    {
+      GfMonitorConfig *monitor_config;
+      GfMonitorModeSpec *mode_spec;
+      GfMonitorSpec *spec;
+      gchar rate_str[G_ASCII_DTOSTR_BUF_SIZE];
+
+      monitor_config = l->data;
+      mode_spec = monitor_config->mode_spec;
+      spec = monitor_config->monitor_spec;
+
+      g_ascii_dtostr (rate_str, sizeof (rate_str), mode_spec->refresh_rate);
+
+      g_string_append (buffer, "      <monitor>\n");
+      g_string_append (buffer, "        <monitorspec>\n");
+      g_string_append_printf (buffer, "          <connector>%s</connector>\n", spec->connector);
+      g_string_append_printf (buffer, "          <vendor>%s</vendor>\n", spec->vendor);
+      g_string_append_printf (buffer, "          <product>%s</product>\n", spec->product);
+      g_string_append_printf (buffer, "          <serial>%s</serial>\n", spec->serial);
+      g_string_append (buffer, "        </monitorspec>\n");
+      g_string_append (buffer, "        <mode>\n");
+      g_string_append_printf (buffer, "          <width>%d</width>\n", mode_spec->width);
+      g_string_append_printf (buffer, "          <height>%d</height>\n", mode_spec->height);
+      g_string_append_printf (buffer, "          <rate>%s</rate>\n", rate_str);
+      if (monitor_config->mode_spec->flags & GF_CRTC_MODE_FLAG_INTERLACE)
+        g_string_append_printf (buffer, "          <flag>interlace</flag>\n");
+      g_string_append (buffer, "        </mode>\n");
+      if (monitor_config->enable_underscanning)
+        g_string_append (buffer, "        <underscanning>yes</underscanning>\n");
+      g_string_append (buffer, "      </monitor>\n");
+    }
+}
+
+static void
+append_logical_monitor_xml (GString                *buffer,
+                            GfMonitorsConfig       *config,
+                            GfLogicalMonitorConfig *logical_monitor_config)
+{
+  gchar scale_str[G_ASCII_DTOSTR_BUF_SIZE];
+
+  g_string_append (buffer, "    <logicalmonitor>\n");
+  g_string_append_printf (buffer, "      <x>%d</x>\n", logical_monitor_config->layout.x);
+  g_string_append_printf (buffer, "      <y>%d</y>\n", logical_monitor_config->layout.y);
+
+  g_ascii_dtostr (scale_str, G_ASCII_DTOSTR_BUF_SIZE, logical_monitor_config->scale);
+  if ((config->flags & GF_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)
+    g_string_append (buffer, "      <presentation>yes</presentation>\n");
+
+  append_transform (buffer, logical_monitor_config->transform);
+  append_monitors (buffer, logical_monitor_config->monitor_configs);
+  g_string_append (buffer, "    </logicalmonitor>\n");
+}
+
+static GString *
+generate_config_xml (GfMonitorConfigStore *config_store)
+{
+  GString *buffer;
+  GHashTableIter iter;
+  GfMonitorsConfig *config;
+
+  buffer = g_string_new ("");
+  g_string_append_printf (buffer, "<monitors version=\"%d\">\n",
+                          MONITORS_CONFIG_XML_FORMAT_VERSION);
+
+  g_hash_table_iter_init (&iter, config_store->configs);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &config))
+    {
+      GList *l;
+
+      g_string_append (buffer, "  <configuration>\n");
+
+      if (config->flags & GF_MONITORS_CONFIG_FLAG_MIGRATED)
+        g_string_append (buffer, "    <migrated/>\n");
+
+      for (l = config->logical_monitor_configs; l; l = l->next)
+        {
+          GfLogicalMonitorConfig *logical_monitor_config = l->data;
+
+          append_logical_monitor_xml (buffer, config, logical_monitor_config);
+        }
+
+      g_string_append (buffer, "  </configuration>\n");
+    }
+
+  g_string_append (buffer, "</monitors>\n");
+
+  return buffer;
+}
+
+static void
+saved_cb (GObject      *object,
+          GAsyncResult *result,
+          gpointer      user_data)
+{
+  SaveData *data;
+  GError *error;
+
+  data = user_data;
+
+  error = NULL;
+  if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Saving monitor configuration failed: %s\n", error->message);
+          g_clear_object (&data->config_store->save_cancellable);
+        }
+
+      g_error_free (error);
+    }
+  else
+    {
+      g_clear_object (&data->config_store->save_cancellable);
+    }
+
+  g_clear_object (&data->config_store);
+  g_string_free (data->buffer, TRUE);
+  g_free (data);
+}
+
+static void
+gf_monitor_config_store_save_sync (GfMonitorConfigStore *config_store)
+{
+  GFile *file;
+  GString *buffer;
+  GError *error;
+
+  if (config_store->custom_write_file)
+    file = config_store->custom_write_file;
+  else
+    file = config_store->user_file;
+
+  buffer = generate_config_xml (config_store);
+
+  error = NULL;
+  if (!g_file_replace_contents (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);
+    }
+
+  g_string_free (buffer, TRUE);
+}
+
+static void
+gf_monitor_config_store_save (GfMonitorConfigStore *config_store)
+{
+  GString *buffer;
+  SaveData *data;
+
+  if (config_store->save_cancellable)
+    {
+      g_cancellable_cancel (config_store->save_cancellable);
+      g_clear_object (&config_store->save_cancellable);
+    }
+
+  /*
+   * 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)
+    {
+      gf_monitor_config_store_save_sync (config_store);
+      return;
+    }
+
+  config_store->save_cancellable = g_cancellable_new ();
+
+  buffer = generate_config_xml (config_store);
+
+  data = g_new0 (SaveData, 1);
+
+  data->config_store = g_object_ref (config_store);
+  data->buffer = buffer;
+
+  g_file_replace_contents_async (config_store->user_file,
+                                 buffer->str, buffer->len,
+                                 NULL,
+                                 TRUE,
+                                 G_FILE_CREATE_REPLACE_DESTINATION,
+                                 config_store->save_cancellable,
+                                 saved_cb, data);
+}
+
+static void
+maybe_save_configs (GfMonitorConfigStore *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)
+    gf_monitor_config_store_save (config_store);
+}
+
+static void
+gf_monitor_config_store_constructed (GObject *object)
+{
+  GfMonitorConfigStore *config_store;
+  gchar *filename;
+
+  G_OBJECT_CLASS (gf_monitor_config_store_parent_class)->constructed (object);
+
+  config_store = GF_MONITOR_CONFIG_STORE (object);
+
+  filename = g_build_filename (g_get_user_config_dir (), "monitors.xml", NULL);
+  config_store->user_file = g_file_new_for_path (filename);
+
+  if (g_file_test (filename, G_FILE_TEST_EXISTS))
+    {
+      GError *error;
+
+      error = NULL;
+      if (!read_config_file (config_store, config_store->user_file, &error))
+        {
+          if (error->domain == GF_MONITOR_CONFIG_STORE_ERROR &&
+              error->code == GF_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION)
+            {
+              g_clear_error (&error);
+              if (!gf_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",
+                         filename, error->message);
+              g_error_free (error);
+            }
+        }
+    }
+
+  g_free (filename);
+}
+
+static void
+gf_monitor_config_store_dispose (GObject *object)
+{
+  GfMonitorConfigStore *config_store;
+
+  config_store = GF_MONITOR_CONFIG_STORE (object);
+
+  if (config_store->save_cancellable)
+    {
+      g_cancellable_cancel (config_store->save_cancellable);
+      g_clear_object (&config_store->save_cancellable);
+
+      gf_monitor_config_store_save_sync (config_store);
+    }
+
+  config_store->monitor_manager = NULL;
+
+  g_clear_pointer (&config_store->configs, g_hash_table_destroy);
+
+  g_clear_object (&config_store->user_file);
+  g_clear_object (&config_store->custom_read_file);
+  g_clear_object (&config_store->custom_write_file);
+
+  G_OBJECT_CLASS (gf_monitor_config_store_parent_class)->dispose (object);
+}
+
+static void
+gf_monitor_config_store_get_property (GObject    *object,
+                                      guint       property_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  GfMonitorConfigStore *config_store;
+
+  config_store = GF_MONITOR_CONFIG_STORE (object);
+
+  switch (property_id)
+    {
+      case PROP_MONITOR_MANAGER:
+        g_value_set_object (value, &config_store->monitor_manager);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+gf_monitor_config_store_set_property (GObject      *object,
+                                      guint         property_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  GfMonitorConfigStore *config_store;
+
+  config_store = GF_MONITOR_CONFIG_STORE (object);
+
+  switch (property_id)
+    {
+      case PROP_MONITOR_MANAGER:
+        config_store->monitor_manager = g_value_get_object (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+gf_monitor_config_store_class_init (GfMonitorConfigStoreClass *config_store_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (config_store_class);
+
+  object_class->constructed = gf_monitor_config_store_constructed;
+  object_class->dispose = gf_monitor_config_store_dispose;
+  object_class->get_property = gf_monitor_config_store_get_property;
+  object_class->set_property = gf_monitor_config_store_set_property;
+
+  config_store_properties[PROP_MONITOR_MANAGER] =
+    g_param_spec_object ("monitor-manager",
+                         "GfMonitorManager",
+                         "GfMonitorManager",
+                         GF_TYPE_MONITOR_MANAGER,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_STRINGS |
+                         G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, LAST_PROP,
+                                     config_store_properties);
+}
+
+static void
+gf_monitor_config_store_init (GfMonitorConfigStore *config_store)
+{
+  config_store->configs = g_hash_table_new_full (gf_monitors_config_key_hash,
+                                                 gf_monitors_config_key_equal,
+                                                 NULL, g_object_unref);
+}
+
+GfMonitorConfigStore *
+gf_monitor_config_store_new (GfMonitorManager *monitor_manager)
+{
+  return g_object_new (GF_TYPE_MONITOR_CONFIG_STORE,
+                       "monitor-manager", monitor_manager,
+                       NULL);
+}
+
+GfMonitorsConfig *
+gf_monitor_config_store_lookup (GfMonitorConfigStore *config_store,
+                                GfMonitorsConfigKey  *key)
+{
+  return GF_MONITORS_CONFIG (g_hash_table_lookup (config_store->configs, key));
+}
+
+void
+gf_monitor_config_store_add (GfMonitorConfigStore *config_store,
+                             GfMonitorsConfig     *config)
+{
+  g_hash_table_replace (config_store->configs, config->key, g_object_ref (config));
+  maybe_save_configs (config_store);
+}
+
+void
+gf_monitor_config_store_remove (GfMonitorConfigStore *config_store,
+                                GfMonitorsConfig     *config)
+{
+  g_hash_table_remove (config_store->configs, config->key);
+  maybe_save_configs (config_store);
+}
+
+gboolean
+gf_monitor_config_store_set_custom (GfMonitorConfigStore  *config_store,
+                                    const gchar           *read_path,
+                                    const gchar           *write_path,
+                                    GError               **error)
+{
+  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_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_read_file, error);
+}
+
+gint
+gf_monitor_config_store_get_config_count (GfMonitorConfigStore *config_store)
+{
+  return (gint) g_hash_table_size (config_store->configs);
+}
+
+GfMonitorManager *
+gf_monitor_config_store_get_monitor_manager (GfMonitorConfigStore *config_store)
+{
+  return config_store->monitor_manager;
+}
diff --git a/backends/gf-monitor-manager-private.h b/backends/gf-monitor-manager-private.h
index 7228286..ad45e52 100644
--- a/backends/gf-monitor-manager-private.h
+++ b/backends/gf-monitor-manager-private.h
@@ -155,20 +155,22 @@ typedef struct
   GfLogicalMonitorLayoutMode   (* get_default_layout_mode)      (GfMonitorManager            *manager);
 } GfMonitorManagerClass;
 
-GType                       gf_monitor_manager_get_type              (void);
+GType                       gf_monitor_manager_get_type                (void);
 
-GfBackend                  *gf_monitor_manager_get_backend           (GfMonitorManager *manager);
+GfBackend                  *gf_monitor_manager_get_backend             (GfMonitorManager *manager);
 
-GfMonitor                  *gf_monitor_manager_get_monitor_from_spec (GfMonitorManager *manager,
-                                                                      GfMonitorSpec    *monitor_spec);
+GfMonitor                  *gf_monitor_manager_get_monitor_from_spec   (GfMonitorManager *manager,
+                                                                        GfMonitorSpec    *monitor_spec);
 
-void                        gf_monitor_manager_tiled_monitor_added   (GfMonitorManager *manager,
-                                                                      GfMonitor        *monitor);
+void                        gf_monitor_manager_tiled_monitor_added     (GfMonitorManager *manager,
+                                                                        GfMonitor        *monitor);
 
-void                        gf_monitor_manager_tiled_monitor_removed (GfMonitorManager *manager,
-                                                                      GfMonitor        *monitor);
+void                        gf_monitor_manager_tiled_monitor_removed   (GfMonitorManager *manager,
+                                                                        GfMonitor        *monitor);
 
-GfMonitorManagerCapability  gf_monitor_manager_get_capabilities      (GfMonitorManager *manager);
+GfMonitorManagerCapability  gf_monitor_manager_get_capabilities        (GfMonitorManager *manager);
+
+GfLogicalMonitorLayoutMode  gf_monitor_manager_get_default_layout_mode (GfMonitorManager *manager);
 
 static inline gboolean
 gf_monitor_transform_is_rotated (GfMonitorTransform transform)
diff --git a/backends/gf-monitor-manager-types-private.h b/backends/gf-monitor-manager-types-private.h
index 919f5a8..8014bc4 100644
--- a/backends/gf-monitor-manager-types-private.h
+++ b/backends/gf-monitor-manager-types-private.h
@@ -30,6 +30,7 @@
 G_BEGIN_DECLS
 
 typedef struct _GfMonitorConfigManager GfMonitorConfigManager;
+typedef struct _GfMonitorConfigStore GfMonitorConfigStore;
 typedef struct _GfMonitorsConfig GfMonitorsConfig;
 
 typedef struct _GfMonitor GfMonitor;
diff --git a/backends/gf-monitor-manager.c b/backends/gf-monitor-manager.c
index ba7b808..6595ee8 100644
--- a/backends/gf-monitor-manager.c
+++ b/backends/gf-monitor-manager.c
@@ -380,3 +380,13 @@ gf_monitor_manager_get_capabilities (GfMonitorManager *manager)
 
   return manager_class->get_capabilities (manager);
 }
+
+GfLogicalMonitorLayoutMode
+gf_monitor_manager_get_default_layout_mode (GfMonitorManager *manager)
+{
+  GfMonitorManagerClass *manager_class;
+
+  manager_class = GF_MONITOR_MANAGER_GET_CLASS (manager);
+
+  return manager_class->get_default_layout_mode (manager);
+}


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