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