mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
97c28f7fe1
Fix various warnings regarding unused variables, duplicated branches etc by adjusting the ifdeffery and some missing casts. gnulib triggers -Wduplicated-branches in one of the copied files, disable as that just makes updating the code harder. The warning indicating missing features are made none fatal through pragmas. They still show but don't abort the build. https://bugzilla.gnome.org/show_bug.cgi?id=793729
2167 lines
65 KiB
C
2167 lines
65 KiB
C
/*
|
|
* Copyright © 2009-10 Sam Thursfield
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.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>
|
|
*/
|
|
|
|
/* GRegistryBackend implementation notes:
|
|
*
|
|
* - All settings are stored under the path:
|
|
* HKEY_CURRENT_USER\Software\GSettings\
|
|
* This means all settings are per-user. Permissions and system-wide
|
|
* defaults are not implemented and will probably always be out of scope of
|
|
* the Windows port of GLib.
|
|
*
|
|
* - The registry type system is limited. Most GVariant types are stored as
|
|
* literals via g_variant_print/parse(). Strings are stored without the
|
|
* quotes that GVariant requires. Integer types are stored as native
|
|
* REG_DWORD or REG_QWORD. The REG_MULTI_SZ (string array) type could be
|
|
* used to avoid flattening container types.
|
|
*
|
|
* - Notifications are handled; the change event is watched for in a separate
|
|
* thread (Windows does not provide a callback API) which sends them with
|
|
* g_idle_add to the GLib main loop. The threading is done using Windows
|
|
* API functions, so there is no dependence on GThread.
|
|
*
|
|
* - Windows doesn't tell us which value has changed. This means we have to
|
|
* maintain a cache of every stored value so we can play spot the
|
|
* difference. This should not be a performance issue because if you are
|
|
* storing thousands of values in GSettings, you are probably using it
|
|
* wrong.
|
|
*
|
|
* - The cache stores the value as a registry type. Because many variants are
|
|
* stored as string representations, values which have changed equality but
|
|
* not equivalence may trigger spurious change notifications. GSettings
|
|
* users must already deal with this possibility and converting all data to
|
|
* GVariant values would be more effort.
|
|
*
|
|
* - Because we have to cache every registry value locally, reads are done
|
|
* from the cache rather than directly from the registry. Writes update
|
|
* both. This means that the backend will not work if the watch thread is
|
|
* not running. A GSettings object always subscribes to changes so we can
|
|
* be sure that the watch thread will be running, but if for some reason
|
|
* the backend is being used directly you should bear that in mind.
|
|
*
|
|
* - The registry is totally user-editable, so we are very forgiving about
|
|
* errors in the data we get.
|
|
*
|
|
* - The registry uses backslashes as path separators. GSettings keys only
|
|
* allow [A-Za-z\-] so no escaping is needed. No attempt is made to solve
|
|
* clashes between keys differing only in case.
|
|
*
|
|
* - 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 "giomodule.h"
|
|
|
|
#include <windows.h>
|
|
|
|
//#define TRACE
|
|
|
|
/* GSettings' limit */
|
|
#define MAX_KEY_NAME_LENGTH 32
|
|
|
|
/* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with
|
|
* "The parameter is incorrect" after 64 watches. We need one for the
|
|
* message_sent cond, which is allowed for in the way the watches_remaining
|
|
* variable is used.
|
|
*/
|
|
#define MAX_WATCHES 64
|
|
|
|
/* A watch on one registry path and its subkeys */
|
|
typedef struct
|
|
{
|
|
HANDLE event;
|
|
HKEY hpath;
|
|
char *prefix;
|
|
GNode *cache_node;
|
|
} RegistryWatch;
|
|
|
|
/* Simple message passing for the watch thread. Not enough traffic to
|
|
* justify a queue.
|
|
*/
|
|
typedef enum
|
|
{
|
|
WATCH_THREAD_NONE,
|
|
WATCH_THREAD_ADD_WATCH,
|
|
WATCH_THREAD_REMOVE_WATCH,
|
|
WATCH_THREAD_STOP
|
|
} WatchThreadMessageType;
|
|
|
|
typedef struct
|
|
{
|
|
WatchThreadMessageType type;
|
|
RegistryWatch watch;
|
|
} WatchThreadMessage;
|
|
|
|
typedef struct
|
|
{
|
|
GSettingsBackend *owner;
|
|
HANDLE *thread;
|
|
|
|
/* Details of the things we are watching. */
|
|
int watches_remaining;
|
|
GPtrArray *events, *handles, *prefixes, *cache_nodes;
|
|
|
|
/* Communication with the main thread. Only one message is stored at a time,
|
|
* to make sure that messages are acknowledged before being overwritten we
|
|
* create two events - one is signalled when a new message is set, the
|
|
* other is signalled by the thread when it has processed the message.
|
|
*/
|
|
WatchThreadMessage message;
|
|
CRITICAL_SECTION *message_lock;
|
|
HANDLE message_sent_event, message_received_event;
|
|
} WatchThreadState;
|
|
|
|
#define G_TYPE_REGISTRY_BACKEND (g_registry_backend_get_type ())
|
|
#define G_REGISTRY_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
|
G_TYPE_REGISTRY_BACKEND, GRegistryBackend))
|
|
#define G_IS_REGISTRY_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
|
G_TYPE_REGISTRY_BACKEND))
|
|
|
|
typedef GSettingsBackendClass GRegistryBackendClass;
|
|
|
|
typedef struct {
|
|
GSettingsBackend parent_instance;
|
|
|
|
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;
|
|
|
|
WatchThreadState *watch;
|
|
} GRegistryBackend;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GRegistryBackend,
|
|
g_registry_backend,
|
|
G_TYPE_SETTINGS_BACKEND,
|
|
g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
|
|
g_define_type_id, "registry", 90))
|
|
|
|
/**********************************************************************************
|
|
* Utility functions
|
|
**********************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
static void
|
|
trace (const char *format,
|
|
...)
|
|
{
|
|
#ifdef TRACE
|
|
va_list va; va_start (va, format);
|
|
vprintf (format, va);
|
|
fflush (stdout);
|
|
va_end (va);
|
|
#endif
|
|
}
|
|
|
|
/* g_message including a windows error message. It is not useful to have an
|
|
* equivalent function for g_warning because none of the registry errors can
|
|
* result from programmer error (Microsoft programmers don't count), instead
|
|
* they will mostly occur from people messing with the registry by hand. */
|
|
static void
|
|
g_message_win32_error (DWORD result_code,
|
|
const gchar *format,
|
|
...)
|
|
{
|
|
va_list va;
|
|
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: %s.\n",
|
|
path_name, value_name);
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Cache of registry values
|
|
***************************************************************************/
|
|
|
|
/* Generic container for registry values */
|
|
typedef struct {
|
|
DWORD type;
|
|
|
|
union {
|
|
gint dword; /* FIXME: could inline QWORD on 64-bit systems too */
|
|
void *ptr;
|
|
};
|
|
} RegistryValue;
|
|
|
|
static char *
|
|
registry_value_dump (RegistryValue value)
|
|
{
|
|
if (value.type == REG_DWORD)
|
|
return g_strdup_printf ("%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;
|
|
|
|
gint32 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 ("\treg cache: adding %s to %s\n",
|
|
name, ((RegistryCacheItem *)parent->data)->name);
|
|
|
|
cache_node = g_node_new (item);
|
|
g_node_append (parent, cache_node);
|
|
|
|
return cache_node;
|
|
}
|
|
|
|
/* The reference counting of cache tree nodes works like this: when a node is
|
|
* subscribed to (GSettings wants us to watch that path and everything below
|
|
* it) the reference count of that node and everything below is increased, as
|
|
* well as each parent up to the root.
|
|
*/
|
|
|
|
static void
|
|
_ref_down (GNode *node)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
g_node_children_foreach (node, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)_ref_down, NULL);
|
|
item->ref_count++;
|
|
}
|
|
|
|
static void
|
|
registry_cache_ref_tree (GNode *tree)
|
|
{
|
|
RegistryCacheItem *item = tree->data;
|
|
GNode *node = tree->parent;
|
|
|
|
g_return_if_fail (tree != NULL);
|
|
|
|
item->ref_count++;
|
|
|
|
g_node_children_foreach (tree, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)_ref_down, NULL);
|
|
|
|
for (node = tree->parent; node; node = node->parent)
|
|
{
|
|
item = node->data;
|
|
item->ref_count++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
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 ("gregistrybackend: registry_cache_update_node: Unhandled value type");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Blocking notifications is a useful optimisation. When a change is made
|
|
* through GSettings we update the cache manually, but a notifcation is
|
|
* triggered as well. This function is also used for nested notifications,
|
|
* eg. if /test and /test/foo are watched, and /test/foo/value is changed then
|
|
* we will get notified both for /test/foo and /test and it is helpful to block
|
|
* the second.
|
|
*/
|
|
static void
|
|
registry_cache_block_notification (GNode *node)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
g_return_if_fail (node != NULL);
|
|
|
|
if (item->subscription_count > 0)
|
|
item->block_count++;
|
|
|
|
if (node->parent != NULL)
|
|
registry_cache_block_notification (node->parent);
|
|
}
|
|
|
|
static void registry_cache_destroy_tree (GNode *node,
|
|
WatchThreadState *self);
|
|
|
|
/***************************************************************************
|
|
* Reading and writing
|
|
***************************************************************************/
|
|
|
|
static gboolean
|
|
registry_read (HKEY hpath,
|
|
const gchar *path_name,
|
|
const gchar *value_name,
|
|
RegistryValue *p_value)
|
|
{
|
|
LONG result;
|
|
DWORD value_data_size;
|
|
gpointer *buffer;
|
|
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_backend_read (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
const GVariantType *expected_type,
|
|
gboolean default_value)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
GNode *cache_node;
|
|
RegistryValue registry_value;
|
|
GVariant *gsettings_value = NULL;
|
|
gchar *gsettings_type;
|
|
|
|
g_return_val_if_fail (expected_type != NULL, NULL);
|
|
|
|
if (default_value)
|
|
return NULL;
|
|
|
|
/* Simply read from the cache, which is updated from the registry by the
|
|
* watch thread as soon as changes can propagate. Any changes not yet in the
|
|
* cache will have the 'changed' signal emitted after this function returns.
|
|
*/
|
|
EnterCriticalSection (self->cache_lock);
|
|
cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
|
|
trace ("Reading key %s, cache node %x\n", key_name, cache_node);
|
|
|
|
/* Maybe it's not set, we can return to default */
|
|
if (cache_node == NULL)
|
|
return NULL;
|
|
|
|
trace ("\t- cached value %s\n", registry_value_dump (((RegistryCacheItem *)cache_node->data)->value));
|
|
|
|
registry_value = ((RegistryCacheItem *)cache_node->data)->value;
|
|
|
|
gsettings_type = g_variant_type_dup_string (expected_type);
|
|
|
|
/* The registry is user-editable, so we need to be fault-tolerant here. */
|
|
switch (gsettings_type[0])
|
|
{
|
|
case 'b':
|
|
case 'y':
|
|
case 'n':
|
|
case 'q':
|
|
case 'i':
|
|
case 'u':
|
|
if (registry_value.type == REG_DWORD)
|
|
gsettings_value = g_variant_new (gsettings_type, registry_value.dword);
|
|
break;
|
|
|
|
case 't':
|
|
case 'x':
|
|
if (registry_value.type == REG_QWORD)
|
|
{
|
|
DWORDLONG qword_value = *(DWORDLONG *)registry_value.ptr;
|
|
gsettings_value = g_variant_new (gsettings_type, qword_value);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (registry_value.type == REG_SZ)
|
|
{
|
|
if (gsettings_type[0] == 's')
|
|
gsettings_value = g_variant_new_string ((char *)registry_value.ptr);
|
|
else
|
|
{
|
|
GError *error = NULL;
|
|
|
|
gsettings_value = g_variant_parse (expected_type, registry_value.ptr,
|
|
NULL, NULL, &error);
|
|
|
|
if (error != NULL)
|
|
g_message ("gregistrysettingsbackend: error parsing key %s: %s",
|
|
key_name, error->message);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_free (gsettings_type);
|
|
|
|
return gsettings_value;
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GRegistryBackend *self;
|
|
HKEY hroot;
|
|
} RegistryWrite;
|
|
|
|
static gboolean
|
|
g_registry_backend_write_one (const char *key_name,
|
|
GVariant *variant,
|
|
gpointer user_data)
|
|
{
|
|
GRegistryBackend *self;
|
|
RegistryWrite *action;
|
|
RegistryValue value;
|
|
HKEY hroot;
|
|
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_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, "gregistrybackend: 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, "gregistrybackend: setting value %s\\%s\\%s failed.\n",
|
|
self->base_path, path_name, value_name);
|
|
|
|
/* If the write fails then it will seem like the value has changed until the
|
|
* next execution (because we wrote to the cache first). There's no reason
|
|
* for it to fail unless something is weirdly broken, however.
|
|
*/
|
|
|
|
RegCloseKey (hpath);
|
|
g_free (path_name);
|
|
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_backend_write (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
GVariant *value,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
LONG result;
|
|
HKEY hroot;
|
|
RegistryWrite action;
|
|
|
|
result = RegCreateKeyExW (HKEY_CURRENT_USER, self->base_pathw, 0, NULL, 0,
|
|
KEY_WRITE, NULL, &hroot, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
trace ("Error opening/creating key %s.\n", self->base_path);
|
|
return FALSE;
|
|
}
|
|
|
|
action.self = self;
|
|
action.hroot = hroot;
|
|
g_registry_backend_write_one (key_name, value, &action);
|
|
g_settings_backend_changed (backend, key_name, origin_tag);
|
|
|
|
RegCloseKey (hroot);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
g_registry_backend_write_tree (GSettingsBackend *backend,
|
|
GTree *values,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
LONG result;
|
|
HKEY hroot;
|
|
RegistryWrite action;
|
|
|
|
result = RegCreateKeyExW (HKEY_CURRENT_USER, self->base_pathw, 0, NULL, 0,
|
|
KEY_WRITE, NULL, &hroot, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
trace ("Error opening/creating key %s.\n", self->base_path);
|
|
return FALSE;
|
|
}
|
|
|
|
action.self = self;
|
|
action.hroot = hroot;
|
|
g_tree_foreach (values, (GTraverseFunc)g_registry_backend_write_one,
|
|
&action);
|
|
|
|
g_settings_backend_changed_tree (backend, values, origin_tag);
|
|
RegCloseKey (hroot);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
g_registry_backend_reset (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
gchar *path_name;
|
|
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 (HKEY_CURRENT_USER, path_namew, 0, KEY_SET_VALUE, &hpath);
|
|
g_free (path_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "Registry: resetting key '%s'", 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'", path_name);
|
|
g_free (path_name);
|
|
return;
|
|
}
|
|
|
|
g_free (path_name);
|
|
|
|
g_settings_backend_changed (backend, key_name, origin_tag);
|
|
}
|
|
|
|
static gboolean
|
|
g_registry_backend_get_writable (GSettingsBackend *backend,
|
|
const gchar *key_name)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name;
|
|
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 (HKEY_CURRENT_USER, 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.\n",
|
|
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
|
|
{
|
|
GRegistryBackend *self;
|
|
gchar *prefix; /* prefix is a gsettings path, all items are subkeys of this. */
|
|
GPtrArray *items; /* each item is a subkey below prefix that has changed. */
|
|
} RegistryEvent;
|
|
|
|
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_backend functions, but this is okay because they only
|
|
* access self->base which is constant.
|
|
*
|
|
* When looking at this code bear in mind the terminology: in the registry, keys
|
|
* are containers that contain values, and other keys. Keys have a 'default'
|
|
* value which we always ignore.
|
|
*
|
|
* n_parent_watches: a counter used to set the reference count of any new nodes
|
|
* that are created - they should have as many references as
|
|
* there are notifications that are watching them.
|
|
*/
|
|
static void
|
|
registry_cache_update (GRegistryBackend *self,
|
|
HKEY hpath,
|
|
const gchar *prefix,
|
|
const gchar *partial_key_name,
|
|
GNode *cache_node,
|
|
int n_watches,
|
|
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, "gregistrybackend: 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;
|
|
|
|
if (partial_key_name == NULL)
|
|
item = g_strdup (buffer);
|
|
else
|
|
item = g_build_path ("/", partial_key_name, buffer, NULL);
|
|
|
|
g_ptr_array_add (event->items, item);
|
|
}
|
|
|
|
g_free (buffer);
|
|
}
|
|
|
|
if (result != ERROR_NO_MORE_ITEMS)
|
|
g_message_win32_error (result, "gregistrybackend: 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_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);
|
|
|
|
if (cache_node != NULL)
|
|
{
|
|
cache_item = cache_node->data;
|
|
|
|
/* There may be more than one GSettings object subscribed to this
|
|
* path, only free the watch when the last one unsubscribes.
|
|
*/
|
|
cache_item->subscription_count--;
|
|
if (cache_item->subscription_count > 0)
|
|
break;
|
|
}
|
|
|
|
_free_watch (self, i, cache_node);
|
|
g_free (self->message.watch.prefix);
|
|
|
|
g_atomic_int_inc (&self->watches_remaining);
|
|
break;
|
|
}
|
|
|
|
case WATCH_THREAD_STOP:
|
|
{
|
|
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));
|
|
|
|
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_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_BACKEND (self->owner)->cache_lock);
|
|
registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath,
|
|
prefix, NULL, cache_node, 0, event);
|
|
LeaveCriticalSection (G_REGISTRY_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;
|
|
}
|
|
|
|
static gboolean
|
|
watch_start (GRegistryBackend *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 (), "gregistrybackend: 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 (), "gregistrybackend: 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 message lock! */
|
|
static void
|
|
watch_stop_unlocked (GRegistryBackend *self)
|
|
{
|
|
WatchThreadState *watch = self->watch;
|
|
DWORD result;
|
|
|
|
g_return_if_fail (watch != NULL);
|
|
|
|
watch->message.type = WATCH_THREAD_STOP;
|
|
SetEvent (watch->message_sent_event);
|
|
|
|
/* This is signalled as soon as the message is received. We must not return
|
|
* while the watch thread is still firing off callbacks. Freeing all of the
|
|
* memory is done in the watch thread after this is signalled.
|
|
*/
|
|
result = WaitForSingleObject (watch->message_received_event, INFINITE);
|
|
if (result != WAIT_OBJECT_0)
|
|
{
|
|
g_warning ("gregistrybackend: unable to stop watch thread.");
|
|
return;
|
|
}
|
|
|
|
LeaveCriticalSection (watch->message_lock);
|
|
DeleteCriticalSection (watch->message_lock);
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
watch_add_notify (GRegistryBackend *self,
|
|
HANDLE event,
|
|
HKEY hpath,
|
|
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 = 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;
|
|
}
|
|
|
|
static void
|
|
watch_remove_notify (GRegistryBackend *self,
|
|
const gchar *key_name)
|
|
{
|
|
WatchThreadState *watch = self->watch;
|
|
LONG result;
|
|
|
|
if (self->watch == NULL)
|
|
/* Here we assume that the unsubscribe message is for somewhere that was
|
|
* deleted, and so it has already been removed and the watch thread has
|
|
* stopped.
|
|
*/
|
|
return;
|
|
|
|
EnterCriticalSection (watch->message_lock);
|
|
watch->message.type = WATCH_THREAD_REMOVE_WATCH;
|
|
watch->message.watch.prefix = g_strdup (key_name);
|
|
|
|
SetEvent (watch->message_sent_event);
|
|
|
|
/* Wait for the received event in return, to avoid sending another message before the first
|
|
* one was received.
|
|
*/
|
|
result = WaitForSingleObject (watch->message_received_event, INFINITE);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
g_warning ("unsubscribe from %s: message not acknowledged", key_name);
|
|
|
|
if (g_atomic_int_get (&watch->watches_remaining) >= MAX_WATCHES)
|
|
/* Stop it before any new ones can get added and confuse things */
|
|
watch_stop_unlocked (self);
|
|
else
|
|
LeaveCriticalSection (watch->message_lock);
|
|
}
|
|
|
|
/* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that
|
|
* key. Our job is easier because keys and values are separate.
|
|
*/
|
|
static void
|
|
g_registry_backend_subscribe (GSettingsBackend *backend,
|
|
const char *key_name)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (backend);
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name = NULL;
|
|
HKEY hpath;
|
|
HANDLE event;
|
|
LONG result;
|
|
|
|
if (self->watch == NULL && !watch_start (self))
|
|
return;
|
|
|
|
if (g_atomic_int_dec_and_test (&self->watch->watches_remaining))
|
|
{
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
g_warning ("subscribe() failed: only %i different paths may be watched.", 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 (HKEY_CURRENT_USER, path_namew, 0, NULL, 0, KEY_READ, NULL, &hpath,
|
|
NULL);
|
|
g_free (path_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "gregistrybackend: Unable to subscribe to key %s.", key_name);
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
return;
|
|
}
|
|
|
|
event = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
if (event == NULL)
|
|
{
|
|
g_message_win32_error (result, "gregistrybackend: CreateEvent failed.");
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
RegCloseKey (hpath);
|
|
return;
|
|
}
|
|
|
|
/* The actual watch is added by the thread, which has to re-subscribe each time it
|
|
* receives a change. */
|
|
if (!watch_add_notify (self, event, hpath, g_strdup (key_name)))
|
|
{
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
RegCloseKey (hpath);
|
|
CloseHandle (event);
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_registry_backend_unsubscribe (GSettingsBackend *backend,
|
|
const char *key_name)
|
|
{
|
|
trace ("unsubscribe: %s.\n", key_name);
|
|
|
|
watch_remove_notify (G_REGISTRY_BACKEND (backend), key_name);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Object management junk
|
|
********************************************************************************/
|
|
|
|
static void
|
|
g_registry_backend_finalize (GObject *object)
|
|
{
|
|
GRegistryBackend *self = G_REGISTRY_BACKEND (object);
|
|
RegistryCacheItem *item;
|
|
|
|
item = self->cache_root->data;
|
|
g_warn_if_fail (item->ref_count == 1);
|
|
|
|
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->cache_lock);
|
|
g_slice_free (CRITICAL_SECTION, self->cache_lock);
|
|
|
|
g_free (self->base_path);
|
|
g_free (self->base_pathw);
|
|
}
|
|
|
|
static void
|
|
g_registry_backend_class_init (GRegistryBackendClass *class)
|
|
{
|
|
GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->finalize = g_registry_backend_finalize;
|
|
|
|
backend_class->read = g_registry_backend_read;
|
|
backend_class->write = g_registry_backend_write;
|
|
backend_class->write_tree = g_registry_backend_write_tree;
|
|
backend_class->reset = g_registry_backend_reset;
|
|
backend_class->get_writable = g_registry_backend_get_writable;
|
|
backend_class->subscribe = g_registry_backend_subscribe;
|
|
backend_class->unsubscribe = g_registry_backend_unsubscribe;
|
|
}
|
|
|
|
static void
|
|
g_registry_backend_init (GRegistryBackend *self)
|
|
{
|
|
RegistryCacheItem *item;
|
|
|
|
self->base_path = g_strdup_printf ("Software\\GSettings");
|
|
self->base_pathw = g_utf8_to_utf16 (self->base_path, -1, NULL, NULL, NULL);
|
|
|
|
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;
|
|
}
|