/* 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 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;

  /* 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;

  /* 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_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->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;

static gunichar2 *
g_wcsdup (const gunichar2 *str, gssize str_size)
{
  if (str_size == -1)
    {
      str_size = wcslen (str) + 1;
      str_size *= sizeof (gunichar2);
    }
  return g_memdup (str, str_size);
}

#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))

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;
}


static gboolean
utf8_and_fold (const gunichar2  *str,
               gchar           **str_u8,
               gchar           **str_u8_folded)
{
  gchar *u8;
  gchar *folded;
  u8 = g_utf16_to_utf8 (str, -1, NULL, NULL, NULL);

  if (u8 == NULL)
    return FALSE;

  folded = g_utf8_casefold (u8, -1);

  if (folded == NULL)
    {
      g_free (u8);
      return FALSE;
    }

  if (str_u8)
    *str_u8 = u8;
  else
    g_free (u8);

  if (str_u8_folded)
    *str_u8_folded = folded;
  else
    g_free (folded);

  return TRUE;
}


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) &&
              !utf8_and_fold (program_id, program_id_u8, program_id_folded))
            {
              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);
  if (!got_value ||
      (val_type != G_WIN32_REGISTRY_VALUE_STR))
    {
      g_object_unref (key);
      g_clear_pointer (proxy_id, g_free);
      return FALSE;
    }

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

  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);
      if (proxy_key)
        g_clear_object (proxy_key);
      return FALSE;
    }

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

  if (!got_value ||
      val_type != G_WIN32_REGISTRY_VALUE_STR ||
      ((program_id_u8 != NULL || program_id_folded != NULL) &&
       !utf8_and_fold (program_id, program_id_u8, program_id_folded)))
    {
      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
extract_executable (gunichar2  *commandline,
                    gchar     **ex_out,
                    gchar     **ex_basename_out,
                    gchar     **ex_folded_out,
                    gchar     **ex_folded_basename_out)
{
  gchar *ex;
  gchar *ex_folded;
  gunichar2 *p;
  gboolean quoted;
  size_t len;
  size_t execlen;
  gunichar2 *exepart;
  gboolean found;

  quoted = FALSE;
  execlen = 0;
  found = FALSE;
  len = wcslen (commandline);
  p = commandline;

  while (p < &commandline[len])
    {
      switch (p[0])
        {
        case L'"':
          quoted = !quoted;
          break;
        case L' ':
          if (!quoted)
            {
              execlen = p - commandline;
              p = &commandline[len];
              found = TRUE;
            }
          break;
        default:
          break;
        }
      p += 1;
    }

  if (!found)
    execlen = len;

  exepart = g_wcsdup (commandline, (execlen + 1) * sizeof (gunichar2));
  exepart[execlen] = L'\0';

  p = &exepart[0];

  while (execlen > 0 && exepart[0] == L'"' && exepart[execlen - 1] == L'"')
    {
      p = &exepart[1];
      exepart[execlen - 1] = L'\0';
      execlen -= 2;
    }

  if (!utf8_and_fold (p, &ex, &ex_folded))
    /* Currently no code to handle this case. It shouldn't happen though... */
    g_assert_not_reached ();

  g_free (exepart);

  if (ex_out)
    {
      *ex_out = ex;

      if (ex_basename_out)
        {
          *ex_basename_out = &ex[strlen (ex) - 1];

          while (*ex_basename_out > ex)
            {
              if ((*ex_basename_out)[0] == '/' ||
                  (*ex_basename_out)[0] == '\\')
                {
                  *ex_basename_out += 1;
                  break;
                }

              *ex_basename_out -= 1;
            }
        }
    }
  else
    {
      g_free (ex);
    }

  if (ex_folded_out)
    {
      *ex_folded_out = ex_folded;

      if (ex_folded_basename_out)
        {
          *ex_folded_basename_out = &ex_folded[strlen (ex_folded) - 1];

          while (*ex_folded_basename_out > ex_folded)
            {
              if ((*ex_folded_basename_out)[0] == '/' ||
                  (*ex_folded_basename_out)[0] == '\\')
                {
                  *ex_folded_basename_out += 1;
                  break;
                }

              *ex_folded_basename_out -= 1;
            }
        }
    }
  else
    {
      g_free (ex_folded);
    }
}

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 (!utf8_and_fold (schema, &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;
      extract_executable (proxy_command ? proxy_command : program_command,
                          &handler_rec->executable,
                          &handler_rec->executable_basename,
                          &handler_rec->executable_folded,
                          NULL);
      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 (!utf8_and_fold (ext, &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;
                  extract_executable (proxy_command ? proxy_command : program_command,
                                      &handler_rec->executable,
                                      &handler_rec->executable_basename,
                                      &handler_rec->executable_folded,
                                      NULL);
                  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;
                  extract_executable (proxy_command ? proxy_command : program_command,
                                      &handler_rec->executable,
                                      &handler_rec->executable_basename,
                                      &handler_rec->executable_folded,
                                      NULL);
                  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;
      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;
  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 (!utf8_and_fold (canonical_name, &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)
    {
      /* 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;
    }

  extract_executable (shell_open_command,
                      &app_executable,
                      &app_executable_basename,
                      &app_executable_folded,
                      &app_executable_folded_basename);

  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;

      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;
                  extract_executable (proxy_command ? proxy_command : program_command,
                                      &handler_rec->executable,
                                      &handler_rec->executable_basename,
                                      &handler_rec->executable_folded,
                                      NULL);
                  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 (utf8_and_fold (file_extension,
                                   &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;
                  extract_executable (proxy_command ? proxy_command : program_command,
                                      &handler_rec->executable,
                                      &handler_rec->executable_basename,
                                      &handler_rec->executable_folded,
                                      NULL);
                  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 (utf8_and_fold (url_schema,
                                   &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 (&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;
  gunichar2 *app_exe_basename;
  gsize app_exe_basename_len;

  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))
    {
      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))
        continue;

      incapable_app =
          g_win32_registry_key_get_child_w (applications_key,
                                            app_exe_basename,
                                            NULL);

      if (incapable_app == NULL)
        continue;

      extract_executable (app_exe_basename,
                          &appexe,
                          &appexe_basename,
                          &appexe_folded,
                          &appexe_folded_basename);

      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_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);

          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'.') ||
                      (!utf8_and_fold (ext_name,
                                       &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;
      extract_executable (proxy_command ? proxy_command : program_command,
                          &handler_rec->executable,
                          &handler_rec->executable_basename,
                          &handler_rec->executable_folded,
                          NULL);
      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;
      extract_executable (proxy_command ? proxy_command : program_command,
                          &handler_rec->executable,
                          &handler_rec->executable_basename,
                          &handler_rec->executable_folded,
                          NULL);
      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 &&
              utf8_and_fold (handler->proxy_id,
                             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 &&
              utf8_and_fold (handler->proxy_id,
                             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;

      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_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;

      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;

  g_return_val_if_fail (commandline, NULL);

  info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);

  app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);

  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);
    }

  app->command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL);
  app->command_u8 = g_strdup (commandline);

  extract_executable (app->command,
                      &app->executable,
                      &app->executable_basename,
                      &app->executable_folded,
                      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 */
}
