Commit 8c78267b5f67f644d581783142ed4d5486361900
- Diff rendering mode:
- inline
- side by side
gio/gregistrystorage.c
(669 / 46)
|   | |||
| 10 | 10 | * Authors: Sam Thursfield <ssssam@gmail.com> | |
| 11 | 11 | */ | |
| 12 | 12 | ||
| 13 | /* FIXME: | ||
| 14 | * - use g_idle_add instead of all of the pipe nonsense. | ||
| 15 | * - remove g_thread dependency. */ | ||
| 16 | |||
| 13 | 17 | /* GRegistryStorage implementation notes: | |
| 14 | 18 | * | |
| 15 | 19 | * - http://msdn.microsoft.com/en-us/library/ms724875%28VS.85%29.aspx | |
| … | … | ||
| 26 | 26 | * - Backslashes are used as path separators in the registry. Forward slashes are invalid in | |
| 27 | 27 | * gsettings key names, so we swap the two as a simple escape procedure. | |
| 28 | 28 | * | |
| 29 | * - Notifications are handled; the change event is watched for in a separate thread (Windows | ||
| 30 | * seems not to want to give a callback), which then sends them down a pipe to the GLib main | ||
| 31 | * loop. | ||
| 32 | * | ||
| 33 | * - The threading is done using Windows API functions, so there is no GThread dependence. | ||
| 34 | * | ||
| 35 | * - The pipe io is done with MSVCRT functions and glib IOChannels. This means the code will not | ||
| 36 | * work with Visual C past version 6 (unless you use a glib built with Visual C past version 6). | ||
| 37 | * There is no alternative, because the glib main loop reads from the pipe during the poll and | ||
| 38 | * then doesn't pass the data to the callback - so the only way to integrate a pipe into the | ||
| 39 | * main loop on Windows is to use a GIOChannel. | ||
| 40 | * | ||
| 41 | * - Windows doesn't tell us what key has changed. This means we have to maintain a cache of every | ||
| 42 | * stored value so we can play spot the difference. | ||
| 43 | * FIXME: this would become very slow for a GSettings with lots and lots of keys! The solution | ||
| 44 | * here is (assuming it's a tree and not a flat list, in which case we are stuck) to break the | ||
| 45 | * tree up into multiple watches, so we have more granularity about the notifications. Use non | ||
| 46 | * recursive notify in the root of the subscription (there's a parameter for that). Set up | ||
| 47 | * separate watches for each of the subkeys. | ||
| 48 | * | ||
| 49 | * - RegNotifyChangeKeyValue is called on a key after each change notification on that key. | ||
| 50 | * MSDN says that the notification only occurs once: | ||
| 51 | * http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx | ||
| 52 | * but MS support says that it occurs as long as the handle stays open: | ||
| 53 | * http://support.microsoft.com/kb/236570 | ||
| 54 | * the former seems to be true, at least on my system. | ||
| 55 | * | ||
| 56 | * - FIXME: there is an undocumented function in ntdll.dll which looks a lot better for this job: | ||
| 57 | * NtNotifyChangeKey: | ||
| 58 | * http://source.winehq.org/source/dlls/ntdll/reg.c#L618 | ||
| 59 | * http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Key/NtNotifyChangeKey.html | ||
| 60 | * It looks like the optional parameters could be 1. an APC notification mechanism (better than | ||
| 61 | * the watch thread presumably) and 2. a buffer given the changes that have occured. Of course, | ||
| 62 | * it's hard to find that out without some serious research and disassembly. And it's inherently | ||
| 63 | * a bit scary using undocumented Windows API. Still, it would be worth testing because it could | ||
| 64 | * make the code a whole lot cleaner .. | ||
| 65 | * | ||
| 29 | 66 | * - No attempt is made to solve clashes between keys differing only in case. You deserve to | |
| 30 | 67 | * suffer if you try to make keys that differ only by case. | |
| 31 | 68 | * | |
| 69 | * - When a buffer is used for a key name it is sized 256. That means key names can't be longer | ||
| 70 | * than 255 characters. If that's too little, talk to someone who uses twitter | ||
| 71 | * | ||
| 32 | 72 | * - FIXME: read failures should be handled better (not with g_warning) because in the registry, | |
| 33 | 73 | * we have to be open to the possibility of the user editing the registry by hand and being dumb | |
| 34 | * | ||
| 74 | * | ||
| 35 | 75 | * - No maximum size for binary data is enforced right now. See also: | |
| 36 | 76 | * http://msdn.microsoft.com/en-us/library/ms724872(VS.85).aspx | |
| 77 | * | ||
| 37 | 78 | */ | |
| 38 | 79 | ||
| 39 | 80 | /* - RegCreateKeyA is used - Windows can also handle UTF16LE strings. GSettings doesn't pay | |
| … | … | ||
| 83 | 83 | ||
| 84 | 84 | #include <stdarg.h> | |
| 85 | 85 | #include <stdio.h> | |
| 86 | #include <io.h> | ||
| 87 | #include <fcntl.h> | ||
| 88 | #include <unistd.h> | ||
| 86 | 89 | #include <windows.h> | |
| 87 | 90 | #include "gregistrystorage.h" | |
| 91 | #include "gmemstorage.h" | ||
| 88 | 92 | ||
| 93 | typedef struct _WatchThreadState WatchThreadState; | ||
| 94 | |||
| 95 | #define TRACE | ||
| 96 | |||
| 89 | 97 | struct _GRegistryStoragePrivate | |
| 90 | 98 | { | |
| 91 | 99 | char *base; | |
| 100 | |||
| 101 | WatchThreadState *watch; | ||
| 92 | 102 | }; | |
| 93 | 103 | ||
| 104 | static GSource *__g_fd_source_new (int fd, gushort events); | ||
| 105 | |||
| 94 | 106 | static void trace (const char *format, ...); | |
| 95 | 107 | ||
| 96 | 108 | G_DEFINE_TYPE (GRegistryStorage, g_registry_storage, G_TYPE_SETTINGS_STORAGE) | |
| 97 | 109 | ||
| 110 | /* g_warning including a windows error message. | ||
| 111 | * FIXME: g_warning is only appropriate for programmer errors - which most of these ones are, | ||
| 112 | * because they can't really fail otherwise, but we need to be wary of not abusing g_warning. */ | ||
| 113 | static void | ||
| 114 | g_warning_win32_error (DWORD result_code, | ||
| 115 | const gchar *format, | ||
| 116 | ...) | ||
| 117 | { | ||
| 118 | va_list va; | ||
| 119 | gint pos; | ||
| 120 | gchar win32_message[1024]; | ||
| 121 | |||
| 122 | if (result_code == 0) | ||
| 123 | result_code = GetLastError (); | ||
| 124 | |||
| 125 | va_start (va, format); | ||
| 126 | pos = g_vsnprintf (win32_message, 512, format, va); | ||
| 127 | |||
| 128 | win32_message[pos++] = ':'; win32_message[pos++] = ' '; | ||
| 129 | |||
| 130 | FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result_code, 0, (LPTSTR)(win32_message+pos), | ||
| 131 | 1023 - pos, NULL); | ||
| 132 | g_warning (win32_message); | ||
| 133 | }; | ||
| 134 | |||
| 98 | 135 | /* Make gsettings key into a registry key & value pair. In registry terminology, the 'value' is the | |
| 99 | * last component of the path, and the 'key' is the rest. The paths are \ separated. */ | ||
| 136 | * last component of the path, and the 'key' is the rest. The paths are \ separated. | ||
| 137 | * | ||
| 138 | * Note that the return value *only* needs freeing - registry_value_name is a pointer to further | ||
| 139 | * inside the same block of memory. | ||
| 140 | */ | ||
| 100 | 141 | static gchar * | |
| 101 | 142 | parse_key (const gchar *gsettings_key, | |
| 102 | 143 | const gchar *prefix, | |
| … | … | ||
| 263 | 263 | result = RegCreateKeyExA (root_key, key_path, 0, NULL, 0, KEY_WRITE, NULL, &key, NULL); | |
| 264 | 264 | if (result != ERROR_SUCCESS) | |
| 265 | 265 | { | |
| 266 | /* Error success is no error: no error success means error and no success */ | ||
| 267 | char message[512]; | ||
| 268 | FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result, 0, (LPTSTR)&message, 511, NULL); | ||
| 269 | g_warning ("gregistrystorage: opening key %s failed: %s\n", key_path+1, message); | ||
| 270 | HeapFree (GetProcessHeap (), 0, value_data); g_free (key_path); | ||
| 266 | g_warning_win32_error (result, "gregistrystorage: opening key %s failed", key_path+1); | ||
| 267 | g_free (value_data); g_free (key_path); | ||
| 271 | 268 | return FALSE; | |
| 272 | 269 | } | |
| 273 | 270 | ||
| 274 | 271 | result = RegSetValueExA (key, value_name, 0, value_type, value_data, value_data_size); | |
| 275 | 272 | if (result != ERROR_SUCCESS) | |
| 276 | g_warning ("gregistrystorage: setting value %s\%s\\%s failed.\n", self->priv->base, | ||
| 277 | key_path, value_name); | ||
| 273 | g_warning_win32_error (result, "gregistrystorage: setting value %s\%s\\%s failed.\n", | ||
| 274 | self->priv->base, key_path, value_name); | ||
| 278 | 275 | ||
| 279 | 276 | RegCloseKey (key); | |
| 280 | 277 | g_free (value_data); | |
| … | … | ||
| 290 | 290 | LONG result; | |
| 291 | 291 | HKEY root_key; | |
| 292 | 292 | ||
| 293 | /* The dconf policy is to do the write while making out it succeeded, and then backtrack if it | ||
| 294 | * didn't. The registry functions are synchronous so I assume we don't need to bother with this. | ||
| 295 | */ | ||
| 296 | |||
| 293 | 297 | result = RegCreateKeyExA (HKEY_CURRENT_USER, self->priv->base, 0, NULL, 0, | |
| 294 | 298 | KEY_WRITE, NULL, &root_key, NULL); | |
| 295 | 299 | if (result != ERROR_SUCCESS) { | |
| 296 | 300 | trace ("Error creating key %s.\n", self->priv->base); | |
| 297 | /* FIXME: what would dconf do? pretend that the value was set, but then notify that it was unset | ||
| 298 | * again after. */ | ||
| 299 | return; | ||
| 301 | return; | ||
| 300 | 302 | } | |
| 301 | 303 | ||
| 302 | 304 | const gchar **changes; | |
| … | … | ||
| 327 | 327 | const gchar *key_path, | |
| 328 | 328 | const gchar *value_name) | |
| 329 | 329 | { | |
| 330 | /* file not found means key value not set. */ | ||
| 330 | /* file not found means key value not set, this isn't an error for us. */ | ||
| 331 | 331 | if (result != ERROR_FILE_NOT_FOUND) | |
| 332 | { | ||
| 333 | char message[512]; | ||
| 334 | FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result, 0, (LPTSTR)&message, 511, NULL); | ||
| 335 | g_warning ("Unable to query value %s\\%s: %s.\n", key_path, value_name, message); | ||
| 336 | } | ||
| 332 | g_warning_win32_error (result, "Unable to query value %s\\%s: %s.\n", key_path, value_name); | ||
| 337 | 333 | } | |
| 338 | 334 | ||
| 339 | 335 | static GVariant * | |
| … | … | ||
| 339 | 339 | const gchar *expected_type) | |
| 340 | 340 | { | |
| 341 | 341 | GVariant *variant = NULL; | |
| 342 | DWORD value, value_type, value_data_size = 4; | ||
| 342 | DWORD value, value_data_size = 4; | ||
| 343 | 343 | LONG result; | |
| 344 | 344 | ||
| 345 | result = RegQueryValueExA (key, value_name, 0, &value_type, (LPBYTE)&value, &value_data_size); | ||
| 345 | result = RegQueryValueExA (key, value_name, 0, NULL, (LPBYTE)&value, &value_data_size); | ||
| 346 | 346 | if (result != ERROR_SUCCESS) | |
| 347 | 347 | { | |
| 348 | 348 | handle_read_error (result, key_path, value_name); | |
| 349 | 349 | return NULL; | |
| 350 | 350 | } | |
| 351 | 351 | ||
| 352 | /* This is allowed because the watch thread cache doesn't need to store variants as the correct | ||
| 353 | * type - just a consistent type so they can be compared. */ | ||
| 354 | if (expected_type == NULL) | ||
| 355 | expected_type = g_variant_type_peek_string (G_VARIANT_TYPE_INT32); | ||
| 356 | |||
| 352 | 357 | switch (expected_type[0]) | |
| 353 | 358 | { | |
| 354 | 359 | case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': | |
| 355 | if (value_type == REG_DWORD) | ||
| 356 | variant = g_variant_new (expected_type, value); | ||
| 360 | variant = g_variant_new (expected_type, value); | ||
| 357 | 361 | break; | |
| 358 | 362 | ||
| 359 | 363 | default: | |
| 360 | g_warn_if_reached (); | ||
| 364 | g_warning ("gregistrystorage: %s/%s: got dword, expected %s.\n", key_path, value_name, | ||
| 365 | expected_type); | ||
| 361 | 366 | } | |
| 362 | 367 | ||
| 363 | 368 | return variant; | |
| … | … | ||
| 375 | 375 | const gchar *expected_type) | |
| 376 | 376 | { | |
| 377 | 377 | GVariant *variant = NULL; | |
| 378 | guint64 value; DWORD value_type, value_data_size = 8; | ||
| 378 | guint64 value; DWORD value_data_size = 8; | ||
| 379 | 379 | LONG result; | |
| 380 | 380 | ||
| 381 | result = RegQueryValueExA (key, value_name, 0, &value_type, (LPBYTE)&value, &value_data_size); | ||
| 381 | result = RegQueryValueExA (key, value_name, 0, NULL, (LPBYTE)&value, &value_data_size); | ||
| 382 | 382 | if (result != ERROR_SUCCESS) | |
| 383 | 383 | { | |
| 384 | 384 | handle_read_error (result, key_path, value_name); | |
| 385 | 385 | return NULL; | |
| 386 | 386 | } | |
| 387 | 387 | ||
| 388 | if (expected_type == NULL) | ||
| 389 | expected_type = g_variant_type_peek_string (G_VARIANT_TYPE_INT64); | ||
| 390 | |||
| 388 | 391 | switch (expected_type[0]) | |
| 389 | 392 | { | |
| 390 | 393 | case 't': case 'x': | |
| 391 | if (value_type == REG_QWORD) | ||
| 392 | variant = g_variant_new (expected_type, value); | ||
| 394 | variant = g_variant_new (expected_type, value); | ||
| 393 | 395 | break; | |
| 394 | 396 | ||
| 395 | 397 | default: | |
| 396 | g_warn_if_reached (); | ||
| 398 | g_warning ("gregistrystorage: %s/%s: got qword, expected %s.\n", key_path, value_name, | ||
| 399 | expected_type); | ||
| 397 | 400 | } | |
| 398 | 401 | ||
| 399 | 402 | return variant; | |
| … | … | ||
| 409 | 409 | const gchar *expected_type) | |
| 410 | 410 | { | |
| 411 | 411 | GVariant *variant = NULL; | |
| 412 | gchar *value; DWORD value_type, value_data_size; | ||
| 412 | gchar *value; DWORD value_data_size; | ||
| 413 | 413 | LONG result; | |
| 414 | 414 | ||
| 415 | if (expected_type != NULL && expected_type[0] != 's') | ||
| 416 | { | ||
| 417 | g_warning ("gregistrystorage: %s/%s: got string, expected %s.\n", key_path, value_name, | ||
| 418 | expected_type); | ||
| 419 | return NULL; | ||
| 420 | } | ||
| 421 | |||
| 415 | 422 | /* First, find length of the string */ | |
| 416 | result = RegQueryValueExA (key, value_name, 0, &value_type, NULL, &value_data_size); | ||
| 423 | result = RegQueryValueExA (key, value_name, 0, NULL, NULL, &value_data_size); | ||
| 417 | 424 | if (result != ERROR_SUCCESS) | |
| 418 | 425 | { | |
| 419 | 426 | handle_read_error (result, key_path, value_name); | |
| … | … | ||
| 439 | 439 | if (value[value_data_size] != 0) | |
| 440 | 440 | value[value_data_size + 1] = 0; | |
| 441 | 441 | ||
| 442 | if (value_type != REG_SZ) | ||
| 443 | return NULL; | ||
| 442 | variant = g_variant_new ("s", value); | ||
| 444 | 443 | ||
| 445 | variant = g_variant_new (expected_type, value); | ||
| 446 | |||
| 447 | 444 | return variant; | |
| 448 | 445 | } | |
| 449 | 446 | ||
| … | … | ||
| 452 | 452 | GRegistryStorage *self = G_REGISTRY_STORAGE (s_storage); | |
| 453 | 453 | gchar *key_path, *value_name; | |
| 454 | 454 | HKEY registry_key; LONG result; | |
| 455 | const gchar *variant_type = g_variant_type_peek_string (expected_type); | ||
| 456 | GVariant *variant; | ||
| 455 | DWORD registry_type; | ||
| 456 | const gchar *variant_type = NULL; | ||
| 457 | GVariant *variant = NULL; | ||
| 457 | 458 | ||
| 459 | if (expected_type != NULL) | ||
| 460 | variant_type = g_variant_type_peek_string (expected_type); | ||
| 458 | 461 | key_path = parse_key (key, self->priv->base, &value_name); | |
| 459 | 462 | trace ("Reading key %s / %s\n", key_path, value_name); | |
| 460 | 463 | ||
| … | … | ||
| 466 | 466 | { | |
| 467 | 467 | if (result != ERROR_FILE_NOT_FOUND) | |
| 468 | 468 | /* Warn if the error was something other than the key not existing yet. */ | |
| 469 | g_warning ("Unable to open key %s.", key_path); | ||
| 470 | g_free (key_path); return NULL; | ||
| 469 | g_warning_win32_error (result, "Unable to open key %s.", key_path); | ||
| 470 | goto fail_1; | ||
| 471 | 471 | } | |
| 472 | 472 | ||
| 473 | /* Read the type first, so we use the correct buffer size. */ | ||
| 474 | result = RegQueryValueExA (registry_key, value_name, 0, ®istry_type, NULL, NULL); | ||
| 475 | if (result != ERROR_SUCCESS) | ||
| 476 | { | ||
| 477 | g_warning_win32_error (result, "Unable to read type of key %s\n", key_path); | ||
| 478 | goto fail_2; | ||
| 479 | } | ||
| 480 | |||
| 473 | 481 | /* FIXME: the registry is user-editable, so we need to be very fault-tolerant here. */ | |
| 474 | switch (variant_type[0]) | ||
| 482 | switch (registry_type) | ||
| 475 | 483 | { | |
| 476 | case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': | ||
| 484 | case REG_DWORD: | ||
| 477 | 485 | variant = g_registry_storage_read_dword (registry_key, key_path, value_name, | |
| 478 | 486 | variant_type); | |
| 479 | 487 | break; | |
| 480 | 488 | ||
| 481 | case 'x': case 't': | ||
| 489 | case REG_QWORD: | ||
| 482 | 490 | variant = g_registry_storage_read_qword (registry_key, key_path, value_name, | |
| 483 | 491 | variant_type); | |
| 484 | 492 | break; | |
| 485 | 493 | ||
| 486 | case 's': case 'o': case 'g': | ||
| 494 | case REG_SZ: | ||
| 487 | 495 | variant = g_registry_storage_read_string (registry_key, key_path, value_name, | |
| 488 | variant_type); | ||
| 496 | variant_type); | ||
| 489 | 497 | break; | |
| 490 | 498 | ||
| 491 | 499 | default: | |
| 492 | variant = NULL; | ||
| 493 | 500 | g_warning ("gregistrystorage: Unable to read key of type %s.\n", variant_type); | |
| 494 | 501 | break; | |
| 495 | 502 | } | |
| 496 | 503 | ||
| 504 | fail_2: | ||
| 497 | 505 | RegCloseKey (registry_key); | |
| 506 | fail_1: | ||
| 498 | 507 | g_free (key_path); | |
| 499 | 508 | return variant; | |
| 500 | 509 | } | |
| … | … | ||
| 515 | 515 | return TRUE; | |
| 516 | 516 | } | |
| 517 | 517 | ||
| 518 | /* | ||
| 519 | * Thread to watch for registry change events. This is hopelessly complicated :( | ||
| 520 | */ | ||
| 521 | |||
| 522 | /* This struct represents a watch on one registry key and its subkeys */ | ||
| 523 | typedef struct | ||
| 524 | { | ||
| 525 | HANDLE event; | ||
| 526 | HKEY key; | ||
| 527 | char *prefix; | ||
| 528 | } RegistryWatch; | ||
| 529 | |||
| 530 | /* One of these is sent down the pipe when something happens in the registry. */ | ||
| 531 | typedef struct | ||
| 532 | { | ||
| 533 | /* prefix is a gsettings path, all items are subkeys of this. */ | ||
| 534 | gchar *prefix; | ||
| 535 | |||
| 536 | /* each item is a subkey below prefix that has changed. */ | ||
| 537 | GArray *items; | ||
| 538 | } RegistryEvent; | ||
| 539 | |||
| 540 | /* Simple message passing for the thread. I didn't bother with a queue. FIXME: there's no reason | ||
| 541 | * not to use GQueue. | ||
| 542 | */ | ||
| 543 | typedef enum | ||
| 544 | { | ||
| 545 | WATCH_THREAD_ADD_WATCH, | ||
| 546 | WATCH_THREAD_REMOVE_WATCH, | ||
| 547 | WATCH_THREAD_STOP | ||
| 548 | } WatchThreadMessageType; | ||
| 549 | |||
| 550 | typedef struct | ||
| 551 | { | ||
| 552 | WatchThreadMessageType type; | ||
| 553 | RegistryWatch watch; | ||
| 554 | } WatchThreadMessage; | ||
| 555 | |||
| 556 | struct _WatchThreadState | ||
| 557 | { | ||
| 558 | HANDLE *thread; | ||
| 559 | |||
| 560 | /* Details of the things we are watching. */ | ||
| 561 | GPtrArray *events, *keys, *prefixes; | ||
| 562 | |||
| 563 | /* A stored copy of the whole tree being watched. When we receive a change notification | ||
| 564 | * we have to check against this to see what has changed ... every time ...*/ | ||
| 565 | GSettingsStorage *cache; | ||
| 566 | |||
| 567 | /* Output to the main thread and main loop - the write end of a pipe. */ | ||
| 568 | int pipe_write; | ||
| 569 | |||
| 570 | /* Input from the main thread. Windows threading doesn't have conds, so to make sure the | ||
| 571 | * messages are acknowledged before being overwritten we create two events - one is signalled | ||
| 572 | * when a new message is set, and the other is signalled by the thread when it has processed the | ||
| 573 | * message. | ||
| 574 | */ | ||
| 575 | WatchThreadMessage message; | ||
| 576 | CRITICAL_SECTION *message_lock, *cache_lock; | ||
| 577 | HANDLE message_sent_event, message_received_event; | ||
| 578 | }; | ||
| 579 | |||
| 580 | |||
| 581 | static gboolean | ||
| 582 | watch_handler (RegistryEvent *event) | ||
| 583 | { | ||
| 584 | trace ("Watch handler: got event in %s.\n", event->prefix); | ||
| 585 | g_free (event->prefix); | ||
| 586 | g_slice_free (RegistryEvent, event); | ||
| 587 | return FALSE; | ||
| 588 | }; | ||
| 589 | |||
| 590 | /* Called by watch thread. */ | ||
| 591 | static gboolean | ||
| 592 | watch_key (HKEY key, HANDLE event, gchar *key_path) | ||
| 593 | { | ||
| 594 | DWORD result; | ||
| 595 | result = RegNotifyChangeKeyValue (key, TRUE, | ||
| 596 | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, | ||
| 597 | event, TRUE); | ||
| 598 | if (result != ERROR_SUCCESS) | ||
| 599 | { | ||
| 600 | if (result == ERROR_KEY_DELETED) | ||
| 601 | { | ||
| 602 | /* Someone has called RegDeleteKey here, and Windows is waiting until all of the handles | ||
| 603 | * are closed to carry out the deletion. | ||
| 604 | */ | ||
| 605 | } | ||
| 606 | else | ||
| 607 | g_warning_win32_error (result, "gregistrystorage: unable to watch prefix %s\n", key_path); | ||
| 608 | CloseHandle (event); | ||
| 609 | RegCloseKey (key); | ||
| 610 | g_free (key_path); | ||
| 611 | return FALSE; | ||
| 612 | } | ||
| 613 | return TRUE; | ||
| 614 | } | ||
| 615 | |||
| 616 | /* Thread which watches for win32 registry events */ | ||
| 617 | static DWORD WINAPI | ||
| 618 | watch_thread_function (LPVOID parameter) | ||
| 619 | { | ||
| 620 | WatchThreadState *self = (WatchThreadState *)parameter; | ||
| 621 | DWORD result; | ||
| 622 | |||
| 623 | self->events = g_ptr_array_new (); | ||
| 624 | self->keys = g_ptr_array_new (); | ||
| 625 | self->prefixes = g_ptr_array_new (); | ||
| 626 | g_ptr_array_add (self->events, self->message_sent_event); | ||
| 627 | g_ptr_array_add (self->keys, NULL); | ||
| 628 | g_ptr_array_add (self->prefixes, NULL); | ||
| 629 | |||
| 630 | while (1) | ||
| 631 | { | ||
| 632 | trace ("watch thread: going to sleep; %i events watched.\n", self->events->len); | ||
| 633 | result = WaitForMultipleObjects (self->events->len, self->events->pdata, FALSE, /*INFINITE*/1000); | ||
| 634 | |||
| 635 | if (result == WAIT_OBJECT_0) | ||
| 636 | { | ||
| 637 | /* A message to you. The sender (main thread) will block until we signal the received | ||
| 638 | * event, so there should be no danger of it sending another before we receive the | ||
| 639 | * first. | ||
| 640 | */ | ||
| 641 | switch (self->message.type) | ||
| 642 | { | ||
| 643 | case WATCH_THREAD_ADD_WATCH: | ||
| 644 | { | ||
| 645 | RegistryWatch *watch = &self->message.watch; | ||
| 646 | if (watch_key (watch->key, watch->event, watch->prefix)) | ||
| 647 | { | ||
| 648 | g_ptr_array_add (self->events, watch->event); | ||
| 649 | g_ptr_array_add (self->keys, watch->key); | ||
| 650 | g_ptr_array_add (self->prefixes, watch->prefix); | ||
| 651 | } | ||
| 652 | break; | ||
| 653 | } | ||
| 654 | case WATCH_THREAD_REMOVE_WATCH: | ||
| 655 | break; | ||
| 656 | case WATCH_THREAD_STOP: | ||
| 657 | SetEvent (self->message_received_event); | ||
| 658 | /* FIXME: send a null event down the pipe so the main loop can close its handle */ | ||
| 659 | ExitThread (0); | ||
| 660 | } | ||
| 661 | SetEvent (self->message_received_event); | ||
| 662 | } | ||
| 663 | else if (result > WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + self->events->len) | ||
| 664 | { | ||
| 665 | RegistryEvent *event = g_slice_new (RegistryEvent); | ||
| 666 | HKEY key; | ||
| 667 | HANDLE cond; | ||
| 668 | gchar *prefix; | ||
| 669 | /*DWORD bytes_written = 0;*/ | ||
| 670 | size_t bytes_written = 0; | ||
| 671 | |||
| 672 | /* One of our notifications has triggered. All we know is which one, and which key | ||
| 673 | * this is for. We do most of the processing here, because we may as well. If the | ||
| 674 | * registry changes further while we are processing it doesn't matter - we will then | ||
| 675 | * receive another change notification from the OS anyway. | ||
| 676 | */ | ||
| 677 | gint notify_index = result - WAIT_OBJECT_0; | ||
| 678 | key = g_ptr_array_index (self->keys, notify_index); | ||
| 679 | cond = g_ptr_array_index (self->events, notify_index); | ||
| 680 | prefix = g_ptr_array_index (self->prefixes, notify_index); | ||
| 681 | |||
| 682 | trace ("Watch thread: notify received on prefix: %s.\n", prefix); | ||
| 683 | |||
| 684 | /* Firstly we need to reapply for the notification. FIXME: MSDN says you don't somewhere */ | ||
| 685 | if (!watch_key (key, cond, prefix)) | ||
| 686 | { | ||
| 687 | /* Error, or the key has been deleted. Stop watching it. The rest of the cleanup is | ||
| 688 | * done by watch_key(). */ | ||
| 689 | /* FIXME: need to remove it from the cache .. */ | ||
| 690 | g_ptr_array_remove_index (self->keys, notify_index); | ||
| 691 | g_ptr_array_remove_index (self->events, notify_index); | ||
| 692 | g_ptr_array_remove_index (self->prefixes, notify_index); | ||
| 693 | }; | ||
| 694 | |||
| 695 | event->prefix = g_strdup (prefix); | ||
| 696 | |||
| 697 | /* FIXME: filter keys against stored copies (from when the notify was added) and only | ||
| 698 | * notify on keys that have changed. Update the stored copy as we go. | ||
| 699 | */ | ||
| 700 | |||
| 701 | g_idle_add ((GSourceFunc) watch_handler, event); | ||
| 702 | /*bytes_written = write (self->pipe_write, &event, sizeof (RegistryEvent)); | ||
| 703 | if (bytes_written != sizeof (RegistryEvent)) | ||
| 704 | g_warning ("gregistrystorage: watch thread: error sending event: %i.\n", errno);*/ | ||
| 705 | } | ||
| 706 | else | ||
| 707 | { | ||
| 708 | /* God knows what has happened */ | ||
| 709 | trace ("watch thread: Unexpected result from WaitForMultipleObjects: %d\n", result); | ||
| 710 | } | ||
| 711 | } | ||
| 712 | } | ||
| 713 | |||
| 714 | /* Handler called for registry events once received inside the glib main loop */ | ||
| 715 | /*static gboolean | ||
| 716 | watch_handler (GIOChannel *source, | ||
| 717 | GIOCondition condition, | ||
| 718 | gpointer data) | ||
| 719 | { | ||
| 720 | GRegistryStorage *self = G_REGISTRY_STORAGE (data); | ||
| 721 | RegistryEvent event; | ||
| 722 | gsize bytes_read; | ||
| 723 | GIOStatus status; | ||
| 724 | GError *error = NULL; | ||
| 725 | |||
| 726 | trace ("watch handler: got condition %i. in %i, out %i, err %i, hup %i\n", condition, G_IO_IN, | ||
| 727 | G_IO_OUT, G_IO_ERR, G_IO_HUP); | ||
| 728 | |||
| 729 | trace ("watch handler: Reading from pipe..."); | ||
| 730 | status = g_io_channel_read_chars (source, (gchar *)&event, sizeof (RegistryEvent), &bytes_read, | ||
| 731 | &error); | ||
| 732 | trace ("...done (%i bytes), status %i.\n", bytes_read, status); | ||
| 733 | if (bytes_read != sizeof (RegistryEvent) || status != G_IO_STATUS_NORMAL) | ||
| 734 | { | ||
| 735 | g_warning ("gregistrystorage: Could not read data from pipe: %s", error->message); | ||
| 736 | return TRUE; | ||
| 737 | } | ||
| 738 | |||
| 739 | if (event.prefix == NULL) | ||
| 740 | { | ||
| 741 | /* NULL event is HUP *//* | ||
| 742 | trace ("watch handler: received hangup.\n"); | ||
| 743 | g_io_channel_unref (source); | ||
| 744 | return FALSE; | ||
| 745 | } | ||
| 746 | |||
| 747 | trace ("watch_handler: received a notify: %s\n", event.prefix); | ||
| 748 | |||
| 749 | // ULTIMATE GOAL: call this (from the main thread) | ||
| 750 | // g_settings_storage_changed (G_SETTINGS_STORAGE (self), key + strlen(self->priv->base), | ||
| 751 | // items, n_items, NULL); | ||
| 752 | g_free (event.prefix); | ||
| 753 | |||
| 754 | return TRUE; | ||
| 755 | };*/ | ||
| 756 | |||
| 757 | static gboolean | ||
| 758 | watch_start (GRegistryStorage *self) | ||
| 759 | { | ||
| 760 | WatchThreadState *watch; | ||
| 761 | GIOChannel *pipe_read; | ||
| 762 | int tmp[2]; | ||
| 763 | |||
| 764 | g_return_val_if_fail (self->priv->watch == NULL, FALSE); | ||
| 765 | |||
| 766 | watch = g_slice_new (WatchThreadState); | ||
| 767 | |||
| 768 | if (_pipe (tmp, 256, _O_BINARY) != 0) | ||
| 769 | { | ||
| 770 | g_warning ("gregistrystorage: Failed to create pipe for watch thread: %i", errno); | ||
| 771 | goto fail_1; | ||
| 772 | } | ||
| 773 | watch->pipe_write = tmp[1]; | ||
| 774 | pipe_read = g_io_channel_win32_new_fd (tmp[0]); | ||
| 775 | /* FIXME: what buffer size do we get? */ | ||
| 776 | g_io_channel_set_encoding (pipe_read, NULL, NULL); | ||
| 777 | |||
| 778 | watch->cache_lock = g_slice_new (CRITICAL_SECTION); | ||
| 779 | InitializeCriticalSection (watch->cache_lock); | ||
| 780 | watch->message_lock = g_slice_new (CRITICAL_SECTION); | ||
| 781 | InitializeCriticalSection (watch->message_lock); | ||
| 782 | watch->message_sent_event = CreateEvent (NULL, FALSE, FALSE, NULL); | ||
| 783 | watch->message_received_event = CreateEvent (NULL, FALSE, FALSE, NULL); | ||
| 784 | if (watch->message_sent_event == NULL || watch->message_received_event == NULL) | ||
| 785 | { | ||
| 786 | g_warning_win32_error (0, "gregistrystorage: Failed to create sync objects."); | ||
| 787 | goto fail_2; | ||
| 788 | } | ||
| 789 | |||
| 790 | /* Use a small stack to make the thread more lightweight. */ | ||
| 791 | watch->thread = CreateThread (NULL, 1024, watch_thread_function, watch, 0, NULL); | ||
| 792 | if (watch->thread == NULL) | ||
| 793 | { | ||
| 794 | g_warning_win32_error (0, "gregistrystorage: Failed to create notify watch thread."); | ||
| 795 | goto fail_3; | ||
| 796 | } | ||
| 797 | |||
| 798 | watch->cache = g_mem_storage_new (); | ||
| 799 | |||
| 800 | self->priv->watch = watch; | ||
| 801 | |||
| 802 | /* Add a source to the glib main loop to read from the other end of the pipe and call | ||
| 803 | * g_settings_storage_changed for us. | ||
| 804 | */ | ||
| 805 | g_io_add_watch (pipe_read, G_IO_ERR | G_IO_HUP | G_IO_IN, watch_handler, self); | ||
| 806 | |||
| 807 | return TRUE; | ||
| 808 | |||
| 809 | fail_3: | ||
| 810 | DeleteCriticalSection (watch->message_lock); | ||
| 811 | CloseHandle (watch->message_sent_event); CloseHandle (watch->message_received_event); | ||
| 812 | fail_2: | ||
| 813 | close (watch->pipe_write); | ||
| 814 | g_io_channel_unref (pipe_read); | ||
| 815 | fail_1: | ||
| 816 | g_slice_free (WatchThreadState, watch); | ||
| 817 | return FALSE; | ||
| 818 | } | ||
| 819 | |||
| 518 | 820 | static void | |
| 519 | g_registry_storage_subscribe (GSettingsStorage *base, | ||
| 821 | watch_stop (GRegistryStorage *self) | ||
| 822 | { | ||
| 823 | WatchThreadState *watch = self->priv->watch; | ||
| 824 | RegistryEvent null_event = { 0 }; | ||
| 825 | DWORD result; | ||
| 826 | g_return_if_fail (watch != NULL); | ||
| 827 | |||
| 828 | EnterCriticalSection (watch->message_lock); | ||
| 829 | |||
| 830 | watch->message.type = WATCH_THREAD_STOP; | ||
| 831 | SetEvent (watch->message_sent_event); | ||
| 832 | |||
| 833 | /* Give a long delay - we need the thread to have stopped before the function returns. This | ||
| 834 | * function will probably only be called at the end of the program - while any GSettings objects | ||
| 835 | * exist, they will be subscribed to changes on their keys. If the stop fails, we don't free | ||
| 836 | * any data. | ||
| 837 | */ | ||
| 838 | result = WaitForSingleObject (watch->message_received_event, 250); | ||
| 839 | if (result != WAIT_OBJECT_0) | ||
| 840 | { | ||
| 841 | g_warning ("gregistrystorage: unable to stop watch thread."); | ||
| 842 | return; | ||
| 843 | } | ||
| 844 | LeaveCriticalSection (watch->message_lock); | ||
| 845 | LeaveCriticalSection (watch->cache_lock); | ||
| 846 | |||
| 847 | /* Send null event down the pipe to close the other end. | ||
| 848 | * FIXME: could we sent HUP signal? there doesn't seem to be a way. */ | ||
| 849 | write (watch->pipe_write, &null_event, sizeof(RegistryEvent)); | ||
| 850 | |||
| 851 | /* Once we get a message received event, we know the thread will not access any more of its | ||
| 852 | * data. | ||
| 853 | */ | ||
| 854 | CloseHandle (watch->thread); | ||
| 855 | g_object_unref (watch->cache); | ||
| 856 | DeleteCriticalSection (watch->message_lock); | ||
| 857 | CloseHandle (watch->message_sent_event); CloseHandle (watch->message_received_event); | ||
| 858 | close (watch->pipe_write); | ||
| 859 | g_slice_free (WatchThreadState, watch); | ||
| 860 | }; | ||
| 861 | |||
| 862 | static void | ||
| 863 | add_prefix_to_cache (GRegistryStorage *self, | ||
| 864 | GSettingsStorage *cache, | ||
| 865 | HKEY key, | ||
| 866 | const gchar *gsettings_prefix) | ||
| 867 | { | ||
| 868 | /* FIXME: we copy stuff into the cache even if a prefix above it is already in there, | ||
| 869 | * which is pointless */ | ||
| 870 | /* FIXME: also, a much better way to do this (with another use case I can think of) would be to | ||
| 871 | * implement a g_settings_copy function */ | ||
| 872 | gchar buffer[256]; | ||
| 873 | LONG result; | ||
| 874 | int i; | ||
| 875 | |||
| 876 | /* Recurse into each subkey */ | ||
| 877 | i = 0; | ||
| 878 | trace ("Mirroring subkeys .. "); | ||
| 879 | while (1) | ||
| 880 | { | ||
| 881 | DWORD buffer_size = 255; | ||
| 882 | HKEY subkey; | ||
| 883 | result = RegEnumKeyEx (key, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); | ||
| 884 | if (result != ERROR_SUCCESS) | ||
| 885 | break; | ||
| 886 | result = RegOpenKeyEx (key, buffer, 0, KEY_READ, &subkey); | ||
| 887 | if (result == ERROR_SUCCESS) | ||
| 888 | { | ||
| 889 | char *gsettings_subprefix = g_build_path ("/", gsettings_prefix, buffer, NULL); | ||
| 890 | add_prefix_to_cache (self, cache, subkey, gsettings_subprefix); | ||
| 891 | g_free (gsettings_subprefix); | ||
| 892 | } | ||
| 893 | RegCloseKey (subkey); | ||
| 894 | } | ||
| 895 | |||
| 896 | if (result != ERROR_NO_MORE_ITEMS) | ||
| 897 | g_warning_win32_error (result, "gregistrystorage: error enumerating subkeys for cache."); | ||
| 898 | |||
| 899 | /* Add each value. We access 'self' here (outside the main thread), which is okay - we | ||
| 900 | * only read from self->priv, and it won't be freed until this thread quits. */ | ||
| 901 | i = 0; | ||
| 902 | trace ("mirroring values .. \n"); | ||
| 903 | while (1) | ||
| 904 | { | ||
| 905 | GVariant *value; | ||
| 906 | GTree *tree; | ||
| 907 | gchar *gsettings_key, *c; | ||
| 908 | DWORD buffer_size = 255; | ||
| 909 | result = RegEnumValue (key, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); | ||
| 910 | if (result != ERROR_SUCCESS) | ||
| 911 | break; | ||
| 912 | trace ("Got: %s\n", buffer); | ||
| 913 | |||
| 914 | /* \ in keys is escaped as / in the registry, / isn't allowed in gsettings key names */ | ||
| 915 | for (c=buffer; *c!=0; c++) | ||
| 916 | if (*c=='/') *c = '\\'; | ||
| 917 | gsettings_key = g_build_path ("/", gsettings_prefix, buffer, NULL); | ||
| 918 | |||
| 919 | /* Without an 'expected_type', the return value could be of the wrong type. This doesn't | ||
| 920 | * matter, because when we query the changes to check against the cache they will also be of | ||
| 921 | * the wrong type, so as long as we're consistent we're okay. We only need to pass the names | ||
| 922 | * of the changed keys to gsettings. | ||
| 923 | */ | ||
| 924 | value = g_registry_storage_read (G_SETTINGS_STORAGE (self), gsettings_key, NULL); | ||
| 925 | |||
| 926 | tree = g_settings_storage_new_tree (); | ||
| 927 | g_tree_insert (tree, g_strdup (""), g_variant_ref_sink (value)); | ||
| 928 | g_settings_storage_write (cache, gsettings_key, tree, NULL); | ||
| 929 | g_tree_unref (tree); | ||
| 930 | |||
| 931 | g_free (gsettings_key); | ||
| 932 | } | ||
| 933 | |||
| 934 | if (result != ERROR_NO_MORE_ITEMS) | ||
| 935 | g_warning_win32_error (result, "gregistrystorage: error enumerating values for cache."); | ||
| 936 | trace ("Mirrored current state.\n"); | ||
| 937 | } | ||
| 938 | |||
| 939 | static gboolean | ||
| 940 | watch_add_notify (GRegistryStorage *self, | ||
| 941 | HANDLE event, | ||
| 942 | HKEY registry_key, | ||
| 943 | gchar *gsettings_prefix, | ||
| 944 | gchar *registry_key_path, | ||
| 945 | gchar *registry_value_name) | ||
| 946 | { | ||
| 947 | WatchThreadState *watch = self->priv->watch; | ||
| 948 | DWORD result; | ||
| 949 | |||
| 950 | g_return_val_if_fail (watch != NULL, FALSE); | ||
| 951 | |||
| 952 | /* Duplicate tree into the cache in the main thread, before we add the notify: if we do it in the | ||
| 953 | * thread we can miss changes while we are caching. | ||
| 954 | * FIXME: There is a danger of blocking the app ui when doing this for a big tree. | ||
| 955 | */ | ||
| 956 | EnterCriticalSection (watch->cache_lock); | ||
| 957 | add_prefix_to_cache (self, watch->cache, registry_key, gsettings_prefix); | ||
| 958 | LeaveCriticalSection (watch->cache_lock); | ||
| 959 | |||
| 960 | EnterCriticalSection (watch->message_lock); | ||
| 961 | |||
| 962 | watch->message.type = WATCH_THREAD_ADD_WATCH; | ||
| 963 | watch->message.watch.event = event; | ||
| 964 | watch->message.watch.key = registry_key; | ||
| 965 | watch->message.watch.prefix = gsettings_prefix; | ||
| 966 | /* FIXME: not used */ | ||
| 967 | g_free (registry_key_path); | ||
| 968 | |||
| 969 | SetEvent (watch->message_sent_event); | ||
| 970 | |||
| 971 | /* Wait for the received event in return, to avoid sending another message before the first | ||
| 972 | * one was received. Note this is only a small timeout, because there's no point freezing the | ||
| 973 | * app for ages when the only possible race error is that a notification doesn't get added, which | ||
| 974 | * is hardly the end of the world. | ||
| 975 | */ | ||
| 976 | result = WaitForSingleObject (watch->message_received_event, 20); | ||
| 977 | if (result != WAIT_OBJECT_0) | ||
| 978 | g_warning ("gregistrystorage: watch thread is slow to respond - race conditions may occur."); | ||
| 979 | LeaveCriticalSection (watch->message_lock); | ||
| 980 | return TRUE; | ||
| 981 | }; | ||
| 982 | |||
| 983 | |||
| 984 | /* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that | ||
| 985 | * key. Our job is easier because keys and values are separate. | ||
| 986 | */ | ||
| 987 | static void | ||
| 988 | g_registry_storage_subscribe (GSettingsStorage *settings_storage, | ||
| 520 | 989 | const char *name) | |
| 521 | 990 | { | |
| 991 | GRegistryStorage *self = G_REGISTRY_STORAGE (settings_storage); | ||
| 992 | gchar *key_path, *value_name; | ||
| 993 | HKEY registry_key; | ||
| 994 | HANDLE event; | ||
| 995 | LONG result; | ||
| 996 | |||
| 997 | if (self->priv->watch == NULL) | ||
| 998 | if (!watch_start (self)) | ||
| 999 | return; | ||
| 1000 | |||
| 1001 | key_path = parse_key (name, self->priv->base, &value_name); | ||
| 1002 | trace ("Subscribing to key %s / %s\n", key_path, value_name); | ||
| 1003 | |||
| 1004 | /* Give the caller the benefit of the doubt if the key doesn't exist and create it. The caller | ||
| 1005 | * is almost certainly a new g_settings with this path as base path. */ | ||
| 1006 | result = RegCreateKeyExA (HKEY_CURRENT_USER, key_path, 0, NULL, 0, KEY_READ, NULL, ®istry_key, | ||
| 1007 | NULL); | ||
| 1008 | if (result != ERROR_SUCCESS) | ||
| 1009 | { | ||
| 1010 | g_warning_win32_error (result, "gregistrystorage: Unable to subscribe to key %s.", name); | ||
| 1011 | g_free (key_path); | ||
| 1012 | return; | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | event = CreateEvent (NULL, FALSE, FALSE, NULL); | ||
| 1016 | if (event == NULL) | ||
| 1017 | { | ||
| 1018 | g_warning_win32_error (result, "gregistrystorage: CreateEvent failed.\n"); | ||
| 1019 | RegCloseKey (registry_key); | ||
| 1020 | g_free (key_path); | ||
| 1021 | return; | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | /* The actual watch is added by the thread, which has to re-subscribe each time it | ||
| 1025 | * receives a change. */ | ||
| 1026 | watch_add_notify (self, event, registry_key, g_strdup (name), key_path, value_name); | ||
| 522 | 1027 | } | |
| 523 | 1028 | ||
| 1029 | static void | ||
| 1030 | g_registry_storage_unsubscribe (GSettingsStorage *base, | ||
| 1031 | const char *name) | ||
| 1032 | { | ||
| 1033 | /* FIXME: if no more watches, stop the watch thread. */ | ||
| 1034 | } | ||
| 1035 | |||
| 524 | 1036 | GSettingsStorage* g_registry_storage_new (void) { | |
| 525 | 1037 | return g_object_new (G_TYPE_REGISTRY_STORAGE, NULL); | |
| 526 | 1038 | } | |
| … | … | ||
| 1042 | 1042 | { | |
| 1043 | 1043 | GRegistryStorage *self = G_REGISTRY_STORAGE (object); | |
| 1044 | 1044 | ||
| 1045 | if (self->priv->watch != NULL) | ||
| 1046 | watch_stop (self); | ||
| 1047 | |||
| 1045 | 1048 | g_free (self->priv->base); | |
| 1046 | 1049 | } | |
| 1047 | 1050 | ||
| … | … | ||
| 1061 | 1061 | storage_class->read = g_registry_storage_read; | |
| 1062 | 1062 | storage_class->sensitive = g_registry_storage_sensitive; | |
| 1063 | 1063 | storage_class->subscribe = g_registry_storage_subscribe; | |
| 1064 | storage_class->unsubscribe = g_registry_storage_subscribe; | ||
| 1064 | storage_class->unsubscribe = g_registry_storage_unsubscribe; | ||
| 1065 | 1065 | } | |
| 1066 | 1066 | ||
| 1067 | 1067 | static void | |
| … | … | ||
| 1072 | 1072 | GRegistryStoragePrivate); | |
| 1073 | 1073 | ||
| 1074 | 1074 | self->priv->base = g_strdup_printf ("Software\\%s", g_get_prgname ()); | |
| 1075 | |||
| 1076 | self->priv->watch = NULL; | ||
| 1075 | 1077 | } | |
| 1078 | |||
| 1076 | 1079 | ||
| 1077 | 1080 | static void | |
| 1078 | 1081 | trace (const char *format, ...) |
gsettings/Makefile.am
(3 / 2)
|   | |||
| 1 | CFLAGS = $(gio_CFLAGS) -I.. | ||
| 1 | AM_CFLAGS = $(gio_CFLAGS) -I.. | ||
| 2 | 2 | LDADD = $(top_builddir)/gio/libgsettings.la | |
| 3 | 3 | ||
| 4 | 4 | storage_test_SOURCES = storage-test.c | |
| 5 | notify_test_SOURCES = notify-test.c | ||
| 5 | 6 | ||
| 6 | TESTS = storage-test | ||
| 7 | TESTS = storage-test notify-test | ||
| 7 | 8 | ||
| 8 | 9 | check_PROGRAMS = $(TESTS) |
gsettings/notify-test.c
(112 / 0)
|   | |||
| 1 | /* | ||
| 2 | * Copyright © 2009 Sam Thursfield | ||
| 3 | * | ||
| 4 | * This program is free software: you can redistribute it and/or modify | ||
| 5 | * it under the terms of version 3 of the GNU General Public License as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | * | ||
| 8 | * See the included COPYING file for more information. | ||
| 9 | * | ||
| 10 | * Authors: Sam Thursfield <ssssam@gmail.com> | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <stdio.h> | ||
| 14 | #include <windows.h> | ||
| 15 | #include <glib.h> | ||
| 16 | |||
| 17 | #include <gio/gsettings.h> | ||
| 18 | #include <gio/gregistrystorage.h> | ||
| 19 | |||
| 20 | typedef struct { | ||
| 21 | /* Need a main loop for notifications to make their way back to the main thread */ | ||
| 22 | GMainLoop *main_loop; | ||
| 23 | |||
| 24 | GSettings *settings; | ||
| 25 | GSettingsStorage *storage; | ||
| 26 | } RegistryFixture; | ||
| 27 | |||
| 28 | void | ||
| 29 | registry_fixture_setup (RegistryFixture *fixture, | ||
| 30 | gconstpointer test_data) | ||
| 31 | { | ||
| 32 | fixture->main_loop = g_main_loop_new (NULL, FALSE); | ||
| 33 | fixture->storage = g_registry_storage_new (); | ||
| 34 | fixture->settings = g_object_new (G_TYPE_SETTINGS, "backend", fixture->storage, | ||
| 35 | "schema-name", "test.storage", NULL); | ||
| 36 | } | ||
| 37 | |||
| 38 | void | ||
| 39 | registry_fixture_teardown (RegistryFixture *fixture, | ||
| 40 | gconstpointer test_data) | ||
| 41 | { | ||
| 42 | g_object_unref (fixture->settings); | ||
| 43 | g_object_unref (fixture->storage); | ||
| 44 | g_main_loop_unref (fixture->main_loop); | ||
| 45 | } | ||
| 46 | |||
| 47 | static void | ||
| 48 | test_registry_notify_1 (RegistryFixture *fixture, | ||
| 49 | gconstpointer test_data) | ||
| 50 | { | ||
| 51 | char *string; | ||
| 52 | int i; | ||
| 53 | HKEY root_key; | ||
| 54 | LONG result; | ||
| 55 | |||
| 56 | /*TEST_TYPE (settings, gboolean, "bool", TRUE, FALSE); | ||
| 57 | TEST_TYPE (settings, gint32, "int32", 55, -999); | ||
| 58 | TEST_TYPE (settings, gint32, "\\", 666666, 66666666); | ||
| 59 | //TEST_TYPE (settings, guint64, "qword", 4398046511104, 31313131);*/ | ||
| 60 | |||
| 61 | g_settings_set (fixture->settings, "string", "Notify me", NULL); | ||
| 62 | |||
| 63 | for (i=0; i<100; i++) | ||
| 64 | { | ||
| 65 | g_main_context_iteration (NULL, FALSE); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | |||
| 69 | static void | ||
| 70 | test_registry_notify_delete (RegistryFixture *fixture, | ||
| 71 | gconstpointer test_data) | ||
| 72 | { | ||
| 73 | char *string; | ||
| 74 | HKEY root_key; | ||
| 75 | LONG result; | ||
| 76 | |||
| 77 | /* g_settings_get (settings, "string", &string, NULL); | ||
| 78 | g_assert_cmpstr (string, ==, "Hello world"); | ||
| 79 | |||
| 80 | g_usleep (400000);*/ | ||
| 81 | |||
| 82 | printf ("Deleting the key.\n"); fflush (stdout); | ||
| 83 | |||
| 84 | /* Delete the entire key from under gsettings's nose. This causes problems further down the line. | ||
| 85 | * Windows queues the key for deletion and actually carries it out once all open handles on it are | ||
| 86 | * closed. The backend will get ERROR_KEY_DELETED when it tries to resubscribe and needs to handle | ||
| 87 | * this correctly. */ | ||
| 88 | result = RegDeleteKey (HKEY_CURRENT_USER, "Software\\notify-test.exe\\test\\gsettings\\storage"); | ||
| 89 | if (result != ERROR_SUCCESS) | ||
| 90 | { | ||
| 91 | g_warning ("Error deleting key.\n"); fflush (stdout); | ||
| 92 | } | ||
| 93 | |||
| 94 | g_usleep (400000); | ||
| 95 | }; | ||
| 96 | |||
| 97 | int | ||
| 98 | main (int argc, char **argv) | ||
| 99 | { | ||
| 100 | g_type_init (); | ||
| 101 | g_test_init (&argc, &argv, NULL); | ||
| 102 | |||
| 103 | /* Notifications don't work unless we have a multithreaded app. */ | ||
| 104 | //g_thread_init (NULL); | ||
| 105 | |||
| 106 | g_test_add ("/gsettings/notify/Windows Registry 1", RegistryFixture, 0, | ||
| 107 | registry_fixture_setup, test_registry_notify_1, registry_fixture_teardown); | ||
| 108 | g_test_add ("/gsettings/notify/Windows Registry - deletion", RegistryFixture, 0, | ||
| 109 | registry_fixture_setup, test_registry_notify_delete, registry_fixture_teardown); | ||
| 110 | |||
| 111 | return g_test_run(); | ||
| 112 | }; |
gsettings/storage-test.c
(12 / 0)
|   | |||
| 1 | /* | ||
| 2 | * Copyright © 2009 Sam Thursfield | ||
| 3 | * | ||
| 4 | * This program is free software: you can redistribute it and/or modify | ||
| 5 | * it under the terms of version 3 of the GNU General Public License as | ||
| 6 | * published by the Free Software Foundation. | ||
| 7 | * | ||
| 8 | * See the included COPYING file for more information. | ||
| 9 | * | ||
| 10 | * Authors: Sam Thursfield <ssssam@gmail.com> | ||
| 11 | */ | ||
| 12 | |||
| 1 | 13 | #include <glib.h> | |
| 2 | 14 | ||
| 3 | 15 | #include <gio/gsettings.h> |
Comments
Add a new comment:
Login or create an account to post a comment
Add your comment
Please log in to comment

