/*
 * Copyright © 2009-10 Sam Thursfield
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Sam Thursfield <ssssam@gmail.com>
 */

/* GRegistrySettingsBackend implementation notes:
 *
 *   - All settings are stored under the registry path given at construction
 *     time. Permissions and system-wide defaults are not implemented and will
 *     probably always be out of scope of the Windows port of GLib.
 *
 *   - The registry type system is limited. Most GVariant types are stored as
 *     literals via g_variant_print/parse(). Strings are stored without the
 *     quotes that GVariant requires. Integer types are stored as native
 *     REG_DWORD or REG_QWORD. The REG_MULTI_SZ (string array) type could be
 *     used to avoid flattening container types.
 *
 *   - Notifications are handled; the change event is watched for in a separate
 *     thread (Windows does not provide a callback API) which sends them with
 *     g_idle_add to the GLib main loop. The threading is done using Windows
 *     API functions, so there is no dependence on GThread.
 *
 *   - Windows doesn't tell us which value has changed. This means we have to
 *     maintain a cache of every stored value so we can play spot the
 *     difference. This should not be a performance issue because if you are
 *     storing thousands of values in GSettings, you are probably using it
 *     wrong.
 *
 *   - The cache stores the value as a registry type. Because many variants are
 *     stored as string representations, values which have changed equality but
 *     not equivalence may trigger spurious change notifications. GSettings
 *     users must already deal with this possibility and converting all data to
 *     GVariant values would be more effort.
 *
 *   - Because we have to cache every registry value locally, reads are done
 *     from the cache rather than directly from the registry. Writes update
 *     both. This means that the backend will not work if the watch thread is
 *     not running. A GSettings object always subscribes to changes so we can
 *     be sure that the watch thread will be running, but if for some reason
 *     the backend is being used directly you should bear that in mind.
 *
 *   - The registry is totally user-editable, so we are very forgiving about
 *     errors in the data we get.
 *
 *   - The registry uses backslashes as path separators. GSettings keys only
 *     allow [A-Za-z\-] so no escaping is needed. No attempt is made to solve
 *     clashes between keys differing only in case.
 *
 *   - RegCreateKeyW is used - We should always make the UTF-8 -> UTF-16
 *     conversion ourselves to avoid problems when the system language changes.
 *
 *   - The Windows registry has the following limitations: a key may not exceed
 *     255 characters, an entry's value may not exceed 16,383 characters, and
 *     all the values of a key may not exceed 65,535 characters.
 *
 *   - Terminology:
 *     * in GSettings, a 'key' is eg. /desktop/gnome/background/primary-color
 *     * in the registry, the 'key' is path, which contains some 'values'.
 *     * in this file, any GSettings key is a 'key', while a registry key is
 *       termed a 'path', which contains 'values'.
 *
 *   - My set of tests for this backend are currently at:
 *       http://gitorious.org/gsettings-gtk/gsettings-test.git
 *
 *   - There is an undocumented function in ntdll.dll which might be more
 *     than RegNotifyChangeKeyValue(), NtNotifyChangeKey:
 *       http://source.winehq.org/source/dlls/ntdll/reg.c#L618
 *       http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Key/NtNotifyChangeKey.html
 *
 *   - If updating the cache ever becomes a performance issue it may make sense
 *     to use a red-black tree, but I don't currently think it's worth the time
 */

#include "config.h"

#include "gregistrysettingsbackend.h"
#include "gsettingsbackend.h"
#include "gsettingsbackendinternal.h"
#include "giomodule-priv.h"

#include <glibintl.h>

#include <windows.h>

//#define TRACE

/* GSettings' limit */
#define MAX_KEY_NAME_LENGTH   128

/* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with
 * "The parameter is incorrect" after 64 watches. We need one for the
 * message_sent cond, which is allowed for in the way the watches_remaining
 * variable is used.
 */
#define MAX_WATCHES   64

/* A watch on one registry path and its subkeys */
typedef struct
{
  HANDLE event;
  HKEY hpath;
  char *prefix;
  GNode *cache_node;
} RegistryWatch;

/* Simple message passing for the watch thread. Not enough traffic to
 * justify a queue.
 */
typedef enum
{
  WATCH_THREAD_NONE,
  WATCH_THREAD_ADD_WATCH,
  WATCH_THREAD_REMOVE_WATCH,
  WATCH_THREAD_STOP
} WatchThreadMessageType;

typedef struct
{
  WatchThreadMessageType type;
  RegistryWatch watch;
} WatchThreadMessage;

typedef struct
{
  GSettingsBackend *owner;
  HANDLE *thread;

  /* Details of the things we are watching. */
  int watches_remaining;
  GPtrArray *events, *handles, *prefixes, *cache_nodes;

  /* Communication with the main thread. Only one message is stored at a time,
   * to make sure that messages are acknowledged before being overwritten we
   * create two events - one is signalled when a new message is set, the
   * other is signalled by the thread when it has processed the message.
   */
  WatchThreadMessage message;
  CRITICAL_SECTION *message_lock;
  HANDLE message_sent_event, message_received_event;
} WatchThreadState;

#define G_TYPE_REGISTRY_SETTINGS_BACKEND      (g_registry_settings_backend_get_type ())
#define G_REGISTRY_SETTINGS_BACKEND(inst)     (G_TYPE_CHECK_INSTANCE_CAST ((inst),         \
                                               G_TYPE_REGISTRY_SETTINGS_BACKEND, GRegistrySettingsBackend))
#define G_IS_REGISTRY_SETTINGS_BACKEND(inst)  (G_TYPE_CHECK_INSTANCE_TYPE ((inst),         \
                                               G_TYPE_REGISTRY_SETTINGS_BACKEND))

typedef enum {
  PROP_REGISTRY_KEY = 1,
} GRegistrySettingsBackendProperty;

typedef GSettingsBackendClass GRegistrySettingsBackendClass;

typedef struct {
  GSettingsBackend parent_instance;

  HKEY base_key;
  gchar *base_path;
  gunichar2 *base_pathw;

  /* A stored copy of the whole tree being watched. When we receive a change notification
   * we have to check against this to see what has changed ... every time ...*/
  CRITICAL_SECTION *cache_lock;
  GNode *cache_root;

  /* A lock to protect access to the watch variable */
  CRITICAL_SECTION watch_lock;
  /* Contains the state of the watching thread. Any access to this variable
   * must be done while holding the watch_lock critical section. */
  WatchThreadState *watch;
} GRegistrySettingsBackend;

G_DEFINE_TYPE_WITH_CODE (GRegistrySettingsBackend,
                         g_registry_settings_backend,
                         G_TYPE_SETTINGS_BACKEND,
                         _g_io_modules_ensure_extension_points_registered ();
                         g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
                                                         g_define_type_id, "registry", 90))

/**********************************************************************************
 * Utility functions
 **********************************************************************************/

#include <stdio.h>
static void
trace (const char *format,
       ...)
{
#ifdef TRACE
  va_list va; va_start (va, format);
  vprintf (format, va);
  fflush (stdout);
  va_end (va);
#endif
}

/* g_message including a windows error message. It is not useful to have an
 * equivalent function for g_warning because none of the registry errors can
 * result from programmer error (Microsoft programmers don't count), instead
 * they will mostly occur from people messing with the registry by hand. */
static void G_GNUC_PRINTF (2, 3)
g_message_win32_error (DWORD        result_code,
                       const gchar *format,
                      ...)
{
  va_list va;
  gchar *message;
  gchar *win32_error;
  gchar *win32_message;

  g_return_if_fail (result_code != 0);

  va_start (va, format);
  message = g_strdup_vprintf (format, va);
  win32_error = g_win32_error_message (result_code);
  win32_message = g_strdup_printf ("%s: %s", message, win32_error);
  g_free (message);
  g_free (win32_error);

  if (result_code == ERROR_KEY_DELETED)
    trace ("(%s)", win32_message);
  else
    g_message ("%s", win32_message);

  g_free (win32_message);
}

/* Make gsettings key into a registry path & value pair.
 *
 * Note that the return value *only* needs freeing - registry_value_name
 * is a pointer to further inside the same block of memory.
 */
static gchar *
parse_key (const gchar  *key_name,
           const gchar  *registry_prefix,
           gchar       **value_name)
{
  gchar *path_name, *c;

  /* All key paths are treated as absolute; gsettings doesn't seem to enforce a
   * preceding /.
   */
  if (key_name[0] == '/')
    key_name++;

  if (registry_prefix == NULL)
    path_name = g_strdup (key_name);
  else
    path_name = g_strjoin ("/", registry_prefix, key_name, NULL);

  /* Prefix is expected to be in registry format (\ separators) so don't escape that. */
  for (c = path_name + (registry_prefix ? strlen (registry_prefix) : 0); *c != 0; c++)
    {
      if (*c == '/')
        {
          *c = '\\';
          *value_name = c;
        }
    }

  **value_name = 0;
  (*value_name)++;

  return path_name;
}

static DWORD
g_variant_get_as_dword (GVariant *variant)
{
  switch (g_variant_get_type_string (variant)[0])
    {
    case 'b':
      return g_variant_get_boolean (variant);
    case 'y':
      return g_variant_get_byte (variant);
    case 'n':
      return g_variant_get_int16 (variant);
    case 'q':
      return g_variant_get_uint16 (variant);
    case 'i':
      return g_variant_get_int32 (variant);
    case 'u':
      return g_variant_get_uint32 (variant);
    default:
      g_warn_if_reached ();
    }
  return 0;
}

static DWORDLONG
g_variant_get_as_qword (GVariant *variant)
{
  switch (g_variant_get_type_string (variant)[0])
    {
    case 'x':
      return g_variant_get_int64 (variant);
    case 't':
      return g_variant_get_uint64 (variant);
    default:
      g_warn_if_reached ();
    }
  return 0;
}

static void
handle_read_error (LONG         result,
                   const gchar *path_name,
                   const gchar *value_name)
{
  /* file not found means key value not set, this isn't an error for us. */
  if (result != ERROR_FILE_NOT_FOUND)
    g_message_win32_error (result, "Unable to query value %s/%s",
                           path_name, value_name);
}

typedef struct {
  HKEY handle;
  const char *name;
} HandleNamePair;

static const HandleNamePair predefined_key_names[] = {
  { HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT" },
  { HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG" },
  { HKEY_CURRENT_USER, "HKEY_CURRENT_USER" },
  { HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE" },
  { HKEY_USERS, "HKEY_USERS" }
};

static const gchar*
predefined_key_to_string (HKEY key)
{
  for (gsize i = 0; i < G_N_ELEMENTS (predefined_key_names); i++)
    {
      if (predefined_key_names[i].handle == key)
        return predefined_key_names[i].name;
    }

  g_warning ("gregistrysettingsbackend: unexpected root key (%p)", key);
  return "";
}

static const gchar*
split_registry_path (const gchar *full_path,
                     HKEY        *root_key)
{
  g_assert (full_path != NULL);

  for (gsize i = 0; i < G_N_ELEMENTS (predefined_key_names); i++)
    {
      const gchar *root_name = predefined_key_names[i].name;

      if (g_str_has_prefix (full_path, root_name))
        {
          const gchar *rest = full_path + strlen (root_name);

          if (*rest == '\\')
            {
              if (root_key != NULL)
                *root_key = predefined_key_names[i].handle;

              return ++rest;
            }
        }
    }

  return NULL;
}

/***************************************************************************
 * Cache of registry values
 ***************************************************************************/

/* Generic container for registry values */
typedef struct {
  DWORD type;

  union {
    gint  dword;  /* FIXME: could inline QWORD on 64-bit systems too */
    void *ptr;
  };
} RegistryValue;

static char *
registry_value_dump (RegistryValue value)
{
  if (value.type == REG_DWORD)
    return g_strdup_printf ("%d", value.dword);
  else if (value.type == REG_QWORD)
    return g_strdup_printf ("%"G_GINT64_FORMAT, value.ptr == NULL ? 0: *(DWORDLONG *)value.ptr);
  else if (value.type == REG_SZ)
    return g_strdup_printf ("%s", (char *)value.ptr);
  else if (value.type == REG_NONE)
    return g_strdup_printf ("<empty>");
  else
    return g_strdup_printf ("<invalid>");
}

static void
registry_value_free (RegistryValue value)
{
  if (value.type == REG_SZ || value.type == REG_QWORD)
    g_free (value.ptr);

  value.type = REG_NONE;
  value.ptr = NULL;
}

/* The registry cache is stored as a tree, for easy traversal. Right now we
 * don't sort it in a clever way. Each node corresponds to a path element
 * ('key' in registry terms) or a value.
 *
 * Each subscription uses the same cache. Because GSettings can subscribe to
 * the tree at any node any number of times, we need to reference count the
 * nodes.
 */
typedef struct
{
  /* Component of path that this node represents */
  gchar *name;

  /* If a watch is subscribed at this point (subscription_count > 0) we can
   * block its next notification. This is useful because if two watches cover
   * the same path, both will trigger when it changes. It also allows changes
   * done by the application to be ignored by the watch thread.
   */
  gint32 block_count : 8;

  /* Number of times g_settings_subscribe has been called for this location
   * (I guess you can't subscribe more than 16383 times) */
  gint32 subscription_count : 14;

  gint32 ref_count : 9;

  guint32 readable : 1;
  RegistryValue value;
} RegistryCacheItem;

static GNode *
registry_cache_add_item (GNode         *parent,
                         gchar         *name,
                         RegistryValue  value,
                         gint           ref_count)
{
  RegistryCacheItem *item;
  GNode *cache_node;

  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (parent != NULL, NULL);

  item = g_slice_new (RegistryCacheItem);

  /* Ref count should be the number of watch points above this node */
  item->ref_count = ref_count;

  item->name = g_strdup (name);
  item->value = value;
  item->subscription_count = 0;
  item->block_count = 0;
  item->readable = FALSE;

  trace ("\tregistry cache: adding %s to %s\n",
         name, ((RegistryCacheItem *)parent->data)->name);

  cache_node = g_node_new (item);
  g_node_append (parent, cache_node);

  return cache_node;
}

/* The reference counting of cache tree nodes works like this: when a node is
 * subscribed to (GSettings wants us to watch that path and everything below
 * it) the reference count of that node and everything below is increased, as
 * well as each parent up to the root.
 */

static void
_ref_down (GNode *node)
{
  RegistryCacheItem *item = node->data;

  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_ref_down, NULL);
  item->ref_count++;
}

static void
registry_cache_ref_tree (GNode *tree)
{
  RegistryCacheItem *item = tree->data;
  GNode *node = tree->parent;

  g_return_if_fail (tree != NULL);

  item->ref_count++;

  g_node_children_foreach (tree, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_ref_down, NULL);

  for (node = tree->parent; node; node = node->parent)
    {
      item = node->data;
      item->ref_count++;
    }
}

static void
registry_cache_item_free (RegistryCacheItem *item)
{
  trace ("\t -- Free node %s\n", item->name);

  g_free (item->name);
  registry_value_free (item->value);
  g_slice_free (RegistryCacheItem, item);
}

/* Unreferencing has to be done bottom-up */
static void
_unref_node (GNode *node)
{
  RegistryCacheItem *item = node->data;

  item->ref_count--;

  g_warn_if_fail (item->ref_count >= 0);

  if (item->ref_count == 0)
    {
      registry_cache_item_free (item);
      g_node_destroy (node);
    }
}

static void
_unref_down (GNode *node)
{
  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)_unref_down, NULL);
  _unref_node (node);
}

static void
registry_cache_unref_tree (GNode *tree)
{
  GNode *parent = tree->parent, *next_parent;

  _unref_down (tree);

  while (parent)
    {
      next_parent = parent->parent;
      _unref_node (parent);
      parent = next_parent;
    }
}

#if 0
static void
registry_cache_dump (GNode    *cache_node,
                     gpointer  data)
{
  RegistryCacheItem *item = cache_node->data;

  int depth     = GPOINTER_TO_INT(data),
      new_depth = depth+1,
      i;

  g_return_if_fail (cache_node != NULL);

  for (i=0; i<depth; i++)
    g_print ("  ");
  if (item == NULL)
    g_print ("*root*\n");
  else
    g_print ("'%s'  [%i] @ %x = %s\n", item->name, item->ref_count, (guint)cache_node,
             registry_value_dump (item->value));
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL, registry_cache_dump,
                           GINT_TO_POINTER (new_depth));
}
#endif

typedef struct
{
  gchar *name;
  GNode *result;
} RegistryCacheSearch;

static gboolean
registry_cache_find_compare (GNode    *node,
                             gpointer  data)
{
  RegistryCacheSearch *search = data;
  RegistryCacheItem *item = node->data;

  if (item == NULL)  /* root node */
    return FALSE;

  g_return_val_if_fail (search->name != NULL, FALSE);
  g_return_val_if_fail (item->name != NULL, FALSE);

  if (strcmp (search->name, item->name) == 0)
    {
      search->result = node;
      return TRUE;
    }

  return FALSE;
}

static GNode *
registry_cache_find_immediate_child (GNode *node,
                                     gchar *name)
{
  RegistryCacheSearch search;

  search.result = NULL;
  search.name = name;

  g_node_traverse (node, G_POST_ORDER, G_TRAVERSE_ALL, 2,
                   registry_cache_find_compare, &search);

  return search.result;
}

static GNode *
registry_cache_get_node_for_key_recursive (GNode    *node,
                                           gchar    *key_name,
                                           gboolean  create_if_not_found,
                                           gint      n_parent_watches)
{
  RegistryCacheItem *item;
  gchar *component = key_name;
  gchar *c = strchr (component, '/');
  GNode *child;

  if (c != NULL)
    *c = 0;

  /* We count up how many watch points we travel through finding this node,
   * because a new node should have as many references as there are watches at
   * points above it in the tree.
   */
  item = node->data;
  if (item->subscription_count > 0)
    n_parent_watches++;

  child = registry_cache_find_immediate_child (node, component);
  if (child == NULL && create_if_not_found)
    {
      RegistryValue null_value = { REG_NONE, {0} };

      child = registry_cache_add_item (node, component,
                                       null_value, n_parent_watches);

      trace ("\tget node for key recursive: new %x = %s.\n", node, component);
    }

  /* We are done if there are no more path components. Allow for a trailing /. */
  if (child == NULL || c == NULL || *(c + 1) == 0)
    return child;

  trace ("get node for key recursive: next: %s.\n", c + 1);

  return registry_cache_get_node_for_key_recursive (child, c + 1,
                                                    create_if_not_found,
                                                    n_parent_watches);
}

/* Look up a GSettings key in the cache. */
static GNode *
registry_cache_get_node_for_key (GNode       *root,
                                 const gchar *key_name,
                                 gboolean     create_if_not_found)
{
  GNode *child = NULL;
  GNode *result = NULL;
  gchar *component, *c;

  g_return_val_if_fail (key_name != NULL, NULL);

  if (key_name[0] == '/')
    key_name++;

  /* Ignore preceding / */
  component = g_strdup (key_name);
  c = strchr (component, '/');

  if (c == NULL)
    {
      g_free (component);
      return root;
    }

  if (c != NULL)
    *c = 0;

  child = registry_cache_find_immediate_child (root, component);
  if (child == NULL && create_if_not_found)
    {
      RegistryValue null_value = { REG_NONE, {0} };

      /* Reference count is set to 0, tree should be referenced by the caller */
      child = registry_cache_add_item (root, component,
                                       null_value, 0);

      trace ("get_node_for_key: New node for component '%s'\n", component);
    }

  if (*(c + 1) == 0)
    result = child;
  else if (child != NULL)
    result = registry_cache_get_node_for_key_recursive (child, c + 1,
                                                        create_if_not_found, 0);

  g_free (component);

  return result;
}

/* Check the cache node against the registry key it represents. Return TRUE if
 * they differ, and update the cache with the new value.
 */
static gboolean
registry_cache_update_node (GNode        *cache_node,
                            RegistryValue registry_value)
{
  RegistryCacheItem *cache_item;

  g_return_val_if_fail (cache_node != NULL, FALSE);
  g_return_val_if_fail (cache_node->data != NULL, FALSE);

  cache_item = cache_node->data;

  if (registry_value.type != cache_item->value.type)
    {
      /* The type has changed. Update cache item and register it as changed.
       * Either the schema has changed and this is entirely legitimate, or
       * whenever the app reads the key it will get the default value due to
       * the type mismatch.
       */
      cache_item->value = registry_value;
      return TRUE;
    }

  switch (registry_value.type)
    {
    case REG_DWORD:
      {
        if (cache_item->value.dword == registry_value.dword)
          return FALSE;
        else
          {
            cache_item->value.dword = registry_value.dword;
            return TRUE;
          }
      }
    case REG_QWORD:
      {
        g_return_val_if_fail (registry_value.ptr != NULL &&
                              cache_item->value.ptr != NULL, FALSE);

        if (memcmp (registry_value.ptr, cache_item->value.ptr, 8)==0)
          {
            g_free (registry_value.ptr);
            return FALSE;
          }
        else
          {
            g_free (cache_item->value.ptr);
            cache_item->value.ptr = registry_value.ptr;
            return TRUE;
          }
      }
    case REG_SZ:
      {
        /* Value should not exist if it is NULL, an empty string is "" */
        g_return_val_if_fail (cache_item->value.ptr != NULL, FALSE);
        g_return_val_if_fail (registry_value.ptr != NULL, FALSE);

        if (strcmp (registry_value.ptr, cache_item->value.ptr) == 0)
          {
            g_free (registry_value.ptr);
            return FALSE;
          }
        else
          {
            g_free (cache_item->value.ptr);
            cache_item->value.ptr = registry_value.ptr;
            return TRUE;
          }
      }
    default:
      g_warning ("gregistrysettingsbackend: registry_cache_update_node: Unhandled value type");
      return FALSE;
    }
}

/* Blocking notifications is a useful optimisation. When a change is made
 * through GSettings we update the cache manually, but a notification is
 * triggered as well. This function is also used for nested notifications,
 * eg. if /test and /test/foo are watched, and /test/foo/value is changed then
 * we will get notified both for /test/foo and /test and it is helpful to block
 * the second.
 */
static void
registry_cache_block_notification (GNode *node)
{
  RegistryCacheItem *item = node->data;

  g_return_if_fail (node != NULL);

  if (item->subscription_count > 0)
    item->block_count++;

  if (node->parent != NULL)
    registry_cache_block_notification (node->parent);
}

static void registry_cache_destroy_tree (GNode            *node,
                                         WatchThreadState *self);

/***************************************************************************
 * Reading and writing
 ***************************************************************************/

static gboolean
registry_read (HKEY           hpath,
               const gchar   *path_name,
               const gchar   *value_name,
               RegistryValue *p_value)
{
  LONG result;
  DWORD value_data_size;
  gpointer *buffer;
  gunichar2 *value_namew;

  g_return_val_if_fail (p_value != NULL, FALSE);

  p_value->type = REG_NONE;
  p_value->ptr = NULL;

  value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);

  result = RegQueryValueExW (hpath, value_namew, 0, &p_value->type, NULL, &value_data_size);
  if (result != ERROR_SUCCESS)
    {
      handle_read_error (result, path_name, value_name);
      g_free (value_namew);
      return FALSE;
    }

  if (p_value->type == REG_SZ && value_data_size == 0)
    {
      p_value->ptr = g_strdup ("");
      g_free (value_namew);
      return TRUE;
    }

  if (p_value->type == REG_DWORD)
    /* REG_DWORD is inlined */
    buffer = (void *)&p_value->dword;
  else
    buffer = p_value->ptr = g_malloc (value_data_size);

  result = RegQueryValueExW (hpath, value_namew, 0, NULL, (LPBYTE)buffer, &value_data_size);
  g_free (value_namew);

  if (result != ERROR_SUCCESS)
    {
      handle_read_error (result, path_name, value_name);

      if (p_value->type != REG_DWORD)
        g_free (buffer);

      return FALSE;
    }

  if (p_value->type == REG_SZ)
    {
      gchar *valueu8 = g_utf16_to_utf8 (p_value->ptr, -1, NULL, NULL, NULL);
      g_free (p_value->ptr);
      p_value->ptr = valueu8;
    }

  return TRUE;
}

static GVariant *
g_registry_settings_backend_read (GSettingsBackend   *backend,
                                  const gchar        *key_name,
                                  const GVariantType *expected_type,
                                  gboolean            default_value)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  GNode *cache_node;
  RegistryValue registry_value;
  GVariant *gsettings_value = NULL;
  gchar *gsettings_type;

  g_return_val_if_fail (expected_type != NULL, NULL);

  if (default_value)
    return NULL;

  /* Simply read from the cache, which is updated from the registry by the
   * watch thread as soon as changes can propagate. Any changes not yet in the
   * cache will have the 'changed' signal emitted after this function returns.
   */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
  LeaveCriticalSection (self->cache_lock);

  trace ("Reading key %s, cache node %x\n", key_name, cache_node);

  /* Maybe it's not set, we can return to default */
  if (cache_node == NULL)
    return NULL;

  trace ("\t- cached value %s\n", registry_value_dump (((RegistryCacheItem *)cache_node->data)->value));

  registry_value = ((RegistryCacheItem *)cache_node->data)->value;

  gsettings_type = g_variant_type_dup_string (expected_type);

  /* The registry is user-editable, so we need to be fault-tolerant here. */
  switch (gsettings_type[0])
    {
    case 'b':
    case 'y':
    case 'n':
    case 'q':
    case 'i':
    case 'u':
      if (registry_value.type == REG_DWORD)
        gsettings_value = g_variant_new (gsettings_type, registry_value.dword);
      break;

    case 't':
    case 'x':
      if (registry_value.type == REG_QWORD)
        {
          DWORDLONG qword_value = *(DWORDLONG *)registry_value.ptr;
          gsettings_value = g_variant_new (gsettings_type, qword_value);
        }
      break;

    default:
      if (registry_value.type == REG_SZ)
        {
          if (gsettings_type[0] == 's')
            gsettings_value = g_variant_new_string ((char *)registry_value.ptr);
          else
            {
              GError *error = NULL;

              gsettings_value = g_variant_parse (expected_type, registry_value.ptr,
                                                 NULL, NULL, &error);

              if (error != NULL)
                g_message ("gregistrysettingsbackend: error parsing key %s: %s",
                           key_name, error->message);
            }
        }
        break;
    }

  g_free (gsettings_type);

  return gsettings_value;
}


typedef struct
{
  GRegistrySettingsBackend *self;
  HKEY hroot;
} RegistryWrite;

static gboolean
g_registry_settings_backend_write_one (const char *key_name,
                                       GVariant   *variant,
                                       gpointer    user_data)
{
  GRegistrySettingsBackend *self;
  RegistryWrite *action;
  RegistryValue value;
  HKEY hroot;
  HKEY hpath;
  gchar *path_name;
  gunichar2 *path_namew;
  gchar *value_name = NULL;
  gunichar2 *value_namew;
  DWORD value_data_size;
  LPVOID value_data;
  gunichar2 *value_dataw;
  LONG result;
  GNode *node;
  gboolean changed;
  const gchar *type_string;

  type_string = g_variant_get_type_string (variant);
  action = user_data;
  self = G_REGISTRY_SETTINGS_BACKEND (action->self);
  hroot = action->hroot;

  value.type = REG_NONE;
  value.ptr = NULL;

  switch (type_string[0])
    {
    case 'b':
    case 'y':
    case 'n':
    case 'q':
    case 'i':
    case 'u':
      value.type = REG_DWORD;
      value.dword = g_variant_get_as_dword (variant);
      value_data_size = 4;
      value_data = &value.dword;
      break;

    case 'x':
    case 't':
      value.type = REG_QWORD;
      value.ptr = g_malloc (8);
      *(DWORDLONG *)value.ptr = g_variant_get_as_qword (variant);
      value_data_size = 8;
      value_data = value.ptr;
      break;

    default:
      value.type = REG_SZ;
      if (type_string[0] == 's')
        {
          gsize length;
          value.ptr = g_strdup (g_variant_get_string (variant, &length));
          value_data_size = length + 1;
          value_data = value.ptr;
        }
      else
        {
          GString *value_string;
          value_string = g_variant_print_string (variant, NULL, FALSE);
          value_data_size = value_string->len + 1;
          value.ptr = value_data = g_string_free (value_string, FALSE);
        }
      break;
    }

  /* First update the cache, because the value may not have changed and we can
   * save a write.
   *
   * If 'value' has changed then its memory will not be freed by update_node(),
   * because it will be stored in the node.
   */
  EnterCriticalSection (self->cache_lock);
  node = registry_cache_get_node_for_key (self->cache_root, key_name, TRUE);
  changed = registry_cache_update_node (node, value);
  LeaveCriticalSection (self->cache_lock);

  if (!changed)
    return FALSE;

  /* Block the next notification to any watch points above this location,
   * because they will each get triggered on a change that is already updated
   * in the cache.
   */
  registry_cache_block_notification (node);

  path_name = parse_key (key_name, NULL, &value_name);

  trace ("Set key: %s / %s\n", path_name, value_name);

  path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);

  /* Store the value in the registry */
  result = RegCreateKeyExW (hroot, path_namew, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL);
  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "gregistrysettingsbackend: opening key %s failed",
                             path_name + 1);
      registry_value_free (value);
      g_free (path_namew);
      g_free (path_name);
      return FALSE;
    }

  g_free (path_namew);

  value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);

  value_dataw = NULL;

  switch (type_string[0])
    {
    case 'b':
    case 'y':
    case 'n':
    case 'q':
    case 'i':
    case 'u':
    case 'x':
    case 't':
      break;
    default:
      value_dataw = g_utf8_to_utf16 (value_data, -1, NULL, NULL, NULL);
      value_data = value_dataw;
      value_data_size = (DWORD)((wcslen (value_data) + 1) * sizeof (gunichar2));
      break;
    }

  result = RegSetValueExW (hpath, value_namew, 0, value.type, value_data, value_data_size);

  if (result != ERROR_SUCCESS)
    g_message_win32_error (result, "gregistrysettingsbackend: setting value %s\\%s\\%s\\%s failed.\n",
                           predefined_key_to_string (self->base_key),
                           self->base_path, path_name, value_name);

  /* If the write fails then it will seem like the value has changed until the
   * next execution (because we wrote to the cache first). There's no reason
   * for it to fail unless something is weirdly broken, however.
   */

  RegCloseKey (hpath);
  g_free (path_name);
  g_free (value_namew);
  g_free (value_dataw);

  return FALSE;
}

/* The dconf write policy is to do the write while making out it succeeded,
 * and then backtrack if it didn't. The registry functions are synchronous so
 * we can't do that. */

static gboolean
g_registry_settings_backend_write (GSettingsBackend *backend,
                                   const gchar      *key_name,
                                   GVariant         *value,
                                   gpointer          origin_tag)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  LONG result;
  HKEY hroot;
  RegistryWrite action;

  result = RegCreateKeyExW (self->base_key, self->base_pathw, 0, NULL, 0,
                            KEY_WRITE, NULL, &hroot, NULL);
  if (result != ERROR_SUCCESS)
    {
      trace ("Error opening/creating key %s\\%s.\n",
             predefined_key_to_string (self->base_key),
             self->base_path);
      return FALSE;
    }

  action.self = self;
  action.hroot = hroot;
  g_registry_settings_backend_write_one (key_name, value, &action);
  g_settings_backend_changed (backend, key_name, origin_tag);

  RegCloseKey (hroot);

  return TRUE;
}

static gboolean
g_registry_settings_backend_write_tree (GSettingsBackend *backend,
                                        GTree            *values,
                                        gpointer          origin_tag)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  LONG result;
  HKEY hroot;
  RegistryWrite action;

  result = RegCreateKeyExW (self->base_key, self->base_pathw, 0, NULL, 0,
                            KEY_WRITE, NULL, &hroot, NULL);
  if (result != ERROR_SUCCESS)
    {
      trace ("Error opening/creating key %s\\%s.\n",
             predefined_key_to_string (self->base_key),
             self->base_path);
      return FALSE;
    }

  action.self =  self;
  action.hroot = hroot;
  g_tree_foreach (values, (GTraverseFunc)g_registry_settings_backend_write_one,
                  &action);

  g_settings_backend_changed_tree (backend, values, origin_tag);
  RegCloseKey (hroot);

  return TRUE;
}

static void
g_registry_settings_backend_reset (GSettingsBackend *backend,
                                   const gchar      *key_name,
                                   gpointer          origin_tag)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  gchar *path_name;
  gunichar2 *path_namew;
  gchar *value_name = NULL;
  gunichar2 *value_namew;
  GNode *cache_node;
  LONG result;
  HKEY hpath;

  /* Remove from cache */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
  if (cache_node)
    registry_cache_destroy_tree (cache_node, self->watch);
  LeaveCriticalSection (self->cache_lock);

  /* Remove from the registry */
  path_name = parse_key (key_name, self->base_path, &value_name);
  path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);

  result = RegOpenKeyExW (self->base_key, path_namew, 0, KEY_SET_VALUE, &hpath);
  g_free (path_namew);

  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "Registry: resetting key '%s\\%s'",
                             predefined_key_to_string (self->base_key),
                             path_name);
      g_free (path_name);
      return;
    }

  value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);

  result = RegDeleteValueW (hpath, value_namew);
  g_free (value_namew);
  RegCloseKey (hpath);

  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "Registry: resetting key '%s\\%s'",
                             predefined_key_to_string (self->base_key),
                             path_name);
      g_free (path_name);
      return;
    }

  g_free (path_name);

  g_settings_backend_changed (backend, key_name, origin_tag);
}

static gboolean
g_registry_settings_backend_get_writable (GSettingsBackend *backend,
                                          const gchar      *key_name)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  gchar *path_name;
  gunichar2 *path_namew;
  gchar *value_name = NULL;
  HKEY hpath;
  LONG result;

  path_name = parse_key (key_name, self->base_path, &value_name);
  path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);

  /* Note: we create the key if it wasn't created yet, but it is not much
   * of a problem since at the end of the day we have to create it anyway
   * to read or to write from it
   */
  result = RegCreateKeyExW (self->base_key, path_namew, 0, NULL, 0,
                            KEY_WRITE, NULL, &hpath, NULL);
  g_free (path_namew);

  if (result != ERROR_SUCCESS)
    {
      trace ("Error opening/creating key to check writability: %s\\%s.\n",
             predefined_key_to_string (self->base_key),
             path_name);
      g_free (path_name);

      return FALSE;
    }

  g_free (path_name);
  RegCloseKey (hpath);

  return TRUE;
}

/********************************************************************************
 * Spot-the-difference engine
 ********************************************************************************/

static void
_free_watch (WatchThreadState *self,
             guint             index,
             GNode            *cache_node);

static void
registry_cache_item_reset_readable (GNode    *node,
                                    gpointer  data)
{
  RegistryCacheItem *item = node->data;
  item->readable = FALSE;
}

/* Delete a node and any children, for when it has been deleted from the registry */
static void
registry_cache_destroy_tree (GNode            *node,
                             WatchThreadState *self)
{
  RegistryCacheItem *item = node->data;

  g_node_children_foreach (node, G_TRAVERSE_ALL,
                           (GNodeForeachFunc)registry_cache_destroy_tree, self);

  if (item->subscription_count > 0)
    {
      guint i;

      /* There must be some watches active if this node is a watch point */
      g_warn_if_fail (self->cache_nodes->len > 1);

      /* This is a watch point that has been deleted. Let's free the watch! */
      for (i = 1; i < self->cache_nodes->len; i++)
        {
          if (g_ptr_array_index (self->cache_nodes, i) == node)
            break;
        }

      if (i >= self->cache_nodes->len)
        g_warning ("watch thread: a watch point was deleted, but unable to "
                   "find '%s' in the list of %i watch nodes\n", item->name,
                   self->cache_nodes->len - 1);
      else
        {
          _free_watch (self, i, node);
          g_atomic_int_inc (&self->watches_remaining);
        }
    }
  registry_cache_item_free (node->data);
  g_node_destroy (node);
}

/* One of these is sent down the pipe when something happens in the registry. */
typedef struct
{
  GRegistrySettingsBackend *self;
  gchar *prefix;          /* prefix is a gsettings path, all items are subkeys of this. */
  GPtrArray *items;       /* each item is a subkey below prefix that has changed. */
} RegistryEvent;

typedef struct
{
  RegistryEvent *event;
  gchar *current_key_name;
} DeletedItemData;

static void
mark_all_subkeys_as_changed (GNode    *node,
                             gpointer  data)
{
  RegistryCacheItem *item = node->data;
  DeletedItemData *item_data = data;

  if (item_data->current_key_name == NULL)
    item_data->current_key_name = g_strdup (item->name);
  else
    {
      gchar *name;

      name = g_build_path ("/", item_data->current_key_name, item->name, NULL);
      g_free (item_data->current_key_name);
      item_data->current_key_name = name;
    }

  /* Iterate until we find an item that is a value */
  if (item->value.type == REG_NONE)
    g_node_children_foreach (node, G_TRAVERSE_ALL,
                             mark_all_subkeys_as_changed, data);
  else
    g_ptr_array_add (item_data->event->items, item_data->current_key_name);
}

static void
registry_cache_remove_deleted (GNode    *node,
                               gpointer  data)
{
  RegistryCacheItem *item = node->data;
  RegistryEvent *event = data;

  if (!item->readable)
    {
      DeletedItemData item_data;

      item_data.event = event;
      item_data.current_key_name = NULL;

      mark_all_subkeys_as_changed (node, &item_data);
      registry_cache_destroy_tree (node, event->self->watch);
    }
}

/* Update cache from registry, and optionally report on the changes.
 *
 * This function is sometimes called from the watch thread, with no locking. It
 * does call g_registry_settings_backend functions, but this is okay because they only
 * access self->base which is constant.
 *
 * When looking at this code bear in mind the terminology: in the registry, keys
 * are containers that contain values, and other keys. Keys have a 'default'
 * value which we always ignore.
 *
 * n_parent_watches: a counter used to set the reference count of any new nodes
 *                   that are created - they should have as many references as
 *                   there are notifications that are watching them.
 */
static void
registry_cache_update (GRegistrySettingsBackend *self,
                       HKEY                      hpath,
                       const gchar              *prefix,
                       const gchar              *partial_key_name,
                       GNode                    *cache_node,
                       int                       n_watches,
                       RegistryEvent            *event)
{
  gunichar2 bufferw[MAX_KEY_NAME_LENGTH + 1];
  gchar *buffer;
  gchar *key_name;
  gint i;
  LONG result;
  RegistryCacheItem *item;

  item = cache_node->data;

  if (item->subscription_count > 0)
    n_watches++;

  /* prefix is the level that all changes occur below; partial_key_name should
   * be NULL on the first call to this function */
  key_name = g_build_path ("/", prefix, partial_key_name, NULL);

  trace ("registry cache update: %s. Node %x has %i children\n", key_name,
         cache_node, g_node_n_children (cache_node));

  /* Start by zeroing 'readable' flag. When the registry traversal is done, any unreadable nodes
   * must have been deleted from the registry.
   */
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
                           registry_cache_item_reset_readable, NULL);

  /* Recurse into each subpath at the current level, if any */
  i = 0;
  while (1)
    {
      DWORD bufferw_size = MAX_KEY_NAME_LENGTH + 1;
      HKEY  hsubpath;

      result = RegEnumKeyExW (hpath, i++, bufferw, &bufferw_size, NULL, NULL, NULL, NULL);
      if (result != ERROR_SUCCESS)
        break;

      result = RegOpenKeyExW (hpath, bufferw, 0, KEY_READ, &hsubpath);
      if (result == ERROR_SUCCESS)
        {
          GNode *subkey_node;
          RegistryCacheItem *child_item;
          gchar *new_partial_key_name;

          buffer = g_utf16_to_utf8 (bufferw, -1, NULL, NULL, NULL);
          if (buffer == NULL)
            continue;

          subkey_node = registry_cache_find_immediate_child (cache_node, buffer);
          if (subkey_node == NULL)
            {
              RegistryValue null_value = {REG_NONE, {0}};
              subkey_node = registry_cache_add_item (cache_node, buffer,
                                                     null_value, n_watches);
            }

          new_partial_key_name = g_build_path ("/", partial_key_name, buffer, NULL);
          registry_cache_update (self, hsubpath, prefix, new_partial_key_name,
                                 subkey_node, n_watches, event);
          g_free (new_partial_key_name);

          child_item = subkey_node->data;
          child_item->readable = TRUE;

          g_free (buffer);
          RegCloseKey (hsubpath);
        }
    }

  if (result != ERROR_NO_MORE_ITEMS)
    g_message_win32_error (result, "gregistrysettingsbackend: error enumerating subkeys for cache.");

  /* Enumerate each value at 'path' and check if it has changed */
  i = 0;
  while (1)
    {
      DWORD bufferw_size = MAX_KEY_NAME_LENGTH + 1;
      GNode *cache_child_node;
      RegistryCacheItem *child_item;
      RegistryValue value;
      gboolean changed = FALSE;

      result = RegEnumValueW (hpath, i++, bufferw, &bufferw_size, NULL, NULL, NULL, NULL);
      if (result != ERROR_SUCCESS)
        break;

      buffer = g_utf16_to_utf8 (bufferw, -1, NULL, NULL, NULL);

      if (buffer == NULL || buffer[0] == 0)
        {
          /* This is the key's 'default' value, for which we have no use. */
          g_free (buffer);
          continue;
        }

      cache_child_node = registry_cache_find_immediate_child (cache_node, buffer);

      if (!registry_read (hpath, key_name, buffer, &value))
        {
          g_free (buffer);
          continue;
        }

      trace ("\tgot value %s for %s, node %x\n",
             registry_value_dump (value), buffer, cache_child_node);

      if (cache_child_node == NULL)
        {
          /* This is a new value */
          cache_child_node = registry_cache_add_item (cache_node, buffer, value,
                                                      n_watches);
          changed = TRUE;
        }
      else
        {
         /* For efficiency, instead of converting every value back to a GVariant to
          * compare it, we compare them as registry values (integers, or string
          * representations of the variant). The spurious change notifications that may
          * result should not be a big issue.
          *
          * Note that 'value' is swallowed or freed.
          */
          changed = registry_cache_update_node (cache_child_node, value);
        }

      child_item = cache_child_node->data;
      child_item->readable = TRUE;
      if (changed && event != NULL)
        {
          gchar *item_path;

          if (partial_key_name == NULL)
            item_path = g_strdup (buffer);
          else
            item_path = g_build_path ("/", partial_key_name, buffer, NULL);

          g_ptr_array_add (event->items, item_path);
        }

      g_free (buffer);
    }

  if (result != ERROR_NO_MORE_ITEMS)
    g_message_win32_error (result, "gregistrysettingsbackend: error enumerating values for cache");

  /* Any nodes now left unreadable must have been deleted, remove them from cache */
  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
                           registry_cache_remove_deleted, event);

  trace ("registry cache update complete.\n");

  g_free (key_name);
}

/***********************************************************************************
 * Thread to watch for registry change events
 ***********************************************************************************/

/* Called by watch thread. Apply for notifications on a registry key and its subkeys. */
static DWORD
registry_watch_key (HKEY   hpath,
                    HANDLE event)
{
  return RegNotifyChangeKeyValue (hpath, TRUE,
                                  REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
                                  event, TRUE);
}

/* This handler runs in the main thread to emit the changed signals */
static gboolean
watch_handler (RegistryEvent *event)
{
  trace ("Watch handler: got event in %s, items %i.\n", event->prefix, event->items->len);

  /* GSettings requires us to NULL-terminate the array. */
  g_ptr_array_add (event->items, NULL);
  g_settings_backend_keys_changed (G_SETTINGS_BACKEND (event->self), event->prefix,
                                   (gchar const **)event->items->pdata, NULL);

  g_ptr_array_free (event->items, TRUE);
  g_free (event->prefix);
  g_object_unref (event->self);
  g_slice_free (RegistryEvent, event);

  return G_SOURCE_REMOVE;
}

static void
_free_watch (WatchThreadState *self,
             guint             index,
             GNode            *cache_node)
{
  HKEY hpath;
  HANDLE cond;
  gchar *prefix;

  g_return_if_fail (index > 0 && index < self->events->len);

  cond = g_ptr_array_index (self->events, index);
  hpath = g_ptr_array_index (self->handles, index);
  prefix = g_ptr_array_index (self->prefixes, index);

  trace ("Freeing watch %i [%s]\n", index, prefix);

  /* These can be NULL if the watch was already dead, this can happen when eg.
   * a key is deleted but GSettings is still subscribed to it - the watch is
   * kept alive so that the unsubscribe function works properly, but does not
   * do anything.
   */
  if (hpath != NULL)
    RegCloseKey (hpath);

  if (cache_node != NULL)
    {
      //registry_cache_dump (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_root, NULL);
      registry_cache_unref_tree (cache_node);
    }

  CloseHandle (cond);
  g_free (prefix);

  /* As long as we remove from each array at the same time, it doesn't matter that
   * their orders get messed up - they all get messed up the same.
   */
  g_ptr_array_remove_index_fast (self->handles, index);
  g_ptr_array_remove_index_fast (self->events, index);
  g_ptr_array_remove_index_fast (self->prefixes, index);
  g_ptr_array_remove_index_fast (self->cache_nodes, index);
}

static void
watch_thread_handle_message (WatchThreadState *self)
{
  switch (self->message.type)
    {
    case WATCH_THREAD_NONE:
      trace ("watch thread: you woke me up for nothin', man!");
      break;

    case WATCH_THREAD_ADD_WATCH:
      {
        RegistryWatch *watch = &self->message.watch;
        LONG result;

        result = registry_watch_key (watch->hpath, watch->event);

        if (result == ERROR_SUCCESS)
          {
            g_ptr_array_add (self->events, watch->event);
            g_ptr_array_add (self->handles, watch->hpath);
            g_ptr_array_add (self->prefixes, watch->prefix);
            g_ptr_array_add (self->cache_nodes, watch->cache_node);

            trace ("watch thread: new watch on %s, %i total\n", watch->prefix,
                   self->events->len);
          }
        else
          {
            g_message_win32_error (result, "watch thread: could not watch %s", watch->prefix);

            CloseHandle (watch->event);
            RegCloseKey (watch->hpath);
            g_free (watch->prefix);
            registry_cache_unref_tree (watch->cache_node);
          }
        break;
      }

    case WATCH_THREAD_REMOVE_WATCH:
      {
        GNode *cache_node;
        RegistryCacheItem *cache_item;
        guint i;

        for (i = 1; i < self->prefixes->len; i++)
          {
            if (strcmp (g_ptr_array_index (self->prefixes, i),
                        self->message.watch.prefix) == 0)
              break;
          }

        if (i >= self->prefixes->len)
          {
            /* Don't make a fuss if the prefix is not being watched because
             * maybe the path was deleted so we removed the watch.
             */
            trace ("unsubscribe: prefix %s is not being watched [%i things are]!\n",
                   self->message.watch.prefix, self->prefixes->len);
            g_free (self->message.watch.prefix);
            break;
          }

        cache_node = g_ptr_array_index (self->cache_nodes, i);

        trace ("watch thread: unsubscribe: freeing node %p, prefix %s, index %i\n",
               cache_node, self->message.watch.prefix, i);
        g_free (self->message.watch.prefix);

        if (cache_node != NULL)
          {
            cache_item = cache_node->data;

            /* There may be more than one GSettings object subscribed to this
             * path, only free the watch when the last one unsubscribes.
             */
            cache_item->subscription_count--;
            if (cache_item->subscription_count > 0)
              break;
          }

        _free_watch (self, i, cache_node);

        g_atomic_int_inc (&self->watches_remaining);
        break;
      }

    case WATCH_THREAD_STOP:
      {
        guint i;

        /* Free any remaining cache and watch handles */
        for (i = 1; i < self->events->len; i++)
          _free_watch (self, i, g_ptr_array_index (self->cache_nodes, i));

        g_ptr_array_unref (self->events);
        g_ptr_array_unref (self->handles);
        g_ptr_array_unref (self->prefixes);
        g_ptr_array_unref (self->cache_nodes);

        SetEvent (self->message_received_event);
        ExitThread (0);
      }
    }

  self->message.type = WATCH_THREAD_NONE;
  SetEvent (self->message_received_event);
}

/* Thread which watches for win32 registry events */
static DWORD WINAPI
watch_thread_function (LPVOID parameter)
{
  WatchThreadState *self = (WatchThreadState *)parameter;
  DWORD result;

  self->events = g_ptr_array_new ();
  self->handles = g_ptr_array_new ();
  self->prefixes = g_ptr_array_new ();
  self->cache_nodes = g_ptr_array_new ();
  g_ptr_array_add (self->events, self->message_sent_event);
  g_ptr_array_add (self->handles, NULL);
  g_ptr_array_add (self->prefixes, NULL);
  g_ptr_array_add (self->cache_nodes, NULL);

  while (1)
    {
      trace ("watch thread: going to sleep; %i events watched.\n", self->events->len);
      result = WaitForMultipleObjects (self->events->len, self->events->pdata, FALSE, INFINITE);

      if (result == WAIT_OBJECT_0)
        {
          /* A message to you. The sender (main thread) will block until we signal the received
           * event, so there should be no danger of it sending another before we receive the
           * first.
           */
          watch_thread_handle_message (self);
        }
      else if (result > WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + self->events->len)
        {
          HKEY hpath;
          HANDLE cond;
          gchar *prefix;
          GNode *cache_node;
          RegistryCacheItem *cache_item;
          RegistryEvent *event;
          gint notify_index;

          /* One of our notifications has triggered. All we know is which one, and which key
           * this is for. We do most of the processing here, because we may as well. If the
           * registry changes further while we are processing it doesn't matter - we will then
           * receive another change notification from the OS anyway.
           */
          notify_index = result - WAIT_OBJECT_0;
          hpath = g_ptr_array_index (self->handles, notify_index);
          cond = g_ptr_array_index (self->events, notify_index);
          prefix = g_ptr_array_index (self->prefixes, notify_index);
          cache_node = g_ptr_array_index (self->cache_nodes, notify_index);

          trace ("Watch thread: notify received on prefix %i: %s.\n", notify_index, prefix);

          if (cache_node == NULL)
            {
              /* This path has been deleted */
              trace ("Notify received on a path that was deleted\n");
              continue;
            }

          /* Firstly we need to reapply for the notification, because (what a
           * sensible API) we won't receive any more. MSDN is pretty
           * inconsistent on this matter:
           *   http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx
           *   http://support.microsoft.com/kb/236570
           * But my tests (on Windows XP SP3) show that we need to reapply
           * each time.
           */
          result = registry_watch_key (hpath, cond);

          if (result != ERROR_SUCCESS)
            {
              /* Watch failed, most likely because the key has just been
               * deleted. Free the watch and unref the cache nodes.
               */
             if (result != ERROR_KEY_DELETED)
               g_message_win32_error (result, "watch thread: failed to watch %s", prefix);

             _free_watch (self, notify_index, cache_node);
             g_atomic_int_inc (&self->watches_remaining);
             continue;
            }

          /* The notification may have been blocked because we just changed
           * some data ourselves.
           */
          cache_item = cache_node->data;
          if (cache_item->block_count)
            {
              cache_item->block_count--;
              trace ("Watch thread: notify blocked at %s\n", prefix);
              continue;
            }

          /* Now we update our stored cache from registry data, and find which keys have
           * actually changed. If more changes happen while we are processing, we will get
           * another event because we have reapplied for change notifications already.
           *
           * Working here rather than in the main thread is preferable because the UI is less
           * likely to block (only when changing notification subscriptions).
           */
          event = g_slice_new (RegistryEvent);
          event->self = G_REGISTRY_SETTINGS_BACKEND (g_object_ref (self->owner));
          event->prefix = g_strdup (prefix);
          event->items = g_ptr_array_new_with_free_func (g_free);

          EnterCriticalSection (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_lock);
          registry_cache_update (G_REGISTRY_SETTINGS_BACKEND (self->owner), hpath,
                                 prefix, NULL, cache_node, 0, event);
          LeaveCriticalSection (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_lock);

          if (event->items->len > 0)
            g_idle_add ((GSourceFunc) watch_handler, event);
          else
            {
              g_object_unref (event->self);
              g_free (event->prefix);
              g_ptr_array_free (event->items, TRUE);
              g_slice_free (RegistryEvent, event);
            }
        }
      else
        {
          /* God knows what has happened */
          g_message_win32_error (GetLastError(), "watch thread: WaitForMultipleObjects error");
        }
    }

  return -1;
}

/* This function assumes you hold the watch lock! */
static gboolean
watch_start (GRegistrySettingsBackend *self)
{
  WatchThreadState *watch;

  g_return_val_if_fail (self->watch == NULL, FALSE);

  watch = g_slice_new (WatchThreadState);
  watch->owner = G_SETTINGS_BACKEND (self);

  watch->watches_remaining = MAX_WATCHES;

  watch->message_lock = g_slice_new (CRITICAL_SECTION);
  InitializeCriticalSection (watch->message_lock);
  watch->message_sent_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  watch->message_received_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (watch->message_sent_event == NULL || watch->message_received_event == NULL)
    {
      g_message_win32_error (GetLastError (), "gregistrysettingsbackend: Failed to create sync objects.");
      goto fail;
    }

  /* Use a small stack to make the thread more lightweight. */
  watch->thread = CreateThread (NULL, 1024, watch_thread_function, watch, 0, NULL);
  if (watch->thread == NULL)
    {
      g_message_win32_error (GetLastError (), "gregistrysettingsbackend: Failed to create notify watch thread.");
      goto fail;
    }

  self->watch = watch;

  return TRUE;

fail:
  DeleteCriticalSection (watch->message_lock);
  g_slice_free (CRITICAL_SECTION, watch->message_lock);
  if (watch->message_sent_event != NULL)
    CloseHandle (watch->message_sent_event);
  if (watch->message_received_event != NULL)
    CloseHandle (watch->message_received_event);
  g_slice_free (WatchThreadState, watch);

  return FALSE;
}

/* This function assumes you hold the watch lock! */
/* This function assumes you hold the message lock! */
static void
watch_stop_unlocked (GRegistrySettingsBackend *self)
{
  WatchThreadState *watch = self->watch;
  DWORD result;

  g_return_if_fail (watch != NULL);

  watch->message.type = WATCH_THREAD_STOP;
  SetEvent (watch->message_sent_event);

  /* This is signalled as soon as the message is received. We must not return
   * while the watch thread is still firing off callbacks. Freeing all of the
   * memory is done in the watch thread after this is signalled.
   */
  result = WaitForSingleObject (watch->message_received_event, INFINITE);
  if (result != WAIT_OBJECT_0)
    {
      g_warning ("gregistrysettingsbackend: unable to stop watch thread.");
      return;
    }

  LeaveCriticalSection (watch->message_lock);
  DeleteCriticalSection (watch->message_lock);
  g_slice_free (CRITICAL_SECTION, watch->message_lock);
  CloseHandle (watch->message_sent_event);
  CloseHandle (watch->message_received_event);
  CloseHandle (watch->thread);
  g_slice_free (WatchThreadState, watch);

  trace ("\nwatch thread: %x: all data freed.\n", self);
  self->watch = NULL;
}

/* This function assumes you hold the watch lock! */
static gboolean
watch_add_notify (GRegistrySettingsBackend *self,
                  HANDLE                    event,
                  HKEY                      hpath,
                  const gchar              *gsettings_prefix)
{
  WatchThreadState *watch = self->watch;
  GNode *cache_node;
  RegistryCacheItem *cache_item;
#ifdef TRACE
  DWORD result;
#endif

  g_return_val_if_fail (watch != NULL, FALSE);

  trace ("watch_add_notify: prefix %s.\n", gsettings_prefix);

  /* Duplicate tree into the cache in the main thread, before we add the notify: if we do it in the
   * thread we can miss changes while we are caching.
   */
  EnterCriticalSection (self->cache_lock);
  cache_node = registry_cache_get_node_for_key (self->cache_root, gsettings_prefix, TRUE);

  if (cache_node == NULL || cache_node->data == NULL)
    {
      LeaveCriticalSection (self->cache_lock);
      g_warn_if_reached ();
      return FALSE;
    }

  cache_item = cache_node->data;

  cache_item->subscription_count++;
  if (cache_item->subscription_count > 1)
    {
      trace ("watch_add_notify: prefix %s already watched, %i subscribers.\n",
             gsettings_prefix, cache_item->subscription_count);
      LeaveCriticalSection (self->cache_lock);
      return FALSE;
    }

  registry_cache_ref_tree (cache_node);
  registry_cache_update (self, hpath, gsettings_prefix, NULL, cache_node, 0, NULL);
  //registry_cache_dump (self->cache_root, NULL);
  LeaveCriticalSection (self->cache_lock);

  EnterCriticalSection (watch->message_lock);
  watch->message.type = WATCH_THREAD_ADD_WATCH;
  watch->message.watch.event = event;
  watch->message.watch.hpath = hpath;
  watch->message.watch.prefix = g_strdup (gsettings_prefix);
  watch->message.watch.cache_node = cache_node;

  SetEvent (watch->message_sent_event);

  /* Wait for the received event in return, to avoid sending another message before the first
   * one was received. If it takes > 200ms there is a possible race but the worst outcome is
   * a notification is ignored.
   */
#ifdef TRACE
  result =
#endif
    WaitForSingleObject (watch->message_received_event, 200);
#ifdef TRACE
  if (result != WAIT_OBJECT_0)
    trace ("watch thread is slow to respond - notification may not be added.");
#endif

  LeaveCriticalSection (watch->message_lock);

  return TRUE;
}

/* This function assumes you hold the watch lock! */
static void
watch_remove_notify (GRegistrySettingsBackend *self,
                     const gchar              *key_name)
{
  WatchThreadState *watch = self->watch;
  LONG result;

  if (self->watch == NULL)
    /* Here we assume that the unsubscribe message is for somewhere that was
     * deleted, and so it has already been removed and the watch thread has
     * stopped.
     */
    return;

  EnterCriticalSection (watch->message_lock);
  watch->message.type = WATCH_THREAD_REMOVE_WATCH;
  watch->message.watch.prefix = g_strdup (key_name);

  SetEvent (watch->message_sent_event);

  /* Wait for the received event in return, to avoid sending another message before the first
   * one was received.
   */
  result = WaitForSingleObject (watch->message_received_event, INFINITE);

  if (result != ERROR_SUCCESS)
    g_warning ("unsubscribe from %s: message not acknowledged", key_name);

  if (g_atomic_int_get (&watch->watches_remaining) >= MAX_WATCHES)
    /* Stop it before any new ones can get added and confuse things */
    watch_stop_unlocked (self);
  else
    LeaveCriticalSection (watch->message_lock);
}

/* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that
 * key. Our job is easier because keys and values are separate.
 */
static void
g_registry_settings_backend_subscribe (GSettingsBackend *backend,
                                       const char       *key_name)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
  gchar *path_name;
  gunichar2 *path_namew;
  gchar *value_name = NULL;
  HKEY hpath;
  HANDLE event;
  LONG result;

  EnterCriticalSection (&self->watch_lock);
  if (self->watch == NULL && !watch_start (self))
    {
      LeaveCriticalSection (&self->watch_lock);
      return;
    }

  if (g_atomic_int_dec_and_test (&self->watch->watches_remaining))
    {
      g_atomic_int_inc (&self->watch->watches_remaining);
      LeaveCriticalSection (&self->watch_lock);
      g_warning ("subscribe() failed: only %i different paths may be watched.", MAX_WATCHES);
      return;
    }

  path_name = parse_key (key_name, self->base_path, &value_name);

  /* Must check for this, otherwise strange crashes occur because the cache
   * node that is being watched gets freed. All path names to subscribe must
   * end in a slash!
   */
  if (value_name != NULL && *value_name != 0)
    g_warning ("subscribe() failed: path must end in a /, got %s", key_name);

  trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch);

  path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);
  g_free (path_name);

  /* Give the caller the benefit of the doubt if the key doesn't exist and create it. The caller
   * is almost certainly a new g_settings with this path as base path. */
  result = RegCreateKeyExW (self->base_key, path_namew, 0, NULL, 0, KEY_READ, NULL, &hpath,
                            NULL);
  g_free (path_namew);

  if (result != ERROR_SUCCESS)
    {
      g_message_win32_error (result, "gregistrysettingsbackend: Unable to subscribe to key %s.", key_name);
      g_atomic_int_inc (&self->watch->watches_remaining);
      LeaveCriticalSection (&self->watch_lock);
      return;
    }

  event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (event == NULL)
    {
      g_message_win32_error (result, "gregistrysettingsbackend: CreateEvent failed.");
      g_atomic_int_inc (&self->watch->watches_remaining);
      LeaveCriticalSection (&self->watch_lock);
      RegCloseKey (hpath);
      return;
    }

  /* The actual watch is added by the thread, which has to re-subscribe each time it
   * receives a change. */
  if (!watch_add_notify (self, event, hpath, key_name))
    {
      g_atomic_int_inc (&self->watch->watches_remaining);
      RegCloseKey (hpath);
      CloseHandle (event);
    }

  LeaveCriticalSection (&self->watch_lock);
}

static void
g_registry_settings_backend_unsubscribe (GSettingsBackend *backend,
                                         const char       *key_name)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);

  trace ("unsubscribe: %s.\n", key_name);

  EnterCriticalSection (&self->watch_lock);
  watch_remove_notify (self, key_name);
  LeaveCriticalSection (&self->watch_lock);
}

/********************************************************************************
 * Object management junk
 ********************************************************************************/

static void
g_registry_settings_backend_finalize (GObject *object)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
  RegistryCacheItem *item;

  item = self->cache_root->data;
  g_warn_if_fail (item->ref_count == 1);

  registry_cache_item_free (item);
  g_node_destroy (self->cache_root);

  if (self->watch != NULL)
    {
      EnterCriticalSection (self->watch->message_lock);
      watch_stop_unlocked (self);
    }
  DeleteCriticalSection (&self->watch_lock);

  DeleteCriticalSection (self->cache_lock);
  g_slice_free (CRITICAL_SECTION, self->cache_lock);

  g_free (self->base_path);
  g_free (self->base_pathw);
}

static void
g_registry_settings_backend_constructed (GObject *object)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);

  if (self->base_key == NULL || self->base_path == NULL || self->base_pathw == NULL)
    {
      self->base_key = HKEY_CURRENT_USER;
      self->base_path = g_strdup ("Software\\GSettings");
      self->base_pathw = g_utf8_to_utf16 (self->base_path, -1, NULL, NULL, NULL);
    }
}

static void
g_registry_settings_backend_get_property (GObject    *object,
                                          guint       prop_id,
                                          GValue     *value,
                                          GParamSpec *pspec)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);

  switch ((GRegistrySettingsBackendProperty) prop_id)
    {
    case PROP_REGISTRY_KEY:
      g_value_take_string (value,
                           g_strdup_printf ("%s\\%s",
                                            predefined_key_to_string (self->base_key),
                                            self->base_path));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
g_registry_settings_backend_set_property (GObject      *object,
                                          guint         prop_id,
                                          const GValue *value,
                                          GParamSpec   *pspec)
{
  GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
  const gchar *reg_path;

  switch ((GRegistrySettingsBackendProperty) prop_id)
    {
    case PROP_REGISTRY_KEY:
      /* Construct only. */
      g_assert (self->base_key == NULL);
      g_assert (self->base_path == NULL);
      g_assert (self->base_pathw == NULL);

      reg_path = g_value_get_string (value);
      if (reg_path != NULL)
        {
          HKEY base_key;
          const gchar *base_path = split_registry_path (reg_path, &base_key);

          if (base_path != NULL)
            {
              gunichar2 *base_pathw = g_utf8_to_utf16 (base_path, -1, NULL, NULL, NULL);

              if (base_pathw != NULL)
                {
                  self->base_key = g_steal_pointer (&base_key);
                  self->base_path = g_strdup (base_path);
                  self->base_pathw = g_steal_pointer (&base_pathw);
                }
              else
                g_warning ("gregistrysettingsbackend: invalid base registry path '%s'", reg_path);
            }
          else
            g_warning ("gregistrysettingsbackend: base registry path '%s' does not start with a valid root key", reg_path);
        }
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
g_registry_settings_backend_class_init (GRegistrySettingsBackendClass *class)
{
  GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->finalize = g_registry_settings_backend_finalize;
  object_class->constructed = g_registry_settings_backend_constructed;
  object_class->get_property = g_registry_settings_backend_get_property;
  object_class->set_property = g_registry_settings_backend_set_property;

  backend_class->read = g_registry_settings_backend_read;
  backend_class->write = g_registry_settings_backend_write;
  backend_class->write_tree = g_registry_settings_backend_write_tree;
  backend_class->reset = g_registry_settings_backend_reset;
  backend_class->get_writable = g_registry_settings_backend_get_writable;
  backend_class->subscribe = g_registry_settings_backend_subscribe;
  backend_class->unsubscribe = g_registry_settings_backend_unsubscribe;

  /**
   * GRegistrySettingsBackend:registry-key:
   *
   * The location where settings are stored in the registry. Must
   * start with one of the following:
   * - `HKEY_CLASSES_ROOT`
   * - `HKEY_CURRENT_CONFIG`
   * - `HKEY_CURRENT_USER`
   * - `HKEY_LOCAL_MACHINE`
   * - `HKEY_USERS`
   *
   * Defaults to `HKEY_CURRENT_USER\Software\GSettings`.
   *
   * Since: 2.78
   */
  g_object_class_install_property (object_class,
                                   PROP_REGISTRY_KEY,
                                   g_param_spec_string ("registry-key",
                                                        NULL, NULL,
                                                        "HKEY_CURRENT_USER\\Software\\GSettings",
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                                                        G_PARAM_STATIC_STRINGS));
}

static void
g_registry_settings_backend_init (GRegistrySettingsBackend *self)
{
  RegistryCacheItem *item;

  item = g_slice_new (RegistryCacheItem);
  item->value.type = REG_NONE;
  item->value.ptr = NULL;
  item->name = g_strdup ("<root>");
  item->ref_count = 1;
  self->cache_root = g_node_new (item);

  self->cache_lock = g_slice_new (CRITICAL_SECTION);
  InitializeCriticalSection (self->cache_lock);

  self->watch = NULL;
  InitializeCriticalSection (&self->watch_lock);
}

/**
 * g_registry_settings_backend_new:
 * @registry_key: (nullable): the path to the registry key where
 *                settings are stored, or %NULL.
 *
 * If @registry_key is %NULL then the default path
 * `HKEY_CURRENT_USER\Software\GSettings` is used.
 *
 * Returns: (transfer full): a registry-backed #GSettingsBackend
 *
 * Since: 2.78
 **/
GSettingsBackend *
g_registry_settings_backend_new (const gchar *registry_key)
{
  g_return_val_if_fail (registry_key == NULL || split_registry_path (registry_key, NULL), NULL);

  return G_SETTINGS_BACKEND (g_object_new (G_TYPE_REGISTRY_SETTINGS_BACKEND,
                                           "registry-key", registry_key,
                                           NULL));
}
