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