Change LGPL-2.1+ to LGPL-2.1-or-later
[platform/upstream/glib.git] / gio / gwin32appinfo.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  * Copyright (C) 2014 Руслан Ижбулатов
5  *
6  * SPDX-License-Identifier: LGPL-2.1-or-later
7  *
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.
12  *
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.
17  *
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/>.
20  *
21  * Authors: Alexander Larsson <alexl@redhat.com>
22  *          Руслан Ижбулатов  <lrn1986@gmail.com>
23  */
24
25 #include "config.h"
26
27 #define COBJMACROS
28
29 #include <string.h>
30
31 #include "gcontenttype.h"
32 #include "gwin32appinfo.h"
33 #include "gappinfo.h"
34 #include "gioerror.h"
35 #include "gfile.h"
36 #include <glib/gstdio.h>
37 #include "glibintl.h"
38 #include <gio/gwin32registrykey.h>
39 #include <shlobj.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.
43  */
44 #include "gwin32api-application-activation-manager.h"
45
46 #include <windows.h>
47 /* For SHLoadIndirectString() */
48 #include <shlwapi.h>
49
50 #include <glib/gstdioprivate.h>
51 #include "giowin32-priv.h"
52 #include "glib-private.h"
53
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.
73  *
74  *
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>).
82  *
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
89  *
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.
101  */
102
103 #define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index))
104
105 #define _lookup_by_verb(array, verb, dst, itemtype) do { \
106   gsize _index; \
107   itemtype *_v; \
108   for (_index = 0; array && _index < array->len; _index++) \
109     { \
110       _v = (itemtype *) g_ptr_array_index (array, _index); \
111       if (_wcsicmp (_v->verb_name, (verb)) == 0) \
112         { \
113           *(dst) = _v; \
114           break; \
115         } \
116     } \
117   if (array == NULL || _index >= array->len) \
118     *(dst) = NULL; \
119 } while (0)
120
121 #define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb)
122
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"
126  */
127 typedef struct _reg_verb {
128   gunichar2 *name;
129   gunichar2 *shellpath;
130 } reg_verb;
131
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;
137
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;
143
144 struct _GWin32AppInfoURLSchemaClass
145 {
146   GObjectClass parent_class;
147 };
148
149 struct _GWin32AppInfoFileExtensionClass
150 {
151   GObjectClass parent_class;
152 };
153
154 struct _GWin32AppInfoHandlerClass
155 {
156   GObjectClass parent_class;
157 };
158
159 struct _GWin32AppInfoApplicationClass
160 {
161   GObjectClass parent_class;
162 };
163
164 struct _GWin32AppInfoShellVerbClass
165 {
166   GObjectClass parent_class;
167 };
168
169 struct _GWin32AppInfoURLSchema {
170   GObject parent_instance;
171
172   /* url schema (stuff before ':') */
173   gunichar2 *schema;
174
175   /* url schema (stuff before ':'), in UTF-8 */
176   gchar *schema_u8;
177
178   /* url schema (stuff before ':'), in UTF-8, folded */
179   gchar *schema_u8_folded;
180
181   /* Handler currently selected for this schema. Can be NULL. */
182   GWin32AppInfoHandler *chosen_handler;
183
184   /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema.
185    * Includes the chosen handler, if any.
186    */
187   GHashTable *handlers;
188 };
189
190 struct _GWin32AppInfoHandler {
191   GObject parent_instance;
192
193   /* Usually a class name in HKCR */
194   gunichar2 *handler_id;
195
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).
199    */
200   GWin32RegistryKey *key;
201
202   /* @handler_id, in UTF-8, folded */
203   gchar *handler_id_folded;
204
205   /* Icon of the application for this handler */
206   GIcon *icon;
207
208   /* Verbs that this handler supports */
209   GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
210
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.
215    */
216   gunichar2 *uwp_aumid;
217 };
218
219 struct _GWin32AppInfoShellVerb {
220   GObject parent_instance;
221
222   /* The verb that is used to invoke this handler. */
223   gunichar2 *verb_name;
224
225   /* User-friendly (localized) verb name. */
226   gchar *verb_displayname;
227
228   /* %TRUE if this verb is for a UWP app.
229    * It means that @command, @executable and @dll_function are %NULL.
230    */
231   gboolean is_uwp;
232
233   /* shell/verb/command */
234   gunichar2 *command;
235
236   /* Same as @command, but in UTF-8 */
237   gchar *command_utf8;
238
239   /* Executable of the program (UTF-8) */
240   gchar *executable;
241
242   /* Executable of the program (for matching, in folded form; UTF-8) */
243   gchar *executable_folded;
244
245   /* Pointer to a location within @executable */
246   gchar *executable_basename;
247
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...'.
252    */
253   gchar *dll_function;
254
255   /* The application that is linked to this verb. */
256   GWin32AppInfoApplication *app;
257 };
258
259 struct _GWin32AppInfoFileExtension {
260   GObject parent_instance;
261
262   /* File extension (with leading '.') */
263   gunichar2 *extension;
264
265   /* File extension (with leading '.'), in UTF-8 */
266   gchar *extension_u8;
267
268   /* handler currently selected for this extension. Can be NULL. */
269   GWin32AppInfoHandler *chosen_handler;
270
271   /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension.
272    * Includes the chosen handler, if any.
273    */
274   GHashTable *handlers;
275 };
276
277 struct _GWin32AppInfoApplication {
278   GObject parent_instance;
279
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).
290    */
291   gunichar2 *canonical_name;
292
293   /* @canonical_name, in UTF-8 */
294   gchar *canonical_name_u8;
295
296   /* @canonical_name, in UTF-8, folded */
297   gchar *canonical_name_folded;
298
299   /* Human-readable name in English. Can be NULL */
300   gunichar2 *pretty_name;
301
302   /* Human-readable name in English, UTF-8. Can be NULL */
303   gchar *pretty_name_u8;
304
305   /* Human-readable name in user's language. Can be NULL  */
306   gunichar2 *localized_pretty_name;
307
308   /* Human-readable name in user's language, UTF-8. Can be NULL  */
309   gchar *localized_pretty_name_u8;
310
311   /* Description, could be in user's language. Can be NULL */
312   gunichar2 *description;
313
314   /* Description, could be in user's language, UTF-8. Can be NULL */
315   gchar *description_u8;
316
317   /* Verbs that this application supports */
318   GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
319
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.
323    */
324   GHashTable *supported_urls;
325
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.
329    */
330   GHashTable *supported_exts;
331
332   /* Icon of the application (remember, handler can have its own icon too) */
333   GIcon *icon;
334
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.
339    */
340   gboolean no_open_with;
341
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.
345    */
346   gboolean user_specific;
347
348   /* Set to TRUE for applications that are machine-wide defaults (i.e. default
349    * browser) */
350   gboolean default_app;
351
352   /* Set to TRUE for UWP applications */
353   gboolean is_uwp;
354 };
355
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))
358
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))
361
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))
364
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))
367
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))
370
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;
376
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)
382
383 static void
384 g_win32_appinfo_url_schema_dispose (GObject *object)
385 {
386   GWin32AppInfoURLSchema *url = G_WIN32_APPINFO_URL_SCHEMA (object);
387
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);
394 }
395
396
397 static void
398 g_win32_appinfo_handler_dispose (GObject *object)
399 {
400   GWin32AppInfoHandler *handler = G_WIN32_APPINFO_HANDLER (object);
401
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);
409 }
410
411 static void
412 g_win32_appinfo_file_extension_dispose (GObject *object)
413 {
414   GWin32AppInfoFileExtension *ext = G_WIN32_APPINFO_FILE_EXTENSION (object);
415
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);
421 }
422
423 static void
424 g_win32_appinfo_shell_verb_dispose (GObject *object)
425 {
426   GWin32AppInfoShellVerb *shverb = G_WIN32_APPINFO_SHELL_VERB (object);
427
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);
437 }
438
439 static void
440 g_win32_appinfo_application_dispose (GObject *object)
441 {
442   GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object);
443
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);
458 }
459
460 static const gchar *
461 g_win32_appinfo_application_get_some_name (GWin32AppInfoApplication *app)
462 {
463   if (app->localized_pretty_name_u8)
464     return app->localized_pretty_name_u8;
465
466   if (app->pretty_name_u8)
467     return app->pretty_name_u8;
468
469   return app->canonical_name_u8;
470 }
471
472 static void
473 g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass)
474 {
475   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
476
477   gobject_class->dispose = g_win32_appinfo_url_schema_dispose;
478 }
479
480 static void
481 g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klass)
482 {
483   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
484
485   gobject_class->dispose = g_win32_appinfo_file_extension_dispose;
486 }
487
488 static void
489 g_win32_appinfo_shell_verb_class_init (GWin32AppInfoShellVerbClass *klass)
490 {
491   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
492
493   gobject_class->dispose = g_win32_appinfo_shell_verb_dispose;
494 }
495
496 static void
497 g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass)
498 {
499   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
500
501   gobject_class->dispose = g_win32_appinfo_handler_dispose;
502 }
503
504 static void
505 g_win32_appinfo_application_class_init (GWin32AppInfoApplicationClass *klass)
506 {
507   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
508
509   gobject_class->dispose = g_win32_appinfo_application_dispose;
510 }
511
512 static void
513 g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self)
514 {
515   self->handlers = g_hash_table_new_full (g_str_hash,
516                                           g_str_equal,
517                                           g_free,
518                                           g_object_unref);
519 }
520
521 static void
522 g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self)
523 {
524 }
525
526 static void
527 g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
528 {
529   self->handlers = g_hash_table_new_full (g_str_hash,
530                                           g_str_equal,
531                                           g_free,
532                                           g_object_unref);
533 }
534
535 static void
536 g_win32_appinfo_handler_init (GWin32AppInfoHandler *self)
537 {
538   self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
539 }
540
541 static void
542 g_win32_appinfo_application_init (GWin32AppInfoApplication *self)
543 {
544   self->supported_urls = g_hash_table_new_full (g_str_hash,
545                                                 g_str_equal,
546                                                 g_free,
547                                                 g_object_unref);
548   self->supported_exts = g_hash_table_new_full (g_str_hash,
549                                                 g_str_equal,
550                                                 g_free,
551                                                 g_object_unref);
552   self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
553 }
554
555 /* The AppInfo threadpool that does asynchronous AppInfo tree rebuilds */
556 static GThreadPool *gio_win32_appinfo_threadpool;
557
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).
561  */
562 static GMutex gio_win32_appinfo_mutex;
563
564 /* Any thread wanting to access AppInfo can wait on this condition */
565 static GCond gio_win32_appinfo_cond;
566
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.
573  */
574 static gint gio_win32_appinfo_update_counter = 0;
575
576 /* Map of owned ".ext" (with '.', UTF-8, folded)
577  * to GWin32AppInfoFileExtension ptr
578  */
579 static GHashTable *extensions = NULL;
580
581 /* Map of owned "schema" (without ':', UTF-8, folded)
582  * to GWin32AppInfoURLSchema ptr
583  */
584 static GHashTable *urls = NULL;
585
586 /* Map of owned "appID" (UTF-8, folded) to
587  * a GWin32AppInfoApplication
588  */
589 static GHashTable *apps_by_id = NULL;
590
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.
596  */
597 static GHashTable *apps_by_exe = NULL;
598
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.
603  */
604 static GHashTable *fake_apps = NULL;
605
606 /* Map of owned "handler id" (UTF-8, folded)
607  * to a GWin32AppInfoHandler
608  */
609 static GHashTable *handlers = NULL;
610
611 /* Temporary (only exists while the registry is being scanned) table
612  * that maps GWin32RegistryKey objects (keeps a ref) to owned AUMId wchar strings.
613  */
614 static GHashTable *uwp_handler_table = NULL;
615
616 /* Watch this whole subtree */
617 static GWin32RegistryKey *url_associations_key;
618
619 /* Watch this whole subtree */
620 static GWin32RegistryKey *file_exts_key;
621
622 /* Watch this whole subtree */
623 static GWin32RegistryKey *user_clients_key;
624
625 /* Watch this whole subtree */
626 static GWin32RegistryKey *system_clients_key;
627
628 /* Watch this key */
629 static GWin32RegistryKey *user_registered_apps_key;
630
631 /* Watch this key */
632 static GWin32RegistryKey *system_registered_apps_key;
633
634 /* Watch this whole subtree */
635 static GWin32RegistryKey *applications_key;
636
637 /* Watch this key */
638 static GWin32RegistryKey *classes_root_key;
639
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))
649
650 /* for g_wcsdup(),
651  *     _g_win32_extract_executable(),
652  *     _g_win32_fixup_broken_microsoft_rundll_commandline()
653  */
654 #include "giowin32-private.c"
655
656 /* for g_win32_package_parser_enum_packages() */
657 #include "gwin32packageparser.h"
658
659 static void
660 read_handler_icon (GWin32RegistryKey  *key,
661                    GIcon             **icon_out)
662 {
663   GWin32RegistryKey *icon_key;
664   GWin32RegistryValueType default_type;
665   gchar *default_value;
666
667   g_assert (icon_out);
668
669   *icon_out = NULL;
670
671   icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);
672
673   if (icon_key == NULL)
674     return;
675
676   if (g_win32_registry_key_get_value (icon_key,
677                                       NULL,
678                                       TRUE,
679                                       "",
680                                       &default_type,
681                                       (gpointer *) &default_value,
682                                       NULL,
683                                       NULL))
684     {
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).
690        */
691       if (default_type == G_WIN32_REGISTRY_VALUE_STR &&
692           default_value[0] != '\0')
693         *icon_out = g_themed_icon_new (default_value);
694
695       g_clear_pointer (&default_value, g_free);
696     }
697
698   g_object_unref (icon_key);
699 }
700
701 static void
702 reg_verb_free (gpointer p)
703 {
704   if (p == NULL)
705     return;
706
707   g_free (((reg_verb *) p)->name);
708   g_free (((reg_verb *) p)->shellpath);
709   g_free (p);
710 }
711
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') && \
717   ((x)[4] == L'\0') \
718 )
719
720 /* default verb (if any) comes first,
721  * then "open", then the rest of the verbs
722  * are sorted alphabetically
723  */
724 static gint
725 compare_verbs (gconstpointer a,
726                gconstpointer b,
727                gpointer user_data)
728 {
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;
732   gboolean is_open_ca;
733   gboolean is_open_cb;
734
735   if (def != NULL)
736     {
737       if (_wcsicmp (ca->name, def) == 0)
738         return -1;
739       else if (_wcsicmp (cb->name, def) == 0)
740         return 1;
741     }
742
743   is_open_ca = is_open (ca->name);
744   is_open_cb = is_open (cb->name);
745
746   if (is_open_ca && !is_open_cb)
747     return -1;
748   else if (is_open_ca && !is_open_cb)
749     return 1;
750
751   return _wcsicmp (ca->name, cb->name);
752 }
753
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);
756
757 static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED;
758
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
770  *                       commandline.
771  */
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);
780
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);
786
787 static GWin32AppInfoURLSchema *    get_schema_object      (const gunichar2 *schema,
788                                                            const gchar     *schema_u8,
789                                                            const gchar     *schema_u8_folded);
790
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);
795
796 static GWin32AppInfoFileExtension *get_ext_object         (const gunichar2 *ext,
797                                                            const gchar     *ext_u8,
798                                                            const gchar     *ext_u8_folded);
799
800
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);
809
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);
818
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);
826
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);
832
833 /* output_size is in *bytes*, not gunichar2s! */
834 static gboolean
835 build_registry_path (gunichar2 *output, gsize output_size, ...)
836 {
837   va_list ap;
838   gboolean result;
839
840   va_start (ap, output_size);
841
842   result = build_registry_pathv (output, output_size, ap);
843
844   va_end (ap);
845
846   return result;
847 }
848
849 /* output_size is in *bytes*, not gunichar2s! */
850 static gboolean
851 build_registry_pathv (gunichar2 *output, gsize output_size, va_list components)
852 {
853   va_list lentest;
854   gunichar2 *p;
855   gunichar2 *component;
856   gsize length;
857
858   if (output == NULL)
859     return FALSE;
860
861   va_copy (lentest, components);
862
863   for (length = 0, component = va_arg (lentest, gunichar2 *);
864        component != NULL;
865        component = va_arg (lentest, gunichar2 *))
866     {
867       length += wcslen (component);
868     }
869
870   va_end (lentest);
871
872   if ((length >= REG_PATH_MAX_SIZE) ||
873       (length * sizeof (gunichar2) >= output_size))
874     return FALSE;
875
876   output[0] = L'\0';
877
878   for (p = output, component = va_arg (components, gunichar2 *);
879        component != NULL;
880        component = va_arg (components, gunichar2 *))
881     {
882       length = wcslen (component);
883       wcscat (p, component);
884       p += length;
885     }
886
887   return TRUE;
888 }
889
890
891 static GWin32RegistryKey *
892 _g_win32_registry_key_build_and_new_w (GError **error, ...)
893 {
894   va_list ap;
895   gunichar2 key_path[REG_PATH_MAX_SIZE + 1];
896   GWin32RegistryKey *key;
897
898   va_start (ap, error);
899
900   key = NULL;
901
902   if (build_registry_pathv (key_path, sizeof (key_path), ap))
903     key = g_win32_registry_key_new_w (key_path, error);
904
905   va_end (ap);
906
907   return key;
908 }
909
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
919  * empty string.
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.
930  */
931 static gboolean
932 get_verbs (GWin32RegistryKey  *program_id_key,
933            const reg_verb    **preferred_verb,
934            GList             **verbs,
935            const gunichar2    *verbname_prefix,
936            const gunichar2    *verbshell_prefix,
937            gboolean           *is_uwp)
938 {
939   GWin32RegistrySubkeyIter iter;
940   GWin32RegistryKey *key;
941   GWin32RegistryValueType val_type;
942   gunichar2 *default_verb;
943   gsize verbshell_prefix_len;
944   gsize verbname_prefix_len;
945   GList *i;
946
947   g_assert (program_id_key && verbs && preferred_verb);
948
949   *verbs = NULL;
950   *preferred_verb = NULL;
951
952   key = g_win32_registry_key_get_child_w (program_id_key,
953                                           verbshell_prefix,
954                                           NULL);
955
956   if (key == NULL)
957     return FALSE;
958
959   if (!g_win32_registry_subkey_iter_init (&iter, key, NULL))
960     {
961       g_object_unref (key);
962
963       return FALSE;
964     }
965
966   verbshell_prefix_len = g_utf16_len (verbshell_prefix);
967   verbname_prefix_len = g_utf16_len (verbname_prefix);
968
969   while (g_win32_registry_subkey_iter_next (&iter, TRUE, NULL))
970     {
971       const gunichar2 *name;
972       gsize name_len;
973       GWin32RegistryKey *subkey;
974       gboolean has_subcommands;
975       const reg_verb *tmp;
976       GWin32RegistryValueType subc_type;
977       reg_verb *rverb;
978       const gunichar2 *shell = L"Shell";
979       const gsize shell_len = g_utf16_len (shell);
980
981       if (!g_win32_registry_subkey_iter_get_name_w (&iter, &name, &name_len, NULL))
982         continue;
983
984       subkey = g_win32_registry_key_get_child_w (key,
985                                                  name,
986                                                  NULL);
987
988       /* We may not have the required access rights to open the child key */
989       if (subkey == NULL)
990         continue;
991
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.
1003        */
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,
1007                                             NULL,
1008                                             TRUE,
1009                                             L"Subcommands",
1010                                             &subc_type,
1011                                             NULL,
1012                                             NULL,
1013                                             NULL) &&
1014           subc_type == G_WIN32_REGISTRY_VALUE_STR)
1015         {
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;
1025
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);
1033         }
1034
1035       /* Presence of subcommands means that this key itself is not a command-key */
1036       if (has_subcommands)
1037         {
1038           g_clear_object (&subkey);
1039           continue;
1040         }
1041
1042       if (is_uwp != NULL && *is_uwp &&
1043           !g_win32_registry_key_get_value_w (subkey,
1044                                              NULL,
1045                                              TRUE,
1046                                              L"ActivatableClassId",
1047                                              &subc_type,
1048                                              NULL,
1049                                              NULL,
1050                                              NULL))
1051         {
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.
1057            */
1058            *is_uwp = FALSE;
1059         }
1060
1061       g_clear_object (&subkey);
1062
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\\")
1070        */
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);
1082     }
1083
1084   g_win32_registry_subkey_iter_clear (&iter);
1085
1086   if (*verbs == NULL)
1087     {
1088       g_object_unref (key);
1089
1090       return FALSE;
1091     }
1092
1093   default_verb = NULL;
1094
1095   if (g_win32_registry_key_get_value_w (key,
1096                                         NULL,
1097                                         TRUE,
1098                                         L"",
1099                                         &val_type,
1100                                         (void **) &default_verb,
1101                                         NULL,
1102                                         NULL) &&
1103       (val_type != G_WIN32_REGISTRY_VALUE_STR ||
1104        g_utf16_len (default_verb) <= 0))
1105     g_clear_pointer (&default_verb, g_free);
1106
1107   g_object_unref (key);
1108
1109   /* Only sort at the top level */
1110   if (verbname_prefix[0] == 0)
1111     {
1112       *verbs = g_list_sort_with_data (*verbs, compare_verbs, default_verb);
1113
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;
1117     }
1118
1119   g_clear_pointer (&default_verb, g_free);
1120
1121   return TRUE;
1122 }
1123
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
1129  * respectively.
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
1132  */
1133 static void
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)
1140 {
1141   GWin32AppInfoURLSchema *schema_rec;
1142   GWin32AppInfoHandler *handler_rec;
1143   gunichar2 *handler_id;
1144   GList *verbs;
1145   const reg_verb *preferred_verb;
1146   gchar *handler_id_u8;
1147   gchar *handler_id_u8_folded;
1148   gunichar2 *uwp_aumid;
1149   gboolean is_uwp;
1150   GWin32RegistryKey *handler_key;
1151
1152   if ((handler_id = decide_which_id_to_use (program_id,
1153                                             &handler_key,
1154                                             &handler_id_u8,
1155                                             &handler_id_u8_folded,
1156                                             &uwp_aumid)) == NULL)
1157     return;
1158
1159   is_uwp = uwp_aumid != NULL;
1160
1161   if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1162     {
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);
1168
1169       return;
1170     }
1171
1172   if (!is_uwp && uwp_aumid != NULL)
1173     g_clear_pointer (&uwp_aumid, g_free);
1174
1175   schema_rec = get_schema_object (schema,
1176                                   schema_u8,
1177                                   schema_u8_folded);
1178
1179   handler_rec = get_handler_object (handler_id_u8_folded,
1180                                     handler_key,
1181                                     handler_id,
1182                                     uwp_aumid);
1183
1184   if (is_user_choice || schema_rec->chosen_handler == NULL)
1185     g_set_object (&schema_rec->chosen_handler, handler_rec);
1186
1187   g_hash_table_insert (schema_rec->handlers,
1188                        g_strdup (handler_id_u8_folded),
1189                        g_object_ref (handler_rec));
1190
1191   g_clear_object (&handler_key);
1192
1193   if (app)
1194     g_hash_table_insert (app->supported_urls,
1195                          g_strdup (schema_rec->schema_u8_folded),
1196                          g_object_ref (handler_rec));
1197
1198   if (uwp_aumid == NULL)
1199     process_verbs_commands (g_steal_pointer (&verbs),
1200                             preferred_verb,
1201                             HKCR,
1202                             handler_id,
1203                             TRUE,
1204                             handler_add_verb,
1205                             handler_rec,
1206                             app);
1207   else
1208     process_uwp_verbs (g_steal_pointer (&verbs),
1209                        preferred_verb,
1210                        HKCR,
1211                        handler_id,
1212                        TRUE,
1213                        handler_rec,
1214                        app);
1215
1216
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);
1221 }
1222
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
1228  */
1229 static void
1230 get_file_ext (const gunichar2            *program_id,
1231               const gunichar2            *file_extension,
1232               GWin32AppInfoApplication   *app,
1233               gboolean                    is_user_choice)
1234 {
1235   GWin32AppInfoHandler *handler_rec;
1236   gunichar2 *handler_id;
1237   const reg_verb *preferred_verb;
1238   GList *verbs;
1239   gchar *handler_id_u8;
1240   gchar *handler_id_u8_folded;
1241   gunichar2 *uwp_aumid;
1242   gboolean is_uwp;
1243   GWin32RegistryKey *handler_key;
1244   GWin32AppInfoFileExtension *file_extn;
1245   gchar *file_extension_u8;
1246   gchar *file_extension_u8_folded;
1247
1248   if ((handler_id = decide_which_id_to_use (program_id,
1249                                             &handler_key,
1250                                             &handler_id_u8,
1251                                             &handler_id_u8_folded,
1252                                             &uwp_aumid)) == NULL)
1253     return;
1254
1255   if (!g_utf16_to_utf8_and_fold (file_extension,
1256                                  -1,
1257                                  &file_extension_u8,
1258                                  &file_extension_u8_folded))
1259     {
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);
1265
1266       return;
1267     }
1268
1269   is_uwp = uwp_aumid != NULL;
1270
1271   if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1272     {
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);
1280
1281       return;
1282     }
1283
1284   if (!is_uwp && uwp_aumid != NULL)
1285     g_clear_pointer (&uwp_aumid, g_free);
1286
1287   file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded);
1288
1289   handler_rec = get_handler_object (handler_id_u8_folded,
1290                                     handler_key,
1291                                     handler_id,
1292                                     uwp_aumid);
1293
1294   if (is_user_choice || file_extn->chosen_handler == NULL)
1295     g_set_object (&file_extn->chosen_handler, handler_rec);
1296
1297   g_hash_table_insert (file_extn->handlers,
1298                        g_strdup (handler_id_u8_folded),
1299                        g_object_ref (handler_rec));
1300
1301   if (app)
1302     g_hash_table_insert (app->supported_exts,
1303                          g_strdup (file_extension_u8_folded),
1304                          g_object_ref (handler_rec));
1305
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);
1309
1310   if (uwp_aumid == NULL)
1311     process_verbs_commands (g_steal_pointer (&verbs),
1312                             preferred_verb,
1313                             HKCR,
1314                             handler_id,
1315                             TRUE,
1316                             handler_add_verb,
1317                             handler_rec,
1318                             app);
1319   else
1320     process_uwp_verbs (g_steal_pointer (&verbs),
1321                        preferred_verb,
1322                        HKCR,
1323                        handler_id,
1324                        TRUE,
1325                        handler_rec,
1326                        app);
1327
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);
1332 }
1333
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.
1341  */
1342 static gunichar2 *
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)
1348 {
1349   GWin32RegistryKey *key;
1350   GWin32RegistryKey *uwp_key;
1351   GWin32RegistryValueType val_type;
1352   gunichar2 *proxy_id;
1353   gunichar2 *return_id;
1354   gunichar2 *uwp_aumid;
1355   gboolean got_value;
1356   gchar *handler_id_u8;
1357   gchar *handler_id_u8_folded;
1358   g_assert (program_id);
1359
1360   if (return_key)
1361     *return_key = NULL;
1362
1363   if (return_uwp_aumid)
1364     *return_uwp_aumid = NULL;
1365
1366   key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL);
1367
1368   if (key == NULL)
1369     return NULL;
1370
1371   /* Check for UWP first */
1372   uwp_aumid = NULL;
1373   uwp_key = g_win32_registry_key_get_child_w (key, L"Application", NULL);
1374
1375   if (uwp_key != NULL)
1376     {
1377       got_value = g_win32_registry_key_get_value_w (uwp_key,
1378                                                     NULL,
1379                                                     TRUE,
1380                                                     L"AppUserModelID",
1381                                                     &val_type,
1382                                                     (void **) &uwp_aumid,
1383                                                     NULL,
1384                                                     NULL);
1385       if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1386         g_clear_pointer (&uwp_aumid, g_free);
1387
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.
1392        */
1393       if (uwp_aumid == NULL)
1394         g_debug ("ProgramID %S looks like a UWP application, but isn't",
1395                  program_id);
1396       else
1397         g_hash_table_insert (uwp_handler_table, g_object_ref (uwp_key), g_wcsdup (uwp_aumid, -1));
1398
1399       g_object_unref (uwp_key);
1400     }
1401
1402   /* Then check for proxy */
1403   proxy_id = NULL;
1404
1405   if (uwp_aumid == NULL)
1406     {
1407       got_value = g_win32_registry_key_get_value_w (key,
1408                                                     NULL,
1409                                                     TRUE,
1410                                                     L"",
1411                                                     &val_type,
1412                                                     (void **) &proxy_id,
1413                                                     NULL,
1414                                                     NULL);
1415       if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1416         g_clear_pointer (&proxy_id, g_free);
1417     }
1418
1419   return_id = NULL;
1420
1421   if (proxy_id)
1422     {
1423       GWin32RegistryKey *proxy_key;
1424       proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL);
1425
1426       if (proxy_key)
1427         {
1428           if (return_key)
1429             *return_key = g_steal_pointer (&proxy_key);
1430           g_clear_object (&proxy_key);
1431
1432           return_id = g_steal_pointer (&proxy_id);
1433         }
1434
1435       g_clear_pointer (&proxy_id, g_free);
1436     }
1437
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,
1441                                  -1,
1442                                  &handler_id_u8,
1443                                  &handler_id_u8_folded))
1444     {
1445       g_clear_object (&key);
1446       if (return_key)
1447         g_clear_object (return_key);
1448       g_clear_pointer (&return_id, g_free);
1449
1450       return NULL;
1451     }
1452
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);
1462
1463   if (return_id == NULL && return_key)
1464     *return_key = g_steal_pointer (&key);
1465   g_clear_object (&key);
1466
1467   if (return_id == NULL)
1468     return g_wcsdup (program_id, -1);
1469
1470   return return_id;
1471 }
1472
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.
1483  */
1484 static void
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)
1493 {
1494   GList *i;
1495   gboolean got_value;
1496
1497   g_assert (handler != NULL);
1498   g_assert (verbs != NULL);
1499   g_assert (progid != NULL);
1500
1501   for (i = verbs; i; i = i->next)
1502     {
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;
1511
1512       key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1513                                                    L"\\", verb->shellpath, L"\\command", NULL);
1514
1515       if (key == NULL)
1516         {
1517           g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey",
1518                    path_to_progid, progid, verb->shellpath);
1519           continue;
1520         }
1521
1522       command_value = NULL;
1523       got_value = g_win32_registry_key_get_value_w (key,
1524                                                     NULL,
1525                                                     TRUE,
1526                                                     L"",
1527                                                     &val_type,
1528                                                     (void **) &command_value,
1529                                                     NULL,
1530                                                     NULL);
1531       g_clear_object (&key);
1532
1533       if (!got_value ||
1534           val_type != G_WIN32_REGISTRY_VALUE_STR ||
1535           (command_value_utf8 = g_utf16_to_utf8 (command_value,
1536                                                  -1,
1537                                                  NULL,
1538                                                  NULL,
1539                                                  NULL)) == NULL)
1540         {
1541           g_clear_pointer (&command_value, g_free);
1542           continue;
1543         }
1544
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);
1549
1550       if (verb_key)
1551         {
1552           gsize verb_displayname_len;
1553
1554           got_value = g_win32_registry_key_get_value_w (verb_key,
1555                                                         g_win32_registry_get_os_dirs_w (),
1556                                                         TRUE,
1557                                                         L"MUIVerb",
1558                                                         &val_type,
1559                                                         (void **) &verb_displayname,
1560                                                         &verb_displayname_len,
1561                                                         NULL);
1562
1563           if (got_value &&
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);
1567
1568           g_clear_pointer (&verb_displayname, g_free);
1569
1570           if (verb_displayname_u8 == NULL)
1571             {
1572               got_value = g_win32_registry_key_get_value_w (verb_key,
1573                                                             NULL,
1574                                                             TRUE,
1575                                                             L"",
1576                                                             &val_type,
1577                                                             (void **) &verb_displayname,
1578                                                             &verb_displayname_len,
1579                                                             NULL);
1580
1581               if (got_value &&
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);
1585             }
1586
1587           g_clear_pointer (&verb_displayname, g_free);
1588           g_clear_object (&verb_key);
1589         }
1590
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),
1595                FALSE);
1596
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);
1600     }
1601
1602   g_list_free_full (verbs, reg_verb_free);
1603 }
1604
1605 static void
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)
1613 {
1614   GList *i;
1615
1616   g_assert (verbs != NULL);
1617
1618   for (i = verbs; i; i = i->next)
1619     {
1620       const reg_verb *verb = (const reg_verb *) i->data;
1621       GWin32RegistryKey *key;
1622       gboolean got_value;
1623       GWin32RegistryValueType val_type;
1624       gunichar2 *acid;
1625       gsize acid_len;
1626
1627       key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1628                                                    L"\\", verb->shellpath, NULL);
1629
1630       if (key == NULL)
1631         {
1632           g_debug ("%S%S\\%S does not exist",
1633                    path_to_progid, progid, verb->shellpath);
1634           continue;
1635         }
1636
1637       acid = NULL;
1638       got_value = g_win32_registry_key_get_value_w (key,
1639                                                     g_win32_registry_get_os_dirs_w (),
1640                                                     TRUE,
1641                                                     L"ActivatableClassId",
1642                                                     &val_type,
1643                                                     (void **) &acid,
1644                                                     &acid_len,
1645                                                     NULL);
1646
1647       if (got_value &&
1648           val_type == G_WIN32_REGISTRY_VALUE_STR &&
1649           acid_len > sizeof (gunichar2))
1650         {
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.
1654            */
1655           uwp_handler_add_verb (handler_rec,
1656                                 app,
1657                                 verb->name,
1658                                 NULL,
1659                                 (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
1660                                 (!preferred_verb && autoprefer_first_verb && i == verbs));
1661         }
1662       else
1663         {
1664           g_debug ("%S%S\\%S does not have an ActivatableClassId string value",
1665                    path_to_progid, progid, verb->shellpath);
1666         }
1667
1668       g_clear_pointer (&acid, g_free);
1669       g_clear_object (&key);
1670     }
1671
1672   g_list_free_full (verbs, reg_verb_free);
1673 }
1674
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.
1680  */
1681 static GWin32AppInfoURLSchema *
1682 get_schema_object (const gunichar2 *schema,
1683                    const gchar     *schema_u8,
1684                    const gchar     *schema_u8_folded)
1685 {
1686   GWin32AppInfoURLSchema *schema_rec;
1687
1688   schema_rec = g_hash_table_lookup (urls, schema_u8_folded);
1689
1690   if (schema_rec != NULL)
1691     return schema_rec;
1692
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);
1698
1699   return schema_rec;
1700 }
1701
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.
1707  */
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)
1713 {
1714   GWin32AppInfoHandler *handler_rec;
1715
1716   handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded);
1717
1718   if (handler_rec != NULL)
1719     return handler_rec;
1720
1721   handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
1722   if (handler_key)
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);
1726   if (uwp_aumid)
1727     handler_rec->uwp_aumid = g_wcsdup (uwp_aumid, -1);
1728   if (handler_key)
1729     read_handler_icon (handler_key, &handler_rec->icon);
1730   g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec);
1731
1732   return handler_rec;
1733 }
1734
1735 static void
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)
1744 {
1745   GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1;
1746   GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1747   GWin32AppInfoShellVerb *shverb = NULL;
1748
1749   _verb_lookup (handler_rec->verbs, verb, &shverb);
1750
1751   if (shverb != NULL)
1752     return;
1753
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 */
1760   if (app_rec)
1761     shverb->app = g_object_ref (app_rec);
1762
1763   _g_win32_extract_executable (shverb->command,
1764                                &shverb->executable,
1765                                &shverb->executable_basename,
1766                                &shverb->executable_folded,
1767                                NULL,
1768                                &shverb->dll_function);
1769
1770   if (shverb->dll_function != NULL)
1771     _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1772
1773   if (!verb_is_preferred)
1774     g_ptr_array_add (handler_rec->verbs, shverb);
1775   else
1776     g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1777 }
1778
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.
1784  */
1785 static gboolean
1786 generate_new_verb_name (GPtrArray        *verbs,
1787                         const gunichar2  *verb,
1788                         const gchar      *verb_displayname,
1789                         gunichar2       **new_verb,
1790                         gchar           **new_displayname)
1791 {
1792   gsize counter;
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);
1797
1798   *new_verb = NULL;
1799   *new_displayname = NULL;
1800
1801   memcpy (new_verb_name, verb, orig_len * sizeof (gunichar2));
1802   for (counter = 0; counter < 255; counter++)
1803   {
1804     _snwprintf (&new_verb_name[orig_len], new_verb_name_len, L" (%zx)", counter);
1805     _verb_lookup (verbs, new_verb_name, &shverb);
1806
1807     if (shverb == NULL)
1808       {
1809         *new_verb = new_verb_name;
1810         if (verb_displayname != NULL)
1811           *new_displayname = g_strdup_printf ("%s (%zx)", verb_displayname, counter);
1812
1813         return TRUE;
1814       }
1815   }
1816
1817   return FALSE;
1818 }
1819
1820 static void
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)
1829 {
1830   gunichar2 *new_verb = NULL;
1831   gchar *new_displayname = NULL;
1832   GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1833   GWin32AppInfoShellVerb *shverb = NULL;
1834
1835   _verb_lookup (app_rec->verbs, verb, &shverb);
1836
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.
1842    */
1843   if (shverb != NULL)
1844     {
1845       gsize vi;
1846
1847       if (!invent_new_verb_name)
1848         return;
1849
1850       for (vi = 0; vi < app_rec->verbs->len; vi++)
1851         {
1852           GWin32AppInfoShellVerb *app_verb;
1853
1854           app_verb = _verb_idx (app_rec->verbs, vi);
1855
1856           if (_wcsicmp (command_line, app_verb->command) == 0)
1857             break;
1858         }
1859
1860       if (vi < app_rec->verbs->len ||
1861           !generate_new_verb_name (app_rec->verbs,
1862                                    verb,
1863                                    verb_displayname,
1864                                    &new_verb,
1865                                    &new_displayname))
1866         return;
1867     }
1868
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);
1872   else
1873     shverb->verb_name = g_steal_pointer (&new_verb);
1874   if (new_displayname == NULL)
1875     shverb->verb_displayname = g_strdup (verb_displayname);
1876   else
1877     shverb->verb_displayname = g_steal_pointer (&new_displayname);
1878
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);
1882
1883   _g_win32_extract_executable (shverb->command,
1884                                &shverb->executable,
1885                                &shverb->executable_basename,
1886                                &shverb->executable_folded,
1887                                NULL,
1888                                &shverb->dll_function);
1889
1890   if (shverb->dll_function != NULL)
1891     _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1892
1893   if (!verb_is_preferred)
1894     g_ptr_array_add (app_rec->verbs, shverb);
1895   else
1896     g_ptr_array_insert (app_rec->verbs, 0, shverb);
1897 }
1898
1899 static void
1900 uwp_app_add_verb (GWin32AppInfoApplication *app_rec,
1901                   const gunichar2          *verb,
1902                   const gchar              *verb_displayname)
1903 {
1904   GWin32AppInfoShellVerb *shverb = NULL;
1905
1906   _verb_lookup (app_rec->verbs, verb, &shverb);
1907
1908   if (shverb != NULL)
1909     return;
1910
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);
1915
1916   shverb->is_uwp = TRUE;
1917
1918   /* Strictly speaking, this is unnecessary, but
1919    * let's make it clear that UWP verbs have no
1920    * commands and executables.
1921    */
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;
1928
1929   g_ptr_array_add (app_rec->verbs, shverb);
1930 }
1931
1932 static void
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)
1938 {
1939   GWin32AppInfoShellVerb *shverb = NULL;
1940
1941   _verb_lookup (handler_rec->verbs, verb, &shverb);
1942
1943   if (shverb != NULL)
1944     return;
1945
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);
1949
1950   shverb->is_uwp = TRUE;
1951
1952   if (app)
1953     shverb->app = g_object_ref (app);
1954
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;
1961
1962   if (!verb_is_preferred)
1963     g_ptr_array_add (handler_rec->verbs, shverb);
1964   else
1965     g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1966 }
1967
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.
1973  */
1974 static GWin32AppInfoFileExtension *
1975 get_ext_object (const gunichar2 *ext,
1976                 const gchar     *ext_u8,
1977                 const gchar     *ext_u8_folded)
1978 {
1979   GWin32AppInfoFileExtension *file_extn;
1980
1981   if (g_hash_table_lookup_extended (extensions,
1982                                     ext_u8_folded,
1983                                     NULL,
1984                                     (void **) &file_extn))
1985     return file_extn;
1986
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);
1991
1992   return file_extn;
1993 }
1994
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).
2000  */
2001 static void
2002 collect_capable_apps_from_clients (GPtrArray *capable_apps,
2003                                    GPtrArray *priority_capable_apps,
2004                                    gboolean   user_registry)
2005 {
2006   GWin32RegistryKey *clients;
2007   GWin32RegistrySubkeyIter clients_iter;
2008
2009   const gunichar2 *client_type_name;
2010   gsize client_type_name_len;
2011
2012
2013   if (user_registry)
2014     clients =
2015         g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
2016                                      NULL);
2017   else
2018     clients =
2019         g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
2020                                      NULL);
2021
2022   if (clients == NULL)
2023     return;
2024
2025   if (!g_win32_registry_subkey_iter_init (&clients_iter, clients, NULL))
2026     {
2027       g_object_unref (clients);
2028       return;
2029     }
2030
2031   while (g_win32_registry_subkey_iter_next (&clients_iter, TRUE, NULL))
2032     {
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;
2039
2040       if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter,
2041                                                     &client_type_name,
2042                                                     &client_type_name_len,
2043                                                     NULL))
2044         continue;
2045
2046       system_client_type = g_win32_registry_key_get_child_w (clients,
2047                                                              client_type_name,
2048                                                              NULL);
2049
2050       if (system_client_type == NULL)
2051         continue;
2052
2053       if (g_win32_registry_key_get_value_w (system_client_type,
2054                                             NULL,
2055                                             TRUE,
2056                                             L"",
2057                                             &default_type,
2058                                             (gpointer *) &default_value,
2059                                             NULL,
2060                                             NULL))
2061         {
2062           if (default_type != G_WIN32_REGISTRY_VALUE_STR ||
2063               default_value[0] == L'\0')
2064             g_clear_pointer (&default_value, g_free);
2065         }
2066
2067       if (!g_win32_registry_subkey_iter_init (&subkey_iter,
2068                                               system_client_type,
2069                                               NULL))
2070         {
2071           g_clear_pointer (&default_value, g_free);
2072           g_object_unref (system_client_type);
2073           continue;
2074         }
2075
2076       while (g_win32_registry_subkey_iter_next (&subkey_iter, TRUE, NULL))
2077         {
2078           GWin32RegistryKey *system_client;
2079           GWin32RegistryKey *system_client_assoc;
2080           gboolean add;
2081           gunichar2 *keyname;
2082
2083           if (!g_win32_registry_subkey_iter_get_name_w (&subkey_iter,
2084                                                         &client_name,
2085                                                         &client_name_len,
2086                                                         NULL))
2087             continue;
2088
2089           system_client = g_win32_registry_key_get_child_w (system_client_type,
2090                                                             client_name,
2091                                                             NULL);
2092
2093           if (system_client == NULL)
2094             continue;
2095
2096           add = FALSE;
2097
2098           system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2099                                                                   L"Capabilities\\FileAssociations",
2100                                                                   NULL);
2101
2102           if (system_client_assoc != NULL)
2103             {
2104               add = TRUE;
2105               g_object_unref (system_client_assoc);
2106             }
2107           else
2108             {
2109               system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2110                                                                       L"Capabilities\\UrlAssociations",
2111                                                                       NULL);
2112
2113               if (system_client_assoc != NULL)
2114                 {
2115                   add = TRUE;
2116                   g_object_unref (system_client_assoc);
2117                 }
2118             }
2119
2120           if (add)
2121             {
2122               keyname = g_wcsdup (g_win32_registry_key_get_path_w (system_client), -1);
2123
2124               if (default_value && wcscmp (default_value, client_name) == 0)
2125                 g_ptr_array_add (priority_capable_apps, keyname);
2126               else
2127                 g_ptr_array_add (capable_apps, keyname);
2128             }
2129
2130           g_object_unref (system_client);
2131         }
2132
2133       g_win32_registry_subkey_iter_clear (&subkey_iter);
2134       g_clear_pointer (&default_value, g_free);
2135       g_object_unref (system_client_type);
2136     }
2137
2138   g_win32_registry_subkey_iter_clear (&clients_iter);
2139   g_object_unref (clients);
2140 }
2141
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.
2146  */
2147 static void
2148 collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
2149                                            gboolean   user_registry)
2150 {
2151   GWin32RegistryValueIter iter;
2152   const gunichar2 *reg_path;
2153
2154   gunichar2 *value_data;
2155   gsize      value_data_size;
2156   GWin32RegistryValueType value_type;
2157   GWin32RegistryKey *registered_apps;
2158
2159   if (user_registry)
2160     reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications";
2161   else
2162     reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications";
2163
2164   registered_apps =
2165       g_win32_registry_key_new_w (reg_path, NULL);
2166
2167   if (!registered_apps)
2168     return;
2169
2170   if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL))
2171     {
2172       g_object_unref (registered_apps);
2173
2174       return;
2175     }
2176
2177   while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2178     {
2179       gunichar2 possible_location[REG_PATH_MAX_SIZE + 1];
2180       GWin32RegistryKey *location;
2181       gunichar2 *p;
2182
2183       if ((!g_win32_registry_value_iter_get_value_type (&iter,
2184                                                         &value_type,
2185                                                         NULL)) ||
2186           (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2187           (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2188                                                     (void **) &value_data,
2189                                                     &value_data_size,
2190                                                     NULL)) ||
2191           (value_data_size < sizeof (gunichar2)) ||
2192           (value_data[0] == L'\0'))
2193         continue;
2194
2195       if (!build_registry_path (possible_location, sizeof (possible_location),
2196                                 user_registry ? HKCU : HKLM, value_data, NULL))
2197         continue;
2198
2199       location = g_win32_registry_key_new_w (possible_location, NULL);
2200
2201       if (location == NULL)
2202         continue;
2203
2204       p = wcsrchr (possible_location, L'\\');
2205
2206       if (p)
2207         {
2208           *p = L'\0';
2209           g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
2210         }
2211
2212       g_object_unref (location);
2213     }
2214
2215   g_win32_registry_value_iter_clear (&iter);
2216   g_object_unref (registered_apps);
2217 }
2218
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.
2224  */
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,
2232                 gboolean         is_uwp)
2233 {
2234   GWin32AppInfoApplication *app;
2235
2236   app = g_hash_table_lookup (app_hashmap, canonical_name_folded);
2237
2238   if (app != NULL)
2239     return app;
2240
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),
2251                        app);
2252
2253   return app;
2254 }
2255
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
2260  */
2261 static void
2262 read_capable_app (const gunichar2 *app_key_path,
2263                   gboolean         user_specific,
2264                   gboolean         default_app)
2265 {
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;
2274   gboolean success;
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;
2285
2286   appkey = NULL;
2287   capabilities = NULL;
2288
2289   if (!g_utf16_to_utf8_and_fold (app_key_path,
2290                                  -1,
2291                                  &canonical_name_u8,
2292                                  &canonical_name_folded) ||
2293       !g_utf16_to_utf8_and_fold (app_key_path,
2294                                  -1,
2295                                  &app_key_path_u8,
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)))
2302     {
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);
2309
2310       return;
2311     }
2312
2313   app = get_app_object (apps_by_id,
2314                         app_key_path,
2315                         canonical_name_u8,
2316                         canonical_name_folded,
2317                         user_specific,
2318                         default_app,
2319                         FALSE);
2320
2321   process_verbs_commands (g_steal_pointer (&verbs),
2322                           preferred_verb,
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),
2325                           FALSE,
2326                           app_add_verb,
2327                           app,
2328                           app);
2329
2330   fallback_friendly_name = NULL;
2331   success = g_win32_registry_key_get_value_w (appkey,
2332                                               NULL,
2333                                               TRUE,
2334                                               L"",
2335                                               &vtype,
2336                                               (void **) &fallback_friendly_name,
2337                                               NULL,
2338                                               NULL);
2339
2340   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2341     g_clear_pointer (&fallback_friendly_name, g_free);
2342
2343   if (fallback_friendly_name &&
2344       app->pretty_name == NULL)
2345     {
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,
2349                                              -1,
2350                                              NULL,
2351                                              NULL,
2352                                              NULL);
2353     }
2354
2355   friendly_name = NULL;
2356   success = g_win32_registry_key_get_value_w (capabilities,
2357                                               g_win32_registry_get_os_dirs_w (),
2358                                               TRUE,
2359                                               L"LocalizedString",
2360                                               &vtype,
2361                                               (void **) &friendly_name,
2362                                               NULL,
2363                                               NULL);
2364
2365   if (success &&
2366       vtype != G_WIN32_REGISTRY_VALUE_STR)
2367     g_clear_pointer (&friendly_name, g_free);
2368
2369   if (friendly_name &&
2370       app->localized_pretty_name == NULL)
2371     {
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,
2375                                                        -1,
2376                                                        NULL,
2377                                                        NULL,
2378                                                        NULL);
2379     }
2380
2381   description = NULL;
2382   success = g_win32_registry_key_get_value_w (capabilities,
2383                                               g_win32_registry_get_os_dirs_w (),
2384                                               TRUE,
2385                                               L"ApplicationDescription",
2386                                               &vtype,
2387                                               (void **) &description,
2388                                               NULL,
2389                                               NULL);
2390
2391   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2392     g_clear_pointer (&description, g_free);
2393
2394   if (description && app->description == NULL)
2395     {
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);
2399     }
2400
2401   default_icon_key = g_win32_registry_key_get_child_w (appkey,
2402                                                        L"DefaultIcon",
2403                                                        NULL);
2404
2405   icon_source = NULL;
2406
2407   if (default_icon_key != NULL)
2408     {
2409       success = g_win32_registry_key_get_value_w (default_icon_key,
2410                                                   NULL,
2411                                                   TRUE,
2412                                                   L"",
2413                                                   &vtype,
2414                                                   (void **) &icon_source,
2415                                                   NULL,
2416                                                   NULL);
2417
2418       if (success &&
2419           vtype != G_WIN32_REGISTRY_VALUE_STR)
2420         g_clear_pointer (&icon_source, g_free);
2421
2422       g_object_unref (default_icon_key);
2423     }
2424
2425   if (icon_source == NULL)
2426     {
2427       success = g_win32_registry_key_get_value_w (capabilities,
2428                                                   NULL,
2429                                                   TRUE,
2430                                                   L"ApplicationIcon",
2431                                                   &vtype,
2432                                                   (void **) &icon_source,
2433                                                   NULL,
2434                                                   NULL);
2435
2436       if (success &&
2437           vtype != G_WIN32_REGISTRY_VALUE_STR)
2438         g_clear_pointer (&icon_source, g_free);
2439     }
2440
2441   if (icon_source &&
2442       app->icon == NULL)
2443     {
2444       gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2445       app->icon = g_themed_icon_new (name);
2446       g_free (name);
2447     }
2448
2449   narrow_application_name = NULL;
2450   success = g_win32_registry_key_get_value_w (capabilities,
2451                                               g_win32_registry_get_os_dirs_w (),
2452                                               TRUE,
2453                                               L"ApplicationName",
2454                                               &vtype,
2455                                               (void **) &narrow_application_name,
2456                                               NULL,
2457                                               NULL);
2458
2459   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2460     g_clear_pointer (&narrow_application_name, g_free);
2461
2462   if (narrow_application_name &&
2463       app->localized_pretty_name == NULL)
2464     {
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,
2468                                                        -1,
2469                                                        NULL,
2470                                                        NULL,
2471                                                        NULL);
2472     }
2473
2474   associations = g_win32_registry_key_get_child_w (capabilities,
2475                                                    L"FileAssociations",
2476                                                    NULL);
2477
2478   if (associations != NULL)
2479     {
2480       GWin32RegistryValueIter iter;
2481
2482       if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2483         {
2484           gunichar2 *file_extension;
2485           gunichar2 *extension_handler;
2486           gsize      file_extension_len;
2487           gsize      extension_handler_size;
2488           GWin32RegistryValueType value_type;
2489
2490           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2491             {
2492               if ((!g_win32_registry_value_iter_get_value_type (&iter,
2493                                                                 &value_type,
2494                                                                 NULL)) ||
2495                   (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2496                   (!g_win32_registry_value_iter_get_name_w (&iter,
2497                                                             &file_extension,
2498                                                             &file_extension_len,
2499                                                             NULL)) ||
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,
2505                                                             NULL)) ||
2506                   (extension_handler_size < sizeof (gunichar2)) ||
2507                   (extension_handler[0] == L'\0'))
2508                 continue;
2509
2510               get_file_ext (extension_handler, file_extension, app, FALSE);
2511             }
2512
2513           g_win32_registry_value_iter_clear (&iter);
2514         }
2515
2516       g_object_unref (associations);
2517     }
2518
2519   associations = g_win32_registry_key_get_child_w (capabilities, L"URLAssociations", NULL);
2520
2521   if (associations != NULL)
2522     {
2523       GWin32RegistryValueIter iter;
2524
2525       if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2526         {
2527           gunichar2 *url_schema;
2528           gunichar2 *schema_handler;
2529           gsize      url_schema_len;
2530           gsize      schema_handler_size;
2531           GWin32RegistryValueType value_type;
2532
2533           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2534             {
2535               gchar *schema_u8 = NULL;
2536               gchar *schema_u8_folded = NULL;
2537
2538               if ((!g_win32_registry_value_iter_get_value_type (&iter,
2539                                                                 &value_type,
2540                                                                 NULL)) ||
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,
2544                                                             &url_schema,
2545                                                             &url_schema_len,
2546                                                             NULL)) ||
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,
2552                                                             NULL)) ||
2553                   (schema_handler_size < sizeof (gunichar2)) ||
2554                   (schema_handler[0] == L'\0'))
2555                 continue;
2556
2557
2558
2559               if (g_utf16_to_utf8_and_fold (url_schema,
2560                                             url_schema_len,
2561                                             &schema_u8,
2562                                             &schema_u8_folded))
2563                 get_url_association (schema_handler, url_schema, schema_u8, schema_u8_folded, app, FALSE);
2564
2565               g_clear_pointer (&schema_u8, g_free);
2566               g_clear_pointer (&schema_u8_folded, g_free);
2567             }
2568
2569           g_win32_registry_value_iter_clear (&iter);
2570         }
2571
2572       g_object_unref (associations);
2573     }
2574
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);
2579
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);
2586 }
2587
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.
2590  */
2591 static void
2592 read_urls (GWin32RegistryKey *url_associations)
2593 {
2594   GWin32RegistrySubkeyIter url_iter;
2595
2596   if (url_associations == NULL)
2597     return;
2598
2599   if (!g_win32_registry_subkey_iter_init (&url_iter, url_associations, NULL))
2600     return;
2601
2602   while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL))
2603     {
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;
2611
2612       if (g_win32_registry_subkey_iter_get_name_w (&url_iter,
2613                                                    &url_schema,
2614                                                    &url_schema_len,
2615                                                    NULL) &&
2616           g_utf16_to_utf8_and_fold (url_schema,
2617                                     url_schema_len,
2618                                     &schema_u8,
2619                                     &schema_u8_folded) &&
2620           (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS,
2621                                                                 url_schema, USER_CHOICE,
2622                                                                 NULL)) != NULL &&
2623           g_win32_registry_key_get_value_w (user_choice,
2624                                             NULL,
2625                                             TRUE,
2626                                             L"Progid",
2627                                             &val_type,
2628                                             (void **) &program_id,
2629                                             NULL,
2630                                             NULL) &&
2631           val_type == G_WIN32_REGISTRY_VALUE_STR)
2632         get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE);
2633
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);
2638     }
2639
2640   g_win32_registry_subkey_iter_clear (&url_iter);
2641 }
2642
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.
2647  */
2648 static void
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)
2653 {
2654   GWin32RegistryValueIter sup_iter;
2655   GWin32AppInfoApplication *app;
2656   GList *verbs;
2657   const reg_verb *preferred_verb;
2658   gunichar2 *friendly_app_name;
2659   gboolean success;
2660   GWin32RegistryValueType vtype;
2661   gboolean no_open_with;
2662   GWin32RegistryKey *default_icon_key;
2663   gunichar2 *icon_source;
2664   GIcon *icon = NULL;
2665   GWin32RegistryKey *supported_key;
2666
2667   if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell", NULL))
2668     return;
2669
2670   app = get_app_object (apps_by_exe,
2671                         app_exe_basename,
2672                         app_exe_basename_u8,
2673                         app_exe_basename_u8_folded,
2674                         FALSE,
2675                         FALSE,
2676                         FALSE);
2677
2678   process_verbs_commands (g_steal_pointer (&verbs),
2679                           preferred_verb,
2680                           L"HKEY_CLASSES_ROOT\\Applications\\",
2681                           app_exe_basename,
2682                           TRUE,
2683                           app_add_verb,
2684                           app,
2685                           app);
2686
2687   friendly_app_name = NULL;
2688   success = g_win32_registry_key_get_value_w (incapable_app,
2689                                               g_win32_registry_get_os_dirs_w (),
2690                                               TRUE,
2691                                               L"FriendlyAppName",
2692                                               &vtype,
2693                                               (void **) &friendly_app_name,
2694                                               NULL,
2695                                               NULL);
2696
2697   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2698     g_clear_pointer (&friendly_app_name, g_free);
2699
2700   no_open_with = g_win32_registry_key_get_value_w (incapable_app,
2701                                                    NULL,
2702                                                    TRUE,
2703                                                    L"NoOpenWith",
2704                                                    &vtype,
2705                                                    NULL,
2706                                                    NULL,
2707                                                    NULL);
2708
2709   default_icon_key =
2710       g_win32_registry_key_get_child_w (incapable_app,
2711                                         L"DefaultIcon",
2712                                         NULL);
2713
2714   icon_source = NULL;
2715
2716   if (default_icon_key != NULL)
2717     {
2718       success =
2719           g_win32_registry_key_get_value_w (default_icon_key,
2720                                             NULL,
2721                                             TRUE,
2722                                             L"",
2723                                             &vtype,
2724                                             (void **) &icon_source,
2725                                             NULL,
2726                                             NULL);
2727
2728       if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2729         g_clear_pointer (&icon_source, g_free);
2730
2731       g_object_unref (default_icon_key);
2732     }
2733
2734   if (icon_source)
2735     {
2736       gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2737       if (name != NULL)
2738         icon = g_themed_icon_new (name);
2739       g_free (name);
2740     }
2741
2742   app->no_open_with = no_open_with;
2743
2744   if (friendly_app_name &&
2745       app->localized_pretty_name == NULL)
2746     {
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);
2751     }
2752
2753   if (icon && app->icon == NULL)
2754     app->icon = g_object_ref (icon);
2755
2756   supported_key =
2757       g_win32_registry_key_get_child_w (incapable_app,
2758                                         L"SupportedTypes",
2759                                         NULL);
2760
2761   if (supported_key &&
2762       g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL))
2763     {
2764       gunichar2 *ext_name;
2765       gsize      ext_name_len;
2766
2767       while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
2768         {
2769           if ((!g_win32_registry_value_iter_get_name_w (&sup_iter,
2770                                                         &ext_name,
2771                                                         &ext_name_len,
2772                                                         NULL)) ||
2773               (ext_name_len <= 0) ||
2774               (ext_name[0] != L'.'))
2775             continue;
2776
2777           get_file_ext (ext_name, ext_name, app, FALSE);
2778         }
2779
2780       g_win32_registry_value_iter_clear (&sup_iter);
2781     }
2782
2783   g_clear_object (&supported_key);
2784   g_free (friendly_app_name);
2785   g_free (icon_source);
2786
2787   g_clear_object (&icon);
2788 }
2789
2790 /* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications
2791  * and calls read_incapable_app() for each one.
2792  */
2793 static void
2794 read_exeapps (void)
2795 {
2796   GWin32RegistryKey *local_applications_key;
2797   GWin32RegistrySubkeyIter app_iter;
2798
2799   local_applications_key =
2800       g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);
2801
2802   if (local_applications_key == NULL)
2803     return;
2804
2805   if (!g_win32_registry_subkey_iter_init (&app_iter, local_applications_key, NULL))
2806     {
2807       g_object_unref (local_applications_key);
2808       return;
2809     }
2810
2811   while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
2812     {
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;
2818
2819       if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
2820                                                     &app_exe_basename,
2821                                                     &app_exe_basename_len,
2822                                                     NULL) ||
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))
2827         continue;
2828
2829       incapable_app =
2830           g_win32_registry_key_get_child_w (local_applications_key,
2831                                             app_exe_basename,
2832                                             NULL);
2833
2834       if (incapable_app != NULL)
2835         read_incapable_app (incapable_app,
2836                             app_exe_basename,
2837                             app_exe_basename_u8,
2838                             app_exe_basename_u8_folded);
2839
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);
2843     }
2844
2845   g_win32_registry_subkey_iter_clear (&app_iter);
2846   g_object_unref (local_applications_key);
2847 }
2848
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)
2852  */
2853 static void
2854 read_exts (GWin32RegistryKey *file_exts)
2855 {
2856   GWin32RegistrySubkeyIter ext_iter;
2857   const gunichar2 *file_extension;
2858   gsize file_extension_len;
2859
2860   if (file_exts == NULL)
2861     return;
2862
2863   if (!g_win32_registry_subkey_iter_init (&ext_iter, file_exts, NULL))
2864     return;
2865
2866   while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL))
2867     {
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;
2875
2876       if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter,
2877                                                     &file_extension,
2878                                                     &file_extension_len,
2879                                                     NULL))
2880         continue;
2881
2882       program_id = NULL;
2883       user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, file_extension,
2884                                                            USER_CHOICE, NULL);
2885       if (user_choice &&
2886           g_win32_registry_key_get_value_w (user_choice,
2887                                             NULL,
2888                                             TRUE,
2889                                             L"Progid",
2890                                             &value_type,
2891                                             (void **) &program_id,
2892                                             NULL,
2893                                             NULL) &&
2894           value_type == G_WIN32_REGISTRY_VALUE_STR)
2895         {
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.
2902            */
2903           get_file_ext (program_id, file_extension, NULL, TRUE);
2904         }
2905
2906       g_clear_object (&user_choice);
2907       g_clear_pointer (&program_id, g_free);
2908
2909       open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS,
2910                                                                  file_extension,
2911                                                                  OPEN_WITH_PROGIDS,
2912                                                                  NULL);
2913
2914       if (open_with_progids == NULL)
2915         continue;
2916
2917       if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2918         {
2919           g_clear_object (&open_with_progids);
2920           continue;
2921         }
2922
2923       while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2924         {
2925           if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
2926                                                        &value_name_len,
2927                                                        NULL) ||
2928               (value_name_len == 0))
2929             continue;
2930
2931           get_file_ext (value_name, file_extension, NULL, FALSE);
2932         }
2933
2934       g_win32_registry_value_iter_clear (&iter);
2935       g_clear_object (&open_with_progids);
2936     }
2937
2938   g_win32_registry_subkey_iter_clear (&ext_iter);
2939 }
2940
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.
2945  */
2946 static void
2947 read_classes (GWin32RegistryKey *classes_root)
2948 {
2949   GWin32RegistrySubkeyIter class_iter;
2950   const gunichar2 *class_name;
2951   gsize class_name_len;
2952
2953   if (classes_root == NULL)
2954     return;
2955
2956   if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL))
2957     return;
2958
2959   while (g_win32_registry_subkey_iter_next (&class_iter, TRUE, NULL))
2960     {
2961       if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter,
2962                                                      &class_name,
2963                                                      &class_name_len,
2964                                                      NULL)) ||
2965           (class_name_len <= 1))
2966         continue;
2967
2968       if (class_name[0] == L'.')
2969         {
2970           GWin32RegistryKey *class_key;
2971           GWin32RegistryValueIter iter;
2972           GWin32RegistryKey *open_with_progids;
2973           gunichar2 *value_name;
2974           gsize      value_name_len;
2975
2976           /* Read the data from the HKCR\\.ext (usually proxied
2977            * to another HKCR subkey)
2978            */
2979           get_file_ext (class_name, class_name, NULL, FALSE);
2980
2981           class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
2982
2983           if (class_key == NULL)
2984             continue;
2985
2986           open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL);
2987           g_clear_object (&class_key);
2988
2989           if (open_with_progids == NULL)
2990             continue;
2991
2992           if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2993             {
2994               g_clear_object (&open_with_progids);
2995               continue;
2996             }
2997
2998           /* Read the data for other handlers for this extension */
2999           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
3000             {
3001               if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
3002                                                            &value_name_len,
3003                                                            NULL) ||
3004                   (value_name_len == 0))
3005                 continue;
3006
3007               get_file_ext (value_name, class_name, NULL, FALSE);
3008             }
3009
3010           g_win32_registry_value_iter_clear (&iter);
3011           g_clear_object (&open_with_progids);
3012         }
3013       else
3014         {
3015           gsize i;
3016           GWin32RegistryKey *class_key;
3017           gboolean success;
3018           GWin32RegistryValueType vtype;
3019           gchar *schema_u8;
3020           gchar *schema_u8_folded;
3021
3022           for (i = 0; i < class_name_len; i++)
3023             if (!iswalpha (class_name[i]))
3024               break;
3025
3026           if (i != class_name_len)
3027             continue;
3028
3029           class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
3030
3031           if (class_key == NULL)
3032             continue;
3033
3034           success = g_win32_registry_key_get_value_w (class_key,
3035                                                       NULL,
3036                                                       TRUE,
3037                                                       L"URL Protocol",
3038                                                       &vtype,
3039                                                       NULL,
3040                                                       NULL,
3041                                                       NULL);
3042           g_clear_object (&class_key);
3043
3044           if (!success ||
3045               vtype != G_WIN32_REGISTRY_VALUE_STR)
3046             continue;
3047
3048           if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded))
3049             continue;
3050
3051           get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE);
3052
3053           g_clear_pointer (&schema_u8, g_free);
3054           g_clear_pointer (&schema_u8_folded, g_free);
3055         }
3056     }
3057
3058   g_win32_registry_subkey_iter_clear (&class_iter);
3059 }
3060
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.
3064  */
3065 static void
3066 link_handlers_to_unregistered_apps (void)
3067 {
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;
3075
3076   g_hash_table_iter_init (&iter, handlers);
3077   while (g_hash_table_iter_next (&iter,
3078                                  (gpointer *) &handler_id_fld,
3079                                  (gpointer *) &handler))
3080     {
3081       gsize vi;
3082
3083       if (handler->uwp_aumid != NULL)
3084         continue;
3085
3086       for (vi = 0; vi < handler->verbs->len; vi++)
3087         {
3088           GWin32AppInfoShellVerb *handler_verb;
3089           const gchar *handler_exe_basename;
3090           enum
3091             {
3092               SH_UNKNOWN,
3093               GOT_SH_INFO,
3094               ERROR_GETTING_SH_INFO,
3095             } have_stat_handler = SH_UNKNOWN;
3096           GWin32PrivateStat handler_verb_exec_info = { 0 };
3097
3098           handler_verb = _verb_idx (handler->verbs, vi);
3099
3100           if (handler_verb->app != NULL)
3101             continue;
3102
3103           if (handler_verb->executable_folded == NULL)
3104             continue;
3105
3106           handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1);
3107           g_hash_table_iter_init (&app_iter, apps_by_id);
3108
3109           while (g_hash_table_iter_next (&app_iter,
3110                                          (gpointer *) &canonical_name_fld,
3111                                          (gpointer *) &app))
3112             {
3113               GWin32AppInfoShellVerb *app_verb;
3114               gsize ai;
3115
3116               if (app->is_uwp)
3117                 continue;
3118
3119               for (ai = 0; ai < app->verbs->len; ai++)
3120                 {
3121                   GWin32PrivateStat app_verb_exec_info;
3122                   const gchar *app_exe_basename;
3123                   app_verb = _verb_idx (app->verbs, ai);
3124
3125                   if (app_verb->executable_folded == NULL)
3126                     continue;
3127
3128                   app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1);
3129
3130                   /* First check that the executable paths are identical */
3131                   if (g_strcmp0 (app_verb->executable_folded, handler_verb->executable_folded) != 0)
3132                     {
3133                       /* If not, check the basenames. If they are different, don't bother
3134                        * with further checks.
3135                        */
3136                       if (g_strcmp0 (app_exe_basename, handler_exe_basename) != 0)
3137                         continue;
3138
3139                       /* Get filesystem IDs for both files.
3140                        * For the handler that is attempted only once.
3141                        */
3142                       if (have_stat_handler == SH_UNKNOWN)
3143                         {
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;
3147                           else
3148                             have_stat_handler = ERROR_GETTING_SH_INFO;
3149                         }
3150
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)
3155                         continue;
3156                     }
3157
3158                   handler_verb->app = g_object_ref (app);
3159                   break;
3160                 }
3161             }
3162
3163           if (handler_verb->app != NULL)
3164             continue;
3165
3166           g_hash_table_iter_init (&app_iter, apps_by_exe);
3167
3168           while (g_hash_table_iter_next (&app_iter,
3169                                          (gpointer *) &appexe_fld_basename,
3170                                          (gpointer *) &app))
3171             {
3172               if (app->is_uwp)
3173                 continue;
3174
3175               /* Use basename because apps_by_exe only has basenames */
3176               if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0)
3177                 continue;
3178
3179               handler_verb->app = g_object_ref (app);
3180               break;
3181             }
3182         }
3183     }
3184 }
3185
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).
3196  */
3197 static void
3198 link_handlers_to_fake_apps (void)
3199 {
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;
3208
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))
3213     {
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))
3218         {
3219           gsize vi;
3220
3221           if (handler->uwp_aumid != NULL)
3222             continue;
3223
3224           for (vi = 0; vi < handler->verbs->len; vi++)
3225             {
3226               GWin32AppInfoShellVerb *handler_verb;
3227               GWin32AppInfoApplication *app;
3228               gunichar2 *exename_utf16;
3229               handler_verb = _verb_idx (handler->verbs, vi);
3230
3231               if (handler_verb->app != NULL)
3232                 continue;
3233
3234               exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL);
3235               if (exename_utf16 == NULL)
3236                 continue;
3237
3238               app = get_app_object (fake_apps,
3239                                     exename_utf16,
3240                                     handler_verb->executable,
3241                                     handler_verb->executable_folded,
3242                                     FALSE,
3243                                     FALSE,
3244                                     FALSE);
3245               g_clear_pointer (&exename_utf16, g_free);
3246               handler_verb->app = g_object_ref (app);
3247
3248               app_add_verb (app,
3249                             app,
3250                             handler_verb->verb_name,
3251                             handler_verb->command,
3252                             handler_verb->command_utf8,
3253                             handler_verb->verb_displayname,
3254                             TRUE,
3255                             TRUE);
3256               g_hash_table_insert (app->supported_exts,
3257                                    g_strdup (extension_utf8_folded),
3258                                    g_object_ref (handler));
3259             }
3260         }
3261     }
3262
3263   g_hash_table_iter_init (&iter, urls);
3264   while (g_hash_table_iter_next (&iter,
3265                                  (gpointer *) &url_utf8_folded,
3266                                  (gpointer *) &schema))
3267     {
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))
3272         {
3273           gsize vi;
3274
3275           if (handler->uwp_aumid != NULL)
3276             continue;
3277
3278           for (vi = 0; vi < handler->verbs->len; vi++)
3279             {
3280               GWin32AppInfoShellVerb *handler_verb;
3281               GWin32AppInfoApplication *app;
3282               gchar *command_utf8_folded;
3283               handler_verb = _verb_idx (handler->verbs, vi);
3284
3285               if (handler_verb->app != NULL)
3286                 continue;
3287
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,
3293                                     FALSE,
3294                                     FALSE,
3295                                     FALSE);
3296               g_clear_pointer (&command_utf8_folded, g_free);
3297               handler_verb->app = g_object_ref (app);
3298
3299               app_add_verb (app,
3300                             app,
3301                             handler_verb->verb_name,
3302                             handler_verb->command,
3303                             handler_verb->command_utf8,
3304                             handler_verb->verb_displayname,
3305                             TRUE,
3306                             TRUE);
3307               g_hash_table_insert (app->supported_urls,
3308                                    g_strdup (url_utf8_folded),
3309                                    g_object_ref (handler));
3310             }
3311         }
3312     }
3313 }
3314
3315 static GWin32AppInfoHandler *
3316 find_uwp_handler_for_ext (GWin32AppInfoFileExtension *file_extn,
3317                           const gunichar2            *app_user_model_id)
3318 {
3319   GHashTableIter handler_iter;
3320   gchar *handler_id_fld;
3321   GWin32AppInfoHandler *handler;
3322
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))
3327     {
3328       if (handler->uwp_aumid == NULL)
3329         continue;
3330
3331       if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3332         return handler;
3333     }
3334
3335   return NULL;
3336 }
3337
3338 static GWin32AppInfoHandler *
3339 find_uwp_handler_for_schema (GWin32AppInfoURLSchema *schema,
3340                              const gunichar2        *app_user_model_id)
3341 {
3342   GHashTableIter handler_iter;
3343   gchar *handler_id_fld;
3344   GWin32AppInfoHandler *handler;
3345
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))
3350     {
3351       if (handler->uwp_aumid == NULL)
3352         continue;
3353
3354       if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3355         return handler;
3356     }
3357
3358   return NULL;
3359 }
3360
3361 static gboolean
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)
3370 {
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;
3379
3380   if (!g_utf16_to_utf8_and_fold (app_user_model_id,
3381                                  -1,
3382                                  &app_user_model_id_u8,
3383                                  &app_user_model_id_u8_folded))
3384     return TRUE;
3385
3386   app = get_app_object (apps_by_id,
3387                         app_user_model_id,
3388                         app_user_model_id_u8,
3389                         app_user_model_id_u8_folded,
3390                         TRUE,
3391                         FALSE,
3392                         TRUE);
3393
3394   if (!app->pretty_name && !app->pretty_name_u8 && display_name)
3395     {
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);
3399     }
3400
3401   extensions_considered = 0;
3402
3403   for (i = 0; i < supported_extgroups->len; i++)
3404     {
3405       GWin32PackageExtGroup *grp = (GWin32PackageExtGroup *) g_ptr_array_index (supported_extgroups, i);
3406
3407       extensions_considered += grp->extensions->len;
3408
3409       for (i_ext = 0; i_ext < grp->extensions->len; i_ext++)
3410         {
3411           wchar_t *ext = (wchar_t *) g_ptr_array_index (grp->extensions, i_ext);
3412           gchar *ext_u8;
3413           gchar *ext_u8_folded;
3414           GWin32AppInfoFileExtension *file_extn;
3415           GWin32AppInfoHandler *handler_rec;
3416
3417           if (!g_utf16_to_utf8_and_fold (ext,
3418                                          -1,
3419                                          &ext_u8,
3420                                          &ext_u8_folded))
3421             continue;
3422
3423           file_extn = get_ext_object (ext, ext_u8, ext_u8_folded);
3424           g_free (ext_u8);
3425           handler_rec = find_uwp_handler_for_ext (file_extn, app_user_model_id);
3426
3427           if (handler_rec == NULL)
3428             {
3429               /* Use AppUserModelId as the ID of the new fake handler */
3430               handler_rec = get_handler_object (app_user_model_id_u8_folded,
3431                                                 NULL,
3432                                                 app_user_model_id,
3433                                                 app_user_model_id);
3434               g_hash_table_insert (file_extn->handlers,
3435                                    g_strdup (app_user_model_id_u8_folded),
3436                                    g_object_ref (handler_rec));
3437             }
3438
3439           if (file_extn->chosen_handler == NULL)
3440             g_set_object (&file_extn->chosen_handler, handler_rec);
3441
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.
3449            */
3450           for (i_verb = 0; i_verb < grp->verbs->len; i_verb++)
3451             {
3452               wchar_t *verb = NULL;
3453
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.
3457                */
3458               uwp_handler_add_verb (handler_rec,
3459                                     app,
3460                                     verb,
3461                                     NULL,
3462                                     FALSE);
3463             }
3464
3465           g_hash_table_insert (app->supported_exts,
3466                                g_steal_pointer (&ext_u8_folded),
3467                                g_object_ref (handler_rec));
3468         }
3469     }
3470
3471   g_hash_table_iter_init (&iter, app->supported_exts);
3472
3473   /* Pile up all handler verbs into the app too,
3474    * for cases when we don't have a ref to a handler.
3475    */
3476   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &ext_handler))
3477     {
3478       guint i_hverb;
3479
3480       if (!ext_handler)
3481         continue;
3482
3483       for (i_hverb = 0; i_hverb < ext_handler->verbs->len; i_hverb++)
3484         {
3485           GWin32AppInfoShellVerb *handler_verb;
3486
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);
3491         }
3492     }
3493
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);
3497
3498   for (i = 0; i < supported_protocols->len; i++)
3499     {
3500       wchar_t *proto = (wchar_t *) g_ptr_array_index (supported_protocols, i);
3501       gchar *proto_u8;
3502       gchar *proto_u8_folded;
3503       GWin32AppInfoURLSchema *schema_rec;
3504       GWin32AppInfoHandler *handler_rec;
3505
3506       if (!g_utf16_to_utf8_and_fold (proto,
3507                                      -1,
3508                                      &proto_u8,
3509                                      &proto_u8_folded))
3510         continue;
3511
3512       schema_rec = get_schema_object (proto,
3513                                       proto_u8,
3514                                       proto_u8_folded);
3515
3516       g_free (proto_u8);
3517
3518       handler_rec = find_uwp_handler_for_schema (schema_rec, app_user_model_id);
3519
3520       if (handler_rec == NULL)
3521         {
3522           /* Use AppUserModelId as the ID of the new fake handler */
3523           handler_rec = get_handler_object (app_user_model_id_u8_folded,
3524                                             NULL,
3525                                             app_user_model_id,
3526                                             app_user_model_id);
3527
3528           g_hash_table_insert (schema_rec->handlers,
3529                                g_strdup (app_user_model_id_u8_folded),
3530                                g_object_ref (handler_rec));
3531         }
3532
3533       if (schema_rec->chosen_handler == NULL)
3534         g_set_object (&schema_rec->chosen_handler, handler_rec);
3535
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".
3540        */
3541       uwp_handler_add_verb (handler_rec,
3542                             app,
3543                             L"open",
3544                             NULL,
3545                             TRUE);
3546
3547       g_hash_table_insert (app->supported_urls,
3548                            g_steal_pointer (&proto_u8_folded),
3549                            g_object_ref (handler_rec));
3550     }
3551
3552   g_hash_table_iter_init (&iter, app->supported_urls);
3553
3554   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &url))
3555     {
3556       guint i_hverb;
3557
3558       if (!url)
3559         continue;
3560
3561       for (i_hverb = 0; i_hverb < url->verbs->len; i_hverb++)
3562         {
3563           GWin32AppInfoShellVerb *handler_verb;
3564
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);
3569         }
3570     }
3571
3572   g_free (app_user_model_id_u8);
3573   g_free (app_user_model_id_u8_folded);
3574
3575   return TRUE;
3576 }
3577
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).
3584  */
3585 static gunichar2 *
3586 resolve_string (gunichar2 *at_string)
3587 {
3588   HRESULT hr;
3589   gunichar2 *result = NULL;
3590   gsize result_size;
3591   /* This value is arbitrary */
3592   const gsize reasonable_size_limit = 8192;
3593
3594   if (at_string == NULL || at_string[0] != L'@')
3595     return at_string;
3596
3597   /* In case of a no-op @at_string will be copied into the output,
3598    * buffer so allocate at least that much.
3599    */
3600   result_size = wcslen (at_string) + 1;
3601
3602   while (TRUE)
3603     {
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.
3609        */
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))
3614         {
3615           g_free (result);
3616           g_free (at_string);
3617           return NULL;
3618         }
3619       else if (result[result_size - 1] != 0 ||
3620                result_size >= reasonable_size_limit)
3621         {
3622           /* Now that the length is known, allocate the exact amount */
3623           gunichar2 *copy = g_wcsdup (result, -1);
3624           g_free (result);
3625           g_free (at_string);
3626           return copy;
3627         }
3628
3629       result_size *= 2;
3630     }
3631
3632   g_assert_not_reached ();
3633
3634   return at_string;
3635 }
3636
3637 static void
3638 grab_registry_string (GWin32RegistryKey  *handler_appkey,
3639                       const gunichar2    *value_name,
3640                       gunichar2         **destination,
3641                       gchar             **destination_u8)
3642 {
3643   gunichar2 *value;
3644   gsize value_size;
3645   GWin32RegistryValueType vtype;
3646   const gunichar2 *ms_resource_prefix = L"ms-resource:";
3647   gsize ms_resource_prefix_len = wcslen (ms_resource_prefix);
3648
3649   /* Right now this function is not used without destination,
3650    * enforce this. destination_u8 is optional.
3651    */
3652   g_assert (destination != NULL);
3653
3654   if (*destination != NULL)
3655     return;
3656
3657   value = NULL;
3658   if (g_win32_registry_key_get_value_w (handler_appkey,
3659                                         NULL,
3660                                         TRUE,
3661                                         value_name,
3662                                         &vtype,
3663                                         (void **) &value,
3664                                         &value_size,
3665                                         NULL) &&
3666       vtype != G_WIN32_REGISTRY_VALUE_STR)
3667     g_clear_pointer (&value, g_free);
3668
3669   /* There's no way for us to resolve "ms-resource:..." strings */
3670   if (value != NULL &&
3671       value_size >= ms_resource_prefix_len &&
3672       memcmp (value,
3673               ms_resource_prefix,
3674               ms_resource_prefix_len * sizeof (gunichar2)) == 0)
3675     g_clear_pointer (&value, g_free);
3676
3677   if (value == NULL)
3678     return;
3679
3680   *destination = resolve_string (g_steal_pointer (&value));
3681
3682   if (*destination == NULL)
3683     return;
3684
3685   if (destination_u8)
3686     *destination_u8 = g_utf16_to_utf8 (*destination, -1, NULL, NULL, NULL);
3687 }
3688
3689 static void
3690 read_uwp_handler_info (void)
3691 {
3692   GHashTableIter iter;
3693   GWin32RegistryKey *handler_appkey;
3694   gunichar2 *aumid;
3695
3696   g_hash_table_iter_init (&iter, uwp_handler_table);
3697
3698   while (g_hash_table_iter_next (&iter, (gpointer *) &handler_appkey, (gpointer *) &aumid))
3699     {
3700       gchar *aumid_u8_folded;
3701       GWin32AppInfoApplication *app;
3702
3703       if (!g_utf16_to_utf8_and_fold (aumid,
3704                                      -1,
3705                                      NULL,
3706                                      &aumid_u8_folded))
3707         continue;
3708
3709       app = g_hash_table_lookup (apps_by_id, aumid_u8_folded);
3710       g_clear_pointer (&aumid_u8_folded, g_free);
3711
3712       if (app == NULL)
3713         continue;
3714
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).
3719        */
3720     }
3721 }
3722
3723 static void
3724 update_registry_data (void)
3725 {
3726   guint i;
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;
3735
3736   url_associations =
3737       g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3738                                    NULL);
3739   file_exts =
3740       g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
3741                                    NULL);
3742   classes_root = g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT", NULL);
3743
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);
3747
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);
3754
3755   collect_start = GetTickCount ();
3756   collect_capable_apps_from_clients (capable_apps_keys,
3757                                      priority_capable_apps_keys,
3758                                      FALSE);
3759   collect_capable_apps_from_clients (user_capable_apps_keys,
3760                                      priority_capable_apps_keys,
3761                                      TRUE);
3762   collect_capable_apps_from_registered_apps (user_capable_apps_keys,
3763                                              TRUE);
3764   collect_capable_apps_from_registered_apps (capable_apps_keys,
3765                                              FALSE);
3766   collect_end = GetTickCount ();
3767
3768   apps_by_id =
3769       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3770   apps_by_exe =
3771       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3772   fake_apps =
3773       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3774   urls =
3775       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3776   extensions =
3777       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3778   handlers =
3779       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3780   uwp_handler_table =
3781       g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free);
3782   alloc_end = GetTickCount ();
3783
3784   for (i = 0; i < priority_capable_apps_keys->len; i++)
3785     read_capable_app (g_ptr_array_index (priority_capable_apps_keys, i),
3786                       TRUE,
3787                       TRUE);
3788   for (i = 0; i < user_capable_apps_keys->len; i++)
3789     read_capable_app (g_ptr_array_index (user_capable_apps_keys, i),
3790                       TRUE,
3791                       FALSE);
3792   for (i = 0; i < capable_apps_keys->len; i++)
3793     read_capable_app (g_ptr_array_index (capable_apps_keys, i),
3794                       FALSE,
3795                       FALSE);
3796   capable_end = GetTickCount ();
3797
3798   read_urls (url_associations);
3799   url_end = GetTickCount ();
3800   read_exts (file_exts);
3801   ext_end = GetTickCount ();
3802   read_exeapps ();
3803   exeapp_end = GetTickCount ();
3804   read_classes (classes_root);
3805   classes_end = GetTickCount ();
3806
3807   if (!g_win32_package_parser_enum_packages (uwp_package_cb, NULL, &error))
3808     {
3809       g_debug ("Unable to get UWP apps: %s", error->message);
3810       g_clear_error (&error);
3811     }
3812
3813   read_uwp_handler_info ();
3814
3815   uwp_end = GetTickCount ();
3816   link_handlers_to_unregistered_apps ();
3817   link_handlers_to_fake_apps ();
3818   postproc_end = GetTickCount ();
3819
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"
3829            "TOTAL:                       %lums",
3830            collect_end - collect_start,
3831            alloc_end - collect_end,
3832            capable_end - alloc_end,
3833            url_end - capable_end,
3834            ext_end - url_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);
3840
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);
3848
3849   return;
3850 }
3851
3852 static void
3853 watch_keys (void);
3854
3855 /* This function is called when any of our registry watchers detect
3856  * changes in the registry.
3857  */
3858 static void
3859 keys_updated (GWin32RegistryKey  *key,
3860               gpointer            user_data)
3861 {
3862   watch_keys ();
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);
3867 }
3868
3869 static void
3870 watch_keys (void)
3871 {
3872   if (url_associations_key)
3873     g_win32_registry_key_watch (url_associations_key,
3874                                 TRUE,
3875                                 G_WIN32_REGISTRY_WATCH_NAME |
3876                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3877                                 G_WIN32_REGISTRY_WATCH_VALUES,
3878                                 keys_updated,
3879                                 NULL,
3880                                 NULL);
3881
3882   if (file_exts_key)
3883     g_win32_registry_key_watch (file_exts_key,
3884                                 TRUE,
3885                                 G_WIN32_REGISTRY_WATCH_NAME |
3886                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3887                                 G_WIN32_REGISTRY_WATCH_VALUES,
3888                                 keys_updated,
3889                                 NULL,
3890                                 NULL);
3891
3892   if (user_clients_key)
3893     g_win32_registry_key_watch (user_clients_key,
3894                                 TRUE,
3895                                 G_WIN32_REGISTRY_WATCH_NAME |
3896                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3897                                 G_WIN32_REGISTRY_WATCH_VALUES,
3898                                 keys_updated,
3899                                 NULL,
3900                                 NULL);
3901
3902   if (system_clients_key)
3903     g_win32_registry_key_watch (system_clients_key,
3904                                 TRUE,
3905                                 G_WIN32_REGISTRY_WATCH_NAME |
3906                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3907                                 G_WIN32_REGISTRY_WATCH_VALUES,
3908                                 keys_updated,
3909                                 NULL,
3910                                 NULL);
3911
3912   if (applications_key)
3913     g_win32_registry_key_watch (applications_key,
3914                                 TRUE,
3915                                 G_WIN32_REGISTRY_WATCH_NAME |
3916                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3917                                 G_WIN32_REGISTRY_WATCH_VALUES,
3918                                 keys_updated,
3919                                 NULL,
3920                                 NULL);
3921
3922   if (user_registered_apps_key)
3923     g_win32_registry_key_watch (user_registered_apps_key,
3924                                 TRUE,
3925                                 G_WIN32_REGISTRY_WATCH_NAME |
3926                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3927                                 G_WIN32_REGISTRY_WATCH_VALUES,
3928                                 keys_updated,
3929                                 NULL,
3930                                 NULL);
3931
3932   if (system_registered_apps_key)
3933     g_win32_registry_key_watch (system_registered_apps_key,
3934                                 TRUE,
3935                                 G_WIN32_REGISTRY_WATCH_NAME |
3936                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3937                                 G_WIN32_REGISTRY_WATCH_VALUES,
3938                                 keys_updated,
3939                                 NULL,
3940                                 NULL);
3941
3942   if (classes_root_key)
3943     g_win32_registry_key_watch (classes_root_key,
3944                                 FALSE,
3945                                 G_WIN32_REGISTRY_WATCH_NAME |
3946                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3947                                 G_WIN32_REGISTRY_WATCH_VALUES,
3948                                 keys_updated,
3949                                 NULL,
3950                                 NULL);
3951 }
3952
3953 /* This is the main function of the AppInfo thread */
3954 static void
3955 gio_win32_appinfo_thread_func (gpointer data,
3956                                gpointer user_data)
3957 {
3958   gint saved_counter;
3959   g_mutex_lock (&gio_win32_appinfo_mutex);
3960   saved_counter = g_atomic_int_get (&gio_win32_appinfo_update_counter);
3961
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.
3969    */
3970   if (g_atomic_int_compare_and_exchange  (&gio_win32_appinfo_update_counter,
3971                                           saved_counter,
3972                                           0))
3973     g_cond_broadcast (&gio_win32_appinfo_cond);
3974
3975   g_mutex_unlock (&gio_win32_appinfo_mutex);
3976 }
3977
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).
3984  */
3985 void
3986 gio_win32_appinfo_init (gboolean do_wait)
3987 {
3988   static gsize initialized;
3989
3990   if (g_once_init_enter (&initialized))
3991     {
3992       HMODULE gio_dll_extra;
3993
3994       url_associations_key =
3995           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3996                                        NULL);
3997       file_exts_key =
3998           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
3999                                        NULL);
4000       user_clients_key =
4001           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
4002                                        NULL);
4003       system_clients_key =
4004           g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
4005                                        NULL);
4006       applications_key =
4007           g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications",
4008                                        NULL);
4009       user_registered_apps_key =
4010           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
4011                                        NULL);
4012       system_registered_apps_key =
4013           g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
4014                                        NULL);
4015       classes_root_key =
4016           g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT",
4017                                        NULL);
4018
4019       watch_keys ();
4020
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).
4025        */
4026       gio_win32_appinfo_threadpool = g_thread_pool_new (gio_win32_appinfo_thread_func,
4027                                                         NULL,
4028                                                         1,
4029                                                         TRUE,
4030                                                         NULL);
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,
4039                           &gio_dll_extra);
4040       /* gio DLL cannot be unloaded now */
4041
4042       g_once_init_leave (&initialized, TRUE);
4043     }
4044
4045   if (!do_wait)
4046     return;
4047
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.
4051    */
4052   if (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0)
4053     {
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);
4058     }
4059 }
4060
4061
4062 static void g_win32_app_info_iface_init (GAppInfoIface *iface);
4063
4064 struct _GWin32AppInfo
4065 {
4066   GObject parent_instance;
4067
4068   /*<private>*/
4069   gchar **supported_types;
4070
4071   GWin32AppInfoApplication *app;
4072
4073   GWin32AppInfoHandler *handler;
4074
4075   guint startup_notify : 1;
4076 };
4077
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))
4081
4082
4083 static void
4084 g_win32_app_info_finalize (GObject *object)
4085 {
4086   GWin32AppInfo *info;
4087
4088   info = G_WIN32_APP_INFO (object);
4089
4090   g_clear_pointer (&info->supported_types, g_strfreev);
4091   g_clear_object (&info->app);
4092   g_clear_object (&info->handler);
4093
4094   G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
4095 }
4096
4097 static void
4098 g_win32_app_info_class_init (GWin32AppInfoClass *klass)
4099 {
4100   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
4101
4102   gobject_class->finalize = g_win32_app_info_finalize;
4103 }
4104
4105 static void
4106 g_win32_app_info_init (GWin32AppInfo *local)
4107 {
4108 }
4109
4110 static GAppInfo *
4111 g_win32_app_info_new_from_app (GWin32AppInfoApplication *app,
4112                                GWin32AppInfoHandler     *handler)
4113 {
4114   GWin32AppInfo *new_info;
4115   GHashTableIter iter;
4116   gpointer ext;
4117   int i;
4118
4119   new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4120
4121   new_info->app = g_object_ref (app);
4122
4123   gio_win32_appinfo_init (TRUE);
4124   g_mutex_lock (&gio_win32_appinfo_mutex);
4125
4126   i = 0;
4127   g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4128
4129   while (g_hash_table_iter_next (&iter, &ext, NULL))
4130     {
4131       if (ext)
4132         i += 1;
4133     }
4134
4135   new_info->supported_types = g_new (gchar *, i + 1);
4136
4137   i = 0;
4138   g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4139
4140   while (g_hash_table_iter_next (&iter, &ext, NULL))
4141     {
4142       if (!ext)
4143         continue;
4144
4145       new_info->supported_types[i] = g_strdup ((gchar *) ext);
4146       i += 1;
4147     }
4148
4149   g_mutex_unlock (&gio_win32_appinfo_mutex);
4150
4151   new_info->supported_types[i] = NULL;
4152
4153   new_info->handler = handler ? g_object_ref (handler) : NULL;
4154
4155   return G_APP_INFO (new_info);
4156 }
4157
4158
4159 static GAppInfo *
4160 g_win32_app_info_dup (GAppInfo *appinfo)
4161 {
4162   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4163   GWin32AppInfo *new_info;
4164
4165   new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4166
4167   if (info->app)
4168     new_info->app = g_object_ref (info->app);
4169
4170   if (info->handler)
4171     new_info->handler = g_object_ref (info->handler);
4172
4173   new_info->startup_notify = info->startup_notify;
4174
4175   if (info->supported_types)
4176     {
4177       int i;
4178
4179       for (i = 0; info->supported_types[i]; i++)
4180         break;
4181
4182       new_info->supported_types = g_new (gchar *, i + 1);
4183
4184       for (i = 0; info->supported_types[i]; i++)
4185         new_info->supported_types[i] = g_strdup (info->supported_types[i]);
4186
4187       new_info->supported_types[i] = NULL;
4188     }
4189
4190   return G_APP_INFO (new_info);
4191 }
4192
4193 static gboolean
4194 g_win32_app_info_equal (GAppInfo *appinfo1,
4195                         GAppInfo *appinfo2)
4196 {
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;
4203
4204   if (app1 == NULL ||
4205       app2 == NULL)
4206     return info1 == info2;
4207
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;
4212
4213   if (app1->verbs->len > 0 &&
4214       app2->verbs->len > 0)
4215     {
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;
4222     }
4223
4224   return app1 == app2;
4225 }
4226
4227 static const char *
4228 g_win32_app_info_get_id (GAppInfo *appinfo)
4229 {
4230   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4231   GWin32AppInfoShellVerb *shverb;
4232
4233   if (info->app == NULL)
4234     return NULL;
4235
4236   if (info->app->canonical_name_u8)
4237     return info->app->canonical_name_u8;
4238
4239   if (info->app->verbs->len > 0 &&
4240       (shverb = _verb_idx (info->app->verbs, 0))->executable_basename != NULL)
4241     return shverb->executable_basename;
4242
4243   return NULL;
4244 }
4245
4246 static const char *
4247 g_win32_app_info_get_name (GAppInfo *appinfo)
4248 {
4249   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4250
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;
4255   else
4256     return P_("Unnamed");
4257 }
4258
4259 static const char *
4260 g_win32_app_info_get_display_name (GAppInfo *appinfo)
4261 {
4262   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4263
4264   if (info->app)
4265     {
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;
4270     }
4271
4272   return g_win32_app_info_get_name (appinfo);
4273 }
4274
4275 static const char *
4276 g_win32_app_info_get_description (GAppInfo *appinfo)
4277 {
4278   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4279
4280   if (info->app == NULL)
4281     return NULL;
4282
4283   return info->app->description_u8;
4284 }
4285
4286 static const char *
4287 g_win32_app_info_get_executable (GAppInfo *appinfo)
4288 {
4289   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4290
4291   if (info->app == NULL)
4292     return NULL;
4293
4294   if (info->app->verbs->len > 0 && !info->app->is_uwp)
4295     return _verb_idx (info->app->verbs, 0)->executable;
4296
4297   return NULL;
4298 }
4299
4300 static const char *
4301 g_win32_app_info_get_commandline (GAppInfo *appinfo)
4302 {
4303   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4304
4305   if (info->app == NULL)
4306     return NULL;
4307
4308   if (info->app->verbs->len > 0 && !info->app->is_uwp)
4309     return _verb_idx (info->app->verbs, 0)->command_utf8;
4310
4311   return NULL;
4312 }
4313
4314 static GIcon *
4315 g_win32_app_info_get_icon (GAppInfo *appinfo)
4316 {
4317   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4318
4319   if (info->app == NULL)
4320     return NULL;
4321
4322   return info->app->icon;
4323 }
4324
4325 typedef struct _file_or_uri {
4326   gchar *uri;
4327   gchar *file;
4328 } file_or_uri;
4329
4330 static char *
4331 expand_macro_single (char macro, file_or_uri *obj)
4332 {
4333   char *result = NULL;
4334
4335   switch (macro)
4336     {
4337     case '*':
4338     case '0':
4339     case '1':
4340     case 'l':
4341     case 'd':
4342     case '2':
4343     case '3':
4344     case '4':
4345     case '5':
4346     case '6':
4347     case '7':
4348     case '8':
4349     case '9':
4350       /* TODO: handle 'l' and 'd' differently (longname and desktop name) */
4351       if (obj->file)
4352         {
4353           result = g_strdup (obj->file);
4354         }
4355       else if (obj->uri)
4356         {
4357           const char *prefix = "file:///";
4358           const size_t prefix_len = strlen (prefix);
4359
4360           if (g_str_has_prefix (obj->uri, prefix) && obj->uri[prefix_len] != 0)
4361             {
4362               GFile *file = g_file_new_for_uri (obj->uri);
4363               result = g_file_get_path (file);
4364               g_object_unref (file);
4365             }
4366
4367           if (!result)
4368             result = g_strdup (obj->uri);
4369         }
4370       break;
4371     case 'u':
4372     case 'U':
4373       if (obj->uri)
4374         result = g_shell_quote (obj->uri);
4375       break;
4376     case 'f':
4377     case 'F':
4378       if (obj->file)
4379         result = g_shell_quote (obj->file);
4380       break;
4381     }
4382
4383   return result;
4384 }
4385
4386 static gboolean
4387 expand_macro (char               macro,
4388               GString           *exec,
4389               GWin32AppInfo     *info,
4390               GList            **stat_obj_list,
4391               GList            **obj_list)
4392 {
4393   GList *objs = *obj_list;
4394   char *expanded;
4395   gboolean result = FALSE;
4396
4397   g_return_val_if_fail (exec != NULL, FALSE);
4398
4399 /*
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
4405 %s - show command
4406 %h - hotkey value
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
4412 */
4413
4414   switch (macro)
4415     {
4416     case '*':
4417     case '~':
4418       if (*stat_obj_list)
4419         {
4420           gint i;
4421           GList *o;
4422
4423           for (o = *stat_obj_list, i = 0;
4424                macro == '~' && o && i < 2;
4425                o = o->next, i++);
4426
4427           for (; o; o = o->next)
4428             {
4429               expanded = expand_macro_single (macro, o->data);
4430
4431               if (expanded)
4432                 {
4433                   if (o != *stat_obj_list)
4434                     g_string_append (exec, " ");
4435
4436                   g_string_append (exec, expanded);
4437                   g_free (expanded);
4438                 }
4439             }
4440
4441           objs = NULL;
4442           result = TRUE;
4443         }
4444       break;
4445     case '0':
4446     case '1':
4447     case 'l':
4448     case 'd':
4449       if (*stat_obj_list)
4450         {
4451           GList *o;
4452
4453           o = *stat_obj_list;
4454
4455           if (o)
4456             {
4457               expanded = expand_macro_single (macro, o->data);
4458
4459               if (expanded)
4460                 {
4461                   if (o != *stat_obj_list)
4462                     g_string_append (exec, " ");
4463
4464                   g_string_append (exec, expanded);
4465                   g_free (expanded);
4466                 }
4467             }
4468
4469           if (objs)
4470             objs = objs->next;
4471
4472           result = TRUE;
4473         }
4474       break;
4475     case '2':
4476     case '3':
4477     case '4':
4478     case '5':
4479     case '6':
4480     case '7':
4481     case '8':
4482     case '9':
4483       if (*stat_obj_list)
4484         {
4485           gint i;
4486           GList *o;
4487           gint n = 0;
4488
4489           switch (macro)
4490             {
4491             case '2':
4492               n = 2;
4493               break;
4494             case '3':
4495               n = 3;
4496               break;
4497             case '4':
4498               n = 4;
4499               break;
4500             case '5':
4501               n = 5;
4502               break;
4503             case '6':
4504               n = 6;
4505               break;
4506             case '7':
4507               n = 7;
4508               break;
4509             case '8':
4510               n = 8;
4511               break;
4512             case '9':
4513               n = 9;
4514               break;
4515             }
4516
4517           for (o = *stat_obj_list, i = 0; o && i < n; o = o->next, i++);
4518
4519           if (o)
4520             {
4521               expanded = expand_macro_single (macro, o->data);
4522
4523               if (expanded)
4524                 {
4525                   if (o != *stat_obj_list)
4526                     g_string_append (exec, " ");
4527
4528                   g_string_append (exec, expanded);
4529                   g_free (expanded);
4530                 }
4531             }
4532           result = TRUE;
4533
4534           if (objs)
4535             objs = NULL;
4536         }
4537       break;
4538     case 's':
4539       break;
4540     case 'h':
4541       break;
4542     case 'i':
4543       break;
4544     case 'v':
4545       break;
4546     case 'w':
4547       expanded = g_get_current_dir ();
4548       g_string_append (exec, expanded);
4549       g_free (expanded);
4550       break;
4551     case 'u':
4552     case 'f':
4553       if (objs)
4554         {
4555           expanded = expand_macro_single (macro, objs->data);
4556
4557           if (expanded)
4558             {
4559               g_string_append (exec, expanded);
4560               g_free (expanded);
4561             }
4562           objs = objs->next;
4563           result = TRUE;
4564         }
4565
4566       break;
4567
4568     case 'U':
4569     case 'F':
4570       while (objs)
4571         {
4572           expanded = expand_macro_single (macro, objs->data);
4573
4574           if (expanded)
4575             {
4576               g_string_append (exec, expanded);
4577               g_free (expanded);
4578             }
4579
4580           objs = objs->next;
4581           result = TRUE;
4582
4583           if (objs != NULL && expanded)
4584             g_string_append_c (exec, ' ');
4585         }
4586
4587       break;
4588
4589     case 'c':
4590       if (info->app && info->app->localized_pretty_name_u8)
4591         {
4592           expanded = g_shell_quote (info->app->localized_pretty_name_u8);
4593           g_string_append (exec, expanded);
4594           g_free (expanded);
4595         }
4596       break;
4597
4598     case 'm': /* deprecated */
4599     case 'n': /* deprecated */
4600     case 'N': /* deprecated */
4601     /*case 'd': *//* deprecated */
4602     case 'D': /* deprecated */
4603       break;
4604
4605     case '%':
4606       g_string_append_c (exec, '%');
4607       break;
4608     }
4609
4610   *obj_list = objs;
4611
4612   return result;
4613 }
4614
4615 static gboolean
4616 expand_application_parameters (GWin32AppInfo   *info,
4617                                const gchar     *exec_line,
4618                                GList          **objs,
4619                                int             *argc,
4620                                char          ***argv,
4621                                GError         **error)
4622 {
4623   GList *obj_list = *objs;
4624   GList **stat_obj_list = objs;
4625   const char *p = exec_line;
4626   GString *expanded_exec;
4627   gboolean res;
4628   gchar *a_char;
4629
4630   expanded_exec = g_string_new (NULL);
4631   res = FALSE;
4632
4633   while (*p)
4634     {
4635       if (p[0] == '%' && p[1] != '\0')
4636         {
4637           if (expand_macro (p[1],
4638                             expanded_exec,
4639                             info, stat_obj_list,
4640                             objs))
4641             res = TRUE;
4642
4643           p++;
4644         }
4645       else
4646         g_string_append_c (expanded_exec, *p);
4647
4648       p++;
4649     }
4650
4651   /* No file substitutions */
4652   if (obj_list == *objs && obj_list != NULL && !res)
4653     {
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);
4657     }
4658
4659   /* Replace '\\' with '/', because g_shell_parse_argv considers them
4660    * to be escape sequences.
4661    */
4662   for (a_char = expanded_exec->str;
4663        a_char <= &expanded_exec->str[expanded_exec->len];
4664        a_char++)
4665     {
4666       if (*a_char == '\\')
4667         *a_char = '/';
4668     }
4669
4670   res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
4671   g_string_free (expanded_exec, TRUE);
4672   return res;
4673 }
4674
4675
4676 static gchar *
4677 get_appath_for_exe (const gchar *exe_basename)
4678 {
4679   GWin32RegistryKey *apppath_key = NULL;
4680   GWin32RegistryValueType val_type;
4681   gchar *appath = NULL;
4682   gboolean got_value;
4683   gchar *key_path = g_strdup_printf ("HKEY_LOCAL_MACHINE\\"
4684                                      "SOFTWARE\\"
4685                                      "Microsoft\\"
4686                                      "Windows\\"
4687                                      "CurrentVersion\\"
4688                                      "App Paths\\"
4689                                      "%s", exe_basename);
4690
4691   apppath_key = g_win32_registry_key_new (key_path, NULL);
4692   g_clear_pointer (&key_path, g_free);
4693
4694   if (apppath_key == NULL)
4695     return NULL;
4696
4697   got_value = g_win32_registry_key_get_value (apppath_key,
4698                                               NULL,
4699                                               TRUE,
4700                                               "Path",
4701                                               &val_type,
4702                                               (void **) &appath,
4703                                               NULL,
4704                                               NULL);
4705
4706   g_object_unref (apppath_key);
4707
4708   if (got_value &&
4709       val_type == G_WIN32_REGISTRY_VALUE_STR)
4710     return appath;
4711
4712   g_clear_pointer (&appath, g_free);
4713
4714   return appath;
4715 }
4716
4717 /* GDesktopAppInfo::launch_uris_async emits all GAppLaunchContext's signals
4718  * on the main thread.
4719  *
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).
4724  */
4725
4726 typedef struct {
4727   GAppLaunchContext *context;  /* (owned) */
4728   GWin32AppInfo *info;  /* (owned) */
4729 } EmitLaunchStartedData;
4730
4731 static void
4732 emit_launch_started_data_free (EmitLaunchStartedData *data)
4733 {
4734   g_clear_object (&data->context);
4735   g_clear_object (&data->info);
4736   g_free (data);
4737 }
4738
4739 static gboolean
4740 emit_launch_started_cb (EmitLaunchStartedData *data)
4741 {
4742   g_signal_emit_by_name (data->context, "launch-started", data->info, NULL);
4743   return G_SOURCE_REMOVE;
4744 }
4745
4746 static void
4747 emit_launch_started (GAppLaunchContext *context,
4748                      GWin32AppInfo     *info,
4749                      GTask             *from_task)
4750 {
4751   if (!context || !info)
4752     return;
4753
4754   if (!from_task)
4755     g_signal_emit_by_name (context, "launch-started", info, NULL);
4756   else
4757     {
4758       EmitLaunchStartedData *data;
4759
4760       data = g_new (EmitLaunchStartedData, 1);
4761       data->context = g_object_ref (context);
4762       data->info = g_object_ref (info);
4763
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);
4769     }
4770 }
4771
4772 typedef struct {
4773   GAppLaunchContext *context;  /* (owned) */
4774   GWin32AppInfo *info;  /* (owned) */
4775   GPid pid;  /* (owned) */
4776 } EmitLaunchedData;
4777
4778 static void
4779 emit_launched_data_free (EmitLaunchedData *data)
4780 {
4781   g_clear_object (&data->context);
4782   g_clear_object (&data->info);
4783   g_spawn_close_pid (data->pid);
4784   g_free (data);
4785 }
4786
4787 static GVariant*
4788 make_platform_data (GPid pid)
4789 {
4790   GVariantBuilder builder;
4791
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));
4800
4801   return g_variant_ref_sink (g_variant_builder_end (&builder));
4802 }
4803
4804 static gboolean
4805 emit_launched_cb (EmitLaunchedData *data)
4806 {
4807   
4808   GVariant *platform_data = make_platform_data (data->pid);
4809
4810   g_signal_emit_by_name (data->context, "launched", data->info, platform_data);
4811   g_variant_unref (platform_data);
4812
4813   return G_SOURCE_REMOVE;
4814 }
4815
4816 static void
4817 emit_launched (GAppLaunchContext *context,
4818                GWin32AppInfo     *info,
4819                GPid              *pid,
4820                GTask             *from_task)
4821 {
4822   if (!context || !info)
4823     return;
4824
4825   if (!from_task)
4826     {
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);
4831     }
4832   else
4833     {
4834       EmitLaunchedData *data;
4835
4836       data = g_new (EmitLaunchedData, 1);
4837       data->context = g_object_ref (context);
4838       data->info = g_object_ref (info);
4839       data->pid = *pid;
4840
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);
4846     }
4847
4848   *pid = NULL;
4849 }
4850
4851 typedef struct {
4852   GAppLaunchContext *context;  /* (owned) */
4853   GWin32AppInfo *info;  /* (owned) */
4854 } EmitLaunchFailedData;
4855
4856 static void
4857 emit_launch_failed_data_free (EmitLaunchFailedData *data)
4858 {
4859   g_clear_object (&data->context);
4860   g_clear_object (&data->info);
4861   g_free (data);
4862 }
4863
4864 static gboolean
4865 emit_launch_failed_cb (EmitLaunchFailedData *data)
4866 {
4867   g_signal_emit_by_name (data->context, "launch-failed", data->info, NULL);
4868   return G_SOURCE_REMOVE;
4869 }
4870
4871 static void
4872 emit_launch_failed (GAppLaunchContext *context,
4873                     GWin32AppInfo     *info,
4874                     GTask             *from_task)
4875 {
4876   if (!context || !info)
4877     return;
4878
4879   if (!from_task)
4880     g_signal_emit_by_name (context, "launch-failed", info, NULL);
4881   else
4882     {
4883       EmitLaunchFailedData *data;
4884
4885       data = g_new (EmitLaunchFailedData, 1);
4886       data->context = g_object_ref (context);
4887       data->info = g_object_ref (info);
4888
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);
4894     }
4895 }
4896
4897 typedef enum
4898 {
4899   /* PLAIN: just open the application, without arguments of any kind
4900    *        corresponds to: LaunchActivatedEventArgs */
4901   UWP_ACTIVATION_TYPE_PLAIN,
4902
4903   /* FILE: open the applications passing a set of files
4904    *       corresponds to: FileActivatedEventArgs */
4905   UWP_ACTIVATION_TYPE_FILE,
4906
4907   /* PROTOCOL: open the application passing a URI which describe an
4908                app activity
4909    *           corresponds to: ProtocolActivatedEventArgs */
4910   UWP_ACTIVATION_TYPE_PROTOCOL,
4911 } UwpActivationType;
4912
4913 static gboolean
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,
4920                                     GTask                          *from_task,
4921                                     GError                        **error)
4922 {
4923   const wchar_t *canonical_name = (const wchar_t *) info->app->canonical_name;
4924   DWORD process_id = 0;
4925   HRESULT hr = S_OK;
4926
4927   emit_launch_started (launch_context, info, from_task);
4928
4929   /* The Activate methods return a process identifier (PID), so we should consider
4930    * those methods as potentially blocking */
4931
4932   switch (activation_type)
4933     {
4934     case UWP_ACTIVATION_TYPE_PLAIN:
4935       g_assert (items == NULL);
4936
4937       hr = IApplicationActivationManager_ActivateApplication (app_activation_manager,
4938                                                               canonical_name,
4939                                                               NULL, AO_NONE,
4940                                                               &process_id);
4941       break;
4942     case UWP_ACTIVATION_TYPE_PROTOCOL:
4943       g_assert (items != NULL);
4944
4945       hr = IApplicationActivationManager_ActivateForProtocol (app_activation_manager,
4946                                                               canonical_name,
4947                                                               items,
4948                                                               &process_id);
4949       break;
4950     case UWP_ACTIVATION_TYPE_FILE:
4951       g_assert (items != NULL);
4952
4953       hr = IApplicationActivationManager_ActivateForFile (app_activation_manager,
4954                                                           canonical_name,
4955                                                           items, verb,
4956                                                           &process_id);
4957       break;
4958     }
4959
4960   if (FAILED (hr))
4961     {
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);
4965
4966       emit_launch_failed (launch_context, info, from_task);
4967
4968       return FALSE;
4969     }
4970
4971   if (launch_context)
4972     {
4973       DWORD access_rights = 0;
4974       HANDLE process_handle = NULL;
4975
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.
4982        *
4983        * https://docs.microsoft.com/en-us/answers/questions/942879/
4984        * iapplicationactivationmanager-race-condition.html
4985        *
4986        * Maybe we could make use of the WinRT APIs to activate UWP apps,
4987        * instead? */
4988
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
4993        * access rights */
4994       access_rights = PROCESS_ALL_ACCESS;
4995
4996       process_handle = OpenProcess (access_rights, FALSE, process_id);
4997
4998       if (!process_handle && GetLastError () == ERROR_ACCESS_DENIED)
4999         {
5000           DWORD access_rights = PROCESS_QUERY_LIMITED_INFORMATION |
5001                                 SYNCHRONIZE;
5002
5003           process_handle = OpenProcess (access_rights, FALSE, process_id);
5004         }
5005
5006       if (!process_handle)
5007         {
5008           g_warning ("OpenProcess failed with error code %" G_GUINT32_FORMAT,
5009                      (guint32) GetLastError ());
5010         }
5011
5012       /* Emit the launched signal regardless if we have the process
5013        * HANDLE or NULL */
5014       emit_launched (launch_context, info, (GPid*) &process_handle, from_task);
5015
5016       g_spawn_close_pid ((GPid) process_handle);
5017     }
5018
5019   return TRUE;
5020 }
5021
5022 static gboolean
5023 g_win32_app_info_supports_files (GAppInfo *appinfo);
5024
5025 static IShellItemArray *
5026 make_item_array (gboolean   for_files,
5027                  GList     *objs,
5028                  GError   **error);
5029
5030 static inline GList
5031 make_single_entry_list (gpointer data)
5032 {
5033   GList l = { NULL, NULL, NULL };
5034   l.data = data;
5035
5036   return l;
5037 }
5038
5039 static gboolean
5040 g_win32_app_info_launch_uwp_internal (GWin32AppInfo           *info,
5041                                       gboolean                 for_files,
5042                                       GList                   *objs,  /* (element-type file_or_uri) */
5043                                       GWin32AppInfoShellVerb  *shverb,
5044                                       GAppLaunchContext       *launch_context,
5045                                       GTask                   *from_task,
5046                                       GError                 **error)
5047 {
5048   IApplicationActivationManager *paam = NULL;
5049   gboolean com_initialized = FALSE;
5050   gboolean result = FALSE;
5051   HRESULT hr;
5052
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);
5057   if (SUCCEEDED (hr))
5058     com_initialized = TRUE;
5059   else if (hr != RPC_E_CHANGED_MODE)
5060     {
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);
5063       goto cleanup;
5064     }
5065
5066   /* It's best to instantiate ApplicationActivationManager out-of-proc,
5067    * as documented on MSDN:
5068    *
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.
5082    */
5083   hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL,
5084                          CLSCTX_LOCAL_SERVER,
5085                          &IID_IApplicationActivationManager, (void **) &paam);
5086   if (FAILED (hr))
5087     {
5088       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5089                    "Failed to create ApplicationActivationManager: 0x%lx", hr);
5090       goto cleanup;
5091     }
5092
5093   if (!objs)
5094     {
5095       result = g_win32_app_info_launch_uwp_single (paam, UWP_ACTIVATION_TYPE_PLAIN, NULL, NULL,
5096                                                    info, launch_context, from_task, error);
5097     }
5098   else if (for_files)
5099     {
5100       IShellItemArray *items = make_item_array (TRUE, objs, error);
5101
5102       if (!items)
5103         goto cleanup;
5104
5105       result = g_win32_app_info_launch_uwp_single (paam, UWP_ACTIVATION_TYPE_FILE, items,
5106                                                    shverb->verb_name,
5107                                                    info, launch_context, from_task, error);
5108
5109       IShellItemArray_Release (items);
5110     }
5111   else
5112     {
5113       gboolean supports_files;
5114       gboolean supports_file_uris;
5115       gboolean outcome = TRUE;
5116       GList *l;
5117
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");
5122
5123       for (l = objs; l != NULL; l = l->next)
5124         {
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;
5129
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
5134            * that. */
5135
5136           if (!supports_file_uris && supports_files && obj->file)
5137             {
5138               type = UWP_ACTIVATION_TYPE_FILE;
5139               item = make_item_array (TRUE, &single, error);
5140             }
5141           else
5142             {
5143               type = UWP_ACTIVATION_TYPE_PROTOCOL;
5144               item = make_item_array (FALSE, &single, error);
5145             }
5146
5147           if (!item)
5148             {
5149               outcome = FALSE;
5150               continue;
5151             }
5152
5153           if (!g_win32_app_info_launch_uwp_single (paam, type,
5154                                                    item, shverb->verb_name, info,
5155                                                    launch_context, from_task, error))
5156             outcome = FALSE;
5157
5158           IShellItemArray_Release (item);
5159         }
5160
5161       result = outcome;
5162     }
5163
5164 cleanup:
5165
5166   if (paam)
5167     {
5168       IApplicationActivationManager_Release (paam);
5169       paam = NULL;
5170     }
5171
5172   if (com_initialized)
5173     {
5174       CoUninitialize ();
5175       com_initialized = FALSE;
5176     }
5177
5178   return result;
5179 }
5180
5181
5182 static gboolean
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,
5188                                   GTask              *from_task,
5189                                   GError            **error)
5190 {
5191   gboolean completed = FALSE;
5192   char **argv, **envp;
5193   int argc;
5194   const gchar *command;
5195   gchar *apppath;
5196   GWin32AppInfoShellVerb *shverb;
5197   GPid pid = NULL;
5198
5199   g_return_val_if_fail (info != NULL, FALSE);
5200   g_return_val_if_fail (info->app != NULL, FALSE);
5201
5202   argv = NULL;
5203   shverb = NULL;
5204
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);
5211
5212   if (shverb == NULL)
5213     {
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);
5223
5224       return FALSE;
5225     }
5226
5227   if (info->app->is_uwp)
5228     return g_win32_app_info_launch_uwp_internal (info,
5229                                                  for_files,
5230                                                  objs,
5231                                                  shverb,
5232                                                  launch_context,
5233                                                  from_task,
5234                                                  error);
5235
5236   if (launch_context)
5237     envp = g_app_launch_context_get_environment (launch_context);
5238   else
5239     envp = g_get_environ ();
5240
5241   g_assert (shverb->command_utf8 != NULL);
5242   command = shverb->command_utf8;
5243   apppath = get_appath_for_exe (shverb->executable_basename);
5244
5245   if (apppath)
5246     {
5247       gchar **p;
5248       gsize p_index;
5249
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') &&
5255             (p[0][4] == '='))
5256           break;
5257
5258       if (p[0] == NULL)
5259         {
5260           gchar **new_envp;
5261           new_envp = g_new (char *, g_strv_length (envp) + 2);
5262           new_envp[0] = g_strdup_printf ("PATH=%s", apppath);
5263
5264           for (p_index = 0; p_index <= g_strv_length (envp); p_index++)
5265             new_envp[1 + p_index] = envp[p_index];
5266
5267           g_free (envp);
5268           envp = new_envp;
5269         }
5270       else
5271         {
5272           gchar *p_path;
5273
5274           p_path = &p[0][5];
5275
5276           if (p_path[0] != '\0')
5277             envp[p_index] = g_strdup_printf ("PATH=%s%c%s",
5278                                              apppath,
5279                                              G_SEARCHPATH_SEPARATOR,
5280                                              p_path);
5281           else
5282             envp[p_index] = g_strdup_printf ("PATH=%s", apppath);
5283
5284           g_free (&p_path[-5]);
5285         }
5286     }
5287
5288   do
5289     {
5290       if (from_task && g_task_return_error_if_cancelled (from_task))
5291         goto out;
5292
5293       if (!expand_application_parameters (info,
5294                                           command,
5295                                           &objs,
5296                                           &argc,
5297                                           &argv,
5298                                           error))
5299         goto out;
5300
5301       emit_launch_started (launch_context, info, from_task);
5302
5303       if (!g_spawn_async (NULL,
5304                           argv,
5305                           envp,
5306                           spawn_flags |
5307                           G_SPAWN_DO_NOT_REAP_CHILD,
5308                           NULL,
5309                           NULL,
5310                           &pid,
5311                           error))
5312         {
5313           emit_launch_failed (launch_context, info, from_task);
5314
5315           goto out;
5316         }
5317       else if (launch_context)
5318         emit_launched (launch_context, info, &pid, from_task);
5319
5320       g_spawn_close_pid (pid);
5321       pid = NULL;
5322       g_strfreev (argv);
5323       argv = NULL;
5324     }
5325   while (objs != NULL);
5326
5327   completed = TRUE;
5328
5329 out:
5330   g_spawn_close_pid (pid);
5331   g_strfreev (argv);
5332   g_strfreev (envp);
5333
5334   return completed;
5335 }
5336
5337 static void
5338 free_file_or_uri (gpointer ptr)
5339 {
5340   file_or_uri *obj = ptr;
5341   g_free (obj->file);
5342   g_free (obj->uri);
5343   g_free (obj);
5344 }
5345
5346
5347 static gboolean
5348 g_win32_app_supports_uris (GWin32AppInfoApplication *app)
5349 {
5350   gssize num_of_uris_supported;
5351
5352   if (app == NULL)
5353     return FALSE;
5354
5355   num_of_uris_supported = (gssize) g_hash_table_size (app->supported_urls);
5356
5357   if (g_hash_table_lookup (app->supported_urls, "file"))
5358     num_of_uris_supported -= 1;
5359
5360   return num_of_uris_supported > 0;
5361 }
5362
5363
5364 static gboolean
5365 g_win32_app_info_supports_uris (GAppInfo *appinfo)
5366 {
5367   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5368
5369   if (info->app == NULL)
5370     return FALSE;
5371
5372   return g_win32_app_supports_uris (info->app);
5373 }
5374
5375
5376 static gboolean
5377 g_win32_app_info_supports_files (GAppInfo *appinfo)
5378 {
5379   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5380
5381   if (info->app == NULL)
5382     return FALSE;
5383
5384   return g_hash_table_size (info->app->supported_exts) > 0;
5385 }
5386
5387
5388 static IShellItemArray *
5389 make_item_array (gboolean   for_files,
5390                  GList     *objs,  /* (element-type file_or_uri) */
5391                  GError   **error)
5392 {
5393   ITEMIDLIST **item_ids;
5394   IShellItemArray *items;
5395   GList *p;
5396   gsize count;
5397   gsize i;
5398   HRESULT hr;
5399
5400   count = g_list_length (objs);
5401
5402   items = NULL;
5403   item_ids = g_new (ITEMIDLIST*, count);
5404
5405   for (i = 0, p = objs; p != NULL; p = p->next, i++)
5406     {
5407       file_or_uri *obj = (file_or_uri*) p->data;
5408       wchar_t *file_or_uri_utf16;
5409
5410       if (!for_files)
5411         file_or_uri_utf16 = g_utf8_to_utf16 (obj->uri, -1, NULL, NULL, error);
5412       else
5413         file_or_uri_utf16 = g_utf8_to_utf16 (obj->file, -1, NULL, NULL, error);
5414
5415       if (file_or_uri_utf16 == NULL)
5416         break;
5417
5418       if (for_files)
5419         {
5420           wchar_t *c;
5421           gsize len;
5422           gsize len_tail;
5423
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.
5428            */
5429           for (c = file_or_uri_utf16; for_files && *c != 0; c++)
5430             {
5431               if (*c == L'/')
5432                 *c = L'\\';
5433             }
5434           for (len_tail = 0, c = &file_or_uri_utf16[len - 1];
5435                for_files && c > file_or_uri_utf16;
5436                c--, len_tail++)
5437             {
5438               if (c[0] != L'\\' || c[-1] != L'\\')
5439                 continue;
5440
5441               memmove (&c[-1], &c[0], len_tail * sizeof (wchar_t));
5442             }
5443         }
5444
5445       hr = SHParseDisplayName (file_or_uri_utf16, NULL, &item_ids[i], 0, NULL);
5446
5447       if (FAILED (hr))
5448         {
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);
5452
5453           g_free (file_or_uri_utf16);
5454           break;
5455         }
5456
5457       g_free (file_or_uri_utf16);
5458     }
5459
5460   if (i == count)
5461     {
5462       hr = SHCreateShellItemArrayFromIDLists (count, (const ITEMIDLIST **) item_ids, &items);
5463       if (FAILED (hr))
5464         {
5465           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
5466                        "SHCreateShellItemArrayFromIDLists() failed: 0x%lx", hr);
5467           items = NULL;
5468         }
5469     }
5470
5471   count = i;
5472
5473   for (i = 0; i < count; i++)
5474     CoTaskMemFree (item_ids[i]);
5475
5476   g_free (item_ids);
5477
5478   return items;
5479 }
5480
5481
5482 static gboolean
5483 g_win32_app_info_launch_uris_impl (GAppInfo           *appinfo,
5484                                    GList              *uris,  /* (element-type utf8) */
5485                                    GAppLaunchContext  *launch_context,
5486                                    GTask              *from_task,
5487                                    GError            **error)
5488 {
5489   gboolean res = FALSE;
5490   gboolean do_files;
5491   GList *objs = NULL;
5492   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5493   gboolean is_uwp;
5494
5495   do_files = g_win32_app_info_supports_files (appinfo);
5496
5497   while (uris)
5498     {
5499       file_or_uri *obj;
5500       obj = g_new0 (file_or_uri, 1);
5501
5502       if (do_files)
5503         {
5504           GFile *file;
5505           gchar *path;
5506
5507           file = g_file_new_for_uri (uris->data);
5508           path = g_file_get_path (file);
5509           obj->file = path;
5510           g_object_unref (file);
5511         }
5512
5513       obj->uri = g_strdup (uris->data);
5514
5515       objs = g_list_prepend (objs, obj);
5516       uris = uris->next;
5517     }
5518   objs = g_list_reverse (objs);
5519
5520   is_uwp = (info->app != NULL && info->app->is_uwp);
5521
5522   res = g_win32_app_info_launch_internal (info,
5523                                           objs,
5524                                           FALSE,
5525                                           launch_context,
5526                                           is_uwp ?
5527                                           0 :
5528                                           G_SPAWN_SEARCH_PATH,
5529                                           from_task,
5530                                           error);
5531
5532   g_list_free_full (objs, free_file_or_uri);
5533
5534   return res;
5535 }
5536
5537 static gboolean
5538 g_win32_app_info_launch_uris (GAppInfo           *appinfo,
5539                               GList              *uris,
5540                               GAppLaunchContext  *launch_context,
5541                               GError            **error)
5542 {
5543   return g_win32_app_info_launch_uris_impl (appinfo, uris, launch_context, NULL, error);
5544 }
5545
5546 typedef struct
5547 {
5548   GList *uris;  /* (element-type utf8) (owned) (nullable) */
5549   GAppLaunchContext *context;  /* (owned) (nullable) */
5550 } LaunchUrisData;
5551
5552 static void
5553 launch_uris_data_free (LaunchUrisData *data)
5554 {
5555   g_clear_object (&data->context);
5556   g_list_free_full (data->uris, g_free);
5557   g_free (data);
5558 }
5559
5560 static void
5561 launch_uris_async_thread (GTask         *task,
5562                           gpointer       source_object,
5563                           gpointer       task_data,
5564                           GCancellable  *cancellable)
5565 {
5566   GAppInfo *appinfo = G_APP_INFO (source_object);
5567   LaunchUrisData *data = task_data;
5568   GError *local_error = NULL;
5569   gboolean succeeded;
5570
5571   succeeded = g_win32_app_info_launch_uris_impl (appinfo, data->uris, data->context, task, &local_error);
5572   if (succeeded)
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));
5576 }
5577
5578 static void
5579 g_win32_app_info_launch_uris_async (GAppInfo            *appinfo,
5580                                     GList               *uris,
5581                                     GAppLaunchContext   *context,
5582                                     GCancellable        *cancellable,
5583                                     GAsyncReadyCallback  callback,
5584                                     gpointer             user_data)
5585 {
5586   GTask *task;
5587   LaunchUrisData *data;
5588
5589   task = g_task_new (appinfo, cancellable, callback, user_data);
5590   g_task_set_source_tag (task, g_win32_app_info_launch_uris_async);
5591
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);
5596
5597   g_task_run_in_thread (task, launch_uris_async_thread);
5598   g_object_unref (task);
5599 }
5600
5601 static gboolean
5602 g_win32_app_info_launch_uris_finish (GAppInfo *appinfo,
5603                                      GAsyncResult *result,
5604                                      GError **error)
5605 {
5606   g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE);
5607
5608   return g_task_propagate_boolean (G_TASK (result), error);
5609 }
5610
5611 static gboolean
5612 g_win32_app_info_should_show (GAppInfo *appinfo)
5613 {
5614   /* FIXME: This is a placeholder implementation to avoid crashes
5615    * for now. It can be made more specific to @appinfo in future. */
5616
5617   return TRUE;
5618 }
5619
5620 static gboolean
5621 g_win32_app_info_launch (GAppInfo           *appinfo,
5622                          GList              *files,  /* (element-type GFile) */
5623                          GAppLaunchContext  *launch_context,
5624                          GError            **error)
5625 {
5626   gboolean res = FALSE;
5627   gboolean do_uris;
5628   GList *objs = NULL;
5629   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5630   gboolean is_uwp;
5631
5632   do_uris = g_win32_app_info_supports_uris (appinfo);
5633
5634   while (files)
5635     {
5636       file_or_uri *obj;
5637       obj = g_new0 (file_or_uri, 1);
5638       obj->file = g_file_get_path (G_FILE (files->data));
5639
5640       if (do_uris)
5641         obj->uri = g_file_get_uri (G_FILE (files->data));
5642
5643       objs = g_list_prepend (objs, obj);
5644       files = files->next;
5645     }
5646   objs = g_list_reverse (objs);
5647
5648   is_uwp = (info->app != NULL && info->app->is_uwp);
5649
5650   res = g_win32_app_info_launch_internal (info,
5651                                           objs,
5652                                           TRUE,
5653                                           launch_context,
5654                                           is_uwp ?
5655                                           0 :
5656                                           G_SPAWN_SEARCH_PATH,
5657                                           NULL,
5658                                           error);
5659
5660   g_list_free_full (objs, free_file_or_uri);
5661
5662   return res;
5663 }
5664
5665 static const char **
5666 g_win32_app_info_get_supported_types (GAppInfo *appinfo)
5667 {
5668   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5669
5670   return (const char**) info->supported_types;
5671 }
5672
5673 GAppInfo *
5674 g_app_info_create_from_commandline (const char           *commandline,
5675                                     const char           *application_name,
5676                                     GAppInfoCreateFlags   flags,
5677                                     GError              **error)
5678 {
5679   GWin32AppInfo *info;
5680   GWin32AppInfoApplication *app;
5681   gunichar2 *app_command;
5682
5683   g_return_val_if_fail (commandline, NULL);
5684
5685   app_command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL);
5686
5687   if (app_command == NULL)
5688     return NULL;
5689
5690   info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
5691   app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
5692
5693   app->no_open_with = FALSE;
5694   app->user_specific = FALSE;
5695   app->default_app = FALSE;
5696
5697   if (application_name)
5698     {
5699       app->canonical_name = g_utf8_to_utf16 (application_name,
5700                                              -1,
5701                                              NULL,
5702                                              NULL,
5703                                              NULL);
5704       app->canonical_name_u8 = g_strdup (application_name);
5705       app->canonical_name_folded = g_utf8_casefold (application_name, -1);
5706     }
5707
5708   app_add_verb (app,
5709                 app,
5710                 L"open",
5711                 app_command,
5712                 commandline,
5713                 "open",
5714                 TRUE,
5715                 FALSE);
5716
5717   g_clear_pointer (&app_command, g_free);
5718   info->app = app;
5719   info->handler = NULL;
5720
5721   return G_APP_INFO (info);
5722 }
5723
5724 /* GAppInfo interface init */
5725
5726 static void
5727 g_win32_app_info_iface_init (GAppInfoIface *iface)
5728 {
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;
5754 }
5755
5756 GAppInfo *
5757 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
5758 {
5759   GWin32AppInfoURLSchema *scheme = NULL;
5760   char *scheme_down;
5761   GAppInfo *result;
5762   GWin32AppInfoShellVerb *shverb;
5763
5764   scheme_down = g_utf8_casefold (uri_scheme, -1);
5765
5766   if (!scheme_down)
5767     return NULL;
5768
5769   if (strcmp (scheme_down, "file") == 0)
5770     {
5771       g_free (scheme_down);
5772
5773       return NULL;
5774     }
5775
5776   gio_win32_appinfo_init (TRUE);
5777   g_mutex_lock (&gio_win32_appinfo_mutex);
5778
5779   g_set_object (&scheme, g_hash_table_lookup (urls, scheme_down));
5780   g_free (scheme_down);
5781
5782   g_mutex_unlock (&gio_win32_appinfo_mutex);
5783
5784   result = NULL;
5785
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);
5792
5793   g_clear_object (&scheme);
5794
5795   return result;
5796 }
5797
5798 GAppInfo *
5799 g_app_info_get_default_for_type (const char *content_type,
5800                                  gboolean    must_support_uris)
5801 {
5802   GWin32AppInfoFileExtension *ext = NULL;
5803   char *ext_down;
5804   GAppInfo *result;
5805   GWin32AppInfoShellVerb *shverb;
5806
5807   ext_down = g_utf8_casefold (content_type, -1);
5808
5809   if (!ext_down)
5810     return NULL;
5811
5812   gio_win32_appinfo_init (TRUE);
5813   g_mutex_lock (&gio_win32_appinfo_mutex);
5814
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));
5817   g_free (ext_down);
5818
5819   g_mutex_unlock (&gio_win32_appinfo_mutex);
5820
5821   if (ext == NULL)
5822     return NULL;
5823
5824   result = NULL;
5825
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);
5833   else
5834     {
5835       GHashTableIter iter;
5836       GWin32AppInfoHandler *handler;
5837
5838       g_hash_table_iter_init (&iter, ext->handlers);
5839
5840       while (result == NULL &&
5841              g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5842         {
5843           if (handler->verbs->len == 0)
5844             continue;
5845
5846           shverb = _verb_idx (handler->verbs, 0);
5847
5848           if (shverb->app &&
5849               (!must_support_uris ||
5850                g_win32_app_supports_uris (shverb->app)))
5851             result = g_win32_app_info_new_from_app (shverb->app, handler);
5852         }
5853     }
5854
5855   g_clear_object (&ext);
5856
5857   return result;
5858 }
5859
5860 GList *
5861 g_app_info_get_all (void)
5862 {
5863   GHashTableIter iter;
5864   gpointer value;
5865   GList *infos;
5866   GList *apps;
5867   GList *apps_i;
5868
5869   gio_win32_appinfo_init (TRUE);
5870   g_mutex_lock (&gio_win32_appinfo_mutex);
5871
5872   apps = NULL;
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)));
5876
5877   g_mutex_unlock (&gio_win32_appinfo_mutex);
5878
5879   infos = NULL;
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));
5883
5884   g_list_free_full (apps, g_object_unref);
5885
5886   return infos;
5887 }
5888
5889 GList *
5890 g_app_info_get_all_for_type (const char *content_type)
5891 {
5892   GWin32AppInfoFileExtension *ext = NULL;
5893   char *ext_down;
5894   GWin32AppInfoHandler *handler;
5895   GHashTableIter iter;
5896   GHashTable *apps = NULL;
5897   GList *result;
5898   GWin32AppInfoShellVerb *shverb;
5899
5900   ext_down = g_utf8_casefold (content_type, -1);
5901
5902   if (!ext_down)
5903     return NULL;
5904
5905   gio_win32_appinfo_init (TRUE);
5906   g_mutex_lock (&gio_win32_appinfo_mutex);
5907
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));
5910   g_free (ext_down);
5911
5912   g_mutex_unlock (&gio_win32_appinfo_mutex);
5913
5914   if (ext == NULL)
5915     return NULL;
5916
5917   result = NULL;
5918   /* Used as a set to ensure uniqueness */
5919   apps = g_hash_table_new (g_direct_hash, g_direct_equal);
5920
5921   if (ext->chosen_handler != NULL &&
5922       ext->chosen_handler->verbs->len > 0 &&
5923       (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL)
5924     {
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));
5929     }
5930
5931   g_hash_table_iter_init (&iter, ext->handlers);
5932
5933   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5934     {
5935       gsize vi;
5936
5937       for (vi = 0; vi < handler->verbs->len; vi++)
5938         {
5939           shverb = _verb_idx (handler->verbs, vi);
5940
5941           if (shverb->app == NULL ||
5942               g_hash_table_contains (apps, shverb->app))
5943             continue;
5944
5945           g_hash_table_add (apps, shverb->app);
5946           result = g_list_prepend (result,
5947                                    g_win32_app_info_new_from_app (shverb->app,
5948                                                                   handler));
5949         }
5950     }
5951
5952   g_clear_object (&ext);
5953   result = g_list_reverse (result);
5954   g_hash_table_unref (apps);
5955
5956   return result;
5957 }
5958
5959 GList *
5960 g_app_info_get_fallback_for_type (const gchar *content_type)
5961 {
5962   /* TODO: fix this once gcontenttype support is improved */
5963   return g_app_info_get_all_for_type (content_type);
5964 }
5965
5966 GList *
5967 g_app_info_get_recommended_for_type (const gchar *content_type)
5968 {
5969   /* TODO: fix this once gcontenttype support is improved */
5970   return g_app_info_get_all_for_type (content_type);
5971 }
5972
5973 void
5974 g_app_info_reset_type_associations (const char *content_type)
5975 {
5976   /* nothing to do */
5977 }