blob: f177878466a866ab2fd42f0277879338f7811eb0 [file] [log] [blame]
/* 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);