| /* | |
| * 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; | |
| 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); | |
| 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_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; | |
| } | |
| 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 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; | |
| } | |
| static gboolean | |
| watch_add_notify (GRegistrySettingsBackend *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 (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; | |
| 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 (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); | |
| 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); | |
| 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_settings_backend_unsubscribe (GSettingsBackend *backend, | |
| const char *key_name) | |
| { | |
| trace ("unsubscribe: %s.\n", key_name); | |
| watch_remove_notify (G_REGISTRY_SETTINGS_BACKEND (backend), key_name); | |
| } | |
| /******************************************************************************** | |
| * 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->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; | |
| } | |
| /** | |
| * 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)); | |
| } |