| /* GIO - GLib Input, Output and Streaming Library |
| * |
| * Copyright (C) 2006-2007 Red Hat, Inc. |
| * Copyright (C) 2014 Руслан Ижбулатов |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General |
| * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| * |
| * Authors: Alexander Larsson <alexl@redhat.com> |
| * Руслан Ижбулатов <lrn1986@gmail.com> |
| */ |
| |
| #include "config.h" |
| |
| #define COBJMACROS |
| |
| #include <string.h> |
| |
| #include "gcontenttype.h" |
| #include "gwin32appinfo.h" |
| #include "gappinfo.h" |
| #include "gioerror.h" |
| #include "gfile.h" |
| #include <glib/gstdio.h> |
| #include "glibintl.h" |
| #include <gio/gwin32registrykey.h> |
| #include <shlobj.h> |
| /* Contains the definitions from shlobj.h that are |
| * guarded as Windows8-or-newer and are unavailable |
| * to GLib, being only Windows7-or-newer. |
| */ |
| #include "gwin32api-application-activation-manager.h" |
| |
| #include <windows.h> |
| /* For SHLoadIndirectString() */ |
| #include <shlwapi.h> |
| |
| #include <glib/gstdioprivate.h> |
| #include "giowin32-priv.h" |
| #include "glib-private.h" |
| |
| /* We need to watch 8 places: |
| * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations |
| * (anything below that key) |
| * On change: re-enumerate subkeys, read their values. |
| * 1) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts |
| * (anything below that key) |
| * On change: re-enumerate subkeys |
| * 2) HKEY_CURRENT_USER\\Software\\Clients (anything below that key) |
| * On change: re-read the whole hierarchy of handlers |
| * 3) HKEY_LOCAL_MACHINE\\Software\\Clients (anything below that key) |
| * On change: re-read the whole hierarchy of handlers |
| * 4) HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications (values of that key) |
| * On change: re-read the value list of registered applications |
| * 5) HKEY_CURRENT_USER\\Software\\RegisteredApplications (values of that key) |
| * On change: re-read the value list of registered applications |
| * 6) HKEY_CLASSES_ROOT\\Applications (anything below that key) |
| * On change: re-read the whole hierarchy of apps |
| * 7) HKEY_CLASSES_ROOT (only its subkeys) |
| * On change: re-enumerate subkeys, try to filter out wrong names. |
| * |
| * |
| * About verbs. A registry key (the name of that key is known as ProgID) |
| * can contain a "shell" subkey, which can then contain a number of verb |
| * subkeys (the most common being the "open" verb), and each of these |
| * contains a "command" subkey, which has a default string value that |
| * is the command to be run. |
| * Most ProgIDs are in HKEY_CLASSES_ROOT, but some are nested deeper in |
| * the registry (such as HKEY_CURRENT_USER\\Software\\<softwarename>). |
| * |
| * Verb selection works like this (according to https://docs.microsoft.com/en-us/windows/win32/shell/context ): |
| * 1) If "open" verb is available, that verb is used. |
| * 2) If the Shell subkey has a default string value, and if a verb subkey |
| * with that name exists, that verb is used. |
| * 3) The first subkey found in the list of verb subkeys is used. |
| * 4) The "openwith" verb is used |
| * |
| * Testing suggests that Windows never reaches the point 4 in any realistic |
| * circumstances. If a "command" subkey is missing for a verb, or if it has |
| * an empty string as its default value, the app launch fails |
| * (the "openwith" verb is not used, even if it's present). |
| * If the command is present, but is not valid (runs nonexisting executable, |
| * for example), then other verbs are not checked. |
| * It seems that when the documentation said "openwith verb", it meant |
| * that Windows invokes the default "Open with..." dialog (it does not |
| * look at the "openwith" verb subkey, even if it's there). |
| * If a verb subkey that is supposed to be used is present, but it lacks |
| * a command subkey, an error message is shown and nothing else happens. |
| */ |
| |
| #define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index)) |
| |
| #define _lookup_by_verb(array, verb, dst, itemtype) do { \ |
| gsize _index; \ |
| itemtype *_v; \ |
| for (_index = 0; array && _index < array->len; _index++) \ |
| { \ |
| _v = (itemtype *) g_ptr_array_index (array, _index); \ |
| if (_wcsicmp (_v->verb_name, (verb)) == 0) \ |
| { \ |
| *(dst) = _v; \ |
| break; \ |
| } \ |
| } \ |
| if (array == NULL || _index >= array->len) \ |
| *(dst) = NULL; \ |
| } while (0) |
| |
| #define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb) |
| |
| /* Because with subcommands a verb would have |
| * a name like "foo\\bar", but the key its command |
| * should be looked for is "shell\\foo\\shell\\bar\\command" |
| */ |
| typedef struct _reg_verb { |
| gunichar2 *name; |
| gunichar2 *shellpath; |
| } reg_verb; |
| |
| typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema; |
| typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension; |
| typedef struct _GWin32AppInfoShellVerb GWin32AppInfoShellVerb; |
| typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler; |
| typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication; |
| |
| typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass; |
| typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass; |
| typedef struct _GWin32AppInfoShellVerbClass GWin32AppInfoShellVerbClass; |
| typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass; |
| typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass; |
| |
| struct _GWin32AppInfoURLSchemaClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GWin32AppInfoFileExtensionClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GWin32AppInfoHandlerClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GWin32AppInfoApplicationClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GWin32AppInfoShellVerbClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| struct _GWin32AppInfoURLSchema { |
| GObject parent_instance; |
| |
| /* url schema (stuff before ':') */ |
| gunichar2 *schema; |
| |
| /* url schema (stuff before ':'), in UTF-8 */ |
| gchar *schema_u8; |
| |
| /* url schema (stuff before ':'), in UTF-8, folded */ |
| gchar *schema_u8_folded; |
| |
| /* Handler currently selected for this schema. Can be NULL. */ |
| GWin32AppInfoHandler *chosen_handler; |
| |
| /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema. |
| * Includes the chosen handler, if any. |
| */ |
| GHashTable *handlers; |
| }; |
| |
| struct _GWin32AppInfoHandler { |
| GObject parent_instance; |
| |
| /* Usually a class name in HKCR */ |
| gunichar2 *handler_id; |
| |
| /* Registry object obtained by opening @handler_id. |
| * Can be used to watch this handler. |
| * May be %NULL (for fake handlers that we made up). |
| */ |
| GWin32RegistryKey *key; |
| |
| /* @handler_id, in UTF-8, folded */ |
| gchar *handler_id_folded; |
| |
| /* Icon of the application for this handler */ |
| GIcon *icon; |
| |
| /* Verbs that this handler supports */ |
| GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ |
| |
| /* AppUserModelID for a UWP application. When this is not NULL, |
| * this handler launches a UWP application. |
| * UWP applications are launched using a COM interface and have no commandlines, |
| * and the verbs will reflect that too. |
| */ |
| gunichar2 *uwp_aumid; |
| }; |
| |
| struct _GWin32AppInfoShellVerb { |
| GObject parent_instance; |
| |
| /* The verb that is used to invoke this handler. */ |
| gunichar2 *verb_name; |
| |
| /* User-friendly (localized) verb name. */ |
| gchar *verb_displayname; |
| |
| /* %TRUE if this verb is for a UWP app. |
| * It means that @command, @executable and @dll_function are %NULL. |
| */ |
| gboolean is_uwp; |
| |
| /* 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; |
| |
| /* 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; |
| |
| /* The application that is linked to this verb. */ |
| GWin32AppInfoApplication *app; |
| }; |
| |
| struct _GWin32AppInfoFileExtension { |
| GObject parent_instance; |
| |
| /* File extension (with leading '.') */ |
| gunichar2 *extension; |
| |
| /* File extension (with leading '.'), in UTF-8 */ |
| gchar *extension_u8; |
| |
| /* handler currently selected for this extension. Can be NULL. */ |
| GWin32AppInfoHandler *chosen_handler; |
| |
| /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension. |
| * Includes the chosen handler, if any. |
| */ |
| GHashTable *handlers; |
| }; |
| |
| struct _GWin32AppInfoApplication { |
| GObject parent_instance; |
| |
| /* 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 UWP apps this is the AppUserModelID. |
| * 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, in UTF-8 */ |
| gchar *canonical_name_u8; |
| |
| /* @canonical_name, in UTF-8, folded */ |
| gchar *canonical_name_folded; |
| |
| /* Human-readable name in English. Can be NULL */ |
| gunichar2 *pretty_name; |
| |
| /* Human-readable name in English, UTF-8. Can be NULL */ |
| gchar *pretty_name_u8; |
| |
| /* Human-readable name in user's language. Can be NULL */ |
| gunichar2 *localized_pretty_name; |
| |
| /* Human-readable name in user's language, UTF-8. Can be NULL */ |
| gchar *localized_pretty_name_u8; |
| |
| /* Description, could be in user's language. Can be NULL */ |
| gunichar2 *description; |
| |
| /* Description, could be in user's language, UTF-8. Can be NULL */ |
| gchar *description_u8; |
| |
| /* Verbs that this application supports */ |
| GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ |
| |
| /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema, |
| * 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) -> to a GWin32AppInfoHandler |
| * Extension can be used as a key in the extensions hashmap. |
| */ |
| GHashTable *supported_exts; |
| |
| /* Icon of the application (remember, handler can have its own icon too) */ |
| GIcon *icon; |
| |
| /* Set to TRUE to prevent this app from appearing in lists of apps for |
| * opening files. This will not prevent it from appearing in lists of apps |
| * just for running, or lists of apps for opening exts/urls for which this |
| * app reports explicit support. |
| */ |
| gboolean no_open_with; |
| |
| /* Set to TRUE for applications from HKEY_CURRENT_USER. |
| * Give them priority over applications from HKEY_LOCAL_MACHINE, when all |
| * other things are equal. |
| */ |
| gboolean user_specific; |
| |
| /* Set to TRUE for applications that are machine-wide defaults (i.e. default |
| * browser) */ |
| gboolean default_app; |
| |
| /* Set to TRUE for UWP applications */ |
| gboolean is_uwp; |
| }; |
| |
| #define G_TYPE_WIN32_APPINFO_URL_SCHEMA (g_win32_appinfo_url_schema_get_type ()) |
| #define G_WIN32_APPINFO_URL_SCHEMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_URL_SCHEMA, GWin32AppInfoURLSchema)) |
| |
| #define G_TYPE_WIN32_APPINFO_FILE_EXTENSION (g_win32_appinfo_file_extension_get_type ()) |
| #define G_WIN32_APPINFO_FILE_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_FILE_EXTENSION, GWin32AppInfoFileExtension)) |
| |
| #define G_TYPE_WIN32_APPINFO_HANDLER (g_win32_appinfo_handler_get_type ()) |
| #define G_WIN32_APPINFO_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_HANDLER, GWin32AppInfoHandler)) |
| |
| #define G_TYPE_WIN32_APPINFO_APPLICATION (g_win32_appinfo_application_get_type ()) |
| #define G_WIN32_APPINFO_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_APPLICATION, GWin32AppInfoApplication)) |
| |
| #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) |
| |
| static void |
| g_win32_appinfo_url_schema_dispose (GObject *object) |
| { |
| GWin32AppInfoURLSchema *url = G_WIN32_APPINFO_URL_SCHEMA (object); |
| |
| g_clear_pointer (&url->schema, g_free); |
| g_clear_pointer (&url->schema_u8, g_free); |
| g_clear_pointer (&url->schema_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); |
| } |
| |
| |
| static void |
| g_win32_appinfo_handler_dispose (GObject *object) |
| { |
| GWin32AppInfoHandler *handler = G_WIN32_APPINFO_HANDLER (object); |
| |
| g_clear_pointer (&handler->handler_id, g_free); |
| g_clear_pointer (&handler->handler_id_folded, g_free); |
| g_clear_object (&handler->key); |
| g_clear_object (&handler->icon); |
| g_clear_pointer (&handler->verbs, g_ptr_array_unref); |
| g_clear_pointer (&handler->uwp_aumid, g_free); |
| G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object); |
| } |
| |
| static void |
| g_win32_appinfo_file_extension_dispose (GObject *object) |
| { |
| GWin32AppInfoFileExtension *ext = G_WIN32_APPINFO_FILE_EXTENSION (object); |
| |
| g_clear_pointer (&ext->extension, g_free); |
| g_clear_pointer (&ext->extension_u8, g_free); |
| g_clear_object (&ext->chosen_handler); |
| g_clear_pointer (&ext->handlers, g_hash_table_destroy); |
| G_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) |
| { |
| GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object); |
| |
| g_clear_pointer (&app->canonical_name_u8, g_free); |
| g_clear_pointer (&app->canonical_name_folded, g_free); |
| g_clear_pointer (&app->canonical_name, g_free); |
| g_clear_pointer (&app->pretty_name, g_free); |
| g_clear_pointer (&app->localized_pretty_name, g_free); |
| g_clear_pointer (&app->description, g_free); |
| g_clear_pointer (&app->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->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) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->dispose = g_win32_appinfo_url_schema_dispose; |
| } |
| |
| static void |
| g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->dispose = g_win32_appinfo_file_extension_dispose; |
| } |
| |
| static void |
| g_win32_appinfo_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) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->dispose = g_win32_appinfo_handler_dispose; |
| } |
| |
| static void |
| g_win32_appinfo_application_class_init (GWin32AppInfoApplicationClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->dispose = g_win32_appinfo_application_dispose; |
| } |
| |
| static void |
| g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self) |
| { |
| self->handlers = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| g_free, |
| g_object_unref); |
| } |
| |
| static void |
| g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self) |
| { |
| } |
| |
| static void |
| g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self) |
| { |
| self->handlers = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| g_free, |
| g_object_unref); |
| } |
| |
| static void |
| g_win32_appinfo_handler_init (GWin32AppInfoHandler *self) |
| { |
| self->verbs = g_ptr_array_new_with_free_func (g_object_unref); |
| } |
| |
| static void |
| g_win32_appinfo_application_init (GWin32AppInfoApplication *self) |
| { |
| self->supported_urls = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| g_free, |
| g_object_unref); |
| self->supported_exts = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| g_free, |
| g_object_unref); |
| self->verbs = g_ptr_array_new_with_free_func (g_object_unref); |
| } |
| |
| /* The AppInfo threadpool that does asynchronous AppInfo tree rebuilds */ |
| static GThreadPool *gio_win32_appinfo_threadpool; |
| |
| /* This mutex is held by a thread that reads or writes the AppInfo tree. |
| * (tree object references can be obtained and later read without |
| * holding this mutex, since objects are practically immutable). |
| */ |
| static GMutex gio_win32_appinfo_mutex; |
| |
| /* Any thread wanting to access AppInfo can wait on this condition */ |
| static GCond gio_win32_appinfo_cond; |
| |
| /* Increased to indicate that AppInfo tree does needs to be rebuilt. |
| * AppInfo thread checks this to see if it needs to |
| * do a tree re-build. If the value changes during a rebuild, |
| * another rebuild is triggered after that. |
| * Other threads check this to see if they need |
| * to wait for a tree re-build to finish. |
| */ |
| static gint gio_win32_appinfo_update_counter = 0; |
| |
| /* Map of owned ".ext" (with '.', UTF-8, folded) |
| * to GWin32AppInfoFileExtension ptr |
| */ |
| static GHashTable *extensions = NULL; |
| |
| /* Map of owned "schema" (without ':', UTF-8, folded) |
| * to GWin32AppInfoURLSchema ptr |
| */ |
| static GHashTable *urls = NULL; |
| |
| /* Map of owned "appID" (UTF-8, folded) to |
| * a GWin32AppInfoApplication |
| */ |
| static GHashTable *apps_by_id = NULL; |
| |
| /* Map of owned "app.exe" (UTF-8, folded) to |
| * 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 a GWin32AppInfoHandler |
| */ |
| static GHashTable *handlers = NULL; |
| |
| /* Temporary (only exists while the registry is being scanned) table |
| * that maps GWin32RegistryKey objects (keeps a ref) to owned AUMId wchar strings. |
| */ |
| static GHashTable *uwp_handler_table = NULL; |
| |
| /* Watch this whole subtree */ |
| static GWin32RegistryKey *url_associations_key; |
| |
| /* Watch this whole subtree */ |
| static GWin32RegistryKey *file_exts_key; |
| |
| /* Watch this whole subtree */ |
| static GWin32RegistryKey *user_clients_key; |
| |
| /* Watch this whole subtree */ |
| static GWin32RegistryKey *system_clients_key; |
| |
| /* Watch this key */ |
| static GWin32RegistryKey *user_registered_apps_key; |
| |
| /* Watch this key */ |
| static GWin32RegistryKey *system_registered_apps_key; |
| |
| /* Watch this whole subtree */ |
| static GWin32RegistryKey *applications_key; |
| |
| /* Watch this key */ |
| static GWin32RegistryKey *classes_root_key; |
| |
| #define URL_ASSOCIATIONS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\" |
| #define USER_CHOICE L"\\UserChoice" |
| #define OPEN_WITH_PROGIDS L"\\OpenWithProgids" |
| #define FILE_EXTS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\" |
| #define HKCR L"HKEY_CLASSES_ROOT\\" |
| #define HKCU L"HKEY_CURRENT_USER\\" |
| #define HKLM L"HKEY_LOCAL_MACHINE\\" |
| #define REG_PATH_MAX 256 |
| #define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2)) |
| |
| /* for g_wcsdup(), |
| * _g_win32_extract_executable(), |
| * _g_win32_fixup_broken_microsoft_rundll_commandline() |
| */ |
| #include "giowin32-private.c" |
| |
| /* for g_win32_package_parser_enum_packages() */ |
| #include "gwin32packageparser.h" |
| |
| static void |
| read_handler_icon (GWin32RegistryKey *key, |
| GIcon **icon_out) |
| { |
| GWin32RegistryKey *icon_key; |
| GWin32RegistryValueType default_type; |
| gchar *default_value; |
| |
| g_assert (icon_out); |
| |
| *icon_out = NULL; |
| |
| 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)) |
| { |
| /* TODO: For UWP handlers this string is usually in @{...} form, |
| * see grab_registry_string() below. Right now this |
| * string is read as-is and the icon would silently fail to load. |
| * Also, right now handler icon is not used anywhere |
| * (only app icon is used). |
| */ |
| 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); |
| } |
| |
| 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; |
| static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_list components); |
| |
| static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED; |
| |
| /* 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, |
| gunichar2 **return_uwp_aumid); |
| |
| 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, |
| const gunichar2 *uwp_aumid); |
| |
| 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); |
| |
| static void process_uwp_verbs (GList *verbs, |
| const reg_verb *preferred_verb, |
| const gunichar2 *path_to_progid, |
| const gunichar2 *progid, |
| gboolean autoprefer_first_verb, |
| GWin32AppInfoHandler *handler_rec, |
| GWin32AppInfoApplication *app); |
| |
| static void uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec, |
| GWin32AppInfoApplication *app, |
| const gunichar2 *verb, |
| const gchar *verb_displayname, |
| gboolean verb_is_preferred); |
| |
| /* output_size is in *bytes*, not gunichar2s! */ |
| static gboolean |
| build_registry_path (gunichar2 *output, gsize output_size, ...) |
| { |
| va_list ap; |
| gboolean result; |
| |
| va_start (ap, output_size); |
| |
| result = build_registry_pathv (output, output_size, ap); |
| |
| va_end (ap); |
| |
| return result; |
| } |
| |
| /* output_size is in *bytes*, not gunichar2s! */ |
| static gboolean |
| build_registry_pathv (gunichar2 *output, gsize output_size, va_list components) |
| { |
| va_list lentest; |
| gunichar2 *p; |
| gunichar2 *component; |
| gsize length; |
| |
| if (output == NULL) |
| return FALSE; |
| |
| va_copy (lentest, components); |
| |
| for (length = 0, component = va_arg (lentest, gunichar2 *); |
| component != NULL; |
| component = va_arg (lentest, gunichar2 *)) |
| { |
| length += wcslen (component); |
| } |
| |
| va_end (lentest); |
| |
| if ((length >= REG_PATH_MAX_SIZE) || |
| (length * sizeof (gunichar2) >= output_size)) |
| return FALSE; |
| |
| output[0] = L'\0'; |
| |
| for (p = output, component = va_arg (components, gunichar2 *); |
| component != NULL; |
| component = va_arg (components, gunichar2 *)) |
| { |
| length = wcslen (component); |
| wcscat (p, component); |
| p += length; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| static GWin32RegistryKey * |
| _g_win32_registry_key_build_and_new_w (GError **error, ...) |
| { |
| va_list ap; |
| gunichar2 key_path[REG_PATH_MAX_SIZE + 1]; |
| GWin32RegistryKey *key; |
| |
| va_start (ap, error); |
| |
| key = NULL; |
| |
| if (build_registry_pathv (key_path, sizeof (key_path), ap)) |
| key = g_win32_registry_key_new_w (key_path, error); |
| |
| va_end (ap); |
| |
| return key; |
| } |
| |
| /* 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). |
| * @is_uwp points to a boolean, which |
| * indicates whether the function is being called for a UWP app. |
| * It might be switched from %TRUE to %FALSE on return, |
| * if the application turns out to not to be UWP on closer inspection. |
| * If the application is already known not to be UWP before the |
| * call, this pointer can be %NULL instead. |
| * Returns TRUE on success, FALSE on failure. |
| */ |
| static gboolean |
| get_verbs (GWin32RegistryKey *program_id_key, |
| const reg_verb **preferred_verb, |
| GList **verbs, |
| const gunichar2 *verbname_prefix, |
| const gunichar2 *verbshell_prefix, |
| gboolean *is_uwp) |
| { |
| GWin32RegistrySubkeyIter iter; |
| GWin32RegistryKey *key; |
| GWin32RegistryValueType val_type; |
| gunichar2 *default_verb; |
| gsize verbshell_prefix_len; |
| gsize verbname_prefix_len; |
| GList *i; |
| |
| 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; |
| |
| if (!g_win32_registry_subkey_iter_init (&iter, key, NULL)) |
| { |
| g_object_unref (key); |
| |
| return FALSE; |
| } |
| |
| 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); |
| |
| /* We may not have the required access rights to open the child key */ |
| if (subkey == NULL) |
| continue; |
| |
| /* The key we're looking at is "<some_root>/Shell/<this_key>", |
| * where "Shell" is verbshell_prefix. |
| * If it has a value named 'Subcommands' (doesn't matter what its data is), |
| * it means that this key has its own Shell subkey, the subkeys |
| * of which are shell commands (i.e. <some_root>/Shell/<this_key>/Shell/<some_other_keys>). |
| * To handle that, create new, extended nameprefix and shellprefix, |
| * and call the function recursively. |
| * name prefix "" -> "<this_key_name>\\" |
| * shell prefix "Shell" -> "Shell\\<this_key_name>\\Shell" |
| * The root, program_id_key, remains the same in all invocations. |
| * Essentially, we're flattening the command tree into a list. |
| */ |
| has_subcommands = FALSE; |
| if ((is_uwp == NULL || !(*is_uwp)) && /* Assume UWP apps don't have subcommands */ |
| g_win32_registry_key_get_value_w (subkey, |
| NULL, |
| TRUE, |
| L"Subcommands", |
| &subc_type, |
| NULL, |
| NULL, |
| NULL) && |
| subc_type == G_WIN32_REGISTRY_VALUE_STR) |
| { |
| gboolean dummy = FALSE; |
| gunichar2 *new_nameprefix = g_new (gunichar2, verbname_prefix_len + name_len + 1 + 1); |
| gunichar2 *new_shellprefix = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1); |
| 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, &dummy); |
| g_free (new_shellprefix); |
| g_free (new_nameprefix); |
| } |
| |
| /* Presence of subcommands means that this key itself is not a command-key */ |
| if (has_subcommands) |
| { |
| g_clear_object (&subkey); |
| continue; |
| } |
| |
| if (is_uwp != NULL && *is_uwp && |
| !g_win32_registry_key_get_value_w (subkey, |
| NULL, |
| TRUE, |
| L"ActivatableClassId", |
| &subc_type, |
| NULL, |
| NULL, |
| NULL)) |
| { |
| /* We expected a UWP app, but it lacks ActivatableClassId |
| * on a verb, which means that it does not behave like |
| * a UWP app should (msedge being an example - it's UWP, |
| * but has its own launchable exe file and a simple ID), |
| * so we have to treat it like a normal app. |
| */ |
| *is_uwp = FALSE; |
| } |
| |
| g_clear_object (&subkey); |
| |
| /* 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_new (gunichar2, verbname_prefix_len + name_len + 1); |
| 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_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1); |
| 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; |
| } |
| |
| /* Grabs a URL association (from HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\ |
| * or from an application with Capabilities, or just a schema subkey in HKCR). |
| * @program_id is a ProgID of the handler for the URL. |
| * @schema is the schema for the URL. |
| * @schema_u8 and @schema_u8_folded are UTF-8 and folded UTF-8 |
| * respectively. |
| * @app is the app to which the URL handler belongs (can be NULL). |
| * @is_user_choice is TRUE if this association is clearly preferred |
| */ |
| static void |
| get_url_association (const gunichar2 *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; |
| gunichar2 *uwp_aumid; |
| gboolean is_uwp; |
| GWin32RegistryKey *handler_key; |
| |
| if ((handler_id = decide_which_id_to_use (program_id, |
| &handler_key, |
| &handler_id_u8, |
| &handler_id_u8_folded, |
| &uwp_aumid)) == NULL) |
| return; |
| |
| is_uwp = uwp_aumid != NULL; |
| |
| if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp)) |
| { |
| 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 (&uwp_aumid, g_free); |
| |
| return; |
| } |
| |
| if (!is_uwp && uwp_aumid != NULL) |
| g_clear_pointer (&uwp_aumid, g_free); |
| |
| schema_rec = get_schema_object (schema, |
| schema_u8, |
| schema_u8_folded); |
| |
| handler_rec = get_handler_object (handler_id_u8_folded, |
| handler_key, |
| handler_id, |
| uwp_aumid); |
| |
| 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)); |
| |
| if (uwp_aumid == NULL) |
| process_verbs_commands (g_steal_pointer (&verbs), |
| preferred_verb, |
| HKCR, |
| handler_id, |
| TRUE, |
| handler_add_verb, |
| handler_rec, |
| app); |
| else |
| process_uwp_verbs (g_steal_pointer (&verbs), |
| preferred_verb, |
| HKCR, |
| handler_id, |
| TRUE, |
| 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); |
| g_clear_pointer (&uwp_aumid, 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; |
| gunichar2 *uwp_aumid; |
| gboolean is_uwp; |
| 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, |
| &uwp_aumid)) == 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_pointer (&uwp_aumid, g_free); |
| g_clear_object (&handler_key); |
| |
| return; |
| } |
| |
| is_uwp = uwp_aumid != NULL; |
| |
| if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp)) |
| { |
| 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); |
| g_clear_pointer (&uwp_aumid, g_free); |
| |
| return; |
| } |
| |
| if (!is_uwp && uwp_aumid != NULL) |
| g_clear_pointer (&uwp_aumid, g_free); |
| |
| 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, |
| uwp_aumid); |
| |
| 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); |
| |
| if (uwp_aumid == NULL) |
| process_verbs_commands (g_steal_pointer (&verbs), |
| preferred_verb, |
| HKCR, |
| handler_id, |
| TRUE, |
| handler_add_verb, |
| handler_rec, |
| app); |
| else |
| process_uwp_verbs (g_steal_pointer (&verbs), |
| preferred_verb, |
| HKCR, |
| handler_id, |
| TRUE, |
| 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); |
| g_clear_pointer (&uwp_aumid, 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, |
| gunichar2 **return_uwp_aumid) |
| { |
| GWin32RegistryKey *key; |
| GWin32RegistryKey *uwp_key; |
| GWin32RegistryValueType val_type; |
| gunichar2 *proxy_id; |
| gunichar2 *return_id; |
| gunichar2 *uwp_aumid; |
| gboolean got_value; |
| gchar *handler_id_u8; |
| gchar *handler_id_u8_folded; |
| g_assert (program_id); |
| |
| if (return_key) |
| *return_key = NULL; |
| |
| if (return_uwp_aumid) |
| *return_uwp_aumid = NULL; |
| |
| key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL); |
| |
| if (key == NULL) |
| return NULL; |
| |
| /* Check for UWP first */ |
| uwp_aumid = NULL; |
| uwp_key = g_win32_registry_key_get_child_w (key, L"Application", NULL); |
| |
| if (uwp_key != NULL) |
| { |
| got_value = g_win32_registry_key_get_value_w (uwp_key, |
| NULL, |
| TRUE, |
| L"AppUserModelID", |
| &val_type, |
| (void **) &uwp_aumid, |
| NULL, |
| NULL); |
| if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&uwp_aumid, g_free); |
| |
| /* Other values in the Application key contain useful information |
| * (description, name, icon), but it's inconvenient to read |
| * it here (we don't have an app object *yet*). Store the key |
| * in a table instead, and look at it later. |
| */ |
| if (uwp_aumid == NULL) |
| g_debug ("ProgramID %S looks like a UWP application, but isn't", |
| program_id); |
| else |
| g_hash_table_insert (uwp_handler_table, g_object_ref (uwp_key), g_wcsdup (uwp_aumid, -1)); |
| |
| g_object_unref (uwp_key); |
| } |
| |
| /* Then check for proxy */ |
| proxy_id = NULL; |
| |
| if (uwp_aumid == 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); |
| g_clear_pointer (&handler_id_u8, g_free); |
| if (return_handler_id_u8_folded) |
| *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded); |
| g_clear_pointer (&handler_id_u8_folded, g_free); |
| if (return_uwp_aumid) |
| *return_uwp_aumid = g_steal_pointer (&uwp_aumid); |
| g_clear_pointer (&uwp_aumid, g_free); |
| |
| 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 (handler != NULL); |
| g_assert (verbs != NULL); |
| g_assert (progid != 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 **) &command_value, |
| NULL, |
| NULL); |
| 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) |
| { |
| g_clear_pointer (&command_value, g_free); |
| continue; |
| } |
| |
| verb_displayname = NULL; |
| verb_displayname_u8 = NULL; |
| verb_key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid, |
| L"\\", verb->shellpath, NULL); |
| |
| 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); |
| } |
| |
| g_list_free_full (verbs, reg_verb_free); |
| } |
| |
| static void |
| process_uwp_verbs (GList *verbs, |
| const reg_verb *preferred_verb, |
| const gunichar2 *path_to_progid, |
| const gunichar2 *progid, |
| gboolean autoprefer_first_verb, |
| GWin32AppInfoHandler *handler_rec, |
| GWin32AppInfoApplication *app) |
| { |
| GList *i; |
| |
| g_assert (verbs != NULL); |
| |
| for (i = verbs; i; i = i->next) |
| { |
| const reg_verb *verb = (const reg_verb *) i->data; |
| GWin32RegistryKey *key; |
| gboolean got_value; |
| GWin32RegistryValueType val_type; |
| gunichar2 *acid; |
| gsize acid_len; |
| |
| key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid, |
| L"\\", verb->shellpath, NULL); |
| |
| if (key == NULL) |
| { |
| g_debug ("%S%S\\%S does not exist", |
| path_to_progid, progid, verb->shellpath); |
| continue; |
| } |
| |
| acid = NULL; |
| got_value = g_win32_registry_key_get_value_w (key, |
| g_win32_registry_get_os_dirs_w (), |
| TRUE, |
| L"ActivatableClassId", |
| &val_type, |
| (void **) &acid, |
| &acid_len, |
| NULL); |
| |
| if (got_value && |
| val_type == G_WIN32_REGISTRY_VALUE_STR && |
| acid_len > sizeof (gunichar2)) |
| { |
| /* TODO: default value of a shell subkey, if not empty, |
| * migh contain something like @{Some.Identifier_1234.456.678.789_some_words?ms-resource://Arbitrary.Path/Pointing/Somewhere} |
| * and it might be possible to turn it into a nice displayname. |
| */ |
| uwp_handler_add_verb (handler_rec, |
| app, |
| verb->name, |
| NULL, |
| (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) || |
| (!preferred_verb && autoprefer_first_verb && i == verbs)); |
| } |
| else |
| { |
| g_debug ("%S%S\\%S does not have an ActivatableClassId string value", |
| path_to_progid, progid, verb->shellpath); |
| } |
| |
| g_clear_pointer (&acid, g_free); |
| g_clear_object (&key); |
| } |
| |
| g_list_free_full (verbs, reg_verb_free); |
| } |
| |
| /* Looks up a schema object identified by |
| * @schema_u8_folded in the urls hash table. |
| * If such object doesn't exist, |
| * creates it and puts it into the urls hash table. |
| * Returns the object. |
| */ |
| static GWin32AppInfoURLSchema * |
| get_schema_object (const gunichar2 *schema, |
| const gchar *schema_u8, |
| const gchar *schema_u8_folded) |
| { |
| GWin32AppInfoURLSchema *schema_rec; |
| |
| schema_rec = g_hash_table_lookup (urls, schema_u8_folded); |
| |
| if (schema_rec != NULL) |
| return schema_rec; |
| |
| schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); |
| schema_rec->schema = g_wcsdup (schema, -1); |
| schema_rec->schema_u8 = g_strdup (schema_u8); |
| schema_rec->schema_u8_folded = g_strdup (schema_u8_folded); |
| g_hash_table_insert (urls, g_strdup (schema_rec->schema_u8_folded), schema_rec); |
| |
| return schema_rec; |
| } |
| |
| /* Looks up a handler object identified by |
| * @handler_id_u8_folded in the handlers hash table. |
| * If such object doesn't exist, |
| * creates it and puts it into the handlers hash table. |
| * Returns the object. |
| */ |
| static GWin32AppInfoHandler * |
| get_handler_object (const gchar *handler_id_u8_folded, |
| GWin32RegistryKey *handler_key, |
| const gunichar2 *handler_id, |
| const gunichar2 *uwp_aumid) |
| { |
| GWin32AppInfoHandler *handler_rec; |
| |
| handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded); |
| |
| if (handler_rec != NULL) |
| return handler_rec; |
| |
| handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); |
| if (handler_key) |
| 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); |
| if (uwp_aumid) |
| handler_rec->uwp_aumid = g_wcsdup (uwp_aumid, -1); |
| if (handler_key) |
| read_handler_icon (handler_key, &handler_rec->icon); |
| g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec); |
| |
| return handler_rec; |
| } |
| |
| static void |
| handler_add_verb (gpointer handler_data1, |
| gpointer handler_data2, |
| const gunichar2 *verb, |
| const gunichar2 *command_line, |
| const gchar *command_line_utf8, |
| const gchar *verb_displayname, |
| gboolean verb_is_preferred, |
| gboolean invent_new_verb_name) |
| { |
| GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1; |
| GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2; |
| GWin32AppInfoShellVerb *shverb = NULL; |
| |
| _verb_lookup (handler_rec->verbs, verb, &shverb); |
| |
| if (shverb != NULL) |
| return; |
| |
| shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); |
| shverb->verb_name = g_wcsdup (verb, -1); |
| shverb->verb_displayname = g_strdup (verb_displayname); |
| shverb->command = g_wcsdup (command_line, -1); |
| shverb->command_utf8 = g_strdup (command_line_utf8); |
| shverb->is_uwp = FALSE; /* This function is for non-UWP verbs only */ |
| if (app_rec) |
| shverb->app = g_object_ref (app_rec); |
| |
| _g_win32_extract_executable (shverb->command, |
| &shverb->executable, |
| &shverb->executable_basename, |
| &shverb->executable_folded, |
| NULL, |
| &shverb->dll_function); |
| |
| if (shverb->dll_function != NULL) |
| _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command); |
| |
| if (!verb_is_preferred) |
| g_ptr_array_add (handler_rec->verbs, shverb); |
| else |
| g_ptr_array_insert (handler_rec->verbs, 0, shverb); |
| } |
| |
| /* Tries to generate a new name for a verb that looks |
| * like "verb (%x)", where %x is an integer in range of [0;255). |
| * On success puts new verb (and new verb displayname) into |
| * @new_verb and @new_displayname and return TRUE. |
| * On failure puts NULL into both and returns FALSE. |
| */ |
| static gboolean |
| generate_new_verb_name (GPtrArray *verbs, |
| const gunichar2 *verb, |
| const gchar *verb_displayname, |
| gunichar2 **new_verb, |
| gchar **new_displayname) |
| { |
| gsize counter; |
| GWin32AppInfoShellVerb *shverb = NULL; |
| gsize orig_len = g_utf16_len (verb); |
| gsize new_verb_name_len = orig_len + strlen (" ()") + 2 + 1; |
| gunichar2 *new_verb_name = g_new (gunichar2, new_verb_name_len); |
| |
| *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" (%zx)", 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 = NULL; |
| |
| _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); |
| } |
| |
| static void |
| uwp_app_add_verb (GWin32AppInfoApplication *app_rec, |
| const gunichar2 *verb, |
| const gchar *verb_displayname) |
| { |
| GWin32AppInfoShellVerb *shverb = NULL; |
| |
| _verb_lookup (app_rec->verbs, verb, &shverb); |
| |
| if (shverb != NULL) |
| return; |
| |
| shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); |
| shverb->verb_name = g_wcsdup (verb, -1); |
| shverb->app = g_object_ref (app_rec); |
| shverb->verb_displayname = g_strdup (verb_displayname); |
| |
| shverb->is_uwp = TRUE; |
| |
| /* Strictly speaking, this is unnecessary, but |
| * let's make it clear that UWP verbs have no |
| * commands and executables. |
| */ |
| shverb->command = NULL; |
| shverb->command_utf8 = NULL; |
| shverb->executable = NULL; |
| shverb->executable_basename = NULL; |
| shverb->executable_folded = NULL; |
| shverb->dll_function = NULL; |
| |
| g_ptr_array_add (app_rec->verbs, shverb); |
| } |
| |
| static void |
| uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec, |
| GWin32AppInfoApplication *app, |
| const gunichar2 *verb, |
| const gchar *verb_displayname, |
| gboolean verb_is_preferred) |
| { |
| GWin32AppInfoShellVerb *shverb = NULL; |
| |
| _verb_lookup (handler_rec->verbs, verb, &shverb); |
| |
| if (shverb != NULL) |
| return; |
| |
| shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); |
| shverb->verb_name = g_wcsdup (verb, -1); |
| shverb->verb_displayname = g_strdup (verb_displayname); |
| |
| shverb->is_uwp = TRUE; |
| |
| if (app) |
| shverb->app = g_object_ref (app); |
| |
| shverb->command = NULL; |
| shverb->command_utf8 = NULL; |
| shverb->executable = NULL; |
| shverb->executable_basename = NULL; |
| shverb->executable_folded = NULL; |
| shverb->dll_function = NULL; |
| |
| if (!verb_is_preferred) |
| g_ptr_array_add (handler_rec->verbs, shverb); |
| else |
| g_ptr_array_insert (handler_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, |
| gboolean user_registry) |
| { |
| GWin32RegistryKey *clients; |
| GWin32RegistrySubkeyIter clients_iter; |
| |
| const gunichar2 *client_type_name; |
| gsize client_type_name_len; |
| |
| |
| if (user_registry) |
| clients = |
| g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients", |
| NULL); |
| else |
| clients = |
| g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients", |
| NULL); |
| |
| if (clients == NULL) |
| return; |
| |
| if (!g_win32_registry_subkey_iter_init (&clients_iter, clients, NULL)) |
| { |
| g_object_unref (clients); |
| return; |
| } |
| |
| while (g_win32_registry_subkey_iter_next (&clients_iter, TRUE, NULL)) |
| { |
| GWin32RegistrySubkeyIter subkey_iter; |
| GWin32RegistryKey *system_client_type; |
| GWin32RegistryValueType default_type; |
| gunichar2 *default_value = NULL; |
| const gunichar2 *client_name; |
| gsize client_name_len; |
| |
| if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter, |
| &client_type_name, |
| &client_type_name_len, |
| NULL)) |
| continue; |
| |
| system_client_type = g_win32_registry_key_get_child_w (clients, |
| client_type_name, |
| NULL); |
| |
| if (system_client_type == NULL) |
| continue; |
| |
| if (g_win32_registry_key_get_value_w (system_client_type, |
| NULL, |
| TRUE, |
| L"", |
| &default_type, |
| (gpointer *) &default_value, |
| NULL, |
| NULL)) |
| { |
| if (default_type != G_WIN32_REGISTRY_VALUE_STR || |
| default_value[0] == L'\0') |
| g_clear_pointer (&default_value, g_free); |
| } |
| |
| if (!g_win32_registry_subkey_iter_init (&subkey_iter, |
| system_client_type, |
| NULL)) |
| { |
| g_clear_pointer (&default_value, g_free); |
| g_object_unref (system_client_type); |
| continue; |
| } |
| |
| while (g_win32_registry_subkey_iter_next (&subkey_iter, TRUE, NULL)) |
| { |
| GWin32RegistryKey *system_client; |
| GWin32RegistryKey *system_client_assoc; |
| gboolean add; |
| gunichar2 *keyname; |
| |
| if (!g_win32_registry_subkey_iter_get_name_w (&subkey_iter, |
| &client_name, |
| &client_name_len, |
| NULL)) |
| continue; |
| |
| system_client = g_win32_registry_key_get_child_w (system_client_type, |
| client_name, |
| NULL); |
| |
| if (system_client == NULL) |
| continue; |
| |
| add = FALSE; |
| |
| system_client_assoc = g_win32_registry_key_get_child_w (system_client, |
| L"Capabilities\\FileAssociations", |
| NULL); |
| |
| if (system_client_assoc != NULL) |
| { |
| add = TRUE; |
| g_object_unref (system_client_assoc); |
| } |
| else |
| { |
| system_client_assoc = g_win32_registry_key_get_child_w (system_client, |
| L"Capabilities\\UrlAssociations", |
| NULL); |
| |
| if (system_client_assoc != NULL) |
| { |
| add = TRUE; |
| g_object_unref (system_client_assoc); |
| } |
| } |
| |
| if (add) |
| { |
| keyname = g_wcsdup (g_win32_registry_key_get_path_w (system_client), -1); |
| |
| if (default_value && wcscmp (default_value, client_name) == 0) |
| g_ptr_array_add (priority_capable_apps, keyname); |
| else |
| g_ptr_array_add (capable_apps, keyname); |
| } |
| |
| g_object_unref (system_client); |
| } |
| |
| g_win32_registry_subkey_iter_clear (&subkey_iter); |
| g_clear_pointer (&default_value, g_free); |
| g_object_unref (system_client_type); |
| } |
| |
| g_win32_registry_subkey_iter_clear (&clients_iter); |
| g_object_unref (clients); |
| } |
| |
| /* 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; |
| GWin32RegistryValueType value_type; |
| GWin32RegistryKey *registered_apps; |
| |
| if (user_registry) |
| reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications"; |
| else |
| reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications"; |
| |
| registered_apps = |
| g_win32_registry_key_new_w (reg_path, NULL); |
| |
| if (!registered_apps) |
| return; |
| |
| if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL)) |
| { |
| g_object_unref (registered_apps); |
| |
| return; |
| } |
| |
| while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) |
| { |
| gunichar2 possible_location[REG_PATH_MAX_SIZE + 1]; |
| GWin32RegistryKey *location; |
| gunichar2 *p; |
| |
| if ((!g_win32_registry_value_iter_get_value_type (&iter, |
| &value_type, |
| NULL)) || |
| (value_type != G_WIN32_REGISTRY_VALUE_STR) || |
| (!g_win32_registry_value_iter_get_data_w (&iter, TRUE, |
| (void **) &value_data, |
| &value_data_size, |
| NULL)) || |
| (value_data_size < sizeof (gunichar2)) || |
| (value_data[0] == L'\0')) |
| continue; |
| |
| if (!build_registry_path (possible_location, sizeof (possible_location), |
| user_registry ? HKCU : HKLM, value_data, NULL)) |
| continue; |
| |
| location = g_win32_registry_key_new_w (possible_location, NULL); |
| |
| if (location == NULL) |
| continue; |
| |
| p = wcsrchr (possible_location, L'\\'); |
| |
| if (p) |
| { |
| *p = L'\0'; |
| g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1)); |
| } |
| |
| g_object_unref (location); |
| } |
| |
| g_win32_registry_value_iter_clear (&iter); |
| g_object_unref (registered_apps); |
| } |
| |
| /* 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, |
| gboolean is_uwp) |
| { |
| GWin32AppInfoApplication *app; |
| |
| app = g_hash_table_lookup (app_hashmap, canonical_name_folded); |
| |
| if (app != NULL) |
| return app; |
| |
| app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); |
| app->canonical_name = g_wcsdup (canonical_name, -1); |
| app->canonical_name_u8 = g_strdup (canonical_name_u8); |
| app->canonical_name_folded = g_strdup (canonical_name_folded); |
| app->no_open_with = FALSE; |
| app->user_specific = user_specific; |
| app->default_app = default_app; |
| app->is_uwp = is_uwp; |
| 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; |
| gunichar2 *friendly_name; |
| gunichar2 *description; |
| gunichar2 *narrow_application_name; |
| gunichar2 *icon_source; |
| GWin32RegistryKey *capabilities; |
| GWin32RegistryKey *default_icon_key; |
| GWin32RegistryKey *associations; |
| const reg_verb *preferred_verb; |
| GList *verbs = NULL; |
| gboolean verbs_in_root_key = TRUE; |
| |
| appkey = NULL; |
| capabilities = NULL; |
| |
| if (!g_utf16_to_utf8_and_fold (app_key_path, |
| -1, |
| &canonical_name_u8, |
| &canonical_name_folded) || |
| !g_utf16_to_utf8_and_fold (app_key_path, |
| -1, |
| &app_key_path_u8, |
| &app_key_path_u8_folded) || |
| (appkey = g_win32_registry_key_new_w (app_key_path, NULL)) == NULL || |
| (capabilities = g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL)) == NULL || |
| !(get_verbs (appkey, &preferred_verb, &verbs, L"", L"Shell", NULL) || |
| (verbs_in_root_key = FALSE) || |
| get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell", NULL))) |
| { |
| g_clear_pointer (&canonical_name_u8, g_free); |
| g_clear_pointer (&canonical_name_folded, g_free); |
| g_clear_object (&appkey); |
| g_clear_object (&capabilities); |
| g_clear_pointer (&app_key_path_u8, g_free); |
| g_clear_pointer (&app_key_path_u8_folded, g_free); |
| |
| return; |
| } |
| |
| app = get_app_object (apps_by_id, |
| app_key_path, |
| canonical_name_u8, |
| canonical_name_folded, |
| user_specific, |
| default_app, |
| FALSE); |
| |
| process_verbs_commands (g_steal_pointer (&verbs), |
| preferred_verb, |
| L"", /* [ab]use the fact that two strings are simply concatenated */ |
| verbs_in_root_key ? app_key_path : 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, |
| NULL, |
| TRUE, |
| L"", |
| &vtype, |
| (void **) &fallback_friendly_name, |
| NULL, |
| NULL); |
| |
| if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&fallback_friendly_name, g_free); |
| |
| if (fallback_friendly_name && |
| app->pretty_name == NULL) |
| { |
| app->pretty_name = g_wcsdup (fallback_friendly_name, -1); |
| g_clear_pointer (&app->pretty_name_u8, g_free); |
| app->pretty_name_u8 = g_utf16_to_utf8 (fallback_friendly_name, |
| -1, |
| NULL, |
| NULL, |
| NULL); |
| } |
| |
| friendly_name = NULL; |
| success = g_win32_registry_key_get_value_w (capabilities, |
| g_win32_registry_get_os_dirs_w (), |
| TRUE, |
| L"LocalizedString", |
| &vtype, |
| (void **) &friendly_name, |
| NULL, |
| NULL); |
| |
| if (success && |
| vtype != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&friendly_name, g_free); |
| |
| if (friendly_name && |
| app->localized_pretty_name == NULL) |
| { |
| app->localized_pretty_name = g_wcsdup (friendly_name, -1); |
| g_clear_pointer (&app->localized_pretty_name_u8, g_free); |
| app->localized_pretty_name_u8 = g_utf16_to_utf8 (friendly_name, |
| -1, |
| NULL, |
| NULL, |
| NULL); |
| } |
| |
| description = NULL; |
| success = g_win32_registry_key_get_value_w (capabilities, |
| g_win32_registry_get_os_dirs_w (), |
| TRUE, |
| L"ApplicationDescription", |
| &vtype, |
| (void **) &description, |
| NULL, |
| NULL); |
| |
| if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&description, g_free); |
| |
| if (description && app->description == NULL) |
| { |
| app->description = g_wcsdup (description, -1); |
| g_clear_pointer (&app->description_u8, g_free); |
| app->description_u8 = g_utf16_to_utf8 (description, -1, NULL, NULL, NULL); |
| } |
| |
| default_icon_key = g_win32_registry_key_get_child_w (appkey, |
| L"DefaultIcon", |
| NULL); |
| |
| icon_source = NULL; |
| |
| if (default_icon_key != NULL) |
| { |
| success = g_win32_registry_key_get_value_w (default_icon_key, |
| 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 == NULL) |
| { |
| success = g_win32_registry_key_get_value_w (capabilities, |
| NULL, |
| TRUE, |
| L"ApplicationIcon", |
| &vtype, |
| (void **) &icon_source, |
| NULL, |
| NULL); |
| |
| if (success && |
| vtype != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&icon_source, g_free); |
| } |
| |
| if (icon_source && |
| app->icon == NULL) |
| { |
| gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); |
| app->icon = g_themed_icon_new (name); |
| g_free (name); |
| } |
| |
| narrow_application_name = NULL; |
| success = g_win32_registry_key_get_value_w (capabilities, |
| g_win32_registry_get_os_dirs_w (), |
| TRUE, |
| L"ApplicationName", |
| &vtype, |
| (void **) &narrow_application_name, |
| NULL, |
| NULL); |
| |
| if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) |
| g_clear_pointer (&narrow_application_name, g_free); |
| |
| 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", |
| NULL); |
| |
| if (associations != NULL) |
| { |
| GWin32RegistryValueIter iter; |
| |
| if (g_win32_registry_value_iter_init (&iter, associations, NULL)) |
| { |
| gunichar2 *file_extension; |
| gunichar2 *extension_handler; |
| gsize file_extension_len; |
| gsize extension_handler_size; |
| GWin32RegistryValueType value_type; |
| |
| while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) |
| { |
| if ((!g_win32_registry_value_iter_get_value_type (&iter, |
| &value_type, |
| NULL)) || |
| (value_type != G_WIN32_REGISTRY_VALUE_STR) || |
| (!g_win32_registry_value_iter_get_name_w (&iter, |
| &file_extension, |
| &file_extension_len, |
| NULL)) || |
| (file_extension_len <= 0) || |
| (file_extension[0] != L'.') || |
| (!g_win32_registry_value_iter_get_data_w (&iter, TRUE, |
| (void **) &extension_handler, |
| &extension_handler_size, |
| NULL)) || |
| (extension_handler_size < sizeof (gunichar2)) || |
| (extension_handler[0] == L'\0')) |
| continue; |
| |
| get_file_ext (extension_handler, file_extension, app, FALSE); |
| } |
| |
| g_win32_registry_value_iter_clear (&iter); |
| } |
| |
| g_object_unref (associations); |
| } |
| |
| associations = g_win32_registry_key_get_child_w (capabilities, L"URLAssociations", NULL); |
| |
| if (associations != NULL) |
| { |
| GWin32RegistryValueIter iter; |
| |
| if (g_win32_registry_value_iter_init (&iter, associations, NULL)) |
| { |
| gunichar2 *url_schema; |
| gunichar2 *schema_handler; |
| gsize url_schema_len; |
| gsize schema_handler_size; |
| GWin32RegistryValueType value_type; |
| |
| while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) |
| { |
| gchar *schema_u8 = NULL; |
| gchar *schema_u8_folded = NULL; |
| |
| if ((!g_win32_registry_value_iter_get_value_type (&iter, |
| &value_type, |
| NULL)) || |
| ((value_type != G_WIN32_REGISTRY_VALUE_STR) && |
| (value_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) || |
| (!g_win32_registry_value_iter_get_name_w (&iter, |
| &url_schema, |
| &url_schema_len, |
| NULL)) || |
| (url_schema_len <= 0) || |
| (url_schema[0] == L'\0') || |
| (!g_win32_registry_value_iter_get_data_w (&iter, TRUE, |
| (void **) &schema_handler, |
| &schema_handler_size, |
| NULL)) || |
| (schema_handler_size < sizeof (gunichar2)) || |
| (schema_handler[0] == L'\0')) |
| continue; |
| |
| |
| |
| if (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); |
| |
| g_clear_pointer (&schema_u8, g_free); |
| g_clear_pointer (&schema_u8_folded, g_free); |
| } |
| |
| g_win32_registry_value_iter_clear (&iter); |
| } |
| |
| g_object_unref (associations); |
| } |
| |
| 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_object_unref (appkey); |
| g_object_unref (capabilities); |
| 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; |
| |
| if (url_associations == NULL) |
| return; |
| |
| if (!g_win32_registry_subkey_iter_init (&url_iter, url_associations, NULL)) |
| return; |
| |
| while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL)) |
| { |
| gchar *schema_u8 = NULL; |
| gchar *schema_u8_folded = NULL; |
| const gunichar2 *url_schema = NULL; |
| gunichar2 *program_id = NULL; |
| GWin32RegistryKey *user_choice = NULL; |
| gsize url_schema_len; |
| GWin32RegistryValueType val_type; |
| |
| if (g_win32_registry_subkey_iter_get_name_w (&url_iter, |
| &url_schema, |
| &url_schema_len, |
| NULL) && |
| g_utf16_to_utf8_and_fold (url_schema, |
| url_schema_len, |
| &schema_u8, |
| &schema_u8_folded) && |
| (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS, |
| url_schema, USER_CHOICE, |
| NULL)) != NULL && |
| g_win32_registry_key_get_value_w (user_choice, |
| NULL, |
| TRUE, |
| L"Progid", |
| &val_type, |
| (void **) &program_id, |
| NULL, |
| NULL) && |
| val_type == G_WIN32_REGISTRY_VALUE_STR) |
| get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE); |
| |
| g_clear_pointer (&program_id, g_free); |
| g_clear_pointer (&user_choice, g_object_unref); |
| g_clear_pointer (&schema_u8, g_free); |
| g_clear_pointer (&schema_u8_folded, g_free); |
| } |
| |
| g_win32_registry_subkey_iter_clear (&url_iter); |
| } |
| |
| /* Reads an application that is only registered by the basename of its |
| * executable (and doesn't have Capabilities subkey). |
| * @incapable_app is the registry key for the app. |
| * @app_exe_basename is the basename of its executable. |
| */ |
| static void |
| read_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", NULL)) |
| return; |
| |
| app = get_app_object (apps_by_exe, |
| app_exe_basename, |
| app_exe_basename_u8, |
| app_exe_basename_u8_folded, |
| FALSE, |
| 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) |
| { |
| GWin32RegistryKey *local_applications_key; |
| GWin32RegistrySubkeyIter app_iter; |
| |
| local_applications_key = |
| g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL); |
| |
| if (local_applications_key == NULL) |
| return; |
| |
| if (!g_win32_registry_subkey_iter_init (&app_iter, local_applications_key, NULL)) |
| { |
| g_object_unref (local_applications_key); |
| return; |
| } |
| |
| while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL)) |
| { |
| const gunichar2 *app_exe_basename; |
| gsize app_exe_basename_len; |
| GWin32RegistryKey *incapable_app; |
| gchar *app_exe_basename_u8; |
| gchar *app_exe_basename_u8_folded; |
| |
| if (!g_win32_registry_subkey_iter_get_name_w (&app_iter, |
| &app_exe_basename, |
| &app_exe_basename_len, |
| NULL) || |
| !g_utf16_to_utf8_and_fold (app_exe_basename, |
| app_exe_basename_len, |
| &app_exe_basename_u8, |
| &app_exe_basename_u8_folded)) |
| continue; |
| |
| incapable_app = |
| g_win32_registry_key_get_child_w (local_applications_key, |
| app_exe_basename, |
| NULL); |
| |
| if (incapable_app != NULL) |
| read_incapable_app (incapable_app, |
| app_exe_basename, |
| app_exe_basename_u8, |
| app_exe_basename_u8_folded); |
| |
| 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 (local_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; |
| const gunichar2 *file_extension; |
| gsize file_extension_len; |
| |
| if (file_exts == NULL) |
| return; |
| |
| if (!g_win32_registry_subkey_iter_init (&ext_iter, file_exts, NULL)) |
| return; |
| |
| while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL)) |
| { |
| 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; |
| |
| 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); |
| } |
| |
| /* 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; |
| const gunichar2 *class_name; |
| gsize class_name_len; |
| |
| if (classes_root == NULL) |
| return; |
| |
| if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL)) |
| return; |
| |
| while (g_win32_registry_subkey_iter_next (&class_iter, TRUE, NULL)) |
| { |
| if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter, |
| &class_name, |
| &class_name_len, |
| NULL)) || |
| (class_name_len <= 1)) |
| continue; |
| |
| if (class_name[0] == L'.') |
| { |
| 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) |
| 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); |
| } |
| |
| /* 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_fld; |
| GWin32AppInfoApplication *app; |
| 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_fld, |
| (gpointer *) &handler)) |
| { |
| gsize vi; |
| |
| if (handler->uwp_aumid != NULL) |
| continue; |
| |
| for (vi = 0; vi < handler->verbs->len; vi++) |
| { |
| 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 = { 0 }; |
| |
| handler_verb = _verb_idx (handler->verbs, vi); |
| |
| if (handler_verb->app != NULL) |
| continue; |
| |
| if (handler_verb->executable_folded == NULL) |
| continue; |
| |
| 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; |
| |
| if (app->is_uwp) |
| continue; |
| |
| 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); |
| |
| if (app_verb->executable_folded == NULL) |
| continue; |
| |
| 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; |
| |
| g_hash_table_iter_init (&app_iter, apps_by_exe); |
| |
| while (g_hash_table_iter_next (&app_iter, |
| (gpointer *) &appexe_fld_basename, |
| (gpointer *) &app)) |
| { |
| if (app->is_uwp) |
| continue; |
| |
| /* 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; |
| } |
| } |
| } |
| } |
| |
| /* 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; |
| |
| 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)) |
| { |
| gsize vi; |
| |
| if (handler->uwp_aumid != NULL) |
| continue; |
| |
| for (vi = 0; vi < handler->verbs->len; vi++) |
| { |
| GWin32AppInfoShellVerb *handler_verb; |
| GWin32AppInfoApplication *app; |
| gunichar2 *exename_utf16; |
| handler_verb = _verb_idx (handler->verbs, vi); |
| |