Imported Upstream version 2.67.2
[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  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Alexander Larsson <alexl@redhat.com>
20  *          Руслан Ижбулатов  <lrn1986@gmail.com>
21  */
22
23 #include "config.h"
24
25 #define COBJMACROS
26
27 #include <string.h>
28
29 #include "gcontenttype.h"
30 #include "gwin32appinfo.h"
31 #include "gappinfo.h"
32 #include "gioerror.h"
33 #include "gfile.h"
34 #include <glib/gstdio.h>
35 #include "glibintl.h"
36 #include <gio/gwin32registrykey.h>
37 #include <shlobj.h>
38 /* Contains the definitions from shlobj.h that are
39  * guarded as Windows8-or-newer and are unavailable
40  * to GLib, being only Windows7-or-newer.
41  */
42 #include "gwin32api-application-activation-manager.h"
43
44 #include <windows.h>
45 /* For SHLoadIndirectString() */
46 #include <shlwapi.h>
47
48 #include <glib/gstdioprivate.h>
49 #include "giowin32-priv.h"
50 #include "glib-private.h"
51
52 /* We need to watch 8 places:
53  * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations
54  *    (anything below that key)
55  *    On change: re-enumerate subkeys, read their values.
56  * 1) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts
57  *    (anything below that key)
58  *    On change: re-enumerate subkeys
59  * 2) HKEY_CURRENT_USER\\Software\\Clients (anything below that key)
60  *    On change: re-read the whole hierarchy of handlers
61  * 3) HKEY_LOCAL_MACHINE\\Software\\Clients (anything below that key)
62  *    On change: re-read the whole hierarchy of handlers
63  * 4) HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications (values of that key)
64  *    On change: re-read the value list of registered applications
65  * 5) HKEY_CURRENT_USER\\Software\\RegisteredApplications (values of that key)
66  *    On change: re-read the value list of registered applications
67  * 6) HKEY_CLASSES_ROOT\\Applications (anything below that key)
68  *    On change: re-read the whole hierarchy of apps
69  * 7) HKEY_CLASSES_ROOT (only its subkeys)
70  *    On change: re-enumerate subkeys, try to filter out wrong names.
71  *
72  *
73  * About verbs. A registry key (the name of that key is known as ProgID)
74  * can contain a "shell" subkey, which can then contain a number of verb
75  * subkeys (the most common being the "open" verb), and each of these
76  * contains a "command" subkey, which has a default string value that
77  * is the command to be run.
78  * Most ProgIDs are in HKEY_CLASSES_ROOT, but some are nested deeper in
79  * the registry (such as HKEY_CURRENT_USER\\Software\\<softwarename>).
80  *
81  * Verb selection works like this (according to https://docs.microsoft.com/en-us/windows/win32/shell/context ):
82  * 1) If "open" verb is available, that verb is used.
83  * 2) If the Shell subkey has a default string value, and if a verb subkey
84  *    with that name exists, that verb is used.
85  * 3) The first subkey found in the list of verb subkeys is used.
86  * 4) The "openwith" verb is used
87  *
88  * Testing suggests that Windows never reaches the point 4 in any realistic
89  * circumstances. If a "command" subkey is missing for a verb, or if it has
90  * an empty string as its default value, the app launch fails
91  * (the "openwith" verb is not used, even if it's present).
92  * If the command is present, but is not valid (runs nonexisting executable,
93  * for example), then other verbs are not checked.
94  * It seems that when the documentation said "openwith verb", it meant
95  * that Windows invokes the default "Open with..." dialog (it does not
96  * look at the "openwith" verb subkey, even if it's there).
97  * If a verb subkey that is supposed to be used is present, but it lacks
98  * a command subkey, an error message is shown and nothing else happens.
99  */
100
101 #define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index))
102
103 #define _lookup_by_verb(array, verb, dst, itemtype) do { \
104   gsize _index; \
105   itemtype *_v; \
106   for (_index = 0; array && _index < array->len; _index++) \
107     { \
108       _v = (itemtype *) g_ptr_array_index (array, _index); \
109       if (_wcsicmp (_v->verb_name, (verb)) == 0) \
110         { \
111           *(dst) = _v; \
112           break; \
113         } \
114     } \
115   if (array == NULL || _index >= array->len) \
116     *(dst) = NULL; \
117 } while (0)
118
119 #define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb)
120
121 /* Because with subcommands a verb would have
122  * a name like "foo\\bar", but the key its command
123  * should be looked for is "shell\\foo\\shell\\bar\\command"
124  */
125 typedef struct _reg_verb {
126   gunichar2 *name;
127   gunichar2 *shellpath;
128 } reg_verb;
129
130 typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema;
131 typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension;
132 typedef struct _GWin32AppInfoShellVerb GWin32AppInfoShellVerb;
133 typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler;
134 typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication;
135
136 typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass;
137 typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass;
138 typedef struct _GWin32AppInfoShellVerbClass GWin32AppInfoShellVerbClass;
139 typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass;
140 typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass;
141
142 struct _GWin32AppInfoURLSchemaClass
143 {
144   GObjectClass parent_class;
145 };
146
147 struct _GWin32AppInfoFileExtensionClass
148 {
149   GObjectClass parent_class;
150 };
151
152 struct _GWin32AppInfoHandlerClass
153 {
154   GObjectClass parent_class;
155 };
156
157 struct _GWin32AppInfoApplicationClass
158 {
159   GObjectClass parent_class;
160 };
161
162 struct _GWin32AppInfoShellVerbClass
163 {
164   GObjectClass parent_class;
165 };
166
167 struct _GWin32AppInfoURLSchema {
168   GObject parent_instance;
169
170   /* url schema (stuff before ':') */
171   gunichar2 *schema;
172
173   /* url schema (stuff before ':'), in UTF-8 */
174   gchar *schema_u8;
175
176   /* url schema (stuff before ':'), in UTF-8, folded */
177   gchar *schema_u8_folded;
178
179   /* Handler currently selected for this schema. Can be NULL. */
180   GWin32AppInfoHandler *chosen_handler;
181
182   /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema.
183    * Includes the chosen handler, if any.
184    */
185   GHashTable *handlers;
186 };
187
188 struct _GWin32AppInfoHandler {
189   GObject parent_instance;
190
191   /* Usually a class name in HKCR */
192   gunichar2 *handler_id;
193
194   /* Registry object obtained by opening @handler_id.
195    * Can be used to watch this handler.
196    * May be %NULL (for fake handlers that we made up).
197    */
198   GWin32RegistryKey *key;
199
200   /* @handler_id, in UTF-8, folded */
201   gchar *handler_id_folded;
202
203   /* Icon of the application for this handler */
204   GIcon *icon;
205
206   /* Verbs that this handler supports */
207   GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
208
209   /* AppUserModelID for a UWP application. When this is not NULL,
210    * this handler launches a UWP application.
211    * UWP applications are launched using a COM interface and have no commandlines,
212    * and the verbs will reflect that too.
213    */
214   gunichar2 *uwp_aumid;
215 };
216
217 struct _GWin32AppInfoShellVerb {
218   GObject parent_instance;
219
220   /* The verb that is used to invoke this handler. */
221   gunichar2 *verb_name;
222
223   /* User-friendly (localized) verb name. */
224   gchar *verb_displayname;
225
226   /* %TRUE if this verb is for a UWP app.
227    * It means that @command, @executable and @dll_function are %NULL.
228    */
229   gboolean is_uwp;
230
231   /* shell/verb/command */
232   gunichar2 *command;
233
234   /* Same as @command, but in UTF-8 */
235   gchar *command_utf8;
236
237   /* Executable of the program (UTF-8) */
238   gchar *executable;
239
240   /* Executable of the program (for matching, in folded form; UTF-8) */
241   gchar *executable_folded;
242
243   /* Pointer to a location within @executable */
244   gchar *executable_basename;
245
246   /* If not NULL, then @executable and its derived fields contain the name
247    * of a DLL file (without the name of the function that rundll32.exe should
248    * invoke), and this field contains the name of the function to be invoked.
249    * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'.
250    */
251   gchar *dll_function;
252
253   /* The application that is linked to this verb. */
254   GWin32AppInfoApplication *app;
255 };
256
257 struct _GWin32AppInfoFileExtension {
258   GObject parent_instance;
259
260   /* File extension (with leading '.') */
261   gunichar2 *extension;
262
263   /* File extension (with leading '.'), in UTF-8 */
264   gchar *extension_u8;
265
266   /* handler currently selected for this extension. Can be NULL. */
267   GWin32AppInfoHandler *chosen_handler;
268
269   /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension.
270    * Includes the chosen handler, if any.
271    */
272   GHashTable *handlers;
273 };
274
275 struct _GWin32AppInfoApplication {
276   GObject parent_instance;
277
278   /* Canonical name (used for key names).
279    * For applications tracked by id this is the root registry
280    * key path for the application.
281    * For applications tracked by executable name this is the
282    * basename of the executable.
283    * For UWP apps this is the AppUserModelID.
284    * For fake applications this is the full filename of the
285    * executable (as far as it can be inferred from a command line,
286    * meaning that it can also be a basename, if that's
287    * all that a commandline happen to give us).
288    */
289   gunichar2 *canonical_name;
290
291   /* @canonical_name, in UTF-8 */
292   gchar *canonical_name_u8;
293
294   /* @canonical_name, in UTF-8, folded */
295   gchar *canonical_name_folded;
296
297   /* Human-readable name in English. Can be NULL */
298   gunichar2 *pretty_name;
299
300   /* Human-readable name in English, UTF-8. Can be NULL */
301   gchar *pretty_name_u8;
302
303   /* Human-readable name in user's language. Can be NULL  */
304   gunichar2 *localized_pretty_name;
305
306   /* Human-readable name in user's language, UTF-8. Can be NULL  */
307   gchar *localized_pretty_name_u8;
308
309   /* Description, could be in user's language. Can be NULL */
310   gunichar2 *description;
311
312   /* Description, could be in user's language, UTF-8. Can be NULL */
313   gchar *description_u8;
314
315   /* Verbs that this application supports */
316   GPtrArray *verbs; /* of GWin32AppInfoShellVerb */
317
318   /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema,
319    * UTF-8, folded) -> to a GWin32AppInfoHandler
320    * Schema can be used as a key in the urls hashmap.
321    */
322   GHashTable *supported_urls;
323
324   /* Explicitly supported extensions, hashmap from map-owned gchar ptr
325    * (.extension, UTF-8, folded) -> to a GWin32AppInfoHandler
326    * Extension can be used as a key in the extensions hashmap.
327    */
328   GHashTable *supported_exts;
329
330   /* Icon of the application (remember, handler can have its own icon too) */
331   GIcon *icon;
332
333   /* Set to TRUE to prevent this app from appearing in lists of apps for
334    * opening files. This will not prevent it from appearing in lists of apps
335    * just for running, or lists of apps for opening exts/urls for which this
336    * app reports explicit support.
337    */
338   gboolean no_open_with;
339
340   /* Set to TRUE for applications from HKEY_CURRENT_USER.
341    * Give them priority over applications from HKEY_LOCAL_MACHINE, when all
342    * other things are equal.
343    */
344   gboolean user_specific;
345
346   /* Set to TRUE for applications that are machine-wide defaults (i.e. default
347    * browser) */
348   gboolean default_app;
349
350   /* Set to TRUE for UWP applications */
351   gboolean is_uwp;
352 };
353
354 #define G_TYPE_WIN32_APPINFO_URL_SCHEMA           (g_win32_appinfo_url_schema_get_type ())
355 #define G_WIN32_APPINFO_URL_SCHEMA(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_URL_SCHEMA, GWin32AppInfoURLSchema))
356
357 #define G_TYPE_WIN32_APPINFO_FILE_EXTENSION       (g_win32_appinfo_file_extension_get_type ())
358 #define G_WIN32_APPINFO_FILE_EXTENSION(obj)       (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_FILE_EXTENSION, GWin32AppInfoFileExtension))
359
360 #define G_TYPE_WIN32_APPINFO_HANDLER              (g_win32_appinfo_handler_get_type ())
361 #define G_WIN32_APPINFO_HANDLER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_HANDLER, GWin32AppInfoHandler))
362
363 #define G_TYPE_WIN32_APPINFO_APPLICATION          (g_win32_appinfo_application_get_type ())
364 #define G_WIN32_APPINFO_APPLICATION(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_APPLICATION, GWin32AppInfoApplication))
365
366 #define G_TYPE_WIN32_APPINFO_SHELL_VERB           (g_win32_appinfo_shell_verb_get_type ())
367 #define G_WIN32_APPINFO_SHELL_VERB(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_SHELL_VERB, GWin32AppInfoShellVerb))
368
369 GType g_win32_appinfo_url_schema_get_type (void) G_GNUC_CONST;
370 GType g_win32_appinfo_file_extension_get_type (void) G_GNUC_CONST;
371 GType g_win32_appinfo_shell_verb_get_type (void) G_GNUC_CONST;
372 GType g_win32_appinfo_handler_get_type (void) G_GNUC_CONST;
373 GType g_win32_appinfo_application_get_type (void) G_GNUC_CONST;
374
375 G_DEFINE_TYPE (GWin32AppInfoURLSchema, g_win32_appinfo_url_schema, G_TYPE_OBJECT)
376 G_DEFINE_TYPE (GWin32AppInfoFileExtension, g_win32_appinfo_file_extension, G_TYPE_OBJECT)
377 G_DEFINE_TYPE (GWin32AppInfoShellVerb, g_win32_appinfo_shell_verb, G_TYPE_OBJECT)
378 G_DEFINE_TYPE (GWin32AppInfoHandler, g_win32_appinfo_handler, G_TYPE_OBJECT)
379 G_DEFINE_TYPE (GWin32AppInfoApplication, g_win32_appinfo_application, G_TYPE_OBJECT)
380
381 static void
382 g_win32_appinfo_url_schema_dispose (GObject *object)
383 {
384   GWin32AppInfoURLSchema *url = G_WIN32_APPINFO_URL_SCHEMA (object);
385
386   g_clear_pointer (&url->schema, g_free);
387   g_clear_pointer (&url->schema_u8, g_free);
388   g_clear_pointer (&url->schema_u8_folded, g_free);
389   g_clear_object (&url->chosen_handler);
390   g_clear_pointer (&url->handlers, g_hash_table_destroy);
391   G_OBJECT_CLASS (g_win32_appinfo_url_schema_parent_class)->dispose (object);
392 }
393
394
395 static void
396 g_win32_appinfo_handler_dispose (GObject *object)
397 {
398   GWin32AppInfoHandler *handler = G_WIN32_APPINFO_HANDLER (object);
399
400   g_clear_pointer (&handler->handler_id, g_free);
401   g_clear_pointer (&handler->handler_id_folded, g_free);
402   g_clear_object (&handler->key);
403   g_clear_object (&handler->icon);
404   g_clear_pointer (&handler->verbs, g_ptr_array_unref);
405   g_clear_pointer (&handler->uwp_aumid, g_free);
406   G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object);
407 }
408
409 static void
410 g_win32_appinfo_file_extension_dispose (GObject *object)
411 {
412   GWin32AppInfoFileExtension *ext = G_WIN32_APPINFO_FILE_EXTENSION (object);
413
414   g_clear_pointer (&ext->extension, g_free);
415   g_clear_pointer (&ext->extension_u8, g_free);
416   g_clear_object (&ext->chosen_handler);
417   g_clear_pointer (&ext->handlers, g_hash_table_destroy);
418   G_OBJECT_CLASS (g_win32_appinfo_file_extension_parent_class)->dispose (object);
419 }
420
421 static void
422 g_win32_appinfo_shell_verb_dispose (GObject *object)
423 {
424   GWin32AppInfoShellVerb *shverb = G_WIN32_APPINFO_SHELL_VERB (object);
425
426   g_clear_pointer (&shverb->verb_name, g_free);
427   g_clear_pointer (&shverb->verb_displayname, g_free);
428   g_clear_pointer (&shverb->command, g_free);
429   g_clear_pointer (&shverb->command_utf8, g_free);
430   g_clear_pointer (&shverb->executable_folded, g_free);
431   g_clear_pointer (&shverb->executable, g_free);
432   g_clear_pointer (&shverb->dll_function, g_free);
433   g_clear_object (&shverb->app);
434   G_OBJECT_CLASS (g_win32_appinfo_shell_verb_parent_class)->dispose (object);
435 }
436
437 static void
438 g_win32_appinfo_application_dispose (GObject *object)
439 {
440   GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object);
441
442   g_clear_pointer (&app->canonical_name_u8, g_free);
443   g_clear_pointer (&app->canonical_name_folded, g_free);
444   g_clear_pointer (&app->canonical_name, g_free);
445   g_clear_pointer (&app->pretty_name, g_free);
446   g_clear_pointer (&app->localized_pretty_name, g_free);
447   g_clear_pointer (&app->description, g_free);
448   g_clear_pointer (&app->pretty_name_u8, g_free);
449   g_clear_pointer (&app->localized_pretty_name_u8, g_free);
450   g_clear_pointer (&app->description_u8, g_free);
451   g_clear_pointer (&app->supported_urls, g_hash_table_destroy);
452   g_clear_pointer (&app->supported_exts, g_hash_table_destroy);
453   g_clear_object (&app->icon);
454   g_clear_pointer (&app->verbs, g_ptr_array_unref);
455   G_OBJECT_CLASS (g_win32_appinfo_application_parent_class)->dispose (object);
456 }
457
458 static const gchar *
459 g_win32_appinfo_application_get_some_name (GWin32AppInfoApplication *app)
460 {
461   if (app->localized_pretty_name_u8)
462     return app->localized_pretty_name_u8;
463
464   if (app->pretty_name_u8)
465     return app->pretty_name_u8;
466
467   return app->canonical_name_u8;
468 }
469
470 static void
471 g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass)
472 {
473   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
474
475   gobject_class->dispose = g_win32_appinfo_url_schema_dispose;
476 }
477
478 static void
479 g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klass)
480 {
481   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
482
483   gobject_class->dispose = g_win32_appinfo_file_extension_dispose;
484 }
485
486 static void
487 g_win32_appinfo_shell_verb_class_init (GWin32AppInfoShellVerbClass *klass)
488 {
489   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
490
491   gobject_class->dispose = g_win32_appinfo_shell_verb_dispose;
492 }
493
494 static void
495 g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass)
496 {
497   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
498
499   gobject_class->dispose = g_win32_appinfo_handler_dispose;
500 }
501
502 static void
503 g_win32_appinfo_application_class_init (GWin32AppInfoApplicationClass *klass)
504 {
505   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
506
507   gobject_class->dispose = g_win32_appinfo_application_dispose;
508 }
509
510 static void
511 g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self)
512 {
513   self->handlers = g_hash_table_new_full (g_str_hash,
514                                           g_str_equal,
515                                           g_free,
516                                           g_object_unref);
517 }
518
519 static void
520 g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self)
521 {
522 }
523
524 static void
525 g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self)
526 {
527   self->handlers = g_hash_table_new_full (g_str_hash,
528                                           g_str_equal,
529                                           g_free,
530                                           g_object_unref);
531 }
532
533 static void
534 g_win32_appinfo_handler_init (GWin32AppInfoHandler *self)
535 {
536   self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
537 }
538
539 static void
540 g_win32_appinfo_application_init (GWin32AppInfoApplication *self)
541 {
542   self->supported_urls = g_hash_table_new_full (g_str_hash,
543                                                 g_str_equal,
544                                                 g_free,
545                                                 g_object_unref);
546   self->supported_exts = g_hash_table_new_full (g_str_hash,
547                                                 g_str_equal,
548                                                 g_free,
549                                                 g_object_unref);
550   self->verbs = g_ptr_array_new_with_free_func (g_object_unref);
551 }
552
553 /* The AppInfo threadpool that does asynchronous AppInfo tree rebuilds */
554 static GThreadPool *gio_win32_appinfo_threadpool;
555
556 /* This mutex is held by a thread that reads or writes the AppInfo tree.
557  * (tree object references can be obtained and later read without
558  *  holding this mutex, since objects are practically immutable).
559  */
560 static GMutex gio_win32_appinfo_mutex;
561
562 /* Any thread wanting to access AppInfo can wait on this condition */
563 static GCond gio_win32_appinfo_cond;
564
565 /* Increased to indicate that AppInfo tree does needs to be rebuilt.
566  * AppInfo thread checks this to see if it needs to
567  * do a tree re-build. If the value changes during a rebuild,
568  * another rebuild is triggered after that.
569  * Other threads check this to see if they need
570  * to wait for a tree re-build to finish.
571  */
572 static gint gio_win32_appinfo_update_counter = 0;
573
574 /* Map of owned ".ext" (with '.', UTF-8, folded)
575  * to GWin32AppInfoFileExtension ptr
576  */
577 static GHashTable *extensions = NULL;
578
579 /* Map of owned "schema" (without ':', UTF-8, folded)
580  * to GWin32AppInfoURLSchema ptr
581  */
582 static GHashTable *urls = NULL;
583
584 /* Map of owned "appID" (UTF-8, folded) to
585  * a GWin32AppInfoApplication
586  */
587 static GHashTable *apps_by_id = NULL;
588
589 /* Map of owned "app.exe" (UTF-8, folded) to
590  * a GWin32AppInfoApplication.
591  * This map and its values are separate from apps_by_id. The fact that an app
592  * with known ID has the same executable [base]name as an app in this map does
593  * not mean that they are the same application.
594  */
595 static GHashTable *apps_by_exe = NULL;
596
597 /* Map of owned "path:\to\app.exe" (UTF-8, folded) to
598  * a GWin32AppInfoApplication.
599  * The app objects in this map are fake - they are linked to
600  * handlers that do not have any apps associated with them.
601  */
602 static GHashTable *fake_apps = NULL;
603
604 /* Map of owned "handler id" (UTF-8, folded)
605  * to a GWin32AppInfoHandler
606  */
607 static GHashTable *handlers = NULL;
608
609 /* Temporary (only exists while the registry is being scanned) table
610  * that maps GWin32RegistryKey objects (keeps a ref) to owned AUMId wchar strings.
611  */
612 static GHashTable *uwp_handler_table = NULL;
613
614 /* Watch this whole subtree */
615 static GWin32RegistryKey *url_associations_key;
616
617 /* Watch this whole subtree */
618 static GWin32RegistryKey *file_exts_key;
619
620 /* Watch this whole subtree */
621 static GWin32RegistryKey *user_clients_key;
622
623 /* Watch this whole subtree */
624 static GWin32RegistryKey *system_clients_key;
625
626 /* Watch this key */
627 static GWin32RegistryKey *user_registered_apps_key;
628
629 /* Watch this key */
630 static GWin32RegistryKey *system_registered_apps_key;
631
632 /* Watch this whole subtree */
633 static GWin32RegistryKey *applications_key;
634
635 /* Watch this key */
636 static GWin32RegistryKey *classes_root_key;
637
638 #define URL_ASSOCIATIONS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
639 #define USER_CHOICE L"\\UserChoice"
640 #define OPEN_WITH_PROGIDS L"\\OpenWithProgids"
641 #define FILE_EXTS L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\"
642 #define HKCR L"HKEY_CLASSES_ROOT\\"
643 #define HKCU L"HKEY_CURRENT_USER\\"
644 #define HKLM L"HKEY_LOCAL_MACHINE\\"
645 #define REG_PATH_MAX 256
646 #define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2))
647
648 /* for g_wcsdup(),
649  *     _g_win32_extract_executable(),
650  *     _g_win32_fixup_broken_microsoft_rundll_commandline()
651  */
652 #include "giowin32-private.c"
653
654 /* for g_win32_package_parser_enum_packages() */
655 #include "gwin32packageparser.h"
656
657 static void
658 read_handler_icon (GWin32RegistryKey  *key,
659                    GIcon             **icon_out)
660 {
661   GWin32RegistryKey *icon_key;
662   GWin32RegistryValueType default_type;
663   gchar *default_value;
664
665   g_assert (icon_out);
666
667   *icon_out = NULL;
668
669   icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL);
670
671   if (icon_key == NULL)
672     return;
673
674   if (g_win32_registry_key_get_value (icon_key,
675                                       NULL,
676                                       TRUE,
677                                       "",
678                                       &default_type,
679                                       (gpointer *) &default_value,
680                                       NULL,
681                                       NULL))
682     {
683       /* TODO: For UWP handlers this string is usually in @{...} form,
684        * see grab_registry_string() below. Right now this
685        * string is read as-is and the icon would silently fail to load.
686        * Also, right now handler icon is not used anywhere
687        * (only app icon is used).
688        */
689       if (default_type == G_WIN32_REGISTRY_VALUE_STR &&
690           default_value[0] != '\0')
691         *icon_out = g_themed_icon_new (default_value);
692
693       g_clear_pointer (&default_value, g_free);
694     }
695
696   g_object_unref (icon_key);
697 }
698
699 static void
700 reg_verb_free (gpointer p)
701 {
702   if (p == NULL)
703     return;
704
705   g_free (((reg_verb *) p)->name);
706   g_free (((reg_verb *) p)->shellpath);
707   g_free (p);
708 }
709
710 #define is_open(x) ( \
711   ((x)[0] == L'o' || (x)[0] == L'O') && \
712   ((x)[1] == L'p' || (x)[1] == L'P') && \
713   ((x)[2] == L'e' || (x)[2] == L'E') && \
714   ((x)[3] == L'n' || (x)[3] == L'N') && \
715   ((x)[4] == L'\0') \
716 )
717
718 /* default verb (if any) comes first,
719  * then "open", then the rest of the verbs
720  * are sorted alphabetically
721  */
722 static gint
723 compare_verbs (gconstpointer a,
724                gconstpointer b,
725                gpointer user_data)
726 {
727   const reg_verb *ca = (const reg_verb *) a;
728   const reg_verb *cb = (const reg_verb *) b;
729   const gunichar2 *def = (const gunichar2 *) user_data;
730   gboolean is_open_ca;
731   gboolean is_open_cb;
732
733   if (def != NULL)
734     {
735       if (_wcsicmp (ca->name, def) == 0)
736         return -1;
737       else if (_wcsicmp (cb->name, def) == 0)
738         return 1;
739     }
740
741   is_open_ca = is_open (ca->name);
742   is_open_cb = is_open (cb->name);
743
744   if (is_open_ca && !is_open_cb)
745     return -1;
746   else if (is_open_ca && !is_open_cb)
747     return 1;
748
749   return _wcsicmp (ca->name, cb->name);
750 }
751
752 static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) G_GNUC_NULL_TERMINATED;
753 static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_list components);
754
755 static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED;
756
757 /* Called by process_verbs_commands.
758  * @verb is a verb name
759  * @command_line is the commandline of that verb
760  * @command_line_utf8 is the UTF-8 version of @command_line
761  * @verb_displayname is the prettier display name of the verb (might be NULL)
762  * @verb_is_preferred is TRUE if the verb is the preferred one
763  * @invent_new_verb_name is TRUE when the verb should be added
764  *                       even if a verb with such
765  *                       name already exists (in which case
766  *                       a new name is invented), unless
767  *                       the existing verb runs exactly the same
768  *                       commandline.
769  */
770 typedef void (*verb_command_func) (gpointer         handler_data1,
771                                    gpointer         handler_data2,
772                                    const gunichar2 *verb,
773                                    const gunichar2 *command_line,
774                                    const gchar     *command_line_utf8,
775                                    const gchar     *verb_displayname,
776                                    gboolean         verb_is_preferred,
777                                    gboolean         invent_new_verb_name);
778
779 static gunichar2 *                 decide_which_id_to_use (const gunichar2    *program_id,
780                                                            GWin32RegistryKey **return_key,
781                                                            gchar             **return_handler_id_u8,
782                                                            gchar             **return_handler_id_u8_folded,
783                                                            gunichar2         **return_uwp_aumid);
784
785 static GWin32AppInfoURLSchema *    get_schema_object      (const gunichar2 *schema,
786                                                            const gchar     *schema_u8,
787                                                            const gchar     *schema_u8_folded);
788
789 static GWin32AppInfoHandler *      get_handler_object     (const gchar       *handler_id_u8_folded,
790                                                            GWin32RegistryKey *handler_key,
791                                                            const gunichar2   *handler_id,
792                                                            const gunichar2   *uwp_aumid);
793
794 static GWin32AppInfoFileExtension *get_ext_object         (const gunichar2 *ext,
795                                                            const gchar     *ext_u8,
796                                                            const gchar     *ext_u8_folded);
797
798
799 static void                        process_verbs_commands (GList             *verbs,
800                                                            const reg_verb    *preferred_verb,
801                                                            const gunichar2   *path_to_progid,
802                                                            const gunichar2   *progid,
803                                                            gboolean           autoprefer_first_verb,
804                                                            verb_command_func  handler,
805                                                            gpointer           handler_data1,
806                                                            gpointer           handler_data2);
807
808 static void                        handler_add_verb       (gpointer           handler_data1,
809                                                            gpointer           handler_data2,
810                                                            const gunichar2   *verb,
811                                                            const gunichar2   *command_line,
812                                                            const gchar       *command_line_utf8,
813                                                            const gchar       *verb_displayname,
814                                                            gboolean           verb_is_preferred,
815                                                            gboolean           invent_new_verb_name);
816
817 static void                        process_uwp_verbs      (GList                    *verbs,
818                                                            const reg_verb           *preferred_verb,
819                                                            const gunichar2          *path_to_progid,
820                                                            const gunichar2          *progid,
821                                                            gboolean                  autoprefer_first_verb,
822                                                            GWin32AppInfoHandler     *handler_rec,
823                                                            GWin32AppInfoApplication *app);
824
825 static void                        uwp_handler_add_verb   (GWin32AppInfoHandler     *handler_rec,
826                                                            GWin32AppInfoApplication *app,
827                                                            const gunichar2          *verb,
828                                                            const gchar              *verb_displayname,
829                                                            gboolean                  verb_is_preferred);
830
831 /* output_size is in *bytes*, not gunichar2s! */
832 static gboolean
833 build_registry_path (gunichar2 *output, gsize output_size, ...)
834 {
835   va_list ap;
836   gboolean result;
837
838   va_start (ap, output_size);
839
840   result = build_registry_pathv (output, output_size, ap);
841
842   va_end (ap);
843
844   return result;
845 }
846
847 /* output_size is in *bytes*, not gunichar2s! */
848 static gboolean
849 build_registry_pathv (gunichar2 *output, gsize output_size, va_list components)
850 {
851   va_list lentest;
852   gunichar2 *p;
853   gunichar2 *component;
854   gsize length;
855
856   if (output == NULL)
857     return FALSE;
858
859   G_VA_COPY (lentest, components);
860
861   for (length = 0, component = va_arg (lentest, gunichar2 *);
862        component != NULL;
863        component = va_arg (lentest, gunichar2 *))
864     {
865       length += wcslen (component);
866     }
867
868   va_end (lentest);
869
870   if ((length >= REG_PATH_MAX_SIZE) ||
871       (length * sizeof (gunichar2) >= output_size))
872     return FALSE;
873
874   output[0] = L'\0';
875
876   for (p = output, component = va_arg (components, gunichar2 *);
877        component != NULL;
878        component = va_arg (components, gunichar2 *))
879     {
880       length = wcslen (component);
881       wcscat (p, component);
882       p += length;
883     }
884
885   return TRUE;
886 }
887
888
889 static GWin32RegistryKey *
890 _g_win32_registry_key_build_and_new_w (GError **error, ...)
891 {
892   va_list ap;
893   gunichar2 key_path[REG_PATH_MAX_SIZE + 1];
894   GWin32RegistryKey *key;
895
896   va_start (ap, error);
897
898   key = NULL;
899
900   if (build_registry_pathv (key_path, sizeof (key_path), ap))
901     key = g_win32_registry_key_new_w (key_path, error);
902
903   va_end (ap);
904
905   return key;
906 }
907
908 /* Gets the list of shell verbs (a GList of reg_verb, put into @verbs)
909  * from the @program_id_key.
910  * If one of the verbs should be preferred,
911  * a pointer to this verb (in the GList) will be
912  * put into @preferred_verb.
913  * Does not automatically assume that the first verb
914  * is preferred (when no other preferences exist).
915  * @verbname_prefix is prefixed to the name of the verb
916  * (this is used for subcommands) and is initially an
917  * empty string.
918  * @verbshell_prefix is the subkey of @program_id_key
919  * that contains the verbs. It is "Shell" initially,
920  * but grows with recursive invocations (for subcommands).
921  * @is_uwp points to a boolean, which
922  * indicates whether the function is being called for a UWP app.
923  * It might be switched from %TRUE to %FALSE on return,
924  * if the application turns out to not to be UWP on closer inspection.
925  * If the application is already known not to be UWP before the
926  * call, this pointer can be %NULL instead.
927  * Returns TRUE on success, FALSE on failure.
928  */
929 static gboolean
930 get_verbs (GWin32RegistryKey  *program_id_key,
931            const reg_verb    **preferred_verb,
932            GList             **verbs,
933            const gunichar2    *verbname_prefix,
934            const gunichar2    *verbshell_prefix,
935            gboolean           *is_uwp)
936 {
937   GWin32RegistrySubkeyIter iter;
938   GWin32RegistryKey *key;
939   GWin32RegistryValueType val_type;
940   gunichar2 *default_verb;
941   gsize verbshell_prefix_len;
942   gsize verbname_prefix_len;
943   GList *i;
944
945   g_assert (program_id_key && verbs && preferred_verb);
946
947   *verbs = NULL;
948   *preferred_verb = NULL;
949
950   key = g_win32_registry_key_get_child_w (program_id_key,
951                                           verbshell_prefix,
952                                           NULL);
953
954   if (key == NULL)
955     return FALSE;
956
957   if (!g_win32_registry_subkey_iter_init (&iter, key, NULL))
958     {
959       g_object_unref (key);
960
961       return FALSE;
962     }
963
964   verbshell_prefix_len = g_utf16_len (verbshell_prefix);
965   verbname_prefix_len = g_utf16_len (verbname_prefix);
966
967   while (g_win32_registry_subkey_iter_next (&iter, TRUE, NULL))
968     {
969       const gunichar2 *name;
970       gsize name_len;
971       GWin32RegistryKey *subkey;
972       gboolean has_subcommands;
973       const reg_verb *tmp;
974       GWin32RegistryValueType subc_type;
975       reg_verb *rverb;
976       const gunichar2 *shell = L"Shell";
977       const gsize shell_len = g_utf16_len (shell);
978
979       if (!g_win32_registry_subkey_iter_get_name_w (&iter, &name, &name_len, NULL))
980         continue;
981
982       subkey = g_win32_registry_key_get_child_w (key,
983                                                  name,
984                                                  NULL);
985
986       g_assert (subkey != NULL);
987       /* The key we're looking at is "<some_root>/Shell/<this_key>",
988        * where "Shell" is verbshell_prefix.
989        * If it has a value named 'Subcommands' (doesn't matter what its data is),
990        * it means that this key has its own Shell subkey, the subkeys
991        * of which are shell commands (i.e. <some_root>/Shell/<this_key>/Shell/<some_other_keys>).
992        * To handle that, create new, extended nameprefix and shellprefix,
993        * and call the function recursively.
994        * name prefix "" -> "<this_key_name>\\"
995        * shell prefix "Shell" -> "Shell\\<this_key_name>\\Shell"
996        * The root, program_id_key, remains the same in all invocations.
997        * Essentially, we're flattening the command tree into a list.
998        */
999       has_subcommands = FALSE;
1000       if ((is_uwp == NULL || !(*is_uwp)) && /* Assume UWP apps don't have subcommands */
1001           g_win32_registry_key_get_value_w (subkey,
1002                                             NULL,
1003                                             TRUE,
1004                                             L"Subcommands",
1005                                             &subc_type,
1006                                             NULL,
1007                                             NULL,
1008                                             NULL) &&
1009           subc_type == G_WIN32_REGISTRY_VALUE_STR)
1010         {
1011           gboolean dummy = FALSE;
1012           gunichar2 *new_nameprefix = g_new (gunichar2, verbname_prefix_len + name_len + 1 + 1);
1013           gunichar2 *new_shellprefix = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1);
1014           memcpy (&new_shellprefix[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
1015           new_shellprefix[verbshell_prefix_len] = L'\\';
1016           memcpy (&new_shellprefix[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
1017           new_shellprefix[verbshell_prefix_len + 1 + name_len] = L'\\';
1018           memcpy (&new_shellprefix[verbshell_prefix_len + 1 + name_len + 1], shell, shell_len * sizeof (gunichar2));
1019           new_shellprefix[verbshell_prefix_len + 1 + name_len + 1 + shell_len] = 0;
1020
1021           memcpy (&new_nameprefix[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
1022           memcpy (&new_nameprefix[verbname_prefix_len], name, (name_len) * sizeof (gunichar2));
1023           new_nameprefix[verbname_prefix_len + name_len] = L'\\';
1024           new_nameprefix[verbname_prefix_len + name_len + 1] = 0;
1025           has_subcommands = get_verbs (program_id_key, &tmp, verbs, new_nameprefix, new_shellprefix, &dummy);
1026           g_free (new_shellprefix);
1027           g_free (new_nameprefix);
1028         }
1029
1030       /* Presence of subcommands means that this key itself is not a command-key */
1031       if (has_subcommands)
1032         {
1033           g_clear_object (&subkey);
1034           continue;
1035         }
1036
1037       if (is_uwp != NULL && *is_uwp &&
1038           !g_win32_registry_key_get_value_w (subkey,
1039                                              NULL,
1040                                              TRUE,
1041                                              L"ActivatableClassId",
1042                                              &subc_type,
1043                                              NULL,
1044                                              NULL,
1045                                              NULL))
1046         {
1047           /* We expected a UWP app, but it lacks ActivatableClassId
1048            * on a verb, which means that it does not behave like
1049            * a UWP app should (msedge being an example - it's UWP,
1050            * but has its own launchable exe file and a simple ID),
1051            * so we have to treat it like a normal app.
1052            */
1053            *is_uwp = FALSE;
1054         }
1055
1056       g_clear_object (&subkey);
1057
1058       /* We don't look at the command sub-key and its value (the actual command line) here.
1059        * We save the registry path instead, and use it later in process_verbs_commands().
1060        * The name of the verb is also saved.
1061        * verbname_prefix is prefixed to the verb name (it's either an empty string
1062        * or already ends with a '\\', so no extra separators needed).
1063        * verbshell_prefix is prefixed to the verb key path (this one needs a separator,
1064        * because it never has one - all verbshell prefixes end with "Shell", not "Shell\\")
1065        */
1066       rverb = g_new0 (reg_verb, 1);
1067       rverb->name = g_new (gunichar2, verbname_prefix_len + name_len + 1);
1068       memcpy (&rverb->name[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2));
1069       memcpy (&rverb->name[verbname_prefix_len], name, name_len * sizeof (gunichar2));
1070       rverb->name[verbname_prefix_len + name_len] = 0;
1071       rverb->shellpath = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1);
1072       memcpy (&rverb->shellpath[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2));
1073       memcpy (&rverb->shellpath[verbshell_prefix_len], L"\\", sizeof (gunichar2));
1074       memcpy (&rverb->shellpath[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2));
1075       rverb->shellpath[verbshell_prefix_len + 1 + name_len] = 0;
1076       *verbs = g_list_append (*verbs, rverb);
1077     }
1078
1079   g_win32_registry_subkey_iter_clear (&iter);
1080
1081   if (*verbs == NULL)
1082     {
1083       g_object_unref (key);
1084
1085       return FALSE;
1086     }
1087
1088   default_verb = NULL;
1089
1090   if (g_win32_registry_key_get_value_w (key,
1091                                         NULL,
1092                                         TRUE,
1093                                         L"",
1094                                         &val_type,
1095                                         (void **) &default_verb,
1096                                         NULL,
1097                                         NULL) &&
1098       (val_type != G_WIN32_REGISTRY_VALUE_STR ||
1099        g_utf16_len (default_verb) <= 0))
1100     g_clear_pointer (&default_verb, g_free);
1101
1102   g_object_unref (key);
1103
1104   /* Only sort at the top level */
1105   if (verbname_prefix[0] == 0)
1106     {
1107       *verbs = g_list_sort_with_data (*verbs, compare_verbs, default_verb);
1108
1109       for (i = *verbs; default_verb && *preferred_verb == NULL && i; i = i->next)
1110         if (_wcsicmp (default_verb, ((const reg_verb *) i->data)->name) == 0)
1111           *preferred_verb = (const reg_verb *) i->data;
1112     }
1113
1114   g_clear_pointer (&default_verb, g_free);
1115
1116   return TRUE;
1117 }
1118
1119 /* Grabs a URL association (from HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
1120  * or from an application with Capabilities, or just a schema subkey in HKCR).
1121  * @program_id is a ProgID of the handler for the URL.
1122  * @schema is the schema for the URL.
1123  * @schema_u8 and @schema_u8_folded are UTF-8 and folded UTF-8
1124  * respectively.
1125  * @app is the app to which the URL handler belongs (can be NULL).
1126  * @is_user_choice is TRUE if this association is clearly preferred
1127  */
1128 static void
1129 get_url_association (const gunichar2          *program_id,
1130                      const gunichar2          *schema,
1131                      const gchar              *schema_u8,
1132                      const gchar              *schema_u8_folded,
1133                      GWin32AppInfoApplication *app,
1134                      gboolean                  is_user_choice)
1135 {
1136   GWin32AppInfoURLSchema *schema_rec;
1137   GWin32AppInfoHandler *handler_rec;
1138   gunichar2 *handler_id;
1139   GList *verbs;
1140   const reg_verb *preferred_verb;
1141   gchar *handler_id_u8;
1142   gchar *handler_id_u8_folded;
1143   gunichar2 *uwp_aumid;
1144   gboolean is_uwp;
1145   GWin32RegistryKey *handler_key;
1146
1147   if ((handler_id = decide_which_id_to_use (program_id,
1148                                             &handler_key,
1149                                             &handler_id_u8,
1150                                             &handler_id_u8_folded,
1151                                             &uwp_aumid)) == NULL)
1152     return;
1153
1154   is_uwp = uwp_aumid != NULL;
1155
1156   if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1157     {
1158       g_clear_pointer (&handler_id, g_free);
1159       g_clear_pointer (&handler_id_u8, g_free);
1160       g_clear_pointer (&handler_id_u8_folded, g_free);
1161       g_clear_object (&handler_key);
1162       g_clear_pointer (&uwp_aumid, g_free);
1163
1164       return;
1165     }
1166
1167   if (!is_uwp && uwp_aumid != NULL)
1168     g_clear_pointer (&uwp_aumid, g_free);
1169
1170   schema_rec = get_schema_object (schema,
1171                                   schema_u8,
1172                                   schema_u8_folded);
1173
1174   handler_rec = get_handler_object (handler_id_u8_folded,
1175                                     handler_key,
1176                                     handler_id,
1177                                     uwp_aumid);
1178
1179   if (is_user_choice || schema_rec->chosen_handler == NULL)
1180     g_set_object (&schema_rec->chosen_handler, handler_rec);
1181
1182   g_hash_table_insert (schema_rec->handlers,
1183                        g_strdup (handler_id_u8_folded),
1184                        g_object_ref (handler_rec));
1185
1186   g_clear_object (&handler_key);
1187
1188   if (app)
1189     g_hash_table_insert (app->supported_urls,
1190                          g_strdup (schema_rec->schema_u8_folded),
1191                          g_object_ref (handler_rec));
1192
1193   if (uwp_aumid == NULL)
1194     process_verbs_commands (g_steal_pointer (&verbs),
1195                             preferred_verb,
1196                             HKCR,
1197                             handler_id,
1198                             TRUE,
1199                             handler_add_verb,
1200                             handler_rec,
1201                             app);
1202   else
1203     process_uwp_verbs (g_steal_pointer (&verbs),
1204                        preferred_verb,
1205                        HKCR,
1206                        handler_id,
1207                        TRUE,
1208                        handler_rec,
1209                        app);
1210
1211
1212   g_clear_pointer (&handler_id_u8, g_free);
1213   g_clear_pointer (&handler_id_u8_folded, g_free);
1214   g_clear_pointer (&handler_id, g_free);
1215   g_clear_pointer (&uwp_aumid, g_free);
1216 }
1217
1218 /* Grabs a file extension association (from HKCR\.ext or similar).
1219  * @program_id is a ProgID of the handler for the extension.
1220  * @file_extension is the extension (with the leading '.')
1221  * @app is the app to which the extension handler belongs (can be NULL).
1222  * @is_user_choice is TRUE if this is clearly the preferred association
1223  */
1224 static void
1225 get_file_ext (const gunichar2            *program_id,
1226               const gunichar2            *file_extension,
1227               GWin32AppInfoApplication   *app,
1228               gboolean                    is_user_choice)
1229 {
1230   GWin32AppInfoHandler *handler_rec;
1231   gunichar2 *handler_id;
1232   const reg_verb *preferred_verb;
1233   GList *verbs;
1234   gchar *handler_id_u8;
1235   gchar *handler_id_u8_folded;
1236   gunichar2 *uwp_aumid;
1237   gboolean is_uwp;
1238   GWin32RegistryKey *handler_key;
1239   GWin32AppInfoFileExtension *file_extn;
1240   gchar *file_extension_u8;
1241   gchar *file_extension_u8_folded;
1242
1243   if ((handler_id = decide_which_id_to_use (program_id,
1244                                             &handler_key,
1245                                             &handler_id_u8,
1246                                             &handler_id_u8_folded,
1247                                             &uwp_aumid)) == NULL)
1248     return;
1249
1250   if (!g_utf16_to_utf8_and_fold (file_extension,
1251                                  -1,
1252                                  &file_extension_u8,
1253                                  &file_extension_u8_folded))
1254     {
1255       g_clear_pointer (&handler_id, g_free);
1256       g_clear_pointer (&handler_id_u8, g_free);
1257       g_clear_pointer (&handler_id_u8_folded, g_free);
1258       g_clear_pointer (&uwp_aumid, g_free);
1259       g_clear_object (&handler_key);
1260
1261       return;
1262     }
1263
1264   is_uwp = uwp_aumid != NULL;
1265
1266   if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp))
1267     {
1268       g_clear_pointer (&handler_id, g_free);
1269       g_clear_pointer (&handler_id_u8, g_free);
1270       g_clear_pointer (&handler_id_u8_folded, g_free);
1271       g_clear_object (&handler_key);
1272       g_clear_pointer (&file_extension_u8, g_free);
1273       g_clear_pointer (&file_extension_u8_folded, g_free);
1274       g_clear_pointer (&uwp_aumid, g_free);
1275
1276       return;
1277     }
1278
1279   if (!is_uwp && uwp_aumid != NULL)
1280     g_clear_pointer (&uwp_aumid, g_free);
1281
1282   file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded);
1283
1284   handler_rec = get_handler_object (handler_id_u8_folded,
1285                                     handler_key,
1286                                     handler_id,
1287                                     uwp_aumid);
1288
1289   if (is_user_choice || file_extn->chosen_handler == NULL)
1290     g_set_object (&file_extn->chosen_handler, handler_rec);
1291
1292   g_hash_table_insert (file_extn->handlers,
1293                        g_strdup (handler_id_u8_folded),
1294                        g_object_ref (handler_rec));
1295
1296   if (app)
1297     g_hash_table_insert (app->supported_exts,
1298                          g_strdup (file_extension_u8_folded),
1299                          g_object_ref (handler_rec));
1300
1301   g_clear_pointer (&file_extension_u8, g_free);
1302   g_clear_pointer (&file_extension_u8_folded, g_free);
1303   g_clear_object (&handler_key);
1304
1305   if (uwp_aumid == NULL)
1306     process_verbs_commands (g_steal_pointer (&verbs),
1307                             preferred_verb,
1308                             HKCR,
1309                             handler_id,
1310                             TRUE,
1311                             handler_add_verb,
1312                             handler_rec,
1313                             app);
1314   else
1315     process_uwp_verbs (g_steal_pointer (&verbs),
1316                        preferred_verb,
1317                        HKCR,
1318                        handler_id,
1319                        TRUE,
1320                        handler_rec,
1321                        app);
1322
1323   g_clear_pointer (&handler_id, g_free);
1324   g_clear_pointer (&handler_id_u8, g_free);
1325   g_clear_pointer (&handler_id_u8_folded, g_free);
1326   g_clear_pointer (&uwp_aumid, g_free);
1327 }
1328
1329 /* Returns either a @program_id or the string from
1330  * the default value of the program_id key (which is a name
1331  * of a proxy class), or NULL.
1332  * Does not check that proxy represents a valid
1333  * record, just checks that it exists.
1334  * Can return the class key (HKCR/program_id or HKCR/proxy_id).
1335  * Can convert returned value to UTF-8 and fold it.
1336  */
1337 static gunichar2 *
1338 decide_which_id_to_use (const gunichar2    *program_id,
1339                         GWin32RegistryKey **return_key,
1340                         gchar             **return_handler_id_u8,
1341                         gchar             **return_handler_id_u8_folded,
1342                         gunichar2         **return_uwp_aumid)
1343 {
1344   GWin32RegistryKey *key;
1345   GWin32RegistryKey *uwp_key;
1346   GWin32RegistryValueType val_type;
1347   gunichar2 *proxy_id;
1348   gunichar2 *return_id;
1349   gunichar2 *uwp_aumid;
1350   gboolean got_value;
1351   gchar *handler_id_u8;
1352   gchar *handler_id_u8_folded;
1353   g_assert (program_id);
1354
1355   if (return_key)
1356     *return_key = NULL;
1357
1358   if (return_uwp_aumid)
1359     *return_uwp_aumid = NULL;
1360
1361   key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL);
1362
1363   if (key == NULL)
1364     return NULL;
1365
1366   /* Check for UWP first */
1367   uwp_aumid = NULL;
1368   uwp_key = g_win32_registry_key_get_child_w (key, L"Application", NULL);
1369
1370   if (uwp_key != NULL)
1371     {
1372       got_value = g_win32_registry_key_get_value_w (uwp_key,
1373                                                     NULL,
1374                                                     TRUE,
1375                                                     L"AppUserModelID",
1376                                                     &val_type,
1377                                                     (void **) &uwp_aumid,
1378                                                     NULL,
1379                                                     NULL);
1380       if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1381         g_clear_pointer (&uwp_aumid, g_free);
1382
1383       /* Other values in the Application key contain useful information
1384        * (description, name, icon), but it's inconvenient to read
1385        * it here (we don't have an app object *yet*). Store the key
1386        * in a table instead, and look at it later.
1387        */
1388       if (uwp_aumid == NULL)
1389         g_debug ("ProgramID %S looks like a UWP application, but isn't",
1390                  program_id);
1391       else
1392         g_hash_table_insert (uwp_handler_table, g_object_ref (uwp_key), g_wcsdup (uwp_aumid, -1));
1393
1394       g_object_unref (uwp_key);
1395     }
1396
1397   /* Then check for proxy */
1398   proxy_id = NULL;
1399
1400   if (uwp_aumid == NULL)
1401     {
1402       got_value = g_win32_registry_key_get_value_w (key,
1403                                                     NULL,
1404                                                     TRUE,
1405                                                     L"",
1406                                                     &val_type,
1407                                                     (void **) &proxy_id,
1408                                                     NULL,
1409                                                     NULL);
1410       if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR)
1411         g_clear_pointer (&proxy_id, g_free);
1412     }
1413
1414   return_id = NULL;
1415
1416   if (proxy_id)
1417     {
1418       GWin32RegistryKey *proxy_key;
1419       proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL);
1420
1421       if (proxy_key)
1422         {
1423           if (return_key)
1424             *return_key = g_steal_pointer (&proxy_key);
1425           g_clear_object (&proxy_key);
1426
1427           return_id = g_steal_pointer (&proxy_id);
1428         }
1429
1430       g_clear_pointer (&proxy_id, g_free);
1431     }
1432
1433   if ((return_handler_id_u8 ||
1434        return_handler_id_u8_folded) &&
1435       !g_utf16_to_utf8_and_fold (return_id == NULL ? program_id : return_id,
1436                                  -1,
1437                                  &handler_id_u8,
1438                                  &handler_id_u8_folded))
1439     {
1440       g_clear_object (&key);
1441       if (return_key)
1442         g_clear_object (return_key);
1443       g_clear_pointer (&return_id, g_free);
1444
1445       return NULL;
1446     }
1447
1448   if (return_handler_id_u8)
1449     *return_handler_id_u8 = g_steal_pointer (&handler_id_u8);
1450   g_clear_pointer (&handler_id_u8, g_free);
1451   if (return_handler_id_u8_folded)
1452     *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded);
1453   g_clear_pointer (&handler_id_u8_folded, g_free);
1454   if (return_uwp_aumid)
1455     *return_uwp_aumid = g_steal_pointer (&uwp_aumid);
1456   g_clear_pointer (&uwp_aumid, g_free);
1457
1458   if (return_id == NULL && return_key)
1459     *return_key = g_steal_pointer (&key);
1460   g_clear_object (&key);
1461
1462   if (return_id == NULL)
1463     return g_wcsdup (program_id, -1);
1464
1465   return return_id;
1466 }
1467
1468 /* Grabs the command for each verb from @verbs,
1469  * and invokes @handler for it. Consumes @verbs.
1470  * @path_to_progid and @progid are concatenated to
1471  * produce a path to the key where Shell/verb/command
1472  * subkeys are looked up.
1473  * @preferred_verb, if not NULL, will be used to inform
1474  * the @handler that a verb is preferred.
1475  * @autoprefer_first_verb will automatically make the first
1476  * verb to be preferred, if @preferred_verb is NULL.
1477  * @handler_data1 and @handler_data2 are passed to @handler as-is.
1478  */
1479 static void
1480 process_verbs_commands (GList             *verbs,
1481                         const reg_verb    *preferred_verb,
1482                         const gunichar2   *path_to_progid,
1483                         const gunichar2   *progid,
1484                         gboolean           autoprefer_first_verb,
1485                         verb_command_func  handler,
1486                         gpointer           handler_data1,
1487                         gpointer           handler_data2)
1488 {
1489   GList *i;
1490   gboolean got_value;
1491
1492   g_assert (handler != NULL);
1493   g_assert (verbs != NULL);
1494   g_assert (progid != NULL);
1495
1496   for (i = verbs; i; i = i->next)
1497     {
1498       const reg_verb *verb = (const reg_verb *) i->data;
1499       GWin32RegistryKey *key;
1500       GWin32RegistryKey *verb_key;
1501       gunichar2 *command_value;
1502       gchar *command_value_utf8;
1503       GWin32RegistryValueType val_type;
1504       gunichar2 *verb_displayname;
1505       gchar *verb_displayname_u8;
1506
1507       key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1508                                                    L"\\", verb->shellpath, L"\\command", NULL);
1509
1510       if (key == NULL)
1511         {
1512           g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey",
1513                    path_to_progid, progid, verb->shellpath);
1514           continue;
1515         }
1516
1517       command_value = NULL;
1518       got_value = g_win32_registry_key_get_value_w (key,
1519                                                     NULL,
1520                                                     TRUE,
1521                                                     L"",
1522                                                     &val_type,
1523                                                     (void **) &command_value,
1524                                                     NULL,
1525                                                     NULL);
1526       g_clear_object (&key);
1527
1528       if (!got_value ||
1529           val_type != G_WIN32_REGISTRY_VALUE_STR ||
1530           (command_value_utf8 = g_utf16_to_utf8 (command_value,
1531                                                  -1,
1532                                                  NULL,
1533                                                  NULL,
1534                                                  NULL)) == NULL)
1535         {
1536           g_clear_pointer (&command_value, g_free);
1537           continue;
1538         }
1539
1540       verb_displayname = NULL;
1541       verb_displayname_u8 = NULL;
1542       verb_key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1543                                                         L"\\", verb->shellpath, NULL);
1544
1545       if (verb_key)
1546         {
1547           gsize verb_displayname_len;
1548
1549           got_value = g_win32_registry_key_get_value_w (verb_key,
1550                                                         g_win32_registry_get_os_dirs_w (),
1551                                                         TRUE,
1552                                                         L"MUIVerb",
1553                                                         &val_type,
1554                                                         (void **) &verb_displayname,
1555                                                         &verb_displayname_len,
1556                                                         NULL);
1557
1558           if (got_value &&
1559               val_type == G_WIN32_REGISTRY_VALUE_STR &&
1560               verb_displayname_len > sizeof (gunichar2))
1561             verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
1562
1563           g_clear_pointer (&verb_displayname, g_free);
1564
1565           if (verb_displayname_u8 == NULL)
1566             {
1567               got_value = g_win32_registry_key_get_value_w (verb_key,
1568                                                             NULL,
1569                                                             TRUE,
1570                                                             L"",
1571                                                             &val_type,
1572                                                             (void **) &verb_displayname,
1573                                                             &verb_displayname_len,
1574                                                             NULL);
1575
1576               if (got_value &&
1577                   val_type == G_WIN32_REGISTRY_VALUE_STR &&
1578                   verb_displayname_len > sizeof (gunichar2))
1579                 verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL);
1580             }
1581
1582           g_clear_pointer (&verb_displayname, g_free);
1583           g_clear_object (&verb_key);
1584         }
1585
1586       handler (handler_data1, handler_data2, verb->name, command_value, command_value_utf8,
1587                verb_displayname_u8,
1588                (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
1589                (!preferred_verb && autoprefer_first_verb && i == verbs),
1590                FALSE);
1591
1592       g_clear_pointer (&command_value, g_free);
1593       g_clear_pointer (&command_value_utf8, g_free);
1594       g_clear_pointer (&verb_displayname_u8, g_free);
1595     }
1596
1597   g_list_free_full (verbs, reg_verb_free);
1598 }
1599
1600 static void
1601 process_uwp_verbs (GList                    *verbs,
1602                    const reg_verb           *preferred_verb,
1603                    const gunichar2          *path_to_progid,
1604                    const gunichar2          *progid,
1605                    gboolean                  autoprefer_first_verb,
1606                    GWin32AppInfoHandler     *handler_rec,
1607                    GWin32AppInfoApplication *app)
1608 {
1609   GList *i;
1610
1611   g_assert (verbs != NULL);
1612
1613   for (i = verbs; i; i = i->next)
1614     {
1615       const reg_verb *verb = (const reg_verb *) i->data;
1616       GWin32RegistryKey *key;
1617       gboolean got_value;
1618       GWin32RegistryValueType val_type;
1619       gunichar2 *acid;
1620       gsize acid_len;
1621
1622       key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid,
1623                                                    L"\\", verb->shellpath, NULL);
1624
1625       if (key == NULL)
1626         {
1627           g_debug ("%S%S\\%S does not exist",
1628                    path_to_progid, progid, verb->shellpath);
1629           continue;
1630         }
1631
1632       got_value = g_win32_registry_key_get_value_w (key,
1633                                                     g_win32_registry_get_os_dirs_w (),
1634                                                     TRUE,
1635                                                     L"ActivatableClassId",
1636                                                     &val_type,
1637                                                     (void **) &acid,
1638                                                     &acid_len,
1639                                                     NULL);
1640
1641       if (got_value &&
1642           val_type == G_WIN32_REGISTRY_VALUE_STR &&
1643           acid_len > sizeof (gunichar2))
1644         {
1645           /* TODO: default value of a shell subkey, if not empty,
1646            * migh contain something like @{Some.Identifier_1234.456.678.789_some_words?ms-resource://Arbitrary.Path/Pointing/Somewhere}
1647            * and it might be possible to turn it into a nice displayname.
1648            */
1649           uwp_handler_add_verb (handler_rec,
1650                                 app,
1651                                 verb->name,
1652                                 NULL,
1653                                 (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) ||
1654                                 (!preferred_verb && autoprefer_first_verb && i == verbs));
1655         }
1656       else
1657         {
1658           g_debug ("%S%S\\%S does not have an ActivatableClassId string value",
1659                    path_to_progid, progid, verb->shellpath);
1660         }
1661
1662       g_clear_pointer (&acid, g_free);
1663       g_clear_object (&key);
1664     }
1665
1666   g_list_free_full (verbs, reg_verb_free);
1667 }
1668
1669 /* Looks up a schema object identified by
1670  * @schema_u8_folded in the urls hash table.
1671  * If such object doesn't exist,
1672  * creates it and puts it into the urls hash table.
1673  * Returns the object.
1674  */
1675 static GWin32AppInfoURLSchema *
1676 get_schema_object (const gunichar2 *schema,
1677                    const gchar     *schema_u8,
1678                    const gchar     *schema_u8_folded)
1679 {
1680   GWin32AppInfoURLSchema *schema_rec;
1681
1682   schema_rec = g_hash_table_lookup (urls, schema_u8_folded);
1683
1684   if (schema_rec != NULL)
1685     return schema_rec;
1686
1687   schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL);
1688   schema_rec->schema = g_wcsdup (schema, -1);
1689   schema_rec->schema_u8 = g_strdup (schema_u8);
1690   schema_rec->schema_u8_folded = g_strdup (schema_u8_folded);
1691   g_hash_table_insert (urls, g_strdup (schema_rec->schema_u8_folded), schema_rec);
1692
1693   return schema_rec;
1694 }
1695
1696 /* Looks up a handler object identified by
1697  * @handler_id_u8_folded in the handlers hash table.
1698  * If such object doesn't exist,
1699  * creates it and puts it into the handlers hash table.
1700  * Returns the object.
1701  */
1702 static GWin32AppInfoHandler *
1703 get_handler_object (const gchar       *handler_id_u8_folded,
1704                     GWin32RegistryKey *handler_key,
1705                     const gunichar2   *handler_id,
1706                     const gunichar2   *uwp_aumid)
1707 {
1708   GWin32AppInfoHandler *handler_rec;
1709
1710   handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded);
1711
1712   if (handler_rec != NULL)
1713     return handler_rec;
1714
1715   handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL);
1716   if (handler_key)
1717     handler_rec->key = g_object_ref (handler_key);
1718   handler_rec->handler_id = g_wcsdup (handler_id, -1);
1719   handler_rec->handler_id_folded = g_strdup (handler_id_u8_folded);
1720   if (uwp_aumid)
1721     handler_rec->uwp_aumid = g_wcsdup (uwp_aumid, -1);
1722   if (handler_key)
1723     read_handler_icon (handler_key, &handler_rec->icon);
1724   g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec);
1725
1726   return handler_rec;
1727 }
1728
1729 static void
1730 handler_add_verb (gpointer           handler_data1,
1731                   gpointer           handler_data2,
1732                   const gunichar2   *verb,
1733                   const gunichar2   *command_line,
1734                   const gchar       *command_line_utf8,
1735                   const gchar       *verb_displayname,
1736                   gboolean           verb_is_preferred,
1737                   gboolean           invent_new_verb_name)
1738 {
1739   GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1;
1740   GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1741   GWin32AppInfoShellVerb *shverb;
1742
1743   _verb_lookup (handler_rec->verbs, verb, &shverb);
1744
1745   if (shverb != NULL)
1746     return;
1747
1748   shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1749   shverb->verb_name = g_wcsdup (verb, -1);
1750   shverb->verb_displayname = g_strdup (verb_displayname);
1751   shverb->command = g_wcsdup (command_line, -1);
1752   shverb->command_utf8 = g_strdup (command_line_utf8);
1753   shverb->is_uwp = FALSE; /* This function is for non-UWP verbs only */
1754   if (app_rec)
1755     shverb->app = g_object_ref (app_rec);
1756
1757   _g_win32_extract_executable (shverb->command,
1758                                &shverb->executable,
1759                                &shverb->executable_basename,
1760                                &shverb->executable_folded,
1761                                NULL,
1762                                &shverb->dll_function);
1763
1764   if (shverb->dll_function != NULL)
1765     _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1766
1767   if (!verb_is_preferred)
1768     g_ptr_array_add (handler_rec->verbs, shverb);
1769   else
1770     g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1771 }
1772
1773 /* Tries to generate a new name for a verb that looks
1774  * like "verb (%x)", where %x is an integer in range of [0;255).
1775  * On success puts new verb (and new verb displayname) into
1776  * @new_verb and @new_displayname and return TRUE.
1777  * On failure puts NULL into both and returns FALSE.
1778  */
1779 static gboolean
1780 generate_new_verb_name (GPtrArray        *verbs,
1781                         const gunichar2  *verb,
1782                         const gchar      *verb_displayname,
1783                         gunichar2       **new_verb,
1784                         gchar           **new_displayname)
1785 {
1786   gsize counter;
1787   GWin32AppInfoShellVerb *shverb;
1788   gsize orig_len = g_utf16_len (verb);
1789   gsize new_verb_name_len = orig_len + strlen (" ()") + 2 + 1;
1790   gunichar2 *new_verb_name = g_new (gunichar2, new_verb_name_len);
1791
1792   *new_verb = NULL;
1793   *new_displayname = NULL;
1794
1795   memcpy (new_verb_name, verb, orig_len * sizeof (gunichar2));
1796   for (counter = 0; counter < 255; counter++)
1797   {
1798     _snwprintf (&new_verb_name[orig_len], new_verb_name_len, L" (%x)", counter);
1799     _verb_lookup (verbs, new_verb_name, &shverb);
1800
1801     if (shverb == NULL)
1802       {
1803         *new_verb = new_verb_name;
1804         if (verb_displayname != NULL)
1805           *new_displayname = g_strdup_printf ("%s (%zx)", verb_displayname, counter);
1806
1807         return TRUE;
1808       }
1809   }
1810
1811   return FALSE;
1812 }
1813
1814 static void
1815 app_add_verb (gpointer           handler_data1,
1816               gpointer           handler_data2,
1817               const gunichar2   *verb,
1818               const gunichar2   *command_line,
1819               const gchar       *command_line_utf8,
1820               const gchar       *verb_displayname,
1821               gboolean           verb_is_preferred,
1822               gboolean           invent_new_verb_name)
1823 {
1824   gunichar2 *new_verb = NULL;
1825   gchar *new_displayname = NULL;
1826   GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2;
1827   GWin32AppInfoShellVerb *shverb;
1828
1829   _verb_lookup (app_rec->verbs, verb, &shverb);
1830
1831   /* Special logic for fake apps - do our best to
1832    * collate all possible verbs in the app,
1833    * including the verbs that have the same name but
1834    * different commandlines, in which case a new
1835    * verb name has to be invented.
1836    */
1837   if (shverb != NULL)
1838     {
1839       gsize vi;
1840
1841       if (!invent_new_verb_name)
1842         return;
1843
1844       for (vi = 0; vi < app_rec->verbs->len; vi++)
1845         {
1846           GWin32AppInfoShellVerb *app_verb;
1847
1848           app_verb = _verb_idx (app_rec->verbs, vi);
1849
1850           if (_wcsicmp (command_line, app_verb->command) == 0)
1851             break;
1852         }
1853
1854       if (vi < app_rec->verbs->len ||
1855           !generate_new_verb_name (app_rec->verbs,
1856                                    verb,
1857                                    verb_displayname,
1858                                    &new_verb,
1859                                    &new_displayname))
1860         return;
1861     }
1862
1863   shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1864   if (new_verb == NULL)
1865     shverb->verb_name = g_wcsdup (verb, -1);
1866   else
1867     shverb->verb_name = g_steal_pointer (&new_verb);
1868   if (new_displayname == NULL)
1869     shverb->verb_displayname = g_strdup (verb_displayname);
1870   else
1871     shverb->verb_displayname = g_steal_pointer (&new_displayname);
1872
1873   shverb->command = g_wcsdup (command_line, -1);
1874   shverb->command_utf8 = g_strdup (command_line_utf8);
1875   shverb->app = g_object_ref (app_rec);
1876
1877   _g_win32_extract_executable (shverb->command,
1878                                &shverb->executable,
1879                                &shverb->executable_basename,
1880                                &shverb->executable_folded,
1881                                NULL,
1882                                &shverb->dll_function);
1883
1884   if (shverb->dll_function != NULL)
1885     _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command);
1886
1887   if (!verb_is_preferred)
1888     g_ptr_array_add (app_rec->verbs, shverb);
1889   else
1890     g_ptr_array_insert (app_rec->verbs, 0, shverb);
1891 }
1892
1893 static void
1894 uwp_app_add_verb (GWin32AppInfoApplication *app_rec,
1895                   const gunichar2          *verb,
1896                   const gchar              *verb_displayname)
1897 {
1898   GWin32AppInfoShellVerb *shverb;
1899
1900   _verb_lookup (app_rec->verbs, verb, &shverb);
1901
1902   if (shverb != NULL)
1903     return;
1904
1905   shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1906   shverb->verb_name = g_wcsdup (verb, -1);
1907   shverb->app = g_object_ref (app_rec);
1908   shverb->verb_displayname = g_strdup (verb_displayname);
1909
1910   shverb->is_uwp = TRUE;
1911
1912   /* Strictly speaking, this is unnecessary, but
1913    * let's make it clear that UWP verbs have no
1914    * commands and executables.
1915    */
1916   shverb->command = NULL;
1917   shverb->command_utf8 = NULL;
1918   shverb->executable = NULL;
1919   shverb->executable_basename = NULL;
1920   shverb->executable_folded = NULL;
1921   shverb->dll_function = NULL;
1922
1923   g_ptr_array_add (app_rec->verbs, shverb);
1924 }
1925
1926 static void
1927 uwp_handler_add_verb (GWin32AppInfoHandler     *handler_rec,
1928                       GWin32AppInfoApplication *app,
1929                       const gunichar2          *verb,
1930                       const gchar              *verb_displayname,
1931                       gboolean                  verb_is_preferred)
1932 {
1933   GWin32AppInfoShellVerb *shverb;
1934
1935   _verb_lookup (handler_rec->verbs, verb, &shverb);
1936
1937   if (shverb != NULL)
1938     return;
1939
1940   shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL);
1941   shverb->verb_name = g_wcsdup (verb, -1);
1942   shverb->verb_displayname = g_strdup (verb_displayname);
1943
1944   shverb->is_uwp = TRUE;
1945
1946   if (app)
1947     shverb->app = g_object_ref (app);
1948
1949   shverb->command = NULL;
1950   shverb->command_utf8 = NULL;
1951   shverb->executable = NULL;
1952   shverb->executable_basename = NULL;
1953   shverb->executable_folded = NULL;
1954   shverb->dll_function = NULL;
1955
1956   if (!verb_is_preferred)
1957     g_ptr_array_add (handler_rec->verbs, shverb);
1958   else
1959     g_ptr_array_insert (handler_rec->verbs, 0, shverb);
1960 }
1961
1962 /* Looks up a file extension object identified by
1963  * @ext_u8_folded in the extensions hash table.
1964  * If such object doesn't exist,
1965  * creates it and puts it into the extensions hash table.
1966  * Returns the object.
1967  */
1968 static GWin32AppInfoFileExtension *
1969 get_ext_object (const gunichar2 *ext,
1970                 const gchar     *ext_u8,
1971                 const gchar     *ext_u8_folded)
1972 {
1973   GWin32AppInfoFileExtension *file_extn;
1974
1975   if (g_hash_table_lookup_extended (extensions,
1976                                     ext_u8_folded,
1977                                     NULL,
1978                                     (void **) &file_extn))
1979     return file_extn;
1980
1981   file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL);
1982   file_extn->extension = g_wcsdup (ext, -1);
1983   file_extn->extension_u8 = g_strdup (ext_u8);
1984   g_hash_table_insert (extensions, g_strdup (ext_u8_folded), file_extn);
1985
1986   return file_extn;
1987 }
1988
1989 /* Iterates over HKCU\\Software\\Clients or HKLM\\Software\\Clients,
1990  * (depending on @user_registry being TRUE or FALSE),
1991  * collecting applications listed there.
1992  * Puts the path to the client key for each client into @priority_capable_apps
1993  * (only for clients with file or URL associations).
1994  */
1995 static void
1996 collect_capable_apps_from_clients (GPtrArray *capable_apps,
1997                                    GPtrArray *priority_capable_apps,
1998                                    gboolean   user_registry)
1999 {
2000   GWin32RegistryKey *clients;
2001   GWin32RegistrySubkeyIter clients_iter;
2002
2003   const gunichar2 *client_type_name;
2004   gsize client_type_name_len;
2005
2006
2007   if (user_registry)
2008     clients =
2009         g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
2010                                      NULL);
2011   else
2012     clients =
2013         g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
2014                                      NULL);
2015
2016   if (clients == NULL)
2017     return;
2018
2019   if (!g_win32_registry_subkey_iter_init (&clients_iter, clients, NULL))
2020     {
2021       g_object_unref (clients);
2022       return;
2023     }
2024
2025   while (g_win32_registry_subkey_iter_next (&clients_iter, TRUE, NULL))
2026     {
2027       GWin32RegistrySubkeyIter subkey_iter;
2028       GWin32RegistryKey *system_client_type;
2029       GWin32RegistryValueType default_type;
2030       gunichar2 *default_value = NULL;
2031       const gunichar2 *client_name;
2032       gsize client_name_len;
2033
2034       if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter,
2035                                                     &client_type_name,
2036                                                     &client_type_name_len,
2037                                                     NULL))
2038         continue;
2039
2040       system_client_type = g_win32_registry_key_get_child_w (clients,
2041                                                              client_type_name,
2042                                                              NULL);
2043
2044       if (system_client_type == NULL)
2045         continue;
2046
2047       if (g_win32_registry_key_get_value_w (system_client_type,
2048                                             NULL,
2049                                             TRUE,
2050                                             L"",
2051                                             &default_type,
2052                                             (gpointer *) &default_value,
2053                                             NULL,
2054                                             NULL))
2055         {
2056           if (default_type != G_WIN32_REGISTRY_VALUE_STR ||
2057               default_value[0] == L'\0')
2058             g_clear_pointer (&default_value, g_free);
2059         }
2060
2061       if (!g_win32_registry_subkey_iter_init (&subkey_iter,
2062                                               system_client_type,
2063                                               NULL))
2064         {
2065           g_clear_pointer (&default_value, g_free);
2066           g_object_unref (system_client_type);
2067           continue;
2068         }
2069
2070       while (g_win32_registry_subkey_iter_next (&subkey_iter, TRUE, NULL))
2071         {
2072           GWin32RegistryKey *system_client;
2073           GWin32RegistryKey *system_client_assoc;
2074           gboolean add;
2075           gunichar2 *keyname;
2076
2077           if (!g_win32_registry_subkey_iter_get_name_w (&subkey_iter,
2078                                                         &client_name,
2079                                                         &client_name_len,
2080                                                         NULL))
2081             continue;
2082
2083           system_client = g_win32_registry_key_get_child_w (system_client_type,
2084                                                             client_name,
2085                                                             NULL);
2086
2087           if (system_client == NULL)
2088             continue;
2089
2090           add = FALSE;
2091
2092           system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2093                                                                   L"Capabilities\\FileAssociations",
2094                                                                   NULL);
2095
2096           if (system_client_assoc != NULL)
2097             {
2098               add = TRUE;
2099               g_object_unref (system_client_assoc);
2100             }
2101           else
2102             {
2103               system_client_assoc = g_win32_registry_key_get_child_w (system_client,
2104                                                                       L"Capabilities\\UrlAssociations",
2105                                                                       NULL);
2106
2107               if (system_client_assoc != NULL)
2108                 {
2109                   add = TRUE;
2110                   g_object_unref (system_client_assoc);
2111                 }
2112             }
2113
2114           if (add)
2115             {
2116               keyname = g_wcsdup (g_win32_registry_key_get_path_w (system_client), -1);
2117
2118               if (default_value && wcscmp (default_value, client_name) == 0)
2119                 g_ptr_array_add (priority_capable_apps, keyname);
2120               else
2121                 g_ptr_array_add (capable_apps, keyname);
2122             }
2123
2124           g_object_unref (system_client);
2125         }
2126
2127       g_win32_registry_subkey_iter_clear (&subkey_iter);
2128       g_clear_pointer (&default_value, g_free);
2129       g_object_unref (system_client_type);
2130     }
2131
2132   g_win32_registry_subkey_iter_clear (&clients_iter);
2133   g_object_unref (clients);
2134 }
2135
2136 /* Iterates over HKCU\\Software\\RegisteredApplications or HKLM\\Software\\RegisteredApplications,
2137  * (depending on @user_registry being TRUE or FALSE),
2138  * collecting applications listed there.
2139  * Puts the path to the app key for each app into @capable_apps.
2140  */
2141 static void
2142 collect_capable_apps_from_registered_apps (GPtrArray *capable_apps,
2143                                            gboolean   user_registry)
2144 {
2145   GWin32RegistryValueIter iter;
2146   const gunichar2 *reg_path;
2147
2148   gunichar2 *value_data;
2149   gsize      value_data_size;
2150   GWin32RegistryValueType value_type;
2151   GWin32RegistryKey *registered_apps;
2152
2153   if (user_registry)
2154     reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications";
2155   else
2156     reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications";
2157
2158   registered_apps =
2159       g_win32_registry_key_new_w (reg_path, NULL);
2160
2161   if (!registered_apps)
2162     return;
2163
2164   if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL))
2165     {
2166       g_object_unref (registered_apps);
2167
2168       return;
2169     }
2170
2171   while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2172     {
2173       gunichar2 possible_location[REG_PATH_MAX_SIZE + 1];
2174       GWin32RegistryKey *location;
2175       gunichar2 *p;
2176
2177       if ((!g_win32_registry_value_iter_get_value_type (&iter,
2178                                                         &value_type,
2179                                                         NULL)) ||
2180           (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2181           (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2182                                                     (void **) &value_data,
2183                                                     &value_data_size,
2184                                                     NULL)) ||
2185           (value_data_size < sizeof (gunichar2)) ||
2186           (value_data[0] == L'\0'))
2187         continue;
2188
2189       if (!build_registry_path (possible_location, sizeof (possible_location),
2190                                 user_registry ? HKCU : HKLM, value_data, NULL))
2191         continue;
2192
2193       location = g_win32_registry_key_new_w (possible_location, NULL);
2194
2195       if (location == NULL)
2196         continue;
2197
2198       p = wcsrchr (possible_location, L'\\');
2199
2200       if (p)
2201         {
2202           *p = L'\0';
2203           g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1));
2204         }
2205
2206       g_object_unref (location);
2207     }
2208
2209   g_win32_registry_value_iter_clear (&iter);
2210   g_object_unref (registered_apps);
2211 }
2212
2213 /* Looks up an app object identified by
2214  * @canonical_name_folded in the @app_hashmap.
2215  * If such object doesn't exist,
2216  * creates it and puts it into the @app_hashmap.
2217  * Returns the object.
2218  */
2219 static GWin32AppInfoApplication *
2220 get_app_object (GHashTable      *app_hashmap,
2221                 const gunichar2 *canonical_name,
2222                 const gchar     *canonical_name_u8,
2223                 const gchar     *canonical_name_folded,
2224                 gboolean         user_specific,
2225                 gboolean         default_app,
2226                 gboolean         is_uwp)
2227 {
2228   GWin32AppInfoApplication *app;
2229
2230   app = g_hash_table_lookup (app_hashmap, canonical_name_folded);
2231
2232   if (app != NULL)
2233     return app;
2234
2235   app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
2236   app->canonical_name = g_wcsdup (canonical_name, -1);
2237   app->canonical_name_u8 = g_strdup (canonical_name_u8);
2238   app->canonical_name_folded = g_strdup (canonical_name_folded);
2239   app->no_open_with = FALSE;
2240   app->user_specific = user_specific;
2241   app->default_app = default_app;
2242   app->is_uwp = is_uwp;
2243   g_hash_table_insert (app_hashmap,
2244                        g_strdup (canonical_name_folded),
2245                        app);
2246
2247   return app;
2248 }
2249
2250 /* Grabs an application that has Capabilities.
2251  * @app_key_path is the path to the application key
2252  * (which must have a "Capabilities" subkey).
2253  * @default_app is TRUE if the app has priority
2254  */
2255 static void
2256 read_capable_app (const gunichar2 *app_key_path,
2257                   gboolean         user_specific,
2258                   gboolean         default_app)
2259 {
2260   GWin32AppInfoApplication *app;
2261   gchar *canonical_name_u8 = NULL;
2262   gchar *canonical_name_folded = NULL;
2263   gchar *app_key_path_u8 = NULL;
2264   gchar *app_key_path_u8_folded = NULL;
2265   GWin32RegistryKey *appkey = NULL;
2266   gunichar2 *fallback_friendly_name;
2267   GWin32RegistryValueType vtype;
2268   gboolean success;
2269   gunichar2 *friendly_name;
2270   gunichar2 *description;
2271   gunichar2 *narrow_application_name;
2272   gunichar2 *icon_source;
2273   GWin32RegistryKey *capabilities;
2274   GWin32RegistryKey *default_icon_key;
2275   GWin32RegistryKey *associations;
2276   const reg_verb *preferred_verb;
2277   GList *verbs = NULL;
2278   gboolean verbs_in_root_key = TRUE;
2279
2280   appkey = NULL;
2281   capabilities = NULL;
2282
2283   if (!g_utf16_to_utf8_and_fold (app_key_path,
2284                                  -1,
2285                                  &canonical_name_u8,
2286                                  &canonical_name_folded) ||
2287       !g_utf16_to_utf8_and_fold (app_key_path,
2288                                  -1,
2289                                  &app_key_path_u8,
2290                                  &app_key_path_u8_folded) ||
2291       (appkey = g_win32_registry_key_new_w (app_key_path, NULL)) == NULL ||
2292       (capabilities = g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL)) == NULL ||
2293       !(get_verbs (appkey, &preferred_verb, &verbs, L"", L"Shell", NULL) ||
2294         (verbs_in_root_key = FALSE) ||
2295         get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell", NULL)))
2296     {
2297       g_clear_pointer (&canonical_name_u8, g_free);
2298       g_clear_pointer (&canonical_name_folded, g_free);
2299       g_clear_object (&appkey);
2300       g_clear_object (&capabilities);
2301       g_clear_pointer (&app_key_path_u8, g_free);
2302       g_clear_pointer (&app_key_path_u8_folded, g_free);
2303
2304       return;
2305     }
2306
2307   app = get_app_object (apps_by_id,
2308                         app_key_path,
2309                         canonical_name_u8,
2310                         canonical_name_folded,
2311                         user_specific,
2312                         default_app,
2313                         FALSE);
2314
2315   process_verbs_commands (g_steal_pointer (&verbs),
2316                           preferred_verb,
2317                           L"", /* [ab]use the fact that two strings are simply concatenated */
2318                           verbs_in_root_key ? app_key_path : g_win32_registry_key_get_path_w (capabilities),
2319                           FALSE,
2320                           app_add_verb,
2321                           app,
2322                           app);
2323
2324   fallback_friendly_name = NULL;
2325   success = g_win32_registry_key_get_value_w (appkey,
2326                                               NULL,
2327                                               TRUE,
2328                                               L"",
2329                                               &vtype,
2330                                               (void **) &fallback_friendly_name,
2331                                               NULL,
2332                                               NULL);
2333
2334   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2335     g_clear_pointer (&fallback_friendly_name, g_free);
2336
2337   if (fallback_friendly_name &&
2338       app->pretty_name == NULL)
2339     {
2340       app->pretty_name = g_wcsdup (fallback_friendly_name, -1);
2341       g_clear_pointer (&app->pretty_name_u8, g_free);
2342       app->pretty_name_u8 = g_utf16_to_utf8 (fallback_friendly_name,
2343                                              -1,
2344                                              NULL,
2345                                              NULL,
2346                                              NULL);
2347     }
2348
2349   friendly_name = NULL;
2350   success = g_win32_registry_key_get_value_w (capabilities,
2351                                               g_win32_registry_get_os_dirs_w (),
2352                                               TRUE,
2353                                               L"LocalizedString",
2354                                               &vtype,
2355                                               (void **) &friendly_name,
2356                                               NULL,
2357                                               NULL);
2358
2359   if (success &&
2360       vtype != G_WIN32_REGISTRY_VALUE_STR)
2361     g_clear_pointer (&friendly_name, g_free);
2362
2363   if (friendly_name &&
2364       app->localized_pretty_name == NULL)
2365     {
2366       app->localized_pretty_name = g_wcsdup (friendly_name, -1);
2367       g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2368       app->localized_pretty_name_u8 = g_utf16_to_utf8 (friendly_name,
2369                                                        -1,
2370                                                        NULL,
2371                                                        NULL,
2372                                                        NULL);
2373     }
2374
2375   description = NULL;
2376   success = g_win32_registry_key_get_value_w (capabilities,
2377                                               g_win32_registry_get_os_dirs_w (),
2378                                               TRUE,
2379                                               L"ApplicationDescription",
2380                                               &vtype,
2381                                               (void **) &description,
2382                                               NULL,
2383                                               NULL);
2384
2385   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2386     g_clear_pointer (&description, g_free);
2387
2388   if (description && app->description == NULL)
2389     {
2390       app->description = g_wcsdup (description, -1);
2391       g_clear_pointer (&app->description_u8, g_free);
2392       app->description_u8 = g_utf16_to_utf8 (description, -1, NULL, NULL, NULL);
2393     }
2394
2395   default_icon_key = g_win32_registry_key_get_child_w (appkey,
2396                                                        L"DefaultIcon",
2397                                                        NULL);
2398
2399   icon_source = NULL;
2400
2401   if (default_icon_key != NULL)
2402     {
2403       success = g_win32_registry_key_get_value_w (default_icon_key,
2404                                                   NULL,
2405                                                   TRUE,
2406                                                   L"",
2407                                                   &vtype,
2408                                                   (void **) &icon_source,
2409                                                   NULL,
2410                                                   NULL);
2411
2412       if (success &&
2413           vtype != G_WIN32_REGISTRY_VALUE_STR)
2414         g_clear_pointer (&icon_source, g_free);
2415
2416       g_object_unref (default_icon_key);
2417     }
2418
2419   if (icon_source == NULL)
2420     {
2421       success = g_win32_registry_key_get_value_w (capabilities,
2422                                                   NULL,
2423                                                   TRUE,
2424                                                   L"ApplicationIcon",
2425                                                   &vtype,
2426                                                   (void **) &icon_source,
2427                                                   NULL,
2428                                                   NULL);
2429
2430       if (success &&
2431           vtype != G_WIN32_REGISTRY_VALUE_STR)
2432         g_clear_pointer (&icon_source, g_free);
2433     }
2434
2435   if (icon_source &&
2436       app->icon == NULL)
2437     {
2438       gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2439       app->icon = g_themed_icon_new (name);
2440       g_free (name);
2441     }
2442
2443   narrow_application_name = NULL;
2444   success = g_win32_registry_key_get_value_w (capabilities,
2445                                               g_win32_registry_get_os_dirs_w (),
2446                                               TRUE,
2447                                               L"ApplicationName",
2448                                               &vtype,
2449                                               (void **) &narrow_application_name,
2450                                               NULL,
2451                                               NULL);
2452
2453   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2454     g_clear_pointer (&narrow_application_name, g_free);
2455
2456   if (narrow_application_name &&
2457       app->localized_pretty_name == NULL)
2458     {
2459       app->localized_pretty_name = g_wcsdup (narrow_application_name, -1);
2460       g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2461       app->localized_pretty_name_u8 = g_utf16_to_utf8 (narrow_application_name,
2462                                                        -1,
2463                                                        NULL,
2464                                                        NULL,
2465                                                        NULL);
2466     }
2467
2468   associations = g_win32_registry_key_get_child_w (capabilities,
2469                                                    L"FileAssociations",
2470                                                    NULL);
2471
2472   if (associations != NULL)
2473     {
2474       GWin32RegistryValueIter iter;
2475
2476       if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2477         {
2478           gunichar2 *file_extension;
2479           gunichar2 *extension_handler;
2480           gsize      file_extension_len;
2481           gsize      extension_handler_size;
2482           GWin32RegistryValueType value_type;
2483
2484           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2485             {
2486               if ((!g_win32_registry_value_iter_get_value_type (&iter,
2487                                                                 &value_type,
2488                                                                 NULL)) ||
2489                   (value_type != G_WIN32_REGISTRY_VALUE_STR) ||
2490                   (!g_win32_registry_value_iter_get_name_w (&iter,
2491                                                             &file_extension,
2492                                                             &file_extension_len,
2493                                                             NULL)) ||
2494                   (file_extension_len <= 0) ||
2495                   (file_extension[0] != L'.') ||
2496                   (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2497                                                             (void **) &extension_handler,
2498                                                             &extension_handler_size,
2499                                                             NULL)) ||
2500                   (extension_handler_size < sizeof (gunichar2)) ||
2501                   (extension_handler[0] == L'\0'))
2502                 continue;
2503
2504               get_file_ext (extension_handler, file_extension, app, FALSE);
2505             }
2506
2507           g_win32_registry_value_iter_clear (&iter);
2508         }
2509
2510       g_object_unref (associations);
2511     }
2512
2513   associations = g_win32_registry_key_get_child_w (capabilities, L"URLAssociations", NULL);
2514
2515   if (associations != NULL)
2516     {
2517       GWin32RegistryValueIter iter;
2518
2519       if (g_win32_registry_value_iter_init (&iter, associations, NULL))
2520         {
2521           gunichar2 *url_schema;
2522           gunichar2 *schema_handler;
2523           gsize      url_schema_len;
2524           gsize      schema_handler_size;
2525           GWin32RegistryValueType value_type;
2526
2527           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2528             {
2529               gchar *schema_u8;
2530               gchar *schema_u8_folded;
2531
2532               if ((!g_win32_registry_value_iter_get_value_type (&iter,
2533                                                                 &value_type,
2534                                                                 NULL)) ||
2535                   ((value_type != G_WIN32_REGISTRY_VALUE_STR) &&
2536                    (value_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) ||
2537                   (!g_win32_registry_value_iter_get_name_w (&iter,
2538                                                             &url_schema,
2539                                                             &url_schema_len,
2540                                                             NULL)) ||
2541                   (url_schema_len <= 0) ||
2542                   (url_schema[0] == L'\0') ||
2543                   (!g_win32_registry_value_iter_get_data_w (&iter, TRUE,
2544                                                             (void **) &schema_handler,
2545                                                             &schema_handler_size,
2546                                                             NULL)) ||
2547                   (schema_handler_size < sizeof (gunichar2)) ||
2548                   (schema_handler[0] == L'\0'))
2549                 continue;
2550
2551
2552
2553               if (g_utf16_to_utf8_and_fold (url_schema,
2554                                             url_schema_len,
2555                                             &schema_u8,
2556                                             &schema_u8_folded))
2557                 get_url_association (schema_handler, url_schema, schema_u8, schema_u8_folded, app, FALSE);
2558
2559               g_clear_pointer (&schema_u8, g_free);
2560               g_clear_pointer (&schema_u8_folded, g_free);
2561             }
2562
2563           g_win32_registry_value_iter_clear (&iter);
2564         }
2565
2566       g_object_unref (associations);
2567     }
2568
2569   g_clear_pointer (&fallback_friendly_name, g_free);
2570   g_clear_pointer (&description, g_free);
2571   g_clear_pointer (&icon_source, g_free);
2572   g_clear_pointer (&narrow_application_name, g_free);
2573
2574   g_object_unref (appkey);
2575   g_object_unref (capabilities);
2576   g_clear_pointer (&app_key_path_u8, g_free);
2577   g_clear_pointer (&app_key_path_u8_folded, g_free);
2578   g_clear_pointer (&canonical_name_u8, g_free);
2579   g_clear_pointer (&canonical_name_folded, g_free);
2580 }
2581
2582 /* Iterates over subkeys in HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\
2583  * and calls get_url_association() for each one that has a user-chosen handler.
2584  */
2585 static void
2586 read_urls (GWin32RegistryKey *url_associations)
2587 {
2588   GWin32RegistrySubkeyIter url_iter;
2589
2590   if (url_associations == NULL)
2591     return;
2592
2593   if (!g_win32_registry_subkey_iter_init (&url_iter, url_associations, NULL))
2594     return;
2595
2596   while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL))
2597     {
2598       gchar *schema_u8 = NULL;
2599       gchar *schema_u8_folded = NULL;
2600       const gunichar2 *url_schema = NULL;
2601       gunichar2 *program_id = NULL;
2602       GWin32RegistryKey *user_choice = NULL;
2603       gsize url_schema_len;
2604       GWin32RegistryValueType val_type;
2605
2606       if (g_win32_registry_subkey_iter_get_name_w (&url_iter,
2607                                                    &url_schema,
2608                                                    &url_schema_len,
2609                                                    NULL) &&
2610           g_utf16_to_utf8_and_fold (url_schema,
2611                                     url_schema_len,
2612                                     &schema_u8,
2613                                     &schema_u8_folded) &&
2614           (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS,
2615                                                                 url_schema, USER_CHOICE,
2616                                                                 NULL)) != NULL &&
2617           g_win32_registry_key_get_value_w (user_choice,
2618                                             NULL,
2619                                             TRUE,
2620                                             L"Progid",
2621                                             &val_type,
2622                                             (void **) &program_id,
2623                                             NULL,
2624                                             NULL) &&
2625           val_type == G_WIN32_REGISTRY_VALUE_STR)
2626         get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE);
2627
2628       g_clear_pointer (&program_id, g_free);
2629       g_clear_pointer (&user_choice, g_object_unref);
2630       g_clear_pointer (&schema_u8, g_free);
2631       g_clear_pointer (&schema_u8_folded, g_free);
2632     }
2633
2634   g_win32_registry_subkey_iter_clear (&url_iter);
2635 }
2636
2637 /* Reads an application that is only registered by the basename of its
2638  * executable (and doesn't have Capabilities subkey).
2639  * @incapable_app is the registry key for the app.
2640  * @app_exe_basename is the basename of its executable.
2641  */
2642 static void
2643 read_incapable_app (GWin32RegistryKey *incapable_app,
2644                     const gunichar2   *app_exe_basename,
2645                     const gchar       *app_exe_basename_u8,
2646                     const gchar       *app_exe_basename_u8_folded)
2647 {
2648   GWin32RegistryValueIter sup_iter;
2649   GWin32AppInfoApplication *app;
2650   GList *verbs;
2651   const reg_verb *preferred_verb;
2652   gunichar2 *friendly_app_name;
2653   gboolean success;
2654   GWin32RegistryValueType vtype;
2655   gboolean no_open_with;
2656   GWin32RegistryKey *default_icon_key;
2657   gunichar2 *icon_source;
2658   GIcon *icon = NULL;
2659   GWin32RegistryKey *supported_key;
2660
2661   if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell", NULL))
2662     return;
2663
2664   app = get_app_object (apps_by_exe,
2665                         app_exe_basename,
2666                         app_exe_basename_u8,
2667                         app_exe_basename_u8_folded,
2668                         FALSE,
2669                         FALSE,
2670                         FALSE);
2671
2672   process_verbs_commands (g_steal_pointer (&verbs),
2673                           preferred_verb,
2674                           L"HKEY_CLASSES_ROOT\\Applications\\",
2675                           app_exe_basename,
2676                           TRUE,
2677                           app_add_verb,
2678                           app,
2679                           app);
2680
2681   friendly_app_name = NULL;
2682   success = g_win32_registry_key_get_value_w (incapable_app,
2683                                               g_win32_registry_get_os_dirs_w (),
2684                                               TRUE,
2685                                               L"FriendlyAppName",
2686                                               &vtype,
2687                                               (void **) &friendly_app_name,
2688                                               NULL,
2689                                               NULL);
2690
2691   if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2692     g_clear_pointer (&friendly_app_name, g_free);
2693
2694   no_open_with = g_win32_registry_key_get_value_w (incapable_app,
2695                                                    NULL,
2696                                                    TRUE,
2697                                                    L"NoOpenWith",
2698                                                    &vtype,
2699                                                    NULL,
2700                                                    NULL,
2701                                                    NULL);
2702
2703   default_icon_key =
2704       g_win32_registry_key_get_child_w (incapable_app,
2705                                         L"DefaultIcon",
2706                                         NULL);
2707
2708   icon_source = NULL;
2709
2710   if (default_icon_key != NULL)
2711     {
2712       success =
2713           g_win32_registry_key_get_value_w (default_icon_key,
2714                                             NULL,
2715                                             TRUE,
2716                                             L"",
2717                                             &vtype,
2718                                             (void **) &icon_source,
2719                                             NULL,
2720                                             NULL);
2721
2722       if (success && vtype != G_WIN32_REGISTRY_VALUE_STR)
2723         g_clear_pointer (&icon_source, g_free);
2724
2725       g_object_unref (default_icon_key);
2726     }
2727
2728   if (icon_source)
2729     {
2730       gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL);
2731       if (name != NULL)
2732         icon = g_themed_icon_new (name);
2733       g_free (name);
2734     }
2735
2736   app->no_open_with = no_open_with;
2737
2738   if (friendly_app_name &&
2739       app->localized_pretty_name == NULL)
2740     {
2741       app->localized_pretty_name = g_wcsdup (friendly_app_name, -1);
2742       g_clear_pointer (&app->localized_pretty_name_u8, g_free);
2743       app->localized_pretty_name_u8 =
2744           g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL);
2745     }
2746
2747   if (icon && app->icon == NULL)
2748     app->icon = g_object_ref (icon);
2749
2750   supported_key =
2751       g_win32_registry_key_get_child_w (incapable_app,
2752                                         L"SupportedTypes",
2753                                         NULL);
2754
2755   if (supported_key &&
2756       g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL))
2757     {
2758       gunichar2 *ext_name;
2759       gsize      ext_name_len;
2760
2761       while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL))
2762         {
2763           if ((!g_win32_registry_value_iter_get_name_w (&sup_iter,
2764                                                         &ext_name,
2765                                                         &ext_name_len,
2766                                                         NULL)) ||
2767               (ext_name_len <= 0) ||
2768               (ext_name[0] != L'.'))
2769             continue;
2770
2771           get_file_ext (ext_name, ext_name, app, FALSE);
2772         }
2773
2774       g_win32_registry_value_iter_clear (&sup_iter);
2775     }
2776
2777   g_clear_object (&supported_key);
2778   g_free (friendly_app_name);
2779   g_free (icon_source);
2780
2781   g_clear_object (&icon);
2782 }
2783
2784 /* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications
2785  * and calls read_incapable_app() for each one.
2786  */
2787 static void
2788 read_exeapps (void)
2789 {
2790   GWin32RegistryKey *applications_key;
2791   GWin32RegistrySubkeyIter app_iter;
2792
2793   applications_key =
2794       g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL);
2795
2796   if (applications_key == NULL)
2797     return;
2798
2799   if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL))
2800     {
2801       g_object_unref (applications_key);
2802       return;
2803     }
2804
2805   while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL))
2806     {
2807       const gunichar2 *app_exe_basename;
2808       gsize app_exe_basename_len;
2809       GWin32RegistryKey *incapable_app;
2810       gchar *app_exe_basename_u8;
2811       gchar *app_exe_basename_u8_folded;
2812
2813       if (!g_win32_registry_subkey_iter_get_name_w (&app_iter,
2814                                                     &app_exe_basename,
2815                                                     &app_exe_basename_len,
2816                                                     NULL) ||
2817           !g_utf16_to_utf8_and_fold (app_exe_basename,
2818                                      app_exe_basename_len,
2819                                      &app_exe_basename_u8,
2820                                      &app_exe_basename_u8_folded))
2821         continue;
2822
2823       incapable_app =
2824           g_win32_registry_key_get_child_w (applications_key,
2825                                             app_exe_basename,
2826                                             NULL);
2827
2828       if (incapable_app != NULL)
2829         read_incapable_app (incapable_app,
2830                             app_exe_basename,
2831                             app_exe_basename_u8,
2832                             app_exe_basename_u8_folded);
2833
2834       g_clear_object (&incapable_app);
2835       g_clear_pointer (&app_exe_basename_u8, g_free);
2836       g_clear_pointer (&app_exe_basename_u8_folded, g_free);
2837     }
2838
2839   g_win32_registry_subkey_iter_clear (&app_iter);
2840   g_object_unref (applications_key);
2841 }
2842
2843 /* Iterates over subkeys of HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\
2844  * and calls get_file_ext() for each associated handler
2845  * (starting with user-chosen handler, if any)
2846  */
2847 static void
2848 read_exts (GWin32RegistryKey *file_exts)
2849 {
2850   GWin32RegistrySubkeyIter ext_iter;
2851   const gunichar2 *file_extension;
2852   gsize file_extension_len;
2853
2854   if (file_exts == NULL)
2855     return;
2856
2857   if (!g_win32_registry_subkey_iter_init (&ext_iter, file_exts, NULL))
2858     return;
2859
2860   while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL))
2861     {
2862       GWin32RegistryKey *open_with_progids;
2863       gunichar2 *program_id;
2864       GWin32RegistryValueIter iter;
2865       gunichar2 *value_name;
2866       gsize      value_name_len;
2867       GWin32RegistryValueType value_type;
2868       GWin32RegistryKey *user_choice;
2869
2870       if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter,
2871                                                     &file_extension,
2872                                                     &file_extension_len,
2873                                                     NULL))
2874         continue;
2875
2876       program_id = NULL;
2877       user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, file_extension,
2878                                                            USER_CHOICE, NULL);
2879       if (user_choice &&
2880           g_win32_registry_key_get_value_w (user_choice,
2881                                             NULL,
2882                                             TRUE,
2883                                             L"Progid",
2884                                             &value_type,
2885                                             (void **) &program_id,
2886                                             NULL,
2887                                             NULL) &&
2888           value_type == G_WIN32_REGISTRY_VALUE_STR)
2889         {
2890           /* Note: program_id could be "ProgramID" or "Applications\\program.exe".
2891            * The code still works, but handler_id might have a backslash
2892            * in it - that might trip us up later on.
2893            * Even though in that case this is logically an "application"
2894            * registry entry, we don't treat it in any special way.
2895            * We do scan that registry branch anyway, just not here.
2896            */
2897           get_file_ext (program_id, file_extension, NULL, TRUE);
2898         }
2899
2900       g_clear_object (&user_choice);
2901       g_clear_pointer (&program_id, g_free);
2902
2903       open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS,
2904                                                                  file_extension,
2905                                                                  OPEN_WITH_PROGIDS,
2906                                                                  NULL);
2907
2908       if (open_with_progids == NULL)
2909         continue;
2910
2911       if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2912         {
2913           g_clear_object (&open_with_progids);
2914           continue;
2915         }
2916
2917       while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2918         {
2919           if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
2920                                                        &value_name_len,
2921                                                        NULL) ||
2922               (value_name_len == 0))
2923             continue;
2924
2925           get_file_ext (value_name, file_extension, NULL, FALSE);
2926         }
2927
2928       g_win32_registry_value_iter_clear (&iter);
2929       g_clear_object (&open_with_progids);
2930     }
2931
2932   g_win32_registry_subkey_iter_clear (&ext_iter);
2933 }
2934
2935 /* Iterates over subkeys in HKCR, calls
2936  * get_file_ext() for any subkey that starts with ".",
2937  * or get_url_association() for any subkey that could
2938  * be a URL schema and has a "URL Protocol" value.
2939  */
2940 static void
2941 read_classes (GWin32RegistryKey *classes_root)
2942 {
2943   GWin32RegistrySubkeyIter class_iter;
2944   const gunichar2 *class_name;
2945   gsize class_name_len;
2946
2947   if (classes_root == NULL)
2948     return;
2949
2950   if (!g_win32_registry_subkey_iter_init (&class_iter, classes_root, NULL))
2951     return;
2952
2953   while (g_win32_registry_subkey_iter_next (&class_iter, TRUE, NULL))
2954     {
2955       if ((!g_win32_registry_subkey_iter_get_name_w (&class_iter,
2956                                                      &class_name,
2957                                                      &class_name_len,
2958                                                      NULL)) ||
2959           (class_name_len <= 1))
2960         continue;
2961
2962       if (class_name[0] == L'.')
2963         {
2964           GWin32RegistryKey *class_key;
2965           GWin32RegistryValueIter iter;
2966           GWin32RegistryKey *open_with_progids;
2967           gunichar2 *value_name;
2968           gsize      value_name_len;
2969
2970           /* Read the data from the HKCR\\.ext (usually proxied
2971            * to another HKCR subkey)
2972            */
2973           get_file_ext (class_name, class_name, NULL, FALSE);
2974
2975           class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
2976
2977           if (class_key == NULL)
2978             continue;
2979
2980           open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL);
2981           g_clear_object (&class_key);
2982
2983           if (open_with_progids == NULL)
2984             continue;
2985
2986           if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL))
2987             {
2988               g_clear_object (&open_with_progids);
2989               continue;
2990             }
2991
2992           /* Read the data for other handlers for this extension */
2993           while (g_win32_registry_value_iter_next (&iter, TRUE, NULL))
2994             {
2995               if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name,
2996                                                            &value_name_len,
2997                                                            NULL) ||
2998                   (value_name_len == 0))
2999                 continue;
3000
3001               get_file_ext (value_name, class_name, NULL, FALSE);
3002             }
3003
3004           g_win32_registry_value_iter_clear (&iter);
3005           g_clear_object (&open_with_progids);
3006         }
3007       else
3008         {
3009           gsize i;
3010           GWin32RegistryKey *class_key;
3011           gboolean success;
3012           GWin32RegistryValueType vtype;
3013           gchar *schema_u8;
3014           gchar *schema_u8_folded;
3015
3016           for (i = 0; i < class_name_len; i++)
3017             if (!iswalpha (class_name[i]))
3018               break;
3019
3020           if (i != class_name_len)
3021             continue;
3022
3023           class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL);
3024
3025           if (class_key == NULL)
3026             continue;
3027
3028           success = g_win32_registry_key_get_value_w (class_key,
3029                                                       NULL,
3030                                                       TRUE,
3031                                                       L"URL Protocol",
3032                                                       &vtype,
3033                                                       NULL,
3034                                                       NULL,
3035                                                       NULL);
3036           g_clear_object (&class_key);
3037
3038           if (!success ||
3039               vtype != G_WIN32_REGISTRY_VALUE_STR)
3040             continue;
3041
3042           if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded))
3043             continue;
3044
3045           get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE);
3046
3047           g_clear_pointer (&schema_u8, g_free);
3048           g_clear_pointer (&schema_u8_folded, g_free);
3049         }
3050     }
3051
3052   g_win32_registry_subkey_iter_clear (&class_iter);
3053 }
3054
3055 /* Iterates over all handlers and over all apps,
3056  * and links handler verbs to apps if a handler
3057  * runs the same executable as one of the app verbs.
3058  */
3059 static void
3060 link_handlers_to_unregistered_apps (void)
3061 {
3062   GHashTableIter iter;
3063   GHashTableIter app_iter;
3064   GWin32AppInfoHandler *handler;
3065   gchar *handler_id_fld;
3066   GWin32AppInfoApplication *app;
3067   gchar *canonical_name_fld;
3068   gchar *appexe_fld_basename;
3069
3070   g_hash_table_iter_init (&iter, handlers);
3071   while (g_hash_table_iter_next (&iter,
3072                                  (gpointer *) &handler_id_fld,
3073                                  (gpointer *) &handler))
3074     {
3075       gsize vi;
3076
3077       if (handler->uwp_aumid != NULL)
3078         continue;
3079
3080       for (vi = 0; vi < handler->verbs->len; vi++)
3081         {
3082           GWin32AppInfoShellVerb *handler_verb;
3083           const gchar *handler_exe_basename;
3084           enum
3085             {
3086               SH_UNKNOWN,
3087               GOT_SH_INFO,
3088               ERROR_GETTING_SH_INFO,
3089             } have_stat_handler = SH_UNKNOWN;
3090           GWin32PrivateStat handler_verb_exec_info;
3091
3092           handler_verb = _verb_idx (handler->verbs, vi);
3093
3094           if (handler_verb->app != NULL)
3095             continue;
3096
3097           handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1);
3098           g_hash_table_iter_init (&app_iter, apps_by_id);
3099
3100           while (g_hash_table_iter_next (&app_iter,
3101                                          (gpointer *) &canonical_name_fld,
3102                                          (gpointer *) &app))
3103             {
3104               GWin32AppInfoShellVerb *app_verb;
3105               gsize ai;
3106
3107               if (app->is_uwp)
3108                 continue;
3109
3110               for (ai = 0; ai < app->verbs->len; ai++)
3111                 {
3112                   GWin32PrivateStat app_verb_exec_info;
3113                   const gchar *app_exe_basename;
3114                   app_verb = _verb_idx (app->verbs, ai);
3115
3116                   app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1);
3117
3118                   /* First check that the executable paths are identical */
3119                   if (g_strcmp0 (app_verb->executable_folded, handler_verb->executable_folded) != 0)
3120                     {
3121                       /* If not, check the basenames. If they are different, don't bother
3122                        * with further checks.
3123                        */
3124                       if (g_strcmp0 (app_exe_basename, handler_exe_basename) != 0)
3125                         continue;
3126
3127                       /* Get filesystem IDs for both files.
3128                        * For the handler that is attempted only once.
3129                        */
3130                       if (have_stat_handler == SH_UNKNOWN)
3131                         {
3132                           if (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (handler_verb->executable_folded,
3133                                                                      &handler_verb_exec_info) == 0)
3134                             have_stat_handler = GOT_SH_INFO;
3135                           else
3136                             have_stat_handler = ERROR_GETTING_SH_INFO;
3137                         }
3138
3139                       if (have_stat_handler != GOT_SH_INFO ||
3140                           (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (app_verb->executable_folded,
3141                                                                   &app_verb_exec_info) != 0) ||
3142                           app_verb_exec_info.file_index != handler_verb_exec_info.file_index)
3143                         continue;
3144                     }
3145
3146                   handler_verb->app = g_object_ref (app);
3147                   break;
3148                 }
3149             }
3150
3151           if (handler_verb->app != NULL)
3152             continue;
3153
3154           g_hash_table_iter_init (&app_iter, apps_by_exe);
3155
3156           while (g_hash_table_iter_next (&app_iter,
3157                                          (gpointer *) &appexe_fld_basename,
3158                                          (gpointer *) &app))
3159             {
3160               if (app->is_uwp)
3161                 continue;
3162
3163               /* Use basename because apps_by_exe only has basenames */
3164               if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0)
3165                 continue;
3166
3167               handler_verb->app = g_object_ref (app);
3168               break;
3169             }
3170         }
3171     }
3172 }
3173
3174 /* Finds all .ext and schema: handler verbs that have no app linked to them,
3175  * creates a "fake app" object and links these verbs to these
3176  * objects. Objects are identified by the full path to
3177  * the executable being run, thus multiple different invocations
3178  * get grouped in a more-or-less natural way.
3179  * The iteration goes separately over .ext and schema: handlers
3180  * (instead of the global handlers hashmap) to allow us to
3181  * put the handlers into supported_urls or supported_exts as
3182  * needed (handler objects themselves have no knowledge of extensions
3183  * and/or URLs they are associated with).
3184  */
3185 static void
3186 link_handlers_to_fake_apps (void)
3187 {
3188   GHashTableIter iter;
3189   GHashTableIter handler_iter;
3190   gchar *extension_utf8_folded;
3191   GWin32AppInfoFileExtension *file_extn;
3192   gchar *handler_id_fld;
3193   GWin32AppInfoHandler *handler;
3194   gchar *url_utf8_folded;
3195   GWin32AppInfoURLSchema *schema;
3196
3197   g_hash_table_iter_init (&iter, extensions);
3198   while (g_hash_table_iter_next (&iter,
3199                                  (gpointer *) &extension_utf8_folded,
3200                                  (gpointer *) &file_extn))
3201     {
3202       g_hash_table_iter_init (&handler_iter, file_extn->handlers);
3203       while (g_hash_table_iter_next (&handler_iter,
3204                                      (gpointer *) &handler_id_fld,
3205                                      (gpointer *) &handler))
3206         {
3207           gsize vi;
3208
3209           if (handler->uwp_aumid != NULL)
3210             continue;
3211
3212           for (vi = 0; vi < handler->verbs->len; vi++)
3213             {
3214               GWin32AppInfoShellVerb *handler_verb;
3215               GWin32AppInfoApplication *app;
3216               gunichar2 *exename_utf16;
3217               handler_verb = _verb_idx (handler->verbs, vi);
3218
3219               if (handler_verb->app != NULL)
3220                 continue;
3221
3222               exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL);
3223               if (exename_utf16 == NULL)
3224                 continue;
3225
3226               app = get_app_object (fake_apps,
3227                                     exename_utf16,
3228                                     handler_verb->executable,
3229                                     handler_verb->executable_folded,
3230                                     FALSE,
3231                                     FALSE,
3232                                     FALSE);
3233               g_clear_pointer (&exename_utf16, g_free);
3234               handler_verb->app = g_object_ref (app);
3235
3236               app_add_verb (app,
3237                             app,
3238                             handler_verb->verb_name,
3239                             handler_verb->command,
3240                             handler_verb->command_utf8,
3241                             handler_verb->verb_displayname,
3242                             TRUE,
3243                             TRUE);
3244               g_hash_table_insert (app->supported_exts,
3245                                    g_strdup (extension_utf8_folded),
3246                                    g_object_ref (handler));
3247             }
3248         }
3249     }
3250
3251   g_hash_table_iter_init (&iter, urls);
3252   while (g_hash_table_iter_next (&iter,
3253                                  (gpointer *) &url_utf8_folded,
3254                                  (gpointer *) &schema))
3255     {
3256       g_hash_table_iter_init (&handler_iter, schema->handlers);
3257       while (g_hash_table_iter_next (&handler_iter,
3258                                      (gpointer *) &handler_id_fld,
3259                                      (gpointer *) &handler))
3260         {
3261           gsize vi;
3262
3263           if (handler->uwp_aumid != NULL)
3264             continue;
3265
3266           for (vi = 0; vi < handler->verbs->len; vi++)
3267             {
3268               GWin32AppInfoShellVerb *handler_verb;
3269               GWin32AppInfoApplication *app;
3270               gchar *command_utf8_folded;
3271               handler_verb = _verb_idx (handler->verbs, vi);
3272
3273               if (handler_verb->app != NULL)
3274                 continue;
3275
3276               command_utf8_folded = g_utf8_casefold (handler_verb->command_utf8, -1);
3277               app = get_app_object (fake_apps,
3278                                     handler_verb->command,
3279                                     handler_verb->command_utf8,
3280                                     command_utf8_folded,
3281                                     FALSE,
3282                                     FALSE,
3283                                     FALSE);
3284               g_clear_pointer (&command_utf8_folded, g_free);
3285               handler_verb->app = g_object_ref (app);
3286
3287               app_add_verb (app,
3288                             app,
3289                             handler_verb->verb_name,
3290                             handler_verb->command,
3291                             handler_verb->command_utf8,
3292                             handler_verb->verb_displayname,
3293                             TRUE,
3294                             TRUE);
3295               g_hash_table_insert (app->supported_urls,
3296                                    g_strdup (url_utf8_folded),
3297                                    g_object_ref (handler));
3298             }
3299         }
3300     }
3301 }
3302
3303 static GWin32AppInfoHandler *
3304 find_uwp_handler_for_ext (GWin32AppInfoFileExtension *file_extn,
3305                           const gunichar2            *app_user_model_id)
3306 {
3307   GHashTableIter handler_iter;
3308   gchar *handler_id_fld;
3309   GWin32AppInfoHandler *handler;
3310
3311   g_hash_table_iter_init (&handler_iter, file_extn->handlers);
3312   while (g_hash_table_iter_next (&handler_iter,
3313                                  (gpointer *) &handler_id_fld,
3314                                  (gpointer *) &handler))
3315     {
3316       if (handler->uwp_aumid == NULL)
3317         continue;
3318
3319       if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3320         return handler;
3321     }
3322
3323   return NULL;
3324 }
3325
3326 static GWin32AppInfoHandler *
3327 find_uwp_handler_for_schema (GWin32AppInfoURLSchema *schema,
3328                              const gunichar2        *app_user_model_id)
3329 {
3330   GHashTableIter handler_iter;
3331   gchar *handler_id_fld;
3332   GWin32AppInfoHandler *handler;
3333
3334   g_hash_table_iter_init (&handler_iter, schema->handlers);
3335   while (g_hash_table_iter_next (&handler_iter,
3336                                  (gpointer *) &handler_id_fld,
3337                                  (gpointer *) &handler))
3338     {
3339       if (handler->uwp_aumid == NULL)
3340         continue;
3341
3342       if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0)
3343         return handler;
3344     }
3345
3346   return NULL;
3347 }
3348
3349 static gboolean
3350 uwp_package_cb (gpointer         user_data,
3351                 const gunichar2 *full_package_name,
3352                 const gunichar2 *package_name,
3353                 const gunichar2 *app_user_model_id,
3354                 gboolean         show_in_applist,
3355                 GPtrArray       *supported_extgroups,
3356                 GPtrArray       *supported_protocols)
3357 {
3358   gint i, i_verb, i_ext;
3359   gint extensions_considered;
3360   GWin32AppInfoApplication *app;
3361   gchar *app_user_model_id_u8;
3362   gchar *app_user_model_id_u8_folded;
3363   GHashTableIter iter;
3364   GWin32AppInfoHandler *ext;
3365   GWin32AppInfoHandler *url;
3366
3367   if (!g_utf16_to_utf8_and_fold (app_user_model_id,
3368                                  -1,
3369                                  &app_user_model_id_u8,
3370                                  &app_user_model_id_u8_folded))
3371     return TRUE;
3372
3373   app = get_app_object (apps_by_id,
3374                         app_user_model_id,
3375                         app_user_model_id_u8,
3376                         app_user_model_id_u8_folded,
3377                         TRUE,
3378                         FALSE,
3379                         TRUE);
3380
3381   extensions_considered = 0;
3382
3383   for (i = 0; i < supported_extgroups->len; i++)
3384     {
3385       GWin32PackageExtGroup *grp = (GWin32PackageExtGroup *) g_ptr_array_index (supported_extgroups, i);
3386
3387       extensions_considered += grp->extensions->len;
3388
3389       for (i_ext = 0; i_ext < grp->extensions->len; i_ext++)
3390         {
3391           wchar_t *ext = (wchar_t *) g_ptr_array_index (grp->extensions, i_ext);
3392           gchar *ext_u8;
3393           gchar *ext_u8_folded;
3394           GWin32AppInfoFileExtension *file_extn;
3395           GWin32AppInfoHandler *handler_rec;
3396
3397           if (!g_utf16_to_utf8_and_fold (ext,
3398                                          -1,
3399                                          &ext_u8,
3400                                          &ext_u8_folded))
3401             continue;
3402
3403           file_extn = get_ext_object (ext, ext_u8, ext_u8_folded);
3404           g_free (ext_u8);
3405           handler_rec = find_uwp_handler_for_ext (file_extn, app_user_model_id);
3406
3407           if (handler_rec == NULL)
3408             {
3409               /* Use AppUserModelId as the ID of the new fake handler */
3410               handler_rec = get_handler_object (app_user_model_id_u8_folded,
3411                                                 NULL,
3412                                                 app_user_model_id,
3413                                                 app_user_model_id);
3414               g_hash_table_insert (file_extn->handlers,
3415                                    g_strdup (app_user_model_id_u8_folded),
3416                                    g_object_ref (handler_rec));
3417             }
3418
3419           if (file_extn->chosen_handler == NULL)
3420             g_set_object (&file_extn->chosen_handler, handler_rec);
3421
3422           /* This is somewhat wasteful, but for 100% correct handling
3423            * we need to remember which extensions (handlers) support
3424            * which verbs, and each handler gets its own copy of the
3425            * verb object, since our design is handler-centric,
3426            * not verb-centric. The app also gets a list of verbs,
3427            * but without handlers it would have no idea which
3428            * verbs can be used with which extensions.
3429            */
3430           for (i_verb = 0; i_verb < grp->verbs->len; i_verb++)
3431             {
3432               wchar_t *verb = NULL;
3433
3434               verb = (wchar_t *) g_ptr_array_index (grp->verbs, i_verb);
3435               /* *_add_verb() functions are no-ops when a verb already exists,
3436                * so we're free to call them as many times as we want.
3437                */
3438               uwp_handler_add_verb (handler_rec,
3439                                     app,
3440                                     verb,
3441                                     NULL,
3442                                     FALSE);
3443             }
3444
3445           g_hash_table_insert (app->supported_exts,
3446                                g_steal_pointer (&ext_u8_folded),
3447                                g_object_ref (handler_rec));
3448         }
3449     }
3450
3451   g_hash_table_iter_init (&iter, app->supported_exts);
3452
3453   /* Pile up all handler verbs into the app too,
3454    * for cases when we don't have a ref to a handler.
3455    */
3456   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &ext))
3457     {
3458       gint i_hverb;
3459
3460       if (!ext)
3461         continue;
3462
3463       for (i_hverb = 0; i_hverb < ext->verbs->len; i_hverb++)
3464         {
3465           GWin32AppInfoShellVerb *handler_verb;
3466
3467           handler_verb = _verb_idx (ext->verbs, i_hverb);
3468           uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname);
3469           if (handler_verb->app == NULL && handler_verb->is_uwp)
3470             handler_verb->app = g_object_ref (app);
3471         }
3472     }
3473
3474   if (app->verbs->len == 0 && extensions_considered > 0)
3475     g_warning ("Unexpectedly, UWP app `%S' (AUMId `%s') supports %d extensions but has no verbs",
3476                full_package_name, app_user_model_id_u8, extensions_considered);
3477
3478   for (i = 0; i < supported_protocols->len; i++)
3479     {
3480       wchar_t *proto = (wchar_t *) g_ptr_array_index (supported_protocols, i);
3481       gchar *proto_u8;
3482       gchar *proto_u8_folded;
3483       GWin32AppInfoURLSchema *schema_rec;
3484       GWin32AppInfoHandler *handler_rec;
3485
3486       if (!g_utf16_to_utf8_and_fold (proto,
3487                                      -1,
3488                                      &proto_u8,
3489                                      &proto_u8_folded))
3490         continue;
3491
3492       schema_rec = get_schema_object (proto,
3493                                       proto_u8,
3494                                       proto_u8_folded);
3495
3496       g_free (proto_u8);
3497
3498       handler_rec = find_uwp_handler_for_schema (schema_rec, app_user_model_id);
3499
3500       if (handler_rec == NULL)
3501         {
3502           /* Use AppUserModelId as the ID of the new fake handler */
3503           handler_rec = get_handler_object (app_user_model_id_u8_folded,
3504                                             NULL,
3505                                             app_user_model_id,
3506                                             app_user_model_id);
3507
3508           g_hash_table_insert (schema_rec->handlers,
3509                                g_strdup (app_user_model_id_u8_folded),
3510                                g_object_ref (handler_rec));
3511         }
3512
3513       if (schema_rec->chosen_handler == NULL)
3514         g_set_object (&schema_rec->chosen_handler, handler_rec);
3515
3516       /* Technically, UWP apps don't use verbs for URIs,
3517        * but we only store an app field in verbs,
3518        * so each UWP URI handler has to have one.
3519        * Let's call it "open".
3520        */
3521       uwp_handler_add_verb (handler_rec,
3522                             app,
3523                             L"open",
3524                             NULL,
3525                             TRUE);
3526
3527       g_hash_table_insert (app->supported_urls,
3528                            g_steal_pointer (&proto_u8_folded),
3529                            g_object_ref (handler_rec));
3530     }
3531
3532   g_hash_table_iter_init (&iter, app->supported_urls);
3533
3534   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &url))
3535     {
3536       gint i_hverb;
3537
3538       if (!url)
3539         continue;
3540
3541       for (i_hverb = 0; i_hverb < url->verbs->len; i_hverb++)
3542         {
3543           GWin32AppInfoShellVerb *handler_verb;
3544
3545           handler_verb = _verb_idx (url->verbs, i_hverb);
3546           uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname);
3547           if (handler_verb->app == NULL && handler_verb->is_uwp)
3548             handler_verb->app = g_object_ref (app);
3549         }
3550     }
3551
3552   g_free (app_user_model_id_u8);
3553   g_free (app_user_model_id_u8_folded);
3554
3555   return TRUE;
3556 }
3557
3558 /* Calls SHLoadIndirectString() in a loop to resolve
3559  * a string in @{...} format (also supports other indirect
3560  * strings, but we aren't using it for those).
3561  * Consumes the input, but may return it unmodified
3562  * (not an indirect string). May return %NULL (the string
3563  * is indirect, but the OS failed to load it).
3564  */
3565 static gunichar2 *
3566 resolve_string (gunichar2 *at_string)
3567 {
3568   HRESULT hr;
3569   gunichar2 *result = NULL;
3570   gsize result_size;
3571   /* This value is arbitrary */
3572   const gsize reasonable_size_limit = 8192;
3573
3574   if (at_string == NULL || at_string[0] != L'@')
3575     return at_string;
3576
3577   /* In case of a no-op @at_string will be copied into the output,
3578    * buffer so allocate at least that much.
3579    */
3580   result_size = wcslen (at_string) + 1;
3581
3582   while (TRUE)
3583     {
3584       result = g_renew (gunichar2, result, result_size);
3585       /* Since there's no built-in way to detect too small buffer size,
3586        * we do so by putting a sentinel at the end of the buffer.
3587        * If it's 0 (result is always 0-terminated, even if the buffer
3588        * is too small), then try larger buffer.
3589        */
3590       result[result_size - 1] = 0xff;
3591       /* This string accepts size in characters, not bytes. */
3592       hr = SHLoadIndirectString (at_string, result, result_size, NULL);
3593       if (!SUCCEEDED (hr))
3594         {
3595           g_free (result);
3596           g_free (at_string);
3597           return NULL;
3598         }
3599       else if (result[result_size - 1] != 0 ||
3600                result_size >= reasonable_size_limit)
3601         {
3602           /* Now that the length is known, allocate the exact amount */
3603           gunichar2 *copy = g_wcsdup (result, -1);
3604           g_free (result);
3605           g_free (at_string);
3606           return copy;
3607         }
3608
3609       result_size *= 2;
3610     }
3611
3612   g_assert_not_reached ();
3613
3614   return at_string;
3615 }
3616
3617 static void
3618 grab_registry_string (GWin32RegistryKey  *handler_appkey,
3619                       const gunichar2    *value_name,
3620                       gunichar2         **destination,
3621                       gchar             **destination_u8)
3622 {
3623   gunichar2 *value;
3624   gsize value_size;
3625   GWin32RegistryValueType vtype;
3626   const gunichar2 *ms_resource_prefix = L"ms-resource:";
3627   gsize ms_resource_prefix_len = wcslen (ms_resource_prefix);
3628
3629   /* Right now this function is not used without destination,
3630    * enforce this. destination_u8 is optional.
3631    */
3632   g_assert (destination != NULL);
3633
3634   if (*destination != NULL)
3635     return;
3636
3637   if (g_win32_registry_key_get_value_w (handler_appkey,
3638                                         NULL,
3639                                         TRUE,
3640                                         value_name,
3641                                         &vtype,
3642                                         (void **) &value,
3643                                         &value_size,
3644                                         NULL) &&
3645       vtype != G_WIN32_REGISTRY_VALUE_STR)
3646     g_clear_pointer (&value, g_free);
3647
3648   /* There's no way for us to resolve "ms-resource:..." strings */
3649   if (value != NULL &&
3650       value_size >= ms_resource_prefix_len &&
3651       memcmp (value,
3652               ms_resource_prefix,
3653               ms_resource_prefix_len * sizeof (gunichar2)) == 0)
3654     g_clear_pointer (&value, g_free);
3655
3656   if (value == NULL)
3657     return;
3658
3659   *destination = resolve_string (g_steal_pointer (&value));
3660
3661   if (*destination == NULL)
3662     return;
3663
3664   if (destination_u8)
3665     *destination_u8 = g_utf16_to_utf8 (*destination, -1, NULL, NULL, NULL);
3666 }
3667
3668 static void
3669 read_uwp_handler_info (void)
3670 {
3671   GHashTableIter iter;
3672   GWin32RegistryKey *handler_appkey;
3673   gunichar2 *aumid;
3674
3675   g_hash_table_iter_init (&iter, uwp_handler_table);
3676
3677   while (g_hash_table_iter_next (&iter, (gpointer *) &handler_appkey, (gpointer *) &aumid))
3678     {
3679       gchar *aumid_u8_folded;
3680       GWin32AppInfoApplication *app;
3681
3682       if (!g_utf16_to_utf8_and_fold (aumid,
3683                                      -1,
3684                                      NULL,
3685                                      &aumid_u8_folded))
3686         continue;
3687
3688       app = g_hash_table_lookup (apps_by_id, aumid_u8_folded);
3689       g_clear_pointer (&aumid_u8_folded, g_free);
3690
3691       if (app == NULL)
3692         continue;
3693
3694       grab_registry_string (handler_appkey, L"ApplicationDescription", &app->description, &app->description_u8);
3695       grab_registry_string (handler_appkey, L"ApplicationName", &app->localized_pretty_name, &app->localized_pretty_name_u8);
3696       /* TODO: ApplicationIcon value (usually also @{...}) resolves into
3697        * an image (PNG only?) with implicit multiple variants (scale, size, etc).
3698        */
3699     }
3700 }
3701
3702 static void
3703 update_registry_data (void)
3704 {
3705   guint i;
3706   GPtrArray *capable_apps_keys;
3707   GPtrArray *user_capable_apps_keys;
3708   GPtrArray *priority_capable_apps_keys;
3709   GWin32RegistryKey *url_associations;
3710   GWin32RegistryKey *file_exts;
3711   GWin32RegistryKey *classes_root;
3712   DWORD collect_start, collect_end, alloc_end, capable_end, url_end, ext_end, exeapp_end, classes_end, uwp_end, postproc_end;
3713   GError *error = NULL;
3714
3715   url_associations =
3716       g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3717                                    NULL);
3718   file_exts =
3719       g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
3720                                    NULL);
3721   classes_root = g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT", NULL);
3722
3723   capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3724   user_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3725   priority_capable_apps_keys = g_ptr_array_new_with_free_func (g_free);
3726
3727   g_clear_pointer (&apps_by_id, g_hash_table_destroy);
3728   g_clear_pointer (&apps_by_exe, g_hash_table_destroy);
3729   g_clear_pointer (&fake_apps, g_hash_table_destroy);
3730   g_clear_pointer (&urls, g_hash_table_destroy);
3731   g_clear_pointer (&extensions, g_hash_table_destroy);
3732   g_clear_pointer (&handlers, g_hash_table_destroy);
3733
3734   collect_start = GetTickCount ();
3735   collect_capable_apps_from_clients (capable_apps_keys,
3736                                      priority_capable_apps_keys,
3737                                      FALSE);
3738   collect_capable_apps_from_clients (user_capable_apps_keys,
3739                                      priority_capable_apps_keys,
3740                                      TRUE);
3741   collect_capable_apps_from_registered_apps (user_capable_apps_keys,
3742                                              TRUE);
3743   collect_capable_apps_from_registered_apps (capable_apps_keys,
3744                                              FALSE);
3745   collect_end = GetTickCount ();
3746
3747   apps_by_id =
3748       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3749   apps_by_exe =
3750       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3751   fake_apps =
3752       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3753   urls =
3754       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3755   extensions =
3756       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3757   handlers =
3758       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
3759   uwp_handler_table =
3760       g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free);
3761   alloc_end = GetTickCount ();
3762
3763   for (i = 0; i < priority_capable_apps_keys->len; i++)
3764     read_capable_app (g_ptr_array_index (priority_capable_apps_keys, i),
3765                       TRUE,
3766                       TRUE);
3767   for (i = 0; i < user_capable_apps_keys->len; i++)
3768     read_capable_app (g_ptr_array_index (user_capable_apps_keys, i),
3769                       TRUE,
3770                       FALSE);
3771   for (i = 0; i < capable_apps_keys->len; i++)
3772     read_capable_app (g_ptr_array_index (capable_apps_keys, i),
3773                       FALSE,
3774                       FALSE);
3775   capable_end = GetTickCount ();
3776
3777   read_urls (url_associations);
3778   url_end = GetTickCount ();
3779   read_exts (file_exts);
3780   ext_end = GetTickCount ();
3781   read_exeapps ();
3782   exeapp_end = GetTickCount ();
3783   read_classes (classes_root);
3784   classes_end = GetTickCount ();
3785
3786   if (!g_win32_package_parser_enum_packages (uwp_package_cb, NULL, &error))
3787     {
3788       g_debug ("Unable to get UWP apps: %s", error->message);
3789       g_clear_error (&error);
3790     }
3791
3792   read_uwp_handler_info ();
3793
3794   uwp_end = GetTickCount ();
3795   link_handlers_to_unregistered_apps ();
3796   link_handlers_to_fake_apps ();
3797   postproc_end = GetTickCount ();
3798
3799   g_debug ("Collecting capable appnames: %lums\n"
3800            "Allocating hashtables:...... %lums\n"
3801            "Reading capable apps:        %lums\n"
3802            "Reading URL associations:... %lums\n"
3803            "Reading extension assocs:    %lums\n"
3804            "Reading exe-only apps:...... %lums\n"
3805            "Reading classes:             %lums\n"
3806            "Reading UWP apps:            %lums\n"
3807            "Postprocessing:..............%lums\n"
3808            "TOTAL:                       %lums",
3809            collect_end - collect_start,
3810            alloc_end - collect_end,
3811            capable_end - alloc_end,
3812            url_end - capable_end,
3813            ext_end - url_end,
3814            exeapp_end - ext_end,
3815            classes_end - exeapp_end,
3816            uwp_end - classes_end,
3817            postproc_end - uwp_end,
3818            postproc_end - collect_start);
3819
3820   g_clear_object (&classes_root);
3821   g_clear_object (&url_associations);
3822   g_clear_object (&file_exts);
3823   g_ptr_array_free (capable_apps_keys, TRUE);
3824   g_ptr_array_free (user_capable_apps_keys, TRUE);
3825   g_ptr_array_free (priority_capable_apps_keys, TRUE);
3826   g_hash_table_unref (uwp_handler_table);
3827
3828   return;
3829 }
3830
3831 /* This function is called when any of our registry watchers detect
3832  * changes in the registry.
3833  */
3834 static void
3835 keys_updated (GWin32RegistryKey  *key,
3836               gpointer            user_data)
3837 {
3838   /* Indicate the tree as not up-to-date, push a new job for the AppInfo thread */
3839   g_atomic_int_inc (&gio_win32_appinfo_update_counter);
3840   /* We don't use the data pointer, but it must be non-NULL */
3841   g_thread_pool_push (gio_win32_appinfo_threadpool, (gpointer) keys_updated, NULL);
3842 }
3843
3844 static void
3845 watch_keys (void)
3846 {
3847   if (url_associations_key)
3848     g_win32_registry_key_watch (url_associations_key,
3849                                 TRUE,
3850                                 G_WIN32_REGISTRY_WATCH_NAME |
3851                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3852                                 G_WIN32_REGISTRY_WATCH_VALUES,
3853                                 keys_updated,
3854                                 NULL,
3855                                 NULL);
3856
3857   if (file_exts_key)
3858     g_win32_registry_key_watch (file_exts_key,
3859                                 TRUE,
3860                                 G_WIN32_REGISTRY_WATCH_NAME |
3861                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3862                                 G_WIN32_REGISTRY_WATCH_VALUES,
3863                                 keys_updated,
3864                                 NULL,
3865                                 NULL);
3866
3867   if (user_clients_key)
3868     g_win32_registry_key_watch (user_clients_key,
3869                                 TRUE,
3870                                 G_WIN32_REGISTRY_WATCH_NAME |
3871                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3872                                 G_WIN32_REGISTRY_WATCH_VALUES,
3873                                 keys_updated,
3874                                 NULL,
3875                                 NULL);
3876
3877   if (system_clients_key)
3878     g_win32_registry_key_watch (system_clients_key,
3879                                 TRUE,
3880                                 G_WIN32_REGISTRY_WATCH_NAME |
3881                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3882                                 G_WIN32_REGISTRY_WATCH_VALUES,
3883                                 keys_updated,
3884                                 NULL,
3885                                 NULL);
3886
3887   if (applications_key)
3888     g_win32_registry_key_watch (applications_key,
3889                                 TRUE,
3890                                 G_WIN32_REGISTRY_WATCH_NAME |
3891                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3892                                 G_WIN32_REGISTRY_WATCH_VALUES,
3893                                 keys_updated,
3894                                 NULL,
3895                                 NULL);
3896
3897   if (user_registered_apps_key)
3898     g_win32_registry_key_watch (user_registered_apps_key,
3899                                 TRUE,
3900                                 G_WIN32_REGISTRY_WATCH_NAME |
3901                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3902                                 G_WIN32_REGISTRY_WATCH_VALUES,
3903                                 keys_updated,
3904                                 NULL,
3905                                 NULL);
3906
3907   if (system_registered_apps_key)
3908     g_win32_registry_key_watch (system_registered_apps_key,
3909                                 TRUE,
3910                                 G_WIN32_REGISTRY_WATCH_NAME |
3911                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3912                                 G_WIN32_REGISTRY_WATCH_VALUES,
3913                                 keys_updated,
3914                                 NULL,
3915                                 NULL);
3916
3917   if (classes_root_key)
3918     g_win32_registry_key_watch (classes_root_key,
3919                                 FALSE,
3920                                 G_WIN32_REGISTRY_WATCH_NAME |
3921                                 G_WIN32_REGISTRY_WATCH_ATTRIBUTES |
3922                                 G_WIN32_REGISTRY_WATCH_VALUES,
3923                                 keys_updated,
3924                                 NULL,
3925                                 NULL);
3926 }
3927
3928 /* This is the main function of the AppInfo thread */
3929 static void
3930 gio_win32_appinfo_thread_func (gpointer data,
3931                                gpointer user_data)
3932 {
3933   gint saved_counter;
3934   g_mutex_lock (&gio_win32_appinfo_mutex);
3935   saved_counter = g_atomic_int_get (&gio_win32_appinfo_update_counter);
3936
3937   if (saved_counter > 0)
3938     update_registry_data ();
3939   /* If the counter didn't change while we were working, then set it to zero.
3940    * Otherwise we need to rebuild the tree again, so keep it greater than zero.
3941    * Numeric value doesn't matter - even if we're asked to rebuild N times,
3942    * we just need to rebuild once, and as long as there were no new rebuild
3943    * requests while we were working, we're done.
3944    */
3945   if (g_atomic_int_compare_and_exchange  (&gio_win32_appinfo_update_counter,
3946                                           saved_counter,
3947                                           0))
3948     g_cond_broadcast (&gio_win32_appinfo_cond);
3949
3950   g_mutex_unlock (&gio_win32_appinfo_mutex);
3951 }
3952
3953 /* Initializes Windows AppInfo. Creates the registry watchers,
3954  * the AppInfo thread, and initiates an update of the AppInfo tree.
3955  * Called with do_wait = `FALSE` at startup to prevent it from
3956  * blocking until the tree is updated. All subsequent calls
3957  * from everywhere else are made with do_wait = `TRUE`, blocking
3958  * until the tree is re-built (if needed).
3959  */
3960 void
3961 gio_win32_appinfo_init (gboolean do_wait)
3962 {
3963   static gsize initialized;
3964
3965   if (g_once_init_enter (&initialized))
3966     {
3967       url_associations_key =
3968           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations",
3969                                        NULL);
3970       file_exts_key =
3971           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts",
3972                                        NULL);
3973       user_clients_key =
3974           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Clients",
3975                                        NULL);
3976       system_clients_key =
3977           g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\Clients",
3978                                        NULL);
3979       applications_key =
3980           g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications",
3981                                        NULL);
3982       user_registered_apps_key =
3983           g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications",
3984                                        NULL);
3985       system_registered_apps_key =
3986           g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications",
3987                                        NULL);
3988       classes_root_key =
3989           g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT",
3990                                        NULL);
3991
3992       watch_keys ();
3993
3994       /* We don't really require an exclusive pool, but the implementation
3995        * details might cause the g_thread_pool_push() call below to block
3996        * if the pool is not exclusive (specifically - for POSIX threads backend
3997        * lacking thread scheduler settings).
3998        */
3999       gio_win32_appinfo_threadpool = g_thread_pool_new (gio_win32_appinfo_thread_func,
4000                                                         NULL,
4001                                                         1,
4002                                                         TRUE,
4003                                                         NULL);
4004       g_mutex_init (&gio_win32_appinfo_mutex);
4005       g_cond_init (&gio_win32_appinfo_cond);
4006       g_atomic_int_set (&gio_win32_appinfo_update_counter, 1);
4007       /* Trigger initial tree build. Fake data pointer. */
4008       g_thread_pool_push (gio_win32_appinfo_threadpool, (gpointer) keys_updated, NULL);
4009
4010       g_once_init_leave (&initialized, TRUE);
4011     }
4012
4013   if (!do_wait)
4014     return;
4015
4016   /* Previously, we checked each of the watched keys here.
4017    * Now we just look at the update counter, because each key
4018    * has a change callback keys_updated, which increments this counter.
4019    */
4020   if (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0)
4021     {
4022       g_mutex_lock (&gio_win32_appinfo_mutex);
4023       while (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0)
4024         g_cond_wait (&gio_win32_appinfo_cond, &gio_win32_appinfo_mutex);
4025       watch_keys ();
4026       g_mutex_unlock (&gio_win32_appinfo_mutex);
4027     }
4028 }
4029
4030
4031 static void g_win32_app_info_iface_init (GAppInfoIface *iface);
4032
4033 struct _GWin32AppInfo
4034 {
4035   GObject parent_instance;
4036
4037   /*<private>*/
4038   gchar **supported_types;
4039
4040   GWin32AppInfoApplication *app;
4041
4042   GWin32AppInfoHandler *handler;
4043
4044   guint startup_notify : 1;
4045 };
4046
4047 G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT,
4048                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
4049                                                 g_win32_app_info_iface_init))
4050
4051
4052 static void
4053 g_win32_app_info_finalize (GObject *object)
4054 {
4055   GWin32AppInfo *info;
4056
4057   info = G_WIN32_APP_INFO (object);
4058
4059   g_clear_pointer (&info->supported_types, g_strfreev);
4060   g_clear_object (&info->app);
4061   g_clear_object (&info->handler);
4062
4063   G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize (object);
4064 }
4065
4066 static void
4067 g_win32_app_info_class_init (GWin32AppInfoClass *klass)
4068 {
4069   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
4070
4071   gobject_class->finalize = g_win32_app_info_finalize;
4072 }
4073
4074 static void
4075 g_win32_app_info_init (GWin32AppInfo *local)
4076 {
4077 }
4078
4079 static GAppInfo *
4080 g_win32_app_info_new_from_app (GWin32AppInfoApplication *app,
4081                                GWin32AppInfoHandler     *handler)
4082 {
4083   GWin32AppInfo *new_info;
4084   GHashTableIter iter;
4085   gpointer ext;
4086   int i;
4087
4088   new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4089
4090   new_info->app = g_object_ref (app);
4091
4092   gio_win32_appinfo_init (TRUE);
4093   g_mutex_lock (&gio_win32_appinfo_mutex);
4094
4095   i = 0;
4096   g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4097
4098   while (g_hash_table_iter_next (&iter, &ext, NULL))
4099     {
4100       if (ext)
4101         i += 1;
4102     }
4103
4104   new_info->supported_types = g_new (gchar *, i + 1);
4105
4106   i = 0;
4107   g_hash_table_iter_init (&iter, new_info->app->supported_exts);
4108
4109   while (g_hash_table_iter_next (&iter, &ext, NULL))
4110     {
4111       if (!ext)
4112         continue;
4113
4114       new_info->supported_types[i] = g_strdup ((gchar *) ext);
4115       i += 1;
4116     }
4117
4118   g_mutex_unlock (&gio_win32_appinfo_mutex);
4119
4120   new_info->supported_types[i] = NULL;
4121
4122   new_info->handler = handler ? g_object_ref (handler) : NULL;
4123
4124   return G_APP_INFO (new_info);
4125 }
4126
4127
4128 static GAppInfo *
4129 g_win32_app_info_dup (GAppInfo *appinfo)
4130 {
4131   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4132   GWin32AppInfo *new_info;
4133
4134   new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
4135
4136   if (info->app)
4137     new_info->app = g_object_ref (info->app);
4138
4139   if (info->handler)
4140     new_info->handler = g_object_ref (info->handler);
4141
4142   new_info->startup_notify = info->startup_notify;
4143
4144   if (info->supported_types)
4145     {
4146       int i;
4147
4148       for (i = 0; info->supported_types[i]; i++)
4149         break;
4150
4151       new_info->supported_types = g_new (gchar *, i + 1);
4152
4153       for (i = 0; info->supported_types[i]; i++)
4154         new_info->supported_types[i] = g_strdup (info->supported_types[i]);
4155
4156       new_info->supported_types[i] = NULL;
4157     }
4158
4159   return G_APP_INFO (new_info);
4160 }
4161
4162 static gboolean
4163 g_win32_app_info_equal (GAppInfo *appinfo1,
4164                         GAppInfo *appinfo2)
4165 {
4166   GWin32AppInfoShellVerb *shverb1 = NULL;
4167   GWin32AppInfoShellVerb *shverb2 = NULL;
4168   GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1);
4169   GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2);
4170   GWin32AppInfoApplication *app1 = info1->app;
4171   GWin32AppInfoApplication *app2 = info2->app;
4172
4173   if (app1 == NULL ||
4174       app2 == NULL)
4175     return info1 == info2;
4176
4177   if (app1->canonical_name_folded != NULL &&
4178       app2->canonical_name_folded != NULL)
4179     return (g_strcmp0 (app1->canonical_name_folded,
4180                        app2->canonical_name_folded)) == 0;
4181
4182   if (app1->verbs->len > 0 &&
4183       app2->verbs->len > 0)
4184     {
4185       shverb1 = _verb_idx (app1->verbs, 0);
4186       shverb2 = _verb_idx (app2->verbs, 0);
4187       if (shverb1->executable_folded != NULL &&
4188           shverb2->executable_folded != NULL)
4189         return (g_strcmp0 (shverb1->executable_folded,
4190                            shverb2->executable_folded)) == 0;
4191     }
4192
4193   return app1 == app2;
4194 }
4195
4196 static const char *
4197 g_win32_app_info_get_id (GAppInfo *appinfo)
4198 {
4199   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4200   GWin32AppInfoShellVerb *shverb;
4201
4202   if (info->app == NULL)
4203     return NULL;
4204
4205   if (info->app->canonical_name_u8)
4206     return info->app->canonical_name_u8;
4207
4208   if (info->app->verbs->len > 0 &&
4209       (shverb = _verb_idx (info->app->verbs, 0))->executable_basename != NULL)
4210     return shverb->executable_basename;
4211
4212   return NULL;
4213 }
4214
4215 static const char *
4216 g_win32_app_info_get_name (GAppInfo *appinfo)
4217 {
4218   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4219
4220   if (info->app && info->app->pretty_name_u8)
4221     return info->app->pretty_name_u8;
4222   else if (info->app && info->app->canonical_name_u8)
4223     return info->app->canonical_name_u8;
4224   else
4225     return P_("Unnamed");
4226 }
4227
4228 static const char *
4229 g_win32_app_info_get_display_name (GAppInfo *appinfo)
4230 {
4231   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4232
4233   if (info->app)
4234     {
4235       if (info->app->localized_pretty_name_u8)
4236         return info->app->localized_pretty_name_u8;
4237       else if (info->app->pretty_name_u8)
4238         return info->app->pretty_name_u8;
4239     }
4240
4241   return g_win32_app_info_get_name (appinfo);
4242 }
4243
4244 static const char *
4245 g_win32_app_info_get_description (GAppInfo *appinfo)
4246 {
4247   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4248
4249   if (info->app == NULL)
4250     return NULL;
4251
4252   return info->app->description_u8;
4253 }
4254
4255 static const char *
4256 g_win32_app_info_get_executable (GAppInfo *appinfo)
4257 {
4258   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4259
4260   if (info->app == NULL)
4261     return NULL;
4262
4263   if (info->app->verbs->len > 0 && !info->app->is_uwp)
4264     return _verb_idx (info->app->verbs, 0)->executable;
4265
4266   return NULL;
4267 }
4268
4269 static const char *
4270 g_win32_app_info_get_commandline (GAppInfo *appinfo)
4271 {
4272   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4273
4274   if (info->app == NULL)
4275     return NULL;
4276
4277   if (info->app->verbs->len > 0 && !info->app->is_uwp)
4278     return _verb_idx (info->app->verbs, 0)->command_utf8;
4279
4280   return NULL;
4281 }
4282
4283 static GIcon *
4284 g_win32_app_info_get_icon (GAppInfo *appinfo)
4285 {
4286   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4287
4288   if (info->app == NULL)
4289     return NULL;
4290
4291   return info->app->icon;
4292 }
4293
4294 typedef struct _file_or_uri {
4295   gchar *uri;
4296   gchar *file;
4297 } file_or_uri;
4298
4299 static char *
4300 expand_macro_single (char macro, file_or_uri *obj)
4301 {
4302   char *result = NULL;
4303
4304   switch (macro)
4305     {
4306     case '*':
4307     case '0':
4308     case '1':
4309     case 'l':
4310     case 'd':
4311     case '2':
4312     case '3':
4313     case '4':
4314     case '5':
4315     case '6':
4316     case '7':
4317     case '8':
4318     case '9':
4319       /* TODO: handle 'l' and 'd' differently (longname and desktop name) */
4320       if (obj->uri)
4321         result = g_strdup (obj->uri);
4322       else if (obj->file)
4323         result = g_strdup (obj->file);
4324       break;
4325     case 'u':
4326     case 'U':
4327       if (obj->uri)
4328         result = g_shell_quote (obj->uri);
4329       break;
4330     case 'f':
4331     case 'F':
4332       if (obj->file)
4333         result = g_shell_quote (obj->file);
4334       break;
4335     }
4336
4337   return result;
4338 }
4339
4340 static gboolean
4341 expand_macro (char               macro,
4342               GString           *exec,
4343               GWin32AppInfo     *info,
4344               GList            **stat_obj_list,
4345               GList            **obj_list)
4346 {
4347   GList *objs = *obj_list;
4348   char *expanded;
4349   gboolean result = FALSE;
4350
4351   g_return_val_if_fail (exec != NULL, FALSE);
4352
4353 /*
4354 Legend: (from http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101%28v=vs.85%29.aspx)
4355 %* - replace with all parameters
4356 %~ - replace with all parameters starting with and following the second parameter
4357 %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).
4358 %<n> (where N is 2 - 9), replace with the nth parameter
4359 %s - show command
4360 %h - hotkey value
4361 %i - IDList stored in a shared memory handle is passed here.
4362 %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.
4363 %d - desktop absolute parsing name of the first parameter (for items that don't have file system paths)
4364 %v - for verbs that are none implies all, if there is no parameter passed this is the working directory
4365 %w - the working directory
4366 */
4367
4368   switch (macro)
4369     {
4370     case '*':
4371     case '~':
4372       if (*stat_obj_list)
4373         {
4374           gint i;
4375           GList *o;
4376
4377           for (o = *stat_obj_list, i = 0;
4378                macro == '~' && o && i < 2;
4379                o = o->next, i++);
4380
4381           for (; o; o = o->next)
4382             {
4383               expanded = expand_macro_single (macro, o->data);
4384
4385               if (expanded)
4386                 {
4387                   if (o != *stat_obj_list)
4388                     g_string_append (exec, " ");
4389
4390                   g_string_append (exec, expanded);
4391                   g_free (expanded);
4392                 }
4393             }
4394
4395           objs = NULL;
4396           result = TRUE;
4397         }
4398       break;
4399     case '0':
4400     case '1':
4401     case 'l':
4402     case 'd':
4403       if (*stat_obj_list)
4404         {
4405           GList *o;
4406
4407           o = *stat_obj_list;
4408
4409           if (o)
4410             {
4411               expanded = expand_macro_single (macro, o->data);
4412
4413               if (expanded)
4414                 {
4415                   if (o != *stat_obj_list)
4416                     g_string_append (exec, " ");
4417
4418                   g_string_append (exec, expanded);
4419                   g_free (expanded);
4420                 }
4421             }
4422
4423           if (objs)
4424             objs = objs->next;
4425
4426           result = TRUE;
4427         }
4428       break;
4429     case '2':
4430     case '3':
4431     case '4':
4432     case '5':
4433     case '6':
4434     case '7':
4435     case '8':
4436     case '9':
4437       if (*stat_obj_list)
4438         {
4439           gint i;
4440           GList *o;
4441           gint n;
4442
4443           switch (macro)
4444             {
4445             case '2':
4446               n = 2;
4447               break;
4448             case '3':
4449               n = 3;
4450               break;
4451             case '4':
4452               n = 4;
4453               break;
4454             case '5':
4455               n = 5;
4456               break;
4457             case '6':
4458               n = 6;
4459               break;
4460             case '7':
4461               n = 7;
4462               break;
4463             case '8':
4464               n = 8;
4465               break;
4466             case '9':
4467               n = 9;
4468               break;
4469             }
4470
4471           for (o = *stat_obj_list, i = 0; o && i < n; o = o->next, i++);
4472
4473           if (o)
4474             {
4475               expanded = expand_macro_single (macro, o->data);
4476
4477               if (expanded)
4478                 {
4479                   if (o != *stat_obj_list)
4480                     g_string_append (exec, " ");
4481
4482                   g_string_append (exec, expanded);
4483                   g_free (expanded);
4484                 }
4485             }
4486           result = TRUE;
4487
4488           if (objs)
4489             objs = NULL;
4490         }
4491       break;
4492     case 's':
4493       break;
4494     case 'h':
4495       break;
4496     case 'i':
4497       break;
4498     case 'v':
4499       break;
4500     case 'w':
4501       expanded = g_get_current_dir ();
4502       g_string_append (exec, expanded);
4503       g_free (expanded);
4504       break;
4505     case 'u':
4506     case 'f':
4507       if (objs)
4508         {
4509           expanded = expand_macro_single (macro, objs->data);
4510
4511           if (expanded)
4512             {
4513               g_string_append (exec, expanded);
4514               g_free (expanded);
4515             }
4516           objs = objs->next;
4517           result = TRUE;
4518         }
4519
4520       break;
4521
4522     case 'U':
4523     case 'F':
4524       while (objs)
4525         {
4526           expanded = expand_macro_single (macro, objs->data);
4527
4528           if (expanded)
4529             {
4530               g_string_append (exec, expanded);
4531               g_free (expanded);
4532             }
4533
4534           objs = objs->next;
4535           result = TRUE;
4536
4537           if (objs != NULL && expanded)
4538             g_string_append_c (exec, ' ');
4539         }
4540
4541       break;
4542
4543     case 'c':
4544       if (info->app && info->app->localized_pretty_name_u8)
4545         {
4546           expanded = g_shell_quote (info->app->localized_pretty_name_u8);
4547           g_string_append (exec, expanded);
4548           g_free (expanded);
4549         }
4550       break;
4551
4552     case 'm': /* deprecated */
4553     case 'n': /* deprecated */
4554     case 'N': /* deprecated */
4555     /*case 'd': *//* deprecated */
4556     case 'D': /* deprecated */
4557       break;
4558
4559     case '%':
4560       g_string_append_c (exec, '%');
4561       break;
4562     }
4563
4564   *obj_list = objs;
4565
4566   return result;
4567 }
4568
4569 static gboolean
4570 expand_application_parameters (GWin32AppInfo   *info,
4571                                const gchar     *exec_line,
4572                                GList          **objs,
4573                                int             *argc,
4574                                char          ***argv,
4575                                GError         **error)
4576 {
4577   GList *obj_list = *objs;
4578   GList **stat_obj_list = objs;
4579   const char *p = exec_line;
4580   GString *expanded_exec;
4581   gboolean res;
4582   gchar *a_char;
4583
4584   expanded_exec = g_string_new (NULL);
4585   res = FALSE;
4586
4587   while (*p)
4588     {
4589       if (p[0] == '%' && p[1] != '\0')
4590         {
4591           if (expand_macro (p[1],
4592                             expanded_exec,
4593                             info, stat_obj_list,
4594                             objs))
4595             res = TRUE;
4596
4597           p++;
4598         }
4599       else
4600         g_string_append_c (expanded_exec, *p);
4601
4602       p++;
4603     }
4604
4605   /* No file substitutions */
4606   if (obj_list == *objs && obj_list != NULL && !res)
4607     {
4608       /* If there is no macro default to %f. This is also what KDE does */
4609       g_string_append_c (expanded_exec, ' ');
4610       expand_macro ('f', expanded_exec, info, stat_obj_list, objs);
4611     }
4612
4613   /* Replace '\\' with '/', because g_shell_parse_argv considers them
4614    * to be escape sequences.
4615    */
4616   for (a_char = expanded_exec->str;
4617        a_char <= &expanded_exec->str[expanded_exec->len];
4618        a_char++)
4619     {
4620       if (*a_char == '\\')
4621         *a_char = '/';
4622     }
4623
4624   res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
4625   g_string_free (expanded_exec, TRUE);
4626   return res;
4627 }
4628
4629
4630 static gchar *
4631 get_appath_for_exe (const gchar *exe_basename)
4632 {
4633   GWin32RegistryKey *apppath_key = NULL;
4634   GWin32RegistryValueType val_type;
4635   gchar *appath = NULL;
4636   gboolean got_value;
4637   gchar *key_path = g_strdup_printf ("HKEY_LOCAL_MACHINE\\"
4638                                      "SOFTWARE\\"
4639                                      "Microsoft\\"
4640                                      "Windows\\"
4641                                      "CurrentVersion\\"
4642                                      "App Paths\\"
4643                                      "%s", exe_basename);
4644
4645   apppath_key = g_win32_registry_key_new (key_path, NULL);
4646   g_clear_pointer (&key_path, g_free);
4647
4648   if (apppath_key == NULL)
4649     return NULL;
4650
4651   got_value = g_win32_registry_key_get_value (apppath_key,
4652                                               NULL,
4653                                               TRUE,
4654                                               "Path",
4655                                               &val_type,
4656                                               (void **) &appath,
4657                                               NULL,
4658                                               NULL);
4659
4660   g_object_unref (apppath_key);
4661
4662   if (got_value &&
4663       val_type == G_WIN32_REGISTRY_VALUE_STR)
4664     return appath;
4665
4666   g_clear_pointer (&appath, g_free);
4667
4668   return appath;
4669 }
4670
4671
4672 static gboolean
4673 g_win32_app_info_launch_uwp_internal (GWin32AppInfo           *info,
4674                                       gboolean                 for_files,
4675                                       IShellItemArray         *items,
4676                                       GWin32AppInfoShellVerb  *shverb,
4677                                       GError                 **error)
4678 {
4679   DWORD pid;
4680   IApplicationActivationManager* paam = NULL;
4681   gboolean result = TRUE;
4682   HRESULT hr;
4683
4684   hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL, CLSCTX_INPROC_SERVER, &IID_IApplicationActivationManager, (void **) &paam);
4685   if (FAILED (hr))
4686     {
4687       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4688                    "Failed to create ApplicationActivationManager: 0x%lx", hr);
4689       return FALSE;
4690     }
4691
4692   if (items == NULL)
4693     hr = IApplicationActivationManager_ActivateApplication (paam, (const wchar_t *) info->app->canonical_name, NULL, AO_NONE, &pid);
4694   else if (for_files)
4695     hr = IApplicationActivationManager_ActivateForFile (paam, (const wchar_t *) info->app->canonical_name, items, shverb->verb_name, &pid);
4696   else
4697     hr = IApplicationActivationManager_ActivateForProtocol (paam, (const wchar_t *) info->app->canonical_name, items, &pid);
4698
4699   if (FAILED (hr))
4700     {
4701       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4702                    "The app %s failed to launch: 0x%lx",
4703                    g_win32_appinfo_application_get_some_name (info->app), hr);
4704       result = FALSE;
4705     }
4706
4707   IApplicationActivationManager_Release (paam);
4708
4709   return result;
4710 }
4711
4712
4713 static gboolean
4714 g_win32_app_info_launch_internal (GWin32AppInfo      *info,
4715                                   GList              *objs, /* non-UWP only */
4716                                   gboolean            for_files, /* UWP only */
4717                                   IShellItemArray    *items, /* UWP only */
4718                                   GAppLaunchContext  *launch_context,
4719                                   GSpawnFlags         spawn_flags,
4720                                   GError            **error)
4721 {
4722   gboolean completed = FALSE;
4723   char **argv, **envp;
4724   int argc;
4725   const gchar *command;
4726   gchar *apppath;
4727   GWin32AppInfoShellVerb *shverb;
4728
4729   g_return_val_if_fail (info != NULL, FALSE);
4730   g_return_val_if_fail (info->app != NULL, FALSE);
4731
4732   argv = NULL;
4733   shverb = NULL;
4734
4735   if (!info->app->is_uwp &&
4736       info->handler != NULL &&
4737       info->handler->verbs->len > 0)
4738     shverb = _verb_idx (info->handler->verbs, 0);
4739   else if (info->app->verbs->len > 0)
4740     shverb = _verb_idx (info->app->verbs, 0);
4741
4742   if (shverb == NULL)
4743     {
4744       if (info->app->is_uwp || info->handler == NULL)
4745         g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4746                      P_("The app ‘%s’ in the application object has no verbs"),
4747                      g_win32_appinfo_application_get_some_name (info->app));
4748       else if (info->handler->verbs->len == 0)
4749         g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4750                      P_("The app ‘%s’ and the handler ‘%s’ in the application object have no verbs"),
4751                      g_win32_appinfo_application_get_some_name (info->app),
4752                      info->handler->handler_id_folded);
4753
4754       return FALSE;
4755     }
4756
4757   if (info->app->is_uwp)
4758     return g_win32_app_info_launch_uwp_internal (info,
4759                                                  for_files,
4760                                                  items,
4761                                                  shverb,
4762                                                  error);
4763
4764   if (launch_context)
4765     envp = g_app_launch_context_get_environment (launch_context);
4766   else
4767     envp = g_get_environ ();
4768
4769   g_assert (shverb->command_utf8 != NULL);
4770   command = shverb->command_utf8;
4771   apppath = get_appath_for_exe (shverb->executable_basename);
4772
4773   if (apppath)
4774     {
4775       gchar **p;
4776       gint p_index;
4777
4778       for (p = envp, p_index = 0; p[0]; p++, p_index++)
4779         if ((p[0][0] == 'p' || p[0][0] == 'P') &&
4780             (p[0][1] == 'a' || p[0][1] == 'A') &&
4781             (p[0][2] == 't' || p[0][2] == 'T') &&
4782             (p[0][3] == 'h' || p[0][3] == 'H') &&
4783             (p[0][4] == '='))
4784           break;
4785
4786       if (p[0] == NULL)
4787         {
4788           gchar **new_envp;
4789           new_envp = g_new (char *, g_strv_length (envp) + 2);
4790           new_envp[0] = g_strdup_printf ("PATH=%s", apppath);
4791
4792           for (p_index = 0; p_index <= g_strv_length (envp); p_index++)
4793             new_envp[1 + p_index] = envp[p_index];
4794
4795           g_free (envp);
4796           envp = new_envp;
4797         }
4798       else
4799         {
4800           gchar *p_path;
4801
4802           p_path = &p[0][5];
4803
4804           if (p_path[0] != '\0')
4805             envp[p_index] = g_strdup_printf ("PATH=%s%c%s",
4806                                              apppath,
4807                                              G_SEARCHPATH_SEPARATOR,
4808                                              p_path);
4809           else
4810             envp[p_index] = g_strdup_printf ("PATH=%s", apppath);
4811
4812           g_free (&p_path[-5]);
4813         }
4814     }
4815
4816   do
4817     {
4818       GPid pid;
4819
4820       if (!expand_application_parameters (info,
4821                                           command,
4822                                           &objs,
4823                                           &argc,
4824                                           &argv,
4825                                           error))
4826         goto out;
4827
4828       if (!g_spawn_async (NULL,
4829                           argv,
4830                           envp,
4831                           spawn_flags,
4832                           NULL,
4833                           NULL,
4834                           &pid,
4835                           error))
4836           goto out;
4837
4838       if (launch_context != NULL)
4839         {
4840           GVariantBuilder builder;
4841           GVariant *platform_data;
4842
4843           g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
4844           g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gint32) pid));
4845
4846           platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
4847           g_signal_emit_by_name (launch_context, "launched", info, platform_data);
4848           g_variant_unref (platform_data);
4849         }
4850
4851       g_strfreev (argv);
4852       argv = NULL;
4853     }
4854   while (objs != NULL);
4855
4856   completed = TRUE;
4857
4858  out:
4859   g_strfreev (argv);
4860   g_strfreev (envp);
4861
4862   return completed;
4863 }
4864
4865 static void
4866 free_file_or_uri (gpointer ptr)
4867 {
4868   file_or_uri *obj = ptr;
4869   g_free (obj->file);
4870   g_free (obj->uri);
4871   g_free (obj);
4872 }
4873
4874
4875 static gboolean
4876 g_win32_app_supports_uris (GWin32AppInfoApplication *app)
4877 {
4878   gssize num_of_uris_supported;
4879
4880   if (app == NULL)
4881     return FALSE;
4882
4883   num_of_uris_supported = (gssize) g_hash_table_size (app->supported_urls);
4884
4885   if (g_hash_table_lookup (app->supported_urls, "file"))
4886     num_of_uris_supported -= 1;
4887
4888   return num_of_uris_supported > 0;
4889 }
4890
4891
4892 static gboolean
4893 g_win32_app_info_supports_uris (GAppInfo *appinfo)
4894 {
4895   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4896
4897   if (info->app == NULL)
4898     return FALSE;
4899
4900   return g_win32_app_supports_uris (info->app);
4901 }
4902
4903
4904 static gboolean
4905 g_win32_app_info_supports_files (GAppInfo *appinfo)
4906 {
4907   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
4908
4909   if (info->app == NULL)
4910     return FALSE;
4911
4912   return g_hash_table_size (info->app->supported_exts) > 0;
4913 }
4914
4915
4916 static IShellItemArray *
4917 make_item_array (gboolean   for_files,
4918                  GList     *files_or_uris,
4919                  GError   **error)
4920 {
4921   ITEMIDLIST **item_ids;
4922   IShellItemArray *items;
4923   GList *p;
4924   gsize count;
4925   gsize i;
4926   HRESULT hr;
4927
4928   count = g_list_length (files_or_uris);
4929
4930   items = NULL;
4931   item_ids = g_new (ITEMIDLIST*, count);
4932
4933   for (i = 0, p = files_or_uris; p != NULL; p = p->next, i++)
4934     {
4935       wchar_t *file_or_uri_utf16;
4936
4937       if (!for_files)
4938         file_or_uri_utf16 = g_utf8_to_utf16 ((gchar *) p->data, -1, NULL, NULL, error);
4939       else
4940         file_or_uri_utf16 = g_utf8_to_utf16 (g_file_peek_path (G_FILE (p->data)), -1, NULL, NULL, error);
4941
4942       if (file_or_uri_utf16 == NULL)
4943         break;
4944
4945       if (for_files)
4946         {
4947           wchar_t *c;
4948           gsize len;
4949           gsize len_tail;
4950
4951           len = wcslen (file_or_uri_utf16);
4952           /* Filenames *MUST* use single backslashes, else the call
4953            * will fail. First convert all slashes to backslashes,
4954            * then remove duplicates.
4955            */
4956           for (c = file_or_uri_utf16; for_files && *c != 0; c++)
4957             {
4958               if (*c == L'/')
4959                 *c = L'\\';
4960             }
4961           for (len_tail = 0, c = &file_or_uri_utf16[len - 1];
4962                for_files && c > file_or_uri_utf16;
4963                c--, len_tail++)
4964             {
4965               if (c[0] != L'\\' || c[-1] != L'\\')
4966                 continue;
4967
4968               memmove (&c[-1], &c[0], len_tail * sizeof (wchar_t));
4969             }
4970         }
4971
4972       hr = SHParseDisplayName (file_or_uri_utf16, NULL, &item_ids[i], 0, NULL);
4973       g_free (file_or_uri_utf16);
4974
4975       if (FAILED (hr))
4976         {
4977           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4978                        "File or URI `%S' cannot be parsed by SHParseDisplayName: 0x%lx", file_or_uri_utf16, hr);
4979           break;
4980         }
4981     }
4982
4983   if (i == count)
4984     {
4985       hr = SHCreateShellItemArrayFromIDLists (count, (const ITEMIDLIST **) item_ids, &items);
4986       if (FAILED (hr))
4987         {
4988           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4989                        "SHCreateShellItemArrayFromIDLists() failed: 0x%lx", hr);
4990           items = NULL;
4991         }
4992     }
4993
4994   count = i;
4995
4996   for (i = 0; i < count; i++)
4997     CoTaskMemFree (item_ids[i]);
4998
4999   g_free (item_ids);
5000
5001   return items;
5002 }
5003
5004
5005 static gboolean
5006 g_win32_app_info_launch_uris (GAppInfo           *appinfo,
5007                               GList              *uris,
5008                               GAppLaunchContext  *launch_context,
5009                               GError            **error)
5010 {
5011   gboolean res = FALSE;
5012   gboolean do_files;
5013   GList *objs;
5014   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5015
5016   if (info->app != NULL && info->app->is_uwp)
5017     {
5018       IShellItemArray *items = NULL;
5019
5020       if (uris)
5021         {
5022           items = make_item_array (FALSE, uris, error);
5023           if (items == NULL)
5024             return res;
5025         }
5026
5027       res = g_win32_app_info_launch_internal (info, NULL, FALSE, items, launch_context, 0, error);
5028
5029       if (items != NULL)
5030         IShellItemArray_Release (items);
5031
5032       return res;
5033     }
5034
5035   do_files = g_win32_app_info_supports_files (appinfo);
5036
5037   objs = NULL;
5038   while (uris)
5039     {
5040       file_or_uri *obj;
5041       obj = g_new0 (file_or_uri, 1);
5042
5043       if (do_files)
5044         {
5045           GFile *file;
5046           gchar *path;
5047
5048           file = g_file_new_for_uri (uris->data);
5049           path = g_file_get_path (file);
5050           obj->file = path;
5051           g_object_unref (file);
5052         }
5053
5054       obj->uri = g_strdup (uris->data);
5055
5056       objs = g_list_prepend (objs, obj);
5057       uris = uris->next;
5058     }
5059
5060   objs = g_list_reverse (objs);
5061
5062   res = g_win32_app_info_launch_internal (info,
5063                                           objs,
5064                                           FALSE,
5065                                           NULL,
5066                                           launch_context,
5067                                           G_SPAWN_SEARCH_PATH,
5068                                           error);
5069
5070   g_list_free_full (objs, free_file_or_uri);
5071
5072   return res;
5073 }
5074
5075 static gboolean
5076 g_win32_app_info_launch (GAppInfo           *appinfo,
5077                          GList              *files,
5078                          GAppLaunchContext  *launch_context,
5079                          GError            **error)
5080 {
5081   gboolean res = FALSE;
5082   gboolean do_uris;
5083   GList *objs;
5084   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5085
5086   if (info->app != NULL && info->app->is_uwp)
5087     {
5088       IShellItemArray *items = NULL;
5089
5090       if (files)
5091         {
5092           items = make_item_array (TRUE, files, error);
5093           if (items == NULL)
5094             return res;
5095         }
5096
5097       res = g_win32_app_info_launch_internal (info, NULL, TRUE, items, launch_context, 0, error);
5098
5099       if (items != NULL)
5100         IShellItemArray_Release (items);
5101
5102       return res;
5103     }
5104
5105   do_uris = g_win32_app_info_supports_uris (appinfo);
5106
5107   objs = NULL;
5108   while (files)
5109     {
5110       file_or_uri *obj;
5111       obj = g_new0 (file_or_uri, 1);
5112       obj->file = g_file_get_path (G_FILE (files->data));
5113
5114       if (do_uris)
5115         obj->uri = g_file_get_uri (G_FILE (files->data));
5116
5117       objs = g_list_prepend (objs, obj);
5118       files = files->next;
5119     }
5120
5121   objs = g_list_reverse (objs);
5122
5123   res = g_win32_app_info_launch_internal (info,
5124                                           objs,
5125                                           TRUE,
5126                                           NULL,
5127                                           launch_context,
5128                                           G_SPAWN_SEARCH_PATH,
5129                                           error);
5130
5131   g_list_free_full (objs, free_file_or_uri);
5132
5133   return res;
5134 }
5135
5136 static const char **
5137 g_win32_app_info_get_supported_types (GAppInfo *appinfo)
5138 {
5139   GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo);
5140
5141   return (const char**) info->supported_types;
5142 }
5143
5144 GAppInfo *
5145 g_app_info_create_from_commandline (const char           *commandline,
5146                                     const char           *application_name,
5147                                     GAppInfoCreateFlags   flags,
5148                                     GError              **error)
5149 {
5150   GWin32AppInfo *info;
5151   GWin32AppInfoApplication *app;
5152   gunichar2 *app_command;
5153
5154   g_return_val_if_fail (commandline, NULL);
5155
5156   app_command = g_utf8_to_utf16 (commandline, -1, NULL, NULL, NULL);
5157
5158   if (app_command == NULL)
5159     return NULL;
5160
5161   info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL);
5162   app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL);
5163
5164   app->no_open_with = FALSE;
5165   app->user_specific = FALSE;
5166   app->default_app = FALSE;
5167
5168   if (application_name)
5169     {
5170       app->canonical_name = g_utf8_to_utf16 (application_name,
5171                                              -1,
5172                                              NULL,
5173                                              NULL,
5174                                              NULL);
5175       app->canonical_name_u8 = g_strdup (application_name);
5176       app->canonical_name_folded = g_utf8_casefold (application_name, -1);
5177     }
5178
5179   app_add_verb (app,
5180                 app,
5181                 L"open",
5182                 app_command,
5183                 commandline,
5184                 "open",
5185                 TRUE,
5186                 FALSE);
5187
5188   g_clear_pointer (&app_command, g_free);
5189   info->app = app;
5190   info->handler = NULL;
5191
5192   return G_APP_INFO (info);
5193 }
5194
5195 /* GAppInfo interface init */
5196
5197 static void
5198 g_win32_app_info_iface_init (GAppInfoIface *iface)
5199 {
5200   iface->dup = g_win32_app_info_dup;
5201   iface->equal = g_win32_app_info_equal;
5202   iface->get_id = g_win32_app_info_get_id;
5203   iface->get_name = g_win32_app_info_get_name;
5204   iface->get_description = g_win32_app_info_get_description;
5205   iface->get_executable = g_win32_app_info_get_executable;
5206   iface->get_icon = g_win32_app_info_get_icon;
5207   iface->launch = g_win32_app_info_launch;
5208   iface->supports_uris = g_win32_app_info_supports_uris;
5209   iface->supports_files = g_win32_app_info_supports_files;
5210   iface->launch_uris = g_win32_app_info_launch_uris;
5211 /*  iface->should_show = g_win32_app_info_should_show;*/
5212 /*  iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;*/
5213 /*  iface->set_as_default_for_extension = g_win32_app_info_set_as_default_for_extension;*/
5214 /*  iface->add_supports_type = g_win32_app_info_add_supports_type;*/
5215 /*  iface->can_remove_supports_type = g_win32_app_info_can_remove_supports_type;*/
5216 /*  iface->remove_supports_type = g_win32_app_info_remove_supports_type;*/
5217 /*  iface->can_delete = g_win32_app_info_can_delete;*/
5218 /*  iface->do_delete = g_win32_app_info_delete;*/
5219   iface->get_commandline = g_win32_app_info_get_commandline;
5220   iface->get_display_name = g_win32_app_info_get_display_name;
5221 /*  iface->set_as_last_used_for_type = g_win32_app_info_set_as_last_used_for_type;*/
5222   iface->get_supported_types = g_win32_app_info_get_supported_types;
5223 }
5224
5225 GAppInfo *
5226 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
5227 {
5228   GWin32AppInfoURLSchema *scheme = NULL;
5229   char *scheme_down;
5230   GAppInfo *result;
5231   GWin32AppInfoShellVerb *shverb;
5232
5233   scheme_down = g_utf8_casefold (uri_scheme, -1);
5234
5235   if (!scheme_down)
5236     return NULL;
5237
5238   if (strcmp (scheme_down, "file") == 0)
5239     {
5240       g_free (scheme_down);
5241
5242       return NULL;
5243     }
5244
5245   gio_win32_appinfo_init (TRUE);
5246   g_mutex_lock (&gio_win32_appinfo_mutex);
5247
5248   g_set_object (&scheme, g_hash_table_lookup (urls, scheme_down));
5249   g_free (scheme_down);
5250
5251   g_mutex_unlock (&gio_win32_appinfo_mutex);
5252
5253   result = NULL;
5254
5255   if (scheme != NULL &&
5256       scheme->chosen_handler != NULL &&
5257       scheme->chosen_handler->verbs->len > 0 &&
5258       (shverb = _verb_idx (scheme->chosen_handler->verbs, 0))->app != NULL)
5259     result = g_win32_app_info_new_from_app (shverb->app,
5260                                             scheme->chosen_handler);
5261
5262   g_clear_object (&scheme);
5263
5264   return result;
5265 }
5266
5267 GAppInfo *
5268 g_app_info_get_default_for_type (const char *content_type,
5269                                  gboolean    must_support_uris)
5270 {
5271   GWin32AppInfoFileExtension *ext = NULL;
5272   char *ext_down;
5273   GAppInfo *result;
5274   GWin32AppInfoShellVerb *shverb;
5275
5276   ext_down = g_utf8_casefold (content_type, -1);
5277
5278   if (!ext_down)
5279     return NULL;
5280
5281   gio_win32_appinfo_init (TRUE);
5282   g_mutex_lock (&gio_win32_appinfo_mutex);
5283
5284   /* Assuming that "content_type" is a file extension, not a MIME type */
5285   g_set_object (&ext, g_hash_table_lookup (extensions, ext_down));
5286   g_free (ext_down);
5287
5288   g_mutex_unlock (&gio_win32_appinfo_mutex);
5289
5290   if (ext == NULL)
5291     return NULL;
5292
5293   result = NULL;
5294
5295   if (ext->chosen_handler != NULL &&
5296       ext->chosen_handler->verbs->len > 0 &&
5297       (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL &&
5298       (!must_support_uris ||
5299        g_win32_app_supports_uris (shverb->app)))
5300     result = g_win32_app_info_new_from_app (shverb->app,
5301                                             ext->chosen_handler);
5302   else
5303     {
5304       GHashTableIter iter;
5305       GWin32AppInfoHandler *handler;
5306
5307       g_hash_table_iter_init (&iter, ext->handlers);
5308
5309       while (result == NULL &&
5310              g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5311         {
5312           if (handler->verbs->len == 0)
5313             continue;
5314
5315           shverb = _verb_idx (handler->verbs, 0);
5316
5317           if (shverb->app &&
5318               (!must_support_uris ||
5319                g_win32_app_supports_uris (shverb->app)))
5320             result = g_win32_app_info_new_from_app (shverb->app, handler);
5321         }
5322     }
5323
5324   g_clear_object (&ext);
5325
5326   return result;
5327 }
5328
5329 GList *
5330 g_app_info_get_all (void)
5331 {
5332   GHashTableIter iter;
5333   gpointer value;
5334   GList *infos;
5335   GList *apps;
5336   GList *apps_i;
5337
5338   gio_win32_appinfo_init (TRUE);
5339   g_mutex_lock (&gio_win32_appinfo_mutex);
5340
5341   apps = NULL;
5342   g_hash_table_iter_init (&iter, apps_by_id);
5343   while (g_hash_table_iter_next (&iter, NULL, &value))
5344     apps = g_list_prepend (apps, g_object_ref (G_OBJECT (value)));
5345
5346   g_mutex_unlock (&gio_win32_appinfo_mutex);
5347
5348   infos = NULL;
5349   for (apps_i = apps; apps_i; apps_i = apps_i->next)
5350     infos = g_list_prepend (infos,
5351                             g_win32_app_info_new_from_app (apps_i->data, NULL));
5352
5353   g_list_free_full (apps, g_object_unref);
5354
5355   return infos;
5356 }
5357
5358 GList *
5359 g_app_info_get_all_for_type (const char *content_type)
5360 {
5361   GWin32AppInfoFileExtension *ext = NULL;
5362   char *ext_down;
5363   GWin32AppInfoHandler *handler;
5364   GHashTableIter iter;
5365   GHashTable *apps = NULL;
5366   GList *result;
5367   GWin32AppInfoShellVerb *shverb;
5368
5369   ext_down = g_utf8_casefold (content_type, -1);
5370
5371   if (!ext_down)
5372     return NULL;
5373
5374   gio_win32_appinfo_init (TRUE);
5375   g_mutex_lock (&gio_win32_appinfo_mutex);
5376
5377   /* Assuming that "content_type" is a file extension, not a MIME type */
5378   g_set_object (&ext, g_hash_table_lookup (extensions, ext_down));
5379   g_free (ext_down);
5380
5381   g_mutex_unlock (&gio_win32_appinfo_mutex);
5382
5383   if (ext == NULL)
5384     return NULL;
5385
5386   result = NULL;
5387   /* Used as a set to ensure uniqueness */
5388   apps = g_hash_table_new (g_direct_hash, g_direct_equal);
5389
5390   if (ext->chosen_handler != NULL &&
5391       ext->chosen_handler->verbs->len > 0 &&
5392       (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL)
5393     {
5394       g_hash_table_add (apps, shverb->app);
5395       result = g_list_prepend (result,
5396                                g_win32_app_info_new_from_app (shverb->app,
5397                                                               ext->chosen_handler));
5398     }
5399
5400   g_hash_table_iter_init (&iter, ext->handlers);
5401
5402   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler))
5403     {
5404       gsize vi;
5405
5406       for (vi = 0; vi < handler->verbs->len; vi++)
5407         {
5408           shverb = _verb_idx (handler->verbs, vi);
5409
5410           if (shverb->app == NULL ||
5411               g_hash_table_contains (apps, shverb->app))
5412             continue;
5413
5414           g_hash_table_add (apps, shverb->app);
5415           result = g_list_prepend (result,
5416                                    g_win32_app_info_new_from_app (shverb->app,
5417                                                                   handler));
5418         }
5419     }
5420
5421   g_clear_object (&ext);
5422   result = g_list_reverse (result);
5423   g_hash_table_unref (apps);
5424
5425   return result;
5426 }
5427
5428 GList *
5429 g_app_info_get_fallback_for_type (const gchar *content_type)
5430 {
5431   /* TODO: fix this once gcontenttype support is improved */
5432   return g_app_info_get_all_for_type (content_type);
5433 }
5434
5435 GList *
5436 g_app_info_get_recommended_for_type (const gchar *content_type)
5437 {
5438   /* TODO: fix this once gcontenttype support is improved */
5439   return g_app_info_get_all_for_type (content_type);
5440 }
5441
5442 void
5443 g_app_info_reset_type_associations (const char *content_type)
5444 {
5445   /* nothing to do */
5446 }