/* GIO - GLib Input, Output and Streaming Library
 * 
 * Copyright (C) 2006-2007 Red Hat, Inc.
 * Copyright (C) 2014 Руслан Ижбулатов
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Alexander Larsson <alexl@redhat.com>
 *          Руслан Ижбулатов  <lrn1986@gmail.com>
 */

#include "config.h"

#include <string.h>

#include "gcontenttype.h"
#include "gwin32appinfo.h"
#include "gappinfo.h"
#include "gioerror.h"
#include "gfile.h"
#include <glib/gstdio.h>
#include "glibintl.h"
#include <gio/gwin32registrykey.h>

#include <windows.h>

/* We need to watch 8 places:
 * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations
 *    (anything below that key)
 *    On change: re-enumerate subkeys, read their values.
 * 1) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts
 *    (anything below that key)
 *    On change: re-enumerate subkeys
 * 2) HKEY_CURRENT_USER\\Software\\Clients (anything below that key)
 *    On change: re-read the whole hierarchy of handlers
 * 3) HKEY_LOCAL_MACHINE\\Software\\Clients (anything below that key)
 *    On change: re-read the whole hierarchy of handlers
 * 4) HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications (values of that key)
 *    On change: re-read the value list of registered applications
 * 5) HKEY_CURRENT_USER\\Software\\RegisteredApplications (values of that key)
 *    On change: re-read the value list of registered applications
 * 6) HKEY_CLASSES_ROOT\\Applications (anything below that key)
 *    On change: re-read the whole hierarchy of apps
 * 7) HKEY_CLASSES_ROOT (only its subkeys)
 *    On change: re-enumerate subkeys, try to filter out wrong names.
 *
 */

typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema;
typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension;
typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler;
typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication;

typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass;
typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass;
typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass;
typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass;

struct _GWin32AppInfoURLSchemaClass
{
  GObjectClass parent_class;
};

struct _GWin32AppInfoFileExtensionClass
{
  GObjectClass parent_class;
};

struct _GWin32AppInfoHandlerClass
{
  GObjectClass parent_class;
};

struct _GWin32AppInfoApplicationClass
{
  GObjectClass parent_class;
};

struct _GWin32AppInfoURLSchema {
  GObject parent_instance;

  /* url schema (stuff before ':') */
  gunichar2 *schema;

  /* url schema (stuff before ':'), in UTF-8 */
  gchar *schema_u8;

  /* url schema (stuff before ':'), in UTF-8, folded */
  gchar *schema_folded;

  /* Handler currently selected for this schema */
  GWin32AppInfoHandler *chosen_handler;

  /* Maps folded handler IDs -> to other handlers for this schema */
  GHashTable *handlers;
};

struct _GWin32AppInfoHandler {
  GObject parent_instance;

  /* Class name in HKCR */
  gunichar2 *handler_id;

  /* Handler registry key (HKCR\\handler_id). Can be used to watch this handler. */
  GWin32RegistryKey *key;

  /* Class name in HKCR, UTF-8, folded */
  gchar *handler_id_folded;

  /* shell/open/command default value for the class named by class_id */
  gunichar2 *handler_command;

  /* If handler_id class has no command, it might point to another class */
  gunichar2 *proxy_id;

  /* Proxy registry key (HKCR\\proxy_id). Can be used to watch handler's proxy. */
  GWin32RegistryKey *proxy_key;

  /* shell/open/command default value for the class named by proxy_id */
  gunichar2 *proxy_command;

  /* Executable of the program (for matching, in folded form; UTF-8) */
  gchar *executable_folded;

  /* Executable of the program (UTF-8) */
  gchar *executable;

  /* Pointer to a location within @executable */
  gchar *executable_basename;

  /* If not NULL, then @executable and its derived fields contain the name
   * of a DLL file (without the name of the function that rundll32.exe should
   * invoke), and this field contains the name of the function to be invoked.
   * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'.
   */
  gchar *dll_function;

  /* Icon of the application for this handler */
  GIcon *icon;

  /* The application that is linked to this handler. */
  GWin32AppInfoApplication *app;
};

struct _GWin32AppInfoFileExtension {
  GObject parent_instance;

  /* File extension (with leading '.') */
  gunichar2 *extension;

  /* File extension (with leading '.'), in UTF-8 */
  gchar *extension_u8;

  /* handler currently selected for this extension */
  GWin32AppInfoHandler *chosen_handler;

  /* Maps folded handler IDs -> to other handlers for this extension */
  GHashTable *handlers;

  /* Maps folded app exename -> to apps that support this extension.
   * ONLY for apps that are not reachable via handlers (i.e. apps from
   * the HKCR/Applications, which have no handlers). */
  GHashTable *other_apps;
};

struct _GWin32AppInfoApplication {
  GObject parent_instance;

  /* Canonical name (used for key names). Can be NULL. */
  gunichar2 *canonical_name;

  /* Canonical name (used for key names), in UTF-8. Can be NULL. */
  gchar *canonical_name_u8;

  /* Canonical name (used for key names), in UTF-8, folded. Can be NULL. */
  gchar *canonical_name_folded;

  /* Human-readable name in English. Can be NULL */
  gunichar2 *pretty_name;

  /* Human-readable name in English, UTF-8. Can be NULL */
  gchar *pretty_name_u8;

  /* Human-readable name in user's language. Can be NULL  */
  gunichar2 *localized_pretty_name;

  /* Human-readable name in user's language, UTF-8. Can be NULL  */
  gchar *localized_pretty_name_u8;

  /* Description, could be in user's language. Can be NULL */
  gunichar2 *description;

  /* Description, could be in user's language, UTF-8. Can be NULL */
  gchar *description_u8;

  /* shell/open/command for the application. Can be NULL (see executable). */
  gunichar2 *command;

  /* shell/open/command for the application. Can be NULL (see executable). */
  gchar *command_u8;

  /* Executable of the program (for matching, in folded form;
   * UTF-8). Never NULL. */
  gchar *executable_folded;

  /* Executable of the program (UTF-8). Never NULL. */
  gchar *executable;

  /* Pointer to a location within @executable */
  gchar *executable_basename;

  /* If not NULL, then @executable and its derived fields contain the name
   * of a DLL file (without the name of the function that rundll32.exe should
   * invoke), and this field contains the name of the function to be invoked.
   * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'.
   */
  gchar *dll_function;

  /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema,
   * UTF-8, folded) -> a GWin32AppInfoHandler
   * Schema can be used as a key in the urls hashmap.
   */
  GHashTable *supported_urls;

  /* Explicitly supported extensions, hashmap from map-owned gchar ptr
   * (.extension, UTF-8, folded) -> a GWin32AppInfoHandler
   * Extension can be used as a key in the extensions hashmap.
   */
  GHashTable *supported_exts;

  /* Icon of the application (remember, handler can have its own icon too) */
  GIcon *icon;

  /* Set to TRUE to prevent this app from appearing in lists of apps for
   * opening files. This will not prevent it from appearing in lists of apps
   * just for running, or lists of apps for opening exts/urls for which this
   * app reports explicit support.
   */
  gboolean no_open_with;

  /* Set to TRUE for applications from HKEY_CURRENT_USER.
   * Give them priority over applications from HKEY_LOCAL_MACHINE, when all
   * other things are equal.
   */
  gboolean user_specific;

  /* Set to TRUE for applications that are machine-wide defaults (i.e. default
   * browser) */
  gboolean default_app;
};

#define G_TYPE_WIN32_APPINFO_URL_SCHEMA           (g_win32_appinfo_url_schema_get_type ())
#define G_WIN32_APPINFO_URL_SCHEMA(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_URL_SCHEMA, GWin32AppInfoURLSchema))

#define G_TYPE_WIN32_APPINFO_FILE_EXTENSION       (g_win32_appinfo_file_extension_get_type ())
#define G_WIN32_APPINFO_FILE_EXTENSION(obj)       (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_FILE_EXTENSION, GWin32AppInfoFileExtension))

#define G_TYPE_WIN32_APPINFO_HANDLER              (g_win32_appinfo_handler_get_type ())
#define G_WIN32_APPINFO_HANDLER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_HANDLER, GWin32AppInfoHandler))

#define G_TYPE_WIN32_APPINFO_APPLICATION          (g_win32_appinfo_application_get_type ())
#define G_WIN32_APPINFO_APPLICATION(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_APPLICATION, GWin32AppInfoApplication))

GType g_win32_appinfo_url_schema_get_type (void) G_GNUC_CONST;
GType g_win32_appinfo_file_extension_get_type (void) G_GNUC_CONST;
GType g_win32_appinfo_handler_get_type (void) G_GNUC_CONST;
GType g_win32_appinfo_application_get_type (void) G_GNUC_CONST;

G_DEFINE_TYPE (GWin32AppInfoURLSchema, g_win32_appinfo_url_schema, G_TYPE_OBJECT)
G_DEFINE_TYPE (GWin32AppInfoFileExtension, g_win32_appinfo_file_extension, G_TYPE_OBJECT)
G_DEFINE_TYPE (GWin32AppInfoHandler, g_win32_appinfo_handler, G_TYPE_OBJECT)
G_DEFINE_TYPE (GWin32AppInfoApplication, g_win32_appinfo_application, G_TYPE_OBJECT)

static void
g_win32_appinfo_url_schema_dispose (GObject *object)
{
  GWin32AppInfoURLSchema *url = G_WIN32_APPINFO_URL_SCHEMA (object);

  g_clear_pointer (&url->schema, g_free);
  g_clear_pointer (&url->schema_u8, g_free);
  g_clear_pointer (&url->schema_folded, g_free);
  g_clear_object (&url->chosen_handler);
  g_clear_pointer (&url->handlers, g_hash_table_destroy);
  G_OBJECT_CLASS (g_win32_appinfo_url_schema_parent_class)->dispose (object);
}


static void
g_win32_appinfo_handler_dispose (GObject *object)
{
  GWin32AppInfoHandler *handler = G_WIN32_APPINFO_HANDLER (object);

  g_clear_pointer (&handler->handler_id, g_free);
  g_clear_pointer (&handler->handler_id_folded, g_free);
  g_clear_pointer (&handler->handler_command, g_free);
  g_clear_pointer (&handler->proxy_id, g_free);
  g_clear_pointer (&handler->proxy_command, g_free);
  g_clear_pointer (&handler->executable_folded, g_free);
  g_clear_pointer (&handler->executable, g_free);
  g_clear_pointer (&handler->dll_function, g_free);
  g_clear_object (&handler->key);
  g_clear_object (&handler->proxy_key);
  g_clear_object (&handler->icon);
  g_clear_object (&handler->app);
  G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object);
}

static void
g_win32_appinfo_file_extension_dispose (GObject *object)
{
  GWin32AppInfoFileExtension *ext = G_WIN32_APPINFO_FILE_EXTENSION (object);

  g_clear_pointer (&ext->extension, g_free);
  g_clear_pointer (&ext->extension_u8, g_free);
  g_clear_object (&ext->chosen_handler);
  g_clear_pointer (&ext->handlers, g_hash_table_destroy);
  g_clear_pointer (&ext->other_apps, g_hash_table_destroy);
  G_OBJECT_CLASS (g_win32_appinfo_file_extension_parent_class)->dispose (object);
}

static void
g_win32_appinfo_application_dispose (GObject *object)
{
  GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object);

  g_clear_pointer (&app->canonical_name_u8, g_free);
  g_clear_pointer (&app->canonical_name_folded, g_free);
  g_clear_pointer (&app->canonical_name, g_free);
  g_clear_pointer (&app->pretty_name, g_free);
  g_clear_pointer (&app->localized_pretty_name, g_free);
  g_clear_pointer (&app->description, g_free);
  g_clear_pointer (&app->command, g_free);
  g_clear_pointer (&app->pretty_name_u8, g_free);
  g_clear_pointer (&app->localized_pretty_name_u8, g_free);
  g_clear_pointer (&app->description_u8, g_free);
  g_clear_pointer (&app->command_u8, g_free);
  g_clear_pointer (&app->executable_folded, g_free);
  g_clear_pointer (&app->executable, g_free);
  g_clear_pointer (&app->dll_function, g_free);
  g_clear_pointer (&app->supported_urls, g_hash_table_destroy);
  g_clear_pointer (&app->supported_exts, g_hash_table_destroy);
  g_clear_object (&app->icon);
  G_OBJECT_CLASS (g_win32_appinfo_application_parent_class)->dispose (object);
}

static void
g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = g_win32_appinfo_url_schema_dispose;
}

static void
g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = g_win32_appinfo_file_extension_dispose;
}

static void
g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = g_win32_appinfo_handler_dispose;
}

static void
g_win32_appinfo_application_class_init (GWin32AppInfoApplicationClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = g_win32_appinfo_application_dispose;
}

static void
g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self)
{
  self->handlers = g_hash_table_new_full (g_str_hash,
                                          g_str_equal,
                                          g_free,
                                          g_object_unref);
}

static void
g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
{
  self->handlers = g_hash_table_new_full (g_str_hash,
                                          g_str_equal,
                                          g_free,
                                          g_object_unref);
  self->other_apps = g_hash_table_new_full (g_str_hash,
                                            g_str_equal,
                                            g_free,
                                            g_object_unref);
}

static void
g_win32_appinfo_handler_init (GWin32AppInfoHandler *self)
{
}

static void
g_win32_appinfo_application_init (GWin32AppInfoApplication *self)
{
  self->supported_urls = g_hash_table_new_full (g_str_hash,
                                                g_str_equal,
                                                g_free,
                                                g_object_unref);
  self->supported_exts = g_hash_table_new_full (g_str_hash,
                                                g_str_equal,
                                                g_free,
                                                g_object_unref);
}

G_LOCK_DEFINE_STATIC (gio_win32_appinfo);

/* Map of owned ".ext" (with '.', UTF-8, folded)
 * to GWin32AppInfoFileExtension ptr
 */
static GHashTable *extensions = NULL;

/* Map of owned "schema" (without ':', UTF-8, folded)
 * to GWin32AppInfoURLSchema ptr
 */
static GHashTable *urls = NULL;

/* Map of owned "appID" (UTF-8, folded) to
 * GWin32AppInfoApplication ptr
 */
static GHashTable *apps_by_id = NULL;

/* Map of owned "app.exe" (UTF-8, folded) to
 * GWin32AppInfoApplication ptr.
 * This map and its values are separate from apps_by_id. The fact that an app
 * with known ID has the same executable [base]name as an app in this map does
 * not mean that they are the same application.
 */
static GHashTable *apps_by_exe = NULL;

/* Map of owned "handler id" (UTF-8, folded)
 * to GWin32AppInfoHandler ptr
 */
static GHashTable *handlers = NULL;

/* Watch this whole subtree */
static GWin32RegistryKey *url_associations_key;

/* Watch this whole subtree */
static GWin32RegistryKey *file_exts_key;

/* Watch this whole subtree */
static GWin32RegistryKey *user_clients_key;

/* Watch this whole subtree */
static GWin32RegistryKey *system_clients_key;

/* Watch this key */
static GWin32RegistryKey *user_registered_apps_key;

/* Watch this key */
static GWin32RegistryKey *system_registered_apps_key;

/* Watch this whole subtree */
static GWin32RegistryKey *applications_key;

/* Watch this key */
static GWin32RegistryKey *classes_root_key;

#define URL_ASSOCIATIONS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
#define USER_CHOICE L"\\UserChoice"
#define OPEN_WITH_PROGIDS L"\\OpenWithProgids"
#define FILE_EXTS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\"
#define HKCR L"HKEY_CLASSES_ROOT\\"
#define HKCU L"HKEY_CURRENT_USER\\"
#define HKLM L"HKEY_LOCAL_MACHINE\\"
#define SHELL_OPEN_COMMAND L"\\shell\\open\\command"
#define REG_PATH_MAX 256
#define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2))

/* for g_wcsdup(),
 *     _g_win32_extract_executable(),
 *     _g_win32_fixup_broken_microsoft_rundll_commandline()
 */
#include "giowin32-private.c"

static gunichar2 *
read_resource_string (gunichar2 *res)
{
  gunichar2 *id_str;
  gunichar2 *id_str_end;
  gunichar2 *filename_str;
  unsigned long id;
  HMODULE resource_module;
  gunichar2 *buffer;
  int string_length;
  int buffer_length;

  if (res == NULL || res[0] != L'@')
    return res;

  id_str = wcsrchr (res, L'-');

  if (id_str == NULL || id_str[-1] != L',')
    return res;

  id_str += 1;

  id = wcstoul (id_str, &id_str_end, 10);

  if (id_str_end == id_str || id_str_end[0] != L'\0' || id == ULONG_MAX)
    return res;

  filename_str = &res[1];
  id_str[-2] = L'\0';

  resource_module = LoadLibraryExW (filename_str,
                                    0,
                                    LOAD_LIBRARY_AS_DATAFILE |
                                    LOAD_LIBRARY_AS_IMAGE_RESOURCE);

  g_free (res);

  if (resource_module == NULL)
    return NULL;

  buffer_length = 256;
  string_length = buffer_length - 1;

  while (TRUE)
    {
      buffer = g_malloc (buffer_length * sizeof (gunichar2));
      string_length = LoadStringW (resource_module, id, buffer, buffer_length);

      if (string_length != 0 && string_length == buffer_length - 1)
        {
          g_free (buffer);
          buffer_length *= 2;
        }
      else
        {
          if (string_length == 0)
            g_clear_pointer (&buffer, g_free);

          break;
        }
    }

  FreeLibrary (resource_module);

  if (buffer)
    {
      gunichar2 *result = g_wcsdup (buffer, -1);
      g_free (buffer);
      return result;
    }

  return NULL;
}

static void
read_handler_icon (GWin32RegistryKey  *proxy_key,
                   GWin32RegistryKey  *program_key,
                   GIcon             **icon_out)
{
  gint counter;
  GWin32RegistryKey *key;

  *icon_out = NULL;

  for (counter = 0; counter < 2; counter++)
    {
      GWin32RegistryKey *icon_key;

      if (counter == 0)
        key = proxy_key;
      else
        key = program_key;

      if (!key)
        continue;

      icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);

      if (icon_key != NULL)
        {
          GWin32RegistryValueType default_type;
          gchar *default_value;

          if (g_win32_registry_key_get_value (icon_key,
                                              TRUE,
                                              "",
                                              &default_type,
                                              (gpointer *) &default_value,
                                              NULL,
                                              NULL))
            {
              if (default_type == G_WIN32_REGISTRY_VALUE_STR ||
                  default_value[0] != '\0')
                *icon_out = g_themed_icon_new (default_value);

              g_clear_pointer (&default_value, g_free);
            }

          g_object_unref (icon_key);
        }

      if (*icon_out)
        break;
    }
}

static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) G_GNUC_NULL_TERMINATED;
static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_list components);

static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED;

/* output_size is in *bytes*, not gunichar2s! */
static gboolean
build_registry_path (gunichar2 *output, gsize output_size, ...)
{
  va_list ap;
  gboolean result;

  va_start (ap, output_size);

  result = build_registry_pathv (output, output_size, ap);

  va_end (ap);

  return result;
}

/* output_size is in *bytes*, not gunichar2s! */
static gboolean
build_registry_pathv (gunichar2 *output, gsize output_size, va_list components)
{
  va_list lentest;
  gunichar2 *p;
  gunichar2 *component;
  gsize length;

  if (output == NULL)
    return FALSE;

  G_VA_COPY (lentest, components);

  for (length = 0, component = va_arg (lentest, gunichar2 *);
       component != NULL;
       component = va_arg (lentest, gunichar2 *))
    {
      length += wcslen (component);
    }

  va_end (lentest);

  if ((length >= REG_PATH_MAX_SIZE) ||
      (length * sizeof (gunichar2) >= output_size))
    return FALSE;

  output[0] = L'\0';

  for (p = output, component = va_arg (components, gunichar2 *);
       component != NULL;
       component = va_arg (components, gunichar2 *))
    {
      length = wcslen (component);
      wcscat (p, component);
      p += length;
    }

  return TRUE;
}


static GWin32RegistryKey *
_g_win32_registry_key_build_and_new_w (GError **error, ...)
{
  va_list ap;
  gunichar2 key_path[REG_PATH_MAX_SIZE + 1];
  GWin32RegistryKey *key;

  va_start (ap, error);

  key = NULL;

  if (build_registry_pathv (key_path, sizeof (key_path), ap))
    key = g_win32_registry_key_new_w (key_path, error);

  va_end (ap);

  return key;
}

/* Slow and dirty validator for UTF-16 strings */
static gboolean
g_utf16_validate (const gunichar2  *str,
		  glong             len)
{
  gchar *tmp;

  if (str == NULL)
    return FALSE;

  tmp = g_utf16_to_utf8 (str, len, NULL, NULL, NULL);

  if (tmp == NULL)
    return FALSE;

  g_free (tmp);

  return TRUE;
}

/* Does a UTF-16 validity check on *proxy_command and/or *program_command.
 * Fails if that check doesn't pass.
 */
static gboolean
follow_class_chain_to_handler (const gunichar2    *program_id,
                               gsize               program_id_size,
                               gunichar2         **program_command,
                               GWin32RegistryKey **program_key,
                               gunichar2         **proxy_id,
                               gunichar2         **proxy_command,
                               GWin32RegistryKey **proxy_key,
                               gchar             **program_id_u8,
                               gchar             **program_id_folded)
{
  GWin32RegistryKey *key;
  GWin32RegistryValueType val_type;
  gsize proxy_id_size;
  gboolean got_value;

  g_assert (program_id && program_command && proxy_id && proxy_command);

  *program_command = NULL;
  *proxy_id = NULL;
  *proxy_command = NULL;

  if (program_key)
    *program_key = NULL;

  if (proxy_key)
    *proxy_key = NULL;


  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id,
                                               SHELL_OPEN_COMMAND, NULL);

  if (key != NULL)
    {
      got_value = g_win32_registry_key_get_value_w (key,
                                                    TRUE,
                                                    L"",
                                                    &val_type,
                                                    (void **) program_command,
                                                    NULL,
                                                    NULL);
      if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR)
        {
          if (((program_id_u8 != NULL || program_id_folded != NULL) &&
               !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) ||
              !g_utf16_validate (*program_command, -1))
            {
              g_object_unref (key);
              g_free (program_command);

              return FALSE;
            }
          if (program_key == NULL)
            g_object_unref (key);
          else
            *program_key = key;

          return TRUE;
        }
      else if (got_value)
        g_clear_pointer (program_command, g_free);

      g_object_unref (key);
    }

  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, NULL);

  if (key == NULL)
    return FALSE;

  got_value = g_win32_registry_key_get_value_w (key,
                                                TRUE,
                                                L"",
                                                &val_type,
                                                (void **) proxy_id,
                                                &proxy_id_size,
                                                NULL);
  g_object_unref (key);

  if (!got_value ||
      (val_type != G_WIN32_REGISTRY_VALUE_STR))
    {
      g_clear_pointer (proxy_id, g_free);

      return FALSE;
    }

  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, *proxy_id,
                                               SHELL_OPEN_COMMAND, NULL);

  if (key == NULL)
    {
      g_clear_pointer (proxy_id, g_free);

      return FALSE;
    }

  got_value = g_win32_registry_key_get_value_w (key,
                                                TRUE,
                                                L"",
                                                &val_type,
                                                (void **) proxy_command,
                                                NULL,
                                                NULL);

  if (proxy_key)
    *proxy_key = key;
  else
    g_object_unref (key);

  if (!got_value ||
      val_type != G_WIN32_REGISTRY_VALUE_STR ||
      ((program_id_u8 != NULL || program_id_folded != NULL) &&
       !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) ||
      !g_utf16_validate (*proxy_command, -1))
    {
      g_clear_pointer (proxy_id, g_free);
      g_clear_pointer (proxy_command, g_free);
      if (proxy_key)
        g_clear_object (proxy_key);
      return FALSE;
    }

  return TRUE;
}

static void
get_url_association (const gunichar2 *schema)
{
  GWin32AppInfoURLSchema *schema_rec;
  GWin32AppInfoHandler *handler_rec;
  GWin32AppInfoHandler *handler_rec_in_url;
  gchar *schema_u8;
  gchar *schema_folded;
  GWin32RegistryKey *user_choice;
  GWin32RegistryValueType val_type;
  gunichar2 *program_id;
  gsize program_id_size;
  gunichar2 *program_command;
  gunichar2 *proxy_id;
  gunichar2 *proxy_command;
  gchar *program_id_u8;
  gchar *program_id_folded;
  GWin32RegistryKey *program_key;
  GWin32RegistryKey *proxy_key;

  user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS,
                                                       schema, USER_CHOICE,
                                                       NULL);

  if (user_choice == NULL)
    return;

  if (!g_utf16_to_utf8_and_fold (schema, -1, &schema_u8, &schema_folded))
    {
      g_object_unref (user_choice);
      return;
    }

  schema_rec = g_hash_table_lookup (urls, schema_folded);

  if (!g_win32_registry_key_get_value_w (user_choice,
                                         TRUE,
                                         L"Progid",
                                         &val_type,
                                         (void **) &program_id,
                                         &program_id_size,
                                         NULL))
    {
      g_free (schema_u8);
      g_free (schema_folded);
      g_object_unref (user_choice);
      return;
    }

  if (val_type != G_WIN32_REGISTRY_VALUE_STR)
    {
      g_free (schema_u8);
      g_free (schema_folded);
      g_free (program_id);
      g_object_unref (user_choice);
      return;
    }

  program_key = proxy_key = NULL;
  program_command = proxy_id = proxy_command = NULL;

  if (!follow_class_chain_to_handler (program_id,
                                      program_id_size,
                                      &program_command,
                                      &program_key,
                                      &proxy_id,
                                      &proxy_command,
                                      &proxy_key,
                                      &program_id_u8,
                                      &program_id_folded))
    {
      g_free (schema_u8);
      g_free (schema_folded);
      g_free (program_id);
      g_object_unref (user_choice);
      return;
    }

  handler_rec = g_hash_table_lookup (handlers, program_id_folded);

  if (handler_rec == NULL)
    {
      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

      handler_rec->proxy_key = proxy_key;
      handler_rec->key = program_key;
      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
      handler_rec->handler_id_folded =
          g_strdup (program_id_folded);
      handler_rec->handler_command =
          program_command ? g_wcsdup (program_command, -1) : NULL;
      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
      handler_rec->proxy_command =
          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                   &handler_rec->executable,
                                   &handler_rec->executable_basename,
                                   &handler_rec->executable_folded,
                                   NULL,
                                   &handler_rec->dll_function);
      if (handler_rec->dll_function != NULL)
        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
      g_hash_table_insert (handlers,
                           g_strdup (program_id_folded),
                           handler_rec);
    }
  else
    {
      g_clear_object (&program_key);
      g_clear_object (&proxy_key);
    }

  if (schema_rec == NULL)
    {
      schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
      schema_rec->schema = g_wcsdup (schema, -1);
      schema_rec->schema_u8 = g_strdup (schema_u8);
      schema_rec->schema_folded = g_strdup (schema_folded);
      schema_rec->chosen_handler = g_object_ref (handler_rec);
      g_hash_table_insert (urls, g_strdup (schema_folded), schema_rec);
    }

  if (schema_rec->chosen_handler == NULL)
    schema_rec->chosen_handler = g_object_ref (handler_rec);

  handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers,
                                            program_id_folded);

  if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec)
    g_hash_table_insert (schema_rec->handlers,
                         g_strdup (program_id_folded),
                         g_object_ref (handler_rec));

  g_free (schema_u8);
  g_free (schema_folded);
  g_free (program_id);
  g_free (program_id_u8);
  g_free (program_id_folded);
  g_free (program_command);
  g_free (proxy_id);
  g_free (proxy_command);
  g_object_unref (user_choice);
}

static void
get_file_ext (const gunichar2 *ext)
{
  GWin32AppInfoFileExtension *file_extn;
  gboolean file_ext_known;
  GWin32AppInfoHandler *handler_rec;
  GWin32AppInfoHandler *handler_rec_in_ext;
  gchar *ext_u8;
  gchar *ext_folded;
  GWin32RegistryKey *user_choice;
  GWin32RegistryKey *open_with_progids;
  GWin32RegistryValueType val_type;
  gsize program_id_size;
  gboolean found_handler;
  gunichar2 *program_id;
  gunichar2 *proxy_id;
  gchar *program_id_u8;
  gchar *program_id_folded;
  GWin32RegistryKey *program_key;
  GWin32RegistryKey *proxy_key;
  gunichar2 *program_command;
  gunichar2 *proxy_command;

  open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS,
                                                             ext,
                                                             OPEN_WITH_PROGIDS,
                                                             NULL);

  user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, ext,
                                                       USER_CHOICE, NULL);

  if (user_choice == NULL && open_with_progids == NULL)
    return;

  if (!g_utf16_to_utf8_and_fold (ext, -1, &ext_u8, &ext_folded))
    {
      g_clear_object (&user_choice);
      g_clear_object (&open_with_progids);
      return;
    }

  file_extn = NULL;
  file_ext_known = g_hash_table_lookup_extended (extensions,
                                                 ext_folded,
                                                 NULL,
                                                 (void **) &file_extn);

  if (!file_ext_known)
    file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);

  found_handler = FALSE;

  if (user_choice != NULL)
    {
      if (g_win32_registry_key_get_value_w (user_choice,
                                            TRUE,
                                            L"Progid",
                                            &val_type,
                                            (void **) &program_id,
                                            &program_id_size,
                                            NULL))
        {
          program_key = proxy_key = NULL;

          if (val_type == G_WIN32_REGISTRY_VALUE_STR &&
              follow_class_chain_to_handler (program_id,
                                             program_id_size,
                                             &program_command,
                                             &program_key,
                                             &proxy_id,
                                             &proxy_command,
                                             &proxy_key,
                                             &program_id_u8,
                                             &program_id_folded))
            {
              handler_rec = g_hash_table_lookup (handlers,
                                                 program_id_folded);

              if (handler_rec == NULL)
                {
                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER,
                                              NULL);
                  handler_rec->proxy_key = proxy_key;
                  handler_rec->key = program_key;
                  handler_rec->handler_id =
                      g_wcsdup (program_id, program_id_size);
                  handler_rec->handler_id_folded =
                      g_strdup (program_id_folded);
                  handler_rec->handler_command =
                      program_command ? g_wcsdup (program_command, -1) : NULL;
                  handler_rec->proxy_id =
                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
                  handler_rec->proxy_command =
                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                               &handler_rec->executable,
                                               &handler_rec->executable_basename,
                                               &handler_rec->executable_folded,
                                               NULL,
                                               &handler_rec->dll_function);
                  if (handler_rec->dll_function != NULL)
                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
                  read_handler_icon (proxy_key,
                                     program_key,
                                     &handler_rec->icon);
                  g_hash_table_insert (handlers,
                                       g_strdup (program_id_folded),
                                       handler_rec);
                }
              else
                {
                  g_clear_object (&program_key);
                  g_clear_object (&proxy_key);
                }

              found_handler = TRUE;

              handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
                                                        program_id_folded);

              if (file_extn->chosen_handler == NULL)
                {
                  g_hash_table_insert (file_extn->handlers,
                                       g_strdup (program_id_folded),
                                       g_object_ref (handler_rec));
                }
              else if (handler_rec_in_ext == NULL)
                {
                  if (file_extn->chosen_handler->handler_id_folded &&
                      strcmp (file_extn->chosen_handler->handler_id_folded,
                              program_id_folded) != 0)
                    g_hash_table_insert (file_extn->handlers,
                                         g_strdup (program_id_folded),
                                         g_object_ref (handler_rec));
                }

              g_free (program_id_u8);
              g_free (program_id_folded);
              g_free (program_command);
              g_free (proxy_id);
              g_free (proxy_command);
            }

          g_free (program_id);
        }

      g_object_unref (user_choice);
    }

  if (open_with_progids != NULL)
    {
      GWin32RegistryValueIter iter;

      if (g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
        {
          gunichar2 *value_name;
          gunichar2 *value_data;
          gsize      value_name_len;
          gsize      value_data_size;
          GWin32RegistryValueType value_type;

          while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
            {
              gsize value_name_size;
              program_key = proxy_key = NULL;

              if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                                &value_type,
                                                                NULL)) ||
                  ((val_type != G_WIN32_REGISTRY_VALUE_STR) &&
                   (val_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) ||
                  (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
                                                            &value_name_len,
                                                            NULL)) ||
                  (value_name_len <= 0) ||
                  (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
                                                            (void **) &value_data,
                                                            &value_data_size,
                                                            NULL)) ||
                  (value_data_size < sizeof (gunichar2)) ||
                  (value_data[0] == L'\0'))
                continue;

              value_name_size = sizeof (gunichar2) * (value_name_len + 1);

              if (!follow_class_chain_to_handler (value_name,
                                                  value_name_size,
                                                  &program_command,
                                                  &program_key,
                                                  &proxy_id,
                                                  &proxy_command,
                                                  &proxy_key,
                                                  &program_id_u8,
                                                  &program_id_folded))
                continue;

              handler_rec = g_hash_table_lookup (handlers,
                                                 program_id_folded);

              if (handler_rec == NULL)
                {
                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

                  handler_rec->proxy_key = proxy_key;
                  handler_rec->key = program_key;
                  handler_rec->handler_id =
                      g_wcsdup (value_name, value_name_size);
                  handler_rec->handler_id_folded =
                      g_strdup (program_id_folded);
                  handler_rec->handler_command =
                      program_command ? g_wcsdup (program_command, -1) : NULL;
                  handler_rec->proxy_id =
                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
                  handler_rec->proxy_command =
                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                               &handler_rec->executable,
                                               &handler_rec->executable_basename,
                                               &handler_rec->executable_folded,
                                               NULL,
                                               &handler_rec->dll_function);
                  if (handler_rec->dll_function != NULL)
                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
                  read_handler_icon (proxy_key,
                                     program_key,
                                     &handler_rec->icon);
                  g_hash_table_insert (handlers,
                                       g_strdup (program_id_folded),
                                       handler_rec);
                }
              else
                {
                  g_clear_object (&program_key);
                  g_clear_object (&proxy_key);
                }

              found_handler = TRUE;

              handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
                                                        program_id_folded);

              if (handler_rec_in_ext == NULL)
                {
                  if (file_extn->chosen_handler == NULL)
                    g_hash_table_insert (file_extn->handlers,
                                         g_strdup (program_id_folded),
                                         g_object_ref (handler_rec));
                  else if (file_extn->chosen_handler->handler_id_folded &&
                           strcmp (file_extn->chosen_handler->handler_id_folded,
                                   program_id_folded) != 0)
                    g_hash_table_insert (file_extn->handlers,
                                         g_strdup (program_id_folded),
                                         g_object_ref (handler_rec));
                }

              g_free (program_id_u8);
              g_free (program_id_folded);
              g_free (program_command);
              g_free (proxy_id);
              g_free (proxy_command);
            }

          g_win32_registry_value_iter_clear (&iter);
        }

      g_object_unref (open_with_progids);
    }

  if (!found_handler)
    {
      if (!file_ext_known)
        g_object_unref (file_extn);
    }
  else if (!file_ext_known)
    {
      file_extn->extension = g_wcsdup (ext, -1);
      file_extn->extension_u8 = g_strdup (ext_u8);
      g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn);
    }

  g_free (ext_u8);
  g_free (ext_folded);
}

static void
collect_capable_apps_from_clients (GPtrArray *capable_apps,
                                   GPtrArray *priority_capable_apps,
                                   gboolean   user_registry)
{
  GWin32RegistryKey *clients;
  GWin32RegistrySubkeyIter clients_iter;

  gunichar2 *client_type_name;
  gsize client_type_name_len;


  if (user_registry)
    clients =
        g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
                                     NULL);
  else
    clients =
        g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
                                     NULL);

  if (clients == NULL)
    return;

  if (!g_win32_registry_subkey_iter_init (&clients_iter, clients, NULL))
    {
      g_object_unref (clients);
      return;
    }

  while (g_win32_registry_subkey_iter_next (&clients_iter, TRUE, NULL))
    {
      GWin32RegistrySubkeyIter subkey_iter;
      GWin32RegistryKey *system_client_type;
      GWin32RegistryValueType default_type;
      gunichar2 *default_value = NULL;
      gunichar2 *client_name;
      gsize client_name_len;

      if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter,
                                                    &client_type_name,
                                                    &client_type_name_len,
                                                    NULL))
        continue;

      system_client_type = g_win32_registry_key_get_child_w (clients,
                                                             client_type_name,
                                                             NULL);

      if (system_client_type == NULL)
        continue;

      if (g_win32_registry_key_get_value_w (system_client_type,
                                            TRUE,
                                            L"",
                                            &default_type,
                                            (gpointer *) &default_value,
                                            NULL,
                                            NULL))
        {
          if (default_type != G_WIN32_REGISTRY_VALUE_STR ||
              default_value[0] == L'\0')
            g_clear_pointer (&default_value, g_free);
        }

      if (!g_win32_registry_subkey_iter_init (&subkey_iter,
                                              system_client_type,
                                              NULL))
        {
          g_clear_pointer (&default_value, g_free);
          g_object_unref (system_client_type);
          continue;
        }

      while (g_win32_registry_subkey_iter_next (&subkey_iter, TRUE, NULL))
        {
          GWin32RegistryKey *system_client;
          GWin32RegistryKey *system_client_assoc;
          gboolean add;
          gunichar2 *keyname;

          if (!g_win32_registry_subkey_iter_get_name_w (&subkey_iter,
                                                        &client_name,
                                                        &client_name_len,
                                                        NULL))
            continue;

          system_client = g_win32_registry_key_get_child_w (system_client_type,
                                                            client_name,
                                                            NULL);

          if (system_client == NULL)
            continue;

          add = FALSE;

          system_client_assoc = g_win32_registry_key_get_child_w (system_client,
                                                                  L"Capabilities\\FileAssociations",
                                                                  NULL);

          if (system_client_assoc != NULL)
            {
              add = TRUE;
              g_object_unref (system_client_assoc);
            }
          else
            {
              system_client_assoc = g_win32_registry_key_get_child_w (system_client,
                                                                      L"Capabilities\\UrlAssociations",
                                                                      NULL);

              if (system_client_assoc != NULL)
                {
                  add = TRUE;
                  g_object_unref (system_client_assoc);
                }
            }

          if (add)
            {
              keyname = g_wcsdup (g_win32_registry_key_get_path_w (system_client), -1);

              if (default_value && wcscmp (default_value, client_name) == 0)
                g_ptr_array_add (priority_capable_apps, keyname);
              else
                g_ptr_array_add (capable_apps, keyname);
            }

          g_object_unref (system_client);
        }

      g_win32_registry_subkey_iter_clear (&subkey_iter);
      g_clear_pointer (&default_value, g_free);
      g_object_unref (system_client_type);
    }

  g_win32_registry_subkey_iter_clear (&clients_iter);
  g_object_unref (clients);
}

static void
collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
                                           gboolean   user_registry)
{
  GWin32RegistryValueIter iter;

  gunichar2 *value_data;
  gsize      value_data_size;
  GWin32RegistryValueType value_type;
  GWin32RegistryKey *registered_apps;

  if (user_registry)
    registered_apps =
        g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
                                    NULL);
  else
    registered_apps =
        g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
                                    NULL);

  if (!registered_apps)
    return;

  if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL))
    {
      g_object_unref (registered_apps);
      return;
    }

  while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
    {
      gunichar2 possible_location[REG_PATH_MAX_SIZE + 1];
      GWin32RegistryKey *location = NULL;

      if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                        &value_type,
                                                        NULL)) ||
          (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
          (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
                                                    (void **) &value_data,
                                                    &value_data_size,
                                                    NULL)) ||
          (value_data_size < sizeof (gunichar2)) ||
          (value_data[0] == L'\0'))
        continue;

      if (build_registry_path (possible_location, sizeof (possible_location),
                               HKCU, value_data, NULL))
        location = g_win32_registry_key_new_w (possible_location, NULL);

      if (location)
        {
          gunichar2 *p = wcsrchr (possible_location, L'\\');

          if (p)
            *p = L'\0';

          g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
          g_object_unref (location);
          continue;
        }

      if (!build_registry_path (possible_location, sizeof (possible_location),
                                user_registry ? HKCU : HKLM, value_data, NULL))
        continue;

      location = g_win32_registry_key_new_w (possible_location, NULL);

      if (location)
        {
          gunichar2 *p = wcsrchr (possible_location, L'\\');
          if (p)
            *p = L'\0';
          g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
          g_object_unref (location);
        }
    }

  g_win32_registry_value_iter_clear (&iter);
  g_object_unref (registered_apps);
}

static void
read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolean default_app)
{
  GWin32AppInfoApplication *app;
  gunichar2 *app_key_path;
  gunichar2 *canonical_name;
  gchar *canonical_name_u8;
  gchar *canonical_name_folded;
  GWin32RegistryKey *appkey;
  gunichar2 *fallback_friendly_name;
  GWin32RegistryValueType vtype;
  gboolean success;
  gunichar2 *friendly_name;
  gunichar2 *description;
  gunichar2 *narrow_application_name;
  gunichar2 *icon_source;
  GWin32RegistryKey *capabilities;
  GWin32RegistryKey *default_icon_key;
  GWin32RegistryKey *shell_open_command_key;
  gunichar2 *shell_open_command;
  gchar *app_executable;
  gchar *app_executable_basename;
  gchar *app_executable_folded;
  gchar *app_executable_folded_basename;
  gchar *app_dll_function;
  GWin32RegistryKey *associations;

  app_key_path = g_wcsdup (input_app_key_path, -1);

  canonical_name = wcsrchr (app_key_path, L'\\');

  if (canonical_name == NULL)
    {
      /* The key must have at least one '\\' */
      g_free (app_key_path);
      return;
    }

  canonical_name += 1;

  if (!g_utf16_to_utf8_and_fold (canonical_name, -1, &canonical_name_u8, &canonical_name_folded))
    {
      g_free (app_key_path);
      return;
    }

  appkey = g_win32_registry_key_new_w (app_key_path, NULL);

  if (appkey == NULL)
    {
      g_free (canonical_name_u8);
      g_free (canonical_name_folded);
      g_free (app_key_path);
      return;
    }

  capabilities =
      g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL);

  if (capabilities == NULL)
    {
      /* Must have capabilities */
      g_free (canonical_name_u8);
      g_free (canonical_name_folded);
      g_free (app_key_path);
      return;
    }

  shell_open_command_key =
      g_win32_registry_key_get_child_w (appkey,
                                        L"shell\\open\\command",
                                        NULL);

  if (shell_open_command_key == NULL)
    {
      g_object_unref (capabilities);
      g_free (canonical_name_u8);
      g_free (canonical_name_folded);
      g_free (app_key_path);
      g_object_unref (appkey);
      return ;
    }

  shell_open_command = NULL;

  success = g_win32_registry_key_get_value_w (shell_open_command_key,
                                              TRUE,
                                              L"",
                                              &vtype,
                                              (gpointer *) &shell_open_command,
                                              NULL,
                                              NULL);

  if (success &&
      (vtype != G_WIN32_REGISTRY_VALUE_STR ||
       !g_utf16_validate (shell_open_command, -1)))
    {
      /* Must have a command */
      g_clear_pointer (&shell_open_command, g_free);
      g_object_unref (capabilities);
      g_free (canonical_name_u8);
      g_free (canonical_name_folded);
      g_free (app_key_path);
      g_object_unref (appkey);
      return;
    }

  _g_win32_extract_executable (shell_open_command,
                               &app_executable,
                               &app_executable_basename,
                               &app_executable_folded,
                               &app_executable_folded_basename,
                               &app_dll_function);
  if (app_dll_function != NULL)
    _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command);

  app = g_hash_table_lookup (apps_by_id, canonical_name_folded);

  if (app == NULL)
    {
      app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);

      app->canonical_name = g_wcsdup (canonical_name, -1);
      app->canonical_name_u8 = g_strdup (canonical_name_u8);
      app->canonical_name_folded =
          g_strdup (canonical_name_folded);

      app->command = g_wcsdup (shell_open_command, -1);
      app->command_u8 =
          g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL);
      app->executable = g_strdup (app_executable);
      app->executable_basename =
          &app->executable[app_executable_basename - app_executable];
      app->executable_folded =
          g_strdup (app_executable_folded);

      app->no_open_with = FALSE;

      app->user_specific = user_specific;
      app->default_app = default_app;

      app->dll_function = g_strdup (app_dll_function);

      g_hash_table_insert (apps_by_id,
                           g_strdup (canonical_name_folded),
                           app);
    }

  fallback_friendly_name = NULL;
  success = g_win32_registry_key_get_value_w (appkey,
                                              TRUE,
                                              L"",
                                              &vtype,
                                              (void **) &fallback_friendly_name,
                                              NULL,
                                              NULL);

  if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
    g_clear_pointer (&fallback_friendly_name, g_free);

  if (fallback_friendly_name && app->pretty_name == NULL)
    {
      app->pretty_name = g_wcsdup (fallback_friendly_name, -1);
      g_clear_pointer (&app->pretty_name_u8, g_free);
      app->pretty_name_u8 = g_utf16_to_utf8 (fallback_friendly_name,
                                             -1,
                                             NULL,
                                             NULL,
                                             NULL);
    }

  friendly_name = NULL;
  success = g_win32_registry_key_get_value_w (capabilities,
                                              TRUE,
                                              L"LocalizedString",
                                              &vtype,
                                              (void **) &friendly_name,
                                              NULL,
                                              NULL);

  if (success && (vtype != G_WIN32_REGISTRY_VALUE_STR || friendly_name[0] != L'@'))
    g_clear_pointer (&friendly_name, g_free);

  friendly_name = read_resource_string (friendly_name);

  if (friendly_name && app->localized_pretty_name == NULL)
    {
      app->localized_pretty_name = g_wcsdup (friendly_name, -1);
      g_clear_pointer (&app->localized_pretty_name_u8, g_free);
      app->localized_pretty_name_u8 = g_utf16_to_utf8 (friendly_name,
                                                       -1,
                                                       NULL,
                                                       NULL,
                                                       NULL);
    }

  description = NULL;
  success = g_win32_registry_key_get_value_w (capabilities,
                                              TRUE,
                                              L"ApplicationDescription",
                                              &vtype,
                                              (void **) &description,
                                              NULL,
                                              NULL);

  if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
    g_clear_pointer (&description, g_free);

  description = read_resource_string (description);

  if (description && app->description == NULL)
    {
      app->description = g_wcsdup (description, -1);
      g_clear_pointer (&app->description_u8, g_free);
      app->description_u8 = g_utf16_to_utf8 (description, -1, NULL, NULL, NULL);
    }

  default_icon_key = g_win32_registry_key_get_child_w (appkey,
                                                       L"DefaultIcon",
                                                       NULL);

  icon_source = NULL;

  if (default_icon_key != NULL)
    {
      success = g_win32_registry_key_get_value_w (default_icon_key,
                                                  TRUE,
                                                  L"",
                                                  &vtype,
                                                  (void **) &icon_source,
                                                  NULL,
                                                  NULL);

      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
        g_clear_pointer (&icon_source, g_free);

      g_object_unref (default_icon_key);
    }

  if (icon_source == NULL)
    {
      success = g_win32_registry_key_get_value_w (capabilities,
                                                  TRUE,
                                                  L"ApplicationIcon",
                                                  &vtype,
                                                  (void **) &icon_source,
                                                  NULL,
                                                  NULL);

      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
        g_clear_pointer (&icon_source, g_free);
    }

  if (icon_source && app->icon == NULL)
    {
      gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
      app->icon = g_themed_icon_new (name);
      g_free (name);
    }

  narrow_application_name = NULL;
  success = g_win32_registry_key_get_value_w (capabilities,
                                              TRUE,
                                              L"ApplicationName",
                                              &vtype,
                                              (void **) &narrow_application_name,
                                              NULL,
                                              NULL);

  if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
    g_clear_pointer (&narrow_application_name, g_free);

  narrow_application_name = read_resource_string (narrow_application_name);

  /* TODO: do something with the narrow name. Maybe make a kind of sub-app?
   * Narrow name is a more precise name of the application in given context.
   * I.e. Thunderbird's name is "Thunderbird", whereas its narrow name is
   * "Thunderbird (news)" when registering it as a news client.
   * Maybe we should consider applications with different narrow names as
   * different applications altogether?
   */

  associations = g_win32_registry_key_get_child_w (capabilities,
                                                   L"FileAssociations",
                                                   NULL);

  if (associations != NULL)
    {
      GWin32RegistryValueIter iter;

      if (g_win32_registry_value_iter_init (&iter, associations, NULL))
        {
          gunichar2 *file_extension;
          gunichar2 *extension_handler;
          gsize      file_extension_len;
          gsize      extension_handler_size;
          GWin32RegistryValueType value_type;

          while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
            {
              GWin32AppInfoHandler *handler_rec;
              GWin32AppInfoHandler *handler_rec_in_ext;
              GWin32AppInfoFileExtension *ext;
              gunichar2 *program_command;
              gunichar2 *proxy_id;
              gunichar2 *proxy_command;
              GWin32RegistryKey *program_key;
              GWin32RegistryKey *proxy_key;
              gchar *program_id_u8;
              gchar *program_id_folded;
              gchar *file_extension_u8;
              gchar *file_extension_folded;

              if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                                &value_type,
                                                                NULL)) ||
                  (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
                  (!g_win32_registry_value_iter_get_name_w (&iter,
                                                            &file_extension,
                                                            &file_extension_len,
                                                            NULL)) ||
                  (file_extension_len <= 0) ||
                  (file_extension[0] != L'.') ||
                  (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
                                                            (void **) &extension_handler,
                                                            &extension_handler_size,
                                                            NULL)) ||
                  (extension_handler_size < sizeof (gunichar2)) ||
                  (extension_handler[0] == L'\0'))
                continue;

              if (!follow_class_chain_to_handler (extension_handler,
                                                  extension_handler_size,
                                                  &program_command,
                                                  &program_key,
                                                  &proxy_id,
                                                  &proxy_command,
                                                  &proxy_key,
                                                  &program_id_u8,
                                                  &program_id_folded))
                continue;

              handler_rec = g_hash_table_lookup (handlers,
                                                 program_id_folded);

              if (handler_rec == NULL)
                {
                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

                  handler_rec->proxy_key = proxy_key;
                  handler_rec->key = program_key;
                  handler_rec->handler_id =
                      g_wcsdup (extension_handler,extension_handler_size);
                  handler_rec->handler_id_folded =
                      g_strdup (program_id_folded);
                  handler_rec->handler_command =
                      program_command ? g_wcsdup (program_command, -1) : NULL;
                  handler_rec->proxy_id =
                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
                  handler_rec->proxy_command =
                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                               &handler_rec->executable,
                                               &handler_rec->executable_basename,
                                               &handler_rec->executable_folded,
                                               NULL,
                                               &handler_rec->dll_function);
                  if (handler_rec->dll_function != NULL)
                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
                  read_handler_icon (proxy_key,
                                     program_key,
                                     &handler_rec->icon);
                  g_hash_table_insert (handlers,
                                       g_strdup (program_id_folded),
                                       handler_rec);
                }
              else
                {
                  g_clear_object (&program_key);
                  g_clear_object (&proxy_key);
                }

                if (g_utf16_to_utf8_and_fold (file_extension,
                                              -1,
                                              &file_extension_u8,
                                              &file_extension_folded))
                  {
                    ext = g_hash_table_lookup (extensions,
                                               file_extension_folded);

                    if (ext == NULL)
                      {
                        ext = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);

                        ext->extension = g_wcsdup (file_extension, -1);
                        ext->extension_u8 = g_strdup (file_extension_u8);
                        g_hash_table_insert (extensions, g_strdup (file_extension_folded), ext);
                      }

                    handler_rec_in_ext =
                        g_hash_table_lookup (ext->handlers,
                                             program_id_folded);

                    if (handler_rec_in_ext == NULL)
                      {
                        if (ext->chosen_handler == NULL)
                          g_hash_table_insert (ext->handlers,
                                               g_strdup (program_id_folded),
                                               g_object_ref (handler_rec));
                        else if (ext->chosen_handler->handler_id_folded &&
                                 strcmp (ext->chosen_handler->handler_id_folded,
                                         program_id_folded) != 0)
                          g_hash_table_insert (ext->handlers,
                                               g_strdup (program_id_folded),
                                               g_object_ref (handler_rec));
                      }

                    handler_rec_in_ext =
                        g_hash_table_lookup (app->supported_exts,
                                             file_extension_folded);

                    if (handler_rec_in_ext == NULL)
                      g_hash_table_insert (app->supported_exts,
                                           g_strdup (file_extension_folded),
                                           g_object_ref (handler_rec));

                    g_free (file_extension_u8);
                    g_free (file_extension_folded);
                  }

              g_free (program_id_u8);
              g_free (program_id_folded);
              g_free (program_command);
              g_free (proxy_id);
              g_free (proxy_command);
            }

          g_win32_registry_value_iter_clear (&iter);
        }

      g_object_unref (associations);
    }

  associations = g_win32_registry_key_get_child_w (capabilities, L"URLAssociations", NULL);

  if (associations != NULL)
    {
      GWin32RegistryValueIter iter;

      if (g_win32_registry_value_iter_init (&iter, associations, NULL))
        {
          gunichar2 *url_schema;
          gunichar2 *schema_handler;
          gsize      url_schema_len;
          gsize      schema_handler_size;
          GWin32RegistryValueType value_type;

          while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
            {
              GWin32AppInfoHandler *handler_rec;
              GWin32AppInfoHandler *handler_rec_in_url;
              GWin32AppInfoURLSchema *schema;
              gunichar2 *program_command;
              gunichar2 *proxy_id;
              gunichar2 *proxy_command;
              GWin32RegistryKey *program_key;
              GWin32RegistryKey *proxy_key;
              gchar *program_id_u8;
              gchar *program_id_folded;
              gchar *schema_u8;
              gchar *schema_folded;

              if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                                &value_type,
                                                                NULL)) ||
                  ((value_type != G_WIN32_REGISTRY_VALUE_STR) &&
                   (value_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) ||
                  (!g_win32_registry_value_iter_get_name_w (&iter,
                                                            &url_schema,
                                                            &url_schema_len,
                                                            NULL)) ||
                  (url_schema_len <= 0) ||
                  (url_schema[0] == L'\0') ||
                  (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
                                                            (void **) &schema_handler,
                                                            &schema_handler_size,
                                                            NULL)) ||
                  (schema_handler_size < sizeof (gunichar2)) ||
                  (schema_handler[0] == L'\0'))
                continue;

              if (!follow_class_chain_to_handler (schema_handler,
                                                  schema_handler_size,
                                                  &program_command,
                                                  &program_key,
                                                  &proxy_id,
                                                  &proxy_command,
                                                  &proxy_key,
                                                  &program_id_u8,
                                                  &program_id_folded))
                continue;

              
              handler_rec = g_hash_table_lookup (handlers, program_id_folded);

              if (handler_rec == NULL)
                {
                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

                  handler_rec->proxy_key = proxy_key;
                  handler_rec->key = program_key;
                  handler_rec->handler_id =
                      g_wcsdup (schema_handler, schema_handler_size);
                  handler_rec->handler_id_folded =
                      g_strdup (program_id_folded);
                  handler_rec->handler_command = program_command ?
                      g_wcsdup (program_command, -1) : NULL;
                  handler_rec->proxy_id =
                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
                  handler_rec->proxy_command =
                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                               &handler_rec->executable,
                                               &handler_rec->executable_basename,
                                               &handler_rec->executable_folded,
                                               NULL,
                                               &handler_rec->dll_function);
                  if (handler_rec->dll_function != NULL)
                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
                  read_handler_icon (proxy_key,
                                     program_key,
                                     &handler_rec->icon);
                  g_hash_table_insert (handlers,
                                       g_strdup (program_id_folded),
                                       handler_rec);
                }
              else
                {
                  g_clear_object (&program_key);
                  g_clear_object (&proxy_key);
                }

                if (g_utf16_to_utf8_and_fold (url_schema,
                                              -1,
                                              &schema_u8,
                                              &schema_folded))
                  {
                    schema = g_hash_table_lookup (urls,
                                                  schema_folded);

                    if (schema == NULL)
                      {
                        schema = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);

                        schema->schema = g_wcsdup (url_schema, -1);
                        schema->schema_u8 = g_strdup (schema_u8);
                        schema->schema_folded =
                            g_strdup (schema_folded);
                        g_hash_table_insert (urls,
                                             g_strdup (schema_folded),
                                             schema);
                      }

                    handler_rec_in_url =
                        g_hash_table_lookup (schema->handlers,
                                             program_id_folded);

                    if (handler_rec_in_url == NULL)
                      g_hash_table_insert (schema->handlers,
                                           g_strdup (program_id_folded),
                                           g_object_ref (handler_rec));

                    handler_rec_in_url =
                        g_hash_table_lookup (app->supported_urls,
                                             schema_folded);

                    if (handler_rec_in_url == NULL)
                      g_hash_table_insert (app->supported_urls,
                                           g_strdup (schema_folded),
                                           g_object_ref (handler_rec));

                    g_free (schema_u8);
                    g_free (schema_folded);
                  }

              g_free (program_id_u8);
              g_free (program_id_folded);
              g_free (program_command);
              g_free (proxy_id);
              g_free (proxy_command);
            }

          g_win32_registry_value_iter_clear (&iter);
        }

      g_object_unref (associations);
    }

  g_clear_pointer (&app_executable, g_free);
  g_clear_pointer (&app_executable_folded, g_free);
  g_clear_pointer (&app_dll_function, g_free);
  g_clear_pointer (&fallback_friendly_name, g_free);
  g_clear_pointer (&description, g_free);
  g_clear_pointer (&icon_source, g_free);
  g_clear_pointer (&narrow_application_name, g_free);
  g_clear_pointer (&shell_open_command, g_free);

  g_object_unref (appkey);
  g_object_unref (shell_open_command_key);
  g_object_unref (capabilities);
  g_free (canonical_name_u8);
  g_free (canonical_name_folded);
  g_free (app_key_path);
}

static void
read_urls (GWin32RegistryKey *url_associations)
{
  GWin32RegistrySubkeyIter url_iter;
  gunichar2 *url_schema;
  gsize url_schema_len;

  if (url_associations == NULL)
    return;

  if (!g_win32_registry_subkey_iter_init (&url_iter, url_associations, NULL))
    return;

  while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL))
    {
      if (!g_win32_registry_subkey_iter_get_name_w (&url_iter,
                                                    &url_schema,
                                                    &url_schema_len,
                                                    NULL))
        continue;

      get_url_association (url_schema);
    }

  g_win32_registry_subkey_iter_clear (&url_iter);
}

static void
read_exeapps (void)
{
  GWin32RegistryKey *applications_key;
  GWin32RegistrySubkeyIter app_iter;

  applications_key =
      g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);

  if (applications_key == NULL)
    return;

  if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL))
    {
      g_object_unref (applications_key);
      return;
    }

  while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
    {
      gunichar2 *app_exe_basename;
      gsize app_exe_basename_len;
      GWin32RegistryKey *incapable_app;
      gunichar2 *friendly_app_name;
      gboolean success;
      gboolean no_open_with;
      GWin32RegistryValueType vtype;
      GWin32RegistryKey *default_icon_key;
      gunichar2 *icon_source;
      GIcon *icon = NULL;
      gchar *appexe;
      gchar *appexe_basename;
      gchar *appexe_folded;
      gchar *appexe_folded_basename;
      GWin32AppInfoApplication *app;
      GWin32RegistryKey *shell_open_command_key;
      gunichar2 *shell_open_command;
      GWin32RegistryKey *supported_key;

      if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
                                                    &app_exe_basename,
                                                    &app_exe_basename_len,
                                                    NULL) ||
          !g_utf16_validate (app_exe_basename, app_exe_basename_len))
        continue;

      incapable_app =
          g_win32_registry_key_get_child_w (applications_key,
                                            app_exe_basename,
                                            NULL);

      if (incapable_app == NULL)
        continue;

      _g_win32_extract_executable (app_exe_basename,
                                   &appexe,
                                   &appexe_basename,
                                   &appexe_folded,
                                   &appexe_folded_basename,
                                   NULL);

      shell_open_command_key =
          g_win32_registry_key_get_child_w (incapable_app,
                                            L"shell\\open\\command",
                                            NULL);

      shell_open_command = NULL;

      if (shell_open_command_key != NULL)
        {
          success = g_win32_registry_key_get_value_w (shell_open_command_key,
                                                      TRUE,
                                                      L"",
                                                      &vtype,
                                                      (gpointer *) &shell_open_command,
                                                      NULL,
                                                      NULL);

          if (success &&
              (vtype != G_WIN32_REGISTRY_VALUE_STR ||
               !g_utf16_validate (shell_open_command, -1)))
            {
              g_clear_pointer (&shell_open_command, g_free);
            }

          g_object_unref (shell_open_command_key);
        }

      friendly_app_name = NULL;
      success = g_win32_registry_key_get_value_w (incapable_app,
                                                  TRUE,
                                                  L"FriendlyAppName",
                                                  &vtype,
                                                  (void **) &friendly_app_name,
                                                  NULL,
                                                  NULL);

      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
        g_clear_pointer (&friendly_app_name, g_free);

      friendly_app_name = read_resource_string (friendly_app_name);

      no_open_with = FALSE;
      success = g_win32_registry_key_get_value_w (incapable_app,
                                                  TRUE,
                                                  L"NoOpenWith",
                                                  &vtype,
                                                  NULL,
                                                  NULL,
                                                  NULL);

      if (success)
        no_open_with = TRUE;

      default_icon_key =
          g_win32_registry_key_get_child_w (incapable_app,
                                            L"DefaultIcon",
                                            NULL);

      icon_source = NULL;

      if (default_icon_key != NULL)
      {
        success =
            g_win32_registry_key_get_value_w (default_icon_key,
                                              TRUE,
                                              L"",
                                              &vtype,
                                              (void **) &icon_source,
                                              NULL,
                                              NULL);

        if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
          g_clear_pointer (&icon_source, g_free);

        g_object_unref (default_icon_key);
      }

      if (icon_source)
        {
          gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
          icon = g_themed_icon_new (name);
          g_free (name);
        }

      app = g_hash_table_lookup (apps_by_exe, appexe_folded_basename);

      if (app == NULL)
        {
          app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);

          if (shell_open_command)
            {
              gchar *dll_function;

              _g_win32_extract_executable (shell_open_command,
                                           NULL,
                                           NULL,
                                           NULL,
                                           NULL,
                                           &dll_function);
              if (dll_function != NULL)
                _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command);
              g_clear_pointer (&dll_function, g_free);
            }

          app->command =
              shell_open_command ? g_wcsdup (shell_open_command, -1) : NULL;

          if (shell_open_command)
            app->command_u8 = g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL);

          app->executable = g_strdup (appexe);
          app->executable_basename = &app->executable[appexe_basename - appexe];
          app->executable_folded = g_strdup (appexe_folded);

          app->no_open_with = no_open_with;

          if (friendly_app_name)
            {
              app->localized_pretty_name = g_wcsdup (friendly_app_name, -1);
              g_clear_pointer (&app->localized_pretty_name_u8, g_free);
              app->localized_pretty_name_u8 =
                  g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL);
            }

          if (icon)
            app->icon = g_object_ref (icon);

          app->user_specific = FALSE;
          app->default_app = FALSE;

          g_hash_table_insert (apps_by_exe,
                               g_strdup (appexe_folded_basename),
                               app);
        }

      supported_key =
          g_win32_registry_key_get_child_w (incapable_app,
                                            L"SupportedTypes",
                                            NULL);

      if (supported_key)
        {
          GWin32RegistryValueIter sup_iter;
          if (g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL))
            {
              gunichar2 *ext_name;
              gsize      ext_name_len;

              while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
                {
                  gchar *ext_u8;
                  gchar *ext_folded;
                  GWin32AppInfoFileExtension *file_extn;
                  gboolean file_ext_known;

                  if ((!g_win32_registry_value_iter_get_name_w (&sup_iter,
                                                                &ext_name,
                                                                &ext_name_len,
                                                                NULL)) ||
                      (ext_name_len <= 0) ||
                      (ext_name[0] != L'.') ||
                      (!g_utf16_to_utf8_and_fold (ext_name,
                                                  -1,
                                                  &ext_u8,
                                                  &ext_folded)))
                    continue;

                  file_extn = NULL;
                  file_ext_known =
                      g_hash_table_lookup_extended (extensions,
                                                    ext_folded,
                                                    NULL,
                                                    (void **) &file_extn);

                  if (!file_ext_known)
                    {
                      file_extn =
                          g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
                      file_extn->extension = g_wcsdup (ext_name, -1);
                      file_extn->extension_u8 = g_strdup (ext_u8);
                      g_hash_table_insert (extensions,
                                           g_strdup (ext_folded),
                                           file_extn);
                    }

                  g_hash_table_insert (file_extn->other_apps,
                                       g_strdup (appexe_folded),
                                       g_object_ref (app));

                  g_free (ext_u8);
                  g_free (ext_folded);
                }

              g_win32_registry_value_iter_clear (&sup_iter);
            }

          g_object_unref (supported_key);
        }


      g_free (appexe);
      g_free (appexe_folded);
      g_free (shell_open_command);
      g_free (friendly_app_name);
      g_free (icon_source);

      g_clear_object (&icon);
      g_clear_object (&incapable_app);
    }

  g_win32_registry_subkey_iter_clear (&app_iter);
  g_object_unref (applications_key);
}


static void
read_exts (GWin32RegistryKey *file_exts)
{
  GWin32RegistrySubkeyIter ext_iter;
  gunichar2 *file_extension;
  gsize file_extension_len;

  if (file_exts == NULL)
    return;

  if (!g_win32_registry_subkey_iter_init (&ext_iter, file_exts, NULL))
    return;

  while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL))
    {
      if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter,
                                                    &file_extension,
                                                    &file_extension_len,
                                                    NULL))
        continue;

      get_file_ext (file_extension);
    }

  g_win32_registry_subkey_iter_clear (&ext_iter);
}

static void
read_class_extension (GWin32RegistryKey *classes_root,
                      gunichar2         *class_name,
                      gsize              class_name_len)
{
  gchar *ext_u8;
  gchar *ext_folded;
  GWin32AppInfoFileExtension *file_extn;
  GWin32AppInfoHandler *handler_rec;
  GWin32AppInfoHandler *handler_rec_in_ext;
  GWin32RegistryKey *class_key;
  gsize program_id_size;
  gunichar2 *program_id;
  gunichar2 *proxy_id;
  GWin32RegistryKey *program_key;
  GWin32RegistryKey *proxy_key;
  gunichar2 *program_command;
  gunichar2 *proxy_command;

  class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);

  if (class_key == NULL)
    return;

  program_id = class_name;
  program_id_size = (class_name_len + 1) * sizeof (gunichar2);
  program_key = proxy_key = NULL;
  program_command = proxy_command = NULL;

  if (!follow_class_chain_to_handler (program_id,
                                      program_id_size,
                                      &program_command,
                                      &program_key,
                                      &proxy_id,
                                      &proxy_command,
                                      &proxy_key,
                                      &ext_u8,
                                      &ext_folded))
    {
      g_object_unref (class_key);
      return;
    }


  file_extn = g_hash_table_lookup (extensions, ext_folded);
  handler_rec = g_hash_table_lookup (handlers, ext_folded);

  if (file_extn == NULL)
    {
      file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
      file_extn->extension = g_wcsdup (class_name, -1);
      file_extn->extension_u8 = g_strdup (ext_u8);
      g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn);
    }

  if (handler_rec == NULL)
    {
      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

      handler_rec->proxy_key = proxy_key;
      handler_rec->key = program_key;
      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
      handler_rec->handler_id_folded = g_strdup (ext_folded);
      handler_rec->handler_command =
          program_command ? g_wcsdup (program_command, -1) : NULL;
      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
      handler_rec->proxy_command =
          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                   &handler_rec->executable,
                                   &handler_rec->executable_basename,
                                   &handler_rec->executable_folded,
                                   NULL,
                                   &handler_rec->dll_function);
      if (handler_rec->dll_function != NULL)
        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
      g_hash_table_insert (handlers,
                           g_strdup (ext_folded),
                           handler_rec);
    }
  else
    {
      g_clear_object (&program_key);
      g_clear_object (&proxy_key);
    }

  handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
                                            ext_folded);

  if (file_extn->chosen_handler == NULL)
    g_hash_table_insert (file_extn->handlers,
                         g_strdup (ext_folded),
                         g_object_ref (handler_rec));
  else if (handler_rec_in_ext == NULL)
    {
      if (file_extn->chosen_handler->handler_id_folded &&
          strcmp (file_extn->chosen_handler->handler_id_folded,
                  ext_folded) != 0)
        g_hash_table_insert (file_extn->handlers,
                             g_strdup (ext_folded),
                             g_object_ref (handler_rec));
    }

  g_free (program_command);
  g_free (proxy_id);
  g_free (proxy_command);
  g_free (ext_u8);
  g_free (ext_folded);
  g_object_unref (class_key);
}

static void
read_class_url (GWin32RegistryKey *classes_root,
                gunichar2         *class_name,
                gsize              class_name_len)
{
  GWin32RegistryKey *class_key;
  gboolean success;
  GWin32RegistryValueType vtype;
  GWin32AppInfoURLSchema *schema_rec;
  GWin32AppInfoHandler *handler_rec;
  GWin32AppInfoHandler *handler_rec_in_url;
  gunichar2 *program_id;
  gsize program_id_size;
  gunichar2 *program_command;
  gunichar2 *proxy_id;
  gunichar2 *proxy_command;
  gchar *program_id_u8;
  gchar *program_id_folded;
  GWin32RegistryKey *program_key;
  GWin32RegistryKey *proxy_key;

  class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);

  if (class_key == NULL)
    return;

  success = g_win32_registry_key_get_value_w (class_key,
                                              TRUE,
                                              L"URL Protocol",
                                              &vtype,
                                              NULL,
                                              NULL,
                                              NULL);

  if (!success ||
      vtype != G_WIN32_REGISTRY_VALUE_STR)
    {
      g_object_unref (class_key);
      return;
    }

  program_id = class_name;
  program_id_size = (class_name_len + 1) * sizeof (gunichar2);
  proxy_key = program_key = NULL;
  program_command = proxy_id = proxy_command = NULL;

  if (!follow_class_chain_to_handler (program_id,
                                      program_id_size,
                                      &program_command,
                                      &program_key,
                                      &proxy_id,
                                      &proxy_command,
                                      &proxy_key,
                                      &program_id_u8,
                                      &program_id_folded))
    {
      g_object_unref (class_key);
      return;
    }

  schema_rec = g_hash_table_lookup (urls, program_id_folded);
  handler_rec = g_hash_table_lookup (handlers, program_id_folded);

  if (handler_rec == NULL)
    {
      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);

      handler_rec->proxy_key = proxy_key;
      handler_rec->key = program_key;
      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
      handler_rec->handler_id_folded =
          g_strdup (program_id_folded);
      handler_rec->handler_command =
          program_command ? g_wcsdup (program_command, -1) : NULL;
      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
      handler_rec->proxy_command =
          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
                                   &handler_rec->executable,
                                   &handler_rec->executable_basename,
                                   &handler_rec->executable_folded,
                                   NULL,
                                   &handler_rec->dll_function);
      if (handler_rec->dll_function != NULL)
        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command);
      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
      g_hash_table_insert (handlers,
                           g_strdup (program_id_folded),
                           handler_rec);
    }
  else
    {
      g_clear_object (&program_key);
      g_clear_object (&proxy_key);
    }

  if (schema_rec == NULL)
    {
      schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
      schema_rec->schema = g_wcsdup (class_name, -1);
      schema_rec->schema_u8 = g_strdup (program_id_u8);
      schema_rec->schema_folded = g_strdup (program_id_folded);
      schema_rec->chosen_handler = g_object_ref (handler_rec);
      g_hash_table_insert (urls,
                           g_strdup (program_id_folded),
                           schema_rec);
    }

  if (schema_rec->chosen_handler == NULL)
    schema_rec->chosen_handler = g_object_ref (handler_rec);

  handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers,
                                            program_id_folded);

  if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec)
    g_hash_table_insert (schema_rec->handlers,
                         g_strdup (program_id_folded),
                         g_object_ref (handler_rec));

  g_free (program_id_u8);
  g_free (program_id_folded);
  g_free (program_command);
  g_free (proxy_id);
  g_free (proxy_command);
  g_object_unref (class_key);
}

static void
read_classes (GWin32RegistryKey *classes_root)
{
  GWin32RegistrySubkeyIter class_iter;
  gunichar2 *class_name;
  gsize class_name_len;

  if (classes_root == NULL)
    return;

  if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL))
    return;

  while (g_win32_registry_subkey_iter_next (&class_iter, TRUE, NULL))
    {
      if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter,
                                                     &class_name,
                                                     &class_name_len,
                                                     NULL)) ||
          (class_name_len <= 1))
        continue;

      if (class_name[0] == L'.')
        read_class_extension (classes_root, class_name, class_name_len);
      else
        {
          gsize i;

          for (i = 0; i < class_name_len; i++)
            if (!iswalpha (class_name[i]))
              break;

          if (i == class_name_len)
            read_class_url (classes_root, class_name, class_name_len);
        }
    }

  g_win32_registry_subkey_iter_clear (&class_iter);
}

static void
link_chosen_handlers (void)
{
  GHashTableIter iter;
  GHashTableIter handler_iter;
  gchar *schema_folded;
  GWin32AppInfoURLSchema *schema;
  gchar *handler_id_folded;
  GWin32AppInfoHandler *handler;
  gchar *ext_folded;
  GWin32AppInfoFileExtension *ext;

  g_hash_table_iter_init (&iter, urls);

  while (g_hash_table_iter_next (&iter,
                                (gpointer *) &schema_folded,
                                (gpointer *) &schema))
    {
      if (schema->chosen_handler != NULL)
        continue;

      g_hash_table_iter_init (&handler_iter, schema->handlers);

      while (g_hash_table_iter_next (&handler_iter,
                                     (gpointer *) &handler_id_folded,
                                     (gpointer *) &handler))
        {
          gchar *proxy_id_folded;

          if (schema->chosen_handler != NULL)
            break;

          if (strcmp (handler_id_folded, schema_folded) != 0)
            continue;

          if (handler->proxy_command &&
              handler->proxy_id &&
              g_utf16_to_utf8_and_fold (handler->proxy_id,
                                        -1,
                                        NULL,
                                        &proxy_id_folded))
            {
              GWin32AppInfoHandler *proxy;

              proxy = g_hash_table_lookup (handlers, proxy_id_folded);

              if (proxy)
                {
                  schema->chosen_handler = g_object_ref (proxy);
                  g_debug ("Linking schema %s to proxy handler %c ? \"%S\" : %S\n",
                           schema->schema_u8,
                           schema->chosen_handler->proxy_id ? 'P' : 'T',
                           schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id,
                           schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command);
                }

              g_free (proxy_id_folded);
            }

          if (schema->chosen_handler == NULL)
            {
              schema->chosen_handler = g_object_ref (handler);
              g_debug ("Linking schema %s to handler %c ? \"%S\" : %S\n",
                       schema->schema_u8,
                       schema->chosen_handler->proxy_id ? 'P' : 'T',
                       schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id,
                       schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command);
            }
        }
    }

  g_hash_table_iter_init (&iter, extensions);

  while (g_hash_table_iter_next (&iter,
                                 (gpointer *) &ext_folded,
                                 (gpointer *) &ext))
    {
      if (ext->chosen_handler != NULL)
        continue;

      g_hash_table_iter_init (&handler_iter, ext->handlers);

      while (g_hash_table_iter_next (&handler_iter,
                                     (gpointer *) &handler_id_folded,
                                     (gpointer *) &handler))
        {
          gchar *proxy_id_folded;

          if (ext->chosen_handler != NULL)
            break;

          if (strcmp (handler_id_folded, ext_folded) != 0)
            continue;

          if (handler->proxy_command &&
              handler->proxy_id &&
              g_utf16_to_utf8_and_fold (handler->proxy_id,
                                        -1,
                                        NULL,
                                        &proxy_id_folded))
            {
              GWin32AppInfoHandler *proxy;

              proxy = g_hash_table_lookup (handlers, proxy_id_folded);

              if (proxy)
                {
                  ext->chosen_handler = g_object_ref (proxy);
                  g_debug ("Linking ext %s to proxy handler %c ? \"%S\" : %S\n",
                           ext->extension_u8,
                           ext->chosen_handler->proxy_id ? 'P' : 'T',
                           ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id,
                           ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command);
                }

              g_free (proxy_id_folded);
            }

          if (ext->chosen_handler == NULL)
            {
              ext->chosen_handler = g_object_ref (handler);
              g_debug ("Linking ext %s to handler %c ? \"%S\" : %S\n",
                       ext->extension_u8,
                       ext->chosen_handler->proxy_id ? 'P' : 'T',
                       ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id,
                       ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command);
            }
        }
    }
}

static void
link_handlers_to_registered_apps (void)
{
  GHashTableIter iter;
  GHashTableIter sup_iter;
  gchar *app_id_folded;
  GWin32AppInfoApplication *app;
  gchar *schema_folded;
  GWin32AppInfoURLSchema *schema;
  gchar *ext_folded;
  GWin32AppInfoFileExtension *ext;
  gsize unhandled_exts;

  g_hash_table_iter_init (&sup_iter, urls);
  while (g_hash_table_iter_next (&sup_iter,
                                 (gpointer *) &schema_folded,
                                 (gpointer *) &schema))
    {
      if (schema->chosen_handler == NULL)
        g_debug ("WARNING: schema %s has no chosen handler\n", schema->schema_u8);
    }
  unhandled_exts= 0;
  g_hash_table_iter_init (&sup_iter, extensions);
  while (g_hash_table_iter_next (&sup_iter,
                                 (gpointer *) &ext_folded,
                                 (gpointer *) &ext))
    {
      if (ext->chosen_handler == NULL)
        {
          g_debug ("WARNING: extension %s has no chosen handler\n",
                   ext->extension_u8);
          unhandled_exts += 1;
        }
    }

  g_hash_table_iter_init (&iter, apps_by_id);
  while (g_hash_table_iter_next (&iter,
                                 (gpointer *) &app_id_folded,
                                 (gpointer *) &app))
    {
      if (app->supported_urls)
        {
          GWin32AppInfoHandler *handler;

          g_hash_table_iter_init (&sup_iter, app->supported_urls);
          while (g_hash_table_iter_next (&sup_iter,
                                         (gpointer *) &schema_folded,
                                         (gpointer *) &handler))
            {
              schema = g_hash_table_lookup (urls, schema_folded);

              g_assert (schema != NULL);

              if (schema->chosen_handler != NULL &&
                  schema->chosen_handler->app == NULL)
                {
                  schema->chosen_handler->app = g_object_ref (app);
                  g_debug ("Linking %S", app->canonical_name);

                  if (app->localized_pretty_name)
                    g_debug (" '%S'", app->localized_pretty_name);
                  else if (app->pretty_name)
                    g_debug (" '%S'", app->pretty_name);
                  else
                    g_debug (" '%s'", app->executable);

                  if (app->command)
                    g_debug (" %S", app->command);

                  g_debug ("\n to schema %s handler %c ? \"%S\" : %S\n",
                           schema->schema_u8,
                           schema->chosen_handler->proxy_id ? 'P' : 'T',
                           schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id,
                           schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command);
                }
            }

          g_hash_table_iter_init (&sup_iter, app->supported_urls);
          while (g_hash_table_iter_next (&sup_iter,
                                         (gpointer *) &schema_folded,
                                         (gpointer *) &handler))
            {
              if (handler->app == NULL)
                {
                  handler->app = g_object_ref (app);
                  g_debug ("Linking %S", app->canonical_name);

                  if (app->localized_pretty_name)
                    g_debug (" '%S'", app->localized_pretty_name);
                  else if (app->pretty_name)
                    g_debug (" '%S'", app->pretty_name);
                  else
                    g_debug (" '%s'", app->executable);

                  if (app->command)
                    g_debug (" %S", app->command);

                  g_debug ("\n directly to schema handler to %c ? \"%S\" : %S\n",
                           handler->proxy_id ? 'P' : 'T',
                           handler->proxy_id ? handler->proxy_id : handler->handler_id,
                           handler->proxy_command ? handler->proxy_command : handler->handler_command);
                }
            }
        }

      if (app->supported_exts)
        {
          GWin32AppInfoHandler *handler;

          g_hash_table_iter_init (&sup_iter, app->supported_exts);
          while (g_hash_table_iter_next (&sup_iter,
                                         (gpointer *) &ext_folded,
                                         (gpointer *) &handler))
            {
              ext = g_hash_table_lookup (extensions, ext_folded);

              g_assert (ext != NULL);

              if (ext->chosen_handler != NULL &&
                  ext->chosen_handler->app == NULL)
                {
                  ext->chosen_handler->app = g_object_ref (app);
                  g_debug ("Linking %S", app->canonical_name);

                  if (app->localized_pretty_name)
                    g_debug (" '%S'", app->localized_pretty_name);
                  else if (app->pretty_name)
                    g_debug (" '%S'", app->pretty_name);
                  else
                    g_debug (" '%s'", app->executable);

                  if (app->command)
                    g_debug (" %S", app->command);

                  g_debug ("\n to ext %s handler %c ? \"%S\" : %S\n",
                           ext->extension_u8,
                           ext->chosen_handler->proxy_id ? 'P' : 'T',
                           ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id,
                           ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command);
                }
            }

          g_hash_table_iter_init (&sup_iter, app->supported_exts);
          while (g_hash_table_iter_next (&sup_iter,
                                         (gpointer *) &ext_folded,
                                         (gpointer *) &handler))
            {
              if (handler->app == NULL)
                {
                  handler->app = g_object_ref (app);
                  g_debug ("Linking %S", app->canonical_name);

                  if (app->localized_pretty_name)
                    g_debug (" '%S'", app->localized_pretty_name);
                  else if (app->pretty_name)
                    g_debug (" '%S'", app->pretty_name);
                  else
                    g_debug (" '%s'", app->executable);

                  if (app->command)
                    g_debug (" %S", app->command);

                  g_debug ("\n directly to ext handler %c ? \"%S\" : %S\n",
                           handler->proxy_id ? 'P' : 'T',
                           handler->proxy_id ? handler->proxy_id : handler->handler_id,
                           handler->proxy_command ? handler->proxy_command : handler->handler_command);
                }
            }
        }
    }

  g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts);
  unhandled_exts= 0;
  g_hash_table_iter_init (&sup_iter, extensions);
  while (g_hash_table_iter_next (&sup_iter,
                                 (gpointer *) &ext_folded,
                                 (gpointer *) &ext))
    {
      if (ext->chosen_handler == NULL)
        {
          g_debug ("WARNING: extension %s has no chosen handler\n",
                   ext->extension_u8);
          unhandled_exts += 1;
        }
    }
  g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts);
}

static void
link_handlers_to_unregistered_apps (void)
{
  GHashTableIter iter;
  GHashTableIter app_iter;
  GWin32AppInfoHandler *handler;
  gchar *handler_id_fc;
  GWin32AppInfoApplication *app;
  gchar *canonical_name_fc;
  gchar *appexe_fc_basename;

  g_hash_table_iter_init (&iter, handlers);
  while (g_hash_table_iter_next (&iter,
                                 (gpointer *) &handler_id_fc,
                                 (gpointer *) &handler))
    {
      gchar *hndexe_fc_basename;

      if ((handler->app != NULL) ||
          (handler->executable_folded == NULL))
        continue;

      g_hash_table_iter_init (&app_iter, apps_by_id);

      while (g_hash_table_iter_next (&app_iter,
                                     (gpointer *) &canonical_name_fc,
                                     (gpointer *) &app))
        {
          if (app->executable_folded == NULL)
            continue;

          if (strcmp (app->executable_folded,
                      handler->executable_folded) != 0)
            continue;

          handler->app = app;
          break;
        }

      if (handler->app != NULL)
        continue;

      hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1);

      if (hndexe_fc_basename == NULL)
        continue;

      g_hash_table_iter_init (&app_iter, apps_by_exe);

      while ((hndexe_fc_basename != NULL) &&
             (g_hash_table_iter_next (&app_iter,
                                      (gpointer *) &appexe_fc_basename,
                                      (gpointer *) &app)))
        {
          /* Use basename because apps_by_exe only has basenames */
          if (strcmp (hndexe_fc_basename, appexe_fc_basename) != 0)
            continue;

          handler->app = app;
          break;
        }

      g_free (hndexe_fc_basename);

      if (handler->app == NULL)
        g_debug ("WARNING: handler that runs %s has no corresponding app\n",
                 handler->executable);
    }
}


static void
update_registry_data (void)
{
  guint i;
  GPtrArray *capable_apps_keys;
  GPtrArray *user_capable_apps_keys;
  GPtrArray *priority_capable_apps_keys;
  GWin32RegistryKey *url_associations;
  GWin32RegistryKey *file_exts;
  GWin32RegistryKey *classes_root;
  DWORD collect_start, collect_end, alloc_end, capable_end, url_end, ext_end, exeapp_end, classes_end, postproc_end;

  url_associations =
      g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
                                   NULL);
  file_exts =
      g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
                                   NULL);
  classes_root = g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT", NULL);

  capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
  user_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
  priority_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);

  g_clear_pointer (&apps_by_id, g_hash_table_destroy);
  g_clear_pointer (&apps_by_exe, g_hash_table_destroy);
  g_clear_pointer (&urls, g_hash_table_destroy);
  g_clear_pointer (&extensions, g_hash_table_destroy);
  g_clear_pointer (&handlers, g_hash_table_destroy);

  collect_start = GetTickCount ();
  collect_capable_apps_from_clients (capable_apps_keys,
                                     priority_capable_apps_keys,
                                     FALSE);
  collect_capable_apps_from_clients (user_capable_apps_keys,
                                     priority_capable_apps_keys,
                                     TRUE);
  collect_capable_apps_from_registered_apps (user_capable_apps_keys,
                                             TRUE);
  collect_capable_apps_from_registered_apps (capable_apps_keys,
                                             FALSE);
  collect_end = GetTickCount ();

  apps_by_id =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  apps_by_exe =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  urls =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  extensions =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  handlers =
      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
  alloc_end = GetTickCount ();

  for (i = 0; i < priority_capable_apps_keys->len; i++)
    read_capable_app (g_ptr_array_index (priority_capable_apps_keys, i),
                      TRUE,
                      TRUE);
  for (i = 0; i < user_capable_apps_keys->len; i++)
    read_capable_app (g_ptr_array_index (user_capable_apps_keys, i),
                      TRUE,
                      FALSE);
  for (i = 0; i < capable_apps_keys->len; i++)
    read_capable_app (g_ptr_array_index (capable_apps_keys, i),
                      FALSE,
                      FALSE);
  capable_end = GetTickCount ();

  read_urls (url_associations);
  url_end = GetTickCount ();
  read_exts (file_exts);
  ext_end = GetTickCount ();
  read_exeapps ();
  exeapp_end = GetTickCount ();
  read_classes (classes_root);
  classes_end = GetTickCount ();
  link_chosen_handlers ();
  link_handlers_to_registered_apps ();
  link_handlers_to_unregistered_apps ();
  postproc_end = GetTickCount ();

  g_debug ("Collecting capable appnames: %lums\n"
           "Allocating hashtables:...... %lums\n"
           "Reading capable apps:        %lums\n"
           "Reading URL associations:... %lums\n"
           "Reading extension assocs:    %lums\n"
           "Reading exe-only apps:...... %lums\n"
           "Reading classes:             %lums\n"
           "Postprocessing:..............%lums\n"
           "TOTAL:                       %lums\n",
           collect_end - collect_start,
           alloc_end - collect_end,
           capable_end - alloc_end,
           url_end - capable_end,
           ext_end - url_end,
           exeapp_end - ext_end,
           classes_end - exeapp_end,
           postproc_end - classes_end,
           postproc_end - collect_start);

  g_clear_object (&classes_root);
  g_clear_object (&url_associations);
  g_clear_object (&file_exts);
  g_ptr_array_free (capable_apps_keys, TRUE);
  g_ptr_array_free (user_capable_apps_keys, TRUE);
  g_ptr_array_free (priority_capable_apps_keys, TRUE);

  return;
}

static void
watch_keys (void)
{
  if (url_associations_key)
    g_win32_registry_key_watch (url_associations_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (file_exts_key)
    g_win32_registry_key_watch (file_exts_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (user_clients_key)
    g_win32_registry_key_watch (user_clients_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (system_clients_key)
    g_win32_registry_key_watch (system_clients_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (applications_key)
    g_win32_registry_key_watch (applications_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (user_registered_apps_key)
    g_win32_registry_key_watch (user_registered_apps_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (system_registered_apps_key)
    g_win32_registry_key_watch (system_registered_apps_key,
                                TRUE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);

  if (classes_root_key)
    g_win32_registry_key_watch (classes_root_key,
                                FALSE,
                                G_WIN32_REGISTRY_WATCH_NAME |
                                G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
                                G_WIN32_REGISTRY_WATCH_VALUES,
                                NULL,
                                NULL,
                                NULL);
}


static void
g_win32_appinfo_init (void)
{
  static gsize initialized;

  if (g_once_init_enter (&initialized))
    {
      url_associations_key =
          g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
                                       NULL);
      file_exts_key =
          g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
                                       NULL);
      user_clients_key =
          g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
                                       NULL);
      system_clients_key =
          g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
                                       NULL);
      applications_key =
          g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications",
                                       NULL);
      user_registered_apps_key =
          g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
                                       NULL);
      system_registered_apps_key =
          g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
                                       NULL);
      classes_root_key =
          g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT",
                                       NULL);

      watch_keys ();

      update_registry_data ();

      g_once_init_leave (&initialized, TRUE);
    }

  if ((url_associations_key       && g_win32_registry_key_has_changed (url_associations_key))       ||
      (file_exts_key              && g_win32_registry_key_has_changed (file_exts_key))              ||
      (user_clients_key           && g_win32_registry_key_has_changed (user_clients_key))           ||
      (system_clients_key         && g_win32_registry_key_has_changed (system_clients_key))         ||
      (applications_key           && g_win32_registry_key_has_changed (applications_key))           ||
      (user_registered_apps_key   && g_win32_registry_key_has_changed (user_registered_apps_key))   ||
      (system_registered_apps_key && g_win32_registry_key_has_changed (system_registered_apps_key)) ||
      (classes_root_key           && g_win32_registry_key_has_changed (classes_root_key)))
    {
      G_LOCK (gio_win32_appinfo);
      update_registry_data ();
      watch_keys ();
      G_UNLOCK (gio_win32_appinfo);
    }
}


static void g_win32_app_info_iface_init (GAppInfoIface *iface);

struct _GWin32AppInfo
{
  GObject parent_instance;

  /*<private>*/
  gchar **supported_types;

  GWin32AppInfoApplication *app;

  GWin32AppInfoHandler *handler;

  guint startup_notify : 1;
};

G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
                                                g_win32_app_info_iface_init))


static void
g_win32_app_info_finalize (GObject *object)
{
  GWin32AppInfo *info;

  info = G_WIN32_APP_INFO (object);

  g_clear_pointer (&info->supported_types, g_free);
  g_clear_object (&info->app);
  g_clear_object (&info->handler);

  G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
}

static void
g_win32_app_info_class_init (GWin32AppInfoClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = g_win32_app_info_finalize;
}

static void
g_win32_app_info_init (GWin32AppInfo *local)
{
}

static GAppInfo *
g_win32_app_info_new_from_app (GWin32AppInfoApplication *app,
                               GWin32AppInfoHandler     *handler)
{
  GWin32AppInfo *new_info;
  GHashTableIter iter;
  gpointer ext;
  int i;

  new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);

  new_info->app = g_object_ref (app);

  g_win32_appinfo_init ();
  G_LOCK (gio_win32_appinfo);

  i = 0;
  g_hash_table_iter_init (&iter, new_info->app->supported_exts);

  while (g_hash_table_iter_next (&iter, &ext, NULL))
    {
      if (ext)
        i += 1;
    }

  new_info->supported_types = g_malloc (sizeof (gchar *) * (i + 1));

  i = 0;
  g_hash_table_iter_init (&iter, new_info->app->supported_exts);

  while (g_hash_table_iter_next (&iter, &ext, NULL))
    {
      if (!ext)
        continue;

      new_info->supported_types[i] = (gchar *) ext;
      i += 1;
    }

  G_UNLOCK (gio_win32_appinfo);

  new_info->supported_types[i] = NULL;

  new_info->handler = handler ? g_object_ref (handler) : NULL;

  return G_APP_INFO (new_info);
}


static GAppInfo *
g_win32_app_info_dup (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
  GWin32AppInfo *new_info;

  new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);

  if (info->app)
    new_info->app = g_object_ref (info->app);

  if (info->handler)
    new_info->handler = g_object_ref (info->handler);

  new_info->startup_notify = info->startup_notify;

  if (info->supported_types)
    {
      int i;

      for (i = 0; info->supported_types[i]; i++)
        break;

      new_info->supported_types = g_malloc (sizeof (gchar *) * (i + 1));

      for (i = 0; info->supported_types[i]; i++)
        new_info->supported_types[i] = g_strdup (info->supported_types[i]);

      new_info->supported_types[i] = NULL;
    }

  return G_APP_INFO (new_info);
}

static gboolean
g_win32_app_info_equal (GAppInfo *appinfo1,
                        GAppInfo *appinfo2)
{
  GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1);
  GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2);

  if (info1->app == NULL ||
      info2->app == NULL)
    return info1 == info2;

  if (info1->app->canonical_name_folded != NULL &&
      info2->app->canonical_name_folded != NULL)
    return (strcmp (info1->app->canonical_name_folded,
                    info2->app->canonical_name_folded)) == 0;

  if (info1->app->executable_folded != NULL &&
      info2->app->executable_folded != NULL)
    return (strcmp (info1->app->executable_folded,
                    info2->app->executable_folded)) == 0;

  return info1->app == info2->app;
}

static const char *
g_win32_app_info_get_id (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return NULL;

  if (info->app->canonical_name_u8)
    return info->app->canonical_name_u8;

  if (info->app->executable_basename)
    return info->app->executable_basename;

  return NULL;
}

static const char *
g_win32_app_info_get_name (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app && info->app->canonical_name_u8)
    return info->app->canonical_name_u8;
  else
    return P_("Unnamed");
}

static const char *
g_win32_app_info_get_display_name (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app)
    {
      if (info->app->localized_pretty_name_u8)
        return info->app->localized_pretty_name_u8;
      else if (info->app->pretty_name_u8)
        return info->app->pretty_name_u8;
    }

  return g_win32_app_info_get_name (appinfo);
}

static const char *
g_win32_app_info_get_description (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return NULL;

  return info->app->description_u8;
}

static const char *
g_win32_app_info_get_executable (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return NULL;

  return info->app->executable;
}

static const char *
g_win32_app_info_get_commandline (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return NULL;

  return info->app->command_u8;
}

static GIcon *
g_win32_app_info_get_icon (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return NULL;

  return info->app->icon;
}

typedef struct _file_or_uri {
  gchar *uri;
  gchar *file;
} file_or_uri;

static char *
expand_macro_single (char macro, file_or_uri *obj)
{
  char *result = NULL;

  switch (macro)
    {
    case '*':
    case '0':
    case '1':
    case 'l':
    case 'd':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      /* TODO: handle 'l' and 'd' differently (longname and desktop name) */
      if (obj->uri)
        result = g_strdup (obj->uri);
      else if (obj->file)
        result = g_strdup (obj->file);
      break;
    case 'u':
    case 'U':
      if (obj->uri)
        result = g_shell_quote (obj->uri);
      break;
    case 'f':
    case 'F':
      if (obj->file)
        result = g_shell_quote (obj->file);
      break;
    }

  return result;
}

static gboolean
expand_macro (char               macro,
              GString           *exec,
              GWin32AppInfo     *info,
              GList            **stat_obj_list,
              GList            **obj_list)
{
  GList *objs = *obj_list;
  char *expanded;
  gboolean result = FALSE;

  g_return_val_if_fail (exec != NULL, FALSE);

/*
Legend: (from http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101%28v=vs.85%29.aspx)
%* - replace with all parameters
%~ - replace with all parameters starting with and following the second parameter
%0 or %1 the first file parameter. For example "C:\\Users\\Eric\\Destop\\New Text Document.txt". Generally this should be in quotes and the applications command line parsing should accept quotes to disambiguate files with spaces in the name and different command line parameters (this is a security best practice and I believe mentioned in MSDN).
%<n> (where N is 2 - 9), replace with the nth parameter
%s - show command
%h - hotkey value
%i - IDList stored in a shared memory handle is passed here.
%l - long file name form of the first parameter. Note win32 applications will be passed the long file name, win16 applications get the short file name. Specifying %L is preferred as it avoids the need to probe for the application type.
%d - desktop absolute parsing name of the first parameter (for items that don't have file system paths)
%v - for verbs that are none implies all, if there is no parameter passed this is the working directory
%w - the working directory
*/

  switch (macro)
    {
    case '*':
    case '~':
      if (*stat_obj_list)
        {
          gint i;
          GList *o;

          for (o = *stat_obj_list, i = 0;
               macro == '~' && o && i < 2;
               o = o->next, i++);

          for (; o; o = o->next)
            {
              expanded = expand_macro_single (macro, o->data);

              if (expanded)
                {
                  if (o != *stat_obj_list)
                    g_string_append (exec, " ");

                  g_string_append (exec, expanded);
                  g_free (expanded);
                }
            }

          objs = NULL;
          result = TRUE;
        }
      break;
    case '0':
    case '1':
    case 'l':
    case 'd':
      if (*stat_obj_list)
        {
          GList *o;

          o = *stat_obj_list;

          if (o)
            {
              expanded = expand_macro_single (macro, o->data);

              if (expanded)
                {
                  if (o != *stat_obj_list)
                    g_string_append (exec, " ");

                  g_string_append (exec, expanded);
                  g_free (expanded);
                }
            }

          if (objs)
            objs = objs->next;

          result = TRUE;
        }
      break;
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      if (*stat_obj_list)
        {
          gint i;
          GList *o;
          gint n;

          switch (macro)
            {
            case '2':
              n = 2;
              break;
            case '3':
              n = 3;
              break;
            case '4':
              n = 4;
              break;
            case '5':
              n = 5;
              break;
            case '6':
              n = 6;
              break;
            case '7':
              n = 7;
              break;
            case '8':
              n = 8;
              break;
            case '9':
              n = 9;
              break;
            }

          for (o = *stat_obj_list, i = 0; o && i < n; o = o->next, i++);

          if (o)
            {
              expanded = expand_macro_single (macro, o->data);

              if (expanded)
                {
                  if (o != *stat_obj_list)
                    g_string_append (exec, " ");

                  g_string_append (exec, expanded);
                  g_free (expanded);
                }
            }
          result = TRUE;

          if (objs)
            objs = NULL;
        }
      break;
    case 's':
      break;
    case 'h':
      break;
    case 'i':
      break;
    case 'v':
      break;
    case 'w':
      expanded = g_get_current_dir ();
      g_string_append (exec, expanded);
      g_free (expanded);
      break;
    case 'u':
    case 'f':
      if (objs)
        {
          expanded = expand_macro_single (macro, objs->data);

          if (expanded)
            {
              g_string_append (exec, expanded);
              g_free (expanded);
            }
          objs = objs->next;
          result = TRUE;
        }

      break;

    case 'U':
    case 'F':
      while (objs)
        {
          expanded = expand_macro_single (macro, objs->data);

          if (expanded)
            {
              g_string_append (exec, expanded);
              g_free (expanded);
            }

          objs = objs->next;
          result = TRUE;

          if (objs != NULL && expanded)
            g_string_append_c (exec, ' ');
        }

      break;

    case 'c':
      if (info->app && info->app->localized_pretty_name_u8)
        {
          expanded = g_shell_quote (info->app->localized_pretty_name_u8);
          g_string_append (exec, expanded);
          g_free (expanded);
        }
      break;

    case 'm': /* deprecated */
    case 'n': /* deprecated */
    case 'N': /* deprecated */
    /*case 'd': *//* deprecated */
    case 'D': /* deprecated */
      break;

    case '%':
      g_string_append_c (exec, '%');
      break;
    }

  *obj_list = objs;

  return result;
}

static gboolean
expand_application_parameters (GWin32AppInfo   *info,
                               const gchar     *exec_line,
                               GList          **objs,
                               int             *argc,
                               char          ***argv,
                               GError         **error)
{
  GList *obj_list = *objs;
  GList **stat_obj_list = objs;
  const char *p = exec_line;
  GString *expanded_exec;
  gboolean res;
  gchar *a_char;

  if (exec_line == NULL)
    {
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                           P_("Application registry did not specify"
                              " a shell\\open\\command"));
      return FALSE;
    }

  expanded_exec = g_string_new (NULL);
  res = FALSE;

  while (*p)
    {
      if (p[0] == '%' && p[1] != '\0')
        {
          if (expand_macro (p[1],
                            expanded_exec,
                            info, stat_obj_list,
                            objs))
            res = TRUE;

          p++;
        }
      else
        g_string_append_c (expanded_exec, *p);

      p++;
    }

  /* No file substitutions */
  if (obj_list == *objs && obj_list != NULL && !res)
    {
      /* If there is no macro default to %f. This is also what KDE does */
      g_string_append_c (expanded_exec, ' ');
      expand_macro ('f', expanded_exec, info, stat_obj_list, objs);
    }

  /* Replace '\\' with '/', because g_shell_parse_argv considers them
   * to be escape sequences.
   */
  for (a_char = expanded_exec->str;
       a_char <= &expanded_exec->str[expanded_exec->len];
       a_char++)
    {
      if (*a_char == '\\')
        *a_char = '/';
    }

  res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
  g_string_free (expanded_exec, TRUE);
  return res;
}


static gchar *
get_appath_for_exe (gunichar2 *exe_basename)
{
  GWin32RegistryKey *apppath_key = NULL;
  GWin32RegistryValueType val_type;
  gunichar2 *appath = NULL;
  gboolean got_value;
  gchar *result = NULL;

  apppath_key = _g_win32_registry_key_build_and_new_w (NULL, L"HKEY_LOCAL_MACHINE\\"
                                                       L"\\SOFTWARE"
                                                       L"\\Microsoft"
                                                       L"\\Windows"
                                                       L"\\CurrentVersion"
                                                       L"\\App Paths\\",
                                                       exe_basename, NULL);

  if (apppath_key == NULL)
    return NULL;

  got_value = g_win32_registry_key_get_value_w (apppath_key,
                                                TRUE,
                                                L"Path",
                                                &val_type,
                                                (void **) &appath,
                                                NULL,
                                                NULL);

  g_object_unref (apppath_key);

  if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR)
    result = g_utf16_to_utf8 (appath, -1, NULL,NULL, NULL);

  g_clear_pointer (&appath, g_free);

  return result;
}


static gboolean
g_win32_app_info_launch_internal (GWin32AppInfo      *info,
                                  GList              *objs,
                                  GAppLaunchContext  *launch_context,
                                  GSpawnFlags         spawn_flags,
                                  GError            **error)
{
  gboolean completed = FALSE;
  char **argv, **envp;
  int argc;
  gchar *command;
  gchar *apppath;
  gunichar2 *exe_basename;

  g_return_val_if_fail (info != NULL, FALSE);
  g_return_val_if_fail (info->app != NULL, FALSE);

  argv = NULL;

  if (launch_context)
    envp = g_app_launch_context_get_environment (launch_context);
  else
    envp = g_get_environ ();

  command = NULL;
  exe_basename = NULL;

  if (info->handler)
    {
      if (info->handler->handler_command)
        {
          command = g_utf16_to_utf8 (info->handler->handler_command,
                                     -1,
                                     NULL,
                                     NULL,
                                     NULL);
          exe_basename = g_utf8_to_utf16 (info->handler->executable_basename,
                                          -1,
                                          NULL,
                                          NULL,
                                          NULL);
        }
      else if (info->handler->proxy_command)
        {
          command = g_utf16_to_utf8 (info->handler->proxy_command,
                                     -1,
                                     NULL,
                                     NULL,
                                     NULL);
          exe_basename = g_utf8_to_utf16 (info->handler->executable_basename,
                                          -1,
                                          NULL,
                                          NULL,
                                          NULL);
        }
    }

  if (command == NULL)
    {
      command = g_strdup (info->app->command_u8);
      exe_basename = g_utf8_to_utf16 (info->app->executable_basename,
                                      -1,
                                      NULL,
                                      NULL,
                                      NULL);
    }

  apppath = get_appath_for_exe (exe_basename);

  g_free (exe_basename);

  if (apppath)
    {
      gchar **p;
      gint p_index;

      for (p = envp, p_index = 0; p[0]; p++, p_index++)
        if ((p[0][0] == 'p' || p[0][0] == 'P') &&
            (p[0][1] == 'a' || p[0][1] == 'A') &&
            (p[0][2] == 't' || p[0][2] == 'T') &&
            (p[0][3] == 'h' || p[0][3] == 'H') &&
            (p[0][4] == '='))
          break;

      if (p[0] == NULL)
        {
          gchar **new_envp;
          new_envp = g_new (char *, g_strv_length (envp) + 2);
          new_envp[0] = g_strdup_printf ("PATH=%s", apppath);

          for (p_index = 0; p_index <= g_strv_length (envp); p_index++)
            new_envp[1 + p_index] = envp[p_index];

          g_free (envp);
          envp = new_envp;
        }
      else
        {
          gchar *p_path;

          p_path = &p[0][5];

          if (p_path[0] != '\0')
            envp[p_index] = g_strdup_printf ("PATH=%s%c%s",
                                             apppath,
                                             G_SEARCHPATH_SEPARATOR,
                                             p_path);
          else
            envp[p_index] = g_strdup_printf ("PATH=%s", apppath);

          g_free (&p_path[-5]);
        }
    }

  do
    {
      GPid pid;

      if (!expand_application_parameters (info,
                                          command,
                                          &objs,
                                          &argc,
                                          &argv,
                                          error))
        goto out;

      if (!g_spawn_async (NULL,
                          argv,
                          envp,
                          spawn_flags,
                          NULL,
                          NULL,
                          &pid,
                          error))
          goto out;

      if (launch_context != NULL)
        {
          GVariantBuilder builder;
          GVariant *platform_data;

          g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
          g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gint32) pid));

          platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
          g_signal_emit_by_name (launch_context, "launched", info, platform_data);
          g_variant_unref (platform_data);
        }

      g_strfreev (argv);
      argv = NULL;
    }
  while (objs != NULL);

  completed = TRUE;

 out:
  g_strfreev (argv);
  g_strfreev (envp);
  g_free (command);

  return completed;
}

static void
free_file_or_uri (gpointer ptr)
{
  file_or_uri *obj = ptr;
  g_free (obj->file);
  g_free (obj->uri);
  g_free (obj);
}


static gboolean
g_win32_app_supports_uris (GWin32AppInfoApplication *app)
{
  gssize num_of_uris_supported;

  if (app == NULL)
    return FALSE;

  num_of_uris_supported = (gssize) g_hash_table_size (app->supported_urls);

  if (g_hash_table_lookup (app->supported_urls, "file"))
    num_of_uris_supported -= 1;

  return num_of_uris_supported > 0;
}


static gboolean
g_win32_app_info_supports_uris (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return FALSE;

  return g_win32_app_supports_uris (info->app);
}


static gboolean
g_win32_app_info_supports_files (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  if (info->app == NULL)
    return FALSE;

  return g_hash_table_size (info->app->supported_exts) > 0;
}


static gboolean
g_win32_app_info_launch_uris (GAppInfo           *appinfo,
                              GList              *uris,
                              GAppLaunchContext  *launch_context,
                              GError            **error)
{
  gboolean res = FALSE;
  gboolean do_files;
  GList *objs;

  do_files = g_win32_app_info_supports_files (appinfo);

  objs = NULL;
  while (uris)
    {
      file_or_uri *obj;
      obj = g_new0 (file_or_uri, 1);

      if (do_files)
        {
          GFile *file;
          gchar *path;

          file = g_file_new_for_uri (uris->data);
          path = g_file_get_path (file);
          obj->file = path;
          g_object_unref (file);
        }

      obj->uri = g_strdup (uris->data);

      objs = g_list_prepend (objs, obj);
      uris = uris->next;
    }

  objs = g_list_reverse (objs);

  res = g_win32_app_info_launch_internal (G_WIN32_APP_INFO (appinfo),
                                          objs,
                                          launch_context,
                                          G_SPAWN_SEARCH_PATH,
                                          error);

  g_list_free_full (objs, free_file_or_uri);

  return res;
}

static gboolean
g_win32_app_info_launch (GAppInfo           *appinfo,
                         GList              *files,
                         GAppLaunchContext  *launch_context,
                         GError            **error)
{
  gboolean res = FALSE;
  gboolean do_uris;
  GList *objs;

  do_uris = g_win32_app_info_supports_uris (appinfo);

  objs = NULL;
  while (files)
    {
      file_or_uri *obj;
      obj = g_new0 (file_or_uri, 1);
      obj->file = g_file_get_path (G_FILE (files->data));

      if (do_uris)
        obj->uri = g_file_get_uri (G_FILE (files->data));

      objs = g_list_prepend (objs, obj);
      files = files->next;
    }

  objs = g_list_reverse (objs);

  res = g_win32_app_info_launch_internal (G_WIN32_APP_INFO (appinfo),
                                          objs,
                                          launch_context,
                                          G_SPAWN_SEARCH_PATH,
                                          error);

  g_list_free_full (objs, free_file_or_uri);

  return res;
}

static const char **
g_win32_app_info_get_supported_types (GAppInfo *appinfo)
{
  GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);

  return (const char**) info->supported_types;
}

GAppInfo *
g_app_info_create_from_commandline (const char           *commandline,
                                    const char           *application_name,
                                    GAppInfoCreateFlags   flags,
                                    GError              **error)
{
  GWin32AppInfo *info;
  GWin32AppInfoApplication *app;
  gunichar2 *app_command;

  g_return_val_if_fail (commandline, NULL);

  app_command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL);

  if (app_command == NULL)
    return NULL;

  info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
  app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);

  app->command = g_steal_pointer (&app_command);

  if (application_name)
    {
      app->canonical_name = g_utf8_to_utf16 (application_name,
                                             -1,
                                             NULL,
                                             NULL,
                                             NULL);
      app->canonical_name_u8 = g_strdup (application_name);
      app->canonical_name_folded = g_utf8_casefold (application_name, -1);
    }

  _g_win32_extract_executable (app->command,
                               &app->executable,
                               &app->executable_basename,
                               &app->executable_folded,
                               NULL,
                               &app->dll_function);
  if (app->dll_function != NULL)
    _g_win32_fixup_broken_microsoft_rundll_commandline (app->command);

  app->command_u8 = g_utf16_to_utf8 (app->command, -1, NULL, NULL, NULL);

  app->no_open_with = FALSE;
  app->user_specific = FALSE;
  app->default_app = FALSE;

  info->app = app;
  info->handler = NULL;

  return G_APP_INFO (info);
}

/* GAppInfo interface init */

static void
g_win32_app_info_iface_init (GAppInfoIface *iface)
{
  iface->dup = g_win32_app_info_dup;
  iface->equal = g_win32_app_info_equal;
  iface->get_id = g_win32_app_info_get_id;
  iface->get_name = g_win32_app_info_get_name;
  iface->get_description = g_win32_app_info_get_description;
  iface->get_executable = g_win32_app_info_get_executable;
  iface->get_icon = g_win32_app_info_get_icon;
  iface->launch = g_win32_app_info_launch;
  iface->supports_uris = g_win32_app_info_supports_uris;
  iface->supports_files = g_win32_app_info_supports_files;
  iface->launch_uris = g_win32_app_info_launch_uris;
/*  iface->should_show = g_win32_app_info_should_show;*/
/*  iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;*/
/*  iface->set_as_default_for_extension = g_win32_app_info_set_as_default_for_extension;*/
/*  iface->add_supports_type = g_win32_app_info_add_supports_type;*/
/*  iface->can_remove_supports_type = g_win32_app_info_can_remove_supports_type;*/
/*  iface->remove_supports_type = g_win32_app_info_remove_supports_type;*/
/*  iface->can_delete = g_win32_app_info_can_delete;*/
/*  iface->do_delete = g_win32_app_info_delete;*/
  iface->get_commandline = g_win32_app_info_get_commandline;
  iface->get_display_name = g_win32_app_info_get_display_name;
/*  iface->set_as_last_used_for_type = g_win32_app_info_set_as_last_used_for_type;*/
  iface->get_supported_types = g_win32_app_info_get_supported_types;
}

GAppInfo *
g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
{
  GWin32AppInfoURLSchema *scheme;
  char *scheme_down;
  GAppInfo *result;

  scheme_down = g_utf8_casefold (uri_scheme, -1);

  if (!scheme_down)
    return NULL;

  if (strcmp (scheme_down, "file") == 0)
    {
      g_free (scheme_down);
      return NULL;
    }

  g_win32_appinfo_init ();
  G_LOCK (gio_win32_appinfo);

  scheme = g_hash_table_lookup (urls, scheme_down);
  g_free (scheme_down);

  if (scheme)
    g_object_ref (scheme);

  G_UNLOCK (gio_win32_appinfo);

  result = NULL;

  if (scheme != NULL &&
      scheme->chosen_handler != NULL &&
      scheme->chosen_handler->app != NULL)
    result = g_win32_app_info_new_from_app (scheme->chosen_handler->app,
                                            scheme->chosen_handler);

  if (scheme)
    g_object_unref (scheme);

  return result;
}

GAppInfo *
g_app_info_get_default_for_type (const char *content_type,
                                 gboolean    must_support_uris)
{
  GWin32AppInfoFileExtension *ext;
  char *ext_down;
  GWin32AppInfoHandler *handler;
  GAppInfo *result;
  GWin32AppInfoApplication *app;
  GHashTableIter iter;

  ext_down = g_utf8_casefold (content_type, -1);

  if (!ext_down)
    return NULL;

  g_win32_appinfo_init ();
  G_LOCK (gio_win32_appinfo);

  /* Assuming that "content_type" is a file extension, not a MIME type */
  ext = g_hash_table_lookup (extensions, ext_down);
  g_free (ext_down);

  result = NULL;

  if (ext != NULL)
    g_object_ref (ext);

  G_UNLOCK (gio_win32_appinfo);

  if (ext != NULL)
    {
      if (ext->chosen_handler != NULL &&
          ext->chosen_handler->app != NULL &&
          (!must_support_uris ||
           g_win32_app_supports_uris (ext->chosen_handler->app)))
        result = g_win32_app_info_new_from_app (ext->chosen_handler->app,
                                                ext->chosen_handler);
      else
        {
          g_hash_table_iter_init (&iter, ext->handlers);

          while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
            {
              if (handler->app &&
                  (!must_support_uris ||
                   g_win32_app_supports_uris (ext->chosen_handler->app)))
                {
                  result = g_win32_app_info_new_from_app (handler->app, handler);
                  break;
                }
            }

          if (result == NULL)
            {
              g_hash_table_iter_init (&iter, ext->other_apps);
              while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app))
                {
                  if (!must_support_uris ||
                       g_win32_app_supports_uris (ext->chosen_handler->app))
                    {
                      result = g_win32_app_info_new_from_app (app, NULL);
                      break;
                    }
                }
            }
        }
      g_object_unref (ext);
    }

  return result;
}

GList *
g_app_info_get_all (void)
{
  GHashTableIter iter;
  gpointer value;
  GList *infos;
  GList *apps;
  GList *apps_i;

  g_win32_appinfo_init ();
  G_LOCK (gio_win32_appinfo);

  apps = NULL;
  g_hash_table_iter_init (&iter, apps_by_id);
  while (g_hash_table_iter_next (&iter, NULL, &value))
    apps = g_list_prepend (apps, g_object_ref (G_OBJECT (value)));

  G_UNLOCK (gio_win32_appinfo);

  infos = NULL;
  for (apps_i = apps; apps_i; apps_i = apps_i->next)
    infos = g_list_prepend (infos,
                            g_win32_app_info_new_from_app (apps_i->data, NULL));

  g_list_free_full (apps, g_object_unref);

  return infos;
}

GList *
g_app_info_get_all_for_type (const char *content_type)
{
  GWin32AppInfoFileExtension *ext;
  char *ext_down;
  GWin32AppInfoHandler *handler;
  GWin32AppInfoApplication *app;
  GHashTableIter iter;
  GList *result;

  ext_down = g_utf8_casefold (content_type, -1);

  if (!ext_down)
    return NULL;

  g_win32_appinfo_init ();
  G_LOCK (gio_win32_appinfo);

  /* Assuming that "content_type" is a file extension, not a MIME type */
  ext = g_hash_table_lookup (extensions, ext_down);
  g_free (ext_down);

  result = NULL;

  if (ext != NULL)
    g_object_ref (ext);

  G_UNLOCK (gio_win32_appinfo);

  if (ext == NULL)
    return NULL;

  if (ext->chosen_handler != NULL &&
      ext->chosen_handler->app != NULL)
    result = g_list_prepend (result,
                             g_win32_app_info_new_from_app (ext->chosen_handler->app,
                                                            ext->chosen_handler));

  g_hash_table_iter_init (&iter, ext->handlers);

  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
    {
      if (handler->app &&
          (ext->chosen_handler == NULL || ext->chosen_handler->app != handler->app))
          result = g_list_prepend (result,
                                   g_win32_app_info_new_from_app (handler->app,
                                                                  handler));
    }

  g_hash_table_iter_init (&iter, ext->other_apps);

  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app))
    {
      result = g_list_prepend (result, g_win32_app_info_new_from_app (app, NULL));
    }

  g_object_unref (ext);

  result = g_list_reverse (result);

  return result;
}

GList *
g_app_info_get_fallback_for_type (const gchar *content_type)
{
  /* TODO: fix this once gcontenttype support is improved */
  return g_app_info_get_all_for_type (content_type);
}

GList *
g_app_info_get_recommended_for_type (const gchar *content_type)
{
  /* TODO: fix this once gcontenttype support is improved */
  return g_app_info_get_all_for_type (content_type);
}

void
g_app_info_reset_type_associations (const char *content_type)
{
  /* nothing to do */
}
