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