From 106e78af97ef368d29d8037a6b8c10dcce64cbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Sun, 29 Dec 2019 11:35:49 +0000 Subject: [PATCH] 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 | 4039 +++++++++++++++++++-------------------- gio/gwin32registrykey.c | 4 +- gio/gwin32registrykey.h | 4 +- 3 files changed, 1954 insertions(+), 2093 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 +#include +#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\\). + * + * 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; - /* If handler_id class has no command, it might point to another class */ - gunichar2 *proxy_id; + /* Verbs that this handler supports */ + GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ +}; - /* Proxy registry key (HKCR\\proxy_id). Can be used to watch handler's proxy. */ - GWin32RegistryKey *proxy_key; +struct _GWin32AppInfoShellVerb { + GObject parent_instance; - /* shell/open/command default value for the class named by proxy_id */ - gunichar2 *proxy_command; + /* The verb that is used to invoke this handler. */ + gunichar2 *verb_name; - /* Executable of the program (for matching, in folded form; UTF-8) */ - gchar *executable_folded; + /* User-friendly (localized) verb name. */ + gchar *verb_displayname; + + /* 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++) + icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL); + + if (icon_key == NULL) + return; + + if (g_win32_registry_key_get_value (icon_key, + NULL, + TRUE, + "", + &default_type, + (gpointer *) &default_value, + NULL, + NULL)) { - GWin32RegistryKey *icon_key; + if (default_type == G_WIN32_REGISTRY_VALUE_STR && + default_value[0] != '\0') + *icon_out = g_themed_icon_new (default_value); - if (counter == 0) - key = proxy_key; - else - key = program_key; - - if (!key) - continue; - - icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL); - - if (icon_key != NULL) - { - GWin32RegistryValueType default_type; - gchar *default_value; - - if (g_win32_registry_key_get_value (icon_key, - 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); - - g_clear_pointer (&default_value, g_free); - } - - g_object_unref (icon_key); - } - - if (*icon_out) - break; + g_clear_pointer (&default_value, g_free); } + + g_object_unref (icon_key); +} + +static void +reg_verb_free (gpointer p) +{ + if (p == NULL) + return; + + g_free (((reg_verb *) p)->name); + g_free (((reg_verb *) p)->shellpath); + g_free (p); +} + +#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 (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 */ +/* 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 -g_utf16_validate (const gunichar2 *str, - glong len) +get_verbs (GWin32RegistryKey *program_id_key, + const reg_verb **preferred_verb, + GList **verbs, + const gunichar2 *verbname_prefix, + const gunichar2 *verbshell_prefix) { - gchar *tmp; + GWin32RegistrySubkeyIter iter; + GWin32RegistryKey *key; + GWin32RegistryValueType val_type; + gunichar2 *default_verb; + gsize verbshell_prefix_len; + gsize verbname_prefix_len; + GList *i; - if (str == NULL) + g_assert (program_id_key && verbs && preferred_verb); + + *verbs = NULL; + *preferred_verb = NULL; + + key = g_win32_registry_key_get_child_w (program_id_key, + verbshell_prefix, + NULL); + + if (key == NULL) return FALSE; - tmp = g_utf16_to_utf8 (str, len, NULL, NULL, NULL); + if (!g_win32_registry_subkey_iter_init (&iter, key, NULL)) + { + g_object_unref (key); - if (tmp == NULL) - return FALSE; + return FALSE; + } - g_free (tmp); + verbshell_prefix_len = g_utf16_len (verbshell_prefix); + verbname_prefix_len = g_utf16_len (verbname_prefix); + + 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; + + subkey = g_win32_registry_key_get_child_w (key, + name, + NULL); + + g_assert (subkey != NULL); + /* The key we're looking at is "/Shell/", + * 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. /Shell//Shell/). + * To handle that, create new, extended nameprefix and shellprefix, + * and call the function recursively. + * name prefix "" -> "\\" + * shell prefix "Shell" -> "Shell\\\\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); + } + + g_clear_object (&subkey); + + /* 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); + } + + g_win32_registry_subkey_iter_clear (&iter); + + if (*verbs == NULL) + { + g_object_unref (key); + + return FALSE; + } + + default_verb = NULL; + + 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); + + /* Only sort at the top level */ + if (verbname_prefix[0] == 0) + { + *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; } -/* Does a UTF-16 validity check on *proxy_command and/or *program_command. - * Fails if that check doesn't pass. +/* 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 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) +static void +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; + 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 (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) + { + 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 = get_schema_object (schema, + schema_u8, + schema_u8_folded); + + 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 ((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_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; + } + + if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) + { + 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; + } + + file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded); + + handler_rec = get_handler_object (handler_id_u8_folded, + handler_key, + handler_id); + + if (is_user_choice || file_extn->chosen_handler == NULL) + g_set_object (&file_extn->chosen_handler, handler_rec); + + g_hash_table_insert (file_extn->handlers, + g_strdup (handler_id_u8_folded), + g_object_ref (handler_rec)); + + if (app) + g_hash_table_insert (app->supported_exts, + g_strdup (file_extension_u8_folded), + g_object_ref (handler_rec)); + + 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); +} + +/* 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) { GWin32RegistryKey *key; GWin32RegistryValueType val_type; - gsize proxy_id_size; + gunichar2 *proxy_id; + gunichar2 *return_id; + gboolean got_value; + gchar *handler_id_u8; + gchar *handler_id_u8_folded; + g_assert (program_id); + + if (return_key) + *return_key = NULL; + + /* Check the proxy first */ + key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL); + + if (key == NULL) + return 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); + + return_id = NULL; + + if (proxy_id) + { + GWin32RegistryKey *proxy_key; + proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL); + + if (proxy_key) + { + if (return_key) + *return_key = g_steal_pointer (&proxy_key); + g_clear_object (&proxy_key); + + return_id = g_steal_pointer (&proxy_id); + } + + g_clear_pointer (&proxy_id, g_free); + } + + 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); + + return NULL; + } + + 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); + + if (return_id == NULL && return_key) + *return_key = g_steal_pointer (&key); + g_clear_object (&key); + + if (return_id == NULL) + return g_wcsdup (program_id, -1); + + 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 (program_id && program_command && proxy_id && proxy_command); + g_assert (handler != NULL); + g_assert (verbs != NULL); + g_assert (progid != NULL); - *program_command = NULL; - *proxy_id = NULL; - *proxy_command = NULL; - - if (program_key) - *program_key = NULL; - - if (proxy_key) - *proxy_key = NULL; - - - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, - SHELL_OPEN_COMMAND, NULL); - - if (key != NULL) + 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) + { + g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey", + path_to_progid, progid, verb->shellpath); + continue; + } + + command_value = NULL; got_value = g_win32_registry_key_get_value_w (key, NULL, TRUE, L"", &val_type, - (void **) program_command, + (void **) &command_value, NULL, NULL); - if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR) + g_clear_object (&key); + + if (!got_value || + val_type != G_WIN32_REGISTRY_VALUE_STR || + (command_value_utf8 = g_utf16_to_utf8 (command_value, + -1, + NULL, + NULL, + NULL)) == NULL) { - 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; + g_clear_pointer (&command_value, g_free); + continue; } - else if (got_value) - g_clear_pointer (program_command, g_free); - g_object_unref (key); + 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); + + if (verb_key) + { + gsize verb_displayname_len; + + 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); + + 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_clear_pointer (&verb_displayname, g_free); + + 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); + + 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_clear_pointer (&verb_displayname, g_free); + g_clear_object (&verb_key); + } + + 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); + + g_clear_pointer (&command_value, g_free); + g_clear_pointer (&command_value_utf8, g_free); + g_clear_pointer (&verb_displayname_u8, g_free); } - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, NULL); - - if (key == NULL) - return FALSE; - - got_value = g_win32_registry_key_get_value_w (key, - NULL, - TRUE, - L"", - &val_type, - (void **) proxy_id, - &proxy_id_size, - NULL); - g_object_unref (key); - - if (!got_value || - (val_type != G_WIN32_REGISTRY_VALUE_STR)) - { - g_clear_pointer (proxy_id, g_free); - - return FALSE; - } - - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, *proxy_id, - SHELL_OPEN_COMMAND, NULL); - - if (key == NULL) - { - g_clear_pointer (proxy_id, g_free); - - return FALSE; - } - - got_value = g_win32_registry_key_get_value_w (key, - NULL, - TRUE, - L"", - &val_type, - (void **) proxy_command, - NULL, - NULL); - - if (proxy_key) - *proxy_key = key; - else - g_object_unref (key); - - if (!got_value || - val_type != G_WIN32_REGISTRY_VALUE_STR || - ((program_id_u8 != NULL || program_id_folded != NULL) && - !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) || - !g_utf16_validate (*proxy_command, -1)) - { - g_clear_pointer (proxy_id, g_free); - g_clear_pointer (proxy_command, g_free); - if (proxy_key) - g_clear_object (proxy_key); - return FALSE; - } - - return TRUE; + g_list_free_full (verbs, reg_verb_free); } -static void -get_url_association (const gunichar2 *schema) +/* 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; - 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); + handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded); - if (user_choice == NULL) - return; + if (handler_rec != NULL) + return handler_rec; - if (!g_utf16_to_utf8_and_fold (schema, -1, &schema_u8, &schema_folded)) - { - g_object_unref (user_choice); - return; - } + 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); - schema_rec = g_hash_table_lookup (urls, schema_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; - } - - if (val_type != G_WIN32_REGISTRY_VALUE_STR) - { - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_object_unref (user_choice); - return; - } - - program_key = proxy_key = NULL; - program_command = proxy_id = proxy_command = NULL; - - if (!follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - { - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_object_unref (user_choice); - return; - } - - handler_rec = g_hash_table_lookup (handlers, program_id_folded); - - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, program_key, &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - if (schema_rec == NULL) - { - schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); - schema_rec->schema = g_wcsdup (schema, -1); - schema_rec->schema_u8 = g_strdup (schema_u8); - schema_rec->schema_folded = g_strdup (schema_folded); - schema_rec->chosen_handler = g_object_ref (handler_rec); - g_hash_table_insert (urls, g_strdup (schema_folded), schema_rec); - } - - if (schema_rec->chosen_handler == NULL) - schema_rec->chosen_handler = g_object_ref (handler_rec); - - handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers, - program_id_folded); - - if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec) - g_hash_table_insert (schema_rec->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - g_object_unref (user_choice); + return handler_rec; } static void -get_file_ext (const gunichar2 *ext) +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) { - GWin32AppInfoFileExtension *file_extn; - gboolean file_ext_known; - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_ext; - gchar *ext_u8; - gchar *ext_folded; - GWin32RegistryKey *user_choice; - GWin32RegistryKey *open_with_progids; - GWin32RegistryValueType val_type; - gsize program_id_size; - gboolean found_handler; - gunichar2 *program_id; - gunichar2 *proxy_id; - gchar *program_id_u8; - gchar *program_id_folded; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - gunichar2 *program_command; - gunichar2 *proxy_command; + GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1; + GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2; + GWin32AppInfoShellVerb *shverb; - open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, - ext, - OPEN_WITH_PROGIDS, - NULL); + _verb_lookup (handler_rec->verbs, verb, &shverb); - user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, ext, - USER_CHOICE, NULL); - - if (user_choice == NULL && open_with_progids == NULL) + if (shverb != NULL) return; - if (!g_utf16_to_utf8_and_fold (ext, -1, &ext_u8, &ext_folded)) - { - g_clear_object (&user_choice); - g_clear_object (&open_with_progids); - return; - } + 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); - file_extn = NULL; - file_ext_known = g_hash_table_lookup_extended (extensions, - ext_folded, - NULL, - (void **) &file_extn); + _g_win32_extract_executable (shverb->command, + &shverb->executable, + &shverb->executable_basename, + &shverb->executable_folded, + NULL, + &shverb->dll_function); - if (!file_ext_known) - file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); + if (shverb->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command); - found_handler = FALSE; - - if (user_choice != NULL) - { - if (g_win32_registry_key_get_value_w (user_choice, - NULL, - TRUE, - L"Progid", - &val_type, - (void **) &program_id, - &program_id_size, - NULL)) - { - program_key = proxy_key = NULL; - - if (val_type == G_WIN32_REGISTRY_VALUE_STR && - follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - { - handler_rec = g_hash_table_lookup (handlers, - program_id_folded); - - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, - NULL); - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - found_handler = TRUE; - - handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers, - program_id_folded); - - if (file_extn->chosen_handler == NULL) - { - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } - else if (handler_rec_in_ext == NULL) - { - if (file_extn->chosen_handler->handler_id_folded && - strcmp (file_extn->chosen_handler->handler_id_folded, - program_id_folded) != 0) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - } - - g_free (program_id); - } - - g_object_unref (user_choice); - } - - if (open_with_progids != NULL) - { - GWin32RegistryValueIter iter; - - if (g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) - { - gunichar2 *value_name; - gunichar2 *value_data; - gsize value_name_len; - gsize value_data_size; - GWin32RegistryValueType value_type; - - while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) - { - gsize value_name_size; - program_key = proxy_key = NULL; - - if ((!g_win32_registry_value_iter_get_value_type (&iter, - &value_type, - NULL)) || - ((val_type != G_WIN32_REGISTRY_VALUE_STR) && - (val_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) || - (!g_win32_registry_value_iter_get_name_w (&iter, &value_name, - &value_name_len, - NULL)) || - (value_name_len <= 0) || - (!g_win32_registry_value_iter_get_data_w (&iter, TRUE, - (void **) &value_data, - &value_data_size, - NULL)) || - (value_data_size < sizeof (gunichar2)) || - (value_data[0] == L'\0')) - continue; - - value_name_size = sizeof (gunichar2) * (value_name_len + 1); - - if (!follow_class_chain_to_handler (value_name, - value_name_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - continue; - - handler_rec = g_hash_table_lookup (handlers, - program_id_folded); - - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (value_name, value_name_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - found_handler = TRUE; - - handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers, - program_id_folded); - - if (handler_rec_in_ext == NULL) - { - if (file_extn->chosen_handler == NULL) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - else if (file_extn->chosen_handler->handler_id_folded && - strcmp (file_extn->chosen_handler->handler_id_folded, - program_id_folded) != 0) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - } - - g_win32_registry_value_iter_clear (&iter); - } - - g_object_unref (open_with_progids); - } - - if (!found_handler) - { - if (!file_ext_known) - g_object_unref (file_extn); - } - else if (!file_ext_known) - { - file_extn->extension = g_wcsdup (ext, -1); - file_extn->extension_u8 = g_strdup (ext_u8); - g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn); - } - - g_free (ext_u8); - g_free (ext_folded); + 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) + { + 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; + } + + 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); } -static void -read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolean default_app) +/* 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; - gunichar2 *app_key_path; - gunichar2 *canonical_name; - gchar *canonical_name_u8; - gchar *canonical_name_folded; - GWin32RegistryKey *appkey; + + 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 (const gunichar2 *app_key_path, + gboolean user_specific, + gboolean default_app) +{ + GWin32AppInfoApplication *app; + 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; + const reg_verb *preferred_verb; + GList *verbs = NULL; - app_key_path = g_wcsdup (input_app_key_path, -1); + appkey = NULL; + capabilities = NULL; - canonical_name = wcsrchr (app_key_path, L'\\'); - - if (canonical_name == 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); + 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); + return; } - canonical_name += 1; + app = get_app_object (apps_by_id, + app_key_path, + canonical_name_u8, + canonical_name_folded, + user_specific, + default_app); - 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); - - 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); - } + 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); + 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); - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (schema_handler, schema_handler_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = program_command ? - g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - if (g_utf16_to_utf8_and_fold (url_schema, - -1, - &schema_u8, - &schema_folded)) - { - schema = g_hash_table_lookup (urls, - schema_folded); - - if (schema == NULL) - { - schema = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); - - schema->schema = g_wcsdup (url_schema, -1); - schema->schema_u8 = g_strdup (schema_u8); - schema->schema_folded = - g_strdup (schema_folded); - g_hash_table_insert (urls, - g_strdup (schema_folded), - schema); - } - - handler_rec_in_url = - g_hash_table_lookup (schema->handlers, - program_id_folded); - - if (handler_rec_in_url == NULL) - g_hash_table_insert (schema->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - - handler_rec_in_url = - g_hash_table_lookup (app->supported_urls, - schema_folded); - - if (handler_rec_in_url == NULL) - g_hash_table_insert (app->supported_urls, - g_strdup (schema_folded), - g_object_ref (handler_rec)); - - g_free (schema_u8); - g_free (schema_folded); - } - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); + g_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,18 +2245,194 @@ 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; + 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; - get_url_association (url_schema); + 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_incapable_app (GWin32RegistryKey *incapable_app, + const gunichar2 *app_exe_basename, + const gchar *app_exe_basename_u8, + const gchar *app_exe_basename_u8_folded) +{ + 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 (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell")) + return; + + 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"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 = g_win32_registry_key_get_value_w (incapable_app, + NULL, + TRUE, + L"NoOpenWith", + &vtype, + NULL, + NULL, + NULL); + + 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, + TRUE, + L"", + &vtype, + (void **) &icon_source, + NULL, + NULL); + + if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&icon_source, g_free); + + g_object_unref (default_icon_key); + } + + if (icon_source) + { + gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); + if (name != NULL) + icon = g_themed_icon_new (name); + g_free (name); + } + + app->no_open_with = no_open_with; + + 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); + } + + if (icon && app->icon == NULL) + app->icon = g_object_ref (icon); + + supported_key = + g_win32_registry_key_get_child_w (incapable_app, + L"SupportedTypes", + NULL); + + if (supported_key && + g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL)) + { + gunichar2 *ext_name; + gsize ext_name_len; + + while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL)) + { + 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; + + get_file_ext (ext_name, ext_name, app, FALSE); + } + + g_win32_registry_value_iter_clear (&sup_iter); + } + + g_clear_object (&supported_key); + g_free (friendly_app_name); + g_free (icon_source); + + g_clear_object (&icon); +} + +/* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications + * and calls read_incapable_app() for each one. + */ static void read_exeapps (void) { @@ -2101,30 +2453,20 @@ read_exeapps (void) while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL)) { - gunichar2 *app_exe_basename; + const 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; + 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_validate (app_exe_basename, app_exe_basename_len)) + !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 = @@ -2132,239 +2474,30 @@ read_exeapps (void) app_exe_basename, NULL); - if (incapable_app == NULL) - continue; + if (incapable_app != NULL) + read_incapable_app (incapable_app, + app_exe_basename, + app_exe_basename_u8, + app_exe_basename_u8_folded); - _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, - TRUE, - L"", - &vtype, - (void **) &icon_source, - NULL, - NULL); - - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) - g_clear_pointer (&icon_source, g_free); - - g_object_unref (default_icon_key); - } - - if (icon_source) - { - gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); - icon = g_themed_icon_new (name); - g_free (name); - } - - app = g_hash_table_lookup (apps_by_exe, appexe_folded_basename); - - if (app == NULL) - { - app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); - - if (shell_open_command) - { - gchar *dll_function; - - _g_win32_extract_executable (shell_open_command, - NULL, - NULL, - NULL, - NULL, - &dll_function); - if (dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command); - g_clear_pointer (&dll_function, g_free); - } - - app->command = - shell_open_command ? g_wcsdup (shell_open_command, -1) : NULL; - - if (shell_open_command) - app->command_u8 = g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL); - - app->executable = g_strdup (appexe); - app->executable_basename = &app->executable[appexe_basename - appexe]; - app->executable_folded = g_strdup (appexe_folded); - - app->no_open_with = no_open_with; - - if (friendly_app_name) - { - app->localized_pretty_name = g_wcsdup (friendly_app_name, -1); - g_clear_pointer (&app->localized_pretty_name_u8, g_free); - app->localized_pretty_name_u8 = - g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL); - } - - if (icon) - app->icon = g_object_ref (icon); - - app->user_specific = FALSE; - app->default_app = FALSE; - - g_hash_table_insert (apps_by_exe, - g_strdup (appexe_folded_basename), - app); - } - - supported_key = - g_win32_registry_key_get_child_w (incapable_app, - L"SupportedTypes", - NULL); - - if (supported_key) - { - GWin32RegistryValueIter sup_iter; - if (g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL)) - { - gunichar2 *ext_name; - gsize ext_name_len; - - while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL)) - { - gchar *ext_u8; - gchar *ext_folded; - GWin32AppInfoFileExtension *file_extn; - gboolean file_ext_known; - - if ((!g_win32_registry_value_iter_get_name_w (&sup_iter, - &ext_name, - &ext_name_len, - NULL)) || - (ext_name_len <= 0) || - (ext_name[0] != L'.') || - (!g_utf16_to_utf8_and_fold (ext_name, - -1, - &ext_u8, - &ext_folded))) - continue; - - file_extn = NULL; - file_ext_known = - g_hash_table_lookup_extended (extensions, - ext_folded, - NULL, - (void **) &file_extn); - - if (!file_ext_known) - { - file_extn = - g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); - file_extn->extension = g_wcsdup (ext_name, -1); - file_extn->extension_u8 = g_strdup (ext_u8); - g_hash_table_insert (extensions, - g_strdup (ext_folded), - file_extn); - } - - g_hash_table_insert (file_extn->other_apps, - g_strdup (appexe_folded), - g_object_ref (app)); - - g_free (ext_u8); - g_free (ext_folded); - } - - g_win32_registry_value_iter_clear (&sup_iter); - } - - g_object_unref (supported_key); - } - - - g_free (appexe); - g_free (appexe_folded); - g_free (shell_open_command); - g_free (friendly_app_name); - g_free (icon_source); - - g_clear_object (&icon); g_clear_object (&incapable_app); + g_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,263 +2508,89 @@ 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); + 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); + } + + g_clear_object (&user_choice); + g_clear_pointer (&program_id, g_free); + + open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, + file_extension, + OPEN_WITH_PROGIDS, + NULL); + + if (open_with_progids == NULL) + continue; + + if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) + { + g_clear_object (&open_with_progids); + continue; + } + + 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; + + 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 (&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); -} - +/* 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 read_classes (GWin32RegistryKey *classes_root) { GWin32RegistrySubkeyIter class_iter; - gunichar2 *class_name; + const gunichar2 *class_name; gsize class_name_len; if (classes_root == NULL) @@ -2650,409 +2609,326 @@ read_classes (GWin32RegistryKey *classes_root) continue; if (class_name[0] == L'.') - read_class_extension (classes_root, class_name, class_name_len); + { + GWin32RegistryKey *class_key; + GWin32RegistryValueIter iter; + GWin32RegistryKey *open_with_progids; + gunichar2 *value_name; + gsize value_name_len; + + /* Read the data from the HKCR\\.ext (usually proxied + * to another HKCR subkey) + */ + get_file_ext (class_name, class_name, NULL, FALSE); + + class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); + + if (class_key == NULL) + continue; + + open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL); + g_clear_object (&class_key); + + if (open_with_progids == NULL) + continue; + + if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) + { + g_clear_object (&open_with_progids); + continue; + } + + /* Read the data for other handlers for this extension */ + 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; + + get_file_ext (value_name, class_name, NULL, FALSE); + } + + 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; for (i = 0; i < class_name_len; i++) if (!iswalpha (class_name[i])) break; - if (i == class_name_len) - read_class_url (classes_root, class_name, class_name_len); + if (i != class_name_len) + continue; + + class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); + + if (class_key == NULL) + continue; + + success = g_win32_registry_key_get_value_w (class_key, + NULL, + TRUE, + L"URL Protocol", + &vtype, + NULL, + NULL, + NULL); + g_clear_object (&class_key); + + if (!success || + vtype != G_WIN32_REGISTRY_VALUE_STR) + continue; + + if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded)) + continue; + + get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE); + + g_clear_pointer (&schema_u8, g_free); + g_clear_pointer (&schema_u8_folded, g_free); } } g_win32_registry_subkey_iter_clear (&class_iter); } -static void -link_chosen_handlers (void) -{ - GHashTableIter iter; - GHashTableIter handler_iter; - gchar *schema_folded; - GWin32AppInfoURLSchema *schema; - gchar *handler_id_folded; - GWin32AppInfoHandler *handler; - gchar *ext_folded; - GWin32AppInfoFileExtension *ext; - - g_hash_table_iter_init (&iter, urls); - - while (g_hash_table_iter_next (&iter, - (gpointer *) &schema_folded, - (gpointer *) &schema)) - { - if (schema->chosen_handler != NULL) - continue; - - g_hash_table_iter_init (&handler_iter, schema->handlers); - - while (g_hash_table_iter_next (&handler_iter, - (gpointer *) &handler_id_folded, - (gpointer *) &handler)) - { - gchar *proxy_id_folded; - - if (schema->chosen_handler != NULL) - break; - - if (strcmp (handler_id_folded, schema_folded) != 0) - continue; - - if (handler->proxy_command && - handler->proxy_id && - g_utf16_to_utf8_and_fold (handler->proxy_id, - -1, - NULL, - &proxy_id_folded)) - { - GWin32AppInfoHandler *proxy; - - proxy = g_hash_table_lookup (handlers, proxy_id_folded); - - if (proxy) - { - schema->chosen_handler = g_object_ref (proxy); - g_debug ("Linking schema %s to proxy handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - - g_free (proxy_id_folded); - } - - if (schema->chosen_handler == NULL) - { - schema->chosen_handler = g_object_ref (handler); - g_debug ("Linking schema %s to handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - } - } - - g_hash_table_iter_init (&iter, extensions); - - while (g_hash_table_iter_next (&iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler != NULL) - continue; - - g_hash_table_iter_init (&handler_iter, ext->handlers); - - while (g_hash_table_iter_next (&handler_iter, - (gpointer *) &handler_id_folded, - (gpointer *) &handler)) - { - gchar *proxy_id_folded; - - if (ext->chosen_handler != NULL) - break; - - if (strcmp (handler_id_folded, ext_folded) != 0) - continue; - - if (handler->proxy_command && - handler->proxy_id && - g_utf16_to_utf8_and_fold (handler->proxy_id, - -1, - NULL, - &proxy_id_folded)) - { - GWin32AppInfoHandler *proxy; - - proxy = g_hash_table_lookup (handlers, proxy_id_folded); - - if (proxy) - { - ext->chosen_handler = g_object_ref (proxy); - g_debug ("Linking ext %s to proxy handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); - } - - g_free (proxy_id_folded); - } - - if (ext->chosen_handler == NULL) - { - ext->chosen_handler = g_object_ref (handler); - g_debug ("Linking ext %s to handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); - } - } - } -} - -static void -link_handlers_to_registered_apps (void) -{ - GHashTableIter iter; - GHashTableIter sup_iter; - gchar *app_id_folded; - GWin32AppInfoApplication *app; - gchar *schema_folded; - GWin32AppInfoURLSchema *schema; - gchar *ext_folded; - GWin32AppInfoFileExtension *ext; - gsize unhandled_exts; - - g_hash_table_iter_init (&sup_iter, urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &schema)) - { - if (schema->chosen_handler == NULL) - g_debug ("WARNING: schema %s has no chosen handler\n", schema->schema_u8); - } - unhandled_exts= 0; - g_hash_table_iter_init (&sup_iter, extensions); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler == NULL) - { - g_debug ("WARNING: extension %s has no chosen handler\n", - ext->extension_u8); - unhandled_exts += 1; - } - } - - g_hash_table_iter_init (&iter, apps_by_id); - while (g_hash_table_iter_next (&iter, - (gpointer *) &app_id_folded, - (gpointer *) &app)) - { - if (app->supported_urls) - { - GWin32AppInfoHandler *handler; - - g_hash_table_iter_init (&sup_iter, app->supported_urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &handler)) - { - schema = g_hash_table_lookup (urls, schema_folded); - - g_assert (schema != NULL); - - if (schema->chosen_handler != NULL && - schema->chosen_handler->app == NULL) - { - schema->chosen_handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n to schema %s handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - } - - g_hash_table_iter_init (&sup_iter, app->supported_urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &handler)) - { - if (handler->app == NULL) - { - handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n directly to schema handler to %c ? \"%S\" : %S\n", - handler->proxy_id ? 'P' : 'T', - handler->proxy_id ? handler->proxy_id : handler->handler_id, - handler->proxy_command ? handler->proxy_command : handler->handler_command); - } - } - } - - if (app->supported_exts) - { - GWin32AppInfoHandler *handler; - - g_hash_table_iter_init (&sup_iter, app->supported_exts); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &handler)) - { - ext = g_hash_table_lookup (extensions, ext_folded); - - g_assert (ext != NULL); - - if (ext->chosen_handler != NULL && - ext->chosen_handler->app == NULL) - { - ext->chosen_handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n to ext %s handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); - } - } - - g_hash_table_iter_init (&sup_iter, app->supported_exts); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &handler)) - { - if (handler->app == NULL) - { - handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n directly to ext handler %c ? \"%S\" : %S\n", - handler->proxy_id ? 'P' : 'T', - handler->proxy_id ? handler->proxy_id : handler->handler_id, - handler->proxy_command ? handler->proxy_command : handler->handler_command); - } - } - } - } - - g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts); - unhandled_exts= 0; - g_hash_table_iter_init (&sup_iter, extensions); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler == NULL) - { - g_debug ("WARNING: extension %s has no chosen handler\n", - ext->extension_u8); - unhandled_exts += 1; - } - } - g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts); -} - +/* 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_unregistered_apps (void) { GHashTableIter iter; GHashTableIter app_iter; GWin32AppInfoHandler *handler; - gchar *handler_id_fc; + gchar *handler_id_fld; GWin32AppInfoApplication *app; - gchar *canonical_name_fc; - gchar *appexe_fc_basename; + gchar *canonical_name_fld; + gchar *appexe_fld_basename; g_hash_table_iter_init (&iter, handlers); while (g_hash_table_iter_next (&iter, - (gpointer *) &handler_id_fc, + (gpointer *) &handler_id_fld, (gpointer *) &handler)) { - gchar *hndexe_fc_basename; + gsize vi; - 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)) + for (vi = 0; vi < handler->verbs->len; vi++) { - if (app->executable_folded == NULL) + GWin32AppInfoShellVerb *handler_verb; + const gchar *handler_exe_basename; + enum + { + SH_UNKNOWN, + GOT_SH_INFO, + ERROR_GETTING_SH_INFO, + } have_stat_handler = SH_UNKNOWN; + GWin32PrivateStat handler_verb_exec_info; + + handler_verb = _verb_idx (handler->verbs, vi); + + if (handler_verb->app != NULL) continue; - if (strcmp (app->executable_folded, - handler->executable_folded) != 0) + handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1); + g_hash_table_iter_init (&app_iter, apps_by_id); + + while (g_hash_table_iter_next (&app_iter, + (gpointer *) &canonical_name_fld, + (gpointer *) &app)) + { + GWin32AppInfoShellVerb *app_verb; + gsize ai; + + 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); + + app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1); + + /* 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; + } + + handler_verb->app = g_object_ref (app); + break; + } + } + + if (handler_verb->app != NULL) continue; - handler->app = app; - break; + g_hash_table_iter_init (&app_iter, apps_by_exe); + + while (g_hash_table_iter_next (&app_iter, + (gpointer *) &appexe_fld_basename, + (gpointer *) &app)) + { + /* Use basename because apps_by_exe only has basenames */ + if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0) + continue; + + handler_verb->app = g_object_ref (app); + break; + } } + } +} - if (handler->app != NULL) - continue; +/* 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_fake_apps (void) +{ + GHashTableIter iter; + GHashTableIter handler_iter; + gchar *extension_utf8_folded; + GWin32AppInfoFileExtension *file_extn; + gchar *handler_id_fld; + GWin32AppInfoHandler *handler; + gchar *url_utf8_folded; + GWin32AppInfoURLSchema *schema; - hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1); - - if (hndexe_fc_basename == NULL) - continue; - - g_hash_table_iter_init (&app_iter, apps_by_exe); - - while ((hndexe_fc_basename != NULL) && - (g_hash_table_iter_next (&app_iter, - (gpointer *) &appexe_fc_basename, - (gpointer *) &app))) + g_hash_table_iter_init (&iter, extensions); + while (g_hash_table_iter_next (&iter, + (gpointer *) &extension_utf8_folded, + (gpointer *) &file_extn)) + { + g_hash_table_iter_init (&handler_iter, file_extn->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; + gunichar2 *exename_utf16; + handler_verb = _verb_idx (handler->verbs, vi); + + if (handler_verb->app != NULL) + continue; + + exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL); + if (exename_utf16 == NULL) + continue; + + 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)); + } } + } - g_free (hndexe_fc_basename); + 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)) + { + gsize vi; - if (handler->app == NULL) - g_debug ("WARNING: handler that runs %s has no corresponding app\n", - handler->executable); + for (vi = 0; vi < handler->verbs->len; vi++) + { + GWin32AppInfoShellVerb *handler_verb; + GWin32AppInfoApplication *app; + gchar *command_utf8_folded; + handler_verb = _verb_idx (handler->verbs, vi); + + if (handler_verb->app != NULL) + continue; + + 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 != 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 (shverb == NULL) { - 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) + 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); + + return FALSE; } - if (command == NULL) - { - command = g_strdup (info->app->command_u8); - exe_basename = g_utf8_to_utf16 (info->app->executable_basename, - -1, - NULL, - NULL, - NULL); - } - - apppath = get_appath_for_exe (exe_basename); - - g_free (exe_basename); + 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; - - if (ext != NULL) - g_object_ref (ext); - G_UNLOCK (gio_win32_appinfo); - if (ext != NULL) + if (ext == NULL) + return NULL; + + result = 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)) + gsize vi; + + for (vi = 0; vi < handler->verbs->len; vi++) + { + shverb = _verb_idx (handler->verbs, vi); + + if (shverb->app == NULL || + g_hash_table_contains (apps, shverb->app)) + continue; + + g_hash_table_add (apps, shverb->app); result = g_list_prepend (result, - g_win32_app_info_new_from_app (handler->app, + g_win32_app_info_new_from_app (shverb->app, handler)); + } } - g_hash_table_iter_init (&iter, ext->other_apps); - - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) - { - result = g_list_prepend (result, g_win32_app_info_new_from_app (app, NULL)); - } - - g_object_unref (ext); - + 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);