1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 * Copyright (C) 2014 Руслан Ижбулатов
6 * SPDX-License-Identifier: LGPL-2.1-or-later
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General
19 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 * Authors: Alexander Larsson <alexl@redhat.com>
22 * Руслан Ижбулатов <lrn1986@gmail.com>
31 #include "gcontenttype.h"
32 #include "gwin32appinfo.h"
36 #include <glib/gstdio.h>
38 #include <gio/gwin32registrykey.h>
40 /* Contains the definitions from shlobj.h that are
41 * guarded as Windows8-or-newer and are unavailable
42 * to GLib, being only Windows7-or-newer.
44 #include "gwin32api-application-activation-manager.h"
47 /* For SHLoadIndirectString() */
50 #include <glib/gstdioprivate.h>
51 #include "giowin32-priv.h"
52 #include "glib-private.h"
54 /* We need to watch 8 places:
55 * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations
56 * (anything below that key)
57 * On change: re-enumerate subkeys, read their values.
58 * 1) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts
59 * (anything below that key)
60 * On change: re-enumerate subkeys
61 * 2) HKEY_CURRENT_USER\\Software\\Clients (anything below that key)
62 * On change: re-read the whole hierarchy of handlers
63 * 3) HKEY_LOCAL_MACHINE\\Software\\Clients (anything below that key)
64 * On change: re-read the whole hierarchy of handlers
65 * 4) HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications (values of that key)
66 * On change: re-read the value list of registered applications
67 * 5) HKEY_CURRENT_USER\\Software\\RegisteredApplications (values of that key)
68 * On change: re-read the value list of registered applications
69 * 6) HKEY_CLASSES_ROOT\\Applications (anything below that key)
70 * On change: re-read the whole hierarchy of apps
71 * 7) HKEY_CLASSES_ROOT (only its subkeys)
72 * On change: re-enumerate subkeys, try to filter out wrong names.
75 * About verbs. A registry key (the name of that key is known as ProgID)
76 * can contain a "shell" subkey, which can then contain a number of verb
77 * subkeys (the most common being the "open" verb), and each of these
78 * contains a "command" subkey, which has a default string value that
79 * is the command to be run.
80 * Most ProgIDs are in HKEY_CLASSES_ROOT, but some are nested deeper in
81 * the registry (such as HKEY_CURRENT_USER\\Software\\<softwarename>).
83 * Verb selection works like this (according to https://docs.microsoft.com/en-us/windows/win32/shell/context ):
84 * 1) If "open" verb is available, that verb is used.
85 * 2) If the Shell subkey has a default string value, and if a verb subkey
86 * with that name exists, that verb is used.
87 * 3) The first subkey found in the list of verb subkeys is used.
88 * 4) The "openwith" verb is used
90 * Testing suggests that Windows never reaches the point 4 in any realistic
91 * circumstances. If a "command" subkey is missing for a verb, or if it has
92 * an empty string as its default value, the app launch fails
93 * (the "openwith" verb is not used, even if it's present).
94 * If the command is present, but is not valid (runs nonexisting executable,
95 * for example), then other verbs are not checked.
96 * It seems that when the documentation said "openwith verb", it meant
97 * that Windows invokes the default "Open with..." dialog (it does not
98 * look at the "openwith" verb subkey, even if it's there).
99 * If a verb subkey that is supposed to be used is present, but it lacks
100 * a command subkey, an error message is shown and nothing else happens.
103 #define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index))
105 #define _lookup_by_verb(array, verb, dst, itemtype) do { \
108 for (_index = 0; array && _index < array->len; _index++) \
110 _v = (itemtype *) g_ptr_array_index (array, _index); \
111 if (_wcsicmp (_v->verb_name, (verb)) == 0) \
117 if (array == NULL || _index >= array->len) \
121 #define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb)
123 /* Because with subcommands a verb would have
124 * a name like "foo\\bar", but the key its command
125 * should be looked for is "shell\\foo\\shell\\bar\\command"
127 typedef struct _reg_verb {
129 gunichar2 *shellpath;
132 typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema;
133 typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension;
134 typedef struct _GWin32AppInfoShellVerb GWin32AppInfoShellVerb;
135 typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler;
136 typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication;
138 typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass;
139 typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass;
140 typedef struct _GWin32AppInfoShellVerbClass GWin32AppInfoShellVerbClass;
141 typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass;
142 typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass;
144 struct _GWin32AppInfoURLSchemaClass
146 GObjectClass parent_class;
149 struct _GWin32AppInfoFileExtensionClass
151 GObjectClass parent_class;
154 struct _GWin32AppInfoHandlerClass
156 GObjectClass parent_class;
159 struct _GWin32AppInfoApplicationClass
161 GObjectClass parent_class;
164 struct _GWin32AppInfoShellVerbClass
166 GObjectClass parent_class;
169 struct _GWin32AppInfoURLSchema {
170 GObject parent_instance;
172 /* url schema (stuff before ':') */
175 /* url schema (stuff before ':'), in UTF-8 */
178 /* url schema (stuff before ':'), in UTF-8, folded */
179 gchar *schema_u8_folded;
181 /* Handler currently selected for this schema. Can be NULL. */
182 GWin32AppInfoHandler *chosen_handler;
184 /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema.
185 * Includes the chosen handler, if any.
187 GHashTable *handlers;
190 struct _GWin32AppInfoHandler {
191 GObject parent_instance;
193 /* Usually a class name in HKCR */
194 gunichar2 *handler_id;
196 /* Registry object obtained by opening @handler_id.
197 * Can be used to watch this handler.
198 * May be %NULL (for fake handlers that we made up).
200 GWin32RegistryKey *key;
202 /* @handler_id, in UTF-8, folded */
203 gchar *handler_id_folded;
205 /* Icon of the application for this handler */
208 /* Verbs that this handler supports */
209 GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
211 /* AppUserModelID for a UWP application. When this is not NULL,
212 * this handler launches a UWP application.
213 * UWP applications are launched using a COM interface and have no commandlines,
214 * and the verbs will reflect that too.
216 gunichar2 *uwp_aumid;
219 struct _GWin32AppInfoShellVerb {
220 GObject parent_instance;
222 /* The verb that is used to invoke this handler. */
223 gunichar2 *verb_name;
225 /* User-friendly (localized) verb name. */
226 gchar *verb_displayname;
228 /* %TRUE if this verb is for a UWP app.
229 * It means that @command, @executable and @dll_function are %NULL.
233 /* shell/verb/command */
236 /* Same as @command, but in UTF-8 */
239 /* Executable of the program (UTF-8) */
242 /* Executable of the program (for matching, in folded form; UTF-8) */
243 gchar *executable_folded;
245 /* Pointer to a location within @executable */
246 gchar *executable_basename;
248 /* If not NULL, then @executable and its derived fields contain the name
249 * of a DLL file (without the name of the function that rundll32.exe should
250 * invoke), and this field contains the name of the function to be invoked.
251 * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'.
255 /* The application that is linked to this verb. */
256 GWin32AppInfoApplication *app;
259 struct _GWin32AppInfoFileExtension {
260 GObject parent_instance;
262 /* File extension (with leading '.') */
263 gunichar2 *extension;
265 /* File extension (with leading '.'), in UTF-8 */
268 /* handler currently selected for this extension. Can be NULL. */
269 GWin32AppInfoHandler *chosen_handler;
271 /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension.
272 * Includes the chosen handler, if any.
274 GHashTable *handlers;
277 struct _GWin32AppInfoApplication {
278 GObject parent_instance;
280 /* Canonical name (used for key names).
281 * For applications tracked by id this is the root registry
282 * key path for the application.
283 * For applications tracked by executable name this is the
284 * basename of the executable.
285 * For UWP apps this is the AppUserModelID.
286 * For fake applications this is the full filename of the
287 * executable (as far as it can be inferred from a command line,
288 * meaning that it can also be a basename, if that's
289 * all that a commandline happen to give us).
291 gunichar2 *canonical_name;
293 /* @canonical_name, in UTF-8 */
294 gchar *canonical_name_u8;
296 /* @canonical_name, in UTF-8, folded */
297 gchar *canonical_name_folded;
299 /* Human-readable name in English. Can be NULL */
300 gunichar2 *pretty_name;
302 /* Human-readable name in English, UTF-8. Can be NULL */
303 gchar *pretty_name_u8;
305 /* Human-readable name in user's language. Can be NULL */
306 gunichar2 *localized_pretty_name;
308 /* Human-readable name in user's language, UTF-8. Can be NULL */
309 gchar *localized_pretty_name_u8;
311 /* Description, could be in user's language. Can be NULL */
312 gunichar2 *description;
314 /* Description, could be in user's language, UTF-8. Can be NULL */
315 gchar *description_u8;
317 /* Verbs that this application supports */
318 GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
320 /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema,
321 * UTF-8, folded) -> to a GWin32AppInfoHandler
322 * Schema can be used as a key in the urls hashmap.
324 GHashTable *supported_urls;
326 /* Explicitly supported extensions, hashmap from map-owned gchar ptr
327 * (.extension, UTF-8, folded) -> to a GWin32AppInfoHandler
328 * Extension can be used as a key in the extensions hashmap.
330 GHashTable *supported_exts;
332 /* Icon of the application (remember, handler can have its own icon too) */
335 /* Set to TRUE to prevent this app from appearing in lists of apps for
336 * opening files. This will not prevent it from appearing in lists of apps
337 * just for running, or lists of apps for opening exts/urls for which this
338 * app reports explicit support.
340 gboolean no_open_with;
342 /* Set to TRUE for applications from HKEY_CURRENT_USER.
343 * Give them priority over applications from HKEY_LOCAL_MACHINE, when all
344 * other things are equal.
346 gboolean user_specific;
348 /* Set to TRUE for applications that are machine-wide defaults (i.e. default
350 gboolean default_app;
352 /* Set to TRUE for UWP applications */
356 #define G_TYPE_WIN32_APPINFO_URL_SCHEMA (g_win32_appinfo_url_schema_get_type ())
357 #define G_WIN32_APPINFO_URL_SCHEMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_URL_SCHEMA, GWin32AppInfoURLSchema))
359 #define G_TYPE_WIN32_APPINFO_FILE_EXTENSION (g_win32_appinfo_file_extension_get_type ())
360 #define G_WIN32_APPINFO_FILE_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_FILE_EXTENSION, GWin32AppInfoFileExtension))
362 #define G_TYPE_WIN32_APPINFO_HANDLER (g_win32_appinfo_handler_get_type ())
363 #define G_WIN32_APPINFO_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_HANDLER, GWin32AppInfoHandler))
365 #define G_TYPE_WIN32_APPINFO_APPLICATION (g_win32_appinfo_application_get_type ())
366 #define G_WIN32_APPINFO_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_APPLICATION, GWin32AppInfoApplication))
368 #define G_TYPE_WIN32_APPINFO_SHELL_VERB (g_win32_appinfo_shell_verb_get_type ())
369 #define G_WIN32_APPINFO_SHELL_VERB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_SHELL_VERB, GWin32AppInfoShellVerb))
371 GType g_win32_appinfo_url_schema_get_type (void) G_GNUC_CONST;
372 GType g_win32_appinfo_file_extension_get_type (void) G_GNUC_CONST;
373 GType g_win32_appinfo_shell_verb_get_type (void) G_GNUC_CONST;
374 GType g_win32_appinfo_handler_get_type (void) G_GNUC_CONST;
375 GType g_win32_appinfo_application_get_type (void) G_GNUC_CONST;
377 G_DEFINE_TYPE (GWin32AppInfoURLSchema, g_win32_appinfo_url_schema, G_TYPE_OBJECT)
378 G_DEFINE_TYPE (GWin32AppInfoFileExtension, g_win32_appinfo_file_extension, G_TYPE_OBJECT)
379 G_DEFINE_TYPE (GWin32AppInfoShellVerb, g_win32_appinfo_shell_verb, G_TYPE_OBJECT)
380 G_DEFINE_TYPE (GWin32AppInfoHandler, g_win32_appinfo_handler, G_TYPE_OBJECT)
381 G_DEFINE_TYPE (GWin32AppInfoApplication, g_win32_appinfo_application, G_TYPE_OBJECT)
384 g_win32_appinfo_url_schema_dispose (GObject *object)
386 GWin32AppInfoURLSchema *url = G_WIN32_APPINFO_URL_SCHEMA (object);
388 g_clear_pointer (&url->schema, g_free);
389 g_clear_pointer (&url->schema_u8, g_free);
390 g_clear_pointer (&url->schema_u8_folded, g_free);
391 g_clear_object (&url->chosen_handler);
392 g_clear_pointer (&url->handlers, g_hash_table_destroy);
393 G_OBJECT_CLASS (g_win32_appinfo_url_schema_parent_class)->dispose (object);
398 g_win32_appinfo_handler_dispose (GObject *object)
400 GWin32AppInfoHandler *handler = G_WIN32_APPINFO_HANDLER (object);
402 g_clear_pointer (&handler->handler_id, g_free);
403 g_clear_pointer (&handler->handler_id_folded, g_free);
404 g_clear_object (&handler->key);
405 g_clear_object (&handler->icon);
406 g_clear_pointer (&handler->verbs, g_ptr_array_unref);
407 g_clear_pointer (&handler->uwp_aumid, g_free);
408 G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object);
412 g_win32_appinfo_file_extension_dispose (GObject *object)
414 GWin32AppInfoFileExtension *ext = G_WIN32_APPINFO_FILE_EXTENSION (object);
416 g_clear_pointer (&ext->extension, g_free);
417 g_clear_pointer (&ext->extension_u8, g_free);
418 g_clear_object (&ext->chosen_handler);
419 g_clear_pointer (&ext->handlers, g_hash_table_destroy);
420 G_OBJECT_CLASS (g_win32_appinfo_file_extension_parent_class)->dispose (object);
424 g_win32_appinfo_shell_verb_dispose (GObject *object)
426 GWin32AppInfoShellVerb *shverb = G_WIN32_APPINFO_SHELL_VERB (object);
428 g_clear_pointer (&shverb->verb_name, g_free);
429 g_clear_pointer (&shverb->verb_displayname, g_free);
430 g_clear_pointer (&shverb->command, g_free);
431 g_clear_pointer (&shverb->command_utf8, g_free);
432 g_clear_pointer (&shverb->executable_folded, g_free);
433 g_clear_pointer (&shverb->executable, g_free);
434 g_clear_pointer (&shverb->dll_function, g_free);
435 g_clear_object (&shverb->app);
436 G_OBJECT_CLASS (g_win32_appinfo_shell_verb_parent_class)->dispose (object);
440 g_win32_appinfo_application_dispose (GObject *object)
442 GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object);
444 g_clear_pointer (&app->canonical_name_u8, g_free);
445 g_clear_pointer (&app->canonical_name_folded, g_free);
446 g_clear_pointer (&app->canonical_name, g_free);
447 g_clear_pointer (&app->pretty_name, g_free);
448 g_clear_pointer (&app->localized_pretty_name, g_free);
449 g_clear_pointer (&app->description, g_free);
450 g_clear_pointer (&app->pretty_name_u8, g_free);
451 g_clear_pointer (&app->localized_pretty_name_u8, g_free);
452 g_clear_pointer (&app->description_u8, g_free);
453 g_clear_pointer (&app->supported_urls, g_hash_table_destroy);
454 g_clear_pointer (&app->supported_exts, g_hash_table_destroy);
455 g_clear_object (&app->icon);
456 g_clear_pointer (&app->verbs, g_ptr_array_unref);
457 G_OBJECT_CLASS (g_win32_appinfo_application_parent_class)->dispose (object);
461 g_win32_appinfo_application_get_some_name (GWin32AppInfoApplication *app)
463 if (app->localized_pretty_name_u8)
464 return app->localized_pretty_name_u8;
466 if (app->pretty_name_u8)
467 return app->pretty_name_u8;
469 return app->canonical_name_u8;
473 g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass)
475 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
477 gobject_class->dispose = g_win32_appinfo_url_schema_dispose;
481 g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klass)
483 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
485 gobject_class->dispose = g_win32_appinfo_file_extension_dispose;
489 g_win32_appinfo_shell_verb_class_init (GWin32AppInfoShellVerbClass *klass)
491 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
493 gobject_class->dispose = g_win32_appinfo_shell_verb_dispose;
497 g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass)
499 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
501 gobject_class->dispose = g_win32_appinfo_handler_dispose;
505 g_win32_appinfo_application_class_init (GWin32AppInfoApplicationClass *klass)
507 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
509 gobject_class->dispose = g_win32_appinfo_application_dispose;
513 g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self)
515 self->handlers = g_hash_table_new_full (g_str_hash,
522 g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self)
527 g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
529 self->handlers = g_hash_table_new_full (g_str_hash,
536 g_win32_appinfo_handler_init (GWin32AppInfoHandler *self)
538 self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
542 g_win32_appinfo_application_init (GWin32AppInfoApplication *self)
544 self->supported_urls = g_hash_table_new_full (g_str_hash,
548 self->supported_exts = g_hash_table_new_full (g_str_hash,
552 self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
555 /* The AppInfo threadpool that does asynchronous AppInfo tree rebuilds */
556 static GThreadPool *gio_win32_appinfo_threadpool;
558 /* This mutex is held by a thread that reads or writes the AppInfo tree.
559 * (tree object references can be obtained and later read without
560 * holding this mutex, since objects are practically immutable).
562 static GMutex gio_win32_appinfo_mutex;
564 /* Any thread wanting to access AppInfo can wait on this condition */
565 static GCond gio_win32_appinfo_cond;
567 /* Increased to indicate that AppInfo tree does needs to be rebuilt.
568 * AppInfo thread checks this to see if it needs to
569 * do a tree re-build. If the value changes during a rebuild,
570 * another rebuild is triggered after that.
571 * Other threads check this to see if they need
572 * to wait for a tree re-build to finish.
574 static gint gio_win32_appinfo_update_counter = 0;
576 /* Map of owned ".ext" (with '.', UTF-8, folded)
577 * to GWin32AppInfoFileExtension ptr
579 static GHashTable *extensions = NULL;
581 /* Map of owned "schema" (without ':', UTF-8, folded)
582 * to GWin32AppInfoURLSchema ptr
584 static GHashTable *urls = NULL;
586 /* Map of owned "appID" (UTF-8, folded) to
587 * a GWin32AppInfoApplication
589 static GHashTable *apps_by_id = NULL;
591 /* Map of owned "app.exe" (UTF-8, folded) to
592 * a GWin32AppInfoApplication.
593 * This map and its values are separate from apps_by_id. The fact that an app
594 * with known ID has the same executable [base]name as an app in this map does
595 * not mean that they are the same application.
597 static GHashTable *apps_by_exe = NULL;
599 /* Map of owned "path:\to\app.exe" (UTF-8, folded) to
600 * a GWin32AppInfoApplication.
601 * The app objects in this map are fake - they are linked to
602 * handlers that do not have any apps associated with them.
604 static GHashTable *fake_apps = NULL;
606 /* Map of owned "handler id" (UTF-8, folded)
607 * to a GWin32AppInfoHandler
609 static GHashTable *handlers = NULL;
611 /* Temporary (only exists while the registry is being scanned) table
612 * that maps GWin32RegistryKey objects (keeps a ref) to owned AUMId wchar strings.
614 static GHashTable *uwp_handler_table = NULL;
616 /* Watch this whole subtree */
617 static GWin32RegistryKey *url_associations_key;
619 /* Watch this whole subtree */
620 static GWin32RegistryKey *file_exts_key;
622 /* Watch this whole subtree */
623 static GWin32RegistryKey *user_clients_key;
625 /* Watch this whole subtree */
626 static GWin32RegistryKey *system_clients_key;
629 static GWin32RegistryKey *user_registered_apps_key;
632 static GWin32RegistryKey *system_registered_apps_key;
634 /* Watch this whole subtree */
635 static GWin32RegistryKey *applications_key;
638 static GWin32RegistryKey *classes_root_key;
640 #define URL_ASSOCIATIONS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
641 #define USER_CHOICE L"\\UserChoice"
642 #define OPEN_WITH_PROGIDS L"\\OpenWithProgids"
643 #define FILE_EXTS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\"
644 #define HKCR L"HKEY_CLASSES_ROOT\\"
645 #define HKCU L"HKEY_CURRENT_USER\\"
646 #define HKLM L"HKEY_LOCAL_MACHINE\\"
647 #define REG_PATH_MAX 256
648 #define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2))
651 * _g_win32_extract_executable(),
652 * _g_win32_fixup_broken_microsoft_rundll_commandline()
654 #include "giowin32-private.c"
656 /* for g_win32_package_parser_enum_packages() */
657 #include "gwin32packageparser.h"
660 read_handler_icon (GWin32RegistryKey *key,
663 GWin32RegistryKey *icon_key;
664 GWin32RegistryValueType default_type;
665 gchar *default_value;
671 icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);
673 if (icon_key == NULL)
676 if (g_win32_registry_key_get_value (icon_key,
681 (gpointer *) &default_value,
685 /* TODO: For UWP handlers this string is usually in @{...} form,
686 * see grab_registry_string() below. Right now this
687 * string is read as-is and the icon would silently fail to load.
688 * Also, right now handler icon is not used anywhere
689 * (only app icon is used).
691 if (default_type == G_WIN32_REGISTRY_VALUE_STR &&
692 default_value[0] != '\0')
693 *icon_out = g_themed_icon_new (default_value);
695 g_clear_pointer (&default_value, g_free);
698 g_object_unref (icon_key);
702 reg_verb_free (gpointer p)
707 g_free (((reg_verb *) p)->name);
708 g_free (((reg_verb *) p)->shellpath);
712 #define is_open(x) ( \
713 ((x)[0] == L'o' || (x)[0] == L'O') && \
714 ((x)[1] == L'p' || (x)[1] == L'P') && \
715 ((x)[2] == L'e' || (x)[2] == L'E') && \
716 ((x)[3] == L'n' || (x)[3] == L'N') && \
720 /* default verb (if any) comes first,
721 * then "open", then the rest of the verbs
722 * are sorted alphabetically
725 compare_verbs (gconstpointer a,
729 const reg_verb *ca = (const reg_verb *) a;
730 const reg_verb *cb = (const reg_verb *) b;
731 const gunichar2 *def = (const gunichar2 *) user_data;
737 if (_wcsicmp (ca->name, def) == 0)
739 else if (_wcsicmp (cb->name, def) == 0)
743 is_open_ca = is_open (ca->name);
744 is_open_cb = is_open (cb->name);
746 if (is_open_ca && !is_open_cb)
748 else if (is_open_ca && !is_open_cb)
751 return _wcsicmp (ca->name, cb->name);
754 static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) G_GNUC_NULL_TERMINATED;
755 static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_list components);
757 static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED;
759 /* Called by process_verbs_commands.
760 * @verb is a verb name
761 * @command_line is the commandline of that verb
762 * @command_line_utf8 is the UTF-8 version of @command_line
763 * @verb_displayname is the prettier display name of the verb (might be NULL)
764 * @verb_is_preferred is TRUE if the verb is the preferred one
765 * @invent_new_verb_name is TRUE when the verb should be added
766 * even if a verb with such
767 * name already exists (in which case
768 * a new name is invented), unless
769 * the existing verb runs exactly the same
772 typedef void (*verb_command_func) (gpointer handler_data1,
773 gpointer handler_data2,
774 const gunichar2 *verb,
775 const gunichar2 *command_line,
776 const gchar *command_line_utf8,
777 const gchar *verb_displayname,
778 gboolean verb_is_preferred,
779 gboolean invent_new_verb_name);
781 static gunichar2 * decide_which_id_to_use (const gunichar2 *program_id,
782 GWin32RegistryKey **return_key,
783 gchar **return_handler_id_u8,
784 gchar **return_handler_id_u8_folded,
785 gunichar2 **return_uwp_aumid);
787 static GWin32AppInfoURLSchema * get_schema_object (const gunichar2 *schema,
788 const gchar *schema_u8,
789 const gchar *schema_u8_folded);
791 static GWin32AppInfoHandler * get_handler_object (const gchar *handler_id_u8_folded,
792 GWin32RegistryKey *handler_key,
793 const gunichar2 *handler_id,
794 const gunichar2 *uwp_aumid);
796 static GWin32AppInfoFileExtension *get_ext_object (const gunichar2 *ext,
798 const gchar *ext_u8_folded);
801 static void process_verbs_commands (GList *verbs,
802 const reg_verb *preferred_verb,
803 const gunichar2 *path_to_progid,
804 const gunichar2 *progid,
805 gboolean autoprefer_first_verb,
806 verb_command_func handler,
807 gpointer handler_data1,
808 gpointer handler_data2);
810 static void handler_add_verb (gpointer handler_data1,
811 gpointer handler_data2,
812 const gunichar2 *verb,
813 const gunichar2 *command_line,
814 const gchar *command_line_utf8,
815 const gchar *verb_displayname,
816 gboolean verb_is_preferred,
817 gboolean invent_new_verb_name);
819 static void process_uwp_verbs (GList *verbs,
820 const reg_verb *preferred_verb,
821 const gunichar2 *path_to_progid,
822 const gunichar2 *progid,
823 gboolean autoprefer_first_verb,
824 GWin32AppInfoHandler *handler_rec,
825 GWin32AppInfoApplication *app);
827 static void uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec,
828 GWin32AppInfoApplication *app,
829 const gunichar2 *verb,
830 const gchar *verb_displayname,
831 gboolean verb_is_preferred);
833 /* output_size is in *bytes*, not gunichar2s! */
835 build_registry_path (gunichar2 *output, gsize output_size, ...)
840 va_start (ap, output_size);
842 result = build_registry_pathv (output, output_size, ap);
849 /* output_size is in *bytes*, not gunichar2s! */
851 build_registry_pathv (gunichar2 *output, gsize output_size, va_list components)
855 gunichar2 *component;
861 va_copy (lentest, components);
863 for (length = 0, component = va_arg (lentest, gunichar2 *);
865 component = va_arg (lentest, gunichar2 *))
867 length += wcslen (component);
872 if ((length >= REG_PATH_MAX_SIZE) ||
873 (length * sizeof (gunichar2) >= output_size))
878 for (p = output, component = va_arg (components, gunichar2 *);
880 component = va_arg (components, gunichar2 *))
882 length = wcslen (component);
883 wcscat (p, component);
891 static GWin32RegistryKey *
892 _g_win32_registry_key_build_and_new_w (GError **error, ...)
895 gunichar2 key_path[REG_PATH_MAX_SIZE + 1];
896 GWin32RegistryKey *key;
898 va_start (ap, error);
902 if (build_registry_pathv (key_path, sizeof (key_path), ap))
903 key = g_win32_registry_key_new_w (key_path, error);
910 /* Gets the list of shell verbs (a GList of reg_verb, put into @verbs)
911 * from the @program_id_key.
912 * If one of the verbs should be preferred,
913 * a pointer to this verb (in the GList) will be
914 * put into @preferred_verb.
915 * Does not automatically assume that the first verb
916 * is preferred (when no other preferences exist).
917 * @verbname_prefix is prefixed to the name of the verb
918 * (this is used for subcommands) and is initially an
920 * @verbshell_prefix is the subkey of @program_id_key
921 * that contains the verbs. It is "Shell" initially,
922 * but grows with recursive invocations (for subcommands).
923 * @is_uwp points to a boolean, which
924 * indicates whether the function is being called for a UWP app.
925 * It might be switched from %TRUE to %FALSE on return,
926 * if the application turns out to not to be UWP on closer inspection.
927 * If the application is already known not to be UWP before the
928 * call, this pointer can be %NULL instead.
929 * Returns TRUE on success, FALSE on failure.
932 get_verbs (GWin32RegistryKey *program_id_key,
933 const reg_verb **preferred_verb,
935 const gunichar2 *verbname_prefix,
936 const gunichar2 *verbshell_prefix,
939 GWin32RegistrySubkeyIter iter;
940 GWin32RegistryKey *key;
941 GWin32RegistryValueType val_type;
942 gunichar2 *default_verb;
943 gsize verbshell_prefix_len;
944 gsize verbname_prefix_len;
947 g_assert (program_id_key && verbs && preferred_verb);
950 *preferred_verb = NULL;
952 key = g_win32_registry_key_get_child_w (program_id_key,
959 if (!g_win32_registry_subkey_iter_init (&iter, key, NULL))
961 g_object_unref (key);
966 verbshell_prefix_len = g_utf16_len (verbshell_prefix);
967 verbname_prefix_len = g_utf16_len (verbname_prefix);
969 while (g_win32_registry_subkey_iter_next (&iter, TRUE, NULL))
971 const gunichar2 *name;
973 GWin32RegistryKey *subkey;
974 gboolean has_subcommands;
976 GWin32RegistryValueType subc_type;
978 const gunichar2 *shell = L"Shell";
979 const gsize shell_len = g_utf16_len (shell);
981 if (!g_win32_registry_subkey_iter_get_name_w (&iter, &name, &name_len, NULL))
984 subkey = g_win32_registry_key_get_child_w (key,
988 /* We may not have the required access rights to open the child key */
992 /* The key we're looking at is "<some_root>/Shell/<this_key>",
993 * where "Shell" is verbshell_prefix.
994 * If it has a value named 'Subcommands' (doesn't matter what its data is),
995 * it means that this key has its own Shell subkey, the subkeys
996 * of which are shell commands (i.e. <some_root>/Shell/<this_key>/Shell/<some_other_keys>).
997 * To handle that, create new, extended nameprefix and shellprefix,
998 * and call the function recursively.
999 * name prefix "" -> "<this_key_name>\\"
1000 * shell prefix "Shell" -> "Shell\\<this_key_name>\\Shell"
1001 * The root, program_id_key, remains the same in all invocations.
1002 * Essentially, we're flattening the command tree into a list.
1004 has_subcommands = FALSE;
1005 if ((is_uwp == NULL || !(*is_uwp)) && /* Assume UWP apps don't have subcommands */
1006 g_win32_registry_key_get_value_w (subkey,
1014 subc_type == G_WIN32_REGISTRY_VALUE_STR)
1016 gboolean dummy = FALSE;
1017 gunichar2 *new_nameprefix = g_new (gunichar2, verbname_prefix_len + name_len + 1 + 1);
1018 gunichar2 *new_shellprefix = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1);
1019 memcpy (&new_shellprefix[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
1020 new_shellprefix[verbshell_prefix_len] = L'\\';
1021 memcpy (&new_shellprefix[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
1022 new_shellprefix[verbshell_prefix_len + 1 + name_len] = L'\\';
1023 memcpy (&new_shellprefix[verbshell_prefix_len + 1 + name_len + 1], shell, shell_len * sizeof (gunichar2));
1024 new_shellprefix[verbshell_prefix_len + 1 + name_len + 1 + shell_len] = 0;
1026 memcpy (&new_nameprefix[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
1027 memcpy (&new_nameprefix[verbname_prefix_len], name, (name_len) * sizeof (gunichar2));
1028 new_nameprefix[verbname_prefix_len + name_len] = L'\\';
1029 new_nameprefix[verbname_prefix_len + name_len + 1] = 0;
1030 has_subcommands = get_verbs (program_id_key, &tmp, verbs, new_nameprefix, new_shellprefix, &dummy);
1031 g_free (new_shellprefix);
1032 g_free (new_nameprefix);
1035 /* Presence of subcommands means that this key itself is not a command-key */
1036 if (has_subcommands)
1038 g_clear_object (&subkey);
1042 if (is_uwp != NULL && *is_uwp &&
1043 !g_win32_registry_key_get_value_w (subkey,
1046 L"ActivatableClassId",
1052 /* We expected a UWP app, but it lacks ActivatableClassId
1053 * on a verb, which means that it does not behave like
1054 * a UWP app should (msedge being an example - it's UWP,
1055 * but has its own launchable exe file and a simple ID),
1056 * so we have to treat it like a normal app.
1061 g_clear_object (&subkey);
1063 /* We don't look at the command sub-key and its value (the actual command line) here.
1064 * We save the registry path instead, and use it later in process_verbs_commands().
1065 * The name of the verb is also saved.
1066 * verbname_prefix is prefixed to the verb name (it's either an empty string
1067 * or already ends with a '\\', so no extra separators needed).
1068 * verbshell_prefix is prefixed to the verb key path (this one needs a separator,
1069 * because it never has one - all verbshell prefixes end with "Shell", not "Shell\\")
1071 rverb = g_new0 (reg_verb, 1);
1072 rverb->name = g_new (gunichar2, verbname_prefix_len + name_len + 1);
1073 memcpy (&rverb->name[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
1074 memcpy (&rverb->name[verbname_prefix_len], name, name_len * sizeof (gunichar2));
1075 rverb->name[verbname_prefix_len + name_len] = 0;
1076 rverb->shellpath = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1);
1077 memcpy (&rverb->shellpath[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
1078 memcpy (&rverb->shellpath[verbshell_prefix_len], L"\\", sizeof (gunichar2));
1079 memcpy (&rverb->shellpath[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
1080 rverb->shellpath[verbshell_prefix_len + 1 + name_len] = 0;
1081 *verbs = g_list_append (*verbs, rverb);
1084 g_win32_registry_subkey_iter_clear (&iter);
1088 g_object_unref (key);
1093 default_verb = NULL;
1095 if (g_win32_registry_key_get_value_w (key,
1100 (void **) &default_verb,
1103 (val_type != G_WIN32_REGISTRY_VALUE_STR ||
1104 g_utf16_len (default_verb) <= 0))
1105 g_clear_pointer (&default_verb, g_free);
1107 g_object_unref (key);
1109 /* Only sort at the top level */
1110 if (verbname_prefix[0] == 0)
1112 *verbs = g_list_sort_with_data (*verbs, compare_verbs, default_verb);
1114 for (i = *verbs; default_verb && *preferred_verb == NULL && i; i = i->next)
1115 if (_wcsicmp (default_verb, ((const reg_verb *) i->data)->name) == 0)
1116 *preferred_verb = (const reg_verb *) i->data;
1119 g_clear_pointer (&default_verb, g_free);
1124 /* Grabs a URL association (from HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
1125 * or from an application with Capabilities, or just a schema subkey in HKCR).
1126 * @program_id is a ProgID of the handler for the URL.
1127 * @schema is the schema for the URL.
1128 * @schema_u8 and @schema_u8_folded are UTF-8 and folded UTF-8
1130 * @app is the app to which the URL handler belongs (can be NULL).
1131 * @is_user_choice is TRUE if this association is clearly preferred
1134 get_url_association (const gunichar2 *program_id,
1135 const gunichar2 *schema,
1136 const gchar *schema_u8,
1137 const gchar *schema_u8_folded,
1138 GWin32AppInfoApplication *app,
1139 gboolean is_user_choice)
1141 GWin32AppInfoURLSchema *schema_rec;
1142 GWin32AppInfoHandler *handler_rec;
1143 gunichar2 *handler_id;
1145 const reg_verb *preferred_verb;
1146 gchar *handler_id_u8;
1147 gchar *handler_id_u8_folded;
1148 gunichar2 *uwp_aumid;
1150 GWin32RegistryKey *handler_key;
1152 if ((handler_id = decide_which_id_to_use (program_id,
1155 &handler_id_u8_folded,
1156 &uwp_aumid)) == NULL)
1159 is_uwp = uwp_aumid != NULL;
1161 if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1163 g_clear_pointer (&handler_id, g_free);
1164 g_clear_pointer (&handler_id_u8, g_free);
1165 g_clear_pointer (&handler_id_u8_folded, g_free);
1166 g_clear_object (&handler_key);
1167 g_clear_pointer (&uwp_aumid, g_free);
1172 if (!is_uwp && uwp_aumid != NULL)
1173 g_clear_pointer (&uwp_aumid, g_free);
1175 schema_rec = get_schema_object (schema,
1179 handler_rec = get_handler_object (handler_id_u8_folded,
1184 if (is_user_choice || schema_rec->chosen_handler == NULL)
1185 g_set_object (&schema_rec->chosen_handler, handler_rec);
1187 g_hash_table_insert (schema_rec->handlers,
1188 g_strdup (handler_id_u8_folded),
1189 g_object_ref (handler_rec));
1191 g_clear_object (&handler_key);
1194 g_hash_table_insert (app->supported_urls,
1195 g_strdup (schema_rec->schema_u8_folded),
1196 g_object_ref (handler_rec));
1198 if (uwp_aumid == NULL)
1199 process_verbs_commands (g_steal_pointer (&verbs),
1208 process_uwp_verbs (g_steal_pointer (&verbs),
1217 g_clear_pointer (&handler_id_u8, g_free);
1218 g_clear_pointer (&handler_id_u8_folded, g_free);
1219 g_clear_pointer (&handler_id, g_free);
1220 g_clear_pointer (&uwp_aumid, g_free);
1223 /* Grabs a file extension association (from HKCR\.ext or similar).
1224 * @program_id is a ProgID of the handler for the extension.
1225 * @file_extension is the extension (with the leading '.')
1226 * @app is the app to which the extension handler belongs (can be NULL).
1227 * @is_user_choice is TRUE if this is clearly the preferred association
1230 get_file_ext (const gunichar2 *program_id,
1231 const gunichar2 *file_extension,
1232 GWin32AppInfoApplication *app,
1233 gboolean is_user_choice)
1235 GWin32AppInfoHandler *handler_rec;
1236 gunichar2 *handler_id;
1237 const reg_verb *preferred_verb;
1239 gchar *handler_id_u8;
1240 gchar *handler_id_u8_folded;
1241 gunichar2 *uwp_aumid;
1243 GWin32RegistryKey *handler_key;
1244 GWin32AppInfoFileExtension *file_extn;
1245 gchar *file_extension_u8;
1246 gchar *file_extension_u8_folded;
1248 if ((handler_id = decide_which_id_to_use (program_id,
1251 &handler_id_u8_folded,
1252 &uwp_aumid)) == NULL)
1255 if (!g_utf16_to_utf8_and_fold (file_extension,
1258 &file_extension_u8_folded))
1260 g_clear_pointer (&handler_id, g_free);
1261 g_clear_pointer (&handler_id_u8, g_free);
1262 g_clear_pointer (&handler_id_u8_folded, g_free);
1263 g_clear_pointer (&uwp_aumid, g_free);
1264 g_clear_object (&handler_key);
1269 is_uwp = uwp_aumid != NULL;
1271 if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1273 g_clear_pointer (&handler_id, g_free);
1274 g_clear_pointer (&handler_id_u8, g_free);
1275 g_clear_pointer (&handler_id_u8_folded, g_free);
1276 g_clear_object (&handler_key);
1277 g_clear_pointer (&file_extension_u8, g_free);
1278 g_clear_pointer (&file_extension_u8_folded, g_free);
1279 g_clear_pointer (&uwp_aumid, g_free);
1284 if (!is_uwp && uwp_aumid != NULL)
1285 g_clear_pointer (&uwp_aumid, g_free);
1287 file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded);
1289 handler_rec = get_handler_object (handler_id_u8_folded,
1294 if (is_user_choice || file_extn->chosen_handler == NULL)
1295 g_set_object (&file_extn->chosen_handler, handler_rec);
1297 g_hash_table_insert (file_extn->handlers,
1298 g_strdup (handler_id_u8_folded),
1299 g_object_ref (handler_rec));
1302 g_hash_table_insert (app->supported_exts,
1303 g_strdup (file_extension_u8_folded),
1304 g_object_ref (handler_rec));
1306 g_clear_pointer (&file_extension_u8, g_free);
1307 g_clear_pointer (&file_extension_u8_folded, g_free);
1308 g_clear_object (&handler_key);
1310 if (uwp_aumid == NULL)
1311 process_verbs_commands (g_steal_pointer (&verbs),
1320 process_uwp_verbs (g_steal_pointer (&verbs),
1328 g_clear_pointer (&handler_id, g_free);
1329 g_clear_pointer (&handler_id_u8, g_free);
1330 g_clear_pointer (&handler_id_u8_folded, g_free);
1331 g_clear_pointer (&uwp_aumid, g_free);
1334 /* Returns either a @program_id or the string from
1335 * the default value of the program_id key (which is a name
1336 * of a proxy class), or NULL.
1337 * Does not check that proxy represents a valid
1338 * record, just checks that it exists.
1339 * Can return the class key (HKCR/program_id or HKCR/proxy_id).
1340 * Can convert returned value to UTF-8 and fold it.
1343 decide_which_id_to_use (const gunichar2 *program_id,
1344 GWin32RegistryKey **return_key,
1345 gchar **return_handler_id_u8,
1346 gchar **return_handler_id_u8_folded,
1347 gunichar2 **return_uwp_aumid)
1349 GWin32RegistryKey *key;
1350 GWin32RegistryKey *uwp_key;
1351 GWin32RegistryValueType val_type;
1352 gunichar2 *proxy_id;
1353 gunichar2 *return_id;
1354 gunichar2 *uwp_aumid;
1356 gchar *handler_id_u8;
1357 gchar *handler_id_u8_folded;
1358 g_assert (program_id);
1363 if (return_uwp_aumid)
1364 *return_uwp_aumid = NULL;
1366 key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL);
1371 /* Check for UWP first */
1373 uwp_key = g_win32_registry_key_get_child_w (key, L"Application", NULL);
1375 if (uwp_key != NULL)
1377 got_value = g_win32_registry_key_get_value_w (uwp_key,
1382 (void **) &uwp_aumid,
1385 if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1386 g_clear_pointer (&uwp_aumid, g_free);
1388 /* Other values in the Application key contain useful information
1389 * (description, name, icon), but it's inconvenient to read
1390 * it here (we don't have an app object *yet*). Store the key
1391 * in a table instead, and look at it later.
1393 if (uwp_aumid == NULL)
1394 g_debug ("ProgramID %S looks like a UWP application, but isn't",
1397 g_hash_table_insert (uwp_handler_table, g_object_ref (uwp_key), g_wcsdup (uwp_aumid, -1));
1399 g_object_unref (uwp_key);
1402 /* Then check for proxy */
1405 if (uwp_aumid == NULL)
1407 got_value = g_win32_registry_key_get_value_w (key,
1412 (void **) &proxy_id,
1415 if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1416 g_clear_pointer (&proxy_id, g_free);
1423 GWin32RegistryKey *proxy_key;
1424 proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL);
1429 *return_key = g_steal_pointer (&proxy_key);
1430 g_clear_object (&proxy_key);
1432 return_id = g_steal_pointer (&proxy_id);
1435 g_clear_pointer (&proxy_id, g_free);
1438 if ((return_handler_id_u8 ||
1439 return_handler_id_u8_folded) &&
1440 !g_utf16_to_utf8_and_fold (return_id == NULL ? program_id : return_id,
1443 &handler_id_u8_folded))
1445 g_clear_object (&key);
1447 g_clear_object (return_key);
1448 g_clear_pointer (&return_id, g_free);
1453 if (return_handler_id_u8)
1454 *return_handler_id_u8 = g_steal_pointer (&handler_id_u8);
1455 g_clear_pointer (&handler_id_u8, g_free);
1456 if (return_handler_id_u8_folded)
1457 *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded);
1458 g_clear_pointer (&handler_id_u8_folded, g_free);
1459 if (return_uwp_aumid)
1460 *return_uwp_aumid = g_steal_pointer (&uwp_aumid);
1461 g_clear_pointer (&uwp_aumid, g_free);
1463 if (return_id == NULL && return_key)
1464 *return_key = g_steal_pointer (&key);
1465 g_clear_object (&key);
1467 if (return_id == NULL)
1468 return g_wcsdup (program_id, -1);
1473 /* Grabs the command for each verb from @verbs,
1474 * and invokes @handler for it. Consumes @verbs.
1475 * @path_to_progid and @progid are concatenated to
1476 * produce a path to the key where Shell/verb/command
1477 * subkeys are looked up.
1478 * @preferred_verb, if not NULL, will be used to inform
1479 * the @handler that a verb is preferred.
1480 * @autoprefer_first_verb will automatically make the first
1481 * verb to be preferred, if @preferred_verb is NULL.
1482 * @handler_data1 and @handler_data2 are passed to @handler as-is.
1485 process_verbs_commands (GList *verbs,
1486 const reg_verb *preferred_verb,
1487 const gunichar2 *path_to_progid,
1488 const gunichar2 *progid,
1489 gboolean autoprefer_first_verb,
1490 verb_command_func handler,
1491 gpointer handler_data1,
1492 gpointer handler_data2)
1497 g_assert (handler != NULL);
1498 g_assert (verbs != NULL);
1499 g_assert (progid != NULL);
1501 for (i = verbs; i; i = i->next)
1503 const reg_verb *verb = (const reg_verb *) i->data;
1504 GWin32RegistryKey *key;
1505 GWin32RegistryKey *verb_key;
1506 gunichar2 *command_value;
1507 gchar *command_value_utf8;
1508 GWin32RegistryValueType val_type;
1509 gunichar2 *verb_displayname;
1510 gchar *verb_displayname_u8;
1512 key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1513 L"\\", verb->shellpath, L"\\command", NULL);
1517 g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey",
1518 path_to_progid, progid, verb->shellpath);
1522 command_value = NULL;
1523 got_value = g_win32_registry_key_get_value_w (key,
1528 (void **) &command_value,
1531 g_clear_object (&key);
1534 val_type != G_WIN32_REGISTRY_VALUE_STR ||
1535 (command_value_utf8 = g_utf16_to_utf8 (command_value,
1541 g_clear_pointer (&command_value, g_free);
1545 verb_displayname = NULL;
1546 verb_displayname_u8 = NULL;
1547 verb_key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1548 L"\\", verb->shellpath, NULL);
1552 gsize verb_displayname_len;
1554 got_value = g_win32_registry_key_get_value_w (verb_key,
1555 g_win32_registry_get_os_dirs_w (),
1559 (void **) &verb_displayname,
1560 &verb_displayname_len,
1564 val_type == G_WIN32_REGISTRY_VALUE_STR &&
1565 verb_displayname_len > sizeof (gunichar2))
1566 verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
1568 g_clear_pointer (&verb_displayname, g_free);
1570 if (verb_displayname_u8 == NULL)
1572 got_value = g_win32_registry_key_get_value_w (verb_key,
1577 (void **) &verb_displayname,
1578 &verb_displayname_len,
1582 val_type == G_WIN32_REGISTRY_VALUE_STR &&
1583 verb_displayname_len > sizeof (gunichar2))
1584 verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
1587 g_clear_pointer (&verb_displayname, g_free);
1588 g_clear_object (&verb_key);
1591 handler (handler_data1, handler_data2, verb->name, command_value, command_value_utf8,
1592 verb_displayname_u8,
1593 (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
1594 (!preferred_verb && autoprefer_first_verb && i == verbs),
1597 g_clear_pointer (&command_value, g_free);
1598 g_clear_pointer (&command_value_utf8, g_free);
1599 g_clear_pointer (&verb_displayname_u8, g_free);
1602 g_list_free_full (verbs, reg_verb_free);
1606 process_uwp_verbs (GList *verbs,
1607 const reg_verb *preferred_verb,
1608 const gunichar2 *path_to_progid,
1609 const gunichar2 *progid,
1610 gboolean autoprefer_first_verb,
1611 GWin32AppInfoHandler *handler_rec,
1612 GWin32AppInfoApplication *app)
1616 g_assert (verbs != NULL);
1618 for (i = verbs; i; i = i->next)
1620 const reg_verb *verb = (const reg_verb *) i->data;
1621 GWin32RegistryKey *key;
1623 GWin32RegistryValueType val_type;
1627 key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1628 L"\\", verb->shellpath, NULL);
1632 g_debug ("%S%S\\%S does not exist",
1633 path_to_progid, progid, verb->shellpath);
1638 got_value = g_win32_registry_key_get_value_w (key,
1639 g_win32_registry_get_os_dirs_w (),
1641 L"ActivatableClassId",
1648 val_type == G_WIN32_REGISTRY_VALUE_STR &&
1649 acid_len > sizeof (gunichar2))
1651 /* TODO: default value of a shell subkey, if not empty,
1652 * migh contain something like @{Some.Identifier_1234.456.678.789_some_words?ms-resource://Arbitrary.Path/Pointing/Somewhere}
1653 * and it might be possible to turn it into a nice displayname.
1655 uwp_handler_add_verb (handler_rec,
1659 (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
1660 (!preferred_verb && autoprefer_first_verb && i == verbs));
1664 g_debug ("%S%S\\%S does not have an ActivatableClassId string value",
1665 path_to_progid, progid, verb->shellpath);
1668 g_clear_pointer (&acid, g_free);
1669 g_clear_object (&key);
1672 g_list_free_full (verbs, reg_verb_free);
1675 /* Looks up a schema object identified by
1676 * @schema_u8_folded in the urls hash table.
1677 * If such object doesn't exist,
1678 * creates it and puts it into the urls hash table.
1679 * Returns the object.
1681 static GWin32AppInfoURLSchema *
1682 get_schema_object (const gunichar2 *schema,
1683 const gchar *schema_u8,
1684 const gchar *schema_u8_folded)
1686 GWin32AppInfoURLSchema *schema_rec;
1688 schema_rec = g_hash_table_lookup (urls, schema_u8_folded);
1690 if (schema_rec != NULL)
1693 schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
1694 schema_rec->schema = g_wcsdup (schema, -1);
1695 schema_rec->schema_u8 = g_strdup (schema_u8);
1696 schema_rec->schema_u8_folded = g_strdup (schema_u8_folded);
1697 g_hash_table_insert (urls, g_strdup (schema_rec->schema_u8_folded), schema_rec);
1702 /* Looks up a handler object identified by
1703 * @handler_id_u8_folded in the handlers hash table.
1704 * If such object doesn't exist,
1705 * creates it and puts it into the handlers hash table.
1706 * Returns the object.
1708 static GWin32AppInfoHandler *
1709 get_handler_object (const gchar *handler_id_u8_folded,
1710 GWin32RegistryKey *handler_key,
1711 const gunichar2 *handler_id,
1712 const gunichar2 *uwp_aumid)
1714 GWin32AppInfoHandler *handler_rec;
1716 handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded);
1718 if (handler_rec != NULL)
1721 handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
1723 handler_rec->key = g_object_ref (handler_key);
1724 handler_rec->handler_id = g_wcsdup (handler_id, -1);
1725 handler_rec->handler_id_folded = g_strdup (handler_id_u8_folded);
1727 handler_rec->uwp_aumid = g_wcsdup (uwp_aumid, -1);
1729 read_handler_icon (handler_key, &handler_rec->icon);
1730 g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec);
1736 handler_add_verb (gpointer handler_data1,
1737 gpointer handler_data2,
1738 const gunichar2 *verb,
1739 const gunichar2 *command_line,
1740 const gchar *command_line_utf8,
1741 const gchar *verb_displayname,
1742 gboolean verb_is_preferred,
1743 gboolean invent_new_verb_name)
1745 GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1;
1746 GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1747 GWin32AppInfoShellVerb *shverb = NULL;
1749 _verb_lookup (handler_rec->verbs, verb, &shverb);
1754 shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1755 shverb->verb_name = g_wcsdup (verb, -1);
1756 shverb->verb_displayname = g_strdup (verb_displayname);
1757 shverb->command = g_wcsdup (command_line, -1);
1758 shverb->command_utf8 = g_strdup (command_line_utf8);
1759 shverb->is_uwp = FALSE; /* This function is for non-UWP verbs only */
1761 shverb->app = g_object_ref (app_rec);
1763 _g_win32_extract_executable (shverb->command,
1764 &shverb->executable,
1765 &shverb->executable_basename,
1766 &shverb->executable_folded,
1768 &shverb->dll_function);
1770 if (shverb->dll_function != NULL)
1771 _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1773 if (!verb_is_preferred)
1774 g_ptr_array_add (handler_rec->verbs, shverb);
1776 g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1779 /* Tries to generate a new name for a verb that looks
1780 * like "verb (%x)", where %x is an integer in range of [0;255).
1781 * On success puts new verb (and new verb displayname) into
1782 * @new_verb and @new_displayname and return TRUE.
1783 * On failure puts NULL into both and returns FALSE.
1786 generate_new_verb_name (GPtrArray *verbs,
1787 const gunichar2 *verb,
1788 const gchar *verb_displayname,
1789 gunichar2 **new_verb,
1790 gchar **new_displayname)
1793 GWin32AppInfoShellVerb *shverb = NULL;
1794 gsize orig_len = g_utf16_len (verb);
1795 gsize new_verb_name_len = orig_len + strlen (" ()") + 2 + 1;
1796 gunichar2 *new_verb_name = g_new (gunichar2, new_verb_name_len);
1799 *new_displayname = NULL;
1801 memcpy (new_verb_name, verb, orig_len * sizeof (gunichar2));
1802 for (counter = 0; counter < 255; counter++)
1804 _snwprintf (&new_verb_name[orig_len], new_verb_name_len, L" (%zx)", counter);
1805 _verb_lookup (verbs, new_verb_name, &shverb);
1809 *new_verb = new_verb_name;
1810 if (verb_displayname != NULL)
1811 *new_displayname = g_strdup_printf ("%s (%zx)", verb_displayname, counter);
1821 app_add_verb (gpointer handler_data1,
1822 gpointer handler_data2,
1823 const gunichar2 *verb,
1824 const gunichar2 *command_line,
1825 const gchar *command_line_utf8,
1826 const gchar *verb_displayname,
1827 gboolean verb_is_preferred,
1828 gboolean invent_new_verb_name)
1830 gunichar2 *new_verb = NULL;
1831 gchar *new_displayname = NULL;
1832 GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1833 GWin32AppInfoShellVerb *shverb = NULL;
1835 _verb_lookup (app_rec->verbs, verb, &shverb);
1837 /* Special logic for fake apps - do our best to
1838 * collate all possible verbs in the app,
1839 * including the verbs that have the same name but
1840 * different commandlines, in which case a new
1841 * verb name has to be invented.
1847 if (!invent_new_verb_name)
1850 for (vi = 0; vi < app_rec->verbs->len; vi++)
1852 GWin32AppInfoShellVerb *app_verb;
1854 app_verb = _verb_idx (app_rec->verbs, vi);
1856 if (_wcsicmp (command_line, app_verb->command) == 0)
1860 if (vi < app_rec->verbs->len ||
1861 !generate_new_verb_name (app_rec->verbs,
1869 shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1870 if (new_verb == NULL)
1871 shverb->verb_name = g_wcsdup (verb, -1);
1873 shverb->verb_name = g_steal_pointer (&new_verb);
1874 if (new_displayname == NULL)
1875 shverb->verb_displayname = g_strdup (verb_displayname);
1877 shverb->verb_displayname = g_steal_pointer (&new_displayname);
1879 shverb->command = g_wcsdup (command_line, -1);
1880 shverb->command_utf8 = g_strdup (command_line_utf8);
1881 shverb->app = g_object_ref (app_rec);
1883 _g_win32_extract_executable (shverb->command,
1884 &shverb->executable,
1885 &shverb->executable_basename,
1886 &shverb->executable_folded,
1888 &shverb->dll_function);
1890 if (shverb->dll_function != NULL)
1891 _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1893 if (!verb_is_preferred)
1894 g_ptr_array_add (app_rec->verbs, shverb);
1896 g_ptr_array_insert (app_rec->verbs, 0, shverb);
1900 uwp_app_add_verb (GWin32AppInfoApplication *app_rec,
1901 const gunichar2 *verb,
1902 const gchar *verb_displayname)
1904 GWin32AppInfoShellVerb *shverb = NULL;
1906 _verb_lookup (app_rec->verbs, verb, &shverb);
1911 shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1912 shverb->verb_name = g_wcsdup (verb, -1);
1913 shverb->app = g_object_ref (app_rec);
1914 shverb->verb_displayname = g_strdup (verb_displayname);
1916 shverb->is_uwp = TRUE;
1918 /* Strictly speaking, this is unnecessary, but
1919 * let's make it clear that UWP verbs have no
1920 * commands and executables.
1922 shverb->command = NULL;
1923 shverb->command_utf8 = NULL;
1924 shverb->executable = NULL;
1925 shverb->executable_basename = NULL;
1926 shverb->executable_folded = NULL;
1927 shverb->dll_function = NULL;
1929 g_ptr_array_add (app_rec->verbs, shverb);
1933 uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec,
1934 GWin32AppInfoApplication *app,
1935 const gunichar2 *verb,
1936 const gchar *verb_displayname,
1937 gboolean verb_is_preferred)
1939 GWin32AppInfoShellVerb *shverb = NULL;
1941 _verb_lookup (handler_rec->verbs, verb, &shverb);
1946 shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1947 shverb->verb_name = g_wcsdup (verb, -1);
1948 shverb->verb_displayname = g_strdup (verb_displayname);
1950 shverb->is_uwp = TRUE;
1953 shverb->app = g_object_ref (app);
1955 shverb->command = NULL;
1956 shverb->command_utf8 = NULL;
1957 shverb->executable = NULL;
1958 shverb->executable_basename = NULL;
1959 shverb->executable_folded = NULL;
1960 shverb->dll_function = NULL;
1962 if (!verb_is_preferred)
1963 g_ptr_array_add (handler_rec->verbs, shverb);
1965 g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1968 /* Looks up a file extension object identified by
1969 * @ext_u8_folded in the extensions hash table.
1970 * If such object doesn't exist,
1971 * creates it and puts it into the extensions hash table.
1972 * Returns the object.
1974 static GWin32AppInfoFileExtension *
1975 get_ext_object (const gunichar2 *ext,
1976 const gchar *ext_u8,
1977 const gchar *ext_u8_folded)
1979 GWin32AppInfoFileExtension *file_extn;
1981 if (g_hash_table_lookup_extended (extensions,
1984 (void **) &file_extn))
1987 file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
1988 file_extn->extension = g_wcsdup (ext, -1);
1989 file_extn->extension_u8 = g_strdup (ext_u8);
1990 g_hash_table_insert (extensions, g_strdup (ext_u8_folded), file_extn);
1995 /* Iterates over HKCU\\Software\\Clients or HKLM\\Software\\Clients,
1996 * (depending on @user_registry being TRUE or FALSE),
1997 * collecting applications listed there.
1998 * Puts the path to the client key for each client into @priority_capable_apps
1999 * (only for clients with file or URL associations).
2002 collect_capable_apps_from_clients (GPtrArray *capable_apps,
2003 GPtrArray *priority_capable_apps,
2004 gboolean user_registry)
2006 GWin32RegistryKey *clients;
2007 GWin32RegistrySubkeyIter clients_iter;
2009 const gunichar2 *client_type_name;
2010 gsize client_type_name_len;
2015 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
2019 g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
2022 if (clients == NULL)
2025 if (!g_win32_registry_subkey_iter_init (&clients_iter, clients, NULL))
2027 g_object_unref (clients);
2031 while (g_win32_registry_subkey_iter_next (&clients_iter, TRUE, NULL))
2033 GWin32RegistrySubkeyIter subkey_iter;
2034 GWin32RegistryKey *system_client_type;
2035 GWin32RegistryValueType default_type;
2036 gunichar2 *default_value = NULL;
2037 const gunichar2 *client_name;
2038 gsize client_name_len;
2040 if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter,
2042 &client_type_name_len,
2046 system_client_type = g_win32_registry_key_get_child_w (clients,
2050 if (system_client_type == NULL)
2053 if (g_win32_registry_key_get_value_w (system_client_type,
2058 (gpointer *) &default_value,
2062 if (default_type != G_WIN32_REGISTRY_VALUE_STR ||
2063 default_value[0] == L'\0')
2064 g_clear_pointer (&default_value, g_free);
2067 if (!g_win32_registry_subkey_iter_init (&subkey_iter,
2071 g_clear_pointer (&default_value, g_free);
2072 g_object_unref (system_client_type);
2076 while (g_win32_registry_subkey_iter_next (&subkey_iter, TRUE, NULL))
2078 GWin32RegistryKey *system_client;
2079 GWin32RegistryKey *system_client_assoc;
2083 if (!g_win32_registry_subkey_iter_get_name_w (&subkey_iter,
2089 system_client = g_win32_registry_key_get_child_w (system_client_type,
2093 if (system_client == NULL)
2098 system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2099 L"Capabilities\\FileAssociations",
2102 if (system_client_assoc != NULL)
2105 g_object_unref (system_client_assoc);
2109 system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2110 L"Capabilities\\UrlAssociations",
2113 if (system_client_assoc != NULL)
2116 g_object_unref (system_client_assoc);
2122 keyname = g_wcsdup (g_win32_registry_key_get_path_w (system_client), -1);
2124 if (default_value && wcscmp (default_value, client_name) == 0)
2125 g_ptr_array_add (priority_capable_apps, keyname);
2127 g_ptr_array_add (capable_apps, keyname);
2130 g_object_unref (system_client);
2133 g_win32_registry_subkey_iter_clear (&subkey_iter);
2134 g_clear_pointer (&default_value, g_free);
2135 g_object_unref (system_client_type);
2138 g_win32_registry_subkey_iter_clear (&clients_iter);
2139 g_object_unref (clients);
2142 /* Iterates over HKCU\\Software\\RegisteredApplications or HKLM\\Software\\RegisteredApplications,
2143 * (depending on @user_registry being TRUE or FALSE),
2144 * collecting applications listed there.
2145 * Puts the path to the app key for each app into @capable_apps.
2148 collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
2149 gboolean user_registry)
2151 GWin32RegistryValueIter iter;
2152 const gunichar2 *reg_path;
2154 gunichar2 *value_data;
2155 gsize value_data_size;
2156 GWin32RegistryValueType value_type;
2157 GWin32RegistryKey *registered_apps;
2160 reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications";
2162 reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications";
2165 g_win32_registry_key_new_w (reg_path, NULL);
2167 if (!registered_apps)
2170 if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL))
2172 g_object_unref (registered_apps);
2177 while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2179 gunichar2 possible_location[REG_PATH_MAX_SIZE + 1];
2180 GWin32RegistryKey *location;
2183 if ((!g_win32_registry_value_iter_get_value_type (&iter,
2186 (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2187 (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2188 (void **) &value_data,
2191 (value_data_size < sizeof (gunichar2)) ||
2192 (value_data[0] == L'\0'))
2195 if (!build_registry_path (possible_location, sizeof (possible_location),
2196 user_registry ? HKCU : HKLM, value_data, NULL))
2199 location = g_win32_registry_key_new_w (possible_location, NULL);
2201 if (location == NULL)
2204 p = wcsrchr (possible_location, L'\\');
2209 g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
2212 g_object_unref (location);
2215 g_win32_registry_value_iter_clear (&iter);
2216 g_object_unref (registered_apps);
2219 /* Looks up an app object identified by
2220 * @canonical_name_folded in the @app_hashmap.
2221 * If such object doesn't exist,
2222 * creates it and puts it into the @app_hashmap.
2223 * Returns the object.
2225 static GWin32AppInfoApplication *
2226 get_app_object (GHashTable *app_hashmap,
2227 const gunichar2 *canonical_name,
2228 const gchar *canonical_name_u8,
2229 const gchar *canonical_name_folded,
2230 gboolean user_specific,
2231 gboolean default_app,
2234 GWin32AppInfoApplication *app;
2236 app = g_hash_table_lookup (app_hashmap, canonical_name_folded);
2241 app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
2242 app->canonical_name = g_wcsdup (canonical_name, -1);
2243 app->canonical_name_u8 = g_strdup (canonical_name_u8);
2244 app->canonical_name_folded = g_strdup (canonical_name_folded);
2245 app->no_open_with = FALSE;
2246 app->user_specific = user_specific;
2247 app->default_app = default_app;
2248 app->is_uwp = is_uwp;
2249 g_hash_table_insert (app_hashmap,
2250 g_strdup (canonical_name_folded),
2256 /* Grabs an application that has Capabilities.
2257 * @app_key_path is the path to the application key
2258 * (which must have a "Capabilities" subkey).
2259 * @default_app is TRUE if the app has priority
2262 read_capable_app (const gunichar2 *app_key_path,
2263 gboolean user_specific,
2264 gboolean default_app)
2266 GWin32AppInfoApplication *app;
2267 gchar *canonical_name_u8 = NULL;
2268 gchar *canonical_name_folded = NULL;
2269 gchar *app_key_path_u8 = NULL;
2270 gchar *app_key_path_u8_folded = NULL;
2271 GWin32RegistryKey *appkey = NULL;
2272 gunichar2 *fallback_friendly_name;
2273 GWin32RegistryValueType vtype;
2275 gunichar2 *friendly_name;
2276 gunichar2 *description;
2277 gunichar2 *narrow_application_name;
2278 gunichar2 *icon_source;
2279 GWin32RegistryKey *capabilities;
2280 GWin32RegistryKey *default_icon_key;
2281 GWin32RegistryKey *associations;
2282 const reg_verb *preferred_verb;
2283 GList *verbs = NULL;
2284 gboolean verbs_in_root_key = TRUE;
2287 capabilities = NULL;
2289 if (!g_utf16_to_utf8_and_fold (app_key_path,
2292 &canonical_name_folded) ||
2293 !g_utf16_to_utf8_and_fold (app_key_path,
2296 &app_key_path_u8_folded) ||
2297 (appkey = g_win32_registry_key_new_w (app_key_path, NULL)) == NULL ||
2298 (capabilities = g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL)) == NULL ||
2299 !(get_verbs (appkey, &preferred_verb, &verbs, L"", L"Shell", NULL) ||
2300 (verbs_in_root_key = FALSE) ||
2301 get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell", NULL)))
2303 g_clear_pointer (&canonical_name_u8, g_free);
2304 g_clear_pointer (&canonical_name_folded, g_free);
2305 g_clear_object (&appkey);
2306 g_clear_object (&capabilities);
2307 g_clear_pointer (&app_key_path_u8, g_free);
2308 g_clear_pointer (&app_key_path_u8_folded, g_free);
2313 app = get_app_object (apps_by_id,
2316 canonical_name_folded,
2321 process_verbs_commands (g_steal_pointer (&verbs),
2323 L"", /* [ab]use the fact that two strings are simply concatenated */
2324 verbs_in_root_key ? app_key_path : g_win32_registry_key_get_path_w (capabilities),
2330 fallback_friendly_name = NULL;
2331 success = g_win32_registry_key_get_value_w (appkey,
2336 (void **) &fallback_friendly_name,
2340 if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2341 g_clear_pointer (&fallback_friendly_name, g_free);
2343 if (fallback_friendly_name &&
2344 app->pretty_name == NULL)
2346 app->pretty_name = g_wcsdup (fallback_friendly_name, -1);
2347 g_clear_pointer (&app->pretty_name_u8, g_free);
2348 app->pretty_name_u8 = g_utf16_to_utf8 (fallback_friendly_name,
2355 friendly_name = NULL;
2356 success = g_win32_registry_key_get_value_w (capabilities,
2357 g_win32_registry_get_os_dirs_w (),
2361 (void **) &friendly_name,
2366 vtype != G_WIN32_REGISTRY_VALUE_STR)
2367 g_clear_pointer (&friendly_name, g_free);
2369 if (friendly_name &&
2370 app->localized_pretty_name == NULL)
2372 app->localized_pretty_name = g_wcsdup (friendly_name, -1);
2373 g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2374 app->localized_pretty_name_u8 = g_utf16_to_utf8 (friendly_name,
2382 success = g_win32_registry_key_get_value_w (capabilities,
2383 g_win32_registry_get_os_dirs_w (),
2385 L"ApplicationDescription",
2387 (void **) &description,
2391 if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2392 g_clear_pointer (&description, g_free);
2394 if (description && app->description == NULL)
2396 app->description = g_wcsdup (description, -1);
2397 g_clear_pointer (&app->description_u8, g_free);
2398 app->description_u8 = g_utf16_to_utf8 (description, -1, NULL, NULL, NULL);
2401 default_icon_key = g_win32_registry_key_get_child_w (appkey,
2407 if (default_icon_key != NULL)
2409 success = g_win32_registry_key_get_value_w (default_icon_key,
2414 (void **) &icon_source,
2419 vtype != G_WIN32_REGISTRY_VALUE_STR)
2420 g_clear_pointer (&icon_source, g_free);
2422 g_object_unref (default_icon_key);
2425 if (icon_source == NULL)
2427 success = g_win32_registry_key_get_value_w (capabilities,
2432 (void **) &icon_source,
2437 vtype != G_WIN32_REGISTRY_VALUE_STR)
2438 g_clear_pointer (&icon_source, g_free);
2444 gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2445 app->icon = g_themed_icon_new (name);
2449 narrow_application_name = NULL;
2450 success = g_win32_registry_key_get_value_w (capabilities,
2451 g_win32_registry_get_os_dirs_w (),
2455 (void **) &narrow_application_name,
2459 if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2460 g_clear_pointer (&narrow_application_name, g_free);
2462 if (narrow_application_name &&
2463 app->localized_pretty_name == NULL)
2465 app->localized_pretty_name = g_wcsdup (narrow_application_name, -1);
2466 g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2467 app->localized_pretty_name_u8 = g_utf16_to_utf8 (narrow_application_name,
2474 associations = g_win32_registry_key_get_child_w (capabilities,
2475 L"FileAssociations",
2478 if (associations != NULL)
2480 GWin32RegistryValueIter iter;
2482 if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2484 gunichar2 *file_extension;
2485 gunichar2 *extension_handler;
2486 gsize file_extension_len;
2487 gsize extension_handler_size;
2488 GWin32RegistryValueType value_type;
2490 while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2492 if ((!g_win32_registry_value_iter_get_value_type (&iter,
2495 (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2496 (!g_win32_registry_value_iter_get_name_w (&iter,
2498 &file_extension_len,
2500 (file_extension_len <= 0) ||
2501 (file_extension[0] != L'.') ||
2502 (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2503 (void **) &extension_handler,
2504 &extension_handler_size,
2506 (extension_handler_size < sizeof (gunichar2)) ||
2507 (extension_handler[0] == L'\0'))
2510 get_file_ext (extension_handler, file_extension, app, FALSE);
2513 g_win32_registry_value_iter_clear (&iter);
2516 g_object_unref (associations);
2519 associations = g_win32_registry_key_get_child_w (capabilities, L"URLAssociations", NULL);
2521 if (associations != NULL)
2523 GWin32RegistryValueIter iter;
2525 if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2527 gunichar2 *url_schema;
2528 gunichar2 *schema_handler;
2529 gsize url_schema_len;
2530 gsize schema_handler_size;
2531 GWin32RegistryValueType value_type;
2533 while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2535 gchar *schema_u8 = NULL;
2536 gchar *schema_u8_folded = NULL;
2538 if ((!g_win32_registry_value_iter_get_value_type (&iter,
2541 ((value_type != G_WIN32_REGISTRY_VALUE_STR) &&
2542 (value_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) ||
2543 (!g_win32_registry_value_iter_get_name_w (&iter,
2547 (url_schema_len <= 0) ||
2548 (url_schema[0] == L'\0') ||
2549 (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2550 (void **) &schema_handler,
2551 &schema_handler_size,
2553 (schema_handler_size < sizeof (gunichar2)) ||
2554 (schema_handler[0] == L'\0'))
2559 if (g_utf16_to_utf8_and_fold (url_schema,
2563 get_url_association (schema_handler, url_schema, schema_u8, schema_u8_folded, app, FALSE);
2565 g_clear_pointer (&schema_u8, g_free);
2566 g_clear_pointer (&schema_u8_folded, g_free);
2569 g_win32_registry_value_iter_clear (&iter);
2572 g_object_unref (associations);
2575 g_clear_pointer (&fallback_friendly_name, g_free);
2576 g_clear_pointer (&description, g_free);
2577 g_clear_pointer (&icon_source, g_free);
2578 g_clear_pointer (&narrow_application_name, g_free);
2580 g_object_unref (appkey);
2581 g_object_unref (capabilities);
2582 g_clear_pointer (&app_key_path_u8, g_free);
2583 g_clear_pointer (&app_key_path_u8_folded, g_free);
2584 g_clear_pointer (&canonical_name_u8, g_free);
2585 g_clear_pointer (&canonical_name_folded, g_free);
2588 /* Iterates over subkeys in HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
2589 * and calls get_url_association() for each one that has a user-chosen handler.
2592 read_urls (GWin32RegistryKey *url_associations)
2594 GWin32RegistrySubkeyIter url_iter;
2596 if (url_associations == NULL)
2599 if (!g_win32_registry_subkey_iter_init (&url_iter, url_associations, NULL))
2602 while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL))
2604 gchar *schema_u8 = NULL;
2605 gchar *schema_u8_folded = NULL;
2606 const gunichar2 *url_schema = NULL;
2607 gunichar2 *program_id = NULL;
2608 GWin32RegistryKey *user_choice = NULL;
2609 gsize url_schema_len;
2610 GWin32RegistryValueType val_type;
2612 if (g_win32_registry_subkey_iter_get_name_w (&url_iter,
2616 g_utf16_to_utf8_and_fold (url_schema,
2619 &schema_u8_folded) &&
2620 (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS,
2621 url_schema, USER_CHOICE,
2623 g_win32_registry_key_get_value_w (user_choice,
2628 (void **) &program_id,
2631 val_type == G_WIN32_REGISTRY_VALUE_STR)
2632 get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE);
2634 g_clear_pointer (&program_id, g_free);
2635 g_clear_pointer (&user_choice, g_object_unref);
2636 g_clear_pointer (&schema_u8, g_free);
2637 g_clear_pointer (&schema_u8_folded, g_free);
2640 g_win32_registry_subkey_iter_clear (&url_iter);
2643 /* Reads an application that is only registered by the basename of its
2644 * executable (and doesn't have Capabilities subkey).
2645 * @incapable_app is the registry key for the app.
2646 * @app_exe_basename is the basename of its executable.
2649 read_incapable_app (GWin32RegistryKey *incapable_app,
2650 const gunichar2 *app_exe_basename,
2651 const gchar *app_exe_basename_u8,
2652 const gchar *app_exe_basename_u8_folded)
2654 GWin32RegistryValueIter sup_iter;
2655 GWin32AppInfoApplication *app;
2657 const reg_verb *preferred_verb;
2658 gunichar2 *friendly_app_name;
2660 GWin32RegistryValueType vtype;
2661 gboolean no_open_with;
2662 GWin32RegistryKey *default_icon_key;
2663 gunichar2 *icon_source;
2665 GWin32RegistryKey *supported_key;
2667 if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell", NULL))
2670 app = get_app_object (apps_by_exe,
2672 app_exe_basename_u8,
2673 app_exe_basename_u8_folded,
2678 process_verbs_commands (g_steal_pointer (&verbs),
2680 L"HKEY_CLASSES_ROOT\\Applications\\",
2687 friendly_app_name = NULL;
2688 success = g_win32_registry_key_get_value_w (incapable_app,
2689 g_win32_registry_get_os_dirs_w (),
2693 (void **) &friendly_app_name,
2697 if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2698 g_clear_pointer (&friendly_app_name, g_free);
2700 no_open_with = g_win32_registry_key_get_value_w (incapable_app,
2710 g_win32_registry_key_get_child_w (incapable_app,
2716 if (default_icon_key != NULL)
2719 g_win32_registry_key_get_value_w (default_icon_key,
2724 (void **) &icon_source,
2728 if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2729 g_clear_pointer (&icon_source, g_free);
2731 g_object_unref (default_icon_key);
2736 gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2738 icon = g_themed_icon_new (name);
2742 app->no_open_with = no_open_with;
2744 if (friendly_app_name &&
2745 app->localized_pretty_name == NULL)
2747 app->localized_pretty_name = g_wcsdup (friendly_app_name, -1);
2748 g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2749 app->localized_pretty_name_u8 =
2750 g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL);
2753 if (icon && app->icon == NULL)
2754 app->icon = g_object_ref (icon);
2757 g_win32_registry_key_get_child_w (incapable_app,
2761 if (supported_key &&
2762 g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL))
2764 gunichar2 *ext_name;
2767 while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
2769 if ((!g_win32_registry_value_iter_get_name_w (&sup_iter,
2773 (ext_name_len <= 0) ||
2774 (ext_name[0] != L'.'))
2777 get_file_ext (ext_name, ext_name, app, FALSE);
2780 g_win32_registry_value_iter_clear (&sup_iter);
2783 g_clear_object (&supported_key);
2784 g_free (friendly_app_name);
2785 g_free (icon_source);
2787 g_clear_object (&icon);
2790 /* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications
2791 * and calls read_incapable_app() for each one.
2796 GWin32RegistryKey *local_applications_key;
2797 GWin32RegistrySubkeyIter app_iter;
2799 local_applications_key =
2800 g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);
2802 if (local_applications_key == NULL)
2805 if (!g_win32_registry_subkey_iter_init (&app_iter, local_applications_key, NULL))
2807 g_object_unref (local_applications_key);
2811 while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
2813 const gunichar2 *app_exe_basename;
2814 gsize app_exe_basename_len;
2815 GWin32RegistryKey *incapable_app;
2816 gchar *app_exe_basename_u8;
2817 gchar *app_exe_basename_u8_folded;
2819 if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
2821 &app_exe_basename_len,
2823 !g_utf16_to_utf8_and_fold (app_exe_basename,
2824 app_exe_basename_len,
2825 &app_exe_basename_u8,
2826 &app_exe_basename_u8_folded))
2830 g_win32_registry_key_get_child_w (local_applications_key,
2834 if (incapable_app != NULL)
2835 read_incapable_app (incapable_app,
2837 app_exe_basename_u8,
2838 app_exe_basename_u8_folded);
2840 g_clear_object (&incapable_app);
2841 g_clear_pointer (&app_exe_basename_u8, g_free);
2842 g_clear_pointer (&app_exe_basename_u8_folded, g_free);
2845 g_win32_registry_subkey_iter_clear (&app_iter);
2846 g_object_unref (local_applications_key);
2849 /* Iterates over subkeys of HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\
2850 * and calls get_file_ext() for each associated handler
2851 * (starting with user-chosen handler, if any)
2854 read_exts (GWin32RegistryKey *file_exts)
2856 GWin32RegistrySubkeyIter ext_iter;
2857 const gunichar2 *file_extension;
2858 gsize file_extension_len;
2860 if (file_exts == NULL)
2863 if (!g_win32_registry_subkey_iter_init (&ext_iter, file_exts, NULL))
2866 while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL))
2868 GWin32RegistryKey *open_with_progids;
2869 gunichar2 *program_id;
2870 GWin32RegistryValueIter iter;
2871 gunichar2 *value_name;
2872 gsize value_name_len;
2873 GWin32RegistryValueType value_type;
2874 GWin32RegistryKey *user_choice;
2876 if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter,
2878 &file_extension_len,
2883 user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, file_extension,
2886 g_win32_registry_key_get_value_w (user_choice,
2891 (void **) &program_id,
2894 value_type == G_WIN32_REGISTRY_VALUE_STR)
2896 /* Note: program_id could be "ProgramID" or "Applications\\program.exe".
2897 * The code still works, but handler_id might have a backslash
2898 * in it - that might trip us up later on.
2899 * Even though in that case this is logically an "application"
2900 * registry entry, we don't treat it in any special way.
2901 * We do scan that registry branch anyway, just not here.
2903 get_file_ext (program_id, file_extension, NULL, TRUE);
2906 g_clear_object (&user_choice);
2907 g_clear_pointer (&program_id, g_free);
2909 open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS,
2914 if (open_with_progids == NULL)
2917 if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2919 g_clear_object (&open_with_progids);
2923 while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2925 if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
2928 (value_name_len == 0))
2931 get_file_ext (value_name, file_extension, NULL, FALSE);
2934 g_win32_registry_value_iter_clear (&iter);
2935 g_clear_object (&open_with_progids);
2938 g_win32_registry_subkey_iter_clear (&ext_iter);
2941 /* Iterates over subkeys in HKCR, calls
2942 * get_file_ext() for any subkey that starts with ".",
2943 * or get_url_association() for any subkey that could
2944 * be a URL schema and has a "URL Protocol" value.
2947 read_classes (GWin32RegistryKey *classes_root)
2949 GWin32RegistrySubkeyIter class_iter;
2950 const gunichar2 *class_name;
2951 gsize class_name_len;
2953 if (classes_root == NULL)
2956 if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL))
2959 while (g_win32_registry_subkey_iter_next (&class_iter, TRUE, NULL))
2961 if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter,
2965 (class_name_len <= 1))
2968 if (class_name[0] == L'.')
2970 GWin32RegistryKey *class_key;
2971 GWin32RegistryValueIter iter;
2972 GWin32RegistryKey *open_with_progids;
2973 gunichar2 *value_name;
2974 gsize value_name_len;
2976 /* Read the data from the HKCR\\.ext (usually proxied
2977 * to another HKCR subkey)
2979 get_file_ext (class_name, class_name, NULL, FALSE);
2981 class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
2983 if (class_key == NULL)
2986 open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL);
2987 g_clear_object (&class_key);
2989 if (open_with_progids == NULL)
2992 if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2994 g_clear_object (&open_with_progids);
2998 /* Read the data for other handlers for this extension */
2999 while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
3001 if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
3004 (value_name_len == 0))
3007 get_file_ext (value_name, class_name, NULL, FALSE);
3010 g_win32_registry_value_iter_clear (&iter);
3011 g_clear_object (&open_with_progids);
3016 GWin32RegistryKey *class_key;
3018 GWin32RegistryValueType vtype;
3020 gchar *schema_u8_folded;
3022 for (i = 0; i < class_name_len; i++)
3023 if (!iswalpha (class_name[i]))
3026 if (i != class_name_len)
3029 class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
3031 if (class_key == NULL)
3034 success = g_win32_registry_key_get_value_w (class_key,
3042 g_clear_object (&class_key);
3045 vtype != G_WIN32_REGISTRY_VALUE_STR)
3048 if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded))
3051 get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE);
3053 g_clear_pointer (&schema_u8, g_free);
3054 g_clear_pointer (&schema_u8_folded, g_free);
3058 g_win32_registry_subkey_iter_clear (&class_iter);
3061 /* Iterates over all handlers and over all apps,
3062 * and links handler verbs to apps if a handler
3063 * runs the same executable as one of the app verbs.
3066 link_handlers_to_unregistered_apps (void)
3068 GHashTableIter iter;
3069 GHashTableIter app_iter;
3070 GWin32AppInfoHandler *handler;
3071 gchar *handler_id_fld;
3072 GWin32AppInfoApplication *app;
3073 gchar *canonical_name_fld;
3074 gchar *appexe_fld_basename;
3076 g_hash_table_iter_init (&iter, handlers);
3077 while (g_hash_table_iter_next (&iter,
3078 (gpointer *) &handler_id_fld,
3079 (gpointer *) &handler))
3083 if (handler->uwp_aumid != NULL)
3086 for (vi = 0; vi < handler->verbs->len; vi++)
3088 GWin32AppInfoShellVerb *handler_verb;
3089 const gchar *handler_exe_basename;
3094 ERROR_GETTING_SH_INFO,
3095 } have_stat_handler = SH_UNKNOWN;
3096 GWin32PrivateStat handler_verb_exec_info = { 0 };
3098 handler_verb = _verb_idx (handler->verbs, vi);
3100 if (handler_verb->app != NULL)
3103 if (handler_verb->executable_folded == NULL)
3106 handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1);
3107 g_hash_table_iter_init (&app_iter, apps_by_id);
3109 while (g_hash_table_iter_next (&app_iter,
3110 (gpointer *) &canonical_name_fld,
3113 GWin32AppInfoShellVerb *app_verb;
3119 for (ai = 0; ai < app->verbs->len; ai++)
3121 GWin32PrivateStat app_verb_exec_info;
3122 const gchar *app_exe_basename;
3123 app_verb = _verb_idx (app->verbs, ai);
3125 if (app_verb->executable_folded == NULL)
3128 app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1);
3130 /* First check that the executable paths are identical */
3131 if (g_strcmp0 (app_verb->executable_folded, handler_verb->executable_folded) != 0)
3133 /* If not, check the basenames. If they are different, don't bother
3134 * with further checks.
3136 if (g_strcmp0 (app_exe_basename, handler_exe_basename) != 0)
3139 /* Get filesystem IDs for both files.
3140 * For the handler that is attempted only once.
3142 if (have_stat_handler == SH_UNKNOWN)
3144 if (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (handler_verb->executable_folded,
3145 &handler_verb_exec_info) == 0)
3146 have_stat_handler = GOT_SH_INFO;
3148 have_stat_handler = ERROR_GETTING_SH_INFO;
3151 if (have_stat_handler != GOT_SH_INFO ||
3152 (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (app_verb->executable_folded,
3153 &app_verb_exec_info) != 0) ||
3154 app_verb_exec_info.file_index != handler_verb_exec_info.file_index)
3158 handler_verb->app = g_object_ref (app);
3163 if (handler_verb->app != NULL)
3166 g_hash_table_iter_init (&app_iter, apps_by_exe);
3168 while (g_hash_table_iter_next (&app_iter,
3169 (gpointer *) &appexe_fld_basename,
3175 /* Use basename because apps_by_exe only has basenames */
3176 if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0)
3179 handler_verb->app = g_object_ref (app);
3186 /* Finds all .ext and schema: handler verbs that have no app linked to them,
3187 * creates a "fake app" object and links these verbs to these
3188 * objects. Objects are identified by the full path to
3189 * the executable being run, thus multiple different invocations
3190 * get grouped in a more-or-less natural way.
3191 * The iteration goes separately over .ext and schema: handlers
3192 * (instead of the global handlers hashmap) to allow us to
3193 * put the handlers into supported_urls or supported_exts as
3194 * needed (handler objects themselves have no knowledge of extensions
3195 * and/or URLs they are associated with).
3198 link_handlers_to_fake_apps (void)
3200 GHashTableIter iter;
3201 GHashTableIter handler_iter;
3202 gchar *extension_utf8_folded;
3203 GWin32AppInfoFileExtension *file_extn;
3204 gchar *handler_id_fld;
3205 GWin32AppInfoHandler *handler;
3206 gchar *url_utf8_folded;
3207 GWin32AppInfoURLSchema *schema;
3209 g_hash_table_iter_init (&iter, extensions);
3210 while (g_hash_table_iter_next (&iter,
3211 (gpointer *) &extension_utf8_folded,
3212 (gpointer *) &file_extn))
3214 g_hash_table_iter_init (&handler_iter, file_extn->handlers);
3215 while (g_hash_table_iter_next (&handler_iter,
3216 (gpointer *) &handler_id_fld,
3217 (gpointer *) &handler))
3221 if (handler->uwp_aumid != NULL)
3224 for (vi = 0; vi < handler->verbs->len; vi++)
3226 GWin32AppInfoShellVerb *handler_verb;
3227 GWin32AppInfoApplication *app;
3228 gunichar2 *exename_utf16;
3229 handler_verb = _verb_idx (handler->verbs, vi);
3231 if (handler_verb->app != NULL)
3234 exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL);
3235 if (exename_utf16 == NULL)
3238 app = get_app_object (fake_apps,
3240 handler_verb->executable,
3241 handler_verb->executable_folded,
3245 g_clear_pointer (&exename_utf16, g_free);
3246 handler_verb->app = g_object_ref (app);
3250 handler_verb->verb_name,
3251 handler_verb->command,
3252 handler_verb->command_utf8,
3253 handler_verb->verb_displayname,
3256 g_hash_table_insert (app->supported_exts,
3257 g_strdup (extension_utf8_folded),
3258 g_object_ref (handler));
3263 g_hash_table_iter_init (&iter, urls);
3264 while (g_hash_table_iter_next (&iter,
3265 (gpointer *) &url_utf8_folded,
3266 (gpointer *) &schema))
3268 g_hash_table_iter_init (&handler_iter, schema->handlers);
3269 while (g_hash_table_iter_next (&handler_iter,
3270 (gpointer *) &handler_id_fld,
3271 (gpointer *) &handler))
3275 if (handler->uwp_aumid != NULL)
3278 for (vi = 0; vi < handler->verbs->len; vi++)
3280 GWin32AppInfoShellVerb *handler_verb;
3281 GWin32AppInfoApplication *app;
3282 gchar *command_utf8_folded;
3283 handler_verb = _verb_idx (handler->verbs, vi);
3285 if (handler_verb->app != NULL)
3288 command_utf8_folded = g_utf8_casefold (handler_verb->command_utf8, -1);
3289 app = get_app_object (fake_apps,
3290 handler_verb->command,
3291 handler_verb->command_utf8,
3292 command_utf8_folded,
3296 g_clear_pointer (&command_utf8_folded, g_free);
3297 handler_verb->app = g_object_ref (app);
3301 handler_verb->verb_name,
3302 handler_verb->command,
3303 handler_verb->command_utf8,
3304 handler_verb->verb_displayname,
3307 g_hash_table_insert (app->supported_urls,
3308 g_strdup (url_utf8_folded),
3309 g_object_ref (handler));
3315 static GWin32AppInfoHandler *
3316 find_uwp_handler_for_ext (GWin32AppInfoFileExtension *file_extn,
3317 const gunichar2 *app_user_model_id)
3319 GHashTableIter handler_iter;
3320 gchar *handler_id_fld;
3321 GWin32AppInfoHandler *handler;
3323 g_hash_table_iter_init (&handler_iter, file_extn->handlers);
3324 while (g_hash_table_iter_next (&handler_iter,
3325 (gpointer *) &handler_id_fld,
3326 (gpointer *) &handler))
3328 if (handler->uwp_aumid == NULL)
3331 if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3338 static GWin32AppInfoHandler *
3339 find_uwp_handler_for_schema (GWin32AppInfoURLSchema *schema,
3340 const gunichar2 *app_user_model_id)
3342 GHashTableIter handler_iter;
3343 gchar *handler_id_fld;
3344 GWin32AppInfoHandler *handler;
3346 g_hash_table_iter_init (&handler_iter, schema->handlers);
3347 while (g_hash_table_iter_next (&handler_iter,
3348 (gpointer *) &handler_id_fld,
3349 (gpointer *) &handler))
3351 if (handler->uwp_aumid == NULL)
3354 if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3362 uwp_package_cb (gpointer user_data,
3363 const gunichar2 *full_package_name,
3364 const gunichar2 *package_name,
3365 const gunichar2 *display_name,
3366 const gunichar2 *app_user_model_id,
3367 gboolean show_in_applist,
3368 GPtrArray *supported_extgroups,
3369 GPtrArray *supported_protocols)
3371 guint i, i_verb, i_ext;
3372 gint extensions_considered;
3373 GWin32AppInfoApplication *app;
3374 gchar *app_user_model_id_u8;
3375 gchar *app_user_model_id_u8_folded;
3376 GHashTableIter iter;
3377 GWin32AppInfoHandler *ext_handler;
3378 GWin32AppInfoHandler *url;
3380 if (!g_utf16_to_utf8_and_fold (app_user_model_id,
3382 &app_user_model_id_u8,
3383 &app_user_model_id_u8_folded))
3386 app = get_app_object (apps_by_id,
3388 app_user_model_id_u8,
3389 app_user_model_id_u8_folded,
3394 if (!app->pretty_name && !app->pretty_name_u8 && display_name)
3396 char *display_name_u8 = g_utf16_to_utf8 (display_name, -1, NULL, NULL, NULL);
3397 app->pretty_name = g_wcsdup (display_name, -1);
3398 app->pretty_name_u8 = g_steal_pointer (&display_name_u8);
3401 extensions_considered = 0;
3403 for (i = 0; i < supported_extgroups->len; i++)
3405 GWin32PackageExtGroup *grp = (GWin32PackageExtGroup *) g_ptr_array_index (supported_extgroups, i);
3407 extensions_considered += grp->extensions->len;
3409 for (i_ext = 0; i_ext < grp->extensions->len; i_ext++)
3411 wchar_t *ext = (wchar_t *) g_ptr_array_index (grp->extensions, i_ext);
3413 gchar *ext_u8_folded;
3414 GWin32AppInfoFileExtension *file_extn;
3415 GWin32AppInfoHandler *handler_rec;
3417 if (!g_utf16_to_utf8_and_fold (ext,
3423 file_extn = get_ext_object (ext, ext_u8, ext_u8_folded);
3425 handler_rec = find_uwp_handler_for_ext (file_extn, app_user_model_id);
3427 if (handler_rec == NULL)
3429 /* Use AppUserModelId as the ID of the new fake handler */
3430 handler_rec = get_handler_object (app_user_model_id_u8_folded,
3434 g_hash_table_insert (file_extn->handlers,
3435 g_strdup (app_user_model_id_u8_folded),
3436 g_object_ref (handler_rec));
3439 if (file_extn->chosen_handler == NULL)
3440 g_set_object (&file_extn->chosen_handler, handler_rec);
3442 /* This is somewhat wasteful, but for 100% correct handling
3443 * we need to remember which extensions (handlers) support
3444 * which verbs, and each handler gets its own copy of the
3445 * verb object, since our design is handler-centric,
3446 * not verb-centric. The app also gets a list of verbs,
3447 * but without handlers it would have no idea which
3448 * verbs can be used with which extensions.
3450 for (i_verb = 0; i_verb < grp->verbs->len; i_verb++)
3452 wchar_t *verb = NULL;
3454 verb = (wchar_t *) g_ptr_array_index (grp->verbs, i_verb);
3455 /* *_add_verb() functions are no-ops when a verb already exists,
3456 * so we're free to call them as many times as we want.
3458 uwp_handler_add_verb (handler_rec,
3465 g_hash_table_insert (app->supported_exts,
3466 g_steal_pointer (&ext_u8_folded),
3467 g_object_ref (handler_rec));
3471 g_hash_table_iter_init (&iter, app->supported_exts);
3473 /* Pile up all handler verbs into the app too,
3474 * for cases when we don't have a ref to a handler.
3476 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &ext_handler))
3483 for (i_hverb = 0; i_hverb < ext_handler->verbs->len; i_hverb++)
3485 GWin32AppInfoShellVerb *handler_verb;
3487 handler_verb = _verb_idx (ext_handler->verbs, i_hverb);
3488 uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname);
3489 if (handler_verb->app == NULL && handler_verb->is_uwp)
3490 handler_verb->app = g_object_ref (app);
3494 if (app->verbs->len == 0 && extensions_considered > 0)
3495 g_debug ("Unexpectedly, UWP app `%S' (AUMId `%s') supports %d extensions but has no verbs",
3496 full_package_name, app_user_model_id_u8, extensions_considered);
3498 for (i = 0; i < supported_protocols->len; i++)
3500 wchar_t *proto = (wchar_t *) g_ptr_array_index (supported_protocols, i);
3502 gchar *proto_u8_folded;
3503 GWin32AppInfoURLSchema *schema_rec;
3504 GWin32AppInfoHandler *handler_rec;
3506 if (!g_utf16_to_utf8_and_fold (proto,
3512 schema_rec = get_schema_object (proto,
3518 handler_rec = find_uwp_handler_for_schema (schema_rec, app_user_model_id);
3520 if (handler_rec == NULL)
3522 /* Use AppUserModelId as the ID of the new fake handler */
3523 handler_rec = get_handler_object (app_user_model_id_u8_folded,
3528 g_hash_table_insert (schema_rec->handlers,
3529 g_strdup (app_user_model_id_u8_folded),
3530 g_object_ref (handler_rec));
3533 if (schema_rec->chosen_handler == NULL)
3534 g_set_object (&schema_rec->chosen_handler, handler_rec);
3536 /* Technically, UWP apps don't use verbs for URIs,
3537 * but we only store an app field in verbs,
3538 * so each UWP URI handler has to have one.
3539 * Let's call it "open".
3541 uwp_handler_add_verb (handler_rec,
3547 g_hash_table_insert (app->supported_urls,
3548 g_steal_pointer (&proto_u8_folded),
3549 g_object_ref (handler_rec));
3552 g_hash_table_iter_init (&iter, app->supported_urls);
3554 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &url))
3561 for (i_hverb = 0; i_hverb < url->verbs->len; i_hverb++)
3563 GWin32AppInfoShellVerb *handler_verb;
3565 handler_verb = _verb_idx (url->verbs, i_hverb);
3566 uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname);
3567 if (handler_verb->app == NULL && handler_verb->is_uwp)
3568 handler_verb->app = g_object_ref (app);
3572 g_free (app_user_model_id_u8);
3573 g_free (app_user_model_id_u8_folded);
3578 /* Calls SHLoadIndirectString() in a loop to resolve
3579 * a string in @{...} format (also supports other indirect
3580 * strings, but we aren't using it for those).
3581 * Consumes the input, but may return it unmodified
3582 * (not an indirect string). May return %NULL (the string
3583 * is indirect, but the OS failed to load it).
3586 resolve_string (gunichar2 *at_string)
3589 gunichar2 *result = NULL;
3591 /* This value is arbitrary */
3592 const gsize reasonable_size_limit = 8192;
3594 if (at_string == NULL || at_string[0] != L'@')
3597 /* In case of a no-op @at_string will be copied into the output,
3598 * buffer so allocate at least that much.
3600 result_size = wcslen (at_string) + 1;
3604 result = g_renew (gunichar2, result, result_size);
3605 /* Since there's no built-in way to detect too small buffer size,
3606 * we do so by putting a sentinel at the end of the buffer.
3607 * If it's 0 (result is always 0-terminated, even if the buffer
3608 * is too small), then try larger buffer.
3610 result[result_size - 1] = 0xff;
3611 /* This string accepts size in characters, not bytes. */
3612 hr = SHLoadIndirectString (at_string, result, result_size, NULL);
3613 if (!SUCCEEDED (hr))
3619 else if (result[result_size - 1] != 0 ||
3620 result_size >= reasonable_size_limit)
3622 /* Now that the length is known, allocate the exact amount */
3623 gunichar2 *copy = g_wcsdup (result, -1);
3632 g_assert_not_reached ();
3638 grab_registry_string (GWin32RegistryKey *handler_appkey,
3639 const gunichar2 *value_name,
3640 gunichar2 **destination,
3641 gchar **destination_u8)
3645 GWin32RegistryValueType vtype;
3646 const gunichar2 *ms_resource_prefix = L"ms-resource:";
3647 gsize ms_resource_prefix_len = wcslen (ms_resource_prefix);
3649 /* Right now this function is not used without destination,
3650 * enforce this. destination_u8 is optional.
3652 g_assert (destination != NULL);
3654 if (*destination != NULL)
3658 if (g_win32_registry_key_get_value_w (handler_appkey,
3666 vtype != G_WIN32_REGISTRY_VALUE_STR)
3667 g_clear_pointer (&value, g_free);
3669 /* There's no way for us to resolve "ms-resource:..." strings */
3670 if (value != NULL &&
3671 value_size >= ms_resource_prefix_len &&
3674 ms_resource_prefix_len * sizeof (gunichar2)) == 0)
3675 g_clear_pointer (&value, g_free);
3680 *destination = resolve_string (g_steal_pointer (&value));
3682 if (*destination == NULL)
3686 *destination_u8 = g_utf16_to_utf8 (*destination, -1, NULL, NULL, NULL);
3690 read_uwp_handler_info (void)
3692 GHashTableIter iter;
3693 GWin32RegistryKey *handler_appkey;
3696 g_hash_table_iter_init (&iter, uwp_handler_table);
3698 while (g_hash_table_iter_next (&iter, (gpointer *) &handler_appkey, (gpointer *) &aumid))
3700 gchar *aumid_u8_folded;
3701 GWin32AppInfoApplication *app;
3703 if (!g_utf16_to_utf8_and_fold (aumid,
3709 app = g_hash_table_lookup (apps_by_id, aumid_u8_folded);
3710 g_clear_pointer (&aumid_u8_folded, g_free);
3715 grab_registry_string (handler_appkey, L"ApplicationDescription", &app->description, &app->description_u8);
3716 grab_registry_string (handler_appkey, L"ApplicationName", &app->localized_pretty_name, &app->localized_pretty_name_u8);
3717 /* TODO: ApplicationIcon value (usually also @{...}) resolves into
3718 * an image (PNG only?) with implicit multiple variants (scale, size, etc).
3724 update_registry_data (void)
3727 GPtrArray *capable_apps_keys;
3728 GPtrArray *user_capable_apps_keys;
3729 GPtrArray *priority_capable_apps_keys;
3730 GWin32RegistryKey *url_associations;
3731 GWin32RegistryKey *file_exts;
3732 GWin32RegistryKey *classes_root;
3733 DWORD collect_start, collect_end, alloc_end, capable_end, url_end, ext_end, exeapp_end, classes_end, uwp_end, postproc_end;
3734 GError *error = NULL;
3737 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3740 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
3742 classes_root = g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT", NULL);
3744 capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3745 user_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3746 priority_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3748 g_clear_pointer (&apps_by_id, g_hash_table_destroy);
3749 g_clear_pointer (&apps_by_exe, g_hash_table_destroy);
3750 g_clear_pointer (&fake_apps, g_hash_table_destroy);
3751 g_clear_pointer (&urls, g_hash_table_destroy);
3752 g_clear_pointer (&extensions, g_hash_table_destroy);
3753 g_clear_pointer (&handlers, g_hash_table_destroy);
3755 collect_start = GetTickCount ();
3756 collect_capable_apps_from_clients (capable_apps_keys,
3757 priority_capable_apps_keys,
3759 collect_capable_apps_from_clients (user_capable_apps_keys,
3760 priority_capable_apps_keys,
3762 collect_capable_apps_from_registered_apps (user_capable_apps_keys,
3764 collect_capable_apps_from_registered_apps (capable_apps_keys,
3766 collect_end = GetTickCount ();
3769 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3771 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3773 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3775 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3777 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3779 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3781 g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free);
3782 alloc_end = GetTickCount ();
3784 for (i = 0; i < priority_capable_apps_keys->len; i++)
3785 read_capable_app (g_ptr_array_index (priority_capable_apps_keys, i),
3788 for (i = 0; i < user_capable_apps_keys->len; i++)
3789 read_capable_app (g_ptr_array_index (user_capable_apps_keys, i),
3792 for (i = 0; i < capable_apps_keys->len; i++)
3793 read_capable_app (g_ptr_array_index (capable_apps_keys, i),
3796 capable_end = GetTickCount ();
3798 read_urls (url_associations);
3799 url_end = GetTickCount ();
3800 read_exts (file_exts);
3801 ext_end = GetTickCount ();
3803 exeapp_end = GetTickCount ();
3804 read_classes (classes_root);
3805 classes_end = GetTickCount ();
3807 if (!g_win32_package_parser_enum_packages (uwp_package_cb, NULL, &error))
3809 g_debug ("Unable to get UWP apps: %s", error->message);
3810 g_clear_error (&error);
3813 read_uwp_handler_info ();
3815 uwp_end = GetTickCount ();
3816 link_handlers_to_unregistered_apps ();
3817 link_handlers_to_fake_apps ();
3818 postproc_end = GetTickCount ();
3820 g_debug ("Collecting capable appnames: %lums\n"
3821 "Allocating hashtables:...... %lums\n"
3822 "Reading capable apps: %lums\n"
3823 "Reading URL associations:... %lums\n"
3824 "Reading extension assocs: %lums\n"
3825 "Reading exe-only apps:...... %lums\n"
3826 "Reading classes: %lums\n"
3827 "Reading UWP apps: %lums\n"
3828 "Postprocessing:..............%lums\n"
3830 collect_end - collect_start,
3831 alloc_end - collect_end,
3832 capable_end - alloc_end,
3833 url_end - capable_end,
3835 exeapp_end - ext_end,
3836 classes_end - exeapp_end,
3837 uwp_end - classes_end,
3838 postproc_end - uwp_end,
3839 postproc_end - collect_start);
3841 g_clear_object (&classes_root);
3842 g_clear_object (&url_associations);
3843 g_clear_object (&file_exts);
3844 g_ptr_array_free (capable_apps_keys, TRUE);
3845 g_ptr_array_free (user_capable_apps_keys, TRUE);
3846 g_ptr_array_free (priority_capable_apps_keys, TRUE);
3847 g_hash_table_unref (uwp_handler_table);
3855 /* This function is called when any of our registry watchers detect
3856 * changes in the registry.
3859 keys_updated (GWin32RegistryKey *key,
3863 /* Indicate the tree as not up-to-date, push a new job for the AppInfo thread */
3864 g_atomic_int_inc (&gio_win32_appinfo_update_counter);
3865 /* We don't use the data pointer, but it must be non-NULL */
3866 g_thread_pool_push (gio_win32_appinfo_threadpool, (gpointer) keys_updated, NULL);
3872 if (url_associations_key)
3873 g_win32_registry_key_watch (url_associations_key,
3875 G_WIN32_REGISTRY_WATCH_NAME |
3876 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3877 G_WIN32_REGISTRY_WATCH_VALUES,
3883 g_win32_registry_key_watch (file_exts_key,
3885 G_WIN32_REGISTRY_WATCH_NAME |
3886 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3887 G_WIN32_REGISTRY_WATCH_VALUES,
3892 if (user_clients_key)
3893 g_win32_registry_key_watch (user_clients_key,
3895 G_WIN32_REGISTRY_WATCH_NAME |
3896 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3897 G_WIN32_REGISTRY_WATCH_VALUES,
3902 if (system_clients_key)
3903 g_win32_registry_key_watch (system_clients_key,
3905 G_WIN32_REGISTRY_WATCH_NAME |
3906 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3907 G_WIN32_REGISTRY_WATCH_VALUES,
3912 if (applications_key)
3913 g_win32_registry_key_watch (applications_key,
3915 G_WIN32_REGISTRY_WATCH_NAME |
3916 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3917 G_WIN32_REGISTRY_WATCH_VALUES,
3922 if (user_registered_apps_key)
3923 g_win32_registry_key_watch (user_registered_apps_key,
3925 G_WIN32_REGISTRY_WATCH_NAME |
3926 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3927 G_WIN32_REGISTRY_WATCH_VALUES,
3932 if (system_registered_apps_key)
3933 g_win32_registry_key_watch (system_registered_apps_key,
3935 G_WIN32_REGISTRY_WATCH_NAME |
3936 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3937 G_WIN32_REGISTRY_WATCH_VALUES,
3942 if (classes_root_key)
3943 g_win32_registry_key_watch (classes_root_key,
3945 G_WIN32_REGISTRY_WATCH_NAME |
3946 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3947 G_WIN32_REGISTRY_WATCH_VALUES,
3953 /* This is the main function of the AppInfo thread */
3955 gio_win32_appinfo_thread_func (gpointer data,
3959 g_mutex_lock (&gio_win32_appinfo_mutex);
3960 saved_counter = g_atomic_int_get (&gio_win32_appinfo_update_counter);
3962 if (saved_counter > 0)
3963 update_registry_data ();
3964 /* If the counter didn't change while we were working, then set it to zero.
3965 * Otherwise we need to rebuild the tree again, so keep it greater than zero.
3966 * Numeric value doesn't matter - even if we're asked to rebuild N times,
3967 * we just need to rebuild once, and as long as there were no new rebuild
3968 * requests while we were working, we're done.
3970 if (g_atomic_int_compare_and_exchange (&gio_win32_appinfo_update_counter,
3973 g_cond_broadcast (&gio_win32_appinfo_cond);
3975 g_mutex_unlock (&gio_win32_appinfo_mutex);
3978 /* Initializes Windows AppInfo. Creates the registry watchers,
3979 * the AppInfo thread, and initiates an update of the AppInfo tree.
3980 * Called with do_wait = `FALSE` at startup to prevent it from
3981 * blocking until the tree is updated. All subsequent calls
3982 * from everywhere else are made with do_wait = `TRUE`, blocking
3983 * until the tree is re-built (if needed).
3986 gio_win32_appinfo_init (gboolean do_wait)
3988 static gsize initialized;
3990 if (g_once_init_enter (&initialized))
3992 HMODULE gio_dll_extra;
3994 url_associations_key =
3995 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3998 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
4001 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
4003 system_clients_key =
4004 g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
4007 g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications",
4009 user_registered_apps_key =
4010 g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
4012 system_registered_apps_key =
4013 g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
4016 g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT",
4021 /* We don't really require an exclusive pool, but the implementation
4022 * details might cause the g_thread_pool_push() call below to block
4023 * if the pool is not exclusive (specifically - for POSIX threads backend
4024 * lacking thread scheduler settings).
4026 gio_win32_appinfo_threadpool = g_thread_pool_new (gio_win32_appinfo_thread_func,
4031 g_mutex_init (&gio_win32_appinfo_mutex);
4032 g_cond_init (&gio_win32_appinfo_cond);
4033 g_atomic_int_set (&gio_win32_appinfo_update_counter, 1);
4034 /* Trigger initial tree build. Fake data pointer. */
4035 g_thread_pool_push (gio_win32_appinfo_threadpool, (gpointer) keys_updated, NULL);
4036 /* Increment the DLL refcount */
4037 GetModuleHandleExA (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
4038 (const char *) gio_win32_appinfo_init,
4040 /* gio DLL cannot be unloaded now */
4042 g_once_init_leave (&initialized, TRUE);
4048 /* Previously, we checked each of the watched keys here.
4049 * Now we just look at the update counter, because each key
4050 * has a change callback keys_updated, which increments this counter.
4052 if (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0)
4054 g_mutex_lock (&gio_win32_appinfo_mutex);
4055 while (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0)
4056 g_cond_wait (&gio_win32_appinfo_cond, &gio_win32_appinfo_mutex);
4057 g_mutex_unlock (&gio_win32_appinfo_mutex);
4062 static void g_win32_app_info_iface_init (GAppInfoIface *iface);
4064 struct _GWin32AppInfo
4066 GObject parent_instance;
4069 gchar **supported_types;
4071 GWin32AppInfoApplication *app;
4073 GWin32AppInfoHandler *handler;
4075 guint startup_notify : 1;
4078 G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT,
4079 G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
4080 g_win32_app_info_iface_init))
4084 g_win32_app_info_finalize (GObject *object)
4086 GWin32AppInfo *info;
4088 info = G_WIN32_APP_INFO (object);
4090 g_clear_pointer (&info->supported_types, g_strfreev);
4091 g_clear_object (&info->app);
4092 g_clear_object (&info->handler);
4094 G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
4098 g_win32_app_info_class_init (GWin32AppInfoClass *klass)
4100 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
4102 gobject_class->finalize = g_win32_app_info_finalize;
4106 g_win32_app_info_init (GWin32AppInfo *local)
4111 g_win32_app_info_new_from_app (GWin32AppInfoApplication *app,
4112 GWin32AppInfoHandler *handler)
4114 GWin32AppInfo *new_info;
4115 GHashTableIter iter;
4119 new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4121 new_info->app = g_object_ref (app);
4123 gio_win32_appinfo_init (TRUE);
4124 g_mutex_lock (&gio_win32_appinfo_mutex);
4127 g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4129 while (g_hash_table_iter_next (&iter, &ext, NULL))
4135 new_info->supported_types = g_new (gchar *, i + 1);
4138 g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4140 while (g_hash_table_iter_next (&iter, &ext, NULL))
4145 new_info->supported_types[i] = g_strdup ((gchar *) ext);
4149 g_mutex_unlock (&gio_win32_appinfo_mutex);
4151 new_info->supported_types[i] = NULL;
4153 new_info->handler = handler ? g_object_ref (handler) : NULL;
4155 return G_APP_INFO (new_info);
4160 g_win32_app_info_dup (GAppInfo *appinfo)
4162 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4163 GWin32AppInfo *new_info;
4165 new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4168 new_info->app = g_object_ref (info->app);
4171 new_info->handler = g_object_ref (info->handler);
4173 new_info->startup_notify = info->startup_notify;
4175 if (info->supported_types)
4179 for (i = 0; info->supported_types[i]; i++)
4182 new_info->supported_types = g_new (gchar *, i + 1);
4184 for (i = 0; info->supported_types[i]; i++)
4185 new_info->supported_types[i] = g_strdup (info->supported_types[i]);
4187 new_info->supported_types[i] = NULL;
4190 return G_APP_INFO (new_info);
4194 g_win32_app_info_equal (GAppInfo *appinfo1,
4197 GWin32AppInfoShellVerb *shverb1 = NULL;
4198 GWin32AppInfoShellVerb *shverb2 = NULL;
4199 GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1);
4200 GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2);
4201 GWin32AppInfoApplication *app1 = info1->app;
4202 GWin32AppInfoApplication *app2 = info2->app;
4206 return info1 == info2;
4208 if (app1->canonical_name_folded != NULL &&
4209 app2->canonical_name_folded != NULL)
4210 return (g_strcmp0 (app1->canonical_name_folded,
4211 app2->canonical_name_folded)) == 0;
4213 if (app1->verbs->len > 0 &&
4214 app2->verbs->len > 0)
4216 shverb1 = _verb_idx (app1->verbs, 0);
4217 shverb2 = _verb_idx (app2->verbs, 0);
4218 if (shverb1->executable_folded != NULL &&
4219 shverb2->executable_folded != NULL)
4220 return (g_strcmp0 (shverb1->executable_folded,
4221 shverb2->executable_folded)) == 0;
4224 return app1 == app2;
4228 g_win32_app_info_get_id (GAppInfo *appinfo)
4230 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4231 GWin32AppInfoShellVerb *shverb;
4233 if (info->app == NULL)
4236 if (info->app->canonical_name_u8)
4237 return info->app->canonical_name_u8;
4239 if (info->app->verbs->len > 0 &&
4240 (shverb = _verb_idx (info->app->verbs, 0))->executable_basename != NULL)
4241 return shverb->executable_basename;
4247 g_win32_app_info_get_name (GAppInfo *appinfo)
4249 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4251 if (info->app && info->app->pretty_name_u8)
4252 return info->app->pretty_name_u8;
4253 else if (info->app && info->app->canonical_name_u8)
4254 return info->app->canonical_name_u8;
4256 return P_("Unnamed");
4260 g_win32_app_info_get_display_name (GAppInfo *appinfo)
4262 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4266 if (info->app->localized_pretty_name_u8)
4267 return info->app->localized_pretty_name_u8;
4268 else if (info->app->pretty_name_u8)
4269 return info->app->pretty_name_u8;
4272 return g_win32_app_info_get_name (appinfo);
4276 g_win32_app_info_get_description (GAppInfo *appinfo)
4278 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4280 if (info->app == NULL)
4283 return info->app->description_u8;
4287 g_win32_app_info_get_executable (GAppInfo *appinfo)
4289 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4291 if (info->app == NULL)
4294 if (info->app->verbs->len > 0 && !info->app->is_uwp)
4295 return _verb_idx (info->app->verbs, 0)->executable;
4301 g_win32_app_info_get_commandline (GAppInfo *appinfo)
4303 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4305 if (info->app == NULL)
4308 if (info->app->verbs->len > 0 && !info->app->is_uwp)
4309 return _verb_idx (info->app->verbs, 0)->command_utf8;
4315 g_win32_app_info_get_icon (GAppInfo *appinfo)
4317 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4319 if (info->app == NULL)
4322 return info->app->icon;
4325 typedef struct _file_or_uri {
4331 expand_macro_single (char macro, file_or_uri *obj)
4333 char *result = NULL;
4350 /* TODO: handle 'l' and 'd' differently (longname and desktop name) */
4353 result = g_strdup (obj->file);
4357 const char *prefix = "file:///";
4358 const size_t prefix_len = strlen (prefix);
4360 if (g_str_has_prefix (obj->uri, prefix) && obj->uri[prefix_len] != 0)
4362 GFile *file = g_file_new_for_uri (obj->uri);
4363 result = g_file_get_path (file);
4364 g_object_unref (file);
4368 result = g_strdup (obj->uri);
4374 result = g_shell_quote (obj->uri);
4379 result = g_shell_quote (obj->file);
4387 expand_macro (char macro,
4389 GWin32AppInfo *info,
4390 GList **stat_obj_list,
4393 GList *objs = *obj_list;
4395 gboolean result = FALSE;
4397 g_return_val_if_fail (exec != NULL, FALSE);
4400 Legend: (from http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101%28v=vs.85%29.aspx)
4401 %* - replace with all parameters
4402 %~ - replace with all parameters starting with and following the second parameter
4403 %0 or %1 the first file parameter. For example "C:\\Users\\Eric\\Destop\\New Text Document.txt". Generally this should be in quotes and the applications command line parsing should accept quotes to disambiguate files with spaces in the name and different command line parameters (this is a security best practice and I believe mentioned in MSDN).
4404 %<n> (where N is 2 - 9), replace with the nth parameter
4407 %i - IDList stored in a shared memory handle is passed here.
4408 %l - long file name form of the first parameter. Note win32 applications will be passed the long file name, win16 applications get the short file name. Specifying %L is preferred as it avoids the need to probe for the application type.
4409 %d - desktop absolute parsing name of the first parameter (for items that don't have file system paths)
4410 %v - for verbs that are none implies all, if there is no parameter passed this is the working directory
4411 %w - the working directory
4423 for (o = *stat_obj_list, i = 0;
4424 macro == '~' && o && i < 2;
4427 for (; o; o = o->next)
4429 expanded = expand_macro_single (macro, o->data);
4433 if (o != *stat_obj_list)
4434 g_string_append (exec, " ");
4436 g_string_append (exec, expanded);
4457 expanded = expand_macro_single (macro, o->data);
4461 if (o != *stat_obj_list)
4462 g_string_append (exec, " ");
4464 g_string_append (exec, expanded);
4517 for (o = *stat_obj_list, i = 0; o && i < n; o = o->next, i++);
4521 expanded = expand_macro_single (macro, o->data);
4525 if (o != *stat_obj_list)
4526 g_string_append (exec, " ");
4528 g_string_append (exec, expanded);
4547 expanded = g_get_current_dir ();
4548 g_string_append (exec, expanded);
4555 expanded = expand_macro_single (macro, objs->data);
4559 g_string_append (exec, expanded);
4572 expanded = expand_macro_single (macro, objs->data);
4576 g_string_append (exec, expanded);
4583 if (objs != NULL && expanded)
4584 g_string_append_c (exec, ' ');
4590 if (info->app && info->app->localized_pretty_name_u8)
4592 expanded = g_shell_quote (info->app->localized_pretty_name_u8);
4593 g_string_append (exec, expanded);
4598 case 'm': /* deprecated */
4599 case 'n': /* deprecated */
4600 case 'N': /* deprecated */
4601 /*case 'd': *//* deprecated */
4602 case 'D': /* deprecated */
4606 g_string_append_c (exec, '%');
4616 expand_application_parameters (GWin32AppInfo *info,
4617 const gchar *exec_line,
4623 GList *obj_list = *objs;
4624 GList **stat_obj_list = objs;
4625 const char *p = exec_line;
4626 GString *expanded_exec;
4630 expanded_exec = g_string_new (NULL);
4635 if (p[0] == '%' && p[1] != '\0')
4637 if (expand_macro (p[1],
4639 info, stat_obj_list,
4646 g_string_append_c (expanded_exec, *p);
4651 /* No file substitutions */
4652 if (obj_list == *objs && obj_list != NULL && !res)
4654 /* If there is no macro default to %f. This is also what KDE does */
4655 g_string_append_c (expanded_exec, ' ');
4656 expand_macro ('f', expanded_exec, info, stat_obj_list, objs);
4659 /* Replace '\\' with '/', because g_shell_parse_argv considers them
4660 * to be escape sequences.
4662 for (a_char = expanded_exec->str;
4663 a_char <= &expanded_exec->str[expanded_exec->len];
4666 if (*a_char == '\\')
4670 res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
4671 g_string_free (expanded_exec, TRUE);
4677 get_appath_for_exe (const gchar *exe_basename)
4679 GWin32RegistryKey *apppath_key = NULL;
4680 GWin32RegistryValueType val_type;
4681 gchar *appath = NULL;
4683 gchar *key_path = g_strdup_printf ("HKEY_LOCAL_MACHINE\\"
4689 "%s", exe_basename);
4691 apppath_key = g_win32_registry_key_new (key_path, NULL);
4692 g_clear_pointer (&key_path, g_free);
4694 if (apppath_key == NULL)
4697 got_value = g_win32_registry_key_get_value (apppath_key,
4706 g_object_unref (apppath_key);
4709 val_type == G_WIN32_REGISTRY_VALUE_STR)
4712 g_clear_pointer (&appath, g_free);
4717 /* GDesktopAppInfo::launch_uris_async emits all GAppLaunchContext's signals
4718 * on the main thread.
4720 * We do the same: when g_win32_app_info_launch_uris_impl has a non-NULL
4721 * from_task argument we schedule the signal emissions on the main loop,
4722 * taking care not to emit signals after the task itself is completed
4723 * (see g_task_get_completed).
4727 GAppLaunchContext *context; /* (owned) */
4728 GWin32AppInfo *info; /* (owned) */
4729 } EmitLaunchStartedData;
4732 emit_launch_started_data_free (EmitLaunchStartedData *data)
4734 g_clear_object (&data->context);
4735 g_clear_object (&data->info);
4740 emit_launch_started_cb (EmitLaunchStartedData *data)
4742 g_signal_emit_by_name (data->context, "launch-started", data->info, NULL);
4743 return G_SOURCE_REMOVE;
4747 emit_launch_started (GAppLaunchContext *context,
4748 GWin32AppInfo *info,
4751 if (!context || !info)
4755 g_signal_emit_by_name (context, "launch-started", info, NULL);
4758 EmitLaunchStartedData *data;
4760 data = g_new (EmitLaunchStartedData, 1);
4761 data->context = g_object_ref (context);
4762 data->info = g_object_ref (info);
4764 g_main_context_invoke_full (g_task_get_context (from_task),
4765 g_task_get_priority (from_task),
4766 G_SOURCE_FUNC (emit_launch_started_cb),
4767 g_steal_pointer (&data),
4768 (GDestroyNotify) emit_launch_started_data_free);
4773 GAppLaunchContext *context; /* (owned) */
4774 GWin32AppInfo *info; /* (owned) */
4775 GPid pid; /* (owned) */
4779 emit_launched_data_free (EmitLaunchedData *data)
4781 g_clear_object (&data->context);
4782 g_clear_object (&data->info);
4783 g_spawn_close_pid (data->pid);
4788 make_platform_data (GPid pid)
4790 GVariantBuilder builder;
4792 g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
4793 /* pid handles are never bigger than 2^24 as per
4794 * https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects,
4795 * so truncating to `int32` is valid.
4796 * The gsize cast is to silence a compiler warning
4797 * about conversion from pointer to integer of
4798 * different size. */
4799 g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gsize) pid));
4801 return g_variant_ref_sink (g_variant_builder_end (&builder));
4805 emit_launched_cb (EmitLaunchedData *data)
4808 GVariant *platform_data = make_platform_data (data->pid);
4810 g_signal_emit_by_name (data->context, "launched", data->info, platform_data);
4811 g_variant_unref (platform_data);
4813 return G_SOURCE_REMOVE;
4817 emit_launched (GAppLaunchContext *context,
4818 GWin32AppInfo *info,
4822 if (!context || !info)
4827 GVariant *platform_data = make_platform_data (*pid);
4828 g_signal_emit_by_name (context, "launched", info, platform_data);
4829 g_variant_unref (platform_data);
4830 g_spawn_close_pid (*pid);
4834 EmitLaunchedData *data;
4836 data = g_new (EmitLaunchedData, 1);
4837 data->context = g_object_ref (context);
4838 data->info = g_object_ref (info);
4841 g_main_context_invoke_full (g_task_get_context (from_task),
4842 g_task_get_priority (from_task),
4843 G_SOURCE_FUNC (emit_launched_cb),
4844 g_steal_pointer (&data),
4845 (GDestroyNotify) emit_launched_data_free);
4852 GAppLaunchContext *context; /* (owned) */
4853 GWin32AppInfo *info; /* (owned) */
4854 } EmitLaunchFailedData;
4857 emit_launch_failed_data_free (EmitLaunchFailedData *data)
4859 g_clear_object (&data->context);
4860 g_clear_object (&data->info);
4865 emit_launch_failed_cb (EmitLaunchFailedData *data)
4867 g_signal_emit_by_name (data->context, "launch-failed", data->info, NULL);
4868 return G_SOURCE_REMOVE;
4872 emit_launch_failed (GAppLaunchContext *context,
4873 GWin32AppInfo *info,
4876 if (!context || !info)
4880 g_signal_emit_by_name (context, "launch-failed", info, NULL);
4883 EmitLaunchFailedData *data;
4885 data = g_new (EmitLaunchFailedData, 1);
4886 data->context = g_object_ref (context);
4887 data->info = g_object_ref (info);
4889 g_main_context_invoke_full (g_task_get_context (from_task),
4890 g_task_get_priority (from_task),
4891 G_SOURCE_FUNC (emit_launch_failed_cb),
4892 g_steal_pointer (&data),
4893 (GDestroyNotify) emit_launch_failed_data_free);
4899 /* PLAIN: just open the application, without arguments of any kind
4900 * corresponds to: LaunchActivatedEventArgs */
4901 UWP_ACTIVATION_TYPE_PLAIN,
4903 /* FILE: open the applications passing a set of files
4904 * corresponds to: FileActivatedEventArgs */
4905 UWP_ACTIVATION_TYPE_FILE,
4907 /* PROTOCOL: open the application passing a URI which describe an
4909 * corresponds to: ProtocolActivatedEventArgs */
4910 UWP_ACTIVATION_TYPE_PROTOCOL,
4911 } UwpActivationType;
4914 g_win32_app_info_launch_uwp_single (IApplicationActivationManager *app_activation_manager,
4915 UwpActivationType activation_type,
4916 IShellItemArray *items,
4917 const wchar_t *verb,
4918 GWin32AppInfo *info,
4919 GAppLaunchContext *launch_context,
4923 const wchar_t *canonical_name = (const wchar_t *) info->app->canonical_name;
4924 DWORD process_id = 0;
4927 emit_launch_started (launch_context, info, from_task);
4929 /* The Activate methods return a process identifier (PID), so we should consider
4930 * those methods as potentially blocking */
4932 switch (activation_type)
4934 case UWP_ACTIVATION_TYPE_PLAIN:
4935 g_assert (items == NULL);
4937 hr = IApplicationActivationManager_ActivateApplication (app_activation_manager,
4942 case UWP_ACTIVATION_TYPE_PROTOCOL:
4943 g_assert (items != NULL);
4945 hr = IApplicationActivationManager_ActivateForProtocol (app_activation_manager,
4950 case UWP_ACTIVATION_TYPE_FILE:
4951 g_assert (items != NULL);
4953 hr = IApplicationActivationManager_ActivateForFile (app_activation_manager,
4962 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4963 "The app %s failed to launch: 0x%lx",
4964 g_win32_appinfo_application_get_some_name (info->app), hr);
4966 emit_launch_failed (launch_context, info, from_task);
4973 DWORD access_rights = 0;
4974 HANDLE process_handle = NULL;
4976 /* Unfortunately, there's a race condition here.
4977 * ApplicationActivationManager methods return a process ID, but it
4978 * keeps no open HANDLE to the spawned process internally (tested
4979 * on Windows 10 21H2). So we cannot guarantee that by the time
4980 * OpenProcess is called, process ID still referes to the spawned
4981 * process. Anyway hitting such case is extremely unlikely.
4983 * https://docs.microsoft.com/en-us/answers/questions/942879/
4984 * iapplicationactivationmanager-race-condition.html
4986 * Maybe we could make use of the WinRT APIs to activate UWP apps,
4989 /* As documented on MSDN, the handle returned by CreateProcess has
4990 * PROCESS_ALL_ACCESS rights. First try passing PROCESS_ALL_ACCESS
4991 * to have the same access rights as the non-UWP code-path; should
4992 * that fail with ERROR_ACCESS_DENIED error code, retry using safe
4994 access_rights = PROCESS_ALL_ACCESS;
4996 process_handle = OpenProcess (access_rights, FALSE, process_id);
4998 if (!process_handle && GetLastError () == ERROR_ACCESS_DENIED)
5000 DWORD access_rights = PROCESS_QUERY_LIMITED_INFORMATION |
5003 process_handle = OpenProcess (access_rights, FALSE, process_id);
5006 if (!process_handle)
5008 g_warning ("OpenProcess failed with error code %" G_GUINT32_FORMAT,
5009 (guint32) GetLastError ());
5012 /* Emit the launched signal regardless if we have the process
5014 emit_launched (launch_context, info, (GPid*) &process_handle, from_task);
5016 g_spawn_close_pid ((GPid) process_handle);
5023 g_win32_app_info_supports_files (GAppInfo *appinfo);
5025 static IShellItemArray *
5026 make_item_array (gboolean for_files,
5031 make_single_entry_list (gpointer data)
5033 GList l = { NULL, NULL, NULL };
5040 g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info,
5042 GList *objs, /* (element-type file_or_uri) */
5043 GWin32AppInfoShellVerb *shverb,
5044 GAppLaunchContext *launch_context,
5048 IApplicationActivationManager *paam = NULL;
5049 gboolean com_initialized = FALSE;
5050 gboolean result = FALSE;
5053 /* ApplicationActivationManager threading model is both,
5054 * prefer the multithreaded apartment type, as we don't
5055 * need anything of the STA here. */
5056 hr = CoInitializeEx (NULL, COINIT_MULTITHREADED);
5058 com_initialized = TRUE;
5059 else if (hr != RPC_E_CHANGED_MODE)
5061 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5062 "Failed to initialize the COM support library for the thread: 0x%lx", hr);
5066 /* It's best to instantiate ApplicationActivationManager out-of-proc,
5067 * as documented on MSDN:
5069 * An IApplicationActivationManager object creates a thread in its
5070 * host process to serve any activated event arguments objects
5071 * (LaunchActivatedEventArgs, FileActivatedEventArgs, and Protocol-
5072 * ActivatedEventArgs) that are passed to the app. If the calling
5073 * process is long-lived, you can create this object in-proc,
5074 * based on the assumption that the event arguments will exist long
5075 * enough for the target app to use them.
5076 * However, if the calling process is spawned only to launch the
5077 * target app, it should create the IApplicationActivationManager
5078 * object out-of-process, by using CLSCTX_LOCAL_SERVER. This causes
5079 * the object to be created in a Dllhost instance that automatically
5080 * manages the object's lifetime based on outstanding references to
5081 * the activated event argument objects.
5083 hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL,
5084 CLSCTX_LOCAL_SERVER,
5085 &IID_IApplicationActivationManager, (void **) &paam);
5088 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5089 "Failed to create ApplicationActivationManager: 0x%lx", hr);
5095 result = g_win32_app_info_launch_uwp_single (paam, UWP_ACTIVATION_TYPE_PLAIN, NULL, NULL,
5096 info, launch_context, from_task, error);
5100 IShellItemArray *items = make_item_array (TRUE, objs, error);
5105 result = g_win32_app_info_launch_uwp_single (paam, UWP_ACTIVATION_TYPE_FILE, items,
5107 info, launch_context, from_task, error);
5109 IShellItemArray_Release (items);
5113 gboolean supports_files;
5114 gboolean supports_file_uris;
5115 gboolean outcome = TRUE;
5118 supports_files = g_win32_app_info_supports_files (G_APP_INFO (info));
5119 supports_file_uris = info->app &&
5120 info->app->supported_urls &&
5121 g_hash_table_lookup (info->app->supported_urls, "file");
5123 for (l = objs; l != NULL; l = l->next)
5125 file_or_uri *obj = (file_or_uri*) l->data;
5126 GList single = make_single_entry_list (obj);
5127 IShellItemArray *item = NULL;
5128 UwpActivationType type;
5130 /* Most UWP applications support opening files but do not support
5131 * the file:// protocol in URI's. That's because the UWP platform
5132 * has a specific activation for files (see FileActivatedEventArgs)
5133 * which is different from protocol activation. Here we check for
5136 if (!supports_file_uris && supports_files && obj->file)
5138 type = UWP_ACTIVATION_TYPE_FILE;
5139 item = make_item_array (TRUE, &single, error);
5143 type = UWP_ACTIVATION_TYPE_PROTOCOL;
5144 item = make_item_array (FALSE, &single, error);
5153 if (!g_win32_app_info_launch_uwp_single (paam, type,
5154 item, shverb->verb_name, info,
5155 launch_context, from_task, error))
5158 IShellItemArray_Release (item);
5168 IApplicationActivationManager_Release (paam);
5172 if (com_initialized)
5175 com_initialized = FALSE;
5183 g_win32_app_info_launch_internal (GWin32AppInfo *info,
5184 GList *objs, /* (element-type file_or_uri) */
5185 gboolean for_files, /* UWP only */
5186 GAppLaunchContext *launch_context,
5187 GSpawnFlags spawn_flags,
5191 gboolean completed = FALSE;
5192 char **argv, **envp;
5194 const gchar *command;
5196 GWin32AppInfoShellVerb *shverb;
5199 g_return_val_if_fail (info != NULL, FALSE);
5200 g_return_val_if_fail (info->app != NULL, FALSE);
5205 if (!info->app->is_uwp &&
5206 info->handler != NULL &&
5207 info->handler->verbs->len > 0)
5208 shverb = _verb_idx (info->handler->verbs, 0);
5209 else if (info->app->verbs->len > 0)
5210 shverb = _verb_idx (info->app->verbs, 0);
5214 if (info->app->is_uwp || info->handler == NULL)
5215 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5216 P_("The app ‘%s’ in the application object has no verbs"),
5217 g_win32_appinfo_application_get_some_name (info->app));
5218 else if (info->handler->verbs->len == 0)
5219 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5220 P_("The app ‘%s’ and the handler ‘%s’ in the application object have no verbs"),
5221 g_win32_appinfo_application_get_some_name (info->app),
5222 info->handler->handler_id_folded);
5227 if (info->app->is_uwp)
5228 return g_win32_app_info_launch_uwp_internal (info,
5237 envp = g_app_launch_context_get_environment (launch_context);
5239 envp = g_get_environ ();
5241 g_assert (shverb->command_utf8 != NULL);
5242 command = shverb->command_utf8;
5243 apppath = get_appath_for_exe (shverb->executable_basename);
5250 for (p = envp, p_index = 0; p[0]; p++, p_index++)
5251 if ((p[0][0] == 'p' || p[0][0] == 'P') &&
5252 (p[0][1] == 'a' || p[0][1] == 'A') &&
5253 (p[0][2] == 't' || p[0][2] == 'T') &&
5254 (p[0][3] == 'h' || p[0][3] == 'H') &&
5261 new_envp = g_new (char *, g_strv_length (envp) + 2);
5262 new_envp[0] = g_strdup_printf ("PATH=%s", apppath);
5264 for (p_index = 0; p_index <= g_strv_length (envp); p_index++)
5265 new_envp[1 + p_index] = envp[p_index];
5276 if (p_path[0] != '\0')
5277 envp[p_index] = g_strdup_printf ("PATH=%s%c%s",
5279 G_SEARCHPATH_SEPARATOR,
5282 envp[p_index] = g_strdup_printf ("PATH=%s", apppath);
5284 g_free (&p_path[-5]);
5290 if (from_task && g_task_return_error_if_cancelled (from_task))
5293 if (!expand_application_parameters (info,
5301 emit_launch_started (launch_context, info, from_task);
5303 if (!g_spawn_async (NULL,
5307 G_SPAWN_DO_NOT_REAP_CHILD,
5313 emit_launch_failed (launch_context, info, from_task);
5317 else if (launch_context)
5318 emit_launched (launch_context, info, &pid, from_task);
5320 g_spawn_close_pid (pid);
5325 while (objs != NULL);
5330 g_spawn_close_pid (pid);
5338 free_file_or_uri (gpointer ptr)
5340 file_or_uri *obj = ptr;
5348 g_win32_app_supports_uris (GWin32AppInfoApplication *app)
5350 gssize num_of_uris_supported;
5355 num_of_uris_supported = (gssize) g_hash_table_size (app->supported_urls);
5357 if (g_hash_table_lookup (app->supported_urls, "file"))
5358 num_of_uris_supported -= 1;
5360 return num_of_uris_supported > 0;
5365 g_win32_app_info_supports_uris (GAppInfo *appinfo)
5367 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5369 if (info->app == NULL)
5372 return g_win32_app_supports_uris (info->app);
5377 g_win32_app_info_supports_files (GAppInfo *appinfo)
5379 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5381 if (info->app == NULL)
5384 return g_hash_table_size (info->app->supported_exts) > 0;
5388 static IShellItemArray *
5389 make_item_array (gboolean for_files,
5390 GList *objs, /* (element-type file_or_uri) */
5393 ITEMIDLIST **item_ids;
5394 IShellItemArray *items;
5400 count = g_list_length (objs);
5403 item_ids = g_new (ITEMIDLIST*, count);
5405 for (i = 0, p = objs; p != NULL; p = p->next, i++)
5407 file_or_uri *obj = (file_or_uri*) p->data;
5408 wchar_t *file_or_uri_utf16;
5411 file_or_uri_utf16 = g_utf8_to_utf16 (obj->uri, -1, NULL, NULL, error);
5413 file_or_uri_utf16 = g_utf8_to_utf16 (obj->file, -1, NULL, NULL, error);
5415 if (file_or_uri_utf16 == NULL)
5424 len = wcslen (file_or_uri_utf16);
5425 /* Filenames *MUST* use single backslashes, else the call
5426 * will fail. First convert all slashes to backslashes,
5427 * then remove duplicates.
5429 for (c = file_or_uri_utf16; for_files && *c != 0; c++)
5434 for (len_tail = 0, c = &file_or_uri_utf16[len - 1];
5435 for_files && c > file_or_uri_utf16;
5438 if (c[0] != L'\\' || c[-1] != L'\\')
5441 memmove (&c[-1], &c[0], len_tail * sizeof (wchar_t));
5445 hr = SHParseDisplayName (file_or_uri_utf16, NULL, &item_ids[i], 0, NULL);
5449 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5450 "File or URI `%S' cannot be parsed by SHParseDisplayName: 0x%lx",
5451 file_or_uri_utf16, hr);
5453 g_free (file_or_uri_utf16);
5457 g_free (file_or_uri_utf16);
5462 hr = SHCreateShellItemArrayFromIDLists (count, (const ITEMIDLIST **) item_ids, &items);
5465 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5466 "SHCreateShellItemArrayFromIDLists() failed: 0x%lx", hr);
5473 for (i = 0; i < count; i++)
5474 CoTaskMemFree (item_ids[i]);
5483 g_win32_app_info_launch_uris_impl (GAppInfo *appinfo,
5484 GList *uris, /* (element-type utf8) */
5485 GAppLaunchContext *launch_context,
5489 gboolean res = FALSE;
5492 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5495 do_files = g_win32_app_info_supports_files (appinfo);
5500 obj = g_new0 (file_or_uri, 1);
5507 file = g_file_new_for_uri (uris->data);
5508 path = g_file_get_path (file);
5510 g_object_unref (file);
5513 obj->uri = g_strdup (uris->data);
5515 objs = g_list_prepend (objs, obj);
5518 objs = g_list_reverse (objs);
5520 is_uwp = (info->app != NULL && info->app->is_uwp);
5522 res = g_win32_app_info_launch_internal (info,
5528 G_SPAWN_SEARCH_PATH,
5532 g_list_free_full (objs, free_file_or_uri);
5538 g_win32_app_info_launch_uris (GAppInfo *appinfo,
5540 GAppLaunchContext *launch_context,
5543 return g_win32_app_info_launch_uris_impl (appinfo, uris, launch_context, NULL, error);
5548 GList *uris; /* (element-type utf8) (owned) (nullable) */
5549 GAppLaunchContext *context; /* (owned) (nullable) */
5553 launch_uris_data_free (LaunchUrisData *data)
5555 g_clear_object (&data->context);
5556 g_list_free_full (data->uris, g_free);
5561 launch_uris_async_thread (GTask *task,
5562 gpointer source_object,
5564 GCancellable *cancellable)
5566 GAppInfo *appinfo = G_APP_INFO (source_object);
5567 LaunchUrisData *data = task_data;
5568 GError *local_error = NULL;
5571 succeeded = g_win32_app_info_launch_uris_impl (appinfo, data->uris, data->context, task, &local_error);
5573 g_task_return_boolean (task, TRUE);
5574 else if (!g_task_had_error (task))
5575 g_task_return_error (task, g_steal_pointer (&local_error));
5579 g_win32_app_info_launch_uris_async (GAppInfo *appinfo,
5581 GAppLaunchContext *context,
5582 GCancellable *cancellable,
5583 GAsyncReadyCallback callback,
5587 LaunchUrisData *data;
5589 task = g_task_new (appinfo, cancellable, callback, user_data);
5590 g_task_set_source_tag (task, g_win32_app_info_launch_uris_async);
5592 data = g_new0 (LaunchUrisData, 1);
5593 data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
5594 g_set_object (&data->context, context);
5595 g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free);
5597 g_task_run_in_thread (task, launch_uris_async_thread);
5598 g_object_unref (task);
5602 g_win32_app_info_launch_uris_finish (GAppInfo *appinfo,
5603 GAsyncResult *result,
5606 g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE);
5608 return g_task_propagate_boolean (G_TASK (result), error);
5612 g_win32_app_info_should_show (GAppInfo *appinfo)
5614 /* FIXME: This is a placeholder implementation to avoid crashes
5615 * for now. It can be made more specific to @appinfo in future. */
5621 g_win32_app_info_launch (GAppInfo *appinfo,
5622 GList *files, /* (element-type GFile) */
5623 GAppLaunchContext *launch_context,
5626 gboolean res = FALSE;
5629 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5632 do_uris = g_win32_app_info_supports_uris (appinfo);
5637 obj = g_new0 (file_or_uri, 1);
5638 obj->file = g_file_get_path (G_FILE (files->data));
5641 obj->uri = g_file_get_uri (G_FILE (files->data));
5643 objs = g_list_prepend (objs, obj);
5644 files = files->next;
5646 objs = g_list_reverse (objs);
5648 is_uwp = (info->app != NULL && info->app->is_uwp);
5650 res = g_win32_app_info_launch_internal (info,
5656 G_SPAWN_SEARCH_PATH,
5660 g_list_free_full (objs, free_file_or_uri);
5665 static const char **
5666 g_win32_app_info_get_supported_types (GAppInfo *appinfo)
5668 GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5670 return (const char**) info->supported_types;
5674 g_app_info_create_from_commandline (const char *commandline,
5675 const char *application_name,
5676 GAppInfoCreateFlags flags,
5679 GWin32AppInfo *info;
5680 GWin32AppInfoApplication *app;
5681 gunichar2 *app_command;
5683 g_return_val_if_fail (commandline, NULL);
5685 app_command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL);
5687 if (app_command == NULL)
5690 info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
5691 app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
5693 app->no_open_with = FALSE;
5694 app->user_specific = FALSE;
5695 app->default_app = FALSE;
5697 if (application_name)
5699 app->canonical_name = g_utf8_to_utf16 (application_name,
5704 app->canonical_name_u8 = g_strdup (application_name);
5705 app->canonical_name_folded = g_utf8_casefold (application_name, -1);
5717 g_clear_pointer (&app_command, g_free);
5719 info->handler = NULL;
5721 return G_APP_INFO (info);
5724 /* GAppInfo interface init */
5727 g_win32_app_info_iface_init (GAppInfoIface *iface)
5729 iface->dup = g_win32_app_info_dup;
5730 iface->equal = g_win32_app_info_equal;
5731 iface->get_id = g_win32_app_info_get_id;
5732 iface->get_name = g_win32_app_info_get_name;
5733 iface->get_description = g_win32_app_info_get_description;
5734 iface->get_executable = g_win32_app_info_get_executable;
5735 iface->get_icon = g_win32_app_info_get_icon;
5736 iface->launch = g_win32_app_info_launch;
5737 iface->supports_uris = g_win32_app_info_supports_uris;
5738 iface->supports_files = g_win32_app_info_supports_files;
5739 iface->launch_uris = g_win32_app_info_launch_uris;
5740 iface->launch_uris_async = g_win32_app_info_launch_uris_async;
5741 iface->launch_uris_finish = g_win32_app_info_launch_uris_finish;
5742 iface->should_show = g_win32_app_info_should_show;
5743 /* iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;*/
5744 /* iface->set_as_default_for_extension = g_win32_app_info_set_as_default_for_extension;*/
5745 /* iface->add_supports_type = g_win32_app_info_add_supports_type;*/
5746 /* iface->can_remove_supports_type = g_win32_app_info_can_remove_supports_type;*/
5747 /* iface->remove_supports_type = g_win32_app_info_remove_supports_type;*/
5748 /* iface->can_delete = g_win32_app_info_can_delete;*/
5749 /* iface->do_delete = g_win32_app_info_delete;*/
5750 iface->get_commandline = g_win32_app_info_get_commandline;
5751 iface->get_display_name = g_win32_app_info_get_display_name;
5752 /* iface->set_as_last_used_for_type = g_win32_app_info_set_as_last_used_for_type;*/
5753 iface->get_supported_types = g_win32_app_info_get_supported_types;
5757 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
5759 GWin32AppInfoURLSchema *scheme = NULL;
5762 GWin32AppInfoShellVerb *shverb;
5764 scheme_down = g_utf8_casefold (uri_scheme, -1);
5769 if (strcmp (scheme_down, "file") == 0)
5771 g_free (scheme_down);
5776 gio_win32_appinfo_init (TRUE);
5777 g_mutex_lock (&gio_win32_appinfo_mutex);
5779 g_set_object (&scheme, g_hash_table_lookup (urls, scheme_down));
5780 g_free (scheme_down);
5782 g_mutex_unlock (&gio_win32_appinfo_mutex);
5786 if (scheme != NULL &&
5787 scheme->chosen_handler != NULL &&
5788 scheme->chosen_handler->verbs->len > 0 &&
5789 (shverb = _verb_idx (scheme->chosen_handler->verbs, 0))->app != NULL)
5790 result = g_win32_app_info_new_from_app (shverb->app,
5791 scheme->chosen_handler);
5793 g_clear_object (&scheme);
5799 g_app_info_get_default_for_type (const char *content_type,
5800 gboolean must_support_uris)
5802 GWin32AppInfoFileExtension *ext = NULL;
5805 GWin32AppInfoShellVerb *shverb;
5807 ext_down = g_utf8_casefold (content_type, -1);
5812 gio_win32_appinfo_init (TRUE);
5813 g_mutex_lock (&gio_win32_appinfo_mutex);
5815 /* Assuming that "content_type" is a file extension, not a MIME type */
5816 g_set_object (&ext, g_hash_table_lookup (extensions, ext_down));
5819 g_mutex_unlock (&gio_win32_appinfo_mutex);
5826 if (ext->chosen_handler != NULL &&
5827 ext->chosen_handler->verbs->len > 0 &&
5828 (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL &&
5829 (!must_support_uris ||
5830 g_win32_app_supports_uris (shverb->app)))
5831 result = g_win32_app_info_new_from_app (shverb->app,
5832 ext->chosen_handler);
5835 GHashTableIter iter;
5836 GWin32AppInfoHandler *handler;
5838 g_hash_table_iter_init (&iter, ext->handlers);
5840 while (result == NULL &&
5841 g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5843 if (handler->verbs->len == 0)
5846 shverb = _verb_idx (handler->verbs, 0);
5849 (!must_support_uris ||
5850 g_win32_app_supports_uris (shverb->app)))
5851 result = g_win32_app_info_new_from_app (shverb->app, handler);
5855 g_clear_object (&ext);
5861 g_app_info_get_all (void)
5863 GHashTableIter iter;
5869 gio_win32_appinfo_init (TRUE);
5870 g_mutex_lock (&gio_win32_appinfo_mutex);
5873 g_hash_table_iter_init (&iter, apps_by_id);
5874 while (g_hash_table_iter_next (&iter, NULL, &value))
5875 apps = g_list_prepend (apps, g_object_ref (G_OBJECT (value)));
5877 g_mutex_unlock (&gio_win32_appinfo_mutex);
5880 for (apps_i = apps; apps_i; apps_i = apps_i->next)
5881 infos = g_list_prepend (infos,
5882 g_win32_app_info_new_from_app (apps_i->data, NULL));
5884 g_list_free_full (apps, g_object_unref);
5890 g_app_info_get_all_for_type (const char *content_type)
5892 GWin32AppInfoFileExtension *ext = NULL;
5894 GWin32AppInfoHandler *handler;
5895 GHashTableIter iter;
5896 GHashTable *apps = NULL;
5898 GWin32AppInfoShellVerb *shverb;
5900 ext_down = g_utf8_casefold (content_type, -1);
5905 gio_win32_appinfo_init (TRUE);
5906 g_mutex_lock (&gio_win32_appinfo_mutex);
5908 /* Assuming that "content_type" is a file extension, not a MIME type */
5909 g_set_object (&ext, g_hash_table_lookup (extensions, ext_down));
5912 g_mutex_unlock (&gio_win32_appinfo_mutex);
5918 /* Used as a set to ensure uniqueness */
5919 apps = g_hash_table_new (g_direct_hash, g_direct_equal);
5921 if (ext->chosen_handler != NULL &&
5922 ext->chosen_handler->verbs->len > 0 &&
5923 (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL)
5925 g_hash_table_add (apps, shverb->app);
5926 result = g_list_prepend (result,
5927 g_win32_app_info_new_from_app (shverb->app,
5928 ext->chosen_handler));
5931 g_hash_table_iter_init (&iter, ext->handlers);
5933 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5937 for (vi = 0; vi < handler->verbs->len; vi++)
5939 shverb = _verb_idx (handler->verbs, vi);
5941 if (shverb->app == NULL ||
5942 g_hash_table_contains (apps, shverb->app))
5945 g_hash_table_add (apps, shverb->app);
5946 result = g_list_prepend (result,
5947 g_win32_app_info_new_from_app (shverb->app,
5952 g_clear_object (&ext);
5953 result = g_list_reverse (result);
5954 g_hash_table_unref (apps);
5960 g_app_info_get_fallback_for_type (const gchar *content_type)
5962 /* TODO: fix this once gcontenttype support is improved */
5963 return g_app_info_get_all_for_type (content_type);
5967 g_app_info_get_recommended_for_type (const gchar *content_type)
5969 /* TODO: fix this once gcontenttype support is improved */
5970 return g_app_info_get_all_for_type (content_type);
5974 g_app_info_reset_type_associations (const char *content_type)