mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 16:32:18 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2395 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2395 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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));
 | |
| }
 |