[glib] Add GSettings Windows Registry backend



commit 3209024c3ba730df9551074819672756477bd5aa
Author: Sam Thursfield <ssssam gmail com>
Date:   Thu Aug 12 16:10:23 2010 +0100

    Add GSettings Windows Registry backend

 gio/Makefile.am                |    7 +-
 gio/giomodule.c                |    4 +
 gio/gregistrysettingsbackend.c | 1966 ++++++++++++++++++++++++++++++++++++++++
 gio/gregistrysettingsbackend.h |   31 +
 4 files changed, 2007 insertions(+), 1 deletions(-)
---
diff --git a/gio/Makefile.am b/gio/Makefile.am
index b7479e3..4618a12 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -128,6 +128,12 @@ settings_sources = \
 	gsettings-mapping.c		\
 	gsettings.c
 
+if OS_WIN32
+settings_sources += \
+	gregistrysettingsbackend.h	\
+	gregistrysettingsbackend.c
+endif
+
 application_headers = \
 	gactiongroup.h			\
 	gsimpleactiongroup.h		\
@@ -142,7 +148,6 @@ application_sources = \
 	gsimpleaction.c			\
 	gapplication.c
 
-
 local_sources = \
 	glocaldirectorymonitor.c 	\
 	glocaldirectorymonitor.h 	\
diff --git a/gio/giomodule.c b/gio/giomodule.c
index 882e224..39a38ff 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -39,6 +39,9 @@
 #ifdef G_OS_UNIX
 #include "gdesktopappinfo.h"
 #endif
+#ifdef G_OS_WIN32
+#include "gregistrysettingsbackend.h"
+#endif
 #include <glib/gstdio.h>
 
 /**
@@ -596,6 +599,7 @@ _g_io_modules_ensure_loaded (void)
 #ifdef G_OS_WIN32
       _g_win32_volume_monitor_get_type ();
       g_win32_directory_monitor_get_type ();
+      g_registry_backend_get_type ();
 #endif
 #ifdef G_OS_UNIX
       _g_unix_volume_monitor_get_type ();
diff --git a/gio/gregistrysettingsbackend.c b/gio/gregistrysettingsbackend.c
new file mode 100644
index 0000000..3e3db2e
--- /dev/null
+++ b/gio/gregistrysettingsbackend.c
@@ -0,0 +1,1966 @@
+/*
+ * Copyright © 2009-10 Sam Thursfield
+ *
+ * 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 of the licence, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Sam Thursfield <ssssam gmail com>
+ */
+
+/* GRegistryBackend implementation notes:
+ *
+ *   - All settings are stored under the path:
+ *       HKEY_CURRENT_USER\Software\GSettings\
+ *     This means all settings are per-user. 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.
+ *
+ *   - RegCreateKeyA is used - Windows can also handle UTF16LE strings.
+ *     GSettings doesn't pay any attention to encoding, so by using ANSI we
+ *     hopefully avoid passing any invalid Unicode.
+ *
+ *   - 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 "gsimplepermission.h"
+#include "gsettingsbackend.h"
+#include "giomodule.h"
+
+
+#define _WIN32_WINNT 0x0500
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+//#define TRACE
+
+/* GSettings' limit */
+#define MAX_KEY_NAME_LENGTH   32
+
+/* 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_BACKEND      (g_registry_backend_get_type ())
+#define G_REGISTRY_BACKEND(inst)     (G_TYPE_CHECK_INSTANCE_CAST ((inst),         \
+                                      G_TYPE_REGISTRY_BACKEND, GRegistryBackend))
+#define G_IS_REGISTRY_BACKEND(inst)  (G_TYPE_CHECK_INSTANCE_TYPE ((inst),         \
+                                      G_TYPE_REGISTRY_BACKEND))
+
+
+typedef GSettingsBackendClass GRegistryBackendClass;
+
+typedef struct {
+  GSettingsBackend  parent_instance;
+
+  char             *base_path;
+
+  /* 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;
+
+  WatchThreadState *watch;
+} GRegistryBackend;
+
+G_DEFINE_TYPE_WITH_CODE (GRegistryBackend,
+                         g_registry_backend,
+                         G_TYPE_SETTINGS_BACKEND,
+                         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_message_win32_error (DWORD result_code,
+                       const gchar *format,
+                      ...)
+{
+  va_list va;
+  gint pos;
+  gchar win32_message[1024];
+
+  if (result_code == 0)
+    result_code = GetLastError ();
+
+  va_start (va, format);
+  pos = g_vsnprintf (win32_message, 512, format, va);
+
+  win32_message[pos++] = ':'; win32_message[pos++] = ' ';
+
+  FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result_code, 0, (LPTSTR)(win32_message+pos),
+                1023 - pos, NULL);
+
+  if (result_code == ERROR_KEY_DELETED)
+    trace ("(%s)", win32_message);
+  else
+    g_message (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
+   * preceeding /.
+   */
+  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_uint16 (variant);
+      case 'q': return g_variant_get_int16 (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 't': return g_variant_get_uint64 (variant);
+      case 'x': return g_variant_get_int64 (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: %s.\n",
+                             path_name, value_name);
+}
+
+/***************************************************************************
+ * 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 ("%i", value.dword);
+  else if (value.type == REG_QWORD)
+    return g_strdup_printf ("%I64i", 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;
+
+  gint32 touched            : 1;
+  RegistryValue value;
+} RegistryCacheItem;
+
+
+
+static GNode *
+registry_cache_add_item (GNode        *parent,
+                         gchar        *name,
+                         RegistryValue value,
+                         gint          ref_count)
+{
+  RegistryCacheItem *item = g_slice_new (RegistryCacheItem);
+  GNode *cache_node;
+
+  g_return_val_if_fail (name != NULL, NULL);
+  g_return_val_if_fail (parent != NULL, NULL);
+
+  /* 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->touched = FALSE;
+  trace ("\treg 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
+_free_cache_item (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)
+    {
+      _free_cache_item (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;
+    }
+}
+
+
+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));
+}
+
+
+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,
+        *c         = strchr (component, '/');
+
+  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 ++;  
+
+  GNode *child = registry_cache_find_immediate_child (node, component);
+  if (child == NULL && create_if_not_found)
+    {
+      item = g_slice_new (RegistryCacheItem);
+      item->name = g_strdup (component);
+      item->value.type = REG_NONE;
+      item->value.ptr = NULL;
+      item->ref_count = n_parent_watches;
+      child = g_node_new (item);
+      g_node_append (node, child);
+      trace ("\tget node for key recursive: new %x = %s.\n", node, item->name);
+    }
+
+  /* We are done if there are no more path components. Allow for a trailing /. */
+  if (child==NULL || c == NULL || *(c+1)==0)
+    return child;
+  else
+    {
+      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,
+        *result = NULL;
+  gchar *component, *c;
+
+  g_return_val_if_fail (key_name != NULL, NULL);
+
+  if (key_name[0] == '/')
+    key_name ++;
+
+  /* Ignore preceeding / */
+  component = g_strdup (key_name);
+  c = strchr (component, '/');
+  if (c != NULL)
+    *c = 0;
+
+  child = registry_cache_find_immediate_child (root, component);
+  if (child == NULL && create_if_not_found)
+    {
+      /* Reference count is set to 0, tree should be referenced by the caller */
+      RegistryCacheItem *item = g_slice_new (RegistryCacheItem);
+      item->value.type = REG_NONE;
+      item->value.ptr = NULL;
+      item->name = g_strdup (component);
+      item->ref_count = 0;
+      trace ("get_node_for_key: New node for component '%s'\n", item->name);
+      child = g_node_new (item);
+      g_node_append (root, child);
+    }
+
+  if (c == NULL)
+    result = root;
+  else 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 = cache_node->data;
+
+  g_return_val_if_fail (cache_node != NULL, FALSE);
+  g_return_val_if_fail (cache_item != NULL, FALSE);
+
+  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 ("gregistrybackend: 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 notifcation 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;
+
+  g_return_val_if_fail (p_value != NULL, FALSE);
+
+  p_value->type = REG_NONE;
+  p_value->ptr = NULL;
+
+  result = RegQueryValueExA (hpath, value_name, 0, &p_value->type, NULL, &value_data_size);
+  if (result != ERROR_SUCCESS)
+     {
+      handle_read_error (result, path_name, value_name);
+      return FALSE;
+     }
+
+  if (p_value->type == REG_SZ && value_data_size == 0)
+    {
+      p_value->ptr = g_strdup ("");
+      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 = RegQueryValueExA (hpath, value_name, 0, NULL, (LPBYTE)buffer, &value_data_size);
+  if (result != ERROR_SUCCESS)
+    {
+      handle_read_error (result, path_name, value_name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+static GVariant *
+g_registry_backend_read (GSettingsBackend   *backend,
+                         const gchar        *key_name,
+                         const GVariantType *expected_type,
+                         gboolean            default_value)
+{
+  GRegistryBackend *self = G_REGISTRY_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\n",
+                           key_name, error->message);
+              }
+          }
+          break;
+    }
+
+  g_free (gsettings_type);
+
+  return gsettings_value;
+}
+
+
+typedef struct
+{
+  GRegistryBackend  *self;
+  HKEY               hroot;
+} RegistryWrite;
+
+static gboolean
+g_registry_backend_write_one (const char *key_name,
+                              GVariant   *variant,
+                              gpointer    user_data)
+{
+  GRegistryBackend *self;
+  RegistryWrite    *action;
+  RegistryValue     value;
+
+  HKEY    hroot, hpath;
+  gchar  *path_name, *value_name = NULL;
+  DWORD   value_data_size;
+  LPVOID  value_data;
+  LONG    result;
+
+  GNode    *node;
+  gboolean  changed;
+
+  action = user_data;
+  self = G_REGISTRY_BACKEND (action->self);
+  hroot = action->hroot;
+
+  value.type = REG_NONE;
+  value.ptr = NULL;
+
+  const gchar *type_string = g_variant_get_type_string (variant);
+  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);
+
+  /* Store the value in the registry */
+  result = RegCreateKeyExA (hroot, path_name, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL);
+  if (result != ERROR_SUCCESS)
+    {
+      g_message_win32_error (result, "gregistrybackend: opening key %s failed", path_name+1);
+      registry_value_free (value);
+      g_free (path_name);
+      return FALSE;
+    }
+
+  result = RegSetValueExA (hpath, value_name, 0, value.type, value_data, value_data_size);
+  if (result != ERROR_SUCCESS)
+      g_message_win32_error (result, "gregistrybackend: setting value %s\%s\\%s failed.\n",
+                             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);
+
+  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_backend_write (GSettingsBackend *backend,
+                          const gchar      *key_name,
+                          GVariant         *value,
+                          gpointer          origin_tag)
+{
+  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
+  LONG result;
+  HKEY hroot;
+
+  result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0,
+                            KEY_WRITE, NULL, &hroot, NULL);
+  if (result != ERROR_SUCCESS) {
+    trace ("Error opening/creating key %s.\n", self->base_path);
+    return FALSE;
+  }
+
+  RegistryWrite action = { self, hroot };
+  g_registry_backend_write_one (key_name, value, &action);
+  g_settings_backend_changed (backend, key_name, origin_tag);
+
+  RegCloseKey (hroot);
+
+  return TRUE;
+}
+
+static gboolean
+g_registry_backend_write_tree (GSettingsBackend *backend,
+                               GTree            *values,
+                               gpointer          origin_tag)
+{
+  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
+  LONG result;
+  HKEY hroot;
+
+  result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0,
+                            KEY_WRITE, NULL, &hroot, NULL);
+  if (result != ERROR_SUCCESS) {
+    trace ("Error opening/creating key %s.\n", self->base_path);
+    return FALSE;
+  }
+
+  RegistryWrite action = { self, hroot };
+  g_tree_foreach (values, (GTraverseFunc)g_registry_backend_write_one,
+                  &action);
+
+  g_settings_backend_changed_tree (backend, values, origin_tag);
+  RegCloseKey (hroot);
+
+  return TRUE;
+}
+
+static void
+g_registry_backend_reset (GSettingsBackend *backend,
+                          const gchar      *key_name,
+                          gpointer          origin_tag)
+{
+  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
+  gchar *path_name, *value_name = NULL;
+  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);
+
+  result = RegOpenKeyExA (HKEY_CURRENT_USER, path_name, 0, KEY_SET_VALUE, &hpath);
+  if (result != ERROR_SUCCESS)
+    {
+      g_message_win32_error (result, "Registry: resetting key '%s'", path_name);
+      g_free (path_name);
+      return;
+    }
+
+  result = RegDeleteValueA (hpath, value_name);
+  RegCloseKey (hpath);
+
+  if (result != ERROR_SUCCESS)
+    {
+      g_message_win32_error (result, "Registry: resetting key '%s'", path_name);
+      g_free (path_name);
+      return;
+    }
+
+  g_free (path_name);
+
+
+  g_settings_backend_changed (backend, key_name, origin_tag);
+}
+
+/* Not implemented and probably beyond the scope of this backend */
+static gboolean
+g_registry_backend_get_writable (GSettingsBackend *backend,
+                                 const gchar      *key_name)
+{
+  return TRUE;
+}
+
+static GPermission *
+g_registry_backend_get_permission (GSettingsBackend *backend,
+                                   const gchar      *key_name)
+{
+  return g_simple_permission_new (TRUE);
+}
+
+
+/********************************************************************************
+ * Spot-the-difference engine
+ ********************************************************************************/
+
+static void
+_free_watch (WatchThreadState *self,
+             gint              index,
+             GNode            *cache_node);
+
+static void
+registry_cache_item_reset_touched (GNode    *node,
+                                   gpointer  data)
+{
+  RegistryCacheItem *item = node->data;
+  item->touched = 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)
+    {
+      /* 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! */
+      gint i;
+      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);
+        }
+    }
+  _free_cache_item (node->data);
+  g_node_destroy (node);
+}
+
+static void
+registry_cache_remove_deleted (GNode    *node,
+                               gpointer  data)
+{
+  RegistryCacheItem *item = node->data;
+
+  if (!item->touched)
+    registry_cache_destroy_tree (node, data);
+}
+
+/* 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_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 (GRegistryBackend *self,
+                       HKEY              hpath,
+                       const gchar      *prefix,
+                       const gchar      *partial_key_name,
+                       GNode            *cache_node,
+                       int               n_watches, 
+                       GPtrArray        *changes)
+{
+  gchar  buffer[MAX_KEY_NAME_LENGTH + 1];
+  gchar *key_name;
+  gint   i;
+  LONG   result;
+
+  RegistryCacheItem *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 'touched' flag. When the registry traversal is done, any untouched nodes
+   * must have been deleted from the registry.
+   */
+  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
+                           registry_cache_item_reset_touched, NULL);
+
+  /* Recurse into each subpath at the current level, if any */
+  i = 0;
+  while (1)
+    {
+      DWORD buffer_size = MAX_KEY_NAME_LENGTH;
+      HKEY  hsubpath;
+
+      result = RegEnumKeyEx (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL);
+      if (result != ERROR_SUCCESS)
+        break;
+
+      result = RegOpenKeyEx (hpath, buffer, 0, KEY_READ, &hsubpath);
+      if (result == ERROR_SUCCESS)
+        {
+          GNode             *subkey_node;
+          RegistryCacheItem *child_item;
+
+          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);
+            }
+
+
+          registry_cache_update (self, hsubpath, prefix, buffer, subkey_node,
+                                 n_watches, changes);
+          child_item = subkey_node->data;
+          child_item->touched = TRUE;
+        }
+      RegCloseKey (hsubpath);
+    }
+
+  if (result != ERROR_NO_MORE_ITEMS)
+    g_message_win32_error (result, "gregistrybackend: error enumerating subkeys for cache.");
+
+  /* Enumerate each value at 'path' and check if it has changed */
+  i = 0;
+  while (1)
+    {
+      DWORD              buffer_size = MAX_KEY_NAME_LENGTH;
+      GNode             *cache_child_node;
+      RegistryCacheItem *child_item;
+      RegistryValue      value;
+      gboolean           changed = FALSE;
+
+      result = RegEnumValue (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL);
+      if (result != ERROR_SUCCESS)
+        break;
+
+      if (buffer[0]==0)
+        /* This is the key's 'default' value, for which we have no use. */
+        continue;
+
+      cache_child_node = registry_cache_find_immediate_child (cache_node, buffer);
+
+      if (!registry_read (hpath, key_name, buffer, &value))
+        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->touched = TRUE;
+      if (changed == TRUE && changes != NULL)
+        {
+          gchar *item;
+          if (partial_key_name == NULL)
+            item = g_strdup (buffer);
+          else
+            item = g_build_path ("/", partial_key_name, buffer, NULL);
+          g_ptr_array_add (changes, item);
+        }
+    }
+
+  if (result != ERROR_NO_MORE_ITEMS)
+    g_message_win32_error (result, "gregistrybackend: error enumerating values for cache");
+
+  /* Any nodes now left untouched must have been deleted, remove them from cache */
+  g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
+                           registry_cache_remove_deleted, self->watch);
+
+  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);
+}
+
+
+/* One of these is sent down the pipe when something happens in the registry. */
+typedef struct
+{
+  GRegistryBackend *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;
+
+/* This handler runs in the main thread to emit the changed signals */
+static gboolean
+watch_handler (RegistryEvent *event)
+{
+  gint i;
+  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);
+
+  for (i=0; i<event->items->len; i++)
+    g_free (g_ptr_array_index (event->items, i));
+  g_ptr_array_free (event->items, TRUE);
+
+  g_free (event->prefix);
+  g_object_unref (event->self);
+  
+  g_slice_free (RegistryEvent, event);
+  return FALSE;
+};
+
+
+static void
+_free_watch (WatchThreadState *self,
+             gint              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_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;
+          gint               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 %x, prefix %s, index %i\n",
+                 (guint)cache_node, self->message.watch.prefix, i);
+          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_free (self->message.watch.prefix);
+
+          g_atomic_int_inc (&self->watches_remaining);
+          break;
+        }
+
+      case WATCH_THREAD_STOP:
+        {
+          gint 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));
+
+          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;
+
+          /* 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.
+           */
+          gint 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_BACKEND (self->owner);
+          g_object_ref (self->owner);
+
+          event->items = g_ptr_array_new ();
+
+          EnterCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock);
+          registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath,
+                                 prefix, NULL, cache_node, 0, event->items);
+          LeaveCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock);
+
+          if (event->items->len > 0)
+            {
+              event->prefix = g_strdup (prefix);
+              g_idle_add ((GSourceFunc) watch_handler, event);
+            }
+          else
+            {
+              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;
+}
+
+static gboolean
+watch_start (GRegistryBackend *self)
+{
+  WatchThreadState *watch;
+
+  g_return_val_if_fail (self->watch == NULL, FALSE);
+
+  self->cache_lock = g_slice_new (CRITICAL_SECTION);
+  InitializeCriticalSection (self->cache_lock);
+
+  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 (0, "gregistrybackend: Failed to create sync objects.");
+      goto fail_1;
+    }
+
+  /* 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 (0, "gregistrybackend: Failed to create notify watch thread.");
+      goto fail_2;
+    }
+
+  self->watch = watch;
+
+  return TRUE;
+
+fail_2:
+  DeleteCriticalSection (self->cache_lock);
+  g_slice_free (CRITICAL_SECTION, self->cache_lock);
+  DeleteCriticalSection (watch->message_lock);
+  g_slice_free (CRITICAL_SECTION, watch->message_lock);
+  CloseHandle (watch->message_sent_event);
+  CloseHandle (watch->message_received_event);
+fail_1:
+  g_slice_free (WatchThreadState, watch);
+  return FALSE;
+}
+
+/* This function assumes you hold the message lock! */
+static void
+watch_stop_unlocked (GRegistryBackend *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 ("gregistrybackend: unable to stop watch thread.");
+      return;
+    }
+
+  LeaveCriticalSection (watch->message_lock);
+  DeleteCriticalSection (watch->message_lock);
+  DeleteCriticalSection (self->cache_lock);
+  g_slice_free (CRITICAL_SECTION, watch->message_lock);
+  g_slice_free (CRITICAL_SECTION, self->cache_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;
+};
+
+static gboolean
+watch_add_notify (GRegistryBackend *self,
+                  HANDLE            event,
+                  HKEY              hpath,
+                  gchar            *gsettings_prefix)
+{
+  WatchThreadState  *watch = self->watch;
+  GNode             *cache_node;
+  RegistryCacheItem *cache_item;
+  DWORD              result;
+
+  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);
+
+  g_return_val_if_fail (cache_node != NULL, FALSE);
+  g_return_val_if_fail (cache_node->data != NULL, 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);
+      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     = 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.
+   */
+  result = 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;
+};
+
+
+static void
+watch_remove_notify (GRegistryBackend *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\n", 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_backend_subscribe (GSettingsBackend *backend,
+                              const char       *key_name)
+{
+  GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
+  gchar *path_name, *value_name = NULL;
+  HKEY hpath;
+  HANDLE event;
+  LONG result;
+
+  if (self->watch == NULL)
+    if (!watch_start (self))
+      return;
+
+  if (g_atomic_int_dec_and_test (&self->watch->watches_remaining))
+    {
+      g_atomic_int_inc (&self->watch->watches_remaining);
+      g_warning ("subscribe() failed: only %i different paths may be watched.\n", 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\n", key_name);
+
+  trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch);
+
+
+  /* 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 = RegCreateKeyExA (HKEY_CURRENT_USER, path_name, 0, NULL, 0, KEY_READ, NULL, &hpath,
+                            NULL);
+  g_free (path_name);
+
+  if (result != ERROR_SUCCESS)
+    {
+      g_message_win32_error (result, "gregistrybackend: Unable to subscribe to key %s.", key_name);
+      g_atomic_int_inc (&self->watch->watches_remaining);
+      return;
+    }
+
+  event = CreateEvent (NULL, FALSE, FALSE, NULL);
+  if (event == NULL)
+    {
+      g_message_win32_error (result, "gregistrybackend: CreateEvent failed.\n");
+      g_atomic_int_inc (&self->watch->watches_remaining);
+      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, g_strdup (key_name)))
+    g_atomic_int_inc (&self->watch->watches_remaining);
+}
+
+static void
+g_registry_backend_unsubscribe (GSettingsBackend *backend,
+                                const char       *key_name)
+{
+  trace ("unsubscribe: %s.\n", key_name);
+
+  watch_remove_notify (G_REGISTRY_BACKEND (backend), key_name);
+}
+
+
+/********************************************************************************
+ * Object management junk
+ ********************************************************************************/
+
+GSettingsBackend *
+g_registry_backend_new (void) {
+  return g_object_new (G_TYPE_REGISTRY_BACKEND, NULL);
+}
+
+static void
+g_registry_backend_finalize (GObject *object)
+{
+  GRegistryBackend  *self = G_REGISTRY_BACKEND (object);
+  RegistryCacheItem *item;
+
+  item = self->cache_root->data;
+  g_warn_if_fail (item->ref_count == 1);
+
+  _free_cache_item (item);
+  g_node_destroy (self->cache_root);
+
+  if (self->watch != NULL)
+    {
+      EnterCriticalSection (self->watch->message_lock);
+      watch_stop_unlocked (self);
+    }
+
+  g_free (self->base_path);
+}
+
+static void
+g_registry_backend_class_init (GRegistryBackendClass *class)
+{
+  GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = g_registry_backend_finalize;
+
+  backend_class->read = g_registry_backend_read;
+  backend_class->write = g_registry_backend_write;
+  backend_class->write_tree = g_registry_backend_write_tree;
+  backend_class->reset = g_registry_backend_reset;
+  backend_class->get_writable = g_registry_backend_get_writable;
+  backend_class->get_permission = g_registry_backend_get_permission;
+  backend_class->subscribe = g_registry_backend_subscribe;
+  backend_class->unsubscribe = g_registry_backend_unsubscribe;
+}
+
+static void
+g_registry_backend_init (GRegistryBackend *self)
+{
+  self->base_path = g_strdup_printf ("Software\\GSettings");
+
+  RegistryCacheItem *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->watch = NULL;
+}
diff --git a/gio/gregistrysettingsbackend.h b/gio/gregistrysettingsbackend.h
new file mode 100644
index 0000000..cd7bd7c
--- /dev/null
+++ b/gio/gregistrysettingsbackend.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2009-10 Sam Thursfield
+ *
+ * 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 of the licence, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Sam Thursfield <ssssam gmail com>
+ */
+
+#ifndef __G_REGISTRY_SETTINGS_BACKEND_H__
+#define __G_REGISTRY_SETTINGS_BACKEND_H__
+
+#include <glib-object.h>
+
+G_GNUC_INTERNAL
+GType g_registry_backend_get_type (void);
+
+
+#endif /* __G_REGISTRY_SETTINGS_BACKEND_H__ */



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