| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright © 2009-10 Sam Thursfield | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This library is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU Lesser General Public | 
					
						
							|  |  |  |  * License as published by the Free Software Foundation; either | 
					
						
							|  |  |  |  * version 2 of the licence, or (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
					
						
							|  |  |  |  * Lesser General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU Lesser General Public | 
					
						
							|  |  |  |  * License along with this library; if not, write to the | 
					
						
							|  |  |  |  * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | 
					
						
							|  |  |  |  * Boston, MA 02111-1307, USA. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Author: Sam Thursfield <ssssam@gmail.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* GRegistryBackend implementation notes:
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - All settings are stored under the path: | 
					
						
							|  |  |  |  *       HKEY_CURRENT_USER\Software\GSettings\ | 
					
						
							|  |  |  |  *     This means all settings are per-user. Permissions and system-wide | 
					
						
							|  |  |  |  *     defaults are not implemented and will probably always be out of scope of | 
					
						
							|  |  |  |  *     the Windows port of GLib. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - The registry type system is limited. Most GVariant types are stored as | 
					
						
							|  |  |  |  *     literals via g_variant_print/parse(). Strings are stored without the | 
					
						
							|  |  |  |  *     quotes that GVariant requires. Integer types are stored as native | 
					
						
							|  |  |  |  *     REG_DWORD or REG_QWORD. The REG_MULTI_SZ (string array) type could be | 
					
						
							|  |  |  |  *     used to avoid flattening container types. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - Notifications are handled; the change event is watched for in a separate | 
					
						
							|  |  |  |  *     thread (Windows does not provide a callback API) which sends them with | 
					
						
							|  |  |  |  *     g_idle_add to the GLib main loop. The threading is done using Windows | 
					
						
							|  |  |  |  *     API functions, so there is no dependence on GThread. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - Windows doesn't tell us which value has changed. This means we have to | 
					
						
							|  |  |  |  *     maintain a cache of every stored value so we can play spot the | 
					
						
							|  |  |  |  *     difference. This should not be a performance issue because if you are | 
					
						
							|  |  |  |  *     storing thousands of values in GSettings, you are probably using it | 
					
						
							|  |  |  |  *     wrong. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - The cache stores the value as a registry type. Because many variants are | 
					
						
							|  |  |  |  *     stored as string representations, values which have changed equality but | 
					
						
							|  |  |  |  *     not equivalence may trigger spurious change notifications. GSettings | 
					
						
							|  |  |  |  *     users must already deal with this possibility and converting all data to | 
					
						
							|  |  |  |  *     GVariant values would be more effort. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - Because we have to cache every registry value locally, reads are done | 
					
						
							|  |  |  |  *     from the cache rather than directly from the registry. Writes update | 
					
						
							|  |  |  |  *     both. This means that the backend will not work if the watch thread is | 
					
						
							|  |  |  |  *     not running. A GSettings object always subscribes to changes so we can | 
					
						
							|  |  |  |  *     be sure that the watch thread will be running, but if for some reason | 
					
						
							|  |  |  |  *     the backend is being used directly you should bear that in mind. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - The registry is totally user-editable, so we are very forgiving about | 
					
						
							|  |  |  |  *     errors in the data we get. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - The registry uses backslashes as path separators. GSettings keys only | 
					
						
							|  |  |  |  *     allow [A-Za-z\-] so no escaping is needed. No attempt is made to solve | 
					
						
							|  |  |  |  *     clashes between keys differing only in case. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - RegCreateKeyA is used - Windows can also handle UTF16LE strings. | 
					
						
							|  |  |  |  *     GSettings doesn't pay any attention to encoding, so by using ANSI we | 
					
						
							|  |  |  |  *     hopefully avoid passing any invalid Unicode. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - The Windows registry has the following limitations: a key may not exceed | 
					
						
							|  |  |  |  *     255 characters, an entry's value may not exceed 16,383 characters, and | 
					
						
							|  |  |  |  *     all the values of a key may not exceed 65,535 characters. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - Terminology: | 
					
						
							|  |  |  |  *     * in GSettings, a 'key' is eg. /desktop/gnome/background/primary-color | 
					
						
							|  |  |  |  *     * in the registry, the 'key' is path, which contains some 'values'. | 
					
						
							|  |  |  |  *     * in this file, any GSettings key is a 'key', while a registry key is | 
					
						
							|  |  |  |  *       termed a 'path', which contains 'values'. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - My set of tests for this backend are currently at: | 
					
						
							|  |  |  |  *       http://gitorious.org/gsettings-gtk/gsettings-test.git
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - There is an undocumented function in ntdll.dll which might be more | 
					
						
							|  |  |  |  *     than RegNotifyChangeKeyValue(), NtNotifyChangeKey: | 
					
						
							|  |  |  |  *       http://source.winehq.org/source/dlls/ntdll/reg.c#L618
 | 
					
						
							|  |  |  |  *       http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Key/NtNotifyChangeKey.html
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   - If updating the cache ever becomes a performance issue it may make sense | 
					
						
							|  |  |  |  *     to use a red-black tree, but I don't currently think it's worth the time | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "config.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "gregistrysettingsbackend.h"
 | 
					
						
							|  |  |  | #include "gsimplepermission.h"
 | 
					
						
							|  |  |  | #include "gsettingsbackend.h"
 | 
					
						
							|  |  |  | #include "giomodule.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define _WIN32_WINNT 0x0500
 | 
					
						
							|  |  |  | #define WIN32_LEAN_AND_MEAN
 | 
					
						
							|  |  |  | #include <windows.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //#define TRACE
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* GSettings' limit */ | 
					
						
							|  |  |  | #define MAX_KEY_NAME_LENGTH   32
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with
 | 
					
						
							|  |  |  |  * "The parameter is incorrect" after 64 watches. We need one for the | 
					
						
							|  |  |  |  * message_sent cond, which is allowed for in the way the watches_remaining | 
					
						
							|  |  |  |  * variable is used. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #define MAX_WATCHES   64
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* A watch on one registry path and its subkeys */ | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   HANDLE event; | 
					
						
							|  |  |  |   HKEY   hpath; | 
					
						
							|  |  |  |   char  *prefix; | 
					
						
							|  |  |  |   GNode *cache_node; | 
					
						
							|  |  |  | } RegistryWatch; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Simple message passing for the watch thread. Not enough traffic to
 | 
					
						
							|  |  |  |  * justify a queue. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | typedef enum | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WATCH_THREAD_NONE, | 
					
						
							|  |  |  |   WATCH_THREAD_ADD_WATCH, | 
					
						
							|  |  |  |   WATCH_THREAD_REMOVE_WATCH, | 
					
						
							|  |  |  |   WATCH_THREAD_STOP | 
					
						
							|  |  |  | } WatchThreadMessageType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadMessageType type; | 
					
						
							|  |  |  |   RegistryWatch watch; | 
					
						
							|  |  |  | } WatchThreadMessage; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GSettingsBackend *owner; | 
					
						
							|  |  |  |   HANDLE           *thread; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Details of the things we are watching. */ | 
					
						
							|  |  |  |   int watches_remaining; | 
					
						
							|  |  |  |   GPtrArray *events, *handles, *prefixes, *cache_nodes; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Communication with the main thread. Only one message is stored at a time,
 | 
					
						
							|  |  |  |    * to make sure that messages are acknowledged before being overwritten we | 
					
						
							|  |  |  |    * create two events - one is signalled when a new message is set, the | 
					
						
							|  |  |  |    * other is signalled by the thread when it has processed the message. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   WatchThreadMessage message; | 
					
						
							|  |  |  |   CRITICAL_SECTION *message_lock; | 
					
						
							|  |  |  |   HANDLE message_sent_event, message_received_event; | 
					
						
							|  |  |  | } WatchThreadState; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define G_TYPE_REGISTRY_BACKEND      (g_registry_backend_get_type ())
 | 
					
						
							|  |  |  | #define G_REGISTRY_BACKEND(inst)     (G_TYPE_CHECK_INSTANCE_CAST ((inst),         \
 | 
					
						
							|  |  |  |                                       G_TYPE_REGISTRY_BACKEND, GRegistryBackend)) | 
					
						
							|  |  |  | #define G_IS_REGISTRY_BACKEND(inst)  (G_TYPE_CHECK_INSTANCE_TYPE ((inst),         \
 | 
					
						
							|  |  |  |                                       G_TYPE_REGISTRY_BACKEND)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef GSettingsBackendClass GRegistryBackendClass; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |   GSettingsBackend  parent_instance; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   char             *base_path; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* A stored copy of the whole tree being watched. When we receive a change notification
 | 
					
						
							|  |  |  |    * we have to check against this to see what has changed ... every time ...*/ | 
					
						
							|  |  |  |   CRITICAL_SECTION *cache_lock; | 
					
						
							|  |  |  |   GNode            *cache_root; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   WatchThreadState *watch; | 
					
						
							|  |  |  | } GRegistryBackend; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | G_DEFINE_TYPE_WITH_CODE (GRegistryBackend, | 
					
						
							|  |  |  |                          g_registry_backend, | 
					
						
							|  |  |  |                          G_TYPE_SETTINGS_BACKEND, | 
					
						
							|  |  |  |                          g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, | 
					
						
							|  |  |  |                                                          g_define_type_id, "registry", 90)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**********************************************************************************
 | 
					
						
							|  |  |  |  * Utility functions | 
					
						
							|  |  |  |  **********************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | trace (const char *format, ...) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   #ifdef TRACE
 | 
					
						
							|  |  |  |   va_list va; va_start (va, format); | 
					
						
							|  |  |  |   vprintf (format, va); fflush (stdout); | 
					
						
							|  |  |  |   va_end (va); | 
					
						
							|  |  |  |   #endif
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* g_message including a windows error message. It is not useful to have an
 | 
					
						
							|  |  |  |  * equivalent function for g_warning because none of the registry errors can | 
					
						
							|  |  |  |  * result from programmer error (Microsoft programmers don't count), instead | 
					
						
							|  |  |  |  * they will mostly occur from people messing with the registry by hand. */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_message_win32_error (DWORD result_code, | 
					
						
							|  |  |  |                        const gchar *format, | 
					
						
							|  |  |  |                       ...) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   va_list va; | 
					
						
							|  |  |  |   gint pos; | 
					
						
							|  |  |  |   gchar win32_message[1024]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result_code == 0) | 
					
						
							|  |  |  |     result_code = GetLastError (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   va_start (va, format); | 
					
						
							|  |  |  |   pos = g_vsnprintf (win32_message, 512, format, va); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   win32_message[pos++] = ':'; win32_message[pos++] = ' '; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result_code, 0, (LPTSTR)(win32_message+pos), | 
					
						
							|  |  |  |                 1023 - pos, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result_code == ERROR_KEY_DELETED) | 
					
						
							|  |  |  |     trace ("(%s)", win32_message); | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     g_message (win32_message); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Make gsettings key into a registry path & value pair. 
 | 
					
						
							|  |  |  |  *  | 
					
						
							|  |  |  |  * Note that the return value *only* needs freeing - registry_value_name | 
					
						
							|  |  |  |  * is a pointer to further inside the same block of memory. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static gchar * | 
					
						
							|  |  |  | parse_key (const gchar  *key_name, | 
					
						
							|  |  |  |            const gchar  *registry_prefix, | 
					
						
							|  |  |  |            gchar       **value_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   gchar *path_name, *c; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* All key paths are treated as absolute; gsettings doesn't seem to enforce a
 | 
					
						
							| 
									
										
										
										
											2011-08-29 14:49:32 -04:00
										 |  |  |    * preceding /. | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |    */ | 
					
						
							|  |  |  |   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); | 
					
						
							| 
									
										
										
										
											2010-09-01 15:04:41 +02:00
										 |  |  |       case 'n': return g_variant_get_int16 (variant); | 
					
						
							|  |  |  |       case 'q': return g_variant_get_uint16 (variant); | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |       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); | 
					
						
							| 
									
										
										
										
											2010-09-01 15:04:41 +02:00
										 |  |  |       case 't': return g_variant_get_uint64 (variant); | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |       default:  g_warn_if_reached (); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | handle_read_error (LONG         result, | 
					
						
							|  |  |  |                    const gchar *path_name, | 
					
						
							|  |  |  |                    const gchar *value_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   /* file not found means key value not set, this isn't an error for us. */ | 
					
						
							|  |  |  |   if (result != ERROR_FILE_NOT_FOUND) | 
					
						
							|  |  |  |       g_message_win32_error (result, "Unable to query value %s/%s: %s.\n", | 
					
						
							|  |  |  |                              path_name, value_name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /***************************************************************************
 | 
					
						
							|  |  |  |  * Cache of registry values | 
					
						
							|  |  |  |  ***************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Generic container for registry values */ | 
					
						
							|  |  |  | typedef struct { | 
					
						
							|  |  |  |   DWORD type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   union { | 
					
						
							|  |  |  |     gint  dword;  /* FIXME: could inline QWORD on 64-bit systems too */ | 
					
						
							|  |  |  |     void *ptr; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } RegistryValue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static char * | 
					
						
							|  |  |  | registry_value_dump (RegistryValue value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (value.type == REG_DWORD) | 
					
						
							|  |  |  |     return g_strdup_printf ("%i", value.dword); | 
					
						
							|  |  |  |   else if (value.type == REG_QWORD) | 
					
						
							|  |  |  |     return g_strdup_printf ("%I64i", value.ptr==NULL? 0: *(DWORDLONG *)value.ptr); | 
					
						
							|  |  |  |   else if (value.type == REG_SZ) | 
					
						
							|  |  |  |     return g_strdup_printf ("%s", (char *)value.ptr); | 
					
						
							|  |  |  |   else if (value.type == REG_NONE) | 
					
						
							|  |  |  |     return g_strdup_printf ("<empty>"); | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     return g_strdup_printf ("<invalid>"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_value_free (RegistryValue value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (value.type == REG_SZ || value.type == REG_QWORD) | 
					
						
							|  |  |  |     g_free (value.ptr); | 
					
						
							|  |  |  |   value.type = REG_NONE; | 
					
						
							|  |  |  |   value.ptr = NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* The registry cache is stored as a tree, for easy traversal. Right now we
 | 
					
						
							|  |  |  |  * don't sort it in a clever way. Each node corresponds to a path element | 
					
						
							|  |  |  |  * ('key' in registry terms) or a value. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Each subscription uses the same cache. Because GSettings can subscribe to | 
					
						
							|  |  |  |  * the tree at any node any number of times, we need to reference count the | 
					
						
							|  |  |  |  * nodes. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   /* Component of path that this node represents */ | 
					
						
							|  |  |  |   gchar *name;            | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* If a watch is subscribed at this point (subscription_count > 0) we can
 | 
					
						
							|  |  |  |    * block its next notification. This is useful because if two watches cover | 
					
						
							|  |  |  |    * the same path, both will trigger when it changes. It also allows changes | 
					
						
							|  |  |  |    * done by the application to be ignored by the watch thread. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   gint32 block_count        : 8; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Number of times g_settings_subscribe has been called for this location
 | 
					
						
							|  |  |  |    * (I guess you can't subscribe more than 16383 times) */ | 
					
						
							|  |  |  |   gint32 subscription_count : 14; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   gint32 ref_count          : 9; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   gint32 touched            : 1; | 
					
						
							|  |  |  |   RegistryValue value; | 
					
						
							|  |  |  | } RegistryCacheItem; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static GNode * | 
					
						
							|  |  |  | registry_cache_add_item (GNode        *parent, | 
					
						
							|  |  |  |                          gchar        *name, | 
					
						
							|  |  |  |                          RegistryValue value, | 
					
						
							|  |  |  |                          gint          ref_count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = g_slice_new (RegistryCacheItem); | 
					
						
							|  |  |  |   GNode *cache_node; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (name != NULL, NULL); | 
					
						
							|  |  |  |   g_return_val_if_fail (parent != NULL, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Ref count should be the number of watch points above this node */ | 
					
						
							|  |  |  |   item->ref_count = ref_count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   item->name = g_strdup (name); | 
					
						
							|  |  |  |   item->value = value; | 
					
						
							|  |  |  |   item->subscription_count = 0; | 
					
						
							|  |  |  |   item->block_count = 0; | 
					
						
							|  |  |  |   item->touched = FALSE; | 
					
						
							|  |  |  |   trace ("\treg cache: adding %s to %s\n", name, ((RegistryCacheItem *)parent->data)->name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cache_node = g_node_new (item); | 
					
						
							|  |  |  |   g_node_append (parent, cache_node); | 
					
						
							|  |  |  |   return cache_node; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* The reference counting of cache tree nodes works like this: when a node is
 | 
					
						
							|  |  |  |  * subscribed to (GSettings wants us to watch that path and everything below | 
					
						
							|  |  |  |  * it) the reference count of that node and everything below is increased, as | 
					
						
							|  |  |  |  * well as each parent up to the root. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _ref_down (GNode *node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  |   g_node_children_foreach (node, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            (GNodeForeachFunc)_ref_down, NULL); | 
					
						
							|  |  |  |   item->ref_count ++; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_ref_tree (GNode *tree) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = tree->data; | 
					
						
							|  |  |  |   GNode *node = tree->parent; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_if_fail (tree != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   item->ref_count ++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_node_children_foreach (tree, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            (GNodeForeachFunc)_ref_down, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (node=tree->parent; node; node=node->parent) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       item = node->data; | 
					
						
							|  |  |  |       item->ref_count ++; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _free_cache_item (RegistryCacheItem *item) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   trace ("\t -- Free node %s\n", item->name); | 
					
						
							|  |  |  |   g_free (item->name); | 
					
						
							|  |  |  |   registry_value_free (item->value); | 
					
						
							|  |  |  |   g_slice_free (RegistryCacheItem, item); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Unreferencing has to be done bottom-up */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _unref_node (GNode *node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   item->ref_count --; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_warn_if_fail (item->ref_count >= 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (item->ref_count == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       _free_cache_item (item); | 
					
						
							|  |  |  |       g_node_destroy (node); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _unref_down (GNode *node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   g_node_children_foreach (node, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            (GNodeForeachFunc)_unref_down, NULL); | 
					
						
							|  |  |  |   _unref_node (node); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_unref_tree (GNode *tree) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GNode *parent = tree->parent, *next_parent; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _unref_down (tree); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (parent) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       next_parent = parent->parent; | 
					
						
							|  |  |  |       _unref_node (parent); | 
					
						
							|  |  |  |       parent = next_parent; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_dump (GNode    *cache_node, | 
					
						
							|  |  |  |                      gpointer  data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = cache_node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   int depth     = GPOINTER_TO_INT(data), | 
					
						
							|  |  |  |       new_depth = depth+1, | 
					
						
							|  |  |  |       i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_if_fail (cache_node != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (i=0; i<depth; i++) | 
					
						
							|  |  |  |     g_print ("  "); | 
					
						
							|  |  |  |   if (item == NULL) | 
					
						
							|  |  |  |     g_print ("*root*\n"); | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     g_print ("'%s'  [%i] @ %x = %s\n", item->name, item->ref_count, (guint)cache_node, | 
					
						
							|  |  |  |              registry_value_dump (item->value)); | 
					
						
							|  |  |  |   g_node_children_foreach (cache_node, G_TRAVERSE_ALL, registry_cache_dump, | 
					
						
							|  |  |  |                            GINT_TO_POINTER (new_depth)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   gchar *name; | 
					
						
							|  |  |  |   GNode *result; | 
					
						
							|  |  |  | } RegistryCacheSearch; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | registry_cache_find_compare (GNode    *node, | 
					
						
							|  |  |  |                              gpointer  data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheSearch *search = data; | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (item == NULL)  /* root node */ | 
					
						
							|  |  |  |     return FALSE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (search->name != NULL, FALSE); | 
					
						
							|  |  |  |   g_return_val_if_fail (item->name != NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (strcmp (search->name, item->name) == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       search->result = node; | 
					
						
							|  |  |  |       return TRUE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   return FALSE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static GNode * | 
					
						
							|  |  |  | registry_cache_find_immediate_child (GNode *node, | 
					
						
							|  |  |  |                                      gchar *name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheSearch search; | 
					
						
							|  |  |  |   search.result = NULL; | 
					
						
							|  |  |  |   search.name = name; | 
					
						
							|  |  |  |   g_node_traverse (node, G_POST_ORDER, G_TRAVERSE_ALL, 2, | 
					
						
							|  |  |  |                    registry_cache_find_compare, &search); | 
					
						
							|  |  |  |   return search.result;   | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static GNode * | 
					
						
							|  |  |  | registry_cache_get_node_for_key_recursive (GNode    *node, | 
					
						
							|  |  |  |                                            gchar    *key_name, | 
					
						
							|  |  |  |                                            gboolean  create_if_not_found, | 
					
						
							|  |  |  |                                            gint      n_parent_watches) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item; | 
					
						
							|  |  |  |   gchar *component = key_name, | 
					
						
							|  |  |  |         *c         = strchr (component, '/'); | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   GNode *child; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   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 ++;   | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   child = registry_cache_find_immediate_child (node, component); | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   if (child == NULL && create_if_not_found) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       item = g_slice_new (RegistryCacheItem); | 
					
						
							|  |  |  |       item->name = g_strdup (component); | 
					
						
							|  |  |  |       item->value.type = REG_NONE; | 
					
						
							|  |  |  |       item->value.ptr = NULL; | 
					
						
							|  |  |  |       item->ref_count = n_parent_watches; | 
					
						
							|  |  |  |       child = g_node_new (item); | 
					
						
							|  |  |  |       g_node_append (node, child); | 
					
						
							|  |  |  |       trace ("\tget node for key recursive: new %x = %s.\n", node, item->name); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* We are done if there are no more path components. Allow for a trailing /. */ | 
					
						
							|  |  |  |   if (child==NULL || c == NULL || *(c+1)==0) | 
					
						
							|  |  |  |     return child; | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       trace ("get node for key recursive: next: %s.\n", c+1); | 
					
						
							|  |  |  |       return registry_cache_get_node_for_key_recursive | 
					
						
							|  |  |  |                (child, c+1, create_if_not_found, n_parent_watches); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Look up a GSettings key in the cache. */ | 
					
						
							|  |  |  | static GNode * | 
					
						
							|  |  |  | registry_cache_get_node_for_key (GNode       *root, | 
					
						
							|  |  |  |                                  const gchar *key_name, | 
					
						
							|  |  |  |                                  gboolean     create_if_not_found) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GNode *child = NULL, | 
					
						
							|  |  |  |         *result = NULL; | 
					
						
							|  |  |  |   gchar *component, *c; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (key_name != NULL, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (key_name[0] == '/') | 
					
						
							|  |  |  |     key_name ++; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-29 14:49:32 -04:00
										 |  |  |   /* Ignore preceding / */ | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   component = g_strdup (key_name); | 
					
						
							|  |  |  |   c = strchr (component, '/'); | 
					
						
							|  |  |  |   if (c != NULL) | 
					
						
							|  |  |  |     *c = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   child = registry_cache_find_immediate_child (root, component); | 
					
						
							|  |  |  |   if (child == NULL && create_if_not_found) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       /* Reference count is set to 0, tree should be referenced by the caller */ | 
					
						
							|  |  |  |       RegistryCacheItem *item = g_slice_new (RegistryCacheItem); | 
					
						
							|  |  |  |       item->value.type = REG_NONE; | 
					
						
							|  |  |  |       item->value.ptr = NULL; | 
					
						
							|  |  |  |       item->name = g_strdup (component); | 
					
						
							|  |  |  |       item->ref_count = 0; | 
					
						
							|  |  |  |       trace ("get_node_for_key: New node for component '%s'\n", item->name); | 
					
						
							|  |  |  |       child = g_node_new (item); | 
					
						
							|  |  |  |       g_node_append (root, child); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (c == NULL) | 
					
						
							|  |  |  |     result = root; | 
					
						
							|  |  |  |   else if (*(c+1)==0) | 
					
						
							|  |  |  |     result = child; | 
					
						
							|  |  |  |   else if (child != NULL) | 
					
						
							|  |  |  |     result = registry_cache_get_node_for_key_recursive (child, c+1, create_if_not_found, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_free (component); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Check the cache node against the registry key it represents. Return TRUE if
 | 
					
						
							|  |  |  |  * they differ, and update the cache with the new value. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | registry_cache_update_node (GNode        *cache_node, | 
					
						
							|  |  |  |                             RegistryValue registry_value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *cache_item = cache_node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (cache_node != NULL, FALSE); | 
					
						
							|  |  |  |   g_return_val_if_fail (cache_item != NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (registry_value.type != cache_item->value.type) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       /* The type has changed. Update cache item and register it as changed.
 | 
					
						
							|  |  |  |        * Either the schema has changed and this is entirely legitimate, or | 
					
						
							|  |  |  |        * whenever the app reads the key it will get the default value due to | 
					
						
							|  |  |  |        * the type mismatch. | 
					
						
							|  |  |  |        */ | 
					
						
							|  |  |  |       cache_item->value = registry_value; | 
					
						
							|  |  |  |       return TRUE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  |   switch (registry_value.type) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       case REG_DWORD: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           if (cache_item->value.dword == registry_value.dword) | 
					
						
							|  |  |  |             return FALSE; | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               cache_item->value.dword = registry_value.dword; | 
					
						
							|  |  |  |               return TRUE; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       case REG_QWORD: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           g_return_val_if_fail (registry_value.ptr != NULL && | 
					
						
							|  |  |  |                                 cache_item->value.ptr != NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (memcmp (registry_value.ptr, cache_item->value.ptr, 8)==0) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_free (registry_value.ptr); | 
					
						
							|  |  |  |               return FALSE; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_free (cache_item->value.ptr); | 
					
						
							|  |  |  |               cache_item->value.ptr = registry_value.ptr; | 
					
						
							|  |  |  |               return TRUE; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       case REG_SZ: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           /* Value should not exist if it is NULL, an empty string is "" */ | 
					
						
							|  |  |  |           g_return_val_if_fail (cache_item->value.ptr != NULL, FALSE); | 
					
						
							|  |  |  |           g_return_val_if_fail (registry_value.ptr != NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (strcmp (registry_value.ptr, cache_item->value.ptr) == 0) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_free (registry_value.ptr); | 
					
						
							|  |  |  |               return FALSE; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_free (cache_item->value.ptr); | 
					
						
							|  |  |  |               cache_item->value.ptr = registry_value.ptr; | 
					
						
							|  |  |  |               return TRUE; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         g_warning ("gregistrybackend: registry_cache_update_node: Unhandled value type :("); | 
					
						
							|  |  |  |         return FALSE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Blocking notifications is a useful optimisation. When a change is made
 | 
					
						
							|  |  |  |  * through GSettings we update the cache manually, but a notifcation is | 
					
						
							|  |  |  |  * triggered as well. This function is also used for nested notifications, | 
					
						
							|  |  |  |  * eg. if /test and /test/foo are watched, and /test/foo/value is changed then | 
					
						
							|  |  |  |  * we will get notified both for /test/foo and /test and it is helpful to block | 
					
						
							|  |  |  |  * the second. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_block_notification (GNode *node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_if_fail (node != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (item->subscription_count > 0) | 
					
						
							|  |  |  |     item->block_count ++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (node->parent != NULL) | 
					
						
							|  |  |  |     registry_cache_block_notification (node->parent); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_destroy_tree (GNode            *node, | 
					
						
							|  |  |  |                              WatchThreadState *self); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /***************************************************************************
 | 
					
						
							|  |  |  |  * Reading and writing | 
					
						
							|  |  |  |  ***************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | registry_read (HKEY           hpath, | 
					
						
							|  |  |  |                const gchar   *path_name, | 
					
						
							|  |  |  |                const gchar   *value_name, | 
					
						
							|  |  |  |                RegistryValue *p_value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   LONG      result; | 
					
						
							|  |  |  |   DWORD     value_data_size; | 
					
						
							|  |  |  |   gpointer *buffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (p_value != NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   p_value->type = REG_NONE; | 
					
						
							|  |  |  |   p_value->ptr = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   result = RegQueryValueExA (hpath, value_name, 0, &p_value->type, NULL, &value_data_size); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |      { | 
					
						
							|  |  |  |       handle_read_error (result, path_name, value_name); | 
					
						
							|  |  |  |       return FALSE; | 
					
						
							|  |  |  |      } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (p_value->type == REG_SZ && value_data_size == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       p_value->ptr = g_strdup (""); | 
					
						
							|  |  |  |       return TRUE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (p_value->type == REG_DWORD) | 
					
						
							|  |  |  |     /* REG_DWORD is inlined */ | 
					
						
							|  |  |  |     buffer = (void *)&p_value->dword; | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     buffer = p_value->ptr = g_malloc (value_data_size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   result = RegQueryValueExA (hpath, value_name, 0, NULL, (LPBYTE)buffer, &value_data_size); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       handle_read_error (result, path_name, value_name); | 
					
						
							|  |  |  |       return FALSE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return TRUE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static GVariant * | 
					
						
							|  |  |  | g_registry_backend_read (GSettingsBackend   *backend, | 
					
						
							|  |  |  |                          const gchar        *key_name, | 
					
						
							|  |  |  |                          const GVariantType *expected_type, | 
					
						
							|  |  |  |                          gboolean            default_value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self = G_REGISTRY_BACKEND (backend); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   GNode         *cache_node; | 
					
						
							|  |  |  |   RegistryValue  registry_value; | 
					
						
							|  |  |  |   GVariant      *gsettings_value = NULL; | 
					
						
							|  |  |  |   gchar         *gsettings_type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (expected_type != NULL, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (default_value) | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Simply read from the cache, which is updated from the registry by the
 | 
					
						
							|  |  |  |    * watch thread as soon as changes can propagate. Any changes not yet in the | 
					
						
							|  |  |  |    * cache will have the 'changed' signal emitted after this function returns. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   EnterCriticalSection (self->cache_lock); | 
					
						
							|  |  |  |   cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE); | 
					
						
							|  |  |  |   LeaveCriticalSection (self->cache_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("Reading key %s, cache node %x\n", key_name, cache_node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Maybe it's not set, we can return to default */ | 
					
						
							|  |  |  |   if (cache_node == NULL) | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("\t- cached value %s\n", registry_value_dump (((RegistryCacheItem *)cache_node->data)->value)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   registry_value = ((RegistryCacheItem *)cache_node->data)->value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   gsettings_type = g_variant_type_dup_string (expected_type); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* The registry is user-editable, so we need to be fault-tolerant here. */ | 
					
						
							|  |  |  |   switch (gsettings_type[0]) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': | 
					
						
							|  |  |  |         if (registry_value.type == REG_DWORD) | 
					
						
							|  |  |  |           gsettings_value = g_variant_new (gsettings_type, registry_value.dword); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case 't': case 'x': | 
					
						
							|  |  |  |         if (registry_value.type == REG_QWORD) | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             DWORDLONG qword_value = *(DWORDLONG *)registry_value.ptr; | 
					
						
							|  |  |  |             gsettings_value = g_variant_new (gsettings_type, qword_value); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         if (registry_value.type == REG_SZ) | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             if (gsettings_type[0]=='s') | 
					
						
							|  |  |  |               gsettings_value = g_variant_new_string ((char *)registry_value.ptr); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               { | 
					
						
							|  |  |  |                 GError *error = NULL; | 
					
						
							|  |  |  |                 gsettings_value = g_variant_parse (expected_type, registry_value.ptr, NULL, NULL, &error); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (error != NULL) | 
					
						
							|  |  |  |                 g_message ("gregistrysettingsbackend: error parsing key %s: %s\n", | 
					
						
							|  |  |  |                            key_name, error->message); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_free (gsettings_type); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return gsettings_value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend  *self; | 
					
						
							|  |  |  |   HKEY               hroot; | 
					
						
							|  |  |  | } RegistryWrite; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | g_registry_backend_write_one (const char *key_name, | 
					
						
							|  |  |  |                               GVariant   *variant, | 
					
						
							|  |  |  |                               gpointer    user_data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self; | 
					
						
							|  |  |  |   RegistryWrite    *action; | 
					
						
							|  |  |  |   RegistryValue     value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   HKEY    hroot, hpath; | 
					
						
							|  |  |  |   gchar  *path_name, *value_name = NULL; | 
					
						
							|  |  |  |   DWORD   value_data_size; | 
					
						
							|  |  |  |   LPVOID  value_data; | 
					
						
							|  |  |  |   LONG    result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   GNode    *node; | 
					
						
							|  |  |  |   gboolean  changed; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   const gchar *type_string = g_variant_get_type_string (variant); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Store the value in the registry */ | 
					
						
							|  |  |  |   result = RegCreateKeyExA (hroot, path_name, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (result, "gregistrybackend: opening key %s failed", path_name+1); | 
					
						
							|  |  |  |       registry_value_free (value); | 
					
						
							|  |  |  |       g_free (path_name); | 
					
						
							|  |  |  |       return FALSE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   result = RegSetValueExA (hpath, value_name, 0, value.type, value_data, value_data_size); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |       g_message_win32_error (result, "gregistrybackend: setting value %s\\%s\\%s failed.\n", | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |                              self->base_path, path_name, value_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* If the write fails then it will seem like the value has changed until the
 | 
					
						
							|  |  |  |    * next execution (because we wrote to the cache first). There's no reason | 
					
						
							|  |  |  |    * for it to fail unless something is weirdly broken, however. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   RegCloseKey (hpath); | 
					
						
							|  |  |  |   g_free (path_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return FALSE; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* The dconf write policy is to do the write while making out it succeeded, 
 | 
					
						
							|  |  |  |  * and then backtrack if it didn't. The registry functions are synchronous so | 
					
						
							|  |  |  |  * we can't do that. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | g_registry_backend_write (GSettingsBackend *backend, | 
					
						
							|  |  |  |                           const gchar      *key_name, | 
					
						
							|  |  |  |                           GVariant         *value, | 
					
						
							|  |  |  |                           gpointer          origin_tag) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self = G_REGISTRY_BACKEND (backend); | 
					
						
							|  |  |  |   LONG result; | 
					
						
							|  |  |  |   HKEY hroot; | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   RegistryWrite action; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0, | 
					
						
							|  |  |  |                             KEY_WRITE, NULL, &hroot, NULL); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) { | 
					
						
							|  |  |  |     trace ("Error opening/creating key %s.\n", self->base_path); | 
					
						
							|  |  |  |     return FALSE; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   action.self = self; | 
					
						
							|  |  |  |   action.hroot = hroot; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   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; | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   RegistryWrite action; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0, | 
					
						
							|  |  |  |                             KEY_WRITE, NULL, &hroot, NULL); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) { | 
					
						
							|  |  |  |     trace ("Error opening/creating key %s.\n", self->base_path); | 
					
						
							|  |  |  |     return FALSE; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   action.self =  self; | 
					
						
							|  |  |  |   action.hroot = hroot; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   g_tree_foreach (values, (GTraverseFunc)g_registry_backend_write_one, | 
					
						
							|  |  |  |                   &action); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_settings_backend_changed_tree (backend, values, origin_tag); | 
					
						
							|  |  |  |   RegCloseKey (hroot); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return TRUE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_reset (GSettingsBackend *backend, | 
					
						
							|  |  |  |                           const gchar      *key_name, | 
					
						
							|  |  |  |                           gpointer          origin_tag) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self = G_REGISTRY_BACKEND (backend); | 
					
						
							|  |  |  |   gchar *path_name, *value_name = NULL; | 
					
						
							|  |  |  |   GNode *cache_node; | 
					
						
							|  |  |  |   LONG result; | 
					
						
							|  |  |  |   HKEY hpath; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Remove from cache */ | 
					
						
							|  |  |  |   EnterCriticalSection (self->cache_lock); | 
					
						
							|  |  |  |   cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE); | 
					
						
							|  |  |  |   if (cache_node) | 
					
						
							|  |  |  |     registry_cache_destroy_tree (cache_node, self->watch); | 
					
						
							|  |  |  |   LeaveCriticalSection (self->cache_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Remove from the registry */ | 
					
						
							|  |  |  |   path_name = parse_key (key_name, self->base_path, &value_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   result = RegOpenKeyExA (HKEY_CURRENT_USER, path_name, 0, KEY_SET_VALUE, &hpath); | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (result, "Registry: resetting key '%s'", path_name); | 
					
						
							|  |  |  |       g_free (path_name); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   result = RegDeleteValueA (hpath, value_name); | 
					
						
							|  |  |  |   RegCloseKey (hpath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (result, "Registry: resetting key '%s'", path_name); | 
					
						
							|  |  |  |       g_free (path_name); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_free (path_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_settings_backend_changed (backend, key_name, origin_tag); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Not implemented and probably beyond the scope of this backend */ | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | g_registry_backend_get_writable (GSettingsBackend *backend, | 
					
						
							|  |  |  |                                  const gchar      *key_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   return TRUE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static GPermission * | 
					
						
							|  |  |  | g_registry_backend_get_permission (GSettingsBackend *backend, | 
					
						
							|  |  |  |                                    const gchar      *key_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   return g_simple_permission_new (TRUE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************************
 | 
					
						
							|  |  |  |  * Spot-the-difference engine | 
					
						
							|  |  |  |  ********************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _free_watch (WatchThreadState *self, | 
					
						
							|  |  |  |              gint              index, | 
					
						
							|  |  |  |              GNode            *cache_node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_item_reset_touched (GNode    *node, | 
					
						
							|  |  |  |                                    gpointer  data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  |   item->touched = FALSE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Delete a node and any children, for when it has been deleted from the registry */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_destroy_tree (GNode            *node, | 
					
						
							|  |  |  |                              WatchThreadState *self) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_node_children_foreach (node, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            (GNodeForeachFunc)registry_cache_destroy_tree, self); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (item->subscription_count > 0) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  | 	  gint i; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |       /* 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); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   _free_cache_item (node->data); | 
					
						
							|  |  |  |   g_node_destroy (node); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_remove_deleted (GNode    *node, | 
					
						
							|  |  |  |                                gpointer  data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   RegistryCacheItem *item = node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!item->touched) | 
					
						
							|  |  |  |     registry_cache_destroy_tree (node, data); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Update cache from registry, and optionally report on the changes.
 | 
					
						
							|  |  |  |  *  | 
					
						
							|  |  |  |  * This function is sometimes called from the watch thread, with no locking. It | 
					
						
							|  |  |  |  * does call g_registry_backend functions, but this is okay because they only | 
					
						
							|  |  |  |  * access self->base which is constant. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * When looking at this code bear in mind the terminology: in the registry, keys | 
					
						
							|  |  |  |  * are containers that contain values, and other keys. Keys have a 'default' | 
					
						
							|  |  |  |  * value which we always ignore. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * n_parent_watches: a counter used to set the reference count of any new nodes | 
					
						
							|  |  |  |  *                   that are created - they should have as many references as | 
					
						
							|  |  |  |  *                   there are notifications that are watching them. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | registry_cache_update (GRegistryBackend *self, | 
					
						
							|  |  |  |                        HKEY              hpath, | 
					
						
							|  |  |  |                        const gchar      *prefix, | 
					
						
							|  |  |  |                        const gchar      *partial_key_name, | 
					
						
							|  |  |  |                        GNode            *cache_node, | 
					
						
							|  |  |  |                        int               n_watches,  | 
					
						
							|  |  |  |                        GPtrArray        *changes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   gchar  buffer[MAX_KEY_NAME_LENGTH + 1]; | 
					
						
							|  |  |  |   gchar *key_name; | 
					
						
							|  |  |  |   gint   i; | 
					
						
							|  |  |  |   LONG   result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   RegistryCacheItem *item = cache_node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (item->subscription_count > 0) | 
					
						
							|  |  |  |     n_watches ++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* prefix is the level that all changes occur below; partial_key_name should
 | 
					
						
							|  |  |  |    * be NULL on the first call to this function */ | 
					
						
							|  |  |  |   key_name = g_build_path ("/", prefix, partial_key_name, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("registry cache update: %s. Node %x has %i children\n", key_name, | 
					
						
							|  |  |  |          cache_node, g_node_n_children (cache_node)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Start by zeroing 'touched' flag. When the registry traversal is done, any untouched nodes
 | 
					
						
							|  |  |  |    * must have been deleted from the registry. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   g_node_children_foreach (cache_node, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            registry_cache_item_reset_touched, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Recurse into each subpath at the current level, if any */ | 
					
						
							|  |  |  |   i = 0; | 
					
						
							|  |  |  |   while (1) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       DWORD buffer_size = MAX_KEY_NAME_LENGTH; | 
					
						
							|  |  |  |       HKEY  hsubpath; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       result = RegEnumKeyEx (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); | 
					
						
							|  |  |  |       if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       result = RegOpenKeyEx (hpath, buffer, 0, KEY_READ, &hsubpath); | 
					
						
							|  |  |  |       if (result == ERROR_SUCCESS) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           GNode             *subkey_node; | 
					
						
							|  |  |  |           RegistryCacheItem *child_item; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           subkey_node = registry_cache_find_immediate_child (cache_node, buffer); | 
					
						
							|  |  |  |           if (subkey_node == NULL) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               RegistryValue null_value = {REG_NONE, {0}}; | 
					
						
							|  |  |  |               subkey_node = registry_cache_add_item (cache_node, buffer, | 
					
						
							|  |  |  |                                                      null_value, n_watches); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           registry_cache_update (self, hsubpath, prefix, buffer, subkey_node, | 
					
						
							|  |  |  |                                  n_watches, changes); | 
					
						
							|  |  |  |           child_item = subkey_node->data; | 
					
						
							|  |  |  |           child_item->touched = TRUE; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       RegCloseKey (hsubpath); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result != ERROR_NO_MORE_ITEMS) | 
					
						
							|  |  |  |     g_message_win32_error (result, "gregistrybackend: error enumerating subkeys for cache."); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Enumerate each value at 'path' and check if it has changed */ | 
					
						
							|  |  |  |   i = 0; | 
					
						
							|  |  |  |   while (1) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       DWORD              buffer_size = MAX_KEY_NAME_LENGTH; | 
					
						
							|  |  |  |       GNode             *cache_child_node; | 
					
						
							|  |  |  |       RegistryCacheItem *child_item; | 
					
						
							|  |  |  |       RegistryValue      value; | 
					
						
							|  |  |  |       gboolean           changed = FALSE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       result = RegEnumValue (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); | 
					
						
							|  |  |  |       if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (buffer[0]==0) | 
					
						
							|  |  |  |         /* This is the key's 'default' value, for which we have no use. */ | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       cache_child_node = registry_cache_find_immediate_child (cache_node, buffer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!registry_read (hpath, key_name, buffer, &value)) | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       trace ("\tgot value %s for %s, node %x\n", registry_value_dump (value), buffer, cache_child_node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (cache_child_node == NULL) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           /* This is a new value */ | 
					
						
							|  |  |  |           cache_child_node = registry_cache_add_item (cache_node, buffer, value, | 
					
						
							|  |  |  |                                                       n_watches); | 
					
						
							|  |  |  |           changed = TRUE; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |          /* For efficiency, instead of converting every value back to a GVariant to
 | 
					
						
							|  |  |  |           * compare it, we compare them as registry values (integers, or string | 
					
						
							|  |  |  |           * representations of the variant). The spurious change notifications that may | 
					
						
							|  |  |  |           * result should not be a big issue. | 
					
						
							|  |  |  |           * | 
					
						
							|  |  |  |           * Note that 'value' is swallowed or freed. | 
					
						
							|  |  |  |           */ | 
					
						
							|  |  |  |           changed = registry_cache_update_node (cache_child_node, value); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       child_item = cache_child_node->data; | 
					
						
							|  |  |  |       child_item->touched = TRUE; | 
					
						
							|  |  |  |       if (changed == TRUE && changes != NULL) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           gchar *item; | 
					
						
							|  |  |  |           if (partial_key_name == NULL) | 
					
						
							|  |  |  |             item = g_strdup (buffer); | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             item = g_build_path ("/", partial_key_name, buffer, NULL); | 
					
						
							|  |  |  |           g_ptr_array_add (changes, item); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result != ERROR_NO_MORE_ITEMS) | 
					
						
							|  |  |  |     g_message_win32_error (result, "gregistrybackend: error enumerating values for cache"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Any nodes now left untouched must have been deleted, remove them from cache */ | 
					
						
							|  |  |  |   g_node_children_foreach (cache_node, G_TRAVERSE_ALL, | 
					
						
							|  |  |  |                            registry_cache_remove_deleted, self->watch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("registry cache update complete.\n"); | 
					
						
							|  |  |  |   g_free (key_name); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /***********************************************************************************
 | 
					
						
							|  |  |  |  * Thread to watch for registry change events | 
					
						
							|  |  |  |  ***********************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Called by watch thread. Apply for notifications on a registry key and its subkeys. */ | 
					
						
							|  |  |  | static DWORD | 
					
						
							|  |  |  | registry_watch_key (HKEY hpath, HANDLE event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   return RegNotifyChangeKeyValue (hpath, TRUE, | 
					
						
							|  |  |  |                                   REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, | 
					
						
							|  |  |  |                                   event, TRUE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* One of these is sent down the pipe when something happens in the registry. */ | 
					
						
							|  |  |  | typedef struct | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self; | 
					
						
							|  |  |  |   gchar *prefix;          /* prefix is a gsettings path, all items are subkeys of this. */ | 
					
						
							|  |  |  |   GPtrArray *items;       /* each item is a subkey below prefix that has changed. */ | 
					
						
							|  |  |  | } RegistryEvent; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* This handler runs in the main thread to emit the changed signals */ | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | watch_handler (RegistryEvent *event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   gint i; | 
					
						
							|  |  |  |   trace ("Watch handler: got event in %s, items %i.\n", event->prefix, event->items->len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* GSettings requires us to NULL-terminate the array. */ | 
					
						
							|  |  |  |   g_ptr_array_add (event->items, NULL); | 
					
						
							|  |  |  |   g_settings_backend_keys_changed (G_SETTINGS_BACKEND (event->self), event->prefix, | 
					
						
							|  |  |  |                                    (gchar const **)event->items->pdata, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (i=0; i<event->items->len; i++) | 
					
						
							|  |  |  |     g_free (g_ptr_array_index (event->items, i)); | 
					
						
							|  |  |  |   g_ptr_array_free (event->items, TRUE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_free (event->prefix); | 
					
						
							|  |  |  |   g_object_unref (event->self); | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   g_slice_free (RegistryEvent, event); | 
					
						
							| 
									
										
										
										
											2012-01-25 14:18:34 -05:00
										 |  |  |   return G_SOURCE_REMOVE; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | _free_watch (WatchThreadState *self, | 
					
						
							|  |  |  |              gint              index, | 
					
						
							|  |  |  |              GNode            *cache_node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   HKEY    hpath; | 
					
						
							|  |  |  |   HANDLE  cond; | 
					
						
							|  |  |  |   gchar  *prefix; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_if_fail (index > 0 && index < self->events->len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cond       = g_ptr_array_index (self->events,      index); | 
					
						
							|  |  |  |   hpath      = g_ptr_array_index (self->handles,     index); | 
					
						
							|  |  |  |   prefix     = g_ptr_array_index (self->prefixes,    index); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("Freeing watch %i [%s]\n", index, prefix); | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  |   /* These can be NULL if the watch was already dead, this can happen when eg.
 | 
					
						
							|  |  |  |    * a key is deleted but GSettings is still subscribed to it - the watch is | 
					
						
							|  |  |  |    * kept alive so that the unsubscribe function works properly, but does not | 
					
						
							|  |  |  |    * do anything. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   if (hpath != NULL) | 
					
						
							|  |  |  |     RegCloseKey (hpath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (cache_node != NULL) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       //registry_cache_dump (G_REGISTRY_BACKEND (self->owner)->cache_root, NULL);
 | 
					
						
							|  |  |  |       registry_cache_unref_tree (cache_node); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   CloseHandle (cond); | 
					
						
							|  |  |  |   g_free (prefix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* As long as we remove from each array at the same time, it doesn't matter that
 | 
					
						
							|  |  |  |    * their orders get messed up - they all get messed up the same. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   g_ptr_array_remove_index_fast (self->handles,     index); | 
					
						
							|  |  |  |   g_ptr_array_remove_index_fast (self->events,      index); | 
					
						
							|  |  |  |   g_ptr_array_remove_index_fast (self->prefixes,    index); | 
					
						
							|  |  |  |   g_ptr_array_remove_index_fast (self->cache_nodes, index); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | watch_thread_handle_message (WatchThreadState *self) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   switch (self->message.type) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       case WATCH_THREAD_NONE: | 
					
						
							|  |  |  |         trace ("watch thread: you woke me up for nothin', man!"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case WATCH_THREAD_ADD_WATCH: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           RegistryWatch *watch = &self->message.watch; | 
					
						
							|  |  |  |           LONG           result; | 
					
						
							|  |  |  |           result = registry_watch_key (watch->hpath, watch->event); | 
					
						
							|  |  |  |           if (result == ERROR_SUCCESS) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_ptr_array_add (self->events,      watch->event); | 
					
						
							|  |  |  |               g_ptr_array_add (self->handles,     watch->hpath); | 
					
						
							|  |  |  |               g_ptr_array_add (self->prefixes,    watch->prefix); | 
					
						
							|  |  |  |               g_ptr_array_add (self->cache_nodes, watch->cache_node); | 
					
						
							|  |  |  |               trace ("watch thread: new watch on %s, %i total\n", watch->prefix, | 
					
						
							|  |  |  |                      self->events->len); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_message_win32_error (result, "watch thread: could not watch %s", watch->prefix); | 
					
						
							|  |  |  |               CloseHandle (watch->event); | 
					
						
							|  |  |  |               RegCloseKey (watch->hpath); | 
					
						
							|  |  |  |               g_free (watch->prefix); | 
					
						
							|  |  |  |               registry_cache_unref_tree (watch->cache_node); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case WATCH_THREAD_REMOVE_WATCH: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           GNode             *cache_node; | 
					
						
							|  |  |  |           RegistryCacheItem *cache_item; | 
					
						
							|  |  |  |           gint               i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           for (i=1; i<self->prefixes->len; i++) | 
					
						
							|  |  |  |             if (strcmp (g_ptr_array_index (self->prefixes, i), | 
					
						
							|  |  |  |                         self->message.watch.prefix) == 0) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  |           if (i >= self->prefixes->len) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               /* Don't make a fuss if the prefix is not being watched because
 | 
					
						
							|  |  |  |                * maybe the path was deleted so we removed the watch. | 
					
						
							|  |  |  |                */ | 
					
						
							|  |  |  |               trace ("unsubscribe: prefix %s is not being watched [%i things are]!\n", | 
					
						
							|  |  |  |                      self->message.watch.prefix, self->prefixes->len); | 
					
						
							|  |  |  |               g_free (self->message.watch.prefix); | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           cache_node = g_ptr_array_index (self->cache_nodes, i); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           trace ("watch thread: unsubscribe: freeing node %x, prefix %s, index %i\n", | 
					
						
							|  |  |  |                  (guint)cache_node, self->message.watch.prefix, i); | 
					
						
							|  |  |  |           if (cache_node != NULL) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               cache_item = cache_node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               /* There may be more than one GSettings object subscribed to this
 | 
					
						
							|  |  |  |                * path, only free the watch when the last one unsubscribes. | 
					
						
							|  |  |  |                */ | 
					
						
							|  |  |  |               cache_item->subscription_count --; | 
					
						
							|  |  |  |               if (cache_item->subscription_count > 0) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           _free_watch (self, i, cache_node); | 
					
						
							|  |  |  |           g_free (self->message.watch.prefix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           g_atomic_int_inc (&self->watches_remaining); | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case WATCH_THREAD_STOP: | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           gint i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           /* Free any remaining cache and watch handles */ | 
					
						
							|  |  |  |           for (i=1; i<self->events->len; i++) | 
					
						
							|  |  |  |             _free_watch (self, i, g_ptr_array_index (self->cache_nodes, i)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           SetEvent (self->message_received_event); | 
					
						
							|  |  |  |           ExitThread (0); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   self->message.type = WATCH_THREAD_NONE; | 
					
						
							|  |  |  |   SetEvent (self->message_received_event); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Thread which watches for win32 registry events */ | 
					
						
							|  |  |  | static DWORD WINAPI | 
					
						
							|  |  |  | watch_thread_function (LPVOID parameter) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadState *self = (WatchThreadState *)parameter; | 
					
						
							|  |  |  |   DWORD result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   self->events = g_ptr_array_new (); | 
					
						
							|  |  |  |   self->handles = g_ptr_array_new (); | 
					
						
							|  |  |  |   self->prefixes = g_ptr_array_new (); | 
					
						
							|  |  |  |   self->cache_nodes = g_ptr_array_new (); | 
					
						
							|  |  |  |   g_ptr_array_add (self->events, self->message_sent_event); | 
					
						
							|  |  |  |   g_ptr_array_add (self->handles, NULL); | 
					
						
							|  |  |  |   g_ptr_array_add (self->prefixes, NULL); | 
					
						
							|  |  |  |   g_ptr_array_add (self->cache_nodes, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (1) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       trace ("watch thread: going to sleep; %i events watched.\n", self->events->len); | 
					
						
							|  |  |  |       result = WaitForMultipleObjects (self->events->len, self->events->pdata, FALSE, INFINITE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (result == WAIT_OBJECT_0) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           /* A message to you. The sender (main thread) will block until we signal the received
 | 
					
						
							|  |  |  |            * event, so there should be no danger of it sending another before we receive the | 
					
						
							|  |  |  |            * first. | 
					
						
							|  |  |  |            */ | 
					
						
							|  |  |  |           watch_thread_handle_message (self); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       else if (result > WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + self->events->len) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           HKEY               hpath; | 
					
						
							|  |  |  |           HANDLE             cond; | 
					
						
							|  |  |  |           gchar             *prefix; | 
					
						
							|  |  |  |           GNode             *cache_node; | 
					
						
							|  |  |  |           RegistryCacheItem *cache_item; | 
					
						
							|  |  |  |           RegistryEvent     *event; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           /* One of our notifications has triggered. All we know is which one, and which key
 | 
					
						
							|  |  |  |            * this is for. We do most of the processing here, because we may as well. If the | 
					
						
							|  |  |  |            * registry changes further while we are processing it doesn't matter - we will then | 
					
						
							|  |  |  |            * receive another change notification from the OS anyway. | 
					
						
							|  |  |  |            */ | 
					
						
							|  |  |  |           gint notify_index = result - WAIT_OBJECT_0; | 
					
						
							|  |  |  |           hpath      = g_ptr_array_index (self->handles,     notify_index); | 
					
						
							|  |  |  |           cond       = g_ptr_array_index (self->events,      notify_index); | 
					
						
							|  |  |  |           prefix     = g_ptr_array_index (self->prefixes,    notify_index); | 
					
						
							|  |  |  |           cache_node = g_ptr_array_index (self->cache_nodes, notify_index); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           trace ("Watch thread: notify received on prefix %i: %s.\n", notify_index, prefix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (cache_node == NULL) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               /* This path has been deleted */ | 
					
						
							|  |  |  |               trace ("Notify received on a path that was deleted :(\n"); | 
					
						
							|  |  |  |               continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           /* Firstly we need to reapply for the notification, because (what a
 | 
					
						
							|  |  |  |            * sensible API) we won't receive any more. MSDN is pretty | 
					
						
							|  |  |  |            * inconsistent on this matter: | 
					
						
							|  |  |  |            *   http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx
 | 
					
						
							|  |  |  |            *   http://support.microsoft.com/kb/236570
 | 
					
						
							|  |  |  |            * But my tests (on Windows XP SP3) show that we need to reapply | 
					
						
							|  |  |  |            * each time. | 
					
						
							|  |  |  |            */ | 
					
						
							|  |  |  |           result = registry_watch_key (hpath, cond); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               /* Watch failed, most likely because the key has just been
 | 
					
						
							|  |  |  |                * deleted. Free the watch and unref the cache nodes. | 
					
						
							|  |  |  |                */ | 
					
						
							|  |  |  |              if (result != ERROR_KEY_DELETED) | 
					
						
							|  |  |  |                g_message_win32_error (result, "watch thread: failed to watch %s", prefix); | 
					
						
							|  |  |  |              _free_watch (self, notify_index, cache_node); | 
					
						
							|  |  |  |              g_atomic_int_inc (&self->watches_remaining); | 
					
						
							|  |  |  |              continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           /* The notification may have been blocked because we just changed
 | 
					
						
							|  |  |  |            * some data ourselves. | 
					
						
							|  |  |  |            */ | 
					
						
							|  |  |  |           cache_item = cache_node->data; | 
					
						
							|  |  |  |           if (cache_item->block_count) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               cache_item->block_count --; | 
					
						
							|  |  |  |               trace ("Watch thread: notify blocked at %s\n", prefix); | 
					
						
							|  |  |  |               continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |           /* Now we update our stored cache from registry data, and find which keys have
 | 
					
						
							|  |  |  |            * actually changed. If more changes happen while we are processing, we will get | 
					
						
							|  |  |  |            * another event because we have reapplied for change notifications already. | 
					
						
							|  |  |  |            * | 
					
						
							|  |  |  |            * Working here rather than in the main thread is preferable because the UI is less | 
					
						
							|  |  |  |            * likely to block (only when changing notification subscriptions). | 
					
						
							|  |  |  |            */ | 
					
						
							|  |  |  |           event = g_slice_new (RegistryEvent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           event->self = G_REGISTRY_BACKEND (self->owner); | 
					
						
							|  |  |  |           g_object_ref (self->owner); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           event->items = g_ptr_array_new (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           EnterCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock); | 
					
						
							|  |  |  |           registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath, | 
					
						
							|  |  |  |                                  prefix, NULL, cache_node, 0, event->items); | 
					
						
							|  |  |  |           LeaveCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (event->items->len > 0) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               event->prefix = g_strdup (prefix); | 
					
						
							|  |  |  |               g_idle_add ((GSourceFunc) watch_handler, event); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               g_ptr_array_free (event->items, TRUE); | 
					
						
							|  |  |  |               g_slice_free (RegistryEvent, event); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           /* God knows what has happened */ | 
					
						
							|  |  |  |           g_message_win32_error (GetLastError(), "watch thread: WaitForMultipleObjects error"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | watch_start (GRegistryBackend *self) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadState *watch; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (self->watch == NULL, FALSE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   self->cache_lock = g_slice_new (CRITICAL_SECTION); | 
					
						
							|  |  |  |   InitializeCriticalSection (self->cache_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   watch = g_slice_new (WatchThreadState); | 
					
						
							|  |  |  |   watch->owner = G_SETTINGS_BACKEND (self); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   watch->watches_remaining = MAX_WATCHES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   watch->message_lock = g_slice_new (CRITICAL_SECTION); | 
					
						
							|  |  |  |   InitializeCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   watch->message_sent_event = CreateEvent (NULL, FALSE, FALSE, NULL); | 
					
						
							|  |  |  |   watch->message_received_event = CreateEvent (NULL, FALSE, FALSE, NULL); | 
					
						
							|  |  |  |   if (watch->message_sent_event == NULL || watch->message_received_event == NULL) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (0, "gregistrybackend: Failed to create sync objects."); | 
					
						
							|  |  |  |       goto fail_1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Use a small stack to make the thread more lightweight. */ | 
					
						
							|  |  |  |   watch->thread = CreateThread (NULL, 1024, watch_thread_function, watch, 0, NULL); | 
					
						
							|  |  |  |   if (watch->thread == NULL) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (0, "gregistrybackend: Failed to create notify watch thread."); | 
					
						
							|  |  |  |       goto fail_2; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   self->watch = watch; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return TRUE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | fail_2: | 
					
						
							|  |  |  |   DeleteCriticalSection (self->cache_lock); | 
					
						
							|  |  |  |   g_slice_free (CRITICAL_SECTION, self->cache_lock); | 
					
						
							|  |  |  |   DeleteCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   g_slice_free (CRITICAL_SECTION, watch->message_lock); | 
					
						
							|  |  |  |   CloseHandle (watch->message_sent_event); | 
					
						
							|  |  |  |   CloseHandle (watch->message_received_event); | 
					
						
							|  |  |  | fail_1: | 
					
						
							|  |  |  |   g_slice_free (WatchThreadState, watch); | 
					
						
							|  |  |  |   return FALSE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* This function assumes you hold the message lock! */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | watch_stop_unlocked (GRegistryBackend *self) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadState *watch = self->watch; | 
					
						
							|  |  |  |   DWORD result; | 
					
						
							|  |  |  |   g_return_if_fail (watch != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   watch->message.type = WATCH_THREAD_STOP; | 
					
						
							|  |  |  |   SetEvent (watch->message_sent_event); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* This is signalled as soon as the message is received. We must not return
 | 
					
						
							|  |  |  |    * while the watch thread is still firing off callbacks. Freeing all of the | 
					
						
							|  |  |  |    * memory is done in the watch thread after this is signalled. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   result = WaitForSingleObject (watch->message_received_event, INFINITE); | 
					
						
							|  |  |  |   if (result != WAIT_OBJECT_0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_warning ("gregistrybackend: unable to stop watch thread."); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   LeaveCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   DeleteCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   DeleteCriticalSection (self->cache_lock); | 
					
						
							|  |  |  |   g_slice_free (CRITICAL_SECTION, watch->message_lock); | 
					
						
							|  |  |  |   g_slice_free (CRITICAL_SECTION, self->cache_lock); | 
					
						
							|  |  |  |   CloseHandle (watch->message_sent_event); | 
					
						
							|  |  |  |   CloseHandle (watch->message_received_event); | 
					
						
							|  |  |  |   CloseHandle (watch->thread); | 
					
						
							|  |  |  |   g_slice_free (WatchThreadState, watch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("\nwatch thread: %x: all data freed.\n", self); | 
					
						
							|  |  |  |   self->watch = NULL; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static gboolean | 
					
						
							|  |  |  | watch_add_notify (GRegistryBackend *self, | 
					
						
							|  |  |  |                   HANDLE            event, | 
					
						
							|  |  |  |                   HKEY              hpath, | 
					
						
							|  |  |  |                   gchar            *gsettings_prefix) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadState  *watch = self->watch; | 
					
						
							|  |  |  |   GNode             *cache_node; | 
					
						
							|  |  |  |   RegistryCacheItem *cache_item; | 
					
						
							|  |  |  |   DWORD              result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (watch != NULL, FALSE); | 
					
						
							|  |  |  |   trace ("watch_add_notify: prefix %s.\n", gsettings_prefix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Duplicate tree into the cache in the main thread, before we add the notify: if we do it in the
 | 
					
						
							|  |  |  |    * thread we can miss changes while we are caching. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   EnterCriticalSection (self->cache_lock); | 
					
						
							|  |  |  |   cache_node = registry_cache_get_node_for_key (self->cache_root, gsettings_prefix, TRUE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_return_val_if_fail (cache_node != NULL, FALSE); | 
					
						
							|  |  |  |   g_return_val_if_fail (cache_node->data != NULL, FALSE); | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   cache_item = cache_node->data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cache_item->subscription_count ++; | 
					
						
							|  |  |  |   if (cache_item->subscription_count > 1) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       trace ("watch_add_notify: prefix %s already watched, %i subscribers.\n", | 
					
						
							|  |  |  |              gsettings_prefix, cache_item->subscription_count); | 
					
						
							|  |  |  |       return FALSE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   registry_cache_ref_tree (cache_node); | 
					
						
							|  |  |  |   registry_cache_update (self, hpath, gsettings_prefix, NULL, cache_node, 0, NULL); | 
					
						
							|  |  |  |   //registry_cache_dump (self->cache_root, NULL);
 | 
					
						
							|  |  |  |   LeaveCriticalSection (self->cache_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   EnterCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   watch->message.type = WATCH_THREAD_ADD_WATCH; | 
					
						
							|  |  |  |   watch->message.watch.event      = event; | 
					
						
							|  |  |  |   watch->message.watch.hpath      = hpath; | 
					
						
							|  |  |  |   watch->message.watch.prefix     = gsettings_prefix; | 
					
						
							|  |  |  |   watch->message.watch.cache_node = cache_node; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   SetEvent (watch->message_sent_event); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Wait for the received event in return, to avoid sending another message before the first
 | 
					
						
							|  |  |  |    * one was received. If it takes > 200ms there is a possible race but the worst outcome is | 
					
						
							|  |  |  |    * a notification is ignored. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   result = WaitForSingleObject (watch->message_received_event, 200); | 
					
						
							|  |  |  |   #ifdef TRACE
 | 
					
						
							|  |  |  |     if (result != WAIT_OBJECT_0) | 
					
						
							|  |  |  |       trace ("watch thread is slow to respond - notification may not be added."); | 
					
						
							|  |  |  |   #endif
 | 
					
						
							|  |  |  |   LeaveCriticalSection (watch->message_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return TRUE; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | watch_remove_notify (GRegistryBackend *self, | 
					
						
							|  |  |  |                      const gchar      *key_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   WatchThreadState *watch = self->watch; | 
					
						
							|  |  |  |   LONG     result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (self->watch == NULL) | 
					
						
							|  |  |  |     /* Here we assume that the unsubscribe message is for somewhere that was
 | 
					
						
							|  |  |  |      * deleted, and so it has already been removed and the watch thread has | 
					
						
							|  |  |  |      * stopped. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   EnterCriticalSection (watch->message_lock); | 
					
						
							|  |  |  |   watch->message.type = WATCH_THREAD_REMOVE_WATCH; | 
					
						
							|  |  |  |   watch->message.watch.prefix = g_strdup (key_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   SetEvent (watch->message_sent_event); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Wait for the received event in return, to avoid sending another message before the first
 | 
					
						
							|  |  |  |    * one was received. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   result = WaitForSingleObject (watch->message_received_event, INFINITE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     g_warning ("unsubscribe from %s: message not acknowledged\n", key_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (g_atomic_int_get (&watch->watches_remaining) >= MAX_WATCHES) | 
					
						
							|  |  |  |     /* Stop it before any new ones can get added and confuse things */ | 
					
						
							|  |  |  |     watch_stop_unlocked (self);  | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     LeaveCriticalSection (watch->message_lock); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that
 | 
					
						
							|  |  |  |  * key. Our job is easier because keys and values are separate. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_subscribe (GSettingsBackend *backend, | 
					
						
							|  |  |  |                               const char       *key_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend *self = G_REGISTRY_BACKEND (backend); | 
					
						
							|  |  |  |   gchar *path_name, *value_name = NULL; | 
					
						
							|  |  |  |   HKEY hpath; | 
					
						
							|  |  |  |   HANDLE event; | 
					
						
							|  |  |  |   LONG result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (self->watch == NULL) | 
					
						
							|  |  |  |     if (!watch_start (self)) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (g_atomic_int_dec_and_test (&self->watch->watches_remaining)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_atomic_int_inc (&self->watch->watches_remaining); | 
					
						
							|  |  |  |       g_warning ("subscribe() failed: only %i different paths may be watched.\n", MAX_WATCHES); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   path_name = parse_key (key_name, self->base_path, &value_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Must check for this, otherwise strange crashes occur because the cache
 | 
					
						
							|  |  |  |    * node that is being watched gets freed. All path names to subscribe must | 
					
						
							|  |  |  |    * end in a slash! | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   if (value_name != NULL && *value_name != 0) | 
					
						
							|  |  |  |     g_warning ("subscribe() failed: path must end in a /, got %s\n", key_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* Give the caller the benefit of the doubt if the key doesn't exist and create it. The caller
 | 
					
						
							|  |  |  |    * is almost certainly a new g_settings with this path as base path. */ | 
					
						
							|  |  |  |   result = RegCreateKeyExA (HKEY_CURRENT_USER, path_name, 0, NULL, 0, KEY_READ, NULL, &hpath, | 
					
						
							|  |  |  |                             NULL); | 
					
						
							|  |  |  |   g_free (path_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (result != ERROR_SUCCESS) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (result, "gregistrybackend: Unable to subscribe to key %s.", key_name); | 
					
						
							|  |  |  |       g_atomic_int_inc (&self->watch->watches_remaining); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   event = CreateEvent (NULL, FALSE, FALSE, NULL); | 
					
						
							|  |  |  |   if (event == NULL) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       g_message_win32_error (result, "gregistrybackend: CreateEvent failed.\n"); | 
					
						
							|  |  |  |       g_atomic_int_inc (&self->watch->watches_remaining); | 
					
						
							|  |  |  |       RegCloseKey (hpath); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* The actual watch is added by the thread, which has to re-subscribe each time it
 | 
					
						
							|  |  |  |    * receives a change. */ | 
					
						
							|  |  |  |   if (!watch_add_notify (self, event, hpath, g_strdup (key_name))) | 
					
						
							|  |  |  |     g_atomic_int_inc (&self->watch->watches_remaining); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_unsubscribe (GSettingsBackend *backend, | 
					
						
							|  |  |  |                                 const char       *key_name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   trace ("unsubscribe: %s.\n", key_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   watch_remove_notify (G_REGISTRY_BACKEND (backend), key_name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************************
 | 
					
						
							|  |  |  |  * Object management junk | 
					
						
							|  |  |  |  ********************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GSettingsBackend * | 
					
						
							|  |  |  | g_registry_backend_new (void) { | 
					
						
							|  |  |  |   return g_object_new (G_TYPE_REGISTRY_BACKEND, NULL); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_finalize (GObject *object) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GRegistryBackend  *self = G_REGISTRY_BACKEND (object); | 
					
						
							|  |  |  |   RegistryCacheItem *item; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   item = self->cache_root->data; | 
					
						
							|  |  |  |   g_warn_if_fail (item->ref_count == 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _free_cache_item (item); | 
					
						
							|  |  |  |   g_node_destroy (self->cache_root); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (self->watch != NULL) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       EnterCriticalSection (self->watch->message_lock); | 
					
						
							|  |  |  |       watch_stop_unlocked (self); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_free (self->base_path); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_class_init (GRegistryBackendClass *class) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class); | 
					
						
							|  |  |  |   GObjectClass *object_class = G_OBJECT_CLASS (class); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   object_class->finalize = g_registry_backend_finalize; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   backend_class->read = g_registry_backend_read; | 
					
						
							|  |  |  |   backend_class->write = g_registry_backend_write; | 
					
						
							|  |  |  |   backend_class->write_tree = g_registry_backend_write_tree; | 
					
						
							|  |  |  |   backend_class->reset = g_registry_backend_reset; | 
					
						
							|  |  |  |   backend_class->get_writable = g_registry_backend_get_writable; | 
					
						
							|  |  |  |   backend_class->get_permission = g_registry_backend_get_permission; | 
					
						
							|  |  |  |   backend_class->subscribe = g_registry_backend_subscribe; | 
					
						
							|  |  |  |   backend_class->unsubscribe = g_registry_backend_unsubscribe; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void | 
					
						
							|  |  |  | g_registry_backend_init (GRegistryBackend *self) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   RegistryCacheItem *item; | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   self->base_path = g_strdup_printf ("Software\\GSettings"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 20:36:53 +08:00
										 |  |  |   item = g_slice_new (RegistryCacheItem); | 
					
						
							| 
									
										
										
										
											2010-08-12 16:10:23 +01:00
										 |  |  |   item->value.type = REG_NONE; | 
					
						
							|  |  |  |   item->value.ptr = NULL; | 
					
						
							|  |  |  |   item->name = g_strdup ("<root>"); | 
					
						
							|  |  |  |   item->ref_count = 1; | 
					
						
							|  |  |  |   self->cache_root = g_node_new (item); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   self->watch = NULL; | 
					
						
							|  |  |  | } |