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