[glib: 2/3] GWin32AppInfo: Support verbs other than "open"




commit 106e78af97ef368d29d8037a6b8c10dcce64cbd5
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Sun Dec 29 11:35:49 2019 +0000

    GWin32AppInfo: Support verbs other than "open"
    
    This combines a massive code re-folding with functionlity expansion
    that allows us to track multiple verbs per handler or per application.
    
    Also fixes a few issues and removes a function that made no sense.

 gio/gwin32appinfo.c     | 3883 +++++++++++++++++++++++------------------------
 gio/gwin32registrykey.c |    4 +-
 gio/gwin32registrykey.h |    4 +-
 3 files changed, 1876 insertions(+), 2015 deletions(-)
---
diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c
index ec3e2b70c..612373a6a 100644
--- a/gio/gwin32appinfo.c
+++ b/gio/gwin32appinfo.c
@@ -35,6 +35,9 @@
 
 #include <windows.h>
 
+#include <glib/gstdioprivate.h>
+#include "glib-private.h"
+
 /* We need to watch 8 places:
  * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations
  *    (anything below that key)
@@ -55,15 +58,73 @@
  * 7) HKEY_CLASSES_ROOT (only its subkeys)
  *    On change: re-enumerate subkeys, try to filter out wrong names.
  *
+ *
+ * About verbs. A registry key (the name of that key is known as ProgID)
+ * can contain a "shell" subkey, which can then contain a number of verb
+ * subkeys (the most common being the "open" verb), and each of these
+ * contains a "command" subkey, which has a default string value that
+ * is the command to be run.
+ * Most ProgIDs are in HKEY_CLASSES_ROOT, but some are nested deeper in
+ * the registry (such as HKEY_CURRENT_USER\\Software\\<softwarename>).
+ *
+ * Verb selection works like this (according to https://docs.microsoft.com/en-us/windows/win32/shell/context 
):
+ * 1) If "open" verb is available, that verb is used.
+ * 2) If the Shell subkey has a default string value, and if a verb subkey
+ *    with that name exists, that verb is used.
+ * 3) The first subkey found in the list of verb subkeys is used.
+ * 4) The "openwith" verb is used
+ *
+ * Testing suggests that Windows never reaches the point 4 in any realistic
+ * circumstances. If a "command" subkey is missing for a verb, or if it has
+ * an empty string as its default value, the app launch fails
+ * (the "openwith" verb is not used, even if it's present).
+ * If the command is present, but is not valid (runs nonexisting executable,
+ * for example), then other verbs are not checked.
+ * It seems that when the documentation said "openwith verb", it meant
+ * that Windows invokes the default "Open with..." dialog (it does not
+ * look at the "openwith" verb subkey, even if it's there).
+ * If a verb subkey that is supposed to be used is present, but it lacks
+ * a command subkey, an error message is shown and nothing else happens.
  */
 
+#define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index))
+
+#define _lookup_by_verb(array, verb, dst, itemtype) do { \
+  gsize _index; \
+  itemtype *_v; \
+  for (_index = 0; array && _index < array->len; _index++) \
+    { \
+      _v = (itemtype *) g_ptr_array_index (array, _index); \
+      if (_wcsicmp (_v->verb_name, (verb)) == 0) \
+        { \
+          *(dst) = _v; \
+          break; \
+        } \
+    } \
+  if (array == NULL || _index >= array->len) \
+    *(dst) = NULL; \
+} while (0)
+
+#define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb)
+
+/* Because with subcommands a verb would have
+ * a name like "foo\\bar", but the key its command
+ * should be looked for is "shell\\foo\\shell\\bar\\command"
+ */
+typedef struct _reg_verb {
+  gunichar2 *name;
+  gunichar2 *shellpath;
+} reg_verb;
+
 typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema;
 typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension;
+typedef struct _GWin32AppInfoShellVerb GWin32AppInfoShellVerb;
 typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler;
 typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication;
 
 typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass;
 typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass;
+typedef struct _GWin32AppInfoShellVerbClass GWin32AppInfoShellVerbClass;
 typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass;
 typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass;
 
@@ -87,6 +148,11 @@ struct _GWin32AppInfoApplicationClass
   GObjectClass parent_class;
 };
 
+struct _GWin32AppInfoShellVerbClass
+{
+  GObjectClass parent_class;
+};
+
 struct _GWin32AppInfoURLSchema {
   GObject parent_instance;
 
@@ -97,45 +163,57 @@ struct _GWin32AppInfoURLSchema {
   gchar *schema_u8;
 
   /* url schema (stuff before ':'), in UTF-8, folded */
-  gchar *schema_folded;
+  gchar *schema_u8_folded;
 
-  /* Handler currently selected for this schema */
+  /* Handler currently selected for this schema. Can be NULL. */
   GWin32AppInfoHandler *chosen_handler;
 
-  /* Maps folded handler IDs -> to other handlers for this schema */
+  /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema.
+   * Includes the chosen handler, if any.
+   */
   GHashTable *handlers;
 };
 
 struct _GWin32AppInfoHandler {
   GObject parent_instance;
 
-  /* Class name in HKCR */
+  /* Usually a class name in HKCR */
   gunichar2 *handler_id;
 
-  /* Handler registry key (HKCR\\handler_id). Can be used to watch this handler. */
+  /* Registry object obtained by opening @handler_id. Can be used to watch this handler. */
   GWin32RegistryKey *key;
 
-  /* Class name in HKCR, UTF-8, folded */
+  /* @handler_id, in UTF-8, folded */
   gchar *handler_id_folded;
 
-  /* shell/open/command default value for the class named by class_id */
-  gunichar2 *handler_command;
+  /* Icon of the application for this handler */
+  GIcon *icon;
+
+  /* Verbs that this handler supports */
+  GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
+};
 
-  /* If handler_id class has no command, it might point to another class */
-  gunichar2 *proxy_id;
+struct _GWin32AppInfoShellVerb {
+  GObject parent_instance;
 
-  /* Proxy registry key (HKCR\\proxy_id). Can be used to watch handler's proxy. */
-  GWin32RegistryKey *proxy_key;
+  /* The verb that is used to invoke this handler. */
+  gunichar2 *verb_name;
 
-  /* shell/open/command default value for the class named by proxy_id */
-  gunichar2 *proxy_command;
+  /* User-friendly (localized) verb name. */
+  gchar *verb_displayname;
 
-  /* Executable of the program (for matching, in folded form; UTF-8) */
-  gchar *executable_folded;
+  /* shell/verb/command */
+  gunichar2 *command;
+
+  /* Same as @command, but in UTF-8 */
+  gchar *command_utf8;
 
   /* Executable of the program (UTF-8) */
   gchar *executable;
 
+  /* Executable of the program (for matching, in folded form; UTF-8) */
+  gchar *executable_folded;
+
   /* Pointer to a location within @executable */
   gchar *executable_basename;
 
@@ -146,10 +224,7 @@ struct _GWin32AppInfoHandler {
    */
   gchar *dll_function;
 
-  /* Icon of the application for this handler */
-  GIcon *icon;
-
-  /* The application that is linked to this handler. */
+  /* The application that is linked to this verb. */
   GWin32AppInfoApplication *app;
 };
 
@@ -162,28 +237,34 @@ struct _GWin32AppInfoFileExtension {
   /* File extension (with leading '.'), in UTF-8 */
   gchar *extension_u8;
 
-  /* handler currently selected for this extension */
+  /* handler currently selected for this extension. Can be NULL. */
   GWin32AppInfoHandler *chosen_handler;
 
-  /* Maps folded handler IDs -> to other handlers for this extension */
+  /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension.
+   * Includes the chosen handler, if any.
+   */
   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. */
+  /* Canonical name (used for key names).
+   * For applications tracked by id this is the root registry
+   * key path for the application.
+   * For applications tracked by executable name this is the
+   * basename of the executable.
+   * For fake applications this is the full filename of the
+   * executable (as far as it can be inferred from a command line,
+   * meaning that it can also be a basename, if that's
+   * all that a commandline happen to give us).
+   */
   gunichar2 *canonical_name;
 
-  /* Canonical name (used for key names), in UTF-8. Can be NULL. */
+  /* @canonical_name, in UTF-8 */
   gchar *canonical_name_u8;
 
-  /* Canonical name (used for key names), in UTF-8, folded. Can be NULL. */
+  /* @canonical_name, in UTF-8, folded */
   gchar *canonical_name_folded;
 
   /* Human-readable name in English. Can be NULL */
@@ -204,37 +285,17 @@ struct _GWin32AppInfoApplication {
   /* Description, could be in user's language, UTF-8. Can be NULL */
   gchar *description_u8;
 
-  /* shell/open/command for the application. Can be NULL (see executable). */
-  gunichar2 *command;
-
-  /* shell/open/command for the application. Can be NULL (see executable). */
-  gchar *command_u8;
-
-  /* Executable of the program (for matching, in folded form;
-   * UTF-8). Never NULL. */
-  gchar *executable_folded;
-
-  /* Executable of the program (UTF-8). Never NULL. */
-  gchar *executable;
-
-  /* Pointer to a location within @executable */
-  gchar *executable_basename;
-
-  /* If not NULL, then @executable and its derived fields contain the name
-   * of a DLL file (without the name of the function that rundll32.exe should
-   * invoke), and this field contains the name of the function to be invoked.
-   * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'.
-   */
-  gchar *dll_function;
+  /* Verbs that this application supports */
+  GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
 
   /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema,
-   * UTF-8, folded) -> a GWin32AppInfoHandler
+   * UTF-8, folded) -> to 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, UTF-8, folded) -> to a GWin32AppInfoHandler
    * Extension can be used as a key in the extensions hashmap.
    */
   GHashTable *supported_exts;
@@ -272,13 +333,18 @@ struct _GWin32AppInfoApplication {
 #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))
 
+#define G_TYPE_WIN32_APPINFO_SHELL_VERB           (g_win32_appinfo_shell_verb_get_type ())
+#define G_WIN32_APPINFO_SHELL_VERB(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
G_TYPE_WIN32_APPINFO_SHELL_VERB, GWin32AppInfoShellVerb))
+
 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_shell_verb_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 (GWin32AppInfoShellVerb, g_win32_appinfo_shell_verb, 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)
 
@@ -289,7 +355,7 @@ g_win32_appinfo_url_schema_dispose (GObject *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_pointer (&url->schema_u8_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);
@@ -303,16 +369,9 @@ g_win32_appinfo_handler_dispose (GObject *object)
 
   g_clear_pointer (&handler->handler_id, g_free);
   g_clear_pointer (&handler->handler_id_folded, g_free);
-  g_clear_pointer (&handler->handler_command, g_free);
-  g_clear_pointer (&handler->proxy_id, g_free);
-  g_clear_pointer (&handler->proxy_command, g_free);
-  g_clear_pointer (&handler->executable_folded, g_free);
-  g_clear_pointer (&handler->executable, g_free);
-  g_clear_pointer (&handler->dll_function, g_free);
   g_clear_object (&handler->key);
-  g_clear_object (&handler->proxy_key);
   g_clear_object (&handler->icon);
-  g_clear_object (&handler->app);
+  g_clear_pointer (&handler->verbs, g_ptr_array_unref);
   G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object);
 }
 
@@ -325,10 +384,25 @@ g_win32_appinfo_file_extension_dispose (GObject *object)
   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_shell_verb_dispose (GObject *object)
+{
+  GWin32AppInfoShellVerb *shverb = G_WIN32_APPINFO_SHELL_VERB (object);
+
+  g_clear_pointer (&shverb->verb_name, g_free);
+  g_clear_pointer (&shverb->verb_displayname, g_free);
+  g_clear_pointer (&shverb->command, g_free);
+  g_clear_pointer (&shverb->command_utf8, g_free);
+  g_clear_pointer (&shverb->executable_folded, g_free);
+  g_clear_pointer (&shverb->executable, g_free);
+  g_clear_pointer (&shverb->dll_function, g_free);
+  g_clear_object (&shverb->app);
+  G_OBJECT_CLASS (g_win32_appinfo_shell_verb_parent_class)->dispose (object);
+}
+
 static void
 g_win32_appinfo_application_dispose (GObject *object)
 {
@@ -340,20 +414,28 @@ g_win32_appinfo_application_dispose (GObject *object)
   g_clear_pointer (&app->pretty_name, g_free);
   g_clear_pointer (&app->localized_pretty_name, g_free);
   g_clear_pointer (&app->description, g_free);
-  g_clear_pointer (&app->command, g_free);
   g_clear_pointer (&app->pretty_name_u8, g_free);
   g_clear_pointer (&app->localized_pretty_name_u8, g_free);
   g_clear_pointer (&app->description_u8, g_free);
-  g_clear_pointer (&app->command_u8, g_free);
-  g_clear_pointer (&app->executable_folded, g_free);
-  g_clear_pointer (&app->executable, g_free);
-  g_clear_pointer (&app->dll_function, g_free);
   g_clear_pointer (&app->supported_urls, g_hash_table_destroy);
   g_clear_pointer (&app->supported_exts, g_hash_table_destroy);
   g_clear_object (&app->icon);
+  g_clear_pointer (&app->verbs, g_ptr_array_unref);
   G_OBJECT_CLASS (g_win32_appinfo_application_parent_class)->dispose (object);
 }
 
+static const gchar *
+g_win32_appinfo_application_get_some_name (GWin32AppInfoApplication *app)
+{
+  if (app->localized_pretty_name_u8)
+    return app->localized_pretty_name_u8;
+
+  if (app->pretty_name_u8)
+    return app->pretty_name_u8;
+
+  return app->canonical_name_u8;
+}
+
 static void
 g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass)
 {
@@ -370,6 +452,14 @@ g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klas
   gobject_class->dispose = g_win32_appinfo_file_extension_dispose;
 }
 
+static void
+g_win32_appinfo_shell_verb_class_init (GWin32AppInfoShellVerbClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = g_win32_appinfo_shell_verb_dispose;
+}
+
 static void
 g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass)
 {
@@ -395,6 +485,11 @@ g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self)
                                           g_object_unref);
 }
 
+static void
+g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self)
+{
+}
+
 static void
 g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
 {
@@ -402,15 +497,12 @@ g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
                                           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)
 {
+  self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
 }
 
 static void
@@ -424,6 +516,7 @@ g_win32_appinfo_application_init (GWin32AppInfoApplication *self)
                                                 g_str_equal,
                                                 g_free,
                                                 g_object_unref);
+  self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
 }
 
 G_LOCK_DEFINE_STATIC (gio_win32_appinfo);
@@ -439,20 +532,27 @@ static GHashTable *extensions = NULL;
 static GHashTable *urls = NULL;
 
 /* Map of owned "appID" (UTF-8, folded) to
- * GWin32AppInfoApplication ptr
+ * a GWin32AppInfoApplication
  */
 static GHashTable *apps_by_id = NULL;
 
 /* Map of owned "app.exe" (UTF-8, folded) to
- * GWin32AppInfoApplication ptr.
+ * a GWin32AppInfoApplication.
  * 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 "path:\to\app.exe" (UTF-8, folded) to
+ * a GWin32AppInfoApplication.
+ * The app objects in this map are fake - they are linked to
+ * handlers that do not have any apps associated with them.
+ */
+static GHashTable *fake_apps = NULL;
+
 /* Map of owned "handler id" (UTF-8, folded)
- * to GWin32AppInfoHandler ptr
+ * to a GWin32AppInfoHandler
  */
 static GHashTable *handlers = NULL;
 
@@ -487,7 +587,6 @@ static GWin32RegistryKey *classes_root_key;
 #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))
 
@@ -498,56 +597,92 @@ static GWin32RegistryKey *classes_root_key;
 #include "giowin32-private.c"
 
 static void
-read_handler_icon (GWin32RegistryKey  *proxy_key,
-                   GWin32RegistryKey  *program_key,
+read_handler_icon (GWin32RegistryKey  *key,
                    GIcon             **icon_out)
 {
-  gint counter;
-  GWin32RegistryKey *key;
+  GWin32RegistryKey *icon_key;
+  GWin32RegistryValueType default_type;
+  gchar *default_value;
+
+  g_assert (icon_out);
 
   *icon_out = NULL;
 
-  for (counter = 0; counter < 2; counter++)
-    {
-      GWin32RegistryKey *icon_key;
+  icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);
 
-      if (counter == 0)
-        key = proxy_key;
-      else
-        key = program_key;
+  if (icon_key == NULL)
+    return;
 
-      if (!key)
-        continue;
+  if (g_win32_registry_key_get_value (icon_key,
+                                      NULL,
+                                      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);
 
-      icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);
+      g_clear_pointer (&default_value, g_free);
+    }
 
-      if (icon_key != NULL)
-        {
-          GWin32RegistryValueType default_type;
-          gchar *default_value;
+  g_object_unref (icon_key);
+}
 
-          if (g_win32_registry_key_get_value (icon_key,
-                                              NULL,
-                                              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);
+static void
+reg_verb_free (gpointer p)
+{
+  if (p == NULL)
+    return;
 
-              g_clear_pointer (&default_value, g_free);
-            }
+  g_free (((reg_verb *) p)->name);
+  g_free (((reg_verb *) p)->shellpath);
+  g_free (p);
+}
 
-          g_object_unref (icon_key);
-        }
+#define is_open(x) ( \
+  ((x)[0] == L'o' || (x)[0] == L'O') && \
+  ((x)[1] == L'p' || (x)[1] == L'P') && \
+  ((x)[2] == L'e' || (x)[2] == L'E') && \
+  ((x)[3] == L'n' || (x)[3] == L'N') && \
+  ((x)[4] == L'\0') \
+)
+
+/* default verb (if any) comes first,
+ * then "open", then the rest of the verbs
+ * are sorted alphabetically
+ */
+static gint
+compare_verbs (gconstpointer a,
+               gconstpointer b,
+               gpointer user_data)
+{
+  const reg_verb *ca = (const reg_verb *) a;
+  const reg_verb *cb = (const reg_verb *) b;
+  const gunichar2 *def = (const gunichar2 *) user_data;
+  gboolean is_open_ca;
+  gboolean is_open_cb;
 
-      if (*icon_out)
-        break;
+  if (def != NULL)
+    {
+      if (_wcsicmp (ca->name, def) == 0)
+        return 1;
+      else if (_wcsicmp (cb->name, def) == 0)
+        return -1;
     }
+
+  is_open_ca = is_open (ca->name);
+  is_open_cb = is_open (cb->name);
+
+  if (is_open_ca && !is_open_cb)
+    return 1;
+  else if (is_open_ca && !is_open_cb)
+    return -1;
+
+  return _wcsicmp (ca->name, cb->name);
 }
 
 static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) G_GNUC_NULL_TERMINATED;
@@ -555,6 +690,64 @@ static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_l
 
 static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED;
 
+/* Called by process_verbs_commands.
+ * @verb is a verb name
+ * @command_line is the commandline of that verb
+ * @command_line_utf8 is the UTF-8 version of @command_line
+ * @verb_displayname is the prettier display name of the verb (might be NULL)
+ * @verb_is_preferred is TRUE if the verb is the preferred one
+ * @invent_new_verb_name is TRUE when the verb should be added
+ *                       even if a verb with such
+ *                       name already exists (in which case
+ *                       a new name is invented), unless
+ *                       the existing verb runs exactly the same
+ *                       commandline.
+ */
+typedef void (*verb_command_func) (gpointer         handler_data1,
+                                   gpointer         handler_data2,
+                                   const gunichar2 *verb,
+                                   const gunichar2 *command_line,
+                                   const gchar     *command_line_utf8,
+                                   const gchar     *verb_displayname,
+                                   gboolean         verb_is_preferred,
+                                   gboolean         invent_new_verb_name);
+
+static gunichar2 *                 decide_which_id_to_use (const gunichar2    *program_id,
+                                                           GWin32RegistryKey **return_key,
+                                                           gchar             **return_handler_id_u8,
+                                                           gchar             **return_handler_id_u8_folded);
+
+static GWin32AppInfoURLSchema *    get_schema_object      (const gunichar2 *schema,
+                                                           const gchar     *schema_u8,
+                                                           const gchar     *schema_u8_folded);
+
+static GWin32AppInfoHandler *      get_handler_object     (const gchar       *handler_id_u8_folded,
+                                                           GWin32RegistryKey *handler_key,
+                                                           const gunichar2   *handler_id);
+
+static GWin32AppInfoFileExtension *get_ext_object         (const gunichar2 *ext,
+                                                           const gchar     *ext_u8,
+                                                           const gchar     *ext_u8_folded);
+
+
+static void                        process_verbs_commands (GList             *verbs,
+                                                           const reg_verb    *preferred_verb,
+                                                           const gunichar2   *path_to_progid,
+                                                           const gunichar2   *progid,
+                                                           gboolean           autoprefer_first_verb,
+                                                           verb_command_func  handler,
+                                                           gpointer           handler_data1,
+                                                           gpointer           handler_data2);
+
+static void                        handler_add_verb       (gpointer           handler_data1,
+                                                           gpointer           handler_data2,
+                                                           const gunichar2   *verb,
+                                                           const gunichar2   *command_line,
+                                                           const gchar       *command_line_utf8,
+                                                           const gchar       *verb_displayname,
+                                                           gboolean           verb_is_preferred,
+                                                           gboolean           invent_new_verb_name);
+
 /* output_size is in *bytes*, not gunichar2s! */
 static gboolean
 build_registry_path (gunichar2 *output, gsize output_size, ...)
@@ -632,589 +825,830 @@ _g_win32_registry_key_build_and_new_w (GError **error, ...)
   return key;
 }
 
-/* Slow and dirty validator for UTF-16 strings */
-static gboolean
-g_utf16_validate (const gunichar2  *str,
-                 glong             len)
-{
-  gchar *tmp;
-
-  if (str == NULL)
-    return FALSE;
-
-  tmp = g_utf16_to_utf8 (str, len, NULL, NULL, NULL);
-
-  if (tmp == NULL)
-    return FALSE;
-
-  g_free (tmp);
-
-  return TRUE;
-}
-
-/* Does a UTF-16 validity check on *proxy_command and/or *program_command.
- * Fails if that check doesn't pass.
+/* Gets the list of shell verbs (a GList of reg_verb, put into @verbs)
+ * from the @program_id_key.
+ * If one of the verbs should be preferred,
+ * a pointer to this verb (in the GList) will be
+ * put into @preferred_verb.
+ * Does not automatically assume that the first verb
+ * is preferred (when no other preferences exist).
+ * @verbname_prefix is prefixed to the name of the verb
+ * (this is used for subcommands) and is initially an
+ * empty string.
+ * @verbshell_prefix is the subkey of @program_id_key
+ * that contains the verbs. It is "Shell" initially,
+ * but grows with recursive invocations (for subcommands).
+ * Returns TRUE on success, FALSE on failure.
  */
 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)
+get_verbs (GWin32RegistryKey  *program_id_key,
+           const reg_verb    **preferred_verb,
+           GList             **verbs,
+           const gunichar2    *verbname_prefix,
+           const gunichar2    *verbshell_prefix)
 {
+  GWin32RegistrySubkeyIter iter;
   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;
+  gunichar2 *default_verb;
+  gsize verbshell_prefix_len;
+  gsize verbname_prefix_len;
+  GList *i;
 
-  if (program_key)
-    *program_key = NULL;
+  g_assert (program_id_key && verbs && preferred_verb);
 
-  if (proxy_key)
-    *proxy_key = NULL;
+  *verbs = NULL;
+  *preferred_verb = NULL;
 
+  key = g_win32_registry_key_get_child_w (program_id_key,
+                                          verbshell_prefix,
+                                          NULL);
 
-  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id,
-                                               SHELL_OPEN_COMMAND, NULL);
+  if (key == NULL)
+    return FALSE;
 
-  if (key != NULL)
+  if (!g_win32_registry_subkey_iter_init (&iter, key, NULL))
     {
-      got_value = g_win32_registry_key_get_value_w (key,
-                                                    NULL,
-                                                    TRUE,
-                                                    L"",
-                                                    &val_type,
-                                                    (void **) program_command,
-                                                    NULL,
-                                                    NULL);
-      if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR)
-        {
-          if (((program_id_u8 != NULL || program_id_folded != NULL) &&
-               !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) ||
-              !g_utf16_validate (*program_command, -1))
-            {
-              g_object_unref (key);
-              g_free (program_command);
-
-              return FALSE;
-            }
-          if (program_key == NULL)
-            g_object_unref (key);
-          else
-            *program_key = key;
-
-          return TRUE;
-        }
-      else if (got_value)
-        g_clear_pointer (program_command, g_free);
-
       g_object_unref (key);
+
+      return FALSE;
     }
 
-  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, NULL);
+  verbshell_prefix_len = g_utf16_len (verbshell_prefix);
+  verbname_prefix_len = g_utf16_len (verbname_prefix);
 
-  if (key == NULL)
-    return FALSE;
+  while (g_win32_registry_subkey_iter_next (&iter, TRUE, NULL))
+    {
+      const gunichar2 *name;
+      gsize name_len;
+      GWin32RegistryKey *subkey;
+      gboolean has_subcommands;
+      const reg_verb *tmp;
+      GWin32RegistryValueType subc_type;
+      reg_verb *rverb;
+      const gunichar2 *shell = L"Shell";
+      const gsize shell_len = g_utf16_len (shell);
+
+      if (!g_win32_registry_subkey_iter_get_name_w (&iter, &name, &name_len, NULL))
+        continue;
 
-  got_value = g_win32_registry_key_get_value_w (key,
-                                                NULL,
-                                                TRUE,
-                                                L"",
-                                                &val_type,
-                                                (void **) proxy_id,
-                                                &proxy_id_size,
-                                                NULL);
-  g_object_unref (key);
+      subkey = g_win32_registry_key_get_child_w (key,
+                                                 name,
+                                                 NULL);
+
+      g_assert (subkey != NULL);
+      /* The key we're looking at is "<some_root>/Shell/<this_key>",
+       * where "Shell" is verbshell_prefix.
+       * If it has a value named 'Subcommands' (doesn't matter what its data is),
+       * it means that this key has its own Shell subkey, the subkeys
+       * of which are shell commands (i.e. <some_root>/Shell/<this_key>/Shell/<some_other_keys>).
+       * To handle that, create new, extended nameprefix and shellprefix,
+       * and call the function recursively.
+       * name prefix "" -> "<this_key_name>\\"
+       * shell prefix "Shell" -> "Shell\\<this_key_name>\\Shell"
+       * The root, program_id_key, remains the same in all invocations.
+       * Essentially, we're flattening the command tree into a list.
+       */
+      has_subcommands = FALSE;
+      if (g_win32_registry_key_get_value_w (subkey,
+                                            NULL,
+                                            TRUE,
+                                            L"Subcommands",
+                                            &subc_type,
+                                            NULL,
+                                            NULL,
+                                            NULL) &&
+          subc_type == G_WIN32_REGISTRY_VALUE_STR)
+        {
+          gunichar2 *new_nameprefix = g_malloc ((verbname_prefix_len + name_len + 1 + 1) * sizeof 
(gunichar2));
+          gunichar2 *new_shellprefix = g_malloc ((verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1) * 
sizeof (gunichar2));
+          memcpy (&new_shellprefix[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
+          new_shellprefix[verbshell_prefix_len] = L'\\';
+          memcpy (&new_shellprefix[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
+          new_shellprefix[verbshell_prefix_len + 1 + name_len] = L'\\';
+          memcpy (&new_shellprefix[verbshell_prefix_len + 1 + name_len + 1], shell, shell_len * sizeof 
(gunichar2));
+          new_shellprefix[verbshell_prefix_len + 1 + name_len + 1 + shell_len] = 0;
+
+          memcpy (&new_nameprefix[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
+          memcpy (&new_nameprefix[verbname_prefix_len], name, (name_len) * sizeof (gunichar2));
+          new_nameprefix[verbname_prefix_len + name_len] = L'\\';
+          new_nameprefix[verbname_prefix_len + name_len + 1] = 0;
+          has_subcommands = get_verbs (program_id_key, &tmp, verbs, new_nameprefix, new_shellprefix);
+          g_free (new_shellprefix);
+          g_free (new_nameprefix);
+        }
 
-  if (!got_value ||
-      (val_type != G_WIN32_REGISTRY_VALUE_STR))
-    {
-      g_clear_pointer (proxy_id, g_free);
+      g_clear_object (&subkey);
 
-      return FALSE;
+      /* Presence of subcommands means that this key itself is not a command-key */
+      if (has_subcommands)
+        continue;
+
+      /* We don't look at the command sub-key and its value (the actual command line) here.
+       * We save the registry path instead, and use it later in process_verbs_commands().
+       * The name of the verb is also saved.
+       * verbname_prefix is prefixed to the verb name (it's either an empty string
+       * or already ends with a '\\', so no extra separators needed).
+       * verbshell_prefix is prefixed to the verb key path (this one needs a separator,
+       * because it never has one - all verbshell prefixes end with "Shell", not "Shell\\")
+       */
+      rverb = g_new0 (reg_verb, 1);
+      rverb->name = g_malloc ((verbname_prefix_len + name_len + 1) * sizeof (gunichar2));
+      memcpy (&rverb->name[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
+      memcpy (&rverb->name[verbname_prefix_len], name, name_len * sizeof (gunichar2));
+      rverb->name[verbname_prefix_len + name_len] = 0;
+      rverb->shellpath = g_malloc ((verbshell_prefix_len + 1 + name_len + 1) * sizeof (gunichar2));
+      memcpy (&rverb->shellpath[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
+      memcpy (&rverb->shellpath[verbshell_prefix_len], L"\\", sizeof (gunichar2));
+      memcpy (&rverb->shellpath[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
+      rverb->shellpath[verbshell_prefix_len + 1 + name_len] = 0;
+      *verbs = g_list_append (*verbs, rverb);
     }
 
-  key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, *proxy_id,
-                                               SHELL_OPEN_COMMAND, NULL);
+  g_win32_registry_subkey_iter_clear (&iter);
 
-  if (key == NULL)
+  if (*verbs == NULL)
     {
-      g_clear_pointer (proxy_id, g_free);
+      g_object_unref (key);
 
       return FALSE;
     }
 
-  got_value = g_win32_registry_key_get_value_w (key,
-                                                NULL,
-                                                TRUE,
-                                                L"",
-                                                &val_type,
-                                                (void **) proxy_command,
-                                                NULL,
-                                                NULL);
+  default_verb = NULL;
 
-  if (proxy_key)
-    *proxy_key = key;
-  else
-    g_object_unref (key);
+  if (g_win32_registry_key_get_value_w (key,
+                                        NULL,
+                                        TRUE,
+                                        L"",
+                                        &val_type,
+                                        (void **) &default_verb,
+                                        NULL,
+                                        NULL) &&
+      (val_type != G_WIN32_REGISTRY_VALUE_STR ||
+       g_utf16_len (default_verb) <= 0))
+    g_clear_pointer (&default_verb, g_free);
+
+  g_object_unref (key);
 
-  if (!got_value ||
-      val_type != G_WIN32_REGISTRY_VALUE_STR ||
-      ((program_id_u8 != NULL || program_id_folded != NULL) &&
-       !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) ||
-      !g_utf16_validate (*proxy_command, -1))
+  /* Only sort at the top level */
+  if (verbname_prefix[0] == 0)
     {
-      g_clear_pointer (proxy_id, g_free);
-      g_clear_pointer (proxy_command, g_free);
-      if (proxy_key)
-        g_clear_object (proxy_key);
-      return FALSE;
+      *verbs = g_list_sort_with_data (*verbs, compare_verbs, default_verb);
+
+      for (i = *verbs; default_verb && *preferred_verb == NULL && i; i = i->next)
+        if (_wcsicmp (default_verb, ((const reg_verb *) i->data)->name) == 0)
+          *preferred_verb = (const reg_verb *) i->data;
     }
 
+  g_clear_pointer (&default_verb, g_free);
+
   return TRUE;
 }
 
+/* Grabs a URL association (from 
HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
+ * or from an application with Capabilities, or just a schema subkey in HKCR).
+ * @program_id is a ProgID of the handler for the URL.
+ * @schema is the schema for the URL.
+ * @schema_u8 and @schema_u8_folded are UTF-8 and folded UTF-8
+ * respectively.
+ * @app is the app to which the URL handler belongs (can be NULL).
+ * @is_user_choice is TRUE if this association is clearly preferred
+ */
 static void
-get_url_association (const gunichar2 *schema)
+get_url_association (const gunichar2          *program_id,
+                     const gunichar2          *schema,
+                     const gchar              *schema_u8,
+                     const gchar              *schema_u8_folded,
+                     GWin32AppInfoApplication *app,
+                     gboolean                  is_user_choice)
 {
   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)
+  gunichar2 *handler_id;
+  GList *verbs;
+  const reg_verb *preferred_verb;
+  gchar *handler_id_u8;
+  gchar *handler_id_u8_folded;
+  GWin32RegistryKey *handler_key;
+
+  if ((handler_id = decide_which_id_to_use (program_id,
+                                            &handler_key,
+                                            &handler_id_u8,
+                                            &handler_id_u8_folded)) == NULL)
     return;
 
-  if (!g_utf16_to_utf8_and_fold (schema, -1, &schema_u8, &schema_folded))
+  if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell"))
     {
-      g_object_unref (user_choice);
+      g_clear_pointer (&handler_id, g_free);
+      g_clear_pointer (&handler_id_u8, g_free);
+      g_clear_pointer (&handler_id_u8_folded, g_free);
+      g_clear_object (&handler_key);
+
       return;
     }
 
-  schema_rec = g_hash_table_lookup (urls, schema_folded);
+  schema_rec = get_schema_object (schema,
+                                  schema_u8,
+                                  schema_u8_folded);
 
-  if (!g_win32_registry_key_get_value_w (user_choice,
-                                         NULL,
-                                         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;
-    }
+  handler_rec = get_handler_object (handler_id_u8_folded,
+                                    handler_key,
+                                    handler_id);
+
+  if (is_user_choice || schema_rec->chosen_handler == NULL)
+    g_set_object (&schema_rec->chosen_handler, handler_rec);
+
+  g_hash_table_insert (schema_rec->handlers,
+                       g_strdup (handler_id_u8_folded),
+                       g_object_ref (handler_rec));
+
+  g_clear_object (&handler_key);
+
+  if (app)
+    g_hash_table_insert (app->supported_urls,
+                         g_strdup (schema_rec->schema_u8_folded),
+                         g_object_ref (handler_rec));
+
+  process_verbs_commands (g_steal_pointer (&verbs),
+                          preferred_verb,
+                          HKCR,
+                          handler_id,
+                          TRUE,
+                          handler_add_verb,
+                          handler_rec,
+                          app);
+
+  g_clear_pointer (&handler_id_u8, g_free);
+  g_clear_pointer (&handler_id_u8_folded, g_free);
+  g_clear_pointer (&handler_id, g_free);
+}
+
+/* Grabs a file extension association (from HKCR\.ext or similar).
+ * @program_id is a ProgID of the handler for the extension.
+ * @file_extension is the extension (with the leading '.')
+ * @app is the app to which the extension handler belongs (can be NULL).
+ * @is_user_choice is TRUE if this is clearly the preferred association
+ */
+static void
+get_file_ext (const gunichar2            *program_id,
+              const gunichar2            *file_extension,
+              GWin32AppInfoApplication   *app,
+              gboolean                    is_user_choice)
+{
+  GWin32AppInfoHandler *handler_rec;
+  gunichar2 *handler_id;
+  const reg_verb *preferred_verb;
+  GList *verbs;
+  gchar *handler_id_u8;
+  gchar *handler_id_u8_folded;
+  GWin32RegistryKey *handler_key;
+  GWin32AppInfoFileExtension *file_extn;
+  gchar *file_extension_u8;
+  gchar *file_extension_u8_folded;
 
-  if (val_type != G_WIN32_REGISTRY_VALUE_STR)
+  if ((handler_id = decide_which_id_to_use (program_id,
+                                            &handler_key,
+                                            &handler_id_u8,
+                                            &handler_id_u8_folded)) == NULL)
+    return;
+
+  if (!g_utf16_to_utf8_and_fold (file_extension,
+                                 -1,
+                                 &file_extension_u8,
+                                 &file_extension_u8_folded))
     {
-      g_free (schema_u8);
-      g_free (schema_folded);
-      g_free (program_id);
-      g_object_unref (user_choice);
+      g_clear_pointer (&handler_id, g_free);
+      g_clear_pointer (&handler_id_u8, g_free);
+      g_clear_pointer (&handler_id_u8_folded, g_free);
+      g_clear_object (&handler_key);
+
       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))
+  if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell"))
     {
-      g_free (schema_u8);
-      g_free (schema_folded);
-      g_free (program_id);
-      g_object_unref (user_choice);
+      g_clear_pointer (&handler_id, g_free);
+      g_clear_pointer (&handler_id_u8, g_free);
+      g_clear_pointer (&handler_id_u8_folded, g_free);
+      g_clear_object (&handler_key);
+      g_clear_pointer (&file_extension_u8, g_free);
+      g_clear_pointer (&file_extension_u8_folded, g_free);
+
       return;
     }
 
-  handler_rec = g_hash_table_lookup (handlers, program_id_folded);
-
-  if (handler_rec == NULL)
-    {
-      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-      handler_rec->proxy_key = proxy_key;
-      handler_rec->key = program_key;
-      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
-      handler_rec->handler_id_folded =
-          g_strdup (program_id_folded);
-      handler_rec->handler_command =
-          program_command ? g_wcsdup (program_command, -1) : NULL;
-      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-      handler_rec->proxy_command =
-          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                   &handler_rec->executable,
-                                   &handler_rec->executable_basename,
-                                   &handler_rec->executable_folded,
-                                   NULL,
-                                   &handler_rec->dll_function);
-      if (handler_rec->dll_function != NULL)
-        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
-      g_hash_table_insert (handlers,
-                           g_strdup (program_id_folded),
-                           handler_rec);
-    }
-  else
-    {
-      g_clear_object (&program_key);
-      g_clear_object (&proxy_key);
-    }
+  file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded);
 
-  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);
-    }
+  handler_rec = get_handler_object (handler_id_u8_folded,
+                                    handler_key,
+                                    handler_id);
 
-  if (schema_rec->chosen_handler == NULL)
-    schema_rec->chosen_handler = g_object_ref (handler_rec);
+  if (is_user_choice || file_extn->chosen_handler == NULL)
+    g_set_object (&file_extn->chosen_handler, handler_rec);
 
-  handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers,
-                                            program_id_folded);
+  g_hash_table_insert (file_extn->handlers,
+                       g_strdup (handler_id_u8_folded),
+                       g_object_ref (handler_rec));
 
-  if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec)
-    g_hash_table_insert (schema_rec->handlers,
-                         g_strdup (program_id_folded),
+  if (app)
+    g_hash_table_insert (app->supported_exts,
+                         g_strdup (file_extension_u8_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);
+  g_clear_pointer (&file_extension_u8, g_free);
+  g_clear_pointer (&file_extension_u8_folded, g_free);
+  g_clear_object (&handler_key);
+
+  process_verbs_commands (g_steal_pointer (&verbs),
+                          preferred_verb,
+                          HKCR,
+                          handler_id,
+                          TRUE,
+                          handler_add_verb,
+                          handler_rec,
+                          app);
+
+  g_clear_pointer (&handler_id, g_free);
+  g_clear_pointer (&handler_id_u8, g_free);
+  g_clear_pointer (&handler_id_u8_folded, g_free);
 }
 
-static void
-get_file_ext (const gunichar2 *ext)
+/* Returns either a @program_id or the string from
+ * the default value of the program_id key (which is a name
+ * of a proxy class), or NULL.
+ * Does not check that proxy represents a valid
+ * record, just checks that it exists.
+ * Can return the class key (HKCR/program_id or HKCR/proxy_id).
+ * Can convert returned value to UTF-8 and fold it.
+ */
+static gunichar2 *
+decide_which_id_to_use (const gunichar2    *program_id,
+                        GWin32RegistryKey **return_key,
+                        gchar             **return_handler_id_u8,
+                        gchar             **return_handler_id_u8_folded)
 {
-  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;
+  GWin32RegistryKey *key;
   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);
+  gunichar2 *return_id;
+  gboolean got_value;
+  gchar *handler_id_u8;
+  gchar *handler_id_u8_folded;
+  g_assert (program_id);
 
-  if (user_choice == NULL && open_with_progids == NULL)
-    return;
+  if (return_key)
+    *return_key = NULL;
 
-  if (!g_utf16_to_utf8_and_fold (ext, -1, &ext_u8, &ext_folded))
-    {
-      g_clear_object (&user_choice);
-      g_clear_object (&open_with_progids);
-      return;
-    }
+  /* Check the proxy first */
+  key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL);
 
-  file_extn = NULL;
-  file_ext_known = g_hash_table_lookup_extended (extensions,
-                                                 ext_folded,
-                                                 NULL,
-                                                 (void **) &file_extn);
+  if (key == NULL)
+    return NULL;
 
-  if (!file_ext_known)
-    file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
+  proxy_id = NULL;
+  got_value = g_win32_registry_key_get_value_w (key,
+                                                NULL,
+                                                TRUE,
+                                                L"",
+                                                &val_type,
+                                                (void **) &proxy_id,
+                                                NULL,
+                                                NULL);
+  if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
+    g_clear_pointer (&proxy_id, g_free);
 
-  found_handler = FALSE;
+  return_id = NULL;
 
-  if (user_choice != NULL)
+  if (proxy_id)
     {
-      if (g_win32_registry_key_get_value_w (user_choice,
-                                            NULL,
-                                            TRUE,
-                                            L"Progid",
-                                            &val_type,
-                                            (void **) &program_id,
-                                            &program_id_size,
-                                            NULL))
+      GWin32RegistryKey *proxy_key;
+      proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL);
+
+      if (proxy_key)
         {
-          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 (return_key)
+            *return_key = g_steal_pointer (&proxy_key);
+          g_clear_object (&proxy_key);
 
-              if (handler_rec == NULL)
-                {
-                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER,
-                                              NULL);
-                  handler_rec->proxy_key = proxy_key;
-                  handler_rec->key = program_key;
-                  handler_rec->handler_id =
-                      g_wcsdup (program_id, program_id_size);
-                  handler_rec->handler_id_folded =
-                      g_strdup (program_id_folded);
-                  handler_rec->handler_command =
-                      program_command ? g_wcsdup (program_command, -1) : NULL;
-                  handler_rec->proxy_id =
-                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-                  handler_rec->proxy_command =
-                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                               &handler_rec->executable,
-                                               &handler_rec->executable_basename,
-                                               &handler_rec->executable_folded,
-                                               NULL,
-                                               &handler_rec->dll_function);
-                  if (handler_rec->dll_function != NULL)
-                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-                  read_handler_icon (proxy_key,
-                                     program_key,
-                                     &handler_rec->icon);
-                  g_hash_table_insert (handlers,
-                                       g_strdup (program_id_folded),
-                                       handler_rec);
-                }
-              else
-                {
-                  g_clear_object (&program_key);
-                  g_clear_object (&proxy_key);
-                }
+          return_id = g_steal_pointer (&proxy_id);
+        }
 
-              found_handler = TRUE;
+      g_clear_pointer (&proxy_id, g_free);
+    }
 
-              handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
-                                                        program_id_folded);
+  if ((return_handler_id_u8 ||
+       return_handler_id_u8_folded) &&
+      !g_utf16_to_utf8_and_fold (return_id == NULL ? program_id : return_id,
+                                 -1,
+                                 &handler_id_u8,
+                                 &handler_id_u8_folded))
+    {
+      g_clear_object (&key);
+      if (return_key)
+        g_clear_object (return_key);
+      g_clear_pointer (&return_id, g_free);
 
-              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));
-                }
+      return NULL;
+    }
 
-              g_free (program_id_u8);
-              g_free (program_id_folded);
-              g_free (program_command);
-              g_free (proxy_id);
-              g_free (proxy_command);
-            }
+  if (return_handler_id_u8)
+    *return_handler_id_u8 = g_steal_pointer (&handler_id_u8);
+  if (return_handler_id_u8_folded)
+    *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded);
 
-          g_free (program_id);
-        }
+  if (return_id == NULL && return_key)
+    *return_key = g_steal_pointer (&key);
+  g_clear_object (&key);
 
-      g_object_unref (user_choice);
-    }
+  if (return_id == NULL)
+    return g_wcsdup (program_id, -1);
 
-  if (open_with_progids != NULL)
-    {
-      GWin32RegistryValueIter iter;
+  return return_id;
+}
+
+/* Grabs the command for each verb from @verbs,
+ * and invokes @handler for it. Consumes @verbs.
+ * @path_to_progid and @progid are concatenated to
+ * produce a path to the key where Shell/verb/command
+ * subkeys are looked up.
+ * @preferred_verb, if not NULL, will be used to inform
+ * the @handler that a verb is preferred.
+ * @autoprefer_first_verb will automatically make the first
+ * verb to be preferred, if @preferred_verb is NULL.
+ * @handler_data1 and @handler_data2 are passed to @handler as-is.
+ */
+static void
+process_verbs_commands (GList             *verbs,
+                        const reg_verb    *preferred_verb,
+                        const gunichar2   *path_to_progid,
+                        const gunichar2   *progid,
+                        gboolean           autoprefer_first_verb,
+                        verb_command_func  handler,
+                        gpointer           handler_data1,
+                        gpointer           handler_data2)
+{
+  GList *i;
+  gboolean got_value;
+
+  g_assert (handler != NULL);
+  g_assert (verbs != NULL);
+  g_assert (progid != NULL);
 
-      if (g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
+  for (i = verbs; i; i = i->next)
+    {
+      const reg_verb *verb = (const reg_verb *) i->data;
+      GWin32RegistryKey *key;
+      GWin32RegistryKey *verb_key;
+      gunichar2 *command_value;
+      gchar *command_value_utf8;
+      GWin32RegistryValueType val_type;
+      gunichar2 *verb_displayname;
+      gchar *verb_displayname_u8;
+
+      key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
+                                                   L"\\", verb->shellpath, L"\\command", NULL);
+
+      if (key == NULL)
         {
-          gunichar2 *value_name;
-          gunichar2 *value_data;
-          gsize      value_name_len;
-          gsize      value_data_size;
-          GWin32RegistryValueType value_type;
+          g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey",
+                   path_to_progid, progid, verb->shellpath);
+          continue;
+        }
 
-          while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
-            {
-              gsize value_name_size;
-              program_key = proxy_key = NULL;
+      command_value = NULL;
+      got_value = g_win32_registry_key_get_value_w (key,
+                                                    NULL,
+                                                    TRUE,
+                                                    L"",
+                                                    &val_type,
+                                                    (void **) &command_value,
+                                                    NULL,
+                                                    NULL);
+      g_clear_object (&key);
 
-              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;
+      if (!got_value ||
+          val_type != G_WIN32_REGISTRY_VALUE_STR ||
+          (command_value_utf8 = g_utf16_to_utf8 (command_value,
+                                                 -1,
+                                                 NULL,
+                                                 NULL,
+                                                 NULL)) == NULL)
+        {
+          g_clear_pointer (&command_value, g_free);
+          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;
+      verb_displayname = NULL;
+      verb_displayname_u8 = NULL;
+      verb_key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
+                                                        L"\\", verb->shellpath, NULL);
 
-              handler_rec = g_hash_table_lookup (handlers,
-                                                 program_id_folded);
+      if (verb_key)
+        {
+          gsize verb_displayname_len;
 
-              if (handler_rec == NULL)
-                {
-                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-                  handler_rec->proxy_key = proxy_key;
-                  handler_rec->key = program_key;
-                  handler_rec->handler_id =
-                      g_wcsdup (value_name, value_name_size);
-                  handler_rec->handler_id_folded =
-                      g_strdup (program_id_folded);
-                  handler_rec->handler_command =
-                      program_command ? g_wcsdup (program_command, -1) : NULL;
-                  handler_rec->proxy_id =
-                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-                  handler_rec->proxy_command =
-                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                               &handler_rec->executable,
-                                               &handler_rec->executable_basename,
-                                               &handler_rec->executable_folded,
-                                               NULL,
-                                               &handler_rec->dll_function);
-                  if (handler_rec->dll_function != NULL)
-                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-                  read_handler_icon (proxy_key,
-                                     program_key,
-                                     &handler_rec->icon);
-                  g_hash_table_insert (handlers,
-                                       g_strdup (program_id_folded),
-                                       handler_rec);
-                }
-              else
-                {
-                  g_clear_object (&program_key);
-                  g_clear_object (&proxy_key);
-                }
+          got_value = g_win32_registry_key_get_value_w (verb_key,
+                                                        g_win32_registry_get_os_dirs_w (),
+                                                        TRUE,
+                                                        L"MUIVerb",
+                                                        &val_type,
+                                                        (void **) &verb_displayname,
+                                                        &verb_displayname_len,
+                                                        NULL);
 
-              found_handler = TRUE;
+          if (got_value &&
+              val_type == G_WIN32_REGISTRY_VALUE_STR &&
+              verb_displayname_len > sizeof (gunichar2))
+            verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
 
-              handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
-                                                        program_id_folded);
+          g_clear_pointer (&verb_displayname, g_free);
 
-              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));
-                }
+          if (verb_displayname_u8 == NULL)
+            {
+              got_value = g_win32_registry_key_get_value_w (verb_key,
+                                                            NULL,
+                                                            TRUE,
+                                                            L"",
+                                                            &val_type,
+                                                            (void **) &verb_displayname,
+                                                            &verb_displayname_len,
+                                                            NULL);
 
-              g_free (program_id_u8);
-              g_free (program_id_folded);
-              g_free (program_command);
-              g_free (proxy_id);
-              g_free (proxy_command);
+              if (got_value &&
+                  val_type == G_WIN32_REGISTRY_VALUE_STR &&
+                  verb_displayname_len > sizeof (gunichar2))
+                verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
             }
 
-          g_win32_registry_value_iter_clear (&iter);
+          g_clear_pointer (&verb_displayname, g_free);
+          g_clear_object (&verb_key);
         }
 
-      g_object_unref (open_with_progids);
-    }
+      handler (handler_data1, handler_data2, verb->name, command_value, command_value_utf8,
+               verb_displayname_u8,
+               (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
+               (!preferred_verb && autoprefer_first_verb && i == verbs),
+               FALSE);
 
-  if (!found_handler)
-    {
-      if (!file_ext_known)
-        g_object_unref (file_extn);
+      g_clear_pointer (&command_value, g_free);
+      g_clear_pointer (&command_value_utf8, g_free);
+      g_clear_pointer (&verb_displayname_u8, g_free);
     }
-  else if (!file_ext_known)
+
+  g_list_free_full (verbs, reg_verb_free);
+}
+
+/* Looks up a schema object identified by
+ * @schema_u8_folded in the urls hash table.
+ * If such object doesn't exist,
+ * creates it and puts it into the urls hash table.
+ * Returns the object.
+ */
+static GWin32AppInfoURLSchema *
+get_schema_object (const gunichar2 *schema,
+                   const gchar     *schema_u8,
+                   const gchar     *schema_u8_folded)
+{
+  GWin32AppInfoURLSchema *schema_rec;
+
+  schema_rec = g_hash_table_lookup (urls, schema_u8_folded);
+
+  if (schema_rec != NULL)
+    return schema_rec;
+
+  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_u8_folded = g_strdup (schema_u8_folded);
+  g_hash_table_insert (urls, g_strdup (schema_rec->schema_u8_folded), schema_rec);
+
+  return schema_rec;
+}
+
+/* Looks up a handler object identified by
+ * @handler_id_u8_folded in the handlers hash table.
+ * If such object doesn't exist,
+ * creates it and puts it into the handlers hash table.
+ * Returns the object.
+ */
+static GWin32AppInfoHandler *
+get_handler_object (const gchar       *handler_id_u8_folded,
+                    GWin32RegistryKey *handler_key,
+                    const gunichar2   *handler_id)
+{
+  GWin32AppInfoHandler *handler_rec;
+
+  handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded);
+
+  if (handler_rec != NULL)
+    return handler_rec;
+
+  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
+  handler_rec->key = g_object_ref (handler_key);
+  handler_rec->handler_id = g_wcsdup (handler_id, -1);
+  handler_rec->handler_id_folded = g_strdup (handler_id_u8_folded);
+  read_handler_icon (handler_key, &handler_rec->icon);
+  g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec);
+
+  return handler_rec;
+}
+
+static void
+handler_add_verb (gpointer           handler_data1,
+                  gpointer           handler_data2,
+                  const gunichar2   *verb,
+                  const gunichar2   *command_line,
+                  const gchar       *command_line_utf8,
+                  const gchar       *verb_displayname,
+                  gboolean           verb_is_preferred,
+                  gboolean           invent_new_verb_name)
+{
+  GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1;
+  GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
+  GWin32AppInfoShellVerb *shverb;
+
+  _verb_lookup (handler_rec->verbs, verb, &shverb);
+
+  if (shverb != NULL)
+    return;
+
+  shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
+  shverb->verb_name = g_wcsdup (verb, -1);
+  shverb->verb_displayname = g_strdup (verb_displayname);
+  shverb->command = g_wcsdup (command_line, -1);
+  shverb->command_utf8 = g_strdup (command_line_utf8);
+  if (app_rec)
+    shverb->app = g_object_ref (app_rec);
+
+  _g_win32_extract_executable (shverb->command,
+                               &shverb->executable,
+                               &shverb->executable_basename,
+                               &shverb->executable_folded,
+                               NULL,
+                               &shverb->dll_function);
+
+  if (shverb->dll_function != NULL)
+    _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
+
+  if (!verb_is_preferred)
+    g_ptr_array_add (handler_rec->verbs, shverb);
+  else
+    g_ptr_array_insert (handler_rec->verbs, 0, shverb);
+}
+
+/* Tries to generate a new name for a verb that looks
+ * like "verb (%x)", where %x is an integer in range of [0;255).
+ * On success puts new verb (and new verb displayname) into
+ * @new_verb and @new_displayname and return TRUE.
+ * On failure puts NULL into both and returns FALSE.
+ */
+static gboolean
+generate_new_verb_name (GPtrArray        *verbs,
+                        const gunichar2  *verb,
+                        const gchar      *verb_displayname,
+                        gunichar2       **new_verb,
+                        gchar           **new_displayname)
+{
+  gsize counter;
+  GWin32AppInfoShellVerb *shverb;
+  gsize orig_len = g_utf16_len (verb);
+  gsize new_verb_name_len = orig_len + strlen (" ()") + 2 + 1;
+  gunichar2 *new_verb_name = g_malloc (new_verb_name_len * sizeof (gunichar2));
+
+  *new_verb = NULL;
+  *new_displayname = NULL;
+
+  memcpy (new_verb_name, verb, orig_len * sizeof (gunichar2));
+  for (counter = 0; counter < 255; counter++)
+  {
+    _snwprintf (&new_verb_name[orig_len], new_verb_name_len, L" (%x)", counter);
+    _verb_lookup (verbs, new_verb_name, &shverb);
+
+    if (shverb == NULL)
+      {
+        *new_verb = new_verb_name;
+        if (verb_displayname != NULL)
+          *new_displayname = g_strdup_printf ("%s (%zx)", verb_displayname, counter);
+
+        return TRUE;
+      }
+  }
+
+  return FALSE;
+}
+
+static void
+app_add_verb (gpointer           handler_data1,
+              gpointer           handler_data2,
+              const gunichar2   *verb,
+              const gunichar2   *command_line,
+              const gchar       *command_line_utf8,
+              const gchar       *verb_displayname,
+              gboolean           verb_is_preferred,
+              gboolean           invent_new_verb_name)
+{
+  gunichar2 *new_verb = NULL;
+  gchar *new_displayname = NULL;
+  GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
+  GWin32AppInfoShellVerb *shverb;
+
+  _verb_lookup (app_rec->verbs, verb, &shverb);
+
+  /* Special logic for fake apps - do our best to
+   * collate all possible verbs in the app,
+   * including the verbs that have the same name but
+   * different commandlines, in which case a new
+   * verb name has to be invented.
+   */
+  if (shverb != NULL)
     {
-      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);
+      gsize vi;
+
+      if (!invent_new_verb_name)
+        return;
+
+      for (vi = 0; vi < app_rec->verbs->len; vi++)
+        {
+          GWin32AppInfoShellVerb *app_verb;
+
+          app_verb = _verb_idx (app_rec->verbs, vi);
+
+          if (_wcsicmp (command_line, app_verb->command) == 0)
+            break;
+        }
+
+      if (vi < app_rec->verbs->len ||
+          !generate_new_verb_name (app_rec->verbs,
+                                   verb,
+                                   verb_displayname,
+                                   &new_verb,
+                                   &new_displayname))
+        return;
     }
 
-  g_free (ext_u8);
-  g_free (ext_folded);
+  shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
+  if (new_verb == NULL)
+    shverb->verb_name = g_wcsdup (verb, -1);
+  else
+    shverb->verb_name = g_steal_pointer (&new_verb);
+  if (new_displayname == NULL)
+    shverb->verb_displayname = g_strdup (verb_displayname);
+  else
+    shverb->verb_displayname = g_steal_pointer (&new_displayname);
+
+  shverb->command = g_wcsdup (command_line, -1);
+  shverb->command_utf8 = g_strdup (command_line_utf8);
+  shverb->app = g_object_ref (app_rec);
+
+  _g_win32_extract_executable (shverb->command,
+                               &shverb->executable,
+                               &shverb->executable_basename,
+                               &shverb->executable_folded,
+                               NULL,
+                               &shverb->dll_function);
+
+  if (shverb->dll_function != NULL)
+    _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
+
+  if (!verb_is_preferred)
+    g_ptr_array_add (app_rec->verbs, shverb);
+  else
+    g_ptr_array_insert (app_rec->verbs, 0, shverb);
+}
+
+/* Looks up a file extension object identified by
+ * @ext_u8_folded in the extensions hash table.
+ * If such object doesn't exist,
+ * creates it and puts it into the extensions hash table.
+ * Returns the object.
+ */
+static GWin32AppInfoFileExtension *
+get_ext_object (const gunichar2 *ext,
+                const gchar     *ext_u8,
+                const gchar     *ext_u8_folded)
+{
+  GWin32AppInfoFileExtension *file_extn;
+
+  if (g_hash_table_lookup_extended (extensions,
+                                    ext_u8_folded,
+                                    NULL,
+                                    (void **) &file_extn))
+    return file_extn;
+
+  file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
+  file_extn->extension = g_wcsdup (ext, -1);
+  file_extn->extension_u8 = g_strdup (ext_u8);
+  g_hash_table_insert (extensions, g_strdup (ext_u8_folded), file_extn);
+
+  return file_extn;
 }
 
+/* Iterates over HKCU\\Software\\Clients or HKLM\\Software\\Clients,
+ * (depending on @user_registry being TRUE or FALSE),
+ * collecting applications listed there.
+ * Puts the path to the client key for each client into @priority_capable_apps
+ * (only for clients with file or URL associations).
+ */
 static void
 collect_capable_apps_from_clients (GPtrArray *capable_apps,
                                    GPtrArray *priority_capable_apps,
@@ -1223,7 +1657,7 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps,
   GWin32RegistryKey *clients;
   GWin32RegistrySubkeyIter clients_iter;
 
-  gunichar2 *client_type_name;
+  const gunichar2 *client_type_name;
   gsize client_type_name_len;
 
 
@@ -1251,7 +1685,7 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps,
       GWin32RegistryKey *system_client_type;
       GWin32RegistryValueType default_type;
       gunichar2 *default_value = NULL;
-      gunichar2 *client_name;
+      const gunichar2 *client_name;
       gsize client_name_len;
 
       if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter,
@@ -1356,11 +1790,17 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps,
   g_object_unref (clients);
 }
 
+/* Iterates over HKCU\\Software\\RegisteredApplications or HKLM\\Software\\RegisteredApplications,
+ * (depending on @user_registry being TRUE or FALSE),
+ * collecting applications listed there.
+ * Puts the path to the app key for each app into @capable_apps.
+ */
 static void
 collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
                                            gboolean   user_registry)
 {
   GWin32RegistryValueIter iter;
+  const gunichar2 *reg_path;
 
   gunichar2 *value_data;
   gsize      value_data_size;
@@ -1368,13 +1808,12 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
   GWin32RegistryKey *registered_apps;
 
   if (user_registry)
-    registered_apps =
-        g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
-                                    NULL);
+    reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications";
   else
-    registered_apps =
-        g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
-                                    NULL);
+    reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications";
+
+  registered_apps =
+      g_win32_registry_key_new_w (reg_path, NULL);
 
   if (!registered_apps)
     return;
@@ -1382,13 +1821,15 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
   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;
+      GWin32RegistryKey *location;
+      gunichar2 *p;
 
       if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                         &value_type,
@@ -1402,51 +1843,81 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
           (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)
+      if (location == NULL)
+        continue;
+
+      p = wcsrchr (possible_location, L'\\');
+
+      if (p)
         {
-          gunichar2 *p = wcsrchr (possible_location, L'\\');
-          if (p)
-            *p = L'\0';
+          *p = L'\0';
           g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
-          g_object_unref (location);
         }
+
+      g_object_unref (location);
     }
 
   g_win32_registry_value_iter_clear (&iter);
   g_object_unref (registered_apps);
 }
 
+/* Looks up an app object identified by
+ * @canonical_name_folded in the @app_hashmap.
+ * If such object doesn't exist,
+ * creates it and puts it into the @app_hashmap.
+ * Returns the object.
+ */
+static GWin32AppInfoApplication *
+get_app_object (GHashTable      *app_hashmap,
+                const gunichar2 *canonical_name,
+                const gchar     *canonical_name_u8,
+                const gchar     *canonical_name_folded,
+                gboolean         user_specific,
+                gboolean         default_app)
+{
+  GWin32AppInfoApplication *app;
+
+  app = g_hash_table_lookup (app_hashmap, canonical_name_folded);
+
+  if (app != NULL)
+    return app;
+
+  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->no_open_with = FALSE;
+  app->user_specific = user_specific;
+  app->default_app = default_app;
+  g_hash_table_insert (app_hashmap,
+                       g_strdup (canonical_name_folded),
+                       app);
+
+  return app;
+}
+
+/* Grabs an application that has Capabilities.
+ * @app_key_path is the path to the application key
+ * (which must have a "Capabilities" subkey).
+ * @default_app is TRUE if the app has priority
+ */
 static void
-read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolean default_app)
+read_capable_app (const gunichar2 *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;
+  gchar *canonical_name_u8 = NULL;
+  gchar *canonical_name_folded = NULL;
+  gchar *app_key_path_u8 = NULL;
+  gchar *app_key_path_u8_folded = NULL;
+  GWin32RegistryKey *appkey = NULL;
   gunichar2 *fallback_friendly_name;
   GWin32RegistryValueType vtype;
   gboolean success;
@@ -1456,136 +1927,49 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
   gunichar2 *icon_source;
   GWin32RegistryKey *capabilities;
   GWin32RegistryKey *default_icon_key;
-  GWin32RegistryKey *shell_open_command_key;
-  gunichar2 *shell_open_command;
-  gchar *app_executable;
-  gchar *app_executable_basename;
-  gchar *app_executable_folded;
-  gchar *app_executable_folded_basename;
-  gchar *app_dll_function;
   GWin32RegistryKey *associations;
-
-  app_key_path = g_wcsdup (input_app_key_path, -1);
-
-  canonical_name = wcsrchr (app_key_path, L'\\');
-
-  if (canonical_name == NULL)
+  const reg_verb *preferred_verb;
+  GList *verbs = NULL;
+
+  appkey = NULL;
+  capabilities = NULL;
+
+  if (!g_utf16_to_utf8_and_fold (app_key_path,
+                                 -1,
+                                 &canonical_name_u8,
+                                 &canonical_name_folded) ||
+      !g_utf16_to_utf8_and_fold (app_key_path,
+                                 -1,
+                                 &app_key_path_u8,
+                                 &app_key_path_u8_folded) ||
+      (appkey = g_win32_registry_key_new_w (app_key_path, NULL)) == NULL ||
+      (capabilities = g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL)) == NULL ||
+      !get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell"))
     {
-      /* The key must have at least one '\\' */
-      g_free (app_key_path);
-      return;
-    }
-
-  canonical_name += 1;
-
-  if (!g_utf16_to_utf8_and_fold (canonical_name, -1, &canonical_name_u8, &canonical_name_folded))
-    {
-      g_free (app_key_path);
-      return;
-    }
-
-  appkey = g_win32_registry_key_new_w (app_key_path, NULL);
-
-  if (appkey == NULL)
-    {
-      g_free (canonical_name_u8);
-      g_free (canonical_name_folded);
-      g_free (app_key_path);
-      return;
-    }
-
-  capabilities =
-      g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL);
-
-  if (capabilities == NULL)
-    {
-      /* Must have capabilities */
-      g_free (canonical_name_u8);
-      g_free (canonical_name_folded);
-      g_free (app_key_path);
-      return;
-    }
-
-  shell_open_command_key =
-      g_win32_registry_key_get_child_w (appkey,
-                                        L"shell\\open\\command",
-                                        NULL);
-
-  if (shell_open_command_key == NULL)
-    {
-      g_object_unref (capabilities);
-      g_free (canonical_name_u8);
-      g_free (canonical_name_folded);
-      g_free (app_key_path);
-      g_object_unref (appkey);
-      return ;
-    }
-
-  shell_open_command = NULL;
-
-  success = g_win32_registry_key_get_value_w (shell_open_command_key,
-                                              NULL,
-                                              TRUE,
-                                              L"",
-                                              &vtype,
-                                              (gpointer *) &shell_open_command,
-                                              NULL,
-                                              NULL);
+      g_clear_pointer (&canonical_name_u8, g_free);
+      g_clear_pointer (&canonical_name_folded, g_free);
+      g_clear_object (&appkey);
+      g_clear_pointer (&app_key_path_u8, g_free);
+      g_clear_pointer (&app_key_path_u8_folded, g_free);
 
-  if (success &&
-      (vtype != G_WIN32_REGISTRY_VALUE_STR ||
-       !g_utf16_validate (shell_open_command, -1)))
-    {
-      /* Must have a command */
-      g_clear_pointer (&shell_open_command, g_free);
-      g_object_unref (capabilities);
-      g_free (canonical_name_u8);
-      g_free (canonical_name_folded);
-      g_free (app_key_path);
-      g_object_unref (appkey);
       return;
     }
 
-  _g_win32_extract_executable (shell_open_command,
-                               &app_executable,
-                               &app_executable_basename,
-                               &app_executable_folded,
-                               &app_executable_folded_basename,
-                               &app_dll_function);
-  if (app_dll_function != NULL)
-    _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command);
-
-  app = g_hash_table_lookup (apps_by_id, canonical_name_folded);
-
-  if (app == NULL)
-    {
-      app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
-
-      app->canonical_name = g_wcsdup (canonical_name, -1);
-      app->canonical_name_u8 = g_strdup (canonical_name_u8);
-      app->canonical_name_folded =
-          g_strdup (canonical_name_folded);
-
-      app->command = g_wcsdup (shell_open_command, -1);
-      app->command_u8 =
-          g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL);
-      app->executable = g_strdup (app_executable);
-      app->executable_basename =
-          &app->executable[app_executable_basename - app_executable];
-      app->executable_folded =
-          g_strdup (app_executable_folded);
-
-      app->no_open_with = FALSE;
-
-      app->user_specific = user_specific;
-      app->default_app = default_app;
-
-      app->dll_function = g_strdup (app_dll_function);
-
-      g_hash_table_insert (apps_by_id,
-                           g_strdup (canonical_name_folded),
-                           app);
-    }
+  app = get_app_object (apps_by_id,
+                        app_key_path,
+                        canonical_name_u8,
+                        canonical_name_folded,
+                        user_specific,
+                        default_app);
+
+  process_verbs_commands (g_steal_pointer (&verbs),
+                          preferred_verb,
+                          L"", /* [ab]use the fact that two strings are simply concatenated */
+                          g_win32_registry_key_get_path_w (capabilities),
+                          FALSE,
+                          app_add_verb,
+                          app,
+                          app);
 
   fallback_friendly_name = NULL;
   success = g_win32_registry_key_get_value_w (appkey,
@@ -1600,7 +1984,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
     g_clear_pointer (&fallback_friendly_name, g_free);
 
-  if (fallback_friendly_name && app->pretty_name == NULL)
+  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);
@@ -1621,10 +2006,12 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
                                               NULL,
                                               NULL);
 
-  if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
+  if (success &&
+      vtype != G_WIN32_REGISTRY_VALUE_STR)
     g_clear_pointer (&friendly_name, g_free);
 
-  if (friendly_name && app->localized_pretty_name == NULL)
+  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);
@@ -1672,7 +2059,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
                                                   NULL,
                                                   NULL);
 
-      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
+      if (success &&
+          vtype != G_WIN32_REGISTRY_VALUE_STR)
         g_clear_pointer (&icon_source, g_free);
 
       g_object_unref (default_icon_key);
@@ -1689,11 +2077,13 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
                                                   NULL,
                                                   NULL);
 
-      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
+      if (success &&
+          vtype != G_WIN32_REGISTRY_VALUE_STR)
         g_clear_pointer (&icon_source, g_free);
     }
 
-  if (icon_source && app->icon == NULL)
+  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);
@@ -1713,13 +2103,17 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
     g_clear_pointer (&narrow_application_name, g_free);
 
-  /* 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?
-   */
+  if (narrow_application_name &&
+      app->localized_pretty_name == NULL)
+    {
+      app->localized_pretty_name = g_wcsdup (narrow_application_name, -1);
+      g_clear_pointer (&app->localized_pretty_name_u8, g_free);
+      app->localized_pretty_name_u8 = g_utf16_to_utf8 (narrow_application_name,
+                                                       -1,
+                                                       NULL,
+                                                       NULL,
+                                                       NULL);
+    }
 
   associations = g_win32_registry_key_get_child_w (capabilities,
                                                    L"FileAssociations",
@@ -1739,19 +2133,6 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
 
           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)) ||
@@ -1770,110 +2151,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
                   (extension_handler[0] == L'\0'))
                 continue;
 
-              if (!follow_class_chain_to_handler (extension_handler,
-                                                  extension_handler_size,
-                                                  &program_command,
-                                                  &program_key,
-                                                  &proxy_id,
-                                                  &proxy_command,
-                                                  &proxy_key,
-                                                  &program_id_u8,
-                                                  &program_id_folded))
-                continue;
-
-              handler_rec = g_hash_table_lookup (handlers,
-                                                 program_id_folded);
-
-              if (handler_rec == NULL)
-                {
-                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-                  handler_rec->proxy_key = proxy_key;
-                  handler_rec->key = program_key;
-                  handler_rec->handler_id =
-                      g_wcsdup (extension_handler,extension_handler_size);
-                  handler_rec->handler_id_folded =
-                      g_strdup (program_id_folded);
-                  handler_rec->handler_command =
-                      program_command ? g_wcsdup (program_command, -1) : NULL;
-                  handler_rec->proxy_id =
-                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-                  handler_rec->proxy_command =
-                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                               &handler_rec->executable,
-                                               &handler_rec->executable_basename,
-                                               &handler_rec->executable_folded,
-                                               NULL,
-                                               &handler_rec->dll_function);
-                  if (handler_rec->dll_function != NULL)
-                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-                  read_handler_icon (proxy_key,
-                                     program_key,
-                                     &handler_rec->icon);
-                  g_hash_table_insert (handlers,
-                                       g_strdup (program_id_folded),
-                                       handler_rec);
-                }
-              else
-                {
-                  g_clear_object (&program_key);
-                  g_clear_object (&proxy_key);
-                }
-
-                if (g_utf16_to_utf8_and_fold (file_extension,
-                                              -1,
-                                              &file_extension_u8,
-                                              &file_extension_folded))
-                  {
-                    ext = g_hash_table_lookup (extensions,
-                                               file_extension_folded);
-
-                    if (ext == NULL)
-                      {
-                        ext = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
-
-                        ext->extension = g_wcsdup (file_extension, -1);
-                        ext->extension_u8 = g_strdup (file_extension_u8);
-                        g_hash_table_insert (extensions, g_strdup (file_extension_folded), ext);
-                      }
-
-                    handler_rec_in_ext =
-                        g_hash_table_lookup (ext->handlers,
-                                             program_id_folded);
-
-                    if (handler_rec_in_ext == NULL)
-                      {
-                        if (ext->chosen_handler == NULL)
-                          g_hash_table_insert (ext->handlers,
-                                               g_strdup (program_id_folded),
-                                               g_object_ref (handler_rec));
-                        else if (ext->chosen_handler->handler_id_folded &&
-                                 strcmp (ext->chosen_handler->handler_id_folded,
-                                         program_id_folded) != 0)
-                          g_hash_table_insert (ext->handlers,
-                                               g_strdup (program_id_folded),
-                                               g_object_ref (handler_rec));
-                      }
-
-                    handler_rec_in_ext =
-                        g_hash_table_lookup (app->supported_exts,
-                                             file_extension_folded);
-
-                    if (handler_rec_in_ext == NULL)
-                      g_hash_table_insert (app->supported_exts,
-                                           g_strdup (file_extension_folded),
-                                           g_object_ref (handler_rec));
-
-                    g_free (file_extension_u8);
-                    g_free (file_extension_folded);
-                  }
-
-              g_free (program_id_u8);
-              g_free (program_id_folded);
-              g_free (program_command);
-              g_free (proxy_id);
-              g_free (proxy_command);
+              get_file_ext (extension_handler, file_extension, app, FALSE);
             }
 
           g_win32_registry_value_iter_clear (&iter);
@@ -1898,18 +2176,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
 
           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;
+              gchar *schema_u8_folded;
 
               if ((!g_win32_registry_value_iter_get_value_type (&iter,
                                                                 &value_type,
@@ -1930,105 +2198,16 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
                   (schema_handler[0] == L'\0'))
                 continue;
 
-              if (!follow_class_chain_to_handler (schema_handler,
-                                                  schema_handler_size,
-                                                  &program_command,
-                                                  &program_key,
-                                                  &proxy_id,
-                                                  &proxy_command,
-                                                  &proxy_key,
-                                                  &program_id_u8,
-                                                  &program_id_folded))
-                continue;
 
-              
-              handler_rec = g_hash_table_lookup (handlers, program_id_folded);
 
-              if (handler_rec == NULL)
-                {
-                  handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-                  handler_rec->proxy_key = proxy_key;
-                  handler_rec->key = program_key;
-                  handler_rec->handler_id =
-                      g_wcsdup (schema_handler, schema_handler_size);
-                  handler_rec->handler_id_folded =
-                      g_strdup (program_id_folded);
-                  handler_rec->handler_command = program_command ?
-                      g_wcsdup (program_command, -1) : NULL;
-                  handler_rec->proxy_id =
-                      proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-                  handler_rec->proxy_command =
-                      proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-                  _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                               &handler_rec->executable,
-                                               &handler_rec->executable_basename,
-                                               &handler_rec->executable_folded,
-                                               NULL,
-                                               &handler_rec->dll_function);
-                  if (handler_rec->dll_function != NULL)
-                    _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-                  read_handler_icon (proxy_key,
-                                     program_key,
-                                     &handler_rec->icon);
-                  g_hash_table_insert (handlers,
-                                       g_strdup (program_id_folded),
-                                       handler_rec);
-                }
-              else
-                {
-                  g_clear_object (&program_key);
-                  g_clear_object (&proxy_key);
-                }
+              if (g_utf16_to_utf8_and_fold (url_schema,
+                                            url_schema_len,
+                                            &schema_u8,
+                                            &schema_u8_folded))
+                get_url_association (schema_handler, url_schema, schema_u8, schema_u8_folded, app, FALSE);
 
-                if (g_utf16_to_utf8_and_fold (url_schema,
-                                              -1,
-                                              &schema_u8,
-                                              &schema_folded))
-                  {
-                    schema = g_hash_table_lookup (urls,
-                                                  schema_folded);
-
-                    if (schema == NULL)
-                      {
-                        schema = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
-
-                        schema->schema = g_wcsdup (url_schema, -1);
-                        schema->schema_u8 = g_strdup (schema_u8);
-                        schema->schema_folded =
-                            g_strdup (schema_folded);
-                        g_hash_table_insert (urls,
-                                             g_strdup (schema_folded),
-                                             schema);
-                      }
-
-                    handler_rec_in_url =
-                        g_hash_table_lookup (schema->handlers,
-                                             program_id_folded);
-
-                    if (handler_rec_in_url == NULL)
-                      g_hash_table_insert (schema->handlers,
-                                           g_strdup (program_id_folded),
-                                           g_object_ref (handler_rec));
-
-                    handler_rec_in_url =
-                        g_hash_table_lookup (app->supported_urls,
-                                             schema_folded);
-
-                    if (handler_rec_in_url == NULL)
-                      g_hash_table_insert (app->supported_urls,
-                                           g_strdup (schema_folded),
-                                           g_object_ref (handler_rec));
-
-                    g_free (schema_u8);
-                    g_free (schema_folded);
-                  }
-
-              g_free (program_id_u8);
-              g_free (program_id_folded);
-              g_free (program_command);
-              g_free (proxy_id);
-              g_free (proxy_command);
+              g_clear_pointer (&schema_u8, g_free);
+              g_clear_pointer (&schema_u8_folded, g_free);
             }
 
           g_win32_registry_value_iter_clear (&iter);
@@ -2037,29 +2216,26 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
       g_object_unref (associations);
     }
 
-  g_clear_pointer (&app_executable, g_free);
-  g_clear_pointer (&app_executable_folded, g_free);
-  g_clear_pointer (&app_dll_function, g_free);
   g_clear_pointer (&fallback_friendly_name, g_free);
   g_clear_pointer (&description, g_free);
   g_clear_pointer (&icon_source, g_free);
   g_clear_pointer (&narrow_application_name, g_free);
-  g_clear_pointer (&shell_open_command, g_free);
 
   g_object_unref (appkey);
-  g_object_unref (shell_open_command_key);
   g_object_unref (capabilities);
-  g_free (canonical_name_u8);
-  g_free (canonical_name_folded);
-  g_free (app_key_path);
+  g_clear_pointer (&app_key_path_u8, g_free);
+  g_clear_pointer (&app_key_path_u8_folded, g_free);
+  g_clear_pointer (&canonical_name_u8, g_free);
+  g_clear_pointer (&canonical_name_folded, g_free);
 }
 
+/* Iterates over subkeys in 
HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
+ * and calls get_url_association() for each one that has a user-chosen handler.
+ */
 static void
 read_urls (GWin32RegistryKey *url_associations)
 {
   GWin32RegistrySubkeyIter url_iter;
-  gunichar2 *url_schema;
-  gsize url_schema_len;
 
   if (url_associations == NULL)
     return;
@@ -2069,302 +2245,259 @@ read_urls (GWin32RegistryKey *url_associations)
 
   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);
+      gchar *schema_u8 = NULL;
+      gchar *schema_u8_folded = NULL;
+      const gunichar2 *url_schema = NULL;
+      gunichar2 *program_id = NULL;
+      GWin32RegistryKey *user_choice = NULL;
+      gsize url_schema_len;
+      GWin32RegistryValueType val_type;
+
+      if (g_win32_registry_subkey_iter_get_name_w (&url_iter,
+                                                   &url_schema,
+                                                   &url_schema_len,
+                                                   NULL) &&
+          g_utf16_to_utf8_and_fold (url_schema,
+                                    url_schema_len,
+                                    &schema_u8,
+                                    &schema_u8_folded) &&
+          (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS,
+                                                                url_schema, USER_CHOICE,
+                                                                NULL)) != NULL &&
+          g_win32_registry_key_get_value_w (user_choice,
+                                            NULL,
+                                            TRUE,
+                                            L"Progid",
+                                            &val_type,
+                                            (void **) &program_id,
+                                            NULL,
+                                            NULL) &&
+          val_type == G_WIN32_REGISTRY_VALUE_STR)
+        get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE);
+
+      g_clear_pointer (&program_id, g_free);
+      g_clear_pointer (&user_choice, g_object_unref);
+      g_clear_pointer (&schema_u8, g_free);
+      g_clear_pointer (&schema_u8_folded, g_free);
     }
 
   g_win32_registry_subkey_iter_clear (&url_iter);
 }
 
+/* Reads an application that is only registered by the basename of its
+ * executable (and doesn't have Capabilities subkey).
+ * @incapable_app is the registry key for the app.
+ * @app_exe_basename is the basename of its executable.
+ */
 static void
-read_exeapps (void)
+read_incapable_app (GWin32RegistryKey *incapable_app,
+                    const gunichar2   *app_exe_basename,
+                    const gchar       *app_exe_basename_u8,
+                    const gchar       *app_exe_basename_u8_folded)
 {
-  GWin32RegistryKey *applications_key;
-  GWin32RegistrySubkeyIter app_iter;
-
-  applications_key =
-      g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);
+  GWin32RegistryValueIter sup_iter;
+  GWin32AppInfoApplication *app;
+  GList *verbs;
+  const reg_verb *preferred_verb;
+  gunichar2 *friendly_app_name;
+  gboolean success;
+  GWin32RegistryValueType vtype;
+  gboolean no_open_with;
+  GWin32RegistryKey *default_icon_key;
+  gunichar2 *icon_source;
+  GIcon *icon = NULL;
+  GWin32RegistryKey *supported_key;
 
-  if (applications_key == NULL)
+  if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell"))
     return;
 
-  if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL))
-    {
-      g_object_unref (applications_key);
-      return;
-    }
-
-  while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
-    {
-      gunichar2 *app_exe_basename;
-      gsize app_exe_basename_len;
-      GWin32RegistryKey *incapable_app;
-      gunichar2 *friendly_app_name;
-      gboolean success;
-      gboolean no_open_with;
-      GWin32RegistryValueType vtype;
-      GWin32RegistryKey *default_icon_key;
-      gunichar2 *icon_source;
-      GIcon *icon = NULL;
-      gchar *appexe;
-      gchar *appexe_basename;
-      gchar *appexe_folded;
-      gchar *appexe_folded_basename;
-      GWin32AppInfoApplication *app;
-      GWin32RegistryKey *shell_open_command_key;
-      gunichar2 *shell_open_command;
-      GWin32RegistryKey *supported_key;
-
-      if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
-                                                    &app_exe_basename,
-                                                    &app_exe_basename_len,
-                                                    NULL) ||
-          !g_utf16_validate (app_exe_basename, app_exe_basename_len))
-        continue;
-
-      incapable_app =
-          g_win32_registry_key_get_child_w (applications_key,
-                                            app_exe_basename,
-                                            NULL);
-
-      if (incapable_app == NULL)
-        continue;
-
-      _g_win32_extract_executable (app_exe_basename,
-                                   &appexe,
-                                   &appexe_basename,
-                                   &appexe_folded,
-                                   &appexe_folded_basename,
-                                   NULL);
-
-      shell_open_command_key =
-          g_win32_registry_key_get_child_w (incapable_app,
-                                            L"shell\\open\\command",
-                                            NULL);
-
-      shell_open_command = NULL;
-
-      if (shell_open_command_key != NULL)
-        {
-          success = g_win32_registry_key_get_value_w (shell_open_command_key,
-                                                      NULL,
-                                                      TRUE,
-                                                      L"",
-                                                      &vtype,
-                                                      (gpointer *) &shell_open_command,
-                                                      NULL,
-                                                      NULL);
-
-          if (success &&
-              (vtype != G_WIN32_REGISTRY_VALUE_STR ||
-               !g_utf16_validate (shell_open_command, -1)))
-            {
-              g_clear_pointer (&shell_open_command, g_free);
-            }
-
-          g_object_unref (shell_open_command_key);
-        }
-
-      friendly_app_name = NULL;
-      success = g_win32_registry_key_get_value_w (incapable_app,
-                                                  g_win32_registry_get_os_dirs_w (),
-                                                  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);
-
-      no_open_with = FALSE;
-      success = g_win32_registry_key_get_value_w (incapable_app,
-                                                  NULL,
-                                                  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,
-                                              NULL,
+  app = get_app_object (apps_by_exe,
+                        app_exe_basename,
+                        app_exe_basename_u8,
+                        app_exe_basename_u8_folded,
+                        FALSE,
+                        FALSE);
+
+  process_verbs_commands (g_steal_pointer (&verbs),
+                          preferred_verb,
+                          L"HKEY_CLASSES_ROOT\\Applications\\",
+                          app_exe_basename,
+                          TRUE,
+                          app_add_verb,
+                          app,
+                          app);
+
+  friendly_app_name = NULL;
+  success = g_win32_registry_key_get_value_w (incapable_app,
+                                              g_win32_registry_get_os_dirs_w (),
                                               TRUE,
-                                              L"",
+                                              L"FriendlyAppName",
                                               &vtype,
-                                              (void **) &icon_source,
+                                              (void **) &friendly_app_name,
                                               NULL,
                                               NULL);
 
-        if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
-          g_clear_pointer (&icon_source, g_free);
+  if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
+    g_clear_pointer (&friendly_app_name, g_free);
+
+  no_open_with = g_win32_registry_key_get_value_w (incapable_app,
+                                                   NULL,
+                                                   TRUE,
+                                                   L"NoOpenWith",
+                                                   &vtype,
+                                                   NULL,
+                                                   NULL,
+                                                   NULL);
 
-        g_object_unref (default_icon_key);
-      }
+  default_icon_key =
+      g_win32_registry_key_get_child_w (incapable_app,
+                                        L"DefaultIcon",
+                                        NULL);
 
-      if (icon_source)
-        {
-          gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
-          icon = g_themed_icon_new (name);
-          g_free (name);
-        }
+  icon_source = NULL;
 
-      app = g_hash_table_lookup (apps_by_exe, appexe_folded_basename);
+  if (default_icon_key != NULL)
+    {
+      success =
+          g_win32_registry_key_get_value_w (default_icon_key,
+                                            NULL,
+                                            TRUE,
+                                            L"",
+                                            &vtype,
+                                            (void **) &icon_source,
+                                            NULL,
+                                            NULL);
 
-      if (app == NULL)
-        {
-          app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
+      if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
+        g_clear_pointer (&icon_source, g_free);
 
-          if (shell_open_command)
-            {
-              gchar *dll_function;
-
-              _g_win32_extract_executable (shell_open_command,
-                                           NULL,
-                                           NULL,
-                                           NULL,
-                                           NULL,
-                                           &dll_function);
-              if (dll_function != NULL)
-                _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command);
-              g_clear_pointer (&dll_function, g_free);
-            }
+      g_object_unref (default_icon_key);
+    }
 
-          app->command =
-              shell_open_command ? g_wcsdup (shell_open_command, -1) : NULL;
+  if (icon_source)
+    {
+      gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
+      if (name != NULL)
+        icon = g_themed_icon_new (name);
+      g_free (name);
+    }
 
-          if (shell_open_command)
-            app->command_u8 = g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL);
+  app->no_open_with = no_open_with;
 
-          app->executable = g_strdup (appexe);
-          app->executable_basename = &app->executable[appexe_basename - appexe];
-          app->executable_folded = g_strdup (appexe_folded);
+  if (friendly_app_name &&
+      app->localized_pretty_name == NULL)
+    {
+      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);
+    }
 
-          app->no_open_with = no_open_with;
+  if (icon && app->icon == NULL)
+    app->icon = g_object_ref (icon);
 
-          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);
-            }
+  supported_key =
+      g_win32_registry_key_get_child_w (incapable_app,
+                                        L"SupportedTypes",
+                                        NULL);
 
-          if (icon)
-            app->icon = g_object_ref (icon);
+  if (supported_key &&
+      g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL))
+    {
+      gunichar2 *ext_name;
+      gsize      ext_name_len;
 
-          app->user_specific = FALSE;
-          app->default_app = FALSE;
+      while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
+        {
+          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'.'))
+            continue;
 
-          g_hash_table_insert (apps_by_exe,
-                               g_strdup (appexe_folded_basename),
-                               app);
+          get_file_ext (ext_name, ext_name, app, FALSE);
         }
 
-      supported_key =
-          g_win32_registry_key_get_child_w (incapable_app,
-                                            L"SupportedTypes",
-                                            NULL);
+      g_win32_registry_value_iter_clear (&sup_iter);
+    }
 
-      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;
+  g_clear_object (&supported_key);
+  g_free (friendly_app_name);
+  g_free (icon_source);
 
-              while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
-                {
-                  gchar *ext_u8;
-                  gchar *ext_folded;
-                  GWin32AppInfoFileExtension *file_extn;
-                  gboolean file_ext_known;
-
-                  if ((!g_win32_registry_value_iter_get_name_w (&sup_iter,
-                                                                &ext_name,
-                                                                &ext_name_len,
-                                                                NULL)) ||
-                      (ext_name_len <= 0) ||
-                      (ext_name[0] != L'.') ||
-                      (!g_utf16_to_utf8_and_fold (ext_name,
-                                                  -1,
-                                                  &ext_u8,
-                                                  &ext_folded)))
-                    continue;
-
-                  file_extn = NULL;
-                  file_ext_known =
-                      g_hash_table_lookup_extended (extensions,
-                                                    ext_folded,
-                                                    NULL,
-                                                    (void **) &file_extn);
+  g_clear_object (&icon);
+}
 
-                  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);
-                    }
+/* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications
+ * and calls read_incapable_app() for each one.
+ */
+static void
+read_exeapps (void)
+{
+  GWin32RegistryKey *applications_key;
+  GWin32RegistrySubkeyIter app_iter;
 
-                  g_hash_table_insert (file_extn->other_apps,
-                                       g_strdup (appexe_folded),
-                                       g_object_ref (app));
+  applications_key =
+      g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);
 
-                  g_free (ext_u8);
-                  g_free (ext_folded);
-                }
+  if (applications_key == NULL)
+    return;
 
-              g_win32_registry_value_iter_clear (&sup_iter);
-            }
+  if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL))
+    {
+      g_object_unref (applications_key);
+      return;
+    }
 
-          g_object_unref (supported_key);
-        }
+  while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
+    {
+      const gunichar2 *app_exe_basename;
+      gsize app_exe_basename_len;
+      GWin32RegistryKey *incapable_app;
+      gchar *app_exe_basename_u8;
+      gchar *app_exe_basename_u8_folded;
+
+      if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
+                                                    &app_exe_basename,
+                                                    &app_exe_basename_len,
+                                                    NULL) ||
+          !g_utf16_to_utf8_and_fold (app_exe_basename,
+                                     app_exe_basename_len,
+                                     &app_exe_basename_u8,
+                                     &app_exe_basename_u8_folded))
+        continue;
 
+      incapable_app =
+          g_win32_registry_key_get_child_w (applications_key,
+                                            app_exe_basename,
+                                            NULL);
 
-      g_free (appexe);
-      g_free (appexe_folded);
-      g_free (shell_open_command);
-      g_free (friendly_app_name);
-      g_free (icon_source);
+      if (incapable_app != NULL)
+        read_incapable_app (incapable_app,
+                            app_exe_basename,
+                            app_exe_basename_u8,
+                            app_exe_basename_u8_folded);
 
-      g_clear_object (&icon);
       g_clear_object (&incapable_app);
+      g_clear_pointer (&app_exe_basename_u8, g_free);
+      g_clear_pointer (&app_exe_basename_u8_folded, g_free);
     }
 
   g_win32_registry_subkey_iter_clear (&app_iter);
   g_object_unref (applications_key);
 }
 
-
+/* Iterates over subkeys of 
HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\
+ * and calls get_file_ext() for each associated handler
+ * (starting with user-chosen handler, if any)
+ */
 static void
 read_exts (GWin32RegistryKey *file_exts)
 {
   GWin32RegistrySubkeyIter ext_iter;
-  gunichar2 *file_extension;
+  const gunichar2 *file_extension;
   gsize file_extension_len;
 
   if (file_exts == NULL)
@@ -2375,684 +2508,427 @@ read_exts (GWin32RegistryKey *file_exts)
 
   while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL))
     {
+      GWin32RegistryKey *open_with_progids;
+      gunichar2 *program_id;
+      GWin32RegistryValueIter iter;
+      gunichar2 *value_name;
+      gsize      value_name_len;
+      GWin32RegistryValueType value_type;
+      GWin32RegistryKey *user_choice;
+
       if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter,
                                                     &file_extension,
                                                     &file_extension_len,
                                                     NULL))
         continue;
 
-      get_file_ext (file_extension);
-    }
-
-  g_win32_registry_subkey_iter_clear (&ext_iter);
-}
-
-static void
-read_class_extension (GWin32RegistryKey *classes_root,
-                      gunichar2         *class_name,
-                      gsize              class_name_len)
-{
-  gchar *ext_u8;
-  gchar *ext_folded;
-  GWin32AppInfoFileExtension *file_extn;
-  GWin32AppInfoHandler *handler_rec;
-  GWin32AppInfoHandler *handler_rec_in_ext;
-  GWin32RegistryKey *class_key;
-  gsize program_id_size;
-  gunichar2 *program_id;
-  gunichar2 *proxy_id;
-  GWin32RegistryKey *program_key;
-  GWin32RegistryKey *proxy_key;
-  gunichar2 *program_command;
-  gunichar2 *proxy_command;
-
-  class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
-
-  if (class_key == NULL)
-    return;
-
-  program_id = class_name;
-  program_id_size = (class_name_len + 1) * sizeof (gunichar2);
-  program_key = proxy_key = NULL;
-  program_command = proxy_command = NULL;
-
-  if (!follow_class_chain_to_handler (program_id,
-                                      program_id_size,
-                                      &program_command,
-                                      &program_key,
-                                      &proxy_id,
-                                      &proxy_command,
-                                      &proxy_key,
-                                      &ext_u8,
-                                      &ext_folded))
-    {
-      g_object_unref (class_key);
-      return;
-    }
-
-
-  file_extn = g_hash_table_lookup (extensions, ext_folded);
-  handler_rec = g_hash_table_lookup (handlers, ext_folded);
-
-  if (file_extn == NULL)
-    {
-      file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
-      file_extn->extension = g_wcsdup (class_name, -1);
-      file_extn->extension_u8 = g_strdup (ext_u8);
-      g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn);
-    }
-
-  if (handler_rec == NULL)
-    {
-      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-      handler_rec->proxy_key = proxy_key;
-      handler_rec->key = program_key;
-      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
-      handler_rec->handler_id_folded = g_strdup (ext_folded);
-      handler_rec->handler_command =
-          program_command ? g_wcsdup (program_command, -1) : NULL;
-      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-      handler_rec->proxy_command =
-          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                   &handler_rec->executable,
-                                   &handler_rec->executable_basename,
-                                   &handler_rec->executable_folded,
-                                   NULL,
-                                   &handler_rec->dll_function);
-      if (handler_rec->dll_function != NULL)
-        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
-      g_hash_table_insert (handlers,
-                           g_strdup (ext_folded),
-                           handler_rec);
-    }
-  else
-    {
-      g_clear_object (&program_key);
-      g_clear_object (&proxy_key);
-    }
-
-  handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers,
-                                            ext_folded);
-
-  if (file_extn->chosen_handler == NULL)
-    g_hash_table_insert (file_extn->handlers,
-                         g_strdup (ext_folded),
-                         g_object_ref (handler_rec));
-  else if (handler_rec_in_ext == NULL)
-    {
-      if (file_extn->chosen_handler->handler_id_folded &&
-          strcmp (file_extn->chosen_handler->handler_id_folded,
-                  ext_folded) != 0)
-        g_hash_table_insert (file_extn->handlers,
-                             g_strdup (ext_folded),
-                             g_object_ref (handler_rec));
-    }
-
-  g_free (program_command);
-  g_free (proxy_id);
-  g_free (proxy_command);
-  g_free (ext_u8);
-  g_free (ext_folded);
-  g_object_unref (class_key);
-}
-
-static void
-read_class_url (GWin32RegistryKey *classes_root,
-                gunichar2         *class_name,
-                gsize              class_name_len)
-{
-  GWin32RegistryKey *class_key;
-  gboolean success;
-  GWin32RegistryValueType vtype;
-  GWin32AppInfoURLSchema *schema_rec;
-  GWin32AppInfoHandler *handler_rec;
-  GWin32AppInfoHandler *handler_rec_in_url;
-  gunichar2 *program_id;
-  gsize program_id_size;
-  gunichar2 *program_command;
-  gunichar2 *proxy_id;
-  gunichar2 *proxy_command;
-  gchar *program_id_u8;
-  gchar *program_id_folded;
-  GWin32RegistryKey *program_key;
-  GWin32RegistryKey *proxy_key;
-
-  class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
-
-  if (class_key == NULL)
-    return;
-
-  success = g_win32_registry_key_get_value_w (class_key,
-                                              NULL,
-                                              TRUE,
-                                              L"URL Protocol",
-                                              &vtype,
-                                              NULL,
-                                              NULL,
-                                              NULL);
-
-  if (!success ||
-      vtype != G_WIN32_REGISTRY_VALUE_STR)
-    {
-      g_object_unref (class_key);
-      return;
-    }
-
-  program_id = class_name;
-  program_id_size = (class_name_len + 1) * sizeof (gunichar2);
-  proxy_key = program_key = NULL;
-  program_command = proxy_id = proxy_command = NULL;
-
-  if (!follow_class_chain_to_handler (program_id,
-                                      program_id_size,
-                                      &program_command,
-                                      &program_key,
-                                      &proxy_id,
-                                      &proxy_command,
-                                      &proxy_key,
-                                      &program_id_u8,
-                                      &program_id_folded))
-    {
-      g_object_unref (class_key);
-      return;
-    }
-
-  schema_rec = g_hash_table_lookup (urls, program_id_folded);
-  handler_rec = g_hash_table_lookup (handlers, program_id_folded);
-
-  if (handler_rec == NULL)
-    {
-      handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
-
-      handler_rec->proxy_key = proxy_key;
-      handler_rec->key = program_key;
-      handler_rec->handler_id = g_wcsdup (program_id, program_id_size);
-      handler_rec->handler_id_folded =
-          g_strdup (program_id_folded);
-      handler_rec->handler_command =
-          program_command ? g_wcsdup (program_command, -1) : NULL;
-      handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL;
-      handler_rec->proxy_command =
-          proxy_command ? g_wcsdup (proxy_command, -1) : NULL;
-      _g_win32_extract_executable (proxy_command ? proxy_command : program_command,
-                                   &handler_rec->executable,
-                                   &handler_rec->executable_basename,
-                                   &handler_rec->executable_folded,
-                                   NULL,
-                                   &handler_rec->dll_function);
-      if (handler_rec->dll_function != NULL)
-        _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? 
handler_rec->handler_command : handler_rec->proxy_command);
-      read_handler_icon (proxy_key, program_key, &handler_rec->icon);
-      g_hash_table_insert (handlers,
-                           g_strdup (program_id_folded),
-                           handler_rec);
-    }
-  else
-    {
-      g_clear_object (&program_key);
-      g_clear_object (&proxy_key);
-    }
-
-  if (schema_rec == NULL)
-    {
-      schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
-      schema_rec->schema = g_wcsdup (class_name, -1);
-      schema_rec->schema_u8 = g_strdup (program_id_u8);
-      schema_rec->schema_folded = g_strdup (program_id_folded);
-      schema_rec->chosen_handler = g_object_ref (handler_rec);
-      g_hash_table_insert (urls,
-                           g_strdup (program_id_folded),
-                           schema_rec);
-    }
-
-  if (schema_rec->chosen_handler == NULL)
-    schema_rec->chosen_handler = g_object_ref (handler_rec);
-
-  handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers,
-                                            program_id_folded);
-
-  if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec)
-    g_hash_table_insert (schema_rec->handlers,
-                         g_strdup (program_id_folded),
-                         g_object_ref (handler_rec));
-
-  g_free (program_id_u8);
-  g_free (program_id_folded);
-  g_free (program_command);
-  g_free (proxy_id);
-  g_free (proxy_command);
-  g_object_unref (class_key);
-}
-
-static void
-read_classes (GWin32RegistryKey *classes_root)
-{
-  GWin32RegistrySubkeyIter class_iter;
-  gunichar2 *class_name;
-  gsize class_name_len;
+      program_id = NULL;
+      user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, file_extension,
+                                                           USER_CHOICE, NULL);
+      if (user_choice &&
+          g_win32_registry_key_get_value_w (user_choice,
+                                            NULL,
+                                            TRUE,
+                                            L"Progid",
+                                            &value_type,
+                                            (void **) &program_id,
+                                            NULL,
+                                            NULL) &&
+          value_type == G_WIN32_REGISTRY_VALUE_STR)
+        {
+          /* Note: program_id could be "ProgramID" or "Applications\\program.exe".
+           * The code still works, but handler_id might have a backslash
+           * in it - that might trip us up later on.
+           * Even though in that case this is logically an "application"
+           * registry entry, we don't treat it in any special way.
+           * We do scan that registry branch anyway, just not here.
+           */
+          get_file_ext (program_id, file_extension, NULL, TRUE);
+        }
 
-  if (classes_root == NULL)
-    return;
+      g_clear_object (&user_choice);
+      g_clear_pointer (&program_id, g_free);
 
-  if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL))
-    return;
+      open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS,
+                                                                 file_extension,
+                                                                 OPEN_WITH_PROGIDS,
+                                                                 NULL);
 
-  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))
+      if (open_with_progids == NULL)
         continue;
 
-      if (class_name[0] == L'.')
-        read_class_extension (classes_root, class_name, class_name_len);
-      else
+      if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
         {
-          gsize i;
+          g_clear_object (&open_with_progids);
+          continue;
+        }
 
-          for (i = 0; i < class_name_len; i++)
-            if (!iswalpha (class_name[i]))
-              break;
+      while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
+        {
+          if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
+                                                       &value_name_len,
+                                                       NULL) ||
+              (value_name_len == 0))
+            continue;
 
-          if (i == class_name_len)
-            read_class_url (classes_root, class_name, class_name_len);
+          get_file_ext (value_name, file_extension, NULL, FALSE);
         }
+
+      g_win32_registry_value_iter_clear (&iter);
+      g_clear_object (&open_with_progids);
     }
 
-  g_win32_registry_subkey_iter_clear (&class_iter);
+  g_win32_registry_subkey_iter_clear (&ext_iter);
 }
 
+/* Iterates over subkeys in HKCR, calls
+ * get_file_ext() for any subkey that starts with ".",
+ * or get_url_association() for any subkey that could
+ * be a URL schema and has a "URL Protocol" value.
+ */
 static void
-link_chosen_handlers (void)
+read_classes (GWin32RegistryKey *classes_root)
 {
-  GHashTableIter iter;
-  GHashTableIter handler_iter;
-  gchar *schema_folded;
-  GWin32AppInfoURLSchema *schema;
-  gchar *handler_id_folded;
-  GWin32AppInfoHandler *handler;
-  gchar *ext_folded;
-  GWin32AppInfoFileExtension *ext;
+  GWin32RegistrySubkeyIter class_iter;
+  const gunichar2 *class_name;
+  gsize class_name_len;
 
-  g_hash_table_iter_init (&iter, urls);
+  if (classes_root == NULL)
+    return;
 
-  while (g_hash_table_iter_next (&iter,
-                                (gpointer *) &schema_folded,
-                                (gpointer *) &schema))
+  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 (schema->chosen_handler != NULL)
+      if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter,
+                                                     &class_name,
+                                                     &class_name_len,
+                                                     NULL)) ||
+          (class_name_len <= 1))
         continue;
 
-      g_hash_table_iter_init (&handler_iter, schema->handlers);
-
-      while (g_hash_table_iter_next (&handler_iter,
-                                     (gpointer *) &handler_id_folded,
-                                     (gpointer *) &handler))
+      if (class_name[0] == L'.')
         {
-          gchar *proxy_id_folded;
+          GWin32RegistryKey *class_key;
+          GWin32RegistryValueIter iter;
+          GWin32RegistryKey *open_with_progids;
+          gunichar2 *value_name;
+          gsize      value_name_len;
 
-          if (schema->chosen_handler != NULL)
-            break;
+          /* Read the data from the HKCR\\.ext (usually proxied
+           * to another HKCR subkey)
+           */
+          get_file_ext (class_name, class_name, NULL, FALSE);
 
-          if (strcmp (handler_id_folded, schema_folded) != 0)
-            continue;
+          class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
 
-          if (handler->proxy_command &&
-              handler->proxy_id &&
-              g_utf16_to_utf8_and_fold (handler->proxy_id,
-                                        -1,
-                                        NULL,
-                                        &proxy_id_folded))
-            {
-              GWin32AppInfoHandler *proxy;
+          if (class_key == NULL)
+            continue;
 
-              proxy = g_hash_table_lookup (handlers, proxy_id_folded);
+          open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL);
+          g_clear_object (&class_key);
 
-              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);
-                }
+          if (open_with_progids == NULL)
+            continue;
 
-              g_free (proxy_id_folded);
+          if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
+            {
+              g_clear_object (&open_with_progids);
+              continue;
             }
 
-          if (schema->chosen_handler == NULL)
+          /* Read the data for other handlers for this extension */
+          while (g_win32_registry_value_iter_next (&iter, TRUE, 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);
-            }
-        }
-    }
+              if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
+                                                           &value_name_len,
+                                                           NULL) ||
+                  (value_name_len == 0))
+                continue;
 
-  g_hash_table_iter_init (&iter, extensions);
+              get_file_ext (value_name, class_name, NULL, FALSE);
+            }
 
-  while (g_hash_table_iter_next (&iter,
-                                 (gpointer *) &ext_folded,
-                                 (gpointer *) &ext))
-    {
-      if (ext->chosen_handler != NULL)
-        continue;
+          g_win32_registry_value_iter_clear (&iter);
+          g_clear_object (&open_with_progids);
+        }
+      else
+        {
+          gsize i;
+          GWin32RegistryKey *class_key;
+          gboolean success;
+          GWin32RegistryValueType vtype;
+          gchar *schema_u8;
+          gchar *schema_u8_folded;
 
-      g_hash_table_iter_init (&handler_iter, ext->handlers);
+          for (i = 0; i < class_name_len; i++)
+            if (!iswalpha (class_name[i]))
+              break;
 
-      while (g_hash_table_iter_next (&handler_iter,
-                                     (gpointer *) &handler_id_folded,
-                                     (gpointer *) &handler))
-        {
-          gchar *proxy_id_folded;
+          if (i != class_name_len)
+            continue;
 
-          if (ext->chosen_handler != NULL)
-            break;
+          class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
 
-          if (strcmp (handler_id_folded, ext_folded) != 0)
+          if (class_key == NULL)
             continue;
 
-          if (handler->proxy_command &&
-              handler->proxy_id &&
-              g_utf16_to_utf8_and_fold (handler->proxy_id,
-                                        -1,
-                                        NULL,
-                                        &proxy_id_folded))
-            {
-              GWin32AppInfoHandler *proxy;
+          success = g_win32_registry_key_get_value_w (class_key,
+                                                      NULL,
+                                                      TRUE,
+                                                      L"URL Protocol",
+                                                      &vtype,
+                                                      NULL,
+                                                      NULL,
+                                                      NULL);
+          g_clear_object (&class_key);
 
-              proxy = g_hash_table_lookup (handlers, proxy_id_folded);
+          if (!success ||
+              vtype != G_WIN32_REGISTRY_VALUE_STR)
+            continue;
 
-              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);
-                }
+          if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded))
+            continue;
 
-              g_free (proxy_id_folded);
-            }
+          get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE);
 
-          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);
-            }
+          g_clear_pointer (&schema_u8, g_free);
+          g_clear_pointer (&schema_u8_folded, g_free);
         }
     }
+
+  g_win32_registry_subkey_iter_clear (&class_iter);
 }
 
+/* Iterates over all handlers and over all apps,
+ * and links handler verbs to apps if a handler
+ * runs the same executable as one of the app verbs.
+ */
 static void
-link_handlers_to_registered_apps (void)
+link_handlers_to_unregistered_apps (void)
 {
   GHashTableIter iter;
-  GHashTableIter sup_iter;
-  gchar *app_id_folded;
+  GHashTableIter app_iter;
+  GWin32AppInfoHandler *handler;
+  gchar *handler_id_fld;
   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;
-        }
-    }
+  gchar *canonical_name_fld;
+  gchar *appexe_fld_basename;
 
-  g_hash_table_iter_init (&iter, apps_by_id);
+  g_hash_table_iter_init (&iter, handlers);
   while (g_hash_table_iter_next (&iter,
-                                 (gpointer *) &app_id_folded,
-                                 (gpointer *) &app))
+                                 (gpointer *) &handler_id_fld,
+                                 (gpointer *) &handler))
     {
-      if (app->supported_urls)
-        {
-          GWin32AppInfoHandler *handler;
+      gsize vi;
 
-          g_hash_table_iter_init (&sup_iter, app->supported_urls);
-          while (g_hash_table_iter_next (&sup_iter,
-                                         (gpointer *) &schema_folded,
-                                         (gpointer *) &handler))
+      for (vi = 0; vi < handler->verbs->len; vi++)
+        {
+          GWin32AppInfoShellVerb *handler_verb;
+          const gchar *handler_exe_basename;
+          enum
             {
-              schema = g_hash_table_lookup (urls, schema_folded);
+              SH_UNKNOWN,
+              GOT_SH_INFO,
+              ERROR_GETTING_SH_INFO,
+            } have_stat_handler = SH_UNKNOWN;
+          GWin32PrivateStat handler_verb_exec_info;
 
-              g_assert (schema != NULL);
+          handler_verb = _verb_idx (handler->verbs, vi);
 
-              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);
-                }
-            }
+          if (handler_verb->app != NULL)
+            continue;
+
+          handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1);
+          g_hash_table_iter_init (&app_iter, apps_by_id);
 
-          g_hash_table_iter_init (&sup_iter, app->supported_urls);
-          while (g_hash_table_iter_next (&sup_iter,
-                                         (gpointer *) &schema_folded,
-                                         (gpointer *) &handler))
+          while (g_hash_table_iter_next (&app_iter,
+                                         (gpointer *) &canonical_name_fld,
+                                         (gpointer *) &app))
             {
-              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);
-                }
-            }
-        }
+              GWin32AppInfoShellVerb *app_verb;
+              gsize ai;
 
-      if (app->supported_exts)
-        {
-          GWin32AppInfoHandler *handler;
+              for (ai = 0; ai < app->verbs->len; ai++)
+                {
+                  GWin32PrivateStat app_verb_exec_info;
+                  const gchar *app_exe_basename;
+                  app_verb = _verb_idx (app->verbs, ai);
 
-          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);
+                  app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1);
 
-              g_assert (ext != NULL);
+                  /* First check that the executable paths are identical */
+                  if (g_strcmp0 (app_verb->executable_folded, handler_verb->executable_folded) != 0)
+                    {
+                      /* If not, check the basenames. If they are different, don't bother
+                       * with further checks.
+                       */
+                      if (g_strcmp0 (app_exe_basename, handler_exe_basename) != 0)
+                        continue;
+
+                      /* Get filesystem IDs for both files.
+                       * For the handler that is attempted only once.
+                       */
+                      if (have_stat_handler == SH_UNKNOWN)
+                        {
+                          if (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (handler_verb->executable_folded,
+                                                                     &handler_verb_exec_info) == 0)
+                            have_stat_handler = GOT_SH_INFO;
+                          else
+                            have_stat_handler = ERROR_GETTING_SH_INFO;
+                        }
+
+                      if (have_stat_handler != GOT_SH_INFO ||
+                          (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (app_verb->executable_folded,
+                                                                  &app_verb_exec_info) != 0) ||
+                          app_verb_exec_info.file_index != handler_verb_exec_info.file_index)
+                        continue;
+                    }
 
-              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);
+                  handler_verb->app = g_object_ref (app);
+                  break;
                 }
             }
 
-          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_verb->app != NULL)
+            continue;
+
+          g_hash_table_iter_init (&app_iter, apps_by_exe);
+
+          while (g_hash_table_iter_next (&app_iter,
+                                         (gpointer *) &appexe_fld_basename,
+                                         (gpointer *) &app))
             {
-              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);
-                }
-            }
-        }
-    }
+              /* Use basename because apps_by_exe only has basenames */
+              if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0)
+                continue;
 
-  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;
+              handler_verb->app = g_object_ref (app);
+              break;
+            }
         }
     }
-  g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts);
 }
 
+/* Finds all .ext and schema: handler verbs that have no app linked to them,
+ * creates a "fake app" object and links these verbs to these
+ * objects. Objects are identified by the full path to
+ * the executable being run, thus multiple different invocations
+ * get grouped in a more-or-less natural way.
+ * The iteration goes separately over .ext and schema: handlers
+ * (instead of the global handlers hashmap) to allow us to
+ * put the handlers into supported_urls or supported_exts as
+ * needed (handler objects themselves have no knowledge of extensions
+ * and/or URLs they are associated with).
+ */
 static void
-link_handlers_to_unregistered_apps (void)
+link_handlers_to_fake_apps (void)
 {
   GHashTableIter iter;
-  GHashTableIter app_iter;
+  GHashTableIter handler_iter;
+  gchar *extension_utf8_folded;
+  GWin32AppInfoFileExtension *file_extn;
+  gchar *handler_id_fld;
   GWin32AppInfoHandler *handler;
-  gchar *handler_id_fc;
-  GWin32AppInfoApplication *app;
-  gchar *canonical_name_fc;
-  gchar *appexe_fc_basename;
+  gchar *url_utf8_folded;
+  GWin32AppInfoURLSchema *schema;
 
-  g_hash_table_iter_init (&iter, handlers);
+  g_hash_table_iter_init (&iter, extensions);
   while (g_hash_table_iter_next (&iter,
-                                 (gpointer *) &handler_id_fc,
-                                 (gpointer *) &handler))
+                                 (gpointer *) &extension_utf8_folded,
+                                 (gpointer *) &file_extn))
     {
-      gchar *hndexe_fc_basename;
-
-      if ((handler->app != NULL) ||
-          (handler->executable_folded == NULL))
-        continue;
-
-      g_hash_table_iter_init (&app_iter, apps_by_id);
-
-      while (g_hash_table_iter_next (&app_iter,
-                                     (gpointer *) &canonical_name_fc,
-                                     (gpointer *) &app))
+      g_hash_table_iter_init (&handler_iter, file_extn->handlers);
+      while (g_hash_table_iter_next (&handler_iter,
+                                     (gpointer *) &handler_id_fld,
+                                     (gpointer *) &handler))
         {
-          if (app->executable_folded == NULL)
-            continue;
-
-          if (strcmp (app->executable_folded,
-                      handler->executable_folded) != 0)
-            continue;
-
-          handler->app = app;
-          break;
-        }
+          gsize vi;
 
-      if (handler->app != NULL)
-        continue;
+          for (vi = 0; vi < handler->verbs->len; vi++)
+            {
+              GWin32AppInfoShellVerb *handler_verb;
+              GWin32AppInfoApplication *app;
+              gunichar2 *exename_utf16;
+              handler_verb = _verb_idx (handler->verbs, vi);
 
-      hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1);
+              if (handler_verb->app != NULL)
+                continue;
 
-      if (hndexe_fc_basename == NULL)
-        continue;
+              exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL);
+              if (exename_utf16 == NULL)
+                continue;
 
-      g_hash_table_iter_init (&app_iter, apps_by_exe);
+              app = get_app_object (fake_apps,
+                                    exename_utf16,
+                                    handler_verb->executable,
+                                    handler_verb->executable_folded,
+                                    FALSE,
+                                    FALSE);
+              g_clear_pointer (&exename_utf16, g_free);
+              handler_verb->app = g_object_ref (app);
+
+              app_add_verb (app,
+                            app,
+                            handler_verb->verb_name,
+                            handler_verb->command,
+                            handler_verb->command_utf8,
+                            handler_verb->verb_displayname,
+                            TRUE,
+                            TRUE);
+              g_hash_table_insert (app->supported_exts,
+                                   g_strdup (extension_utf8_folded),
+                                   g_object_ref (handler));
+            }
+        }
+    }
 
-      while ((hndexe_fc_basename != NULL) &&
-             (g_hash_table_iter_next (&app_iter,
-                                      (gpointer *) &appexe_fc_basename,
-                                      (gpointer *) &app)))
+  g_hash_table_iter_init (&iter, urls);
+  while (g_hash_table_iter_next (&iter,
+                                 (gpointer *) &url_utf8_folded,
+                                 (gpointer *) &schema))
+    {
+      g_hash_table_iter_init (&handler_iter, schema->handlers);
+      while (g_hash_table_iter_next (&handler_iter,
+                                     (gpointer *) &handler_id_fld,
+                                     (gpointer *) &handler))
         {
-          /* Use basename because apps_by_exe only has basenames */
-          if (strcmp (hndexe_fc_basename, appexe_fc_basename) != 0)
-            continue;
+          gsize vi;
 
-          handler->app = app;
-          break;
-        }
+          for (vi = 0; vi < handler->verbs->len; vi++)
+            {
+              GWin32AppInfoShellVerb *handler_verb;
+              GWin32AppInfoApplication *app;
+              gchar *command_utf8_folded;
+              handler_verb = _verb_idx (handler->verbs, vi);
 
-      g_free (hndexe_fc_basename);
+              if (handler_verb->app != NULL)
+                continue;
 
-      if (handler->app == NULL)
-        g_debug ("WARNING: handler that runs %s has no corresponding app\n",
-                 handler->executable);
+              command_utf8_folded = g_utf8_casefold (handler_verb->command_utf8, -1);
+              app = get_app_object (fake_apps,
+                                    handler_verb->command,
+                                    handler_verb->command_utf8,
+                                    command_utf8_folded,
+                                    FALSE,
+                                    FALSE);
+              g_clear_pointer (&command_utf8_folded, g_free);
+              handler_verb->app = g_object_ref (app);
+
+              app_add_verb (app,
+                            app,
+                            handler_verb->verb_name,
+                            handler_verb->command,
+                            handler_verb->command_utf8,
+                            handler_verb->verb_displayname,
+                            TRUE,
+                            TRUE);
+              g_hash_table_insert (app->supported_urls,
+                                   g_strdup (url_utf8_folded),
+                                   g_object_ref (handler));
+            }
+        }
     }
 }
 
@@ -3083,6 +2959,7 @@ update_registry_data (void)
 
   g_clear_pointer (&apps_by_id, g_hash_table_destroy);
   g_clear_pointer (&apps_by_exe, g_hash_table_destroy);
+  g_clear_pointer (&fake_apps, 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);
@@ -3104,6 +2981,8 @@ update_registry_data (void)
       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);
+  fake_apps =
+      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 =
@@ -3134,9 +3013,8 @@ update_registry_data (void)
   exeapp_end = GetTickCount ();
   read_classes (classes_root);
   classes_end = GetTickCount ();
-  link_chosen_handlers ();
-  link_handlers_to_registered_apps ();
   link_handlers_to_unregistered_apps ();
+  link_handlers_to_fake_apps ();
   postproc_end = GetTickCount ();
 
   g_debug ("Collecting capable appnames: %lums\n"
@@ -3147,7 +3025,7 @@ update_registry_data (void)
            "Reading exe-only apps:...... %lums\n"
            "Reading classes:             %lums\n"
            "Postprocessing:..............%lums\n"
-           "TOTAL:                       %lums\n",
+           "TOTAL:                       %lums",
            collect_end - collect_start,
            alloc_end - collect_end,
            capable_end - alloc_end,
@@ -3337,7 +3215,7 @@ g_win32_app_info_finalize (GObject *object)
 
   info = G_WIN32_APP_INFO (object);
 
-  g_clear_pointer (&info->supported_types, g_free);
+  g_clear_pointer (&info->supported_types, g_strfreev);
   g_clear_object (&info->app);
   g_clear_object (&info->handler);
 
@@ -3392,7 +3270,7 @@ g_win32_app_info_new_from_app (GWin32AppInfoApplication *app,
       if (!ext)
         continue;
 
-      new_info->supported_types[i] = (gchar *) ext;
+      new_info->supported_types[i] = g_strdup ((gchar *) ext);
       i += 1;
     }
 
@@ -3444,30 +3322,41 @@ static gboolean
 g_win32_app_info_equal (GAppInfo *appinfo1,
                         GAppInfo *appinfo2)
 {
+  GWin32AppInfoShellVerb *shverb1 = NULL;
+  GWin32AppInfoShellVerb *shverb2 = NULL;
   GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1);
   GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2);
+  GWin32AppInfoApplication *app1 = info1->app;
+  GWin32AppInfoApplication *app2 = info2->app;
 
-  if (info1->app == NULL ||
-      info2->app == NULL)
+  if (app1 == NULL ||
+      app2 == 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 (app1->canonical_name_folded != NULL &&
+      app2->canonical_name_folded != NULL)
+    return (g_strcmp0 (app1->canonical_name_folded,
+                       app2->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;
+  if (app1->verbs->len > 0 &&
+      app2->verbs->len > 0)
+    {
+      shverb1 = _verb_idx (app1->verbs, 0);
+      shverb2 = _verb_idx (app2->verbs, 0);
+      if (shverb1->executable_folded != NULL &&
+          shverb2->executable_folded != NULL)
+        return (g_strcmp0 (shverb1->executable_folded,
+                           shverb2->executable_folded)) == 0;
+    }
 
-  return info1->app == info2->app;
+  return app1 == app2;
 }
 
 static const char *
 g_win32_app_info_get_id (GAppInfo *appinfo)
 {
   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
+  GWin32AppInfoShellVerb *shverb;
 
   if (info->app == NULL)
     return NULL;
@@ -3475,8 +3364,9 @@ g_win32_app_info_get_id (GAppInfo *appinfo)
   if (info->app->canonical_name_u8)
     return info->app->canonical_name_u8;
 
-  if (info->app->executable_basename)
-    return info->app->executable_basename;
+  if (info->app->verbs->len > 0 &&
+      (shverb = _verb_idx (info->app->verbs, 0))->executable_basename != NULL)
+    return shverb->executable_basename;
 
   return NULL;
 }
@@ -3527,7 +3417,10 @@ g_win32_app_info_get_executable (GAppInfo *appinfo)
   if (info->app == NULL)
     return NULL;
 
-  return info->app->executable;
+  if (info->app->verbs->len > 0)
+    return _verb_idx (info->app->verbs, 0)->executable;
+
+  return NULL;
 }
 
 static const char *
@@ -3538,7 +3431,10 @@ g_win32_app_info_get_commandline (GAppInfo *appinfo)
   if (info->app == NULL)
     return NULL;
 
-  return info->app->command_u8;
+  if (info->app->verbs->len > 0)
+    return _verb_idx (info->app->verbs, 0)->command_utf8;
+
+  return NULL;
 }
 
 static GIcon *
@@ -3842,14 +3738,6 @@ expand_application_parameters (GWin32AppInfo   *info,
   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;
 
@@ -3897,42 +3785,44 @@ expand_application_parameters (GWin32AppInfo   *info,
 
 
 static gchar *
-get_appath_for_exe (gunichar2 *exe_basename)
+get_appath_for_exe (const gchar *exe_basename)
 {
   GWin32RegistryKey *apppath_key = NULL;
   GWin32RegistryValueType val_type;
-  gunichar2 *appath = NULL;
+  gchar *appath = NULL;
   gboolean got_value;
-  gchar *result = NULL;
+  gchar *key_path = g_strdup_printf ("HKEY_LOCAL_MACHINE\\"
+                                     "SOFTWARE\\"
+                                     "Microsoft\\"
+                                     "Windows\\"
+                                     "CurrentVersion\\"
+                                     "App Paths\\"
+                                     "%s", exe_basename);
 
-  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);
+  apppath_key = g_win32_registry_key_new (key_path, NULL);
+  g_clear_pointer (&key_path, g_free);
 
   if (apppath_key == NULL)
     return NULL;
 
-  got_value = g_win32_registry_key_get_value_w (apppath_key,
-                                                NULL,
-                                                TRUE,
-                                                L"Path",
-                                                &val_type,
-                                                (void **) &appath,
-                                                NULL,
-                                                NULL);
+  got_value = g_win32_registry_key_get_value (apppath_key,
+                                              NULL,
+                                              TRUE,
+                                              "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);
+  if (got_value &&
+      val_type == G_WIN32_REGISTRY_VALUE_STR)
+    return appath;
 
   g_clear_pointer (&appath, g_free);
 
-  return result;
+  return appath;
 }
 
 
@@ -3946,9 +3836,9 @@ g_win32_app_info_launch_internal (GWin32AppInfo      *info,
   gboolean completed = FALSE;
   char **argv, **envp;
   int argc;
-  gchar *command;
+  const gchar *command;
   gchar *apppath;
-  gunichar2 *exe_basename;
+  GWin32AppInfoShellVerb *shverb;
 
   g_return_val_if_fail (info != NULL, FALSE);
   g_return_val_if_fail (info->app != NULL, FALSE);
@@ -3960,52 +3850,32 @@ g_win32_app_info_launch_internal (GWin32AppInfo      *info,
   else
     envp = g_get_environ ();
 
-  command = NULL;
-  exe_basename = NULL;
+  shverb = 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 (info->handler != NULL &&
+      info->handler->verbs->len > 0)
+    shverb = _verb_idx (info->handler->verbs, 0);
+  else if (info->app->verbs->len > 0)
+    shverb = _verb_idx (info->app->verbs, 0);
 
-  if (command == NULL)
+  if (shverb == NULL)
     {
-      command = g_strdup (info->app->command_u8);
-      exe_basename = g_utf8_to_utf16 (info->app->executable_basename,
-                                      -1,
-                                      NULL,
-                                      NULL,
-                                      NULL);
-    }
+      if (info->handler == NULL)
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                     P_("The app ‘%s’ in the application object has no verbs"),
+                     g_win32_appinfo_application_get_some_name (info->app));
+      else if (info->handler->verbs->len == 0)
+        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                     P_("The app ‘%s’ and the handler ‘%s’ in the application object have no verbs"),
+                     g_win32_appinfo_application_get_some_name (info->app),
+                     info->handler->handler_id_folded);
 
-  apppath = get_appath_for_exe (exe_basename);
+      return FALSE;
+    }
 
-  g_free (exe_basename);
+  g_assert (shverb->command_utf8 != NULL);
+  command = shverb->command_utf8;
+  apppath = get_appath_for_exe (shverb->executable_basename);
 
   if (apppath)
     {
@@ -4095,7 +3965,6 @@ g_win32_app_info_launch_internal (GWin32AppInfo      *info,
  out:
   g_strfreev (argv);
   g_strfreev (envp);
-  g_free (command);
 
   return completed;
 }
@@ -4266,7 +4135,9 @@ g_app_info_create_from_commandline (const char           *commandline,
   info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
   app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
 
-  app->command = g_steal_pointer (&app_command);
+  app->no_open_with = FALSE;
+  app->user_specific = FALSE;
+  app->default_app = FALSE;
 
   if (application_name)
     {
@@ -4279,21 +4150,16 @@ g_app_info_create_from_commandline (const char           *commandline,
       app->canonical_name_folded = g_utf8_casefold (application_name, -1);
     }
 
-  _g_win32_extract_executable (app->command,
-                               &app->executable,
-                               &app->executable_basename,
-                               &app->executable_folded,
-                               NULL,
-                               &app->dll_function);
-  if (app->dll_function != NULL)
-    _g_win32_fixup_broken_microsoft_rundll_commandline (app->command);
-
-  app->command_u8 = g_utf16_to_utf8 (app->command, -1, NULL, NULL, NULL);
-
-  app->no_open_with = FALSE;
-  app->user_specific = FALSE;
-  app->default_app = FALSE;
+  app_add_verb (app,
+                app,
+                L"open",
+                app_command,
+                commandline,
+                "open",
+                TRUE,
+                FALSE);
 
+  g_clear_pointer (&app_command, g_free);
   info->app = app;
   info->handler = NULL;
 
@@ -4333,9 +4199,10 @@ g_win32_app_info_iface_init (GAppInfoIface *iface)
 GAppInfo *
 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
 {
-  GWin32AppInfoURLSchema *scheme;
+  GWin32AppInfoURLSchema *scheme = NULL;
   char *scheme_down;
   GAppInfo *result;
+  GWin32AppInfoShellVerb *shverb;
 
   scheme_down = g_utf8_casefold (uri_scheme, -1);
 
@@ -4345,30 +4212,28 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
   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_set_object (&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->verbs->len > 0 &&
+      (shverb = _verb_idx (scheme->chosen_handler->verbs, 0))->app != NULL)
+    result = g_win32_app_info_new_from_app (shverb->app,
                                             scheme->chosen_handler);
 
-  if (scheme)
-    g_object_unref (scheme);
+  g_clear_object (&scheme);
 
   return result;
 }
@@ -4377,12 +4242,10 @@ GAppInfo *
 g_app_info_get_default_for_type (const char *content_type,
                                  gboolean    must_support_uris)
 {
-  GWin32AppInfoFileExtension *ext;
+  GWin32AppInfoFileExtension *ext = NULL;
   char *ext_down;
-  GWin32AppInfoHandler *handler;
   GAppInfo *result;
-  GWin32AppInfoApplication *app;
-  GHashTableIter iter;
+  GWin32AppInfoShellVerb *shverb;
 
   ext_down = g_utf8_casefold (content_type, -1);
 
@@ -4393,56 +4256,47 @@ g_app_info_get_default_for_type (const char *content_type,
   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_set_object (&ext, g_hash_table_lookup (extensions, ext_down));
   g_free (ext_down);
 
-  result = NULL;
+  G_UNLOCK (gio_win32_appinfo);
 
-  if (ext != NULL)
-    g_object_ref (ext);
+  if (ext == NULL)
+    return NULL;
 
-  G_UNLOCK (gio_win32_appinfo);
+  result = NULL;
 
-  if (ext != NULL)
+  if (ext->chosen_handler != NULL &&
+      ext->chosen_handler->verbs->len > 0 &&
+      (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL &&
+      (!must_support_uris ||
+       g_win32_app_supports_uris (shverb->app)))
+    result = g_win32_app_info_new_from_app (shverb->app,
+                                            ext->chosen_handler);
+  else
     {
-      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
+      GHashTableIter iter;
+      GWin32AppInfoHandler *handler;
+
+      g_hash_table_iter_init (&iter, ext->handlers);
+
+      while (result == NULL &&
+             g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
         {
-          g_hash_table_iter_init (&iter, ext->handlers);
+          if (handler->verbs->len == 0)
+            continue;
 
-          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;
-                }
-            }
+          shverb = _verb_idx (handler->verbs, 0);
 
-          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;
-                    }
-                }
-            }
+          if (shverb->app &&
+              (!must_support_uris ||
+               g_win32_app_supports_uris (shverb->app)))
+            result = g_win32_app_info_new_from_app (shverb->app, handler);
         }
-      g_object_unref (ext);
     }
 
+  g_clear_object (&ext);
+
   return result;
 }
 
@@ -4478,12 +4332,13 @@ g_app_info_get_all (void)
 GList *
 g_app_info_get_all_for_type (const char *content_type)
 {
-  GWin32AppInfoFileExtension *ext;
+  GWin32AppInfoFileExtension *ext = NULL;
   char *ext_down;
   GWin32AppInfoHandler *handler;
-  GWin32AppInfoApplication *app;
   GHashTableIter iter;
+  GHashTable *apps = NULL;
   GList *result;
+  GWin32AppInfoShellVerb *shverb;
 
   ext_down = g_utf8_casefold (content_type, -1);
 
@@ -4494,46 +4349,52 @@ g_app_info_get_all_for_type (const char *content_type)
   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_set_object (&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;
 
+  result = NULL;
+  /* Used as a set to ensure uniqueness */
+  apps = g_hash_table_new (g_direct_hash, g_direct_equal);
+
   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));
+      ext->chosen_handler->verbs->len > 0 &&
+      (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL)
+    {
+      g_hash_table_add (apps, shverb->app);
+      result = g_list_prepend (result,
+                               g_win32_app_info_new_from_app (shverb->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));
-    }
+      gsize vi;
 
-  g_hash_table_iter_init (&iter, ext->other_apps);
+      for (vi = 0; vi < handler->verbs->len; vi++)
+        {
+          shverb = _verb_idx (handler->verbs, vi);
 
-  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app))
-    {
-      result = g_list_prepend (result, g_win32_app_info_new_from_app (app, NULL));
-    }
+          if (shverb->app == NULL ||
+              g_hash_table_contains (apps, shverb->app))
+            continue;
 
-  g_object_unref (ext);
+          g_hash_table_add (apps, shverb->app);
+          result = g_list_prepend (result,
+                                   g_win32_app_info_new_from_app (shverb->app,
+                                                                  handler));
+        }
+    }
 
+  g_clear_object (&ext);
   result = g_list_reverse (result);
+  g_hash_table_unref (apps);
 
   return result;
 }
diff --git a/gio/gwin32registrykey.c b/gio/gwin32registrykey.c
index 7b4905347..6b24fdd90 100644
--- a/gio/gwin32registrykey.c
+++ b/gio/gwin32registrykey.c
@@ -946,7 +946,7 @@ g_win32_registry_subkey_iter_next (GWin32RegistrySubkeyIter  *iter,
  **/
 gboolean
 g_win32_registry_subkey_iter_get_name_w (GWin32RegistrySubkeyIter  *iter,
-                                         gunichar2                **subkey_name,
+                                         const gunichar2          **subkey_name,
                                          gsize                     *subkey_name_len,
                                          GError                   **error)
 {
@@ -988,7 +988,7 @@ g_win32_registry_subkey_iter_get_name_w (GWin32RegistrySubkeyIter  *iter,
  **/
 gboolean
 g_win32_registry_subkey_iter_get_name (GWin32RegistrySubkeyIter  *iter,
-                                       gchar                    **subkey_name,
+                                       const gchar              **subkey_name,
                                        gsize                     *subkey_name_len,
                                        GError                   **error)
 {
diff --git a/gio/gwin32registrykey.h b/gio/gwin32registrykey.h
index 28b57a843..f92a10caf 100644
--- a/gio/gwin32registrykey.h
+++ b/gio/gwin32registrykey.h
@@ -191,12 +191,12 @@ gboolean         g_win32_registry_subkey_iter_next           (GWin32RegistrySubk
                                                               GError                        **error);
 GLIB_AVAILABLE_IN_2_46
 gboolean         g_win32_registry_subkey_iter_get_name       (GWin32RegistrySubkeyIter        *iter,
-                                                              gchar                          **subkey_name,
+                                                              const gchar                    **subkey_name,
                                                               gsize                           
*subkey_name_len,
                                                               GError                         **error);
 GLIB_AVAILABLE_IN_2_46
 gboolean         g_win32_registry_subkey_iter_get_name_w     (GWin32RegistrySubkeyIter        *iter,
-                                                              gunichar2                      **subkey_name,
+                                                              const gunichar2                **subkey_name,
                                                               gsize                           
*subkey_name_len,
                                                               GError                         **error);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]