[gnome-flashback] display-config: implement configuration saving and loading



commit 56f9521dea6a57e5d691c1076b0cb66a9151f5e8
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Fri Mar 27 03:46:25 2015 +0200

    display-config: implement configuration saving and loading
    
    Code based on mutter 3.16.0 release.

 configure.ac                                       |    2 +-
 .../libdisplay-config/flashback-display-config.c   |    4 +-
 .../libdisplay-config/flashback-monitor-config.c   | 1902 +++++++++++++++++++-
 .../libdisplay-config/flashback-monitor-config.h   |   14 +-
 .../libdisplay-config/flashback-monitor-manager.c  |   49 +-
 .../libdisplay-config/flashback-monitor-manager.h  |   16 +
 6 files changed, 1959 insertions(+), 28 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e4cc8fe..41052ba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,7 +49,7 @@ PKG_CHECK_MODULES(DESKTOP_BACKGROUND, gtk+-3.0 >= $GTK_REQUIRED gnome-desktop-3.
 AC_SUBST(DESKTOP_BACKGROUND_CFLAGS)
 AC_SUBST(DESKTOP_BACKGROUND_LIBS)
 
-PKG_CHECK_MODULES(DISPLAY_CONFIG, gtk+-3.0 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED gnome-desktop-3.0 >= 
$LIBGNOME_DESKTOP_REQUIRED x11 xext xrandr x11-xcb xcb-randr)
+PKG_CHECK_MODULES(DISPLAY_CONFIG, gtk+-3.0 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED gnome-desktop-3.0 >= 
$LIBGNOME_DESKTOP_REQUIRED upower-glib >= 0.99.0 x11 xext xrandr x11-xcb xcb-randr)
 AC_SUBST(DISPLAY_CONFIG_CFLAGS)
 AC_SUBST(DISPLAY_CONFIG_LIBS)
 
diff --git a/gnome-flashback/libdisplay-config/flashback-display-config.c 
b/gnome-flashback/libdisplay-config/flashback-display-config.c
index 8a5563b..c80a008 100644
--- a/gnome-flashback/libdisplay-config/flashback-display-config.c
+++ b/gnome-flashback/libdisplay-config/flashback-display-config.c
@@ -80,7 +80,7 @@ save_config_timeout (gpointer user_data)
 
   manager = FLASHBACK_MONITOR_MANAGER (user_data);
 
-  flashback_monitor_config_restore_previous (manager->config, manager);
+  flashback_monitor_config_restore_previous (manager->config);
 
   manager->persistent_timeout_id = 0;
   return G_SOURCE_REMOVE;
@@ -629,7 +629,7 @@ handle_apply_configuration (MetaDBusDisplayConfig *skeleton,
      appropriate UI. Then wait 20 seconds and if not confirmed, revert the
      configuration.
   */
-  flashback_monitor_config_update_current (manager->config, manager);
+  flashback_monitor_config_update_current (manager->config);
 
   if (persistent)
     {
diff --git a/gnome-flashback/libdisplay-config/flashback-monitor-config.c 
b/gnome-flashback/libdisplay-config/flashback-monitor-config.c
index aeaa894..4340e2a 100644
--- a/gnome-flashback/libdisplay-config/flashback-monitor-config.c
+++ b/gnome-flashback/libdisplay-config/flashback-monitor-config.c
@@ -22,19 +22,1764 @@
  * - /src/backends/meta-monitor-config.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 <string.h>
+#include <libupower-glib/upower.h>
 #include "flashback-monitor-config.h"
 
+/* These structures represent the intended/persistent configuration,
+   as stored in the monitors.xml file.
+*/
+
+typedef struct {
+  char *connector;
+  char *vendor;
+  char *product;
+  char *serial;
+} MetaOutputKey;
+
+/* Keep this structure packed, so that we
+   can use memcmp */
+typedef struct {
+  gboolean enabled;
+  GdkRectangle rect;
+  float refresh_rate;
+  MetaMonitorTransform transform;
+
+  gboolean is_primary;
+  gboolean is_presentation;
+} MetaOutputConfig;
+
+typedef struct {
+  guint refcount;
+  MetaOutputKey *keys;
+  MetaOutputConfig *outputs;
+  unsigned int n_outputs;
+} MetaConfiguration;
+
+typedef enum {
+  STATE_INITIAL,
+  STATE_MONITORS,
+  STATE_CONFIGURATION,
+  STATE_OUTPUT,
+  STATE_OUTPUT_FIELD,
+  STATE_CLONE
+} ParserState;
+
+typedef struct {
+  FlashbackMonitorConfig *config;
+  ParserState state;
+  int unknown_count;
+
+  GArray *key_array;
+  GArray *output_array;
+  MetaOutputKey key;
+  MetaOutputConfig output;
+
+  char *output_field;
+} ConfigParser;
+
+/*
+ * CRTC assignment
+ */
+typedef struct
+{
+  MetaConfiguration  *config;
+  FlashbackMonitorManager *manager;
+  GHashTable         *info;
+} CrtcAssignment;
+
+typedef struct {
+  FlashbackMonitorConfig *config;
+  GString *buffer;
+} SaveClosure;
+
 struct _FlashbackMonitorConfig
 {
-  GObject parent;
+  GObject                  parent;
+
+  GHashTable              *configs;
+  MetaConfiguration       *current;
+  gboolean                 current_is_for_laptop_lid;
+  MetaConfiguration       *previous;
+
+  GFile                   *file;
+  GCancellable            *save_cancellable;
+
+  UpClient                *up_client;
+  gboolean                 lid_is_closed;
+
+  FlashbackMonitorManager *manager;
 };
 
 G_DEFINE_TYPE (FlashbackMonitorConfig, flashback_monitor_config, G_TYPE_OBJECT)
 
+static gboolean
+rectangle_overlap (const GdkRectangle *rect1,
+                   const GdkRectangle *rect2)
+{
+  g_return_val_if_fail (rect1 != NULL, FALSE);
+  g_return_val_if_fail (rect2 != NULL, FALSE);
+
+  return !((rect1->x + rect1->width  <= rect2->x) ||
+           (rect2->x + rect2->width  <= rect1->x) ||
+           (rect1->y + rect1->height <= rect2->y) ||
+           (rect2->y + rect2->height <= rect1->y));
+}
+
+static gboolean
+rectangle_overlaps_with_region (const GList        *spanning_rects,
+                                const GdkRectangle *rect)
+{
+  const GList *temp;
+  gboolean     overlaps;
+
+  temp = spanning_rects;
+  overlaps = FALSE;
+  while (!overlaps && temp != NULL)
+    {
+      overlaps = overlaps || rectangle_overlap (temp->data, rect);
+      temp = temp->next;
+    }
+
+  return overlaps;
+}
+
+static void
+init_config_from_preferred_mode (MetaOutputConfig *config,
+                                 MetaOutput       *output)
+{
+  config->enabled = TRUE;
+  config->rect.x = 0;
+  config->rect.y = 0;
+  config->rect.width = output->preferred_mode->width;
+  config->rect.height = output->preferred_mode->height;
+  config->refresh_rate = output->preferred_mode->refresh_rate;
+  config->transform = META_MONITOR_TRANSFORM_NORMAL;
+  config->is_primary = FALSE;
+  config->is_presentation = FALSE;
+}
+
+static void
+init_key_from_output (MetaOutputKey *key,
+                      MetaOutput    *output)
+{
+  key->connector = g_strdup (output->name);
+  key->product = g_strdup (output->product);
+  key->vendor = g_strdup (output->vendor);
+  key->serial = g_strdup (output->serial);
+}
+
+static void
+make_config_key (MetaConfiguration *key,
+                 MetaOutput        *outputs,
+                 unsigned           n_outputs,
+                 unsigned           skip)
+{
+  unsigned int o, i;
+
+  key->outputs = NULL;
+  key->keys = g_new0 (MetaOutputKey, n_outputs);
+
+  for (o = 0, i = 0; i < n_outputs; o++, i++)
+    if (i == skip)
+      o--;
+    else
+      init_key_from_output (&key->keys[o], &outputs[i]);
+
+  key->n_outputs = o;
+}
+
+static MetaOutput *
+find_output_by_key (MetaOutput    *outputs,
+                    unsigned int   n_outputs,
+                    MetaOutputKey *key)
+{
+  unsigned int i;
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      if (strcmp (outputs[i].name, key->connector) == 0)
+        {
+          /* This should be checked a lot earlier! */
+
+          g_warn_if_fail (strcmp (outputs[i].vendor, key->vendor) == 0 &&
+                          strcmp (outputs[i].product, key->product) == 0 &&
+                          strcmp (outputs[i].serial, key->serial) == 0);
+          return &outputs[i];
+        }
+    }
+
+  /* Just to satisfy GCC - this is a fatal error if occurs */
+  return NULL;
+}
+
+static gboolean
+key_is_laptop (MetaOutputKey *key)
+{
+  /* FIXME: extend with better heuristics */
+  return g_str_has_prefix (key->connector, "LVDS") ||
+    g_str_has_prefix (key->connector, "eDP");
+}
+
+static gboolean
+output_is_laptop (MetaOutput *output)
+{
+  /* FIXME: extend with better heuristics */
+  switch (output->connector_type)
+    {
+    case META_CONNECTOR_TYPE_eDP:
+    case META_CONNECTOR_TYPE_LVDS:
+      return TRUE;
+    default:
+      return FALSE;
+    }
+}
+
+static gboolean
+laptop_display_is_on (MetaConfiguration *config)
+{
+  unsigned int i;
+
+  for (i = 0; i < config->n_outputs; i++)
+    {
+      MetaOutputKey *key = &config->keys[i];
+      MetaOutputConfig *output = &config->outputs[i];
+
+      if (key_is_laptop (key) && output->enabled)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+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
+config_clear (MetaConfiguration *config)
+{
+  unsigned int i;
+
+  for (i = 0; i < config->n_outputs; i++)
+    free_output_key (&config->keys[i]);
+
+  g_free (config->keys);
+  g_free (config->outputs);
+}
+
+static MetaConfiguration *
+config_ref (MetaConfiguration *config)
+{
+  config->refcount++;
+  return config;
+}
+
+static void
+config_unref (MetaConfiguration *config)
+{
+  if (--config->refcount == 0)
+    {
+      config_clear (config);
+      g_slice_free (MetaConfiguration, config);
+    }
+}
+
+static MetaConfiguration *
+config_new (void)
+{
+  MetaConfiguration *config = g_slice_new0 (MetaConfiguration);
+  config->refcount = 1;
+  return config;
+}
+
+static void
+set_current (FlashbackMonitorConfig *self,
+             MetaConfiguration      *config)
+{
+  g_clear_pointer (&self->previous, (GDestroyNotify) config_unref);
+  self->previous = self->current;
+  self->current = config_ref (config);
+}
+
+static gboolean
+multiple_outputs_are_enabled (MetaConfiguration *config)
+{
+  unsigned int i, enabled;
+
+  enabled = 0;
+  for (i = 0; i < config->n_outputs; i++)
+    if (config->outputs[i].enabled)
+      enabled++;
+
+  return enabled > 1;
+}
+
+static MetaConfiguration *
+make_laptop_lid_config (MetaConfiguration  *reference)
+{
+  MetaConfiguration *new;
+  unsigned int i;
+  gboolean has_primary;
+  int x_after, y_after;
+  int x_offset, y_offset;
+
+  g_assert (multiple_outputs_are_enabled (reference));
+
+  new = config_new ();
+  new->n_outputs = reference->n_outputs;
+  new->keys = g_new0 (MetaOutputKey, reference->n_outputs);
+  new->outputs = g_new0 (MetaOutputConfig, reference->n_outputs);
+
+  x_after = G_MAXINT; y_after = G_MAXINT;
+  x_offset = 0; y_offset = 0;
+  for (i = 0; i < new->n_outputs; i++)
+    {
+      MetaOutputKey *current_key = &reference->keys[i];
+      MetaOutputConfig *current_output = &reference->outputs[i];
+
+      new->keys[i].connector = g_strdup (current_key->connector);
+      new->keys[i].vendor = g_strdup (current_key->vendor);
+      new->keys[i].product = g_strdup (current_key->product);
+      new->keys[i].serial = g_strdup (current_key->serial);
+
+      if (key_is_laptop (current_key))
+        {
+          new->outputs[i].enabled = FALSE;
+          x_after = current_output->rect.x;
+          y_after = current_output->rect.y;
+          x_offset = current_output->rect.width;
+          y_offset = current_output->rect.height;
+        }
+      else
+        new->outputs[i] = *current_output;
+    }
+  for (i = 0; i < new->n_outputs; i++)
+    {
+      if (new->outputs[i].enabled)
+        {
+          if (new->outputs[i].rect.x > x_after)
+            new->outputs[i].rect.x -= x_offset;
+          if (new->outputs[i].rect.y > y_after)
+            new->outputs[i].rect.y -= y_offset;
+        }
+    }
+
+  has_primary = FALSE;
+  for (i = 0; i < new->n_outputs; i++)
+    {
+      if (new->outputs[i].is_primary)
+        {
+          has_primary = TRUE;
+          break;
+        }
+    }
+  if (!has_primary)
+    new->outputs[0].is_primary = TRUE;
+
+  return new;
+}
+
+static gboolean
+output_can_clone (MetaOutput *output,
+                  MetaOutput *clone)
+{
+  unsigned int i;
+
+  for (i = 0; i < output->n_possible_clones; i++)
+    if (output->possible_clones[i] == clone)
+      return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+can_clone (MetaCRTCInfo *info,
+          MetaOutput   *output)
+{
+  unsigned int i;
+
+  for (i = 0; i < info->outputs->len; ++i)
+    {
+      MetaOutput *clone = info->outputs->pdata[i];
+
+       if (!output_can_clone (clone, output))
+           return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+crtc_can_drive_output (MetaCRTC   *crtc,
+                       MetaOutput *output)
+{
+  unsigned int i;
+
+  for (i = 0; i < output->n_possible_crtcs; i++)
+    if (output->possible_crtcs[i] == crtc)
+      return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+output_supports_mode (MetaOutput      *output,
+                      MetaMonitorMode *mode)
+{
+  unsigned int i;
+
+  for (i = 0; i < output->n_modes; i++)
+    if (output->modes[i] == mode)
+      return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment       *assign,
+                        MetaCRTC             *crtc,
+                        MetaMonitorMode      *mode,
+                        int                   x,
+                        int                   y,
+                        MetaMonitorTransform  transform,
+                        MetaOutput           *output)
+{
+  MetaCRTCInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+  if (!crtc_can_drive_output (crtc, output))
+    return FALSE;
+
+  if (!output_supports_mode (output, mode))
+    return FALSE;
+
+  if ((crtc->all_transforms & (1 << transform)) == 0)
+    return FALSE;
+
+  if (info)
+    {
+      if (!(info->mode == mode &&
+            info->x == x               &&
+            info->y == y               &&
+            info->transform == transform))
+        return FALSE;
+
+      if (!can_clone (info, output))
+        return FALSE;
+
+      g_ptr_array_add (info->outputs, output);
+      return TRUE;
+    }
+  else
+    {
+      MetaCRTCInfo *info = g_slice_new0 (MetaCRTCInfo);
+
+      info->crtc = crtc;
+      info->mode = mode;
+      info->x = x;
+      info->y = y;
+      info->transform = transform;
+      info->outputs = g_ptr_array_new ();
+
+      g_ptr_array_add (info->outputs, output);
+      g_hash_table_insert (assign->info, crtc, info);
+
+      return TRUE;
+    }
+}
+
+static void
+crtc_assignment_unassign (CrtcAssignment *assign,
+                          MetaCRTC       *crtc,
+                          MetaOutput     *output)
+{
+  MetaCRTCInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+  if (info)
+    {
+      g_ptr_array_remove (info->outputs, output);
+
+      if (info->outputs->len == 0)
+        g_hash_table_remove (assign->info, crtc);
+    }
+}
+
+/* Check whether the given set of settings can be used
+ * at the same time -- ie. whether there is an assignment
+ * of CRTC's to outputs.
+ *
+ * Brute force - the number of objects involved is small
+ * enough that it doesn't matter.
+ */
+static gboolean
+real_assign_crtcs (CrtcAssignment     *assignment,
+                   unsigned int        output_num)
+{
+  MetaMonitorMode *modes;
+  MetaCRTC *crtcs;
+  MetaOutput *outputs;
+  unsigned int n_crtcs, n_modes, n_outputs;
+  MetaOutputKey *output_key;
+  MetaOutputConfig *output_config;
+  unsigned int i;
+  gboolean success;
+
+  if (output_num == assignment->config->n_outputs)
+    return TRUE;
+
+  output_key = &assignment->config->keys[output_num];
+  output_config = &assignment->config->outputs[output_num];
+
+  /* It is always allowed for an output to be turned off */
+  if (!output_config->enabled)
+    return real_assign_crtcs (assignment, output_num + 1);
+
+  flashback_monitor_manager_get_resources (assignment->manager,
+                                           &modes, &n_modes,
+                                           &crtcs, &n_crtcs,
+                                           &outputs, &n_outputs);
+
+  success = FALSE;
+
+  for (i = 0; i < n_crtcs; i++)
+    {
+      MetaCRTC *crtc = &crtcs[i];
+      unsigned int pass;
+
+      /* Make two passes, one where frequencies must match, then
+       * one where they don't have to
+       */
+      for (pass = 0; pass < 2; pass++)
+        {
+          MetaOutput *output = find_output_by_key (outputs, n_outputs, output_key);
+          unsigned int j;
+
+          for (j = 0; j < n_modes; j++)
+            {
+              MetaMonitorMode *mode = &modes[j];
+              int width, height;
+
+              if (output_config->transform % 2)
+                {
+                  width = mode->height;
+                  height = mode->width;
+                }
+              else
+                {
+                  width = mode->width;
+                  height = mode->height;
+                }
+
+              if (width == output_config->rect.width &&
+                  height == output_config->rect.height &&
+                  (pass == 1 || mode->refresh_rate == output_config->refresh_rate))
+                {
+                  g_debug ("CRTC %ld: trying mode %dx%d %fHz with output at %dx%d %fHz (transform %d) (pass 
%d)\n",
+                           crtc->crtc_id,
+                           mode->width, mode->height, mode->refresh_rate,
+                           output_config->rect.width, output_config->rect.height, 
output_config->refresh_rate,
+                           output_config->transform,
+                           pass);
+
+                  if (crtc_assignment_assign (assignment, crtc, &modes[j],
+                                              output_config->rect.x, output_config->rect.y,
+                                              output_config->transform,
+                                              output))
+                    {
+                      if (real_assign_crtcs (assignment, output_num + 1))
+                        {
+                          success = TRUE;
+                          goto out;
+                        }
+
+                      crtc_assignment_unassign (assignment, crtc, output);
+                    }
+                }
+            }
+             }
+    }
+
+out:
+  return success;
+}
+
+static gboolean
+meta_monitor_config_assign_crtcs (MetaConfiguration       *config,
+                                  FlashbackMonitorManager *manager,
+                                  GPtrArray               *crtcs,
+                                  GPtrArray               *outputs)
+{
+  CrtcAssignment assignment;
+  GHashTableIter iter;
+  MetaCRTC *crtc;
+  MetaCRTCInfo *info;
+  unsigned int i;
+  MetaOutput *all_outputs;
+  unsigned int n_outputs;
+
+  assignment.config = config;
+  assignment.manager = manager;
+  assignment.info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)meta_crtc_info_free);
+
+  if (!real_assign_crtcs (&assignment, 0))
+    {
+      g_warning ("Could not assign CRTC to outputs, ignoring configuration\n");
+
+      g_hash_table_destroy (assignment.info);
+      return FALSE;
+    }
+
+  g_hash_table_iter_init (&iter, assignment.info);
+  while (g_hash_table_iter_next (&iter, (void**)&crtc, (void**)&info))
+    {
+      g_hash_table_iter_steal (&iter);
+      g_ptr_array_add (crtcs, info);
+    }
+
+  all_outputs = flashback_monitor_manager_get_outputs (manager, &n_outputs);
+  g_assert (n_outputs == config->n_outputs);
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      MetaOutputInfo *output_info = g_slice_new (MetaOutputInfo);
+      MetaOutputConfig *output_config = &config->outputs[i];
+
+      output_info->output = find_output_by_key (all_outputs, n_outputs,
+                                                &config->keys[i]);
+      output_info->is_primary = output_config->is_primary;
+      output_info->is_presentation = output_config->is_presentation;
+
+      g_ptr_array_add (outputs, output_info);
+    }
+
+  g_hash_table_destroy (assignment.info);
+  return TRUE;
+}
+
+static gboolean
+apply_configuration (FlashbackMonitorConfig *self,
+                     MetaConfiguration      *config)
+{
+  FlashbackMonitorManager *manager;
+  GPtrArray *crtcs, *outputs;
+  gboolean ret = FALSE;
+
+  manager = self->manager;
+  crtcs = g_ptr_array_new_full (config->n_outputs, (GDestroyNotify)meta_crtc_info_free);
+  outputs = g_ptr_array_new_full (config->n_outputs, (GDestroyNotify)meta_output_info_free);
+
+  if (!meta_monitor_config_assign_crtcs (config, manager, crtcs, outputs))
+    goto out;
+
+  flashback_monitor_manager_apply_configuration (manager,
+                                                 (MetaCRTCInfo**)crtcs->pdata, crtcs->len,
+                                                 (MetaOutputInfo**)outputs->pdata, outputs->len);
+
+  set_current (self, config);
+
+  /* If true, we'll be overridden at the end of this call
+   * inside turn_off_laptop_display / apply_configuration_with_lid */
+  self->current_is_for_laptop_lid = FALSE;
+
+  ret = TRUE;
+
+ out:
+  g_ptr_array_unref (crtcs);
+  g_ptr_array_unref (outputs);
+  return ret;
+}
+
+static gboolean
+apply_configuration_with_lid (FlashbackMonitorConfig *self,
+                              MetaConfiguration      *config)
+{
+  if (self->lid_is_closed &&
+      multiple_outputs_are_enabled (config) &&
+      laptop_display_is_on (config))
+    {
+      MetaConfiguration *laptop_lid_config = make_laptop_lid_config (config);
+      if (apply_configuration (self, laptop_lid_config))
+        {
+          self->current_is_for_laptop_lid = TRUE;
+          config_unref (laptop_lid_config);
+          return TRUE;
+        }
+      else
+        {
+          config_unref (laptop_lid_config);
+          return FALSE;
+        }
+    }
+  else
+    return apply_configuration (self, config);
+}
+
+static void
+turn_off_laptop_display (FlashbackMonitorConfig *config)
+{
+  MetaConfiguration *new;
+
+  if (!multiple_outputs_are_enabled (config->current))
+    return;
+
+  new = make_laptop_lid_config (config->current);
+  apply_configuration (config, new);
+  config_unref (new);
+  config->current_is_for_laptop_lid = TRUE;
+}
+
+static void
+power_client_changed_cb (UpClient   *client,
+                         GParamSpec *pspec,
+                         gpointer    user_data)
+{
+  FlashbackMonitorConfig *config;
+  gboolean is_closed;
+
+  config = FLASHBACK_MONITOR_CONFIG (user_data);
+  is_closed = up_client_get_lid_is_closed (config->up_client);
+
+  if (is_closed != config->lid_is_closed)
+    {
+      config->lid_is_closed = is_closed;
+
+      if (is_closed)
+        turn_off_laptop_display (config);
+      else if (config->current_is_for_laptop_lid)
+        flashback_monitor_config_restore_previous (config);
+    }
+}
+
+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) && 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)
+          {
+            MetaConfiguration *config = g_slice_new (MetaConfiguration);
+
+            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->config->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.width == 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)
+          {
+            /* FIXME (look at the rotation map in monitor.c) */
+            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
+          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 void
+flashback_monitor_config_load (FlashbackMonitorConfig *self)
+{
+  char *contents;
+  gsize size;
+  gboolean ok;
+  GError *error;
+  GMarkupParseContext *context;
+  ConfigParser parser;
+
+  /* Note: we're explicitly loading this file synchronously because
+     we don't want to leave the default configuration on for even a frame, ie we
+     want atomic modeset as much as possible.
+
+     This function is called only at early initialization anyway, before
+     we connect to X or create the wayland socket.
+  */
+
+  error = NULL;
+  ok = g_file_load_contents (self->file, NULL, &contents, &size, NULL, &error);
+  if (!ok)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        g_warning ("Failed to load stored monitor configuration: %s\n", error->message);
+
+      g_error_free (error);
+      return;
+    }
+
+  memset (&parser, 0, sizeof (ConfigParser));
+  parser.config = self;
+  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);
+  ok = g_markup_parse_context_parse (context, contents, size, &error);
+
+  if (!ok)
+    {
+      g_warning ("Failed to parse stored monitor configuration: %s\n", error->message);
+
+      g_error_free (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_markup_parse_context_free (context);
+  g_free (contents);
+}
+
+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 gboolean
+output_config_equal (const MetaOutputConfig *one,
+                     const MetaOutputConfig *two)
+{
+  return memcmp (one, two, sizeof (MetaOutputConfig)) == 0;
+}
+
+static unsigned int
+config_hash (gconstpointer data)
+{
+  const MetaConfiguration *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
+config_equal (gconstpointer one,
+              gconstpointer two)
+{
+  const MetaConfiguration *c_one = one;
+  const MetaConfiguration *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 gboolean
+config_equal_full (gconstpointer one,
+                   gconstpointer two)
+{
+  const MetaConfiguration *c_one = one;
+  const MetaConfiguration *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]);
+      ok = ok && output_config_equal (&c_one->outputs[i],
+                                      &c_two->outputs[i]);
+    }
+
+  return ok;
+}
+
+static MetaConfiguration *
+meta_monitor_config_get_stored (FlashbackMonitorConfig *self,
+                                MetaOutput             *outputs,
+                                unsigned                n_outputs)
+{
+  MetaConfiguration key;
+  MetaConfiguration *stored;
+
+  if (n_outputs == 0)
+    return NULL;
+
+  make_config_key (&key, outputs, n_outputs, -1);
+  stored = g_hash_table_lookup (self->configs, &key);
+
+  config_clear (&key);
+  return stored;
+}
+
+/*
+ * Tries to find the primary output according to the current layout,
+ * or failing that, an output that is good to be a primary (LVDS or eDP,
+ * which are internal monitors), or failing that, the one with the
+ * best resolution
+ */
+static MetaOutput *
+find_primary_output (MetaOutput *outputs,
+                     unsigned    n_outputs)
+{
+  unsigned i;
+  MetaOutput *best;
+  int best_width, best_height;
+
+  g_assert (n_outputs >= 1);
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      if (outputs[i].is_primary)
+        return &outputs[i];
+    }
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      if (output_is_laptop (&outputs[i]))
+        return &outputs[i];
+    }
+
+  best = NULL;
+  best_width = 0; best_height = 0;
+  for (i = 0; i < n_outputs; i++)
+    {
+      if (outputs[i].preferred_mode->width * outputs[i].preferred_mode->height >
+          best_width * best_height)
+        {
+          best = &outputs[i];
+          best_width = outputs[i].preferred_mode->width;
+          best_height = outputs[i].preferred_mode->height;
+        }
+    }
+
+  return best;
+}
+
+static void
+make_linear_config (FlashbackMonitorConfig *self,
+                    MetaOutput             *outputs,
+                    unsigned                n_outputs,
+                    int                     max_width,
+                    int                     max_height,
+                    MetaConfiguration      *config)
+{
+  MetaOutput *primary;
+  unsigned i;
+  int x;
+
+  g_return_if_fail (config != NULL);
+
+  primary = find_primary_output (outputs, n_outputs);
+
+  x = primary->preferred_mode->width;
+  for (i = 0; i < n_outputs; i++)
+    {
+      gboolean is_primary = (&outputs[i] == primary);
+
+      init_config_from_preferred_mode (&config->outputs[i], &outputs[i]);
+      config->outputs[i].is_primary = is_primary;
+
+      if (is_primary)
+        {
+          config->outputs[i].rect.x = 0;
+        }
+      else
+        {
+          config->outputs[i].rect.x = x;
+          x += config->outputs[i].rect.width;
+        }
+    }
+}
+
+/* This function handles configuring the outputs when the driver provides a
+ * suggested layout position for each output. This is done in recent versions
+ * of qxl and allows displays to be aligned on the guest in the same order as
+ * they are aligned on the client.
+ */
+static gboolean
+make_suggested_config (FlashbackMonitorConfig *self,
+                       MetaOutput             *outputs,
+                       unsigned                n_outputs,
+                       int                     max_width,
+                       int                     max_height,
+                       MetaConfiguration      *config)
+{
+  unsigned int i;
+  MetaOutput *primary;
+  GList *region = NULL;
+
+  g_return_val_if_fail (config != NULL, FALSE);
+  primary = find_primary_output (outputs, n_outputs);
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      gboolean is_primary = (&outputs[i] == primary);
+
+      if (outputs[i].suggested_x < 0 || outputs[i].suggested_y < 0)
+          return FALSE;
+
+      init_config_from_preferred_mode (&config->outputs[i], &outputs[i]);
+      config->outputs[i].is_primary = is_primary;
+
+      config->outputs[i].rect.x = outputs[i].suggested_x;
+      config->outputs[i].rect.y = outputs[i].suggested_y;
+
+      /* Reject the configuration if the suggested positions result in
+       * overlapping displays */
+      if (rectangle_overlaps_with_region (region, &config->outputs[i].rect))
+        {
+          g_warning ("Overlapping outputs, rejecting suggested configuration");
+          g_list_free (region);
+          return FALSE;
+        }
+
+      region = g_list_prepend (region, &config->outputs[i].rect);
+    }
+
+  g_list_free (region);
+  return TRUE;
+}
+
+/* Search for a configuration that includes one less screen, then add the new
+ * one as a presentation screen in preferred mode.
+ *
+ * XXX: but presentation mode is not implemented in the control-center or in
+ * mutter core, so let's do extended for now.
+ */
+static gboolean
+extend_stored_config (FlashbackMonitorConfig *self,
+                      MetaOutput             *outputs,
+                      unsigned                n_outputs,
+                      int                     max_width,
+                      int                     max_height,
+                      MetaConfiguration      *config)
+{
+  int x, y;
+  unsigned i, j;
+
+  x = 0;
+  y = 0;
+  for (i = 0; i < n_outputs; i++)
+    {
+      MetaConfiguration key;
+      MetaConfiguration *ref;
+
+      make_config_key (&key, outputs, n_outputs, i);
+      ref = g_hash_table_lookup (self->configs, &key);
+      config_clear (&key);
+
+      if (ref)
+        {
+          for (j = 0; j < n_outputs; j++)
+            {
+              if (j < i)
+                {
+                  g_assert (output_key_equal (&config->keys[j], &ref->keys[j]));
+                  config->outputs[j] = ref->outputs[j];
+                  x = MAX (x, ref->outputs[j].rect.x + ref->outputs[j].rect.width);
+                  y = MAX (y, ref->outputs[j].rect.y + ref->outputs[j].rect.height);
+                }
+              else if (j > i)
+                {
+                  g_assert (output_key_equal (&config->keys[j], &ref->keys[j - 1]));
+                  config->outputs[j] = ref->outputs[j - 1];
+                  x = MAX (x, ref->outputs[j - 1].rect.x + ref->outputs[j - 1].rect.width);
+                  y = MAX (y, ref->outputs[j - 1].rect.y + ref->outputs[j - 1].rect.height);
+                }
+              else
+                {
+                  init_config_from_preferred_mode (&config->outputs[j], &outputs[0]);
+                }
+            }
+
+          /* Place the new output at the right end of the screen, if it fits,
+             otherwise below it, otherwise disable it (or apply_configuration will fail) */
+          if (x + config->outputs[i].rect.width <= max_width)
+            config->outputs[i].rect.x = x;
+          else if (y + config->outputs[i].rect.height <= max_height)
+            config->outputs[i].rect.y = y;
+          else
+            config->outputs[i].enabled = FALSE;
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static MetaConfiguration *
+make_default_config (FlashbackMonitorConfig *self,
+                     MetaOutput             *outputs,
+                     unsigned                n_outputs,
+                     int                     max_width,
+                     int                     max_height,
+                     gboolean                use_stored_config)
+{
+  MetaConfiguration *ret = NULL;
+  unsigned i;
+
+  ret = config_new ();
+  make_config_key (ret, outputs, n_outputs, -1);
+  ret->outputs = g_new0 (MetaOutputConfig, n_outputs);
+
+  /* Special case the simple case: one output, primary at preferred mode,
+     nothing else to do */
+  if (n_outputs == 1)
+    {
+      init_config_from_preferred_mode (&ret->outputs[0], &outputs[0]);
+      ret->outputs[0].is_primary = TRUE;
+      goto check_limits;
+    }
+
+  if (make_suggested_config (self, outputs, n_outputs, max_width, max_height, ret))
+      goto check_limits;
+
+  if (use_stored_config &&
+      extend_stored_config (self, outputs, n_outputs, max_width, max_height, ret))
+      goto check_limits;
+
+  make_linear_config (self, outputs, n_outputs, max_width, max_height, ret);
+
+check_limits:
+  /* Disable outputs that would go beyond framebuffer limits */
+  for (i = 0; i < n_outputs; i++)
+    {
+        if ((ret->outputs[i].rect.x + ret->outputs[i].rect.width > max_width)
+            || (ret->outputs[i].rect.y + ret->outputs[i].rect.height > max_height))
+          ret->outputs[i].enabled = FALSE;
+    }
+
+  return ret;
+}
+
+static gboolean
+ensure_at_least_one_output (FlashbackMonitorConfig *self,
+                            MetaOutput             *outputs,
+                            unsigned                n_outputs)
+{
+  MetaConfiguration *config;
+  MetaOutput *primary;
+  unsigned i;
+
+  /* Check that we have at least one active output */
+  for (i = 0; i < n_outputs; i++)
+    if (outputs[i].crtc != NULL)
+      return TRUE;
+
+  /* Oh no, we don't! Activate the primary one and disable everything else */
+
+  config = config_new ();
+  make_config_key (config, outputs, n_outputs, -1);
+  config->outputs = g_new0 (MetaOutputConfig, n_outputs);
+
+  primary = find_primary_output (outputs, n_outputs);
+
+  for (i = 0; i < n_outputs; i++)
+    {
+      gboolean is_primary = (&outputs[i] == primary);
+
+      if (is_primary)
+        {
+          init_config_from_preferred_mode (&config->outputs[i], &outputs[0]);
+          config->outputs[i].is_primary = TRUE;
+        }
+      else
+        {
+          config->outputs[i].enabled = FALSE;
+        }
+    }
+
+  apply_configuration (self, config);
+  config_unref (config);
+  return FALSE;
+}
+
+static void
+init_config_from_output (MetaOutputConfig *config,
+                         MetaOutput       *output)
+{
+  config->enabled = (output->crtc != NULL);
+
+  if (!config->enabled)
+    return;
+
+  config->rect = output->crtc->rect;
+  config->refresh_rate = output->crtc->current_mode->refresh_rate;
+  config->transform = output->crtc->transform;
+  config->is_primary = output->is_primary;
+  config->is_presentation = output->is_presentation;
+}
+
+static void
+saved_cb (GObject      *object,
+          GAsyncResult *result,
+          gpointer      user_data)
+{
+  SaveClosure *closure = user_data;
+  GError *error;
+  gboolean ok;
+
+  error = NULL;
+  ok = g_file_replace_contents_finish (G_FILE (object), result, NULL, &error);
+  if (!ok)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Saving monitor configuration failed: %s\n", error->message);
+
+      g_error_free (error);
+    }
+
+  g_clear_object (&closure->config->save_cancellable);
+  g_object_unref (closure->config);
+  g_string_free (closure->buffer, TRUE);
+
+  g_slice_free (SaveClosure, closure);
+}
+
+static void
+meta_monitor_config_save (FlashbackMonitorConfig *self)
+{
+  static const char * const rotation_map[4] = {
+    "normal",
+    "left",
+    "upside_down",
+    "right"
+  };
+  SaveClosure *closure;
+  GString *buffer;
+  GHashTableIter iter;
+  MetaConfiguration *config;
+  unsigned int i;
+
+  if (self->save_cancellable)
+    {
+      g_cancellable_cancel (self->save_cancellable);
+      g_object_unref (self->save_cancellable);
+      self->save_cancellable = NULL;
+    }
+
+  self->save_cancellable = g_cancellable_new ();
+
+  buffer = g_string_new ("<monitors version=\"1\">\n");
+
+  g_hash_table_iter_init (&iter, self->configs);
+  while (g_hash_table_iter_next (&iter, (gpointer*) &config, NULL))
+    {
+      /* Note: we don't distinguish clone vs non-clone here, that's
+         something for the UI (ie gnome-control-center) to handle,
+         and our configurations are more complex anyway.
+      */
+
+      g_string_append (buffer,
+                       "  <configuration>\n"
+                       "    <clone>no</clone>\n");
+
+      for (i = 0; i < config->n_outputs; i++)
+        {
+          MetaOutputKey *key = &config->keys[i];
+          MetaOutputConfig *output = &config->outputs[i];
+
+          g_string_append_printf (buffer,
+                                  "    <output name=\"%s\">\n"
+                                  "      <vendor>%s</vendor>\n"
+                                  "      <product>%s</product>\n"
+                                  "      <serial>%s</serial>\n",
+                                  key->connector, key->vendor,
+                                  key->product, key->serial);
+
+          if (output->enabled)
+            {
+              char refresh_rate[G_ASCII_DTOSTR_BUF_SIZE];
+
+              g_ascii_dtostr (refresh_rate, sizeof (refresh_rate), output->refresh_rate);
+              g_string_append_printf (buffer,
+                                      "      <width>%d</width>\n"
+                                      "      <height>%d</height>\n"
+                                      "      <rate>%s</rate>\n"
+                                      "      <x>%d</x>\n"
+                                      "      <y>%d</y>\n"
+                                      "      <rotation>%s</rotation>\n"
+                                      "      <reflect_x>%s</reflect_x>\n"
+                                      "      <reflect_y>no</reflect_y>\n"
+                                      "      <primary>%s</primary>\n"
+                                      "      <presentation>%s</presentation>\n",
+                                      output->rect.width,
+                                      output->rect.height,
+                                      refresh_rate,
+                                      output->rect.x,
+                                      output->rect.y,
+                                      rotation_map[output->transform & 0x3],
+                                      output->transform >= META_MONITOR_TRANSFORM_FLIPPED ? "yes" : "no",
+                                      output->is_primary ? "yes" : "no",
+                                      output->is_presentation ? "yes" : "no");
+            }
+
+          g_string_append (buffer, "    </output>\n");
+        }
+
+      g_string_append (buffer, "  </configuration>\n");
+    }
+
+  g_string_append (buffer, "</monitors>\n");
+
+  closure = g_slice_new (SaveClosure);
+  closure->config = g_object_ref (self);
+  closure->buffer = buffer;
+
+  g_file_replace_contents_async (self->file,
+                                 buffer->str, buffer->len,
+                                 NULL, /* etag */
+                                 TRUE,
+                                 G_FILE_CREATE_REPLACE_DESTINATION,
+                                 self->save_cancellable,
+                                 saved_cb, closure);
+}
+
 static void
 flashback_monitor_config_finalize (GObject *object)
 {
+  FlashbackMonitorConfig *config;
+
+  config = FLASHBACK_MONITOR_CONFIG (object);
+
+  g_hash_table_destroy (config->configs);
+
   G_OBJECT_CLASS (flashback_monitor_config_parent_class)->finalize (object);
 }
 
@@ -51,42 +1796,173 @@ flashback_monitor_config_class_init (FlashbackMonitorConfigClass *config_class)
 static void
 flashback_monitor_config_init (FlashbackMonitorConfig *config)
 {
+  const char *filename;
+  char *path;
+
+  config->configs = g_hash_table_new_full (config_hash, config_equal,
+                                           NULL, (GDestroyNotify) config_unref);
+
+  filename = g_getenv ("GNOME_FLASHBACK_MONITOR_FILENAME");
+  if (filename == NULL)
+    filename = "monitors.xml";
+
+  path = g_build_filename (g_get_user_config_dir (), filename, NULL);
+  config->file = g_file_new_for_path (path);
+  g_free (path);
+
+  config->up_client = up_client_new ();
+  config->lid_is_closed = up_client_get_lid_is_closed (config->up_client);
+
+  g_signal_connect_object (config->up_client, "notify::lid-is-closed",
+                           G_CALLBACK (power_client_changed_cb), config, 0);
 }
 
 FlashbackMonitorConfig *
-flashback_monitor_config_new (void)
+flashback_monitor_config_new (FlashbackMonitorManager *manager)
 {
-  return g_object_new (FLASHBACK_TYPE_MONITOR_CONFIG,
-                       NULL);
+  FlashbackMonitorConfig *config;
+
+  config = g_object_new (FLASHBACK_TYPE_MONITOR_CONFIG,
+                         NULL);
+
+  config->manager = manager;
+  flashback_monitor_config_load (config);
+
+  return config;
 }
 
 gboolean
-flashback_monitor_config_apply_stored (FlashbackMonitorConfig  *config,
-                                       FlashbackMonitorManager *manager)
+flashback_monitor_config_apply_stored (FlashbackMonitorConfig *config)
 {
+  MetaOutput *outputs;
+  MetaConfiguration *stored;
+  unsigned n_outputs;
+
+  outputs = flashback_monitor_manager_get_outputs (config->manager, &n_outputs);
+  stored = meta_monitor_config_get_stored (config, outputs, n_outputs);
+
+  if (stored)
+    return apply_configuration_with_lid (config, stored);
+  else
+    return FALSE;
 }
 
 void
-flashback_monitor_config_make_default (FlashbackMonitorConfig  *config,
-                                       FlashbackMonitorManager *manager)
+flashback_monitor_config_make_default (FlashbackMonitorConfig *config)
 {
+  MetaOutput *outputs;
+  MetaConfiguration *default_config;
+  unsigned n_outputs;
+  gboolean ok = FALSE;
+  int max_width, max_height;
+  gboolean use_stored_config;
+
+  outputs = flashback_monitor_manager_get_outputs (config->manager, &n_outputs);
+  flashback_monitor_manager_get_screen_limits (config->manager, &max_width, &max_height);
+
+  if (n_outputs == 0)
+    {
+      g_debug ("No output connected, not applying configuration\n");
+      return;
+    }
+
+  /* if the device has hotplug_mode_update, it's possible that the
+   * current display configuration does not match a stored configuration.
+   * Since extend_existing_config() tries to build a configuration that is
+   * based on a previously-stored configuration, it's quite likely that the
+   * resulting config will fail. Even if it doesn't fail, it may result in
+   * an unexpected configuration, so don't attempt to use a stored config
+   * in this situation. */
+  use_stored_config = !flashback_monitor_manager_has_hotplug_mode_update (config->manager);
+  default_config = make_default_config (config, outputs, n_outputs, max_width, max_height, 
use_stored_config);
+
+  if (default_config != NULL)
+    {
+      ok = apply_configuration_with_lid (config, default_config);
+      config_unref (default_config);
+    }
+
+  if (!ok)
+    {
+      g_warning ("Could not make default configuration for current output layout, leaving unconfigured\n");
+      if (ensure_at_least_one_output (config, outputs, n_outputs))
+        flashback_monitor_config_update_current (config);
+    }
 }
 
 void
-flashback_monitor_config_update_current (FlashbackMonitorConfig  *config,
-                                         FlashbackMonitorManager *manager)
+flashback_monitor_config_update_current (FlashbackMonitorConfig *config)
 {
+  MetaOutput *outputs;
+  unsigned n_outputs;
+  MetaConfiguration *current;
+  unsigned int i;
+
+  outputs = flashback_monitor_manager_get_outputs (config->manager, &n_outputs);
+
+  current = config_new ();
+  current->n_outputs = n_outputs;
+  current->outputs = g_new0 (MetaOutputConfig, n_outputs);
+  current->keys = g_new0 (MetaOutputKey, n_outputs);
+
+  for (i = 0; i < current->n_outputs; i++)
+    {
+      init_key_from_output (&current->keys[i], &outputs[i]);
+      init_config_from_output (&current->outputs[i], &outputs[i]);
+    }
+
+  if (config->current && config_equal_full (current, config->current))
+    {
+      config_unref (current);
+      return;
+    }
+
+  set_current (config, current);
 }
 
 void
-flashback_monitor_config_make_persistent (FlashbackMonitorConfig  *config)
+flashback_monitor_config_make_persistent (FlashbackMonitorConfig *config)
 {
+  if (config->previous)
+    {
+      /* The user chose to restore the previous configuration. In this
+       * case, restore the previous configuration. */
+      MetaConfiguration *prev_config = config_ref (config->previous);
+      apply_configuration (config, prev_config);
+      config_unref (prev_config);
+
+      /* After this, self->previous contains the rejected configuration.
+       * Since it was rejected, nuke it. */
+      g_clear_pointer (&config->previous, (GDestroyNotify) config_unref);
+    }
+  else
+    {
+      if (!flashback_monitor_config_apply_stored (config))
+        flashback_monitor_config_make_default (config);
+    }
 }
 
 void
-flashback_monitor_config_restore_previous (FlashbackMonitorConfig  *config,
-                                           FlashbackMonitorManager *manager)
+flashback_monitor_config_restore_previous (FlashbackMonitorConfig *config)
 {
+  g_hash_table_replace (config->configs, config->current, config_ref (config->current));
+  meta_monitor_config_save (config);
+}
+
+gboolean
+flashback_monitor_manager_has_hotplug_mode_update (FlashbackMonitorManager *manager)
+{
+  MetaOutput *outputs;
+  unsigned n_outputs;
+  unsigned int i;
+
+  outputs = flashback_monitor_manager_get_outputs (manager, &n_outputs);
+
+  for (i = 0; i < n_outputs; i++)
+    if (outputs[i].hotplug_mode_update)
+      return TRUE;
+
+  return FALSE;
 }
 
 void
diff --git a/gnome-flashback/libdisplay-config/flashback-monitor-config.h 
b/gnome-flashback/libdisplay-config/flashback-monitor-config.h
index 933995c..2c6914a 100644
--- a/gnome-flashback/libdisplay-config/flashback-monitor-config.h
+++ b/gnome-flashback/libdisplay-config/flashback-monitor-config.h
@@ -34,20 +34,16 @@ G_DECLARE_FINAL_TYPE (FlashbackMonitorConfig, flashback_monitor_config,
                       FLASHBACK, MONITOR_CONFIG,
                       GObject)
 
-FlashbackMonitorConfig *flashback_monitor_config_new              (void);
+FlashbackMonitorConfig *flashback_monitor_config_new              (FlashbackMonitorManager *manager);
 
-gboolean                flashback_monitor_config_apply_stored     (FlashbackMonitorConfig  *config,
-                                                                   FlashbackMonitorManager *manager);
+gboolean                flashback_monitor_config_apply_stored     (FlashbackMonitorConfig  *config);
 
-void                    flashback_monitor_config_make_default     (FlashbackMonitorConfig  *config,
-                                                                   FlashbackMonitorManager *manager);
+void                    flashback_monitor_config_make_default     (FlashbackMonitorConfig  *config);
 
-void                    flashback_monitor_config_update_current   (FlashbackMonitorConfig  *config,
-                                                                   FlashbackMonitorManager *manager);
+void                    flashback_monitor_config_update_current   (FlashbackMonitorConfig  *config);
 void                    flashback_monitor_config_make_persistent  (FlashbackMonitorConfig  *config);
 
-void                    flashback_monitor_config_restore_previous (FlashbackMonitorConfig  *config,
-                                                                   FlashbackMonitorManager *manager);
+void                    flashback_monitor_config_restore_previous (FlashbackMonitorConfig  *config);
 
 G_END_DECLS
 
diff --git a/gnome-flashback/libdisplay-config/flashback-monitor-manager.c 
b/gnome-flashback/libdisplay-config/flashback-monitor-manager.c
index 4b29618..c5064dd 100644
--- a/gnome-flashback/libdisplay-config/flashback-monitor-manager.c
+++ b/gnome-flashback/libdisplay-config/flashback-monitor-manager.c
@@ -997,12 +997,12 @@ flashback_monitor_manager_constructed (GObject *object)
 
   manager->in_init = TRUE;
 
-  manager->config = flashback_monitor_config_new ();
+  manager->config = flashback_monitor_config_new (manager);
 
   flashback_monitor_manager_read_current_config (manager);
 
-  if (!flashback_monitor_config_apply_stored (manager->config, manager))
-    flashback_monitor_config_make_default (manager->config, manager);
+  if (!flashback_monitor_config_apply_stored (manager->config))
+    flashback_monitor_config_make_default (manager->config);
 
   /* Under XRandR, we don't rebuild our data structures until we see
      the RRScreenNotify event, but at least at startup we want to have
@@ -1458,3 +1458,46 @@ meta_output_parse_edid (MetaOutput *meta_output,
       meta_output->serial = g_strdup ("unknown");
     }
 }
+
+MetaOutput *
+flashback_monitor_manager_get_outputs (FlashbackMonitorManager *manager,
+                                       unsigned int            *n_outputs)
+{
+  *n_outputs = manager->n_outputs;
+  return manager->outputs;
+}
+
+void
+flashback_monitor_manager_get_resources (FlashbackMonitorManager  *manager,
+                                         MetaMonitorMode         **modes,
+                                         unsigned int             *n_modes,
+                                         MetaCRTC                **crtcs,
+                                         unsigned int             *n_crtcs,
+                                         MetaOutput              **outputs,
+                                         unsigned int             *n_outputs)
+{
+  if (modes)
+    {
+      *modes = manager->modes;
+      *n_modes = manager->n_modes;
+    }
+  if (crtcs)
+    {
+      *crtcs = manager->crtcs;
+      *n_crtcs = manager->n_crtcs;
+    }
+  if (outputs)
+    {
+      *outputs = manager->outputs;
+      *n_outputs = manager->n_outputs;
+    }
+}
+
+void
+flashback_monitor_manager_get_screen_limits (FlashbackMonitorManager *manager,
+                                             int                     *width,
+                                             int                     *height)
+{
+  *width = manager->max_screen_width;
+  *height = manager->max_screen_height;
+}
diff --git a/gnome-flashback/libdisplay-config/flashback-monitor-manager.h 
b/gnome-flashback/libdisplay-config/flashback-monitor-manager.h
index 398671c..41c3596 100644
--- a/gnome-flashback/libdisplay-config/flashback-monitor-manager.h
+++ b/gnome-flashback/libdisplay-config/flashback-monitor-manager.h
@@ -295,8 +295,24 @@ void                     meta_output_parse_edid                        (MetaOutp
 void                     meta_crtc_info_free                           (MetaCRTCInfo             *info);
 void                     meta_output_info_free                         (MetaOutputInfo           *info);
 
+gboolean                 flashback_monitor_manager_has_hotplug_mode_update (FlashbackMonitorManager 
*manager);
 void                     flashback_monitor_manager_read_current_config (FlashbackMonitorManager  *manager);
 
+MetaOutput              *flashback_monitor_manager_get_outputs         (FlashbackMonitorManager  *manager,
+                                                                        unsigned int             *n_outputs);
+
+void                     flashback_monitor_manager_get_resources       (FlashbackMonitorManager  *manager,
+                                                                        MetaMonitorMode         **modes,
+                                                                        unsigned int             *n_modes,
+                                                                        MetaCRTC                **crtcs,
+                                                                        unsigned int             *n_crtcs,
+                                                                        MetaOutput              **outputs,
+                                                                        unsigned int             *n_outputs);
+
+void                     flashback_monitor_manager_get_screen_limits   (FlashbackMonitorManager *manager,
+                                                                        int                     *width,
+                                                                        int                     *height);
+
 G_END_DECLS
 
 #endif


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